[24.08.06]Unreal C++ - 폰의 제작과 조작 (이득우의 언리얼 C++ 개발의 정석)

손지·2024년 8월 6일
0

Unreal

목록 보기
35/43

플레이어가 조종할 수 있는 특수한 액터인 폰은 움직이는 액터에 조종당하는 기능이 추가된 액터다. 자동차, 비행기 등이 될수있지만 인간형 폰을 제작 하려면 여러가지를 고려해야한다.

시각적 요소 : 인간형 폰이 되려면 애니메이션이 필요하다. 애니메이션을 재생하도록 리깅(Rigging) 데이터를 추가한 메시를 스켈레탈 메시 라고 하며, 이를 관리하는 컴포넌튼는 스켈레탈 메시 컴포넌트 이다.

충돌 요소 : Collision 을 사용하면 된다. 이점은 유니티와 똑같다.

움직임 요소 : 언리얼 엔진은 FloatingPawnMovement 와 CharacterMovement 라는 두 가지 폰 무브먼트 컴포넌트를 제공한다.

내비게이션 : 폰은 언리얼 엔진의 내비게이션 시스템과 연동돼 있어서 목적지를 알려주면 스스로 목적지 까지 이동하는 길 찾기 기능을 가지고있다.

카메라 출력 : 플레이어의 모니터에 보이게 하는것이다.

우선 마켓플레이어스에서 예제를 다운받자.

그리고 프로젝트에 추가를 했으면 스크립트를 작성한다.

ABPawn.h

// 프로젝트 설정 페이지에서 저작권 공지를 작성하세요.

#pragma once

#include "ArenaBattle/ArenaBattle.h"
#include "GameFramework/Pawn.h"
#include "GameFramework/FloatingPawnMovement.h"
#include "ABPawn.generated.h"

// AABPawn 클래스 선언
// ArenaBattle API의 일부로 정의된 APawn를 상속받는 클래스
UCLASS()
class ARENABATTLE_API AABPawn : public APawn
{
	GENERATED_BODY()

public:
	// 이 폰의 기본값을 설정하는 생성자
	AABPawn();

protected:
	// 게임이 시작되거나 폰이 스폰될 때 호출되는 함수
	virtual void BeginPlay() override;

public:
	// 매 프레임마다 호출되는 함수
	virtual void Tick(float DeltaTime) override;

	// 컴포넌트 초기화 후 호출되는 함수
	virtual void PostInitializeComponents() override;

	// 폰이 새로운 컨트롤러에 의해 소유될 때 호출되는 함수
	virtual void PossessedBy(AController* NewController) override;

	// 입력에 기능을 바인딩하기 위해 호출되는 함수
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

	UPROPERTY(VisibleAnywhere, Category = Collision) 
	UCapsuleComponent* Capsule;

	UPROPERTY(VisibleAnywhere, Category = Visual)
	USkeletalMeshComponent* Mesh;

	UPROPERTY(VisibleAnywhere, Category = Movement)
	UFloatingPawnMovement* Movement;

	UPROPERTY(VisibleAnywhere, Category = Camera)
	USpringArmComponent* SpringArm;

	UPROPERTY(VisibleAnywhere, Category = Camera)
	UCameraComponent* Camera;

};

ABPawn.cpp

// 프로젝트 설정 페이지에서 저작권 공지를 작성하세요.

#include "ABPawn.h"

// 기본값을 설정하는 생성자
AABPawn::AABPawn()
{
	// 이 폰이 매 프레임마다 Tick()을 호출하도록 설정. 필요 없다면 성능 향상을 위해 꺼도 됩니다.
	PrimaryActorTick.bCanEverTick = true;

	Capsule = CreateDefaultSubobject<UCapsuleComponent>(TEXT("CAPSULE"));
	Mesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("MESH"));
	Movement = CreateDefaultSubobject<UFloatingPawnMovement>(TEXT("MOVEMENT"));
	SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SPRINGARM"));
	Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("CAMERA"));

	RootComponent = Capsule;
	Mesh->SetupAttachment(Capsule);
	SpringArm->SetupAttachment(Capsule);
	Camera->SetupAttachment(SpringArm);

	Capsule->SetCapsuleHalfHeight(88.0f);
	Capsule->SetCapsuleRadius(34.0f);
	Mesh->SetRelativeLocationAndRotation(FVector(0.0f, 0.0f, -88.0f), FRotator(0.0f, -90.0f, 0.0f));
	SpringArm->TargetArmLength = 400.0f;
	SpringArm->SetRelativeRotation(FRotator(-15.0f, 0.0f, 0.0f));

	static ConstructorHelpers::FObjectFinder<USkeletalMesh> SK_CARDBOARD(TEXT("/Game/InfinityBladeWarriors/Character/CompleteCharacters/SK_CharM_Cardboard.SK_CharM_Cardboard"));


		if (SK_CARDBOARD.Succeeded()) {
			Mesh->SetSkeletalMesh(SK_CARDBOARD.Object);
		}

}

// 게임이 시작되거나 폰이 스폰될 때 호출되는 함수
void AABPawn::BeginPlay()
{
	Super::BeginPlay();
}

// 매 프레임마다 호출되는 함수
void AABPawn::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
}

// 컴포넌트 초기화 후 호출되는 함수
void AABPawn::PostInitializeComponents() {
	Super::PostInitializeComponents();
	// 경고 수준으로 로그 메시지 출력
	ABLOG_S(Warning);
}

// 폰이 새로운 컨트롤러에 의해 소유될 때 호출되는 함수
void AABPawn::PossessedBy(AController* NewController) {
	// 경고 수준으로 로그 메시지 출력
	ABLOG_S(Warning);
	Super::PossessedBy(NewController);
}

// 입력에 기능을 바인딩하기 위해 호출되는 함수
void AABPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);
}


그리고 축 매핑을 해주고

코드를 작성하면 이렇게 로그가 출력된다 (마지막에 스크립트)

ABPlayerController.h

#pragma once

#include "ArenaBattle/ArenaBattle.h"
#include "GameFramework/PlayerController.h"
#include "ABPlayerController.generated.h"

// AABPlayerController 클래스 선언
// ArenaBattle API의 일부로 정의된 APlayerController를 상속받는 클래스
UCLASS()
class ARENABATTLE_API AABPlayerController : public APlayerController
{
	GENERATED_BODY()

public:
	// 컴포넌트 초기화 후 호출되는 함수
	virtual void PostInitializeComponents() override;

	// 폰(Pawn)을 소유할 때 호출되는 함수
	virtual void OnPossess(APawn* aPawn) override;

protected:
	virtual void SetupInputComponent() override;
	virtual void BeginPlay() override;

private:
	void LeftRight(float NewAxisValue);

};

ABPlayerController.cpp

// 프로젝트 설정 페이지에서 저작권 공지를 작성하세요.

#include "ABPlayerController.h"

// 컴포넌트 초기화 후 호출되는 함수
void AABPlayerController::PostInitializeComponents() {
    // 부모 클래스의 PostInitializeComponents 호출
    Super::PostInitializeComponents();
    // 경고 수준으로 로그 메시지 출력
    ABLOG_S(Warning);
}

// 폰(Pawn)을 소유할 때 호출되는 함수
void AABPlayerController::OnPossess(APawn* aPawn) {
    // 경고 수준으로 로그 메시지 출력
    ABLOG_S(Warning);
    // 부모 클래스의 OnPossess 호출
    Super::OnPossess(aPawn);
}

void AABPlayerController::SetupInputComponent() {
    Super::SetupInputComponent();
    InputComponent->BindAxis(TEXT("LeftRight"), this, &AABPlayerController::LeftRight);
}

void AABPlayerController::LeftRight(float NewAxisValue) {
    //nothing
}

void AABPlayerController::BeginPlay() {
    Super::BeginPlay();

    FInputModeGameOnly InputMode;
    SetInputMode(InputMode);
}

그리고 예제에 있는 애니메이션을 추가해준다.


일단은 요로코롬 ㅎ

ABPawn.h

// 프로젝트 설정 페이지에서 저작권 공지를 작성하세요.

#pragma once

#include "ArenaBattle/ArenaBattle.h"
#include "GameFramework/Pawn.h"
#include "GameFramework/FloatingPawnMovement.h"
#include "ABPawn.generated.h"

// AABPawn 클래스 선언
// ArenaBattle API의 일부로 정의된 APawn를 상속받는 클래스
UCLASS()
class ARENABATTLE_API AABPawn : public APawn
{
	GENERATED_BODY()

public:
	// 이 폰의 기본값을 설정하는 생성자
	AABPawn();


protected:
	// 게임이 시작되거나 폰이 스폰될 때 호출되는 함수
	virtual void BeginPlay() override;

public:
	// 매 프레임마다 호출되는 함수
	virtual void Tick(float DeltaTime) override;

	// 컴포넌트 초기화 후 호출되는 함수
	virtual void PostInitializeComponents() override;

	// 폰이 새로운 컨트롤러에 의해 소유될 때 호출되는 함수
	virtual void PossessedBy(AController* NewController) override;

	// 입력에 기능을 바인딩하기 위해 호출되는 함수
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

	UPROPERTY(VisibleAnywhere, Category = Collision) 
	UCapsuleComponent* Capsule;

	UPROPERTY(VisibleAnywhere, Category = Visual)
	USkeletalMeshComponent* Mesh;

	UPROPERTY(VisibleAnywhere, Category = Movement)
	UFloatingPawnMovement* Movement;

	UPROPERTY(VisibleAnywhere, Category = Camera)
	USpringArmComponent* SpringArm;

	UPROPERTY(VisibleAnywhere, Category = Camera)
	UCameraComponent* Camera;
private:
	void UpDown(float NewAxisValue);
	void LeftRight(float NewAxissValue);
};

ABPawn.cpp

// 프로젝트 설정 페이지에서 저작권 공지를 작성하세요.

#include "ABPawn.h"

// 기본값을 설정하는 생성자
AABPawn::AABPawn()
{
	// 이 폰이 매 프레임마다 Tick()을 호출하도록 설정. 필요 없다면 성능 향상을 위해 꺼도 됩니다.
	PrimaryActorTick.bCanEverTick = true;

	Capsule = CreateDefaultSubobject<UCapsuleComponent>(TEXT("CAPSULE"));
	Mesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("MESH"));
	Movement = CreateDefaultSubobject<UFloatingPawnMovement>(TEXT("MOVEMENT"));
	SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SPRINGARM"));
	Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("CAMERA"));

	RootComponent = Capsule;
	Mesh->SetupAttachment(Capsule);
	SpringArm->SetupAttachment(Capsule);
	Camera->SetupAttachment(SpringArm);

	Capsule->SetCapsuleHalfHeight(88.0f);
	Capsule->SetCapsuleRadius(34.0f);
	Mesh->SetRelativeLocationAndRotation(FVector(0.0f, 0.0f, -88.0f), FRotator(0.0f, -90.0f, 0.0f));
	SpringArm->TargetArmLength = 400.0f;
	SpringArm->SetRelativeRotation(FRotator(-15.0f, 0.0f, 0.0f));

	static ConstructorHelpers::FObjectFinder<USkeletalMesh> SK_CARDBOARD(TEXT("/Game/InfinityBladeWarriors/Character/CompleteCharacters/SK_CharM_Cardboard.SK_CharM_Cardboard"));


		if (SK_CARDBOARD.Succeeded()) {
			Mesh->SetSkeletalMesh(SK_CARDBOARD.Object);
		}

		Mesh->SetAnimationMode(EAnimationMode::AnimationBlueprint);

		static ConstructorHelpers::FClassFinder<UAnimInstance> WARRIOR_ANIM(TEXT("/Game/Animation/WarriorAnimBlueprint.WarriorAnimBlueprint"));

		if (WARRIOR_ANIM.Succeeded()) {
			Mesh->SetAnimInstanceClass(WARRIOR_ANIM.Class);
		}
}

// 게임이 시작되거나 폰이 스폰될 때 호출되는 함수
void AABPawn::BeginPlay()
{
	Super::BeginPlay();

	Mesh->SetAnimationMode(EAnimationMode::AnimationSingleNode);
	UAnimationAsset* AnimAsset = LoadObject<UAnimationAsset>(nullptr, TEXT("/Game/Animation/WarriorRun.WarriorRun"));

	if (AnimAsset != nullptr) {
		Mesh->PlayAnimation(AnimAsset, true);
	}
}

// 매 프레임마다 호출되는 함수
void AABPawn::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
}

// 컴포넌트 초기화 후 호출되는 함수
void AABPawn::PostInitializeComponents() {
	Super::PostInitializeComponents();
	// 경고 수준으로 로그 메시지 출력
	ABLOG_S(Warning);
}

// 폰이 새로운 컨트롤러에 의해 소유될 때 호출되는 함수
void AABPawn::PossessedBy(AController* NewController) {
	// 경고 수준으로 로그 메시지 출력
	ABLOG_S(Warning);
	Super::PossessedBy(NewController);
}

// 입력에 기능을 바인딩하기 위해 호출되는 함수
void AABPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	PlayerInputComponent->BindAxis(TEXT("UpDown"), this, &AABPawn::UpDown);
	PlayerInputComponent->BindAxis(TEXT("LeftRight"), this, &AABPawn::LeftRight);

}

void AABPawn::UpDown(float NewAxisValue) {
	//ABLOG(Warning, TEXT("%f"), NewAxisValue);
	AddMovementInput(GetActorForwardVector(), NewAxisValue);
}

void AABPawn::LeftRight(float NewAxisValue) {
	//ABLOG(Warning, TEXT("%f"), NewAxisValue);
	AddMovementInput(GetActorRightVector(), NewAxisValue);
}
profile
언리얼 개발자가 될사람

0개의 댓글