본문 바로가기

언리얼게임프로젝트/슈팅게임 C++

Unreal C++ 플레이어 제작하기

플레이어 클래스 제작

Pawn으로 C++클래스를 만듭니다. public을 선택하고 이름은 PlayerPawn으로 합니다.

C++폴더가 있으니 우클릭을 하고 만들어 봅니다.

Visual Studio로 이동합니다. 모두 다시 수정을 누릅니다.

플레이어에 충돌체  컴포넌트를 추가해 보겠습니다. PlayerPawn.h로 이동하고 Class 맨아래 public:안에 다음 코드를 추가

boxComp는 단순히 UBoxComponent의 인스턴스의 주소를 담을 변수입니다. 

	UPROPERTY(EditAnywhere)
	class UBoxComponent* boxComp;

 

PlayerPawn.cpp로 이동해서 UBoxComponent의 인스턴스를 만들어 연결해 줍니다.

헤더도 인클루드 해줍니다. 오타가 없다면 코드의 UBoxComponent위에 마우스를 올리면 주석을 볼수 있습니다.

#include "Components/BoxComponent.h"

다음 코드를 추가해 boxComp를 루트컴포넌트로 만듭니다.

모양을 담당할 스태틱메시를 추가하기 위해 playerPawn.h 헤더에 meshComp 포인터 변수를 만듭니다.

playerPawn.cpp에 메시 인스턴스를 만들어 meshComp변수에 할당하고 boxComp의 자식으로 만듭니다.

처음에 횟깔리는게 지금 만든건 StaticMesh가 아니라 StaticMesh를 Actor에 추가해주는 Component만 만든겁니다. 그 주소를 meshComp에 연결해 준거구요.  

스태틱메시컴포넌트포인트변수 <- 스태틱메시컴포넌트 <- 스태틱메시 (이건 나중에 연결합니다.)

Unreal Editor로 돌아가기전 Ctrl-SHT-S저장, Ctrl-B를 눌러 빌드해봅니다. 에러가 없어야 합니다. 물론 라이브코딩이 켜져있으므로 언리얼에 가면 자동으로 하긴합니다. 교재에서는 에러가 많다고 라이브코딩을 끄고 씁니다.

블루프린트 폴더에서 우클릭학 블루프린트 클래스를 만들어 봅니다. 방금만든 PlayerPawn을 검색하고 이름을 BP_PlayerPawn로 Create합니다.

더블클릭하고 MeshComp를 선택후 디테일에서 Cube 를 선택해줍니다. ViewPort에 메시 모양이 보입니다.

충돌체의 기본 Shape Extent가 32x32x32로 Cube에 가려 안보이므로 이걸 50x50x50로 수정합니다.

 

Visual Studio PlaywerPawn.cpp APlayerPawn::APlayerPawn() 아래 코드를 추가해 ShapeBoxExtent 변경을 추가해 줍니다 이렇게 하면 블루프린트에서 이값이 디폴트값이 됩니다.

BP_PlayerPawn을 끌어다 레벨에 배치하고 Transform의 Location을 리셋해줍니다.

뷰포트의 기즈모는 x축이 앞을 가르키게 합니다. 언리얼은 앞쪽이 x축이라 이러면 뒤에서 보는게 됩니다. 퍼스펙티브를 선택하고 Back 을 선택하면 뒤에서보는 모양이 되는데 이때 기즈모의 방향을 참조하시면 됩니다.

왼쪽이 뷰포트 오른쪽이 카메라뷰입니다.

BP_PlayerPawn은 자동생성키켜줄꺼기 때문에 지우고 게임모드에서 Default Pawn으로 지정해줍니다.

자동생성될 자리를 만들기 위해 PlayerStart를 추가하고 Location을 리셋 해줍니다.

플레이 해보면 뷰포트에 BP_PlayerPawn이 보입니다.

사용자 입력키 바인딩하기

Inputs폴더를 만들고 우클릭후 InputAction을 만들고 이름을 . IA_Horizontal IA_Vertical  ValueType을 Axis1D로 해줍니다

우클릭해서 Input Mapping Context도 만듭니다.

Mapping+를 2개 눌러 IA_Horizontal, IA_Vertical을 맵핑해 줍니다. A와 S는 음수가 들어와야 하므로 Modifier+를 눌러 Negate를 지정해 줍니다.

AllSave합니다.

VisualStudio에서 shootingCPP.Build.cs파일을 찾아 "EnhancedInput"을 추가해 줍니다.

VisualStudio UnrealEdit를 다 끄고 우클릭해서 GenerateVisualStudioProjectFiels를 만듭니다.

PlayerPawn.h 맨 밑에 다음 변수들을 추가합니다. InpucAction과 IMC를 연결할 포인터들입니다.

	UPROPERTY(EditAnywhere)
	class UInputMappingContext* imc_playerInput;

	UPROPERTY(EditAnywhere)
	class UInputAction* ia_horizontal;

	UPROPERTY(EditAnywhere)
	class UInputAction* ia_vertical;

InputActionValue.h를 추가합니다.

인풋액션 발생시 실행할 함수를 선언합니다. Ctrl+.을 눌러 정의를 만들어줍니다.

이 함수를 정의하기 위해 PlayerPawn.cpp로 이동합니다.

2개의 헤더를 추가합니다.

BeginPlay()코드에서 InputSubsystem의 주소를 얻어 imc_playerInput을 맵핑합니다.

void APlayerPawn::BeginPlay()
{
	Super::BeginPlay();
	// Get controller own by current player
	APlayerController* pc = GetWorld()->GetFirstPlayerController();

	if(pc)
	{	// get UEnhancedInputLocalPlayerSubsystem from Local Player
		UEnhancedInputLocalPlayerSubsystem* subsys =
			ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(pc->GetLocalPlayer());
		if (subsys)
		{	//Connect imc_playerInput to subsys
			subsys->AddMappingContext(imc_playerInput, 0);
		}
	}
}

UE4의 inputComponent를 UE5로 변경하고 InputAction의 이벤트를 binding합니다. Triggered는 눌려있을동안 발생하고 Completed는 버튼이 떨어지는 경우인데 이때 0이 입력되어 멈출수 있답니다. 

void APlayerPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);
	//Cast UE4 InputComponent to UE5 EnhanceInputComponent
	UEnhancedInputComponent* enhancedInputComponent =
		Cast<UEnhancedInputComponent>(PlayerInputComponent);
	if (enhancedInputComponent)
	{
		enhancedInputComponent->BindAction(ia_horizontal, ETriggerEvent::Triggered, this,
        	&APlayerPawn::OnInputHorizontal);
		enhancedInputComponent->BindAction(ia_horizontal, ETriggerEvent::Completed, this,
        	&APlayerPawn::OnInputHorizontal);
		enhancedInputComponent->BindAction(ia_vertical, ETriggerEvent::Triggered, this, 
        	&APlayerPawn::OnInputVertical);
		enhancedInputComponent->BindAction(ia_vertical, ETriggerEvent::Completed, this,
        	&APlayerPawn::OnInputVertical);
	}
}

이벤트 발생시 실행될 함수를 구현합니다. 현재는 값을 프린트 합니다.

void APlayerPawn::OnInputHorizontal(const FInputActionValue& value)
{
	float hor = value.Get<float>();
	UE_LOG(LogTemp, Warning, TEXT("Horizontal: %.2f"), hor);
}

void APlayerPawn::OnInputVertical(const FInputActionValue& value)
{
	float ver = value.Get<float>();
	UE_LOG(LogTemp, Warning, TEXT("Vertical: %.2f"), ver);
}

BP_PlayerPawn에서 ImcPlayerInput과 인풋액션등을 binding해줍니다. 실행하고 WASD를 눌러보면 로그창에 로그가 나타납니다. 눌려진 키에 맞게 Vertical Horizontal 1, -1, 0이 잘 나오는지 확인합니다.

이동공식 적용하기

헤더에 인풋키값을 저장할 변수를 마련합니다.

playerPawn.cpp 함수 구현부에서 h,v 변수에 입력값을 저장합니다.

이제 이값을 이용해 Tick()이벤트에서 Actor를 움직여 보겠습니다.

헤더에 public:에 moveSpeed변수를 생성합니다. UPROPERTY를 사용해 디테일 메뉴에도 노출시킵니다.

playerPawn.cpp에서 Tick()함수에 이동로직을 구현합니다.

dir이 움직일 방향인데 비행기는 위와 옆으로 움직여야 하는데 위가 Z축 옆이 Y축입니다. 비행기가 2D게임이라 앞으로 갈필요는 없습니다.

방향을 구했으면 Normalize해줍니다. 방향에 따른 속도를 일정하게 해주기 위해서입니다.

방향에 속도와 deltaTime을 곱해 보정을 해주고 현재 액터위치에 더해주고 새로운 위치를 액터의 새위치로 업데이트합니다.

void APlayerPawn::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	//Make direction and updata Actor location
	FVector dir = FVector(0, h, v);
	dir.Normalize();
	FVector newLocation = GetActorLocation() + dir * moveSpeed * DeltaTime;
	SetActorLocation(newLocation);
}

여기까지 하고 라이브코딩후 플레이해보면 키보드 입력에 따라 플레이폰이 상하좌우로 잘 움직이는걸 확인하실수 있습니다.