상세 컨텐츠

본문 제목

Learning Unreal 4 언리얼 공부일지 - 캐릭터 움직이기

개발/Unreal Engine

by 2022. 1. 19. 20:48

본문

반응형

Paragon이라는 게임을 언리얼로 출시했었는데, 게임이 흥행하지 못하자 마켓 플레이스에 Paragon관련 모델들을 무료로 공개했다고 한다.

여기서 아무 모델이나 다운받아서 써보자.

 

언리얼 프로젝트를 생성할 때 자동으로 생성도니 StartContent는 삭제해도 사용중인 경우에 경고를 띄우지만 삭제해도 된다.

 

Pawn보다 Character에는 더 많은 기능이 있다.

Character클래스로 MyCharactor를 생성하고, Character.h 스크립트를 확인하면 다음과 같은 구성이다.

뭐 USkeletalMeshComponent, UCharacterMovementComponent 등등이 있는게 보인다.

유니티로 일해본 경험이 있음에도 엔진의 구조에 대한 지식이 희박한 나에게는, 이 구조 하나하나를 뜯어보는 것이 많은 도움이 될 것 같다. (언리얼 공부를 시작한 것도 엔진 구조를 알 수 있을 것이라는 옛 동료직원의 언급 때문이기도 하다.)

 

Character의 생성자로 들어가면 Character.cpp의 생성자 정의문을 볼 수 있다.

보면 CapsuleComponent, MovementComponent, Mesh 등이 전부 미리 만들어져 있음을 볼 수 있다.

 

이전 포스팅에서 Pawn을 움직였던 것과 비슷한 코드를 보자.

#include "MyCharacter.h"

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

	// Pawn과는 다르게 StaticMesh가 아니라 SkeletalMesh 사용
	static ConstructorHelpers::FObjectFinder<USkeletalMesh> SM(TEXT("SkeletalMesh'/Game/ParagonGreystone/Characters/Heroes/Greystone/Meshes/Greystone.Greystone'"));

	if (SM.Succeeded()) {
		// Mesh는 private라 안되고, GetMesh를 사용
		GetMesh()->SetSkeletalMesh(SM.Object);
	}
}

// Called when the game starts or when spawned
void AMyCharacter::BeginPlay()
{
	Super::BeginPlay();
	
}

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

}

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

}

void AMyCharacter::UpDown(float Value)
{
	if (Value == 0.f)
		return;

	UE_LOG(LogTemp, Warning, TEXT("UpDown %f"), Value);
	AddMovementInput(GetActorForwardVector(), Value);
}

void AMyCharacter::LeftRight(float Value)
{
	if (Value == 0.f)
		return;

	UE_LOG(LogTemp, Warning, TEXT("LeftRight %f"), Value);
	AddMovementInput(GetActorRightVector(), Value);
}

Character에서는 Pawn과는 달리 StaticMesh가 아니라 SkeletalMesh를 사용한다. (Mesh의 경로는 엔진에서 넣을 Mesh를 클릭후 Ctrl+C로 경로 복사가능)

Pawn에서 Mesh변수에 StaticMesh를 대입했던 부분도, Character에서는 Skeletalmesh를 넣는다.

생성자AMyCharacter()를 보면 Mesh는 private이므로, GetMesh를 통해 대입하였는데, 아마 이 부분은 언리얼 안에서 클래스에 대한 get, set 함수 부분으로 보인다.

 

MyGameModeBase.cpp파일의 헤더에 위 스크립트 MyCharacter의 header를 include하는 것을 잊지말자.

#include "MyGameModeBase.h"
#include "MyCharacter.h"

AMyGameModeBase::AMyGameModeBase() 
{
	// GameMode의 DefaultPawnClass값을 변경
	DefaultPawnClass = AMyCharacter::StaticClass();
}

위 스크립트가 엔진에서 어떤 값을 바꾸는지 잊어먹을까봐 사진을 넣자.

Default Pawn Class

 

 

이제 MyCharacter 스크립트 (.h, .cpp)에 코드를 작성해보자.

// MyCharacter.h

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "MyCharacter.generated.h"

UCLASS()
class UNREALINTRODUCTION_API AMyCharacter : public ACharacter
{
//
...
//

private:
	// property 선언
	UPROPERTY(VisibleAnywhere)
	// 포함되지 않은 경우 class처리하고 cpp에서 USpringArmComponent의 경로를 include
	class USpringArmComponent* SpringArm;

	UPROPERTY(VisibleAnywhere)
	class UCameraComponent* Camera;
};

이번에 필요한 것은 SpringArm, Camera 컴포넌트인데, MyCharacter.h에 포함되어있지 않아 cpp에서 경로를 include한다.

// MyCharacter.cpp

#include "MyCharacter.h"
#include "GameFramework/SpringArmComponent.h" // header의 USpringArmComponent경로
#include "Camera/CameraComponent.h" // header의 UCameraComponent경로
#include "Components/CapsuleComponent.h" // GetCapsuleComponent의 경로

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

	// SpringArm에 컴포넌트 추가
	SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SPRINGARM"));
	Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("CAMERA"));

	// 부모 component로 설정
	SpringArm->SetupAttachment(GetCapsuleComponent());
	Camera->SetupAttachment(SpringArm);

	// 컴포넌트의 값 수정
	SpringArm->TargetArmLength = 500.f; // TargetArmLength는 오브젝트로부터 떨어진 거리이다.
	SpringArm->SetRelativeRotation(FRotator(-35.f, 0.f, 0.f));

	GetMesh()->SetRelativeLocationAndRotation(
		FVector(0.f, 0.f, -88.f), FRotator(0.f, -90.f, -0.f));

	// Pawn과는 다르게 StaticMesh가 아니라 SkeletalMesh 사용
	static ConstructorHelpers::FObjectFinder<USkeletalMesh> SM(TEXT("SkeletalMesh'/Game/ParagonGreystone/Characters/Heroes/Greystone/Meshes/Greystone.Greystone'"));

	if (SM.Succeeded()) {
		// Mesh는 private라 안되고, GetMesh를 사용
		GetMesh()->SetSkeletalMesh(SM.Object);
	}
}

//
...
//

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

	// 키보드 Axis와 함수를 연결시키는 구문
	PlayerInputComponent->BindAxis(TEXT("UpDown"), this, &AMyCharacter::UpDown);
	PlayerInputComponent->BindAxis(TEXT("LeftRight"), this, &AMyCharacter::LeftRight);
}

void AMyCharacter::UpDown(float Value)
{
	if (Value == 0.f)
		return;

	UE_LOG(LogTemp, Warning, TEXT("UpDown %f"), Value);
	AddMovementInput(GetActorForwardVector(), Value);
}

void AMyCharacter::LeftRight(float Value)
{
	if (Value == 0.f)
		return;

	UE_LOG(LogTemp, Warning, TEXT("LeftRight %f"), Value);
	AddMovementInput(GetActorRightVector(), Value);
}

언리얼은 일관적으로 컴포넌트를 헤더에서 선언하고, cpp파일에서 컴포넌트를 할당하는 것 같다.

 

SetupAttachment가 무엇인지 의문이었다.

언리얼의 컴포넌트들은 root 컴포넌트를 기준으로 조립되기에, 따라서 SpringArm 컴포넌트를 CapsuleComponent에 붙여준다. (parent로 설정)

 

추가로 마우스를 통해 카메라를 회전하는 법을 알아보자.

엔진 세팅 → 프로젝트 세팅 → 엔진 입력 → 바인딩 에서 축 매핑을 Yaw로 추가하고 매핑에 마우스 x축을 추가하자.

스크립트에서 MyCharacter.h에 public 으로 Yaw함수를 추가하고, cpp 파일에

void AMyCharacter::Yaw(float Value)
{
	AddControllerYawInput(Value);
}

위와 같이 함수를 추가하자. 마우스를 이용한 입력에 관해 언리얼에 미리 선언되어있는 AddControllerYawInput을 사용한다.

 

참, 입력을 추가할때에는 SetupPlayerInputComponent에 BindAxis를 추가하는 것을 잊지말자. 깜박해놓고 왜 안되나 싶었네

그래도 안된다는 것은 이유가 뭘까.. 지금 마우스를 원격으로 조종하고 있는데 그것 때문인가. 있다가 집가서 직접 마우스를 움직여봐야겠다.

 

공부한 프로젝트를 github에 올리려 했는데, 보통 Config, Content, Source, .uproject만을 포함시켜 올리지만, Content의용량이 너무 커서 Content를 제외시키고 업로드 하였다. 찾아보니 실제 현업에서도 Content의 경우 외장하드로 공유하는 방법이 빠르다고 한다.

반응형

관련글 더보기