초보 코린이의 성장 일지

UE4 Component, BackStep 본문

언리얼

UE4 Component, BackStep

코오린이 2023. 4. 27. 18:14

Player 캐릭터 이동 및 빽스탭 모션 2가지를 만들어볼 것이다.

1. Player 움직이기 위한 컴포넌트 하나 생성.

더보기
#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "CMovementComponent.generated.h"

UENUM()
enum class ESpeedType : uint8
{
	Walk = 0, Run, Sprint, Max,
};

UCLASS()
class U2212_06_API UCMovementComponent : public UActorComponent
{
	GENERATED_BODY()

private:
	// 카메라 감도
	UPROPERTY(EditAnywhere, Category = "CameraSpeed")
		float HorizontalLook = 45;

	UPROPERTY(EditAnywhere, Category = "CameraSpeed")
		float VerticalLook = 45;

private:
	// 외부 속도 세팅
	UPROPERTY(EditAnywhere, Category = "Speed")
		float Speed[(int32)ESpeedType::Max] = { 200, 400, 600 };

public:
	// 외부로 return 하게 만들어 준다.
	FORCEINLINE bool CanMove() { return bCanMove; }
	FORCEINLINE void Move() { bCanMove = true; }
	FORCEINLINE void Stop() { bCanMove = false; }

	FORCEINLINE float GetWalkSpeed() { return Speed[(int32)ESpeedType::Walk]; }
	FORCEINLINE float GetRunSpeed() { return Speed[(int32)ESpeedType::Run]; }
	FORCEINLINE float GetSprintSpeed() { return Speed[(int32)ESpeedType::Sprint]; }

	FORCEINLINE bool GetFixedCamera() { return bFixedCamera; }
	FORCEINLINE void EnableFixedCamera() { bFixedCamera = true; }
	FORCEINLINE void DisableFixedCamera() { bFixedCamera = false; }

public:	
	UCMovementComponent();

protected:
	virtual void BeginPlay() override;

private:
	void SetSpeed(ESpeedType InType);

public:
	void OnSprint(); // 스피드 세팅에 사용
	void OnRun();
	void OnWalk();

	void EnableControlRotation();
	void DisableControlRotation();

public:
	// 캐릭터 이동할 함수
	void OnMoveForward(float InAxis);
	void OnMoveRight(float InAxis);
	void OnHorizontalLook(float InAxis);
	void OnVerticalLook(float InAxis);

private:
	class ACharacter* OwnerCharacter;

private:
	// 이동할수 있나, 카메라 고정인가 판단
	bool bCanMove = true;
	bool bFixedCamera;
};
#include "Components/CMovementComponent.h"
#include "Global.h"
#include "GameFramework/Character.h"
#include "GameFramework/CharacterMovementComponent.h"

UCMovementComponent::UCMovementComponent()
{

}

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

	OwnerCharacter = Cast<ACharacter>(GetOwner());
}

void UCMovementComponent::SetSpeed(ESpeedType InType)
{
	OwnerCharacter->GetCharacterMovement()->MaxWalkSpeed = Speed[(int32)InType];
}

void UCMovementComponent::OnSprint()
{
	SetSpeed(ESpeedType::Sprint);
}

void UCMovementComponent::OnRun()
{
	SetSpeed(ESpeedType::Run);
}

void UCMovementComponent::OnWalk()
{
	SetSpeed(ESpeedType::Walk);
}

void UCMovementComponent::EnableControlRotation()
{
	OwnerCharacter->bUseControllerRotationYaw = true;
	OwnerCharacter->GetCharacterMovement()->bOrientRotationToMovement = false;
}

void UCMovementComponent::DisableControlRotation()
{
	OwnerCharacter->bUseControllerRotationYaw = false;
	OwnerCharacter->GetCharacterMovement()->bOrientRotationToMovement = true;
}

void UCMovementComponent::OnMoveForward(float InAxis)
{
	CheckFalse(bCanMove);

	FRotator rotator = FRotator(0, OwnerCharacter->GetControlRotation().Yaw, 0);
	FVector direction = FQuat(rotator).GetForwardVector();

	OwnerCharacter->AddMovementInput(direction, InAxis);
}

void UCMovementComponent::OnMoveRight(float InAxis)
{
	CheckFalse(bCanMove);

	FRotator rotator = FRotator(0, OwnerCharacter->GetControlRotation().Yaw, 0);
	FVector direction = FQuat(rotator).GetRightVector();

	OwnerCharacter->AddMovementInput(direction, InAxis);
}

void UCMovementComponent::OnHorizontalLook(float InAxis)
{
	CheckTrue(bFixedCamera);

	OwnerCharacter->AddControllerYawInput(InAxis * HorizontalLook * GetWorld()->GetDeltaSeconds());
}

void UCMovementComponent::OnVerticalLook(float InAxis)
{
	CheckTrue(bFixedCamera);

	OwnerCharacter->AddControllerPitchInput(InAxis * VerticalLook * GetWorld()->GetDeltaSeconds());
}

1. 플레이어에 기본적인 움직임과, 마우스로 카메라 이동을 만들어줬다.

2. 함수들은 전형적으로 InLine으로 빼줘서 활용할 수 있도록 해준다.

1. UCLASS 안에 의미는 만일 프로그래머들이 제어를 하게될때는 안에 있는 내용을 삭제시켜주고 진행해야 한다.

2. 하지만 디자이너들이나 다른이가 접근하여 수정이 필요로하고 추가를해야하는 작업을 진행하고 있다면 필요로 인해 열어줘야한다.

3. 알고 넘어가면 좋을거 같다.

 


실제로 움직이기 위해 이벤트 연결을 Type에 해줄 것이다.

1. State컴포넌트를 하나 생성해준다.

더보기
#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "CStateComponent.generated.h"

UENUM()
enum class EStateType : uint8
{
	Idle = 0, BackStep, Equip, Hitted, Dead, Action, Max,
};
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FStateTypeChanged, EStateType, InPrevType, EStateType, InNewType);

UCLASS()
class U2212_06_API UCStateComponent : public UActorComponent
{
	GENERATED_BODY()

public:
	// 상태 변경 이벤트
	FORCEINLINE bool IsIdleMode() { return Type == EStateType::Idle; }
	FORCEINLINE bool IsBackstepMode() { return Type == EStateType::BackStep; }
	FORCEINLINE bool IsEquipMode() { return Type == EStateType::Equip; }
	FORCEINLINE bool IsHittedMode() { return Type == EStateType::Hitted; }
	FORCEINLINE bool IsDeadMode() { return Type == EStateType::Dead; }
	FORCEINLINE bool IsActionMode() { return Type == EStateType::Action; }

public:	
	UCStateComponent();

protected:
	virtual void BeginPlay() override;		

public:
	void SetIdleMode();
	void SetBackStepMode();
	void SetEquipMode();
	void SetHittedMode();
	void SetDeadMode();
	void SetActionMode();

private:
	void ChangeType(EStateType InType);

public:
	FStateTypeChanged OnStateTypeChanged;

private:
	EStateType Type;
};
#include "Components/CStateComponent.h"
#include "Global.h"

UCStateComponent::UCStateComponent()
{

}

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

}

void UCStateComponent::SetIdleMode()
{
	ChangeType(EStateType::Idle);
}

void UCStateComponent::SetBackStepMode()
{
	ChangeType(EStateType::BackStep);
}

void UCStateComponent::SetEquipMode()
{
	ChangeType(EStateType::Equip);
}

void UCStateComponent::SetHittedMode()
{
	ChangeType(EStateType::Hitted);
}

void UCStateComponent::SetDeadMode()
{
	ChangeType(EStateType::Dead);
}

void UCStateComponent::SetActionMode()
{
	ChangeType(EStateType::Action);
}

void UCStateComponent::ChangeType(EStateType InType)
{
	EStateType prevType = Type;
	Type = InType;

	if (OnStateTypeChanged.IsBound())
		OnStateTypeChanged.Broadcast(prevType, Type);
}

1. 기본적인 동작들을 위한 State를 정의해주었다.

2. Type가 변경될 수 있도록 세팅을 한 상태.

3. 몽타주를 받아와서 동작이 이뤄지도록 할 것이다.


엑셀 파일로 정보를 넘겨주어 몽타주를 실행시킬 것이다.

1. 몽타주를 관리해줄 컴포넌트를 생성해준다.

2. 엑셀로 작업을 하기위함으로 블루프린트때와 같이 행구조를 선택하여 그 값을 읽어들여오는 식으로 구현할 것이다.

1. 저장할때 파일 형식 중요.

2. 엑셀안에 구조체 값을 받아올 수 있도록 넣어준다.

1. 임포트를 해준다.

2. 상속받은 MontagesData로 설정.

3. 정상적으로 설정값이 들어와 있는걸 확인할 수 있다.

 

더보기
#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "Components/CStateComponent.h"
#include "Engine/DataTable.h"
#include "CMontagesComponent.generated.h"

USTRUCT() // 테이블 구조체
struct FMontagesData 
	: public FTableRowBase
{
	GENERATED_BODY()

public:
	// 타입
	UPROPERTY(EditAnywhere)
		EStateType Type;

	// 모션
	UPROPERTY(EditAnywhere)
		class UAnimMontage* Montage;

	// 플레이 속도
	UPROPERTY(EditAnywhere)
		float PlayRate = 1;
};

UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class U2212_06_API UCMontagesComponent : public UActorComponent
{
	GENERATED_BODY()

private:
	// 엑셀로 작업한 Data 받아준다.
	UPROPERTY(EditAnywhere, Category = "DataTable")
		UDataTable* DataTable;

public:	
	UCMontagesComponent();

protected:
	virtual void BeginPlay() override;

public:
	void PlayBackStepMode(); // 빽스탭

private:
	void PlayAnimMontage(EStateType InType); // 타입 변경용

private:
	class ACharacter* OwnerCharacter;
	FMontagesData* Datas[(int32)EStateType::Max];
};
#include "Components/CMontagesComponent.h"
#include "Global.h"
#include "GameFramework/Character.h"

// 아래 define는 디버깅용
//#define LOG_UCMontagesComponent 1

UCMontagesComponent::UCMontagesComponent()
{

}

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

	if (DataTable == nullptr) // 데이터 테이블이 없다면 경고 띄어주기.
	{
		GLog->Log(ELogVerbosity::Error, "DataTable is not selected");

		return;
	}

	OwnerCharacter = Cast<ACharacter>(GetOwner());

	TArray<FMontagesData*> datas;
	DataTable->GetAllRows<FMontagesData>("", datas); // GetAllRows 전체 읽어오기

	// 매칭해서 타입을 배열에 넣는다.
	for (int32 i = 0; i < (int32)EStateType::Max; i++)
	{
		for (FMontagesData* data : datas)
		{
			// 타입이 data 타입과 같다면 넣어준다.
			if ((EStateType)i == data->Type)
			{
				Datas[i] = data;

				continue; // 패스해주기.
			}
		}//for(data)
	}//for(i)


#if LOG_UCMontagesComponent
	for (FMontagesData* data : datas)
	{
		if (data == nullptr)
			continue;

		FString str; // Static가 붙는 유형은 리플렉션이다.
		str.Append(StaticEnum<EStateType>()->GetValueAsString(data->Type)); // GetValueAsString이 Enum을 문자형으로 바꿔준다.
		str.Append(" / ");
		str.Append(data->Montage->GetPathName());

		CLog::Log(str);
	}
#endif
}

void UCMontagesComponent::PlayBackStepMode()
{
	PlayAnimMontage(EStateType::BackStep);
}

void UCMontagesComponent::PlayAnimMontage(EStateType InType)
{
	CheckNull(OwnerCharacter); // Owner이 없으면 하면 안된다.

	FMontagesData* data = Datas[(int32)InType];

	// 두가지 조건들 중 하나라도 맞다면 오류를 띄어준다.
	if (data == nullptr || data->Montage == nullptr)
	{
		GLog->Log(ELogVerbosity::Error, "None montages data");

		return;
	}

	OwnerCharacter->PlayAnimMontage(data->Montage, data->PlayRate); // 담아놓은 값 연결.
}

1. 구조체 엑셀표에 있는 데이터를 받아주기 위함.

2. 캐릭터가 사용할 수 있도록 체크를 해주고 연결.


플레이어가 빽스탭 모션을 할 수 있도록 연결 및 조건 체크.

더보기
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "Components/CStateComponent.h"
#include "CPlayer.generated.h"

UCLASS()
class U2212_06_API ACPlayer : public ACharacter
{
	GENERATED_BODY()

private:
	UPROPERTY(VisibleAnywhere)
		class USpringArmComponent* SpringArm;

	UPROPERTY(VisibleAnywhere)
		class UCameraComponent* Camera;

private:
	UPROPERTY(VisibleAnywhere)
		class UCMontagesComponent* Montages;

	UPROPERTY(VisibleAnywhere)
		class UCMovementComponent* Movement;

	UPROPERTY(VisibleAnywhere)
		class UCStateComponent* State;

public:
	ACPlayer();

protected:
	virtual void BeginPlay() override;

public:
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

private:
	UFUNCTION()
		void OnStateTypeChanged(EStateType InPrevType, EStateType InNewType);

private:
	void OnAvoid(); // 이벤트로 콜 될 동작들은 On을 붙여줘서 구별

private:
	void BackStep();
};
#include "Characters/CPlayer.h"
#include "Global.h"
#include "CAnimInstance.h"
#include "GameFramework/SpringArmComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Camera/CameraComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Components/InputComponent.h"
#include "Components/CMontagesComponent.h"
#include "Components/CMovementComponent.h"

ACPlayer::ACPlayer()
{
	// 생성
	CHelpers::CreateComponent<USpringArmComponent>(this, &SpringArm, "SpringArm", GetMesh());
	CHelpers::CreateComponent<UCameraComponent>(this, &Camera, "Camera", SpringArm);

	// 구조체 내용들
	CHelpers::CreateActorComponent<UCMontagesComponent>(this, &Montages, "Montages");
	CHelpers::CreateActorComponent<UCMovementComponent>(this, &Movement, "Movement");
	CHelpers::CreateActorComponent<UCStateComponent>(this, &State, "State");

	GetMesh()->SetRelativeLocation(FVector(0, 0, -90));
	GetMesh()->SetRelativeRotation(FRotator(0, -90, 0));

	USkeletalMesh* mesh;
	CHelpers::GetAsset<USkeletalMesh>(&mesh, "SkeletalMesh'/Game/Character/Mesh/SK_Mannequin.SK_Mannequin'");
	GetMesh()->SetSkeletalMesh(mesh);

	TSubclassOf<UCAnimInstance> animInstance;
	CHelpers::GetClass<UCAnimInstance>(&animInstance, "AnimBlueprint'/Game/ABP_Character.ABP_Character_C'");
	GetMesh()->SetAnimClass(animInstance);


	SpringArm->SetRelativeLocation(FVector(0, 0, 140));
	SpringArm->SetRelativeRotation(FRotator(0, 90, 0));
	SpringArm->TargetArmLength = 200;
	SpringArm->bDoCollisionTest = false;
	SpringArm->bUsePawnControlRotation = true;
	SpringArm->bEnableCameraLag = true;

	GetCharacterMovement()->RotationRate = FRotator(0, 720, 0);
}

void ACPlayer::BeginPlay()
{
	Super::BeginPlay();
	
	Movement->OnRun(); // 시작하면 기본 Run 상태
	Movement->DisableControlRotation(); // 꺼주고 시작

	State->OnStateTypeChanged.AddDynamic(this, &ACPlayer::OnStateTypeChanged);
}

void ACPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	// 움직임은 전부 Movement안에 있으므로 불러와준다.
	PlayerInputComponent->BindAxis("MoveForward", Movement, &UCMovementComponent::OnMoveForward);
	PlayerInputComponent->BindAxis("MoveRight", Movement, &UCMovementComponent::OnMoveRight);
	PlayerInputComponent->BindAxis("VerticalLook", Movement, &UCMovementComponent::OnVerticalLook);
	PlayerInputComponent->BindAxis("HorizontalLook", Movement, &UCMovementComponent::OnHorizontalLook);

	// 걷고, 달리기 키 연결
	PlayerInputComponent->BindAction("Sprint", EInputEvent::IE_Pressed, Movement, &UCMovementComponent::OnSprint);
	PlayerInputComponent->BindAction("Sprint", EInputEvent::IE_Released, Movement, &UCMovementComponent::OnRun);

	// 회피 이벤트, 자기자신의 OnAvoid 콜해주기.
	PlayerInputComponent->BindAction("Avoid", EInputEvent::IE_Pressed, this, &ACPlayer::OnAvoid);
}

void ACPlayer::OnStateTypeChanged(EStateType InPrevType, EStateType InNewType)
{
	switch (InNewType) 
	{
		// 새로 들어온 Type이 BackStop라면 끝내기.
		case EStateType::BackStep: BackStep(); break; 
	}
}

void ACPlayer::OnAvoid()
{
	// 그냥 누르면 회피를 하는게 아닌 뒤로가는 방향키 + 회피키를 눌러야 나오도록 만들어 줄 것이다.
	// 조건 체크
	CheckFalse(State->IsIdleMode());
	CheckFalse(Movement->CanMove()); // 이동할 수 있을때만 나가도록 해야하므로, Move 체크

	// 가만히 있으면 0, 앞으로 누르면 1, 뒤로 누르면 -1 조건
	CheckTrue(InputComponent->GetAxisValue("MoveForward") >= 0.0f);

	// 뒤로 눌렀을때 -1일때만 빽스텝 모션이 나오도록 설정.
	State->SetBackStepMode();
}

void ACPlayer::BackStep()
{
	// 정면을 바라본 상태로 뒤로 띄어야 하므로, 카메라 정면을 바라보도록 하기 위해 EnableControlRotation 켜준다.
	Movement->EnableControlRotation(); 

	Montages->PlayBackStepMode(); // 몽타주를 설정해줬기 때문에 모션이 나오게 된다.
}

1. 조건들을 체크해서 모션이 나오도록 만들어준다.

2. Type와 그에 알맞은 모션에서 필요한 행동들을 추가적으로 맞춰준다.


수정 전
수정 후

1. 원래 기본모드였던 상태

2. 만들어 준 모션이 나오도록 애니메이션 연결.

 

https://www.youtube.com/watch?v=PeQtzIwe_2g 

 

'언리얼' 카테고리의 다른 글

UE4 Weapon Attach, Sword Equip  (0) 2023.05.01
UE4 Notify, Attach, Weapon  (0) 2023.04.28
UE4 Tool Bar, Player  (0) 2023.04.26
UE4 Button Command, Icon  (0) 2023.04.25
UE4 Button Command  (0) 2023.04.24
Comments