https://school.programmers.co.kr/learn/courses/30/lessons/12902

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

문제를 봤을 때 DP로 풀면 좋을 것 같다고 생각을 했다. 그러려면 일반 점화식을  도출해내야한다. 

문제를 생각해보면 짝수에서만 가능하다는 것을 알 수 있다. 그리고 일반적인 경우를 생각해보자면

 

  • 인 경우:
    • 타일을 채울 수 없는 경우로, 한 가지 방법이 존재-> 아무것도 하지 않는 방법.
  • 인 경우:
    • 가로로 타일 3개 배치
    • 세로로 2개를 위로 쌓아 배치
    • 세로로 2개를 아래로 쌓아 배치 
  • 인 경우:
    • 크기의 바닥을 두 번 채우는 경우와 유사하게 생각할 수 있다.

이제 점화식을 도출해보자 

크기의 바닥을 채우는 방법을 찾기 위해 혹은 그 이하의 크기에서 타일을 어떻게 배치하는지 생각해보자

n의 경우의 수를 구하려면 일단 n-2번째와 n-4.. 0이 될때까지의 경우의 수를 모두 더 해주어야한다. 이때 n-4의 패턴부터는 좌우 대칭적으로 배치할 수 있기 때문에 곱하기2를 해준다.

코드로 구현하면 다음과 같다

#include <string>
#include <vector>
#define MOD 1000000007
using namespace std;
//dp활용 
//짝수일때만 계산가능

int solution(int n) {
   if (n % 2 != 0) return 0; // 홀수일 경우 0 반환
    
    vector<long long> dp(n + 1, 0);
    dp[0] = 1; // base case
    dp[2] = 3; // 기본적인 2x3 타일 배치 방법의 수
    
    for (int i = 4; i <= n; i += 2) {
        dp[i] = dp[i - 2] * dp[2];
        for (int j = i - 4; j >= 0; j -= 2) {
            dp[i] += dp[j] * 2;
        }
        dp[i] %= MOD;
    }
    
    return dp[n];
}

 

 

이제 본격적으로 캐릭터를 움직이게 해보자

 

일단 새를 시점으로 플레이할 수 있도록 새의 auto possess player를 Player0으로 설정해주자

이것을 코드로 구현하게되면 아래와 같다.

	AutoPossessPlayer = EAutoReceiveInput::Player0;

1. 키바인딩

 

만약 컨트롤러가 2개이상이라면 첫번째 컨트롤러가 첫번째 플레이어 두번째 컨트롤러가 두번째 컨트롤러를 사용하게 된다.

그리고 이제 키를 Mapping해주자 키는 축 매핑에서 추가해주자

키 매핑
키 매핑과 함수연관성

키를 통해 움직이게 하는 콜백하려면 콜백 함수를 할당해주어야 한다.

콜백 함수를 붙여줄 때는 SetupPlayerInputComponent을 통해 한다.

이때 우리가 매핑해준 키의 이름을 잘 알고있어야한다. 지금은 MoveForWard를 매핑해볼 것이니 MoveForWard 이것을 잘 기억해주자

이 때 매핑된 키의 이름과 오브젝트, 콜백함수의 주소를 매개변수로 사용하면된다.

	PlayerInputComponent->BindAxis(FName("MoveForward"), this, &ABird::MoveForward);
void ABird::MoveForward(float Value)
{
	UE_LOG(LogTemp, Warning, TEXT("Value: %f"), Value);
}

지금은 로그를 통해 눌러지냐를 확인해 보았다.

 

 

하지만 이 것은 옛날 인풋시스템 방식으로 이제 Enhanced Input System방식으로 바꿔보자

Pawns폴더에 Input 폴더를 만들어주고 여기에 우클릭해서 입력/입력액션을 추가해주자 

 

그리고 이 입력 액션을 Bird와 이어줄 Input Mapping Context도 추가해주자

 

이 Input Mapping Context를 Bird 폰 클래스와 연결해주려면 일단 블루프린트에서는 다음과 같이 할 수 있다. 

일단 Get Controller와 cast를 통해 폰의 컨트롤러를 가져온다.

 

가져온 컨트롤러를 Enhanced Input 시스템을 통해 연결해주면 된다. 이때 Priority에 따라 여러 콘텍스트가 작동할지 정해진다. 

 

그리고 만들어둔 입력액션을 사용하려면 블루프린트에서 액션이벤트를 가져온 뒤 원하는 흐름을 추가해주면 된다.

 

 

이제 이 과정을 코드로 구현해보자 

일단 필요한 함수와 변수를 가져오기 위해 모듈을 가져오는 부분을 수정해주자

 

Slash.Build.cs

PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore","EnhancedInput" });

 

그리고 입력애션과 입력매핑컨텍스트 변수를 헤더파일에서 전방선언해주고 입력액션에 들어갈 함수도 선언해주자

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input)
	UInputMappingContext* BirdMappingContext;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input)
	UInputAction* MoveAction;

	//구조체는 헤더파일 가져와야한다.
	void Move(const FInputActionValue& Value);

 

이제 BeginPlay에서 입력컨텍스트에 클래스를 붙여주자

void ABird::BeginPlay()
{
	Super::BeginPlay();
	
	//조건문안에 넣는 것이 최적화상으로 좋다
	if (APlayerController* PlayerController = Cast<APlayerController>(GetController()))
	{
		if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem< UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
		{
			Subsystem->AddMappingContext(BirdMappingContext, 0);
		}
	}

}

 

또한 입력액션의 콜백함수도 완성해주자

void ABird::Move(const FInputActionValue& Value)
{
	const bool CurrentValue = Value.Get<bool>();
	if (CurrentValue)
	{
		UE_LOG(LogTemp, Warning, TEXT("Good"));
	}
}

이 콜백 함수를 입력액션에 바인딩 해주자

// Called to bind functionality to input
void ABird::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent))
	{
		EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &ABird::Move);
	}

}

 

마지막으로 블루프린트상에서 입력액션과 매핑컨텍스트를 초기화해주면 된다.

 

이제 본격적으로 폰을 움직여보자

 

일단 옛날 인풋방식으로 하는것을 보자

플레이어기준 앞을 향하는 벡터를 가지고와서 그방향으로 AddMovementInput을 해주면 된다.

void ABird::MoveForward(float Value)
{
	if (Controller && (Value != 0.f))
	{
		FVector Forward = GetActorForwardVector();
		AddMovementInput(Forward, Value);
	}
}

 

그리고 실제로 움직일 수 있게 블루프린트상에 FloatingPawnMovement을 추가해주자

움직이는 모습을 볼 수 있다.

이제 이것을 향상된 인풋시스템으로 바꿔보자

입력 액션에서 타입을 float로 바꿔주고 입력 매핑 컨텍스트에 w키도 받을 수 있게 추가해주자 

w키를 눌렀을 때는 w와 반대가 될 수 있도록 Modifiers에 Negate 하나 추가해주자

 

코드상에서 Value를 받아서 앞으로 움직이게 해주자 함수는 동일하게 AddMovementInput를 사용해주자

void ABird::Move(const FInputActionValue& Value)
{
	const float DirectionValue = Value.Get<float>();

	if (Controller && (DirectionValue != 0.f))
	{
		FVector Forward = GetActorForwardVector();
		AddMovementInput(Forward, DirectionValue);
	}
}

 

폰이 움직이는 것을 볼 수 있다.

 

지금상태에서는 어떻게 움직이는지 보이지 않기때문에 카메라를 조정해보도록하자

먼저 블루프린트에서 이 작업을 해보자

첫번째 방법은 카메라 컴포넌트를 루트아래에 두는 것이다. 위치와 각도를 조절하면 탑뷰형태로 볼 수 있다.

 

 

하지만 이런 방식보다는 스프링암을 활용하는 것이 좋다.

 

스프링암은 만약 카메라가 벽과 충돌하게 된다면 줄어들어서 벽 넘어 플레이어를 볼 수 있게 한다.

그리고 팔 길이와 각도도 자유롭게 변경가능하다. 카메라를 돌리는 대신 팔의 각도와 길이를 통해 카메라를 조정하는 효과를 얻는 것이다.

 

이것을 이제 코드로 구현해보자

우선 스프링암과 카메라를 전방선언을 통해 헤더파일에 선언해주고 cpp파일에서 서브오브젝트를 통해 초기화 해주자

Bird.h

	UPROPERTY(VisibleAnywhere)
	USpringArmComponent* SpringArm;

	UPROPERTY(VisibleAnywhere)
	UCameraComponent* ViewCamera;

 

Bird.cpp

SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
SpringArm->SetupAttachment(GetRootComponent());
SpringArm->TargetArmLength = 300.f;

ViewCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("ViewCamera"));
ViewCamera->SetupAttachment(SpringArm);

 

이후 핫리로딩을 해주고 각도를 조금 조정해주면 다음과 같은화면이 나오게된다.

 

이제 앞 뒤뿐만 아니라 원하는 방향으로 움직일 수 있게 해보자 이를 위해 마우스 움직임을 사용할 것이다.

입력 액션을 추가해주자 이때, 2D vector를 받을 수 있게하여 위아래 좌우를 모두 볼 수 있게하자

 

 

입력컨텍스트도 xy를 모두 받을 수 있게 추가해주자

 

이제 코드를 보자면

헤더파일에서는 LookAction에 추가될 액션포인터 변수를 선언해주고 콜백함수도 정의해주자

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input)
UInputAction* LookAction;

void Look(const FInputActionValue& Value);

 

cpp파일에서는 선언된 콜백함수를 완성해주고 바인딩해주자

void ABird::Look(const FInputActionValue& Value)
{
	const FVector2D LookAxisValue = Value.Get<FVector2D>();
	if (GetController())
	{
		AddControllerYawInput(LookAxisValue.X);
		AddControllerPitchInput(LookAxisValue.Y);
	}
}

// Called to bind functionality to input
void ABird::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent))
	{
		EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &ABird::Move);
		EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &ABird::Look);
	}

}

 

컴파일해준 뒤 블루프린트의 액션칸에 Look 입력액션을 추가해주고 컨트롤러 회전의 피치와 요를 사용할 수 있게 바꿔준다.

 

결과

 

입력컨텍스트에서 modifier을 조정하여 자연스럽게 날 수 있도록 조정하자

https://school.programmers.co.kr/learn/courses/30/lessons/87377

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

 

교점을 찾아서 사각형 격자판을 만들어야하는 문제이다.

어떻게 교점을 찾는지가 핵심이다.

이때는 행렬 방정식을 이용해보도록 하자.

#include <string>
#include <vector>
#include <set>
#include <algorithm>
#include <climits>

using namespace std;

vector<string> solution(vector<vector<int>> line) {
    set<pair<int, int>> points;          //교점 저장할 변수

    //교점계산
    for (int i = 0; i < line.size(); i++) {
        for (int j = i + 1; j < line.size(); j++) {
            long long a1 = line[i][0], b1 = line[i][1], c1 = line[i][2];
            long long a2 = line[j][0], b2 = line[j][1], c2 = line[j][2];
            long long inc = a1 * b2 - a2 * b1;              //행렬식
            if (inc == 0) continue;

            //교점계산
            long long x_num = b1 * c2 - b2 * c1;
            long long y_num = a2 * c1 - a1 * c2;

            //정수부분인지 계산
            if (x_num % inc == 0 && y_num % inc == 0) {
                int x = x_num / inc;
                int y = y_num / inc;
                points.insert({ x,y });
            }
        }
    }

    //격자판범위계산
    int min_x = INT_MAX, max_x = INT_MIN;
    int min_y = INT_MAX, max_y = INT_MIN;
    for (auto point : points) {
        min_x = min(min_x, point.first);
        max_x = max(max_x, point.first);
        min_y = min(min_y, point.second);
        max_y = max(max_y, point.second);
    }

    //격자판만들기
    int wid = max_x - min_x + 1;
    int height = max_y - min_y + 1;
    vector<string> grid(height, string(wid, '.'));            //.으로 격자판초기화
    for (auto point : points) {
        int x = point.first - min_x;            //x좌표 계산
        int y = max_y - point.second;           //y좌표 계산
        grid[y][x] = '*';
    }
    return grid;
}

 

오늘은 각종 행렬과 수학을 코드로 구현해볼 것이다.

 

일단 구조체부분을 매트릭스가 들어갈 수 있게 바꿔주자

 

Struct.h

#pragma once
#include "Types.h"


struct Vertex
{
	Vec3 position;		//12바이트 0부터시작
	//Color color;		//12부터시작
	Vec2 uv;
};

struct TransformData
{
	Matrix matWorld = Matrix::Identity;
	Matrix matView = Matrix::Identity;
	Matrix matProjection= Matrix::Identity;
};

 

그리고 지역좌표계에서 이동、회전、크기조절을 담당하게 될 매트릭스 변수를 선언해주자 

	Vec3 _localposition = { 0.f,0.f,0.f };
	Vec3 _localRotation = { 0.f,0.f,0.f };
	Vec3 _localScale = { 1.f,1.f,1.f };

 

그리고 업데이트문을 수정하여 _transformData에 맞는 값이 들어갈 수 있도 하자。

void Game::Update()
{
	//크기 회전 이동
	Matrix matScale = Matrix::CreateScale(_localScale);
	Matrix matRotation = Matrix::CreateRotationX(_localRotation.x);
	matRotation *= Matrix::CreateRotationY(_localRotation.y);
	matRotation *= Matrix::CreateRotationZ(_localRotation.z);
	Matrix matTranslation = Matrix::CreateTranslation(_localposition);

	Matrix matWorld = matScale * matRotation * matTranslation;	//SRT
	_transformData.matWorld = matWorld;

	D3D11_MAPPED_SUBRESOURCE subResouce;
	ZeroMemory(&subResouce, sizeof(subResouce));

	//맵을 통해 값을 넣어줄 준비를 한다. 이후 값을 넣고(복사해주고) UNMAP
	_deviceContext->Map(_constantBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &subResouce);
	::memcpy(subResouce.pData, &_transformData, sizeof(_transformData));			//바로 cpu-> gpu 넘어가게 된다.
	_deviceContext->Unmap(_constantBuffer.Get(), 0);
}

 

이제 이 값을 쉐이더 상에서 처리할 수 있게 hlsl파일 코드도 고쳐주자

Default.hlsl

cbuffer TransformData : register(b0)
{
    //hlsl상의 순서와 코드상의 순서가 달라서
    row_major matrix matWorld;
    row_major matrix matView;
    row_major matrix matProjection;
}

//정점 쉐이더- 위치관련 -> 레스터라이저-정점바탕으로 도형만들고 내/외부 판단 및 보간
VS_OUTPUT VS(VS_INPUT input)
{
    VS_OUTPUT output;
    
    //World view projection
    float4 position = mul(input.position, matWorld);
    position = mul(position, matView);
    position = mul(position, matProjection);
    
    output.position = position;
    output.uv = input.uv;
    
    return output;
}

 

보면 아직 행렬쪽에서 변화가 없기 때문에 그대로인 결과이미지를 볼 수 있다.

결과

만약 크기를 1/3배 줄인다면

Matrix matScale = Matrix::CreateScale(_localScale/3);

 

줄어든 이미지를 볼 수 있다.

 

폰 클래스는 액터클래스에서 상속받은 기능외에 추가 기능을 가지고 있다. 

그 추가기능 중 중요한 기능은 객체에 입력에 따른 변화를 줄 수 있다는 것이다.

 

Bird 폰 Class를 생성해보자

 

그리고 만든 클래스에 기반한 블루프린트도 만들어주자

 

이제 충돌에 사용할 기본적인 캡슐을 먼저 만들어보자. 

실제 메시를 통한 충돌 계산은 비용이 많이 드는 작업으로 캡슐같은 기본적인 모양으로 충돌을 감지한다. 

상속관계

이제 코드에서 추가해보자

일단 UCapsuleComponent 객체는 언리얼 헤더파일에서 Include를 해주어야하는데 이때 주의해야할 점은

항상 .generated.h이 제일 마지막에 위치해야한다는 것이다. 그렇지 않으면 오류가 발생하게 된다.

이유는 .generated.h파일이 리플랙션 시스템의 통합과 관련되어 있기 때문이다.

하지만 이러한 헤더파일은 각 객체에서 Include 작업을 하는 것은 안좋다. 

이유는 후에 다루기로하자.

 

먼저 UCapsuleComponent 변수를 선언해주고 블루프린트상에 보일 수 있게 하자

이렇게만 했을 때는 그냥 포인터변수를 가지고 있는 것으로

우리는 이것을 실제로 쓰기위해 하위 객체를 통해 초기화 해주어야한다.

Bird.h

private:
	UPROPERTY(VisibleAnywhere)
	UCapsuleComponent* Capsule;

 

Bird.cpp

CreateAbstractDefaultSubobject를 통해 초기화해준다.

	Capsule = CreateAbstractDefaultSubobject<UCapsuleComponent>(TEXT("Capsule"));
	SetRootComponent(Capsule);

 

생성된 모습

 

높이와 너비를 조절하려면 아래의 코드와 같이 사용하면 된다.

ABird::ABird()
{
	PrimaryActorTick.bCanEverTick = true;

	Capsule = CreateAbstractDefaultSubobject<UCapsuleComponent>(TEXT("Capsule"));
	Capsule->SetCapsuleHalfHeight(20.f);
	Capsule->SetCapsuleRadius(15.f);
	SetRootComponent(Capsule);
}

 

★★헤더파일은 각 객체에서 Include 작업을 하는 것은 안좋다. 

이유 : 각 객체의  헤더파일에서 계속 헤더 파일을 참조하게 되면 각 객체를 호출 할때마다 호출할 헤더코드가 가지고 있는 또 다른 헤더파일에 관련된 코드를 호출하게 되기때문에 컴파일 시간이 길어진다.

그래서 이러한 문제점을 해결하기 위해

Forward Declaration- 전방선언을 사용한다.

전방선언은

전방 선언(Forward Declaration)은 컴파일러에게 특정 클래스, 구조체, 함수 등의 존재를 미리 알리는 선언으로 이를 통해 해당 객체가 나중에 정의될 것임을 알려준다. 단순히 컴파일러에게 "이 이름이 존재한다"는 것을 알려주는 역할

헤더파일에서 전방선언을 한 뒤 

정확하게 사용하는 것은 cpp파일에서 헤더파일을 가져온 뒤 사용하게 된다. 

정리하자면 

문제점

  1. 컴파일 시간 증가: 헤더 파일을 여러 번 포함하면 컴파일러가 포함된 모든 헤더 파일을 다시 처리해야 합니다. 이는 특히 대규모 프로젝트에서 컴파일 시간을 크게 증가시킬 수 있습니다.
  2. 순환 종속성: 클래스들이 서로를 참조할 때, 헤더 파일에서 직접 #include를 사용하면 순환 종속성 문제가 발생할 수 있습니다. 이는 컴파일 오류를 초래할 수 있습니다.
  3. 의존성 증가: 불필요한 헤더 파일을 포함하면 파일 간의 의존성이 증가하여 코드 수정 시 많은 파일을 다시 컴파일해야 할 수 있습니다. 이는 코드 유지 보수를 어렵게 만듭니다.
  4. 코드 복잡성: 헤더 파일에 너무 많은 #include가 있으면 코드가 복잡해지고, 어떤 헤더 파일이 실제로 필요한지 파악하기 어려워집니다.

장점

 

  • 컴파일 시간 단축: 헤더 파일을 덜 포함함으로써 컴파일러가 처리해야 할 파일의 양을 줄여 컴파일 시간을 단축할 수 있다
  • 순환 종속성 해결: 클래스들이 서로를 참조하는 경우, 전방 선언을 통해 순환 종속성 문제를 해결할 수 있다
  • 의존성 관리: 파일 간의 의존성을 줄여, 코드 수정 시 재컴파일되는 범위를 최소화할 수 있다
  • 코드 명확성: 헤더 파일을 깔끔하게 유지하고, 필요한 정의만 소스 파일에서 포함하여 코드의 명확성을 높일 수 있다.

 

반드시 포함해야 할 경우:

  1. 부모 클래스에서 상속받을 때
    • 부모 클래스의 멤버 변수와 함수를 사용할 수 있도록 하기 위해 헤더 파일을 포함한다.
  2. 타입의 크기가 필요할 때
    • 클래스의 새로운 인스턴스를 생성해야 하는 경우, 해당 타입의 크기를 알아야 하므로 헤더 파일을 포함한다.
  3. 멤버 변수/함수에 접근할 때
    • 클래스의 멤버 변수나 함수를 사용할 때는 해당 클래스의 정의를 알아야 하므로 헤더 파일을 포함한다.

이러한 경우를 제외하고는, 전방 선언을 사용하여 헤더 파일의 불필요한 포함을 피할 수 있다. 이를 통해 컴파일 시간을 단축하고, 코드의 의존성을 줄일 수 있다.

 

이제 코드를 수정해보자 밑의 코드를 헤더파일에서 지워주자

#include "Components/CapsuleComponent.h"

 

Bird.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "Bird.generated.h"

class UCapsuleComponent;		//전방선언

UCLASS()
class SLASH_API ABird : public APawn
{
	GENERATED_BODY()

public:
	ABird();
	virtual void Tick(float DeltaTime) override;

	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

protected:
	virtual void BeginPlay() override;
private:
	UPROPERTY(VisibleAnywhere)
	UCapsuleComponent* Capsule;

};

 

이제 본격적으로 Pawn클래스에 매쉬를 붙여보자 

매쉬에는 static과 skeletal이 있는데 static은 말 그대로 정적인 애니메이션이 불가능한 매쉬이고 skeletal은 뼈대로 구성된 매쉬로 애니메이션이 가능하다.

 

먼저 헤더파일에 변수를 추가해주자 이때 변수는 전방선언으로 선언해주자

class USkeletalMeshComponent;
private:
	UPROPERTY(VisibleAnywhere)
	UCapsuleComponent* Capsule;

	UPROPERTY(VisibleAnywhere)
	USkeletalMeshComponent* BirdMesh;

 

그리고 이제 cpp파일에서 변수에 대한 초기화를 진행해주자

#include "Pawns/Bird.h"
#include "Components/CapsuleComponent.h"
#include "Components/SkeletalMeshComponent.h"

ABird::ABird()
{
	PrimaryActorTick.bCanEverTick = true;

	Capsule = CreateDefaultSubobject<UCapsuleComponent>(TEXT("Capsule"));
	Capsule->SetCapsuleHalfHeight(20.f);
	Capsule->SetCapsuleRadius(15.f);
	SetRootComponent(Capsule);

	BirdMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("BirdMesh"));
	BirdMesh->SetupAttachment(GetRootComponent());
}

 

BirdMesh가 추가되었고 이제 메시에 Sk_Crow를 추가해주었다.

 

그리고 여기에 애니메이션모드를 Use Animation Asset으로 바꿔주고 애님을 붙여주자

https://school.programmers.co.kr/learn/courses/30/lessons/250136

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

석유덩어리를 찾는데 BFS를 사용하는 것은 알았는데 이걸 어떻게 활용할지가 생각이 정말 안났다. 

일단 전체를 순회하면서 덩어리를 찾는데 BFS를 사용 - 이 과정에서 덩어리로 인식할 수 있게 아이디를 부여 

열을 기준으로 행을 순회하면서 아이디에 해당하는 그룹의 사이즈가 아직 시추되지 않았다면 시추하고 사이즈를 저장한다.

마지막으로 이 중에서 가장 큰 값을 결과값으로 둔다.

#include <vector>
#include <queue>
#include <algorithm>
#include <unordered_map>

using namespace std;

const int DIRS[4][2] = { {0, 1}, {1, 0}, {0, -1}, {-1, 0} };

int n, m;
vector<vector<int>> land;
vector<vector<bool>> visited;
vector<vector<int>> component;      //같은 그룹묶어주기
unordered_map<int, int> component_size;     //그룹의 사이즈

//같은 그룹찾기 -> BFS로 
int bfs(int x, int y, int comp_id) {            //x,y,그룹아이디
    queue<pair<int, int>> q;
    q.push({ x, y });
    visited[x][y] = true;
    component[x][y] = comp_id;
    int size = 0;

    while (!q.empty()) {
        auto [cx, cy] = q.front(); q.pop();
        size++;

        for (int d = 0; d < 4; d++) {
            int nx = cx + DIRS[d][0];
            int ny = cy + DIRS[d][1];

            if (nx >= 0 && nx < n && ny >= 0 && ny < m && !visited[nx][ny] && land[nx][ny] == 1) {
                q.push({ nx, ny });
                visited[nx][ny] = true;
                component[nx][ny] = comp_id;
            }
        }
    }

    return size;
}

int solution(vector<vector<int>> land_input) {
    land = land_input;
    n = land.size();
    m = land[0].size();
    visited.assign(n, vector<bool>(m, false));
    component.assign(n, vector<int>(m, -1));
    component_size.clear();

    int comp_id = 0;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            // 석유가 있고 방문하지 않은 위치에서 BFS 실행
            if (land[i][j] == 1 && !visited[i][j]) {
                int size = bfs(i, j, comp_id);
                component_size[comp_id] = size;
                comp_id++;
            }
        }
    }

    vector<int> oil_per_column(m, 0);

    for (int j = 0; j < m; j++) {
        unordered_map<int, bool> touched;  // 현재 열에서 이미 포함된 덩어리를 추적하는 맵
        for (int i = 0; i < n; i++) {
            // 석유가 있고 현재 열에서 아직 포함되지 않은 덩어리라면 
            if (land[i][j] == 1 && !touched[component[i][j]]) {
                oil_per_column[j] += component_size[component[i][j]];  // 덩어리 크기를 현재 열의 석유량에 추가
                touched[component[i][j]] = true;  // 컴포넌트 아이디에 해다아하는 값이 없을때 -> 현재 덩어리를 포함했다고 표시
            }
        }
    }

    return *max_element(oil_per_column.begin(), oil_per_column.end());
}

 

오늘은 캐릭터를 움직여 볼 것이다.

 

일단 블루프린트의 Set Actor Location이라는 기능부터 알아보자

1.Set Actor Location

Set Actor Location

 

Set Actor Location은 말 그대로 액터의 위치를 정해주는 기능이다. 

그리고 결과사진을 보면 블루프린트가 먼저 작동하고 그다음에 구와 벡터가 표시된다는 것을 알 수 있다.

 

이제 코드로 구현해보자 코드에서는 SetActorLocation을 통해 구현할 수 있다.

	SetActorLocation(FVector(0.f, 0.f, 50.f));

결과

 

2.Set Actor Rotation

Set Actor Rotation은 말 그대로 액터를 각도 회전시키는 함수이다. 

블루프린트에서는 Set Actor Rotation노드를 추가해주는 것으로 구현할 수 있다.

Set Actor Rotation

 

이제 코드로 구현해보자

SetActorRotation 함수와 FRotator변수를 사용하여 회전해주었다. 

FRotator의 각 변수는 피치(Pitch, X축), 요(Yaw, Z축), 롤(Roll, Y축) 순서이다.

	SetActorRotation(FRotator(0.f, 90.f, 0.f));

결과화면

3.Add World Offset & Rotation

 

Add World Offset, Add World Rotation 을 통해월드좌표에서 벡터를 더주고 회전을 시킬 수 있다.

Add World Offset
Add World Rotation

이제 코드로 구현해보자

해당 부분을 구현하기전에 매 프레임마다 구가 이동하는 모습을 볼 수 있게 줄 매크로함수를 만들어주자

#pragma once
#include "DrawDebugHelpers.h"

#define DRAW_SPHERE(Location) if (GetWorld()) DrawDebugSphere(GetWorld(),Location,25.f,12,FColor::Red,true);
#define DRAW_SPHERE_SingleFrame(Location) if (GetWorld()) DrawDebugSphere(GetWorld(),Location,25.f,12,FColor::Red,false,-1.f);
#define DRAW_LINE(StartLocation,EndLocation) if(GetWorld()) DrawDebugLine(World, StartLocation,EndLocation, FColor::Red, true, -1.f, 0, 1.f);
#define DRAW_LINE_SingleFrame(StartLocation,EndLocation) if(GetWorld()) DrawDebugLine(World, StartLocation,EndLocation, FColor::Red, false, -1.f, 0, 1.f);
#define DRAW_POINT(Location) if(GetWorld()) DrawDebugPoint(World, Location, 15.f, FColor::Red, true);
#define DRAW_POINT_SingleFrame(Location) if(GetWorld()) DrawDebugPoint(World, Location, 15.f, FColor::Red, false,-1.f);
#define DRAW_VECTOR(StartLocation,EndLocation) if (GetWorld()) \
	{ \
		DrawDebugLine(World, StartLocation, EndLocation, FColor::Red, true, -1.f, 0, 1.f); \
		DrawDebugPoint(World, EndLocation, 15.f, FColor::Red, true); \
	}
#define DRAW_VECTOR_SingleFrame(StartLocation,EndLocation) if (GetWorld()) \
	{ \
		DrawDebugLine(World, StartLocation, EndLocation, FColor::Red, false, -1.f, 0, 1.f); \
		DrawDebugPoint(World, EndLocation, 15.f, FColor::Red, false,-1.f); \
	}

 

그리고 이제 틱이벤트에 AddActorWorldOffset을 통해 구가 움직이도록 해보자 

// Called every frame
void AItem::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	AddActorWorldOffset(FVector(1.f, 0.f, 0.f));
	DRAW_SPHERE_SingleFrame(GetActorLocation());
}

AddActorWorldOffset

매 프레임마다 움직이고 있는 모습을 볼 수 있다 하지만 프레임의 차이가 있기때문에 움직임속도의 차이가 있을 것이다.

일단 우리는 프레임을 고정시켜주어 일정한 속도를 내도록 해주자

프레임 고정

하지만 프레임단위로 이동하는 방식보다는 어느 환경에서나 동일하게 흘러가는 델타타임을 이용하는 방법이 좋다. 

움직이는 정도에 Delta Time을 곱해주는 것으로 처리하면 된다. 

// Called every frame
void AItem::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	
	//움직이는 정도 - cm/s
	float MovementRate = 50.f;

	// MovementRate*DeltaTime -> cm/s * s/frame = cm/frame
	AddActorWorldOffset(FVector(MovementRate*DeltaTime, 0.f, 0.f));
	DRAW_SPHERE_SingleFrame(GetActorLocation());
}

결과

그렇게 되면 프레임속도 에 상관없이 50cm씩 움직이는 구를 볼 수 있다. 이제 프레임 고정을 없애주자

 

이제 여기에 회전을 추가해보자 

회전 또한 프레임속도에 독립적으로 수행될 수 있도록 Delta Time을 곱해준다.

회전하는 모습을 볼 수 있도록 벡터그리는 함수를 추가해주자

// Called every frame
void AItem::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	
	//움직이는 정도 - cm/s
	float MovementRate = 50.f;
	float RotationRate = 45.f;
	
	// MovementRate*DeltaTime -> cm/s * s/frame = cm/frame
	AddActorWorldOffset(FVector(MovementRate*DeltaTime, 0.f, 0.f));
	AddActorWorldRotation(FRotator(0.f, RotationRate*DeltaTime, 0.f));
	DRAW_SPHERE_SingleFrame(GetActorLocation());
	DRAW_VECTOR_SingleFrame(GetActorLocation(), GetActorLocation() + GetActorForwardVector() * 100.f);
}

 

구 회전

 

4. 삼각함수 활용

 

이번에는 삼각함수를 활용해보자 

사인 함수를 활용하여 물체가 사인주기에 맞춰서 위아래로 움직이게 해보자 

블루프린트에서는 Sin(Radius) 노드를 통해 World Offset의 Z좌표를 바꾸어주었다.

Sin

 

이제 코드로 구현해보자 

시간이 지날때마다 늘어나거나 줄어드는 값을 구현하기 위해 float 변수를 추가하고 이를 통해 Sin 함수값을 얻어온다. 이때 Sin 함수는 FMath에 있다.

// Called every frame
void AItem::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	
	RunningTime += DeltaTime;

	float DeltaZ = 0.25f*FMath::Sin(RunningTime*5.f);

	AddActorWorldOffset(FVector(0.f, 0.f, DeltaZ));

	DRAW_SPHERE_SingleFrame(GetActorLocation());
	DRAW_VECTOR_SingleFrame(GetActorLocation(), GetActorLocation() + GetActorForwardVector() * 100.f);
}

위아래로만 움직이는 모습을 볼 수 있다.

이제 헤더파일의 변수를 선언하여 주기값과 진폭값을 변경할 수 있도록하자

private:
	float RunningTime;
	float Amplitude = 0.25f; //진폭
	float TimeConstant = 5.f;		//x 계수값
};

 

이제 이값을 블루프린팅상에 나타나도록 해보자 

이러한 기능은 UPROPERTY 매크로 함수로 지정해줄 수 있다.

UPROPERTY(EditDefaultsOnly)
float Amplitude = 0.25f; //진폭

 

이렇게 코드를 작성한 뒤 핫리로딩 해주면 아이템 블루프린트의 디테일 패널에 Amplitude 값이 보인다.  이 값을 수정할 수 도 있다.

EditDefaultsOnly 지정값으로 블루프린트 상에서만 수정이 가능하다.

그냥 아이템의 디테일 패널에는 보이지않는 것을 알 수 있다.

만약 EditInstanceOnly로 지정해주게 된다면 기본 디테일창에 보이게 된다.

이 경우에는 블루프린트의 디테일에서는 보이지않게 된다.

UPROPERTY(EditInstanceOnly)
float TimeConstant = 5.f;		//x 계수값

만약 EditAnywhere로 바꿔주게 된다면 둘 다의 디테일창에서 보이게 되고 수정할 수 있다. 

	UPROPERTY(EditAnywhere)
	float Amplitude = 0.25f; //진폭

	UPROPERTY(EditAnywhere)
	float TimeConstant = 5.f;		//x 계수값

 

그리고 보이긴하지만 편집할 수 없게 만들 수 도 있다.

UPROPERTY(VisibleDefaultsOnly);			//블루프린트
UPROPERTY(VisibleInstanceOnly);			//인스턴스상
UPROPERTY(VisibleAnywhere);				//둘다
float RunningTime;

VisibleAnywhere

이제 이 변수를 이벤트그래프에서 사용할 수 있도록 만들어보자

이때 BlueprintReadOnly라는 매개변수를 넣어주면 되는데 이때 Private로 선언된 변수에서는 작동하지않는다.

UPROPERTY(EditAnywhere,BlueprintReadOnly)

 

이렇게 설정해주면 블루프린트 상에서 가져와 쓸 수 있다. 지금은 ReadOnly라 Get만 쓸 수 있다

 

이것을 BlueprintReadWrite로 바꿔주게 되면 Set도 사용할 수 있게 된다.

또한 카테고리를 지정해 줄 수 있다.

UPROPERTY(EditAnywhere, BlueprintReadWrite,Category="Sine Parameters")
float Amplitude = 0.25f; //진폭

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Sine Parameters")
float TimeConstant = 5.f;		//x 계수값

 

프라이버으로 지정된 변수를 가져오고 싶을 때는 meta=(AllowPrivateAccess="true")로 지정하고 만들어 주면 된다.

UPROPERTY(VisibleAnywhere,BlueprintReadOnly,meta=(AllowPrivateAccess="true"));
float RunningTime;

 

 

변수뿐만 아니라 함수또한 이런방식으로 보이게 할 수 있다. 

함수는 UFUNCTION(BlueprintCallable)을 통해 블루프린트 상에 보이게 할 수 있다.

	UFUNCTION(BlueprintCallable)
	float TransformedSin(float Value);

 

만약 이것을 UFUNCTION(BlueprintPure) 으로 지정한다면 Get Actor Location과 같이 계산하는 값을 반환해주는 용도로 사용할 수 있다.

	UFUNCTION(BlueprintPure)
	float TransformedSin(float Value);

 

 

★ ★ ★ 제네릭 함수 - 템플릿 문법

 

C++의 템플릿 문법은 제네릭 프로그래밍을 가능하게 하여, 클래스나 함수를 작성할 때 특정 데이터 타입에 의존하지 않고, 다양한 데이터 타입을 처리할 수 있도록 한다.

사용하는 방법은 함수위에 template<typename T>을 선언하고 밑에 typename으로 선언한 제네릭 변수를 통해 함수를 구성해주면 된다. 

template<typename T>
inline T AItem::Avg(T First, T Second)
{
	return (First + Second) / 2;
}

 

사용할 때는 아래와 같이 사용해주면 된다.

Avg<int32>(1, 3);
UE_LOG(LogTemp, Warning, TEXT("Avg : %d"), Avg<int32>(1, 3));

 

이제 액터에 스태틱매쉬를 붙여 게임화면에 보일 수 있도록하자.
블루프린트에서 스태틱매쉬를 추가한 뒤 Sphere을 추가해주자 

 

이 것을 코드로 구현해 보겠다. 

일단 코드 상에서 인스턴스를 건드리려면 아직 액터들이 모두 생성되지않은 생성자 단계가 아니라 BeginPlay이후에 이루어져야한다. 

또한 이 컴포넌트의 하위 객체를 이용할 때는 템플릿 함수를 사용하여 지정해주어야한다.

이 함수는 포인터를 반환해준다.

 

그럼 이제 매쉬를 저장해줄 포인터 변수를 선언해주자

	UStaticMeshComponent* ItemMesh;

 

그리고 생성자에서 매쉬를 붙여주자

AItem::AItem()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	ItemMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ItemMeshComponent"));
	RootComponent = ItemMesh;
}

 

'게임공부 > Unreal Engine' 카테고리의 다른 글

[Unreal Engine][C++]6.움직이기  (1) 2024.07.16
[Unreal Engine][C++]5.Pawn 클래스  (0) 2024.07.15
[Unreal Engine][C++]3. 디버그  (0) 2024.07.10
[Unreal Engine][C++]2. C++살펴보기  (0) 2024.07.09
[Unreal Engine][C++]1.시작  (0) 2024.07.08

https://school.programmers.co.kr/learn/courses/30/lessons/150369

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

결국에는 그리디하게 접근하는게 중요할 것 같다

제일 먼집부터 순회하면서 배달 및 수거를 진행하고 이를 통해 해당 집에 대한 왕복횟수를 계산하면서 최종적인 이동 거리를 계산해주면 된다.

#include <string>
#include <vector>
#include <algorithm>

using namespace std;
//결국은 그리디
long long solution(int cap, int n, vector<int> deliveries, vector<int> pickups) {
    long long answer = 0;
    int delivery_load = 0;  // 현재 트럭에 실린 배달 상자 수
    int pickup_load = 0;    // 현재 트럭에 실린 수거 상자 수

    // 가장 먼 집부터 순회
    for (int i = n - 1; i >= 0; --i) {
        // 해당 집에 배달하거나 수거할 상자가 있는 경우에만 작업
        if (deliveries[i] > 0 || pickups[i] > 0) {
            int rounds = 0;  // 해당 집에 대한 왕복 횟수

            // 배달 또는 수거할 상자가 트럭 용량보다 많은 경우 추가로 왕복해야 함
            while (delivery_load < deliveries[i] || pickup_load < pickups[i]) {
                rounds++;
                delivery_load += cap;  // 트럭에 최대 용량만큼 배달 상자를 추가로 실음
                pickup_load += cap;    // 트럭에 최대 용량만큼 수거 상자를 추가로 실음
            }

            // 해당 집에서 배달할 상자를 트럭에서 내림
            delivery_load -= deliveries[i];
            // 해당 집에서 수거할 빈 상자를 트럭에 실음
            pickup_load -= pickups[i];

            // 왕복 거리를 답에 추가 (집까지의 거리 * 2 * 왕복 횟수)
            answer += (i + 1) * 2 * rounds;
        }
    }

    return answer;
}

 

 

오늘은 레스터라이저, 샘플스테이트, 블랜드스테이트등에 대해 알아보자

 

레스터라이저는 코딩보다는 설정하는 단계라고 보면된다.

 

일단 레스터라이저를 생성하는 함수와 이에맞는 스테이트 변수를 만들어주자 

 

Game.h

//RS
ComPtr<ID3D11RasterizerState> _rasterizerState = nullptr;

 

Game.cpp

void Game::CreateRasterizerState()
{
	D3D11_RASTERIZER_DESC desc;
	ZeroMemory(&desc, sizeof(desc));
	//기본 솔리드 / 백
	desc.FillMode = D3D11_FILL_SOLID;
	desc.CullMode = D3D11_CULL_BACK;
	desc.FrontCounterClockwise = false;

	HRESULT hr = _device->CreateRasterizerState(&desc, _rasterizerState.GetAddressOf());
	CHECK(hr);
}

 

void Game::Init(HWND hwnd)
{
	_hwnd = hwnd;
	_width = GWinSizeX;
	_height = GwinSizeY;


	CreateDeviceAndSwapChain();
	CreateRenderTargetView();
	SetViewport();

	//삼각형 그리기 파트
	CreateGeometry();
	CreateVS();
	CreateInputLayout();
	CreatePS();
	CreateRasterizerState();
	CreateSRV();
	CreateConstantBuffer();
}
	//RS
	_deviceContext->RSSetState(_rasterizerState.Get());

 

이대로 실행하면 레스터라이저 단계에서 변화를 주지않았기때문에 변화가 없다.

결과

레스터라이저 단계에서 

필모드를 

desc.FillMode = D3D11_FILL_WIREFRAME;

로 바꾸게된다면 

이런 화면이 나오는데 이것은 실제로 삼각형을 출력해주고 있는 화면인 것이다.

 

여기서 컬링은 나오지않는 부분은 스킵하는 것인데 후면 인식이라고 보면 된다. 후면인식은 정점의 구성방식에 따라 달라지는데 이 것은 FrontCounterClockwise 를 통해 정해주게 된다. 

desc.FrontCounterClockwise = false;  //앞은 반시계방향
desc.FrontCounterClockwise = true;  //앞은 시계방향

 

이제 텍스처와 uv를 통해 결과값을 주는 샘플과 관련된 SamplerState에 대해 알아보자

이 부분은 uv좌표와 연관이 많다 만약 uv좌표에서 1을 초과할 경우 빈 부분은 어떻게 채우는 지 정해주는 것이다. 

즉 샘플링 규칙을 가지고 있는 것이다.

코드는 

Game.h

	ComPtr<ID3D11SamplerState> _samplerState = nullptr;

Game.cpp

void Game::CreateSamplerState()
{
	D3D11_SAMPLER_DESC desc;
	ZeroMemory(&desc, sizeof(desc));

	_device->CreateSamplerState(&desc, _samplerState.GetAddressOf());
}

 

여기서 기본 설정대로 함수를 완성하게 되면

void Game::CreateSamplerState()
{
	D3D11_SAMPLER_DESC desc;
	ZeroMemory(&desc, sizeof(desc));
	desc.AddressU = D3D11_TEXTURE_ADDRESS_BORDER;
	desc.AddressV = D3D11_TEXTURE_ADDRESS_BORDER;
	desc.AddressW = D3D11_TEXTURE_ADDRESS_BORDER;
	//순서대로 RGBA
	desc.BorderColor[0] = 1;
	desc.BorderColor[1] = 0;
	desc.BorderColor[2] = 0;
	desc.BorderColor[3] = 1;
	desc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
	desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
	desc.MaxAnisotropy = 16;
	desc.MaxLOD = FLT_MAX;
	desc.MinLOD = FLT_MIN;
	desc.MipLODBias = 0.0f;

	_device->CreateSamplerState(&desc, _samplerState.GetAddressOf());
}

 

그리고 이것을 Render 단계에서 PS 단계에 붙여준다.

	//PS
	_deviceContext->PSSetShader(_pixelShader.Get(), nullptr, 0);
	_deviceContext->PSSetShaderResources(0, 1, _shaderResourceView.GetAddressOf());		//0번슬롯에 1개
	_deviceContext->PSSetSamplers(0, 1, _samplerState.GetAddressOf());

 

지금은 아무런 조작을 하지않고 기본값을 넣어줬기 때문에 변화가 없다.

결과

변화를 주고자 정점단계에서 uv 좌표를 늘린후에 테스트를 해보자 

//정점정보
{
	_vertices.resize(4);

	//13 -> 012
	//02 -> 213
	_vertices[0].position = Vec3(-0.5f, -0.5f, 0.f);
	_vertices[0].uv = Vec2(0.f, 5.f);
	//_vertices[0].color = Color(1.f, 0.f, 0.f, 1.f);
	_vertices[1].position = Vec3(-0.5f, 0.5f, 0.f);
	_vertices[1].uv = Vec2(0.f, 0.f);
	//_vertices[1].color = Color(1.f, 0.f, 0.f, 1.f);
	_vertices[2].position = Vec3(0.5f, -0.5f, 0.f);
	_vertices[2].uv = Vec2(5.f, 5.f);
	//_vertices[2].color = Color(1.f, 0.f, 0.f, 1.f);
	_vertices[3].position = Vec3(0.5f, 0.5f, 0.f);
	_vertices[3].uv = Vec2(5.f, 0.f);
	//_vertices[3].color = Color(1.f, 0.f, 0.f, 1.f);
}

 

결과

 

결과화면을 보면 1을 초과한 부분은 빨간색으로 돼있는 것을 볼 수 있다. 이것을 결정해주는 게 샘플러 단계인 것이다.

 

다른모드를 보면 미러로 설정했을 때는 거울에 반사된 것과 같이 나오는 것을 알 수 있다.

	desc.AddressU = D3D11_TEXTURE_ADDRESS_MIRROR;
	desc.AddressV = D3D11_TEXTURE_ADDRESS_MIRROR;
	desc.AddressW = D3D11_TEXTURE_ADDRESS_MIRROR;

결과

경우에 따라서 반복전인 타일을 배치할 때 사용할 수 도 있다.

 

마지막으로 BlendState의 코를 보자

이 부분은 Blend과 관련된 부분으로 밑의 코드에서 BlendEnable을 true로 했을 때 블랜드를 하겠다는 의미로 쓰인다.

나머지 값들도 어떻게 값을 섞어 줄 것인지를 보여주는 코드들이다.

배경이나 벽에 대한 색을 섞을 때 사용하게 될 것이다.

void Game::CreateBlenderState()
{
	D3D11_BLEND_DESC desc;
	ZeroMemory(&desc, sizeof(desc));
	desc.AlphaToCoverageEnable = false;
	desc.IndependentBlendEnable = false;

	desc.RenderTarget[0].BlendEnable = true;
	desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
	desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
	desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
	desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
	desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO;
	desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
	desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;

	HRESULT hr = _device->CreateBlendState(&desc, _blendState.GetAddressOf());
	CHECK(hr);
}

 

이 부분은 OutputMerge 단계에서 이루어지기 때문에 이 단계에 추가해주자

/OM
		_deviceContext->OMSetBlendState(_blendState.Get(), nullptr, 0xFFFFFFFF);

결과

 

 

오늘은 게임 플레이내에서 구나 선, 점을 통해 매쉬가 없이도 화면에 나타날 수 있게 해보자

 

1. 디버그 구 그리기

일단 블루프린트상에서는 Draw Debug Sphere을 추가하여 만들 수 있다.

BluePrints

 

이렇게하면 0,0,0에 구가 나오게 된다. 

결과

 

이제 액터위치에서 구가 보이도록 만들어주자 

BluePrints
결과

 

이제 이걸 C++로 구현해보자 

구현하려면 일단 DrawDebugHelpers 헤더파일을 추가해줘야한다.

DrawDebugSphere 함수를 통해 그려주면 되는데 이 함수의 매개변수에는 UWorld와 FVector가 들어가는데 이때  

GetWorld GetActorLocation을 통해 값을 전달해주면 된다.

	UWorld* World = GetWorld();

	if (World)
	{
		FVector Location = GetActorLocation();
		DrawDebugSphere(World, Location, 25.f, 24, FColor::Red, false, 30.f);
	}

 

결과

 

이것을 매크로 함수를 통해 구현해보자 

#define THIRTY 30
#define DRAW_SPHERE(Location) if (GetWorld()) DrawDebugSphere(GetWorld(),Location,25.f,12,FColor::Red,true);

 

	FVector Location = GetActorLocation();

	DRAW_SPHERE(Location);

 

결과

 

이 매크로 함수는 Slash.h에 넣어서 모든 클래스에서 사용가능하도록 만들어주자 

 

Slash.h

// Copyright Epic Games, Inc. All Rights Reserved.

#pragma once

#include "CoreMinimal.h"

#define DRAW_SPHERE(Location) if (GetWorld()) DrawDebugSphere(GetWorld(),Location,25.f,12,FColor::Red,true);
#include "Slash\Slash.h"

 

동일하게 동작하는 것을 볼 수 있다. 

결과화면

 

2. 디버그 선 그리기

블루 프린트에서는 Draw Debug Line을 사용하여 선을 그릴 수 있다.

 

 

결과

이제 이것을 코드로 구현해보자

 

C++에서는 DrawDebugLine으로 선을 그릴 수 있다.

if (World)
{
	FVector Forward = GetActorForwardVector();
	DrawDebugLine(World, Location, Location + Forward * 100.f, FColor::Red, true, -1.f, 0, 1.f);
}

 

결과

이제 이것을 매크로로 만들어 간결하게 쓸 수 있도록 만들어 보자

 

#define DRAW_LINE(StartLocation,EndLocation) if(GetWorld()) DrawDebugLine(World, StartLocation,EndLocation, FColor::Red, true, -1.f, 0, 1.f);

 

	DRAW_LINE(Location, Location+Forward * 100.f);

 

결과

 

3.디버그 점 그리기

 

블루 프린트에서는 Draw Debug Point를 통해 그릴 수 있다.

 

결과

디버그 포인트는 크기가 어느곳에서 보던 일정하다 하지만 구나 선은 멀어질수록 작아진다

 

이제 이것을 코드로 구현해보자

코드에서는 DrawDebugPoint함수를 사용한다.

	if (World)
	{
		DrawDebugPoint(World, Location + Forward * 100.f, 15, FColor::Red, true);
	}

결과

 

이제 편하게 사용할 수 있도록 매크로 함수로 만들어주자

#define DRAW_POINT(Location) if(GetWorld()) DrawDebugPoint(World, Location, 15.f, FColor::Red, true);
	DRAW_POINT(Location + Forward * 100.f);

결과

추가적으로 점과 선을 한번에 그려주는 즉 벡터를 그려주는 매크로 함수를 만들어보자 벡터는 이전에 만들었던 라인과 점을 그리는 것을 통합하여 만들어주자

#define DRAW_VECTOR(StartLocation,EndLocation) if (GetWorld()) \
	{ \
		DrawDebugLine(World, StartLocation, EndLocation, FColor::Red, true, -1.f, 0, 1.f); \
		DrawDebugPoint(World, EndLocation, 15.f, FColor::Red, true); \
	}
	/*DRAW_LINE(Location, Location + Forward * 100.f);
	DRAW_POINT(Location + Forward * 100.f);*/

	DRAW_VECTOR(Location, Location + Forward * 100.f);

결과

 

이제 매크로 함수를 모아둔 헤더파일을 하나 만들어주자

 

Source/Slash/

경로에 넣어주자

DebugMacros.h

#pragma once
#include "DrawDebugHelpers.h"

#define DRAW_SPHERE(Location) if (GetWorld()) DrawDebugSphere(GetWorld(),Location,25.f,12,FColor::Red,true);
#define DRAW_LINE(StartLocation,EndLocation) if(GetWorld()) DrawDebugLine(World, StartLocation,EndLocation, FColor::Red, true, -1.f, 0, 1.f);
#define DRAW_POINT(Location) if(GetWorld()) DrawDebugPoint(World, Location, 15.f, FColor::Red, true);
#define DRAW_VECTOR(StartLocation,EndLocation) if (GetWorld()) \
	{ \
		DrawDebugLine(World, StartLocation, EndLocation, FColor::Red, true, -1.f, 0, 1.f); \
		DrawDebugPoint(World, EndLocation, 15.f, FColor::Red, true); \
	}

 

결과

 

+ Recent posts