Showing

[Unreal] 플레이어 폰 세팅과 이동 애니메이션 본문

Unreal

[Unreal] 플레이어 폰 세팅과 이동 애니메이션

RabbitCode 2023. 11. 24. 22:12

폰 vs 캐릭터

게임을 제작할 때 인간형 폰을 주인공으로 만드는 케이스가 많다.

이번 포스팅은 일반 폰 클래스로 인간형 폰 구현에 대해 상세히 작성해보도록 한다.

다만, 인간형 폰을 구현하는 데에는 폰을 상속받은 캐릭터 클래스를 쓰는 것이 훨씬 용이할 것이다.

(언리얼엔진이 만든 게임플레이 프레임워 안에 캐릭터 클래스가 들어있다.)

 

이번 포스팅에서는 플로팅폰무브먼트(공중에 있어도 떨어지지 않음, 중력을 받지 않기 때문) 컴포넌트를 쓸 것이고

다음 포스팅에서는 캐릭터무브먼트 컴포넌트(허공에 있으면 떨어짐-> 점프, 걷기, 수영하기, 기어가기 등 다양한 행동 양식 가능해짐)를 쓴다는 것이 주요한 차이점이다.

기본 시각, 물리 컴포넌트 세팅

- 캡슐 컴포넌트(충돌처리를 위한 루트컴포넌트로 캐릭터 절반 높이와 몸둘레 설정)

- 스켈레탈메쉬(캐릭터 렌더링과 애니메이션)

- 플로팅폰무브먼트 컴포넌트(입력에 따라 캐릭터의 움직임 조정)

- 스프링암 컴포넌트(3인칭 시점으로 카메라 구도를 편리하게 설정할 수 있는 컴포넌트로 카메라 지지대의 길이와 컴포넌트의 회전을 설정)

- 카메라 컴포넌트(가상세계의 모습을 플레이어 화면에 전송)

#pragma once

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

UCLASS()
class TRACESOFTHEFOX_API ASPlayerPawn : public APawn
{
	GENERATED_BODY()

public:
	ASPlayerPawn();

private:
    UPROPERTY(EditAnywhere, Category = "ASPlayerPawn")
    TObjectPtr<class UCapsuleComponent> CapsuleComponent;

    UPROPERTY(EditAnywhere, Category = "ASPlayerPawn")
    TObjectPtr<class USkeletalMeshComponent> SkeletalMesh;

    UPROPERTY(EditAnywhere, Category = "ASPlayerPawn")
    TObjectPtr<class UFloatingPawnMovement> FloatingPawnMovement;

    UPROPERTY(EditAnywhere, Category = "ASPlayerPawn")
    TObjectPtr<class USpringArmComponent> SpringArmComponent;

    UPROPERTY(EditAnywhere, Category = "ASPlayerPawn")
    TObjectPtr<class UCameraComponent> CameraComponent;
};

BP_playerPawn을 만들어서 임시로 스켈레탈 메쉬를 입혀서 수치를 재준다.

 

내가 사용할 여성 캐릭터 모델의 height는 180이다. 그러므로 halfHeight는 90

radius는 40으로 지정

 

cpp 반영

#include "Characters/SPlayerPawn.h"

#include "Components/CapsuleComponent.h"
#include "GameFramework/FloatingPawnMovement.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"

// Sets default values
ASPlayerPawn::ASPlayerPawn()
{
	PrimaryActorTick.bCanEverTick = false;
    float CharacterHalfHeight = 90.f;
    float CharacterRadius = 40.f;

    // capsule component
    CapsuleComponent = CreateDefaultSubobject<UCapsuleComponent>(TEXT("CapsuleComponent"));
    SetRootComponent(CapsuleComponent);
    CapsuleComponent->SetCapsuleHalfHeight(CharacterHalfHeight);
    CapsuleComponent->SetCapsuleRadius(CharacterRadius);

    // skeletal mesh component
    SkeletalMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("SkeletalMesh"));
    SkeletalMesh->SetupAttachment(GetRootComponent());
    FVector PivotPosition(0.f, 0.f, -CharacterHalfHeight);
    FRotator PivotRotation(0.f, -90.f, 0.f);
    SkeletalMesh->SetRelativeLocationAndRotation(PivotPosition, PivotRotation);

    // spring arm component
    SpringArmComponent = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArmComponent"));
    SpringArmComponent->SetupAttachment(GetRootComponent());
    SpringArmComponent->TargetArmLength = 400.0f;
    SpringArmComponent->SetRelativeRotation(FRotator(-15.f, 0.f, 0.f)); // 약간 비스듬하게 볼 수 있는 각도

    // camera component
    CameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComponent"));
    CameraComponent->SetupAttachment(SpringArmComponent);

    // floating pawn movement component
    FloatingPawnMovement = CreateDefaultSubobject<UFloatingPawnMovement>(TEXT("FloatingPawnMovement"));

    // Don't rotate the controller's yaw
    //bUseControllerRotationYaw = false;
}

언리얼에서는 피봇 포지션과 피봇로테이션을 위와 같이 변경해주어야 아래와 같이 정상적으로 피봇이 맞아 떨어지게 된다.

카메라를 카메라지지대(카메라암)에 붙이는 이유는 자식컴포넌트로 지지대가 휘면 카메라도 같이 움직일 수 있도록 하기 위함이다.

코드를 주석없이 깔끔하게 보고 싶다면 아래와 같이 작성하여도 무방하다.

#include "Characters/SPlayerPawn.h"

#include "Components/CapsuleComponent.h"
#include "GameFramework/FloatingPawnMovement.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"

ASPlayerPawn::ASPlayerPawn()
{
	PrimaryActorTick.bCanEverTick = false;
    float CharacterHalfHeight = 90.f;
    float CharacterRadius = 40.f;
#pragma region capsule component
    CapsuleComponent = CreateDefaultSubobject<UCapsuleComponent>(TEXT("CapsuleComponent"));
    SetRootComponent(CapsuleComponent);
    CapsuleComponent->SetCapsuleHalfHeight(CharacterHalfHeight);
    CapsuleComponent->SetCapsuleRadius(CharacterRadius);
#pragma endregion
#pragma region skeletal mesh component
    SkeletalMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("SkeletalMesh"));
    SkeletalMesh->SetupAttachment(GetRootComponent());
    FVector PivotPosition(0.f, 0.f, -CharacterHalfHeight);
    FRotator PivotRotation(0.f, -90.f, 0.f);
    SkeletalMesh->SetRelativeLocationAndRotation(PivotPosition, PivotRotation);
#pragma endregion
#pragma region spring arm component
    SpringArmComponent = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArmComponent"));
    SpringArmComponent->SetupAttachment(GetRootComponent());
    SpringArmComponent->TargetArmLength = 400.0f;
    SpringArmComponent->SetRelativeRotation(FRotator(-15.f, 0.f, 0.f));
#pragma endregion
#pragma region camera component
    CameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComponent"));
    CameraComponent->SetupAttachment(SpringArmComponent);
#pragma endregion
    // floating pawn movement component 이동하기 위함
    FloatingPawnMovement = CreateDefaultSubobject<UFloatingPawnMovement>(TEXT("FloatingPawnMovement"));

    // Don't rotate the controller's yaw
    //bUseControllerRotationYaw = false;
}

 

컴파일 직후 

게임모드, 플레이어 컨트롤러, 플레이어 폰까지 잘(헤어가 빠지기는 했지만.....) 생성된 모습

인풋 컴포넌트 세팅

언리얼 엔진은 입력을 처리하기 위해 인풋컴포넌트를 제공한다.

인풋 컴포넌트에서 입력 처리함수를 바인드 시키면 입력 신호는 자동으로 해당 입력 처리 함수의 인자값으로 전달된다.

입력 처리 함수를 바인드하기에 적절한 이벤트 함수가 바로 SetupPlayerInputComponent이다.

텍스트 값으로 인풋이 들어오기 때문에 복사 붙여넣기를 통해 휴먼실수가 없어야겠다.

h

protected:
    virtual void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent);

private:
    void UpDown(float IinAxisValue);
    void LeftRight(float IinAxisValue);

cpp

void ASPlayerPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    Super::SetupPlayerInputComponent(PlayerInputComponent);
    PlayerInputComponent->BindAxis("UpDown", this, &ASPlayerPawn::UpDown);
    PlayerInputComponent->BindAxis("LeftRight", this, &ASPlayerPawn::LeftRight);

}

void ASPlayerPawn::UpDown(float IinAxisValue)
{
    AddMovementInput(GetActorForwardVector(), IinAxisValue);
}

void ASPlayerPawn::LeftRight(float IinAxisValue)
{
    AddMovementInput(GetActorRightVector(), IinAxisValue);
}

 

언리얼에서 이동은 몹시 다양한 방법으로 구현이 가능하다.

 

다른 방법1 Tick에서 처리

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

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
//#include "InputAction.h"
//#include "InputActionValue.h"
#include "PlayerPawn.generated.h"


UCLASS()
class GIT_TEMP_API APlayerPawn : public APawn
{
	GENERATED_BODY()

public:
	// Sets default values for this pawn's properties
	APlayerPawn();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

	//UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input)
	//class UInputMappingContext* PlayerMappingContext;

	//UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input)
	//class UInputAction* MoveAxis;

	//UInputAction* FireActionFireAction;
	//void Move(const FInputActionValue& value);
	void FireCoolTimer(float cooltime, float deltaTime);

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

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

	UPROPERTY(EditAnyWhere)
	float fireCoolTime = 2.0f;

	UPROPERTY(EditAnywhere)
	class UBoxComponent* boxComp;

	UPROPERTY(EditAnywhere)
	class UStaticMeshComponent* meshComp;

	UPROPERTY(EditAnywhere)
	float moveSpeed = 500;

	UPROPERTY(EditAnywhere)
	class UArrowComponent* firePosition;

	UPROPERTY(EditAnywhere)
	TSubclassOf<class ABullet> bulletFactory;

	UPROPERTY(EditAnywhere)
	class USoundBase* fireSound;

	UPROPERTY(EditAnywhere)
	int fireMode;

private:
	float fireTimerTime; // 타이머의 시간
	bool fireReady; // 타이머 도달 여부 

	float h;
	float v;

	void MoveHorizontal(float value);
	void MoveVertical(float value);
	void Fire();
};
// Fill out your copyright notice in the Description page of Project Settings.


#include "PlayerPawn.h"
#include "Components/BoxComponent.h"
#include "Components/ArrowComponent.h"
#include "Components/StaticMeshComponent.h"
//#include "EnhancedInputSubsystems.h"
//#include "EnhancedInputComponent.h"
#include "Bullet.h"
#include "Kismet/GameplayStatics.h"
#include "Kismet/KismetMathLibrary.h"

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

	boxComp = CreateDefaultSubobject<UBoxComponent>(TEXT("My Box Component"));
	SetRootComponent(boxComp);
	//boxComp->SetCollisionProfileName(TEXT("Player"));
	//boxComp->SetGenerateOverlapEvents(true);
	//boxComp->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
	//boxComp->SetCollisionObjectType(ECC_GameTraceChannel1);
	//boxComp->SetCollisionResponseToAllChannels(ECR_Ignore); 
	//boxComp->SetCollisionResponseToChannel(ECC_GameTraceChannel2, ECR_Overlap);
	//boxComp->SetCollisionResponseToChannel(ECC_GameTraceChannel4, ECR_Block);
	boxComp->SetCollisionProfileName(TEXT("Player"));
	
	meshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("My Static Mesh"));
	meshComp->SetupAttachment(boxComp);

	FVector boxSize = FVector(50.0f, 50.0f, 50.0f);
	boxComp->SetBoxExtent(boxSize);

	firePosition = CreateDefaultSubobject<UArrowComponent>(TEXT("FirePos"));
	firePosition->SetupAttachment(meshComp);
	
	// 총알 발사 변수 초기화
	fireTimerTime = 0;  
	fireCoolTime = 2.0f; 
	fireReady = true;    // 발사 준비 On
	fireMode = 1;
}

void APlayerPawn::BeginPlay()
{
	Super::BeginPlay();
}

void APlayerPawn::FireCoolTimer(float cooltime, float deltaTime)
{
	if (fireTimerTime < cooltime) {
		fireTimerTime += deltaTime;
	}
	else 
	{
		fireTimerTime = 0;
		fireReady = true;
	}
}

void APlayerPawn::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	FVector dir = FVector(v,h,0);
	dir.Normalize();
	FVector newLocation = GetActorLocation() + dir * moveSpeed * DeltaTime;
	SetActorLocation(newLocation, true);
	
	if (fireReady == false) {
		FireCoolTimer(fireCoolTime, DeltaTime);
	}

	FHitResult _hitResult;
	UGameplayStatics::GetPlayerController(GetWorld(), 0)->GetHitResultUnderCursor(ECC_Visibility, true, _hitResult);
	//	FRotator _dirRot = UKismetMathLibrary::FindLookAtRotation(this->GetActorLocation(),
//_hitResult.Location);
		//	FRotator _finalRot = FRotator(0, _dirRot.Yaw, 0);

	FVector Dest = FVector(_hitResult.Location.X, _hitResult.Location.Y, 0.0f);
	FVector Start = FVector(this->GetActorLocation().X, this->GetActorLocation().Y, 0.0f);
	FRotator _dirRot = UKismetMathLibrary::FindLookAtRotation(Start,
		Dest);
	SetActorRotation(_dirRot);
}

void APlayerPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);
	PlayerInputComponent->BindAxis("Horizontal",this, &APlayerPawn::MoveHorizontal);
	PlayerInputComponent->BindAxis("Vertical", this, &APlayerPawn::MoveVertical);
	PlayerInputComponent->BindAction("Fire", IE_Pressed,this, &APlayerPawn::Fire);

	//if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked< UEnhancedInputComponent>(PlayerInputComponent))
	//{
	//	EnhancedInputComponent->BindAction(MoveAxis, ETriggerEvent::Triggered, this, &APlayerPawn::Move);
	//	EnhancedInputComponent->BindAction(FireAction, ETriggerEvent::Triggered, this, &APlayerPawn::Fire);
	//}
}

void APlayerPawn::MoveHorizontal(float value) {h = value;}

void APlayerPawn::MoveVertical(float value) {v = value;}

void APlayerPawn::Fire()
{
	if (fireMode == 0) {
		// coolTime
		if (fireReady == true) {
			ABullet* bullet = GetWorld()->SpawnActor<ABullet>(bulletFactory, firePosition->GetComponentLocation()
				, firePosition->GetComponentRotation());
			fireReady = false;
			//fireSound
			//월드(어느 월드), 소리낼 사운드, 소리 날 위치
			UGameplayStatics::PlaySoundAtLocation(GetWorld(), fireSound, firePosition->GetComponentLocation());
		}
	}
	else if (fireMode == 1) {
		int bulletIndex = 1;
		if (fireReady == true) {
			for (int i = 0; i < 2; i++) {
				for (int j = 0; j < 3; j++) {
					// pos variable setting
					float zCod = -150.0f;
					float yCod = 150.0f;
					FVector posTemp = FVector(i * zCod, j * yCod, 0.0f);
					FVector targetPos = firePosition->GetComponentLocation() + posTemp;

					// produce bullet
					ABullet* bullet = GetWorld()->SpawnActor<ABullet>(bulletFactory, targetPos,firePosition->GetComponentRotation());
					bullet->SetActorScale3D(FVector(1, 1, 1));
					if (bulletIndex % 2 ==0) {
						bullet->SetActorScale3D(FVector(2, 2, 2)*2);
					}
					UGameplayStatics::PlaySoundAtLocation(GetWorld(), fireSound, targetPos);

					// bulletIndex counting
					bulletIndex += 1;
				}
			}
			// release fireReady
			fireReady = false;
		}
	}
}

다른 방법2 EnhancedInputComponent

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

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "InputAction.h"
#include "NiagaraComponent.h"
#include "NiagaraFunctionLibrary.h"
#include "MyPlayer.generated.h"

class UInputMappingContext;
class UInputAction;
class UNiagaraComponent;

UCLASS()
class THIRDPERSONTEMPLATE_API AMyPlayer : public ACharacter
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	AMyPlayer();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

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

	UPROPERTY(EditAnywhere, Category = "Anim")
	UAnimMontage* attackMontage;

public:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Camera")
	class USpringArmComponent* springArmComp;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera")
	class UCameraComponent* cameraComp;

	UPROPERTY(EditAnywhere, Category = "Input")
	UInputMappingContext* PlayerMappingContext;

	UPROPERTY(VisibleAnywhere, Category = "Weapon")
	class UStaticMeshComponent* staffMesh;

	UPROPERTY(EditAnywhere, Category = "FX")
	class UNiagaraComponent* jumpNiagara;

	UPROPERTY(EditAnywhere, Category = "Input")
	UInputAction* MoveIA;

	UPROPERTY(EditAnywhere, Category = "Input")
	UInputAction* LookUpIA;

	UPROPERTY(EditAnywhere, Category = "Input")
	UInputAction* TurnIA;

	UPROPERTY(EditAnywhere, Category = "Input")
	UInputAction* JumpIA;

	UPROPERTY(EditAnywhere, Category = "Input")
	UInputAction* FireIA;

	UPROPERTY(EditAnywhere, Category = "Input")
	UInputAction* InteractionIA;

public:
	void Move(const FInputActionValue& Value);
	void LookUp(const FInputActionValue& Value);
	void Turn(const FInputActionValue& Value);
	void InputJump(const FInputActionValue& Value);
	void InputFire(const FInputActionValue& Value);
	void InteractionPositive(const FInputActionValue& Value);
	void Fire();
	float fireTimerTime; // 타이머의 시간
	bool fireReady; // 타이머 도달 여부 
public:
	UPROPERTY(EditAnywhere,BlueprintReadOnly, Category = "Status")
	float maxHp;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Status")
	float currentHp;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Status")
	float maxMp;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Status")
	float currentMp;

	UPROPERTY(EditAnywhere, Category = "Move")
	float moveSpeed;

	UPROPERTY(EditAnywhere)
	TSubclassOf<class APBullet> bulletFactory;

	UPROPERTY(EditAnyWhere)
	float fireCoolTime = 2.0f;

	bool isJump = false;

private:
	FVector moveDirection;

protected:
	void FireCoolTimer(float cooltime, float deltaTime);
public:
	void ShowFX(bool show);
};
// Fill out your copyright notice in the Description page of Project Settings.

#include "MyPlayer.h"
#include "Components/ArrowComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
#include "EnhancedInputSubsystems.h" //https://docs.unrealengine.com/5.0/en-US/enhanced-input-in-unreal-engine/
#include "EnhancedInputComponent.h" //https://docs.unrealengine.com/4.26/en-US/InteractiveExperiences/Input/EnhancedInput/
#include "PBullet.h"
#include "NiagaraComponent.h"
#include "NiagaraFunctionLibrary.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "DrawDebugHelpers.h"

// Sets default values
AMyPlayer::AMyPlayer()
{
 	// 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;
	//ConstructorHelpers 라는 유틸리티로 에셋 가져오기(찾기)
	// InitMesh 굳이 이렇게 하기는 했으나 콘텐츠 브라우저에서 선택하는게 사실 더 좋음
	ConstructorHelpers::FObjectFinder<USkeletalMesh> InitMesh(TEXT("/Script/Engine.SkeletalMesh'/Game/MyResouce/unitychan.unitychan'"));
	if (InitMesh.Succeeded()) { // 제대로 오브젝트를 가져왔다면~
		// GetMesh는 actor(근본)의 상속자인 character에서 제공!
		GetMesh()->SetSkeletalMesh(InitMesh.Object);
		GetMesh()->SetRelativeLocationAndRotation(
			FVector(0, 0, -88)
			, FRotator(0, -90, 0));
	}
	springArmComp = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArmComp"));
	springArmComp->SetupAttachment(RootComponent);
	springArmComp->SetRelativeLocationAndRotation(FVector(0, 0, 50), FRotator(-20,0,0));
	springArmComp->TargetArmLength = 530;
	springArmComp->bUsePawnControlRotation = true;

	//카메라도 생성 초기화
	cameraComp = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComponent"));
	cameraComp->SetupAttachment(springArmComp);
	cameraComp->bUsePawnControlRotation = false;

	//staff mesh
	staffMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("staff meshComp"));// 스태프 외관 컴포넌트 등록
	// 두번째 인자 캐릭터 메쉬에 부착, FName이 문자열 속도가 빠름
	staffMesh->SetupAttachment(GetMesh(), FName("Character1_RightHandSocket"));
	//staffMesh->SetRelativeLocationAndRotation

	//pawn이 들고 있음
	bUseControllerRotationYaw = true;
	moveSpeed = 100.0f;

	maxHp = 100;
	maxMp = 100;
	currentHp = 50;
	currentMp = 50;
	fireTimerTime = 0;
	fireCoolTime = 1.85f;
	fireReady = true;    // 발사 준비 On
}

// Called when the game starts or when spawned
void AMyPlayer::BeginPlay()
{
	Super::BeginPlay();
	if (APlayerController* PlayerController = Cast<APlayerController>(GetController()))
	{
		if (UEnhancedInputLocalPlayerSubsystem* Subsystem
			= ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer())) {
			Subsystem->AddMappingContext(PlayerMappingContext, 0);
		}
	}
	jumpNiagara = GetComponentByClass<UNiagaraComponent>();
	jumpNiagara->SetVisibility(false);
}

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

	if (fireReady == false) {
		FireCoolTimer(fireCoolTime, DeltaTime);
	}

	const TArray<USceneComponent*>& AttachChildren = GetRootComponent()->GetAttachChildren();
	const int32 Count = AttachChildren.Num();


	// 이동방향을 컨트롤 방향 기준으로 변환
	moveDirection = FTransform(GetControlRotation()).TransformVector(moveDirection);

	/*
	// 플레이어 이동 - 등속운동
	FVector P0 = GetActorLocation();//현재 위치
	FVector vt = moveDirection * moveSpeed * DeltaTime; //이동거리
	FVector P = P0 + vt;
	SetActorLocation(P);
	*/

	AddMovementInput(moveDirection);

	// 방향 초기화
	// 방향이 누적되지 않게
	moveDirection = FVector::ZeroVector;
}

// Called to bind functionality to input
void AMyPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);
	if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent)) {
		EnhancedInputComponent->BindAction(MoveIA, ETriggerEvent::Triggered, this, &AMyPlayer::Move);
		EnhancedInputComponent->BindAction(LookUpIA, ETriggerEvent::Triggered, this, &AMyPlayer::LookUp);
		EnhancedInputComponent->BindAction(TurnIA, ETriggerEvent::Triggered, this, &AMyPlayer::Turn);
		EnhancedInputComponent->BindAction(JumpIA, ETriggerEvent::Triggered, this, &AMyPlayer::InputJump);
		EnhancedInputComponent->BindAction(FireIA, ETriggerEvent::Triggered, this, &AMyPlayer::InputFire);
		EnhancedInputComponent->BindAction(InteractionIA, ETriggerEvent::Started, this, &AMyPlayer::InteractionPositive);
	}

}

void AMyPlayer::Move(const FInputActionValue& Value)
{
	//UE_LOG(LogTemp, Log, TEXT("Log Message %f"), moveSpeed);
	const FVector _currentValue = Value.Get<FVector>();
	if (Controller) {
		moveDirection.Y = _currentValue.X;
		moveDirection.X = _currentValue.Y;
	}
	//FVector newLocation = GetActorLocation() + _currentValue;
	//SetActorLocation(newLocation);
}

void AMyPlayer::LookUp(const FInputActionValue& Value)
{
	// mouse y - 한 축의 값(float)
	const float _currentValue = Value.Get<float>();
	APawn::AddControllerPitchInput(_currentValue);
}

void AMyPlayer::Turn(const FInputActionValue& Value)
{
	// mouse x - 한 축의 값(float)
	const float _currentValue = Value.Get<float>();
	APawn::AddControllerYawInput(_currentValue);
}

void AMyPlayer::InputJump(const FInputActionValue& Value)
{
	Jump();
}

void AMyPlayer::InputFire(const FInputActionValue& Value)
{
	if (fireReady == true) {
		UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
		if (AnimInstance) {
			AnimInstance->Montage_Play(attackMontage);
		}
		fireReady = false;
	}
}

void AMyPlayer::InteractionPositive(const FInputActionValue& Value)
{
	//start와 end 가 중요
	FVector _Location;
	FRotator _Rotation;
	FHitResult _HitOut;

	//카메라가 보고 있는 위치
	GetController()->GetPlayerViewPoint(_Location, _Rotation);
	FVector _Start = _Location;
	int ViewDis = 2000;
	FVector _End = (_Rotation.Vector() * ViewDis); // 2000은 무작위 거리
	FCollisionQueryParams _traceParams; //trace의 params들
	GetWorld()->LineTraceSingleByChannel(_HitOut, _Start, _End, ECC_Visibility, _traceParams); // 충돌 결과, 시작점, 결과, 콜리전채널, params(안넣으면 디폴트)
	DrawDebugLine(GetWorld(), _Start, _End, FColor::Green, false, 2.0f);
}

void AMyPlayer::FireCoolTimer(float cooltime, float deltaTime)
{
	if (fireTimerTime < cooltime) {
		fireTimerTime += deltaTime;
	}
	else
	{
		fireTimerTime = 0;
		fireReady = true;
	}
}

void AMyPlayer::ShowFX(bool show)
{
	if (nullptr == jumpNiagara) {
		jumpNiagara = GetComponentByClass<UNiagaraComponent>();
	}
	jumpNiagara->SetVisibility(show);
}

void AMyPlayer::Fire()
{
	FTransform firePosition = staffMesh->GetSocketTransform(TEXT("FirePosition"));
	GetWorld()->SpawnActor<APBullet>(bulletFactory, firePosition);
}

특정 에셋 런타임 중 로딩

#pragma once

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

UCLASS()
class TRACESOFTHEFOX_API ASPlayerPawn : public APawn
{
	GENERATED_BODY()

public:
	// Sets default values for this pawn's properties
	ASPlayerPawn();

private:
    UPROPERTY(EditAnywhere, Category = "ASPlayerPawn")
    TObjectPtr<class UCapsuleComponent> CapsuleComponent;

    UPROPERTY(EditAnywhere, Category = "ASPlayerPawn")
    TObjectPtr<class USkeletalMeshComponent> SkeletalMesh;

    UPROPERTY(EditAnywhere, Category = "ASPlayerPawn")
    TObjectPtr<class UFloatingPawnMovement> FloatingPawnMovement;

    UPROPERTY(EditAnywhere, Category = "ASPlayerPawn")
    TObjectPtr<class USpringArmComponent> SpringArmComponent;

    UPROPERTY(EditAnywhere, Category = "ASPlayerPawn")
    TObjectPtr<class UCameraComponent> CameraComponent;

protected:
    virtual void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent);
    virtual void BeginPlay() override;
private:
    void UpDown(float IinAxisValue);
    void LeftRight(float IinAxisValue);
};
#include "Characters/SPlayerPawn.h"

#include "Components/CapsuleComponent.h"
#include "GameFramework/FloatingPawnMovement.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"

// Sets default values
ASPlayerPawn::ASPlayerPawn()
{
	PrimaryActorTick.bCanEverTick = false;
    float CharacterHalfHeight = 90.f;
    float CharacterRadius = 40.f;
#pragma region capsule component
    CapsuleComponent = CreateDefaultSubobject<UCapsuleComponent>(TEXT("CapsuleComponent"));
    SetRootComponent(CapsuleComponent);
    CapsuleComponent->SetCapsuleHalfHeight(CharacterHalfHeight);
    CapsuleComponent->SetCapsuleRadius(CharacterRadius);
#pragma endregion
#pragma region skeletal mesh component
    SkeletalMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("SkeletalMesh"));
    SkeletalMesh->SetupAttachment(GetRootComponent());
    FVector PivotPosition(0.f, 0.f, -CharacterHalfHeight);
    FRotator PivotRotation(0.f, -90.f, 0.f);
    SkeletalMesh->SetRelativeLocationAndRotation(PivotPosition, PivotRotation);
#pragma endregion
#pragma region spring arm component
    SpringArmComponent = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArmComponent"));
    SpringArmComponent->SetupAttachment(GetRootComponent());
    SpringArmComponent->TargetArmLength = 400.0f;
    SpringArmComponent->SetRelativeRotation(FRotator(-15.f, 0.f, 0.f));
#pragma endregion
#pragma region camera component
    CameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComponent"));
    CameraComponent->SetupAttachment(SpringArmComponent);
#pragma endregion
    // floating pawn movement component
    FloatingPawnMovement = CreateDefaultSubobject<UFloatingPawnMovement>(TEXT("FloatingPawnMovement"));

    // Don't rotate the controller's yaw
    //bUseControllerRotationYaw = false;
}

void ASPlayerPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    Super::SetupPlayerInputComponent(PlayerInputComponent);
    PlayerInputComponent->BindAxis("UpDown", this, &ASPlayerPawn::UpDown);
    PlayerInputComponent->BindAxis("LeftRight", this, &ASPlayerPawn::LeftRight);

}

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

    // Get a reference to the player character
    SkeletalMesh->SetAnimationMode(EAnimationMode::AnimationSingleNode);
    UAnimationAsset* AnimationAsset = LoadObject<UAnimationAsset>(SkeletalMesh, TEXT("/Script/Engine.AnimSequence'/Game/Haena_v02/Animation/InPlace_Weapons/A_Jogging_v02.A_Jogging_v02'"));
    if (nullptr != AnimationAsset)
    {
        SkeletalMesh->PlayAnimation(AnimationAsset,true);
    }
}

void ASPlayerPawn::UpDown(float IinAxisValue)
{
    AddMovementInput(GetActorForwardVector(), IinAxisValue);
}

void ASPlayerPawn::LeftRight(float IinAxisValue)
{
    AddMovementInput(GetActorRightVector(), IinAxisValue);
}

아래 코드가 LoadObject  핵심이다.

런타임 중에 asset을 비동기적으로 로드하겠다는 의미로, 애니메이션 에셋 타입을 설정해주고 경로를 TEXT로 지정해주었다.

    UAnimationAsset* AnimationAsset = LoadObject<UAnimationAsset>(SkeletalMesh, TEXT("/Script/Engine.AnimSequence'/Game/Haena_v02/Animation/InPlace_Weapons/A_Jogging_v02.A_Jogging_v02'"));
    if (nullptr != AnimationAsset)
    {
        SkeletalMesh->PlayAnimation(AnimationAsset,true);
    }

 

바로, 위의 코드를 똑같이 애니메이션 블루프린트로 전환해보도록 한다.

 

애니메이션 블루프린트

AnimGraph 더블클릭

원하는 애니메이션을 찾아 드래그앤 드랍만 해주면 된다.(노드에 하얀색으로 그려진 사람과 사람 모양을 연결해주어야 함)

이렇게 만든 애니메이션 블루프린트를 플레이어폰의 스켈레탈 메쉬에서 Use 애니메이션 블루프린트로 변경하면 애니메이션 블루프린트를 넣을 수 있는 창이 바로 열린다.

애니메이션 블루프린트를 넣을때 끝에 _C 가 붙어야 제대로 작동한 것이다.