플레이어가 조종할 수 있는 특수한 액터인 폰은 움직이는 액터에 조종당하는 기능이 추가된 액터다. 자동차, 비행기 등이 될수있지만 인간형 폰을 제작 하려면 여러가지를 고려해야한다.
시각적 요소 : 인간형 폰이 되려면 애니메이션이 필요하다. 애니메이션을 재생하도록 리깅(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);
}