필요한 개념
<구르기 동작 처리>
* Roll Montage
* Backstep Montage
* 구르기 조건
MoveForward의 Axis값이 0보다 작으면 BackStep(뒷 구르기), 아니라면 Rolling(앞 구르기)이다.
// ACPlayer
// 이벤트로 처리(ACPlayer내부에서 사용되어서) -> 다이나믹 멀티캐스트 델리게이트
// 상태 바뀔 때마다 이 함수가 콜된다.
State->OnStateTypeChanged.AddDynamic(this, &ACPlayer::OnStateTypeChanged);
private:
// 이전 상태와 현재 상태를 알 수 있어서,
UFUNCTION()
void OnStateTypeChanged(EStateType InPrevType, EStateType InNewType);
// 얘는 상태 바뀔때 마다 콜됨
void ACPlayer::OnStateTypeChanged(EStateType InPrevType, EStateType InNewType)
{
// 상태에 따라 처리가능
// Begin()은 여기서 처리해주고, End()함수들은 Notify에서 콜되도록 할 것이다.
switch (InNewType)
{
case EStateType::Roll: Begin_Roll(); break;
case EStateType::Backstep: Begin_Backstep(); break;
}
}
<CMontagesComponent 생성>
* 캐릭터 애니메이션은 데이터 테이블로 관리할 것이다.(간단하니까)
(예전에 BP할때는 데이터 테이블로 무기들을 관리했다. 그러나 차후 C에서 무기관리는 데이터 에셋으로 관리 할 것이다.)
-> CMontagesComponent 클래스 생성
CMontagesComponent : 데이터 테이블로 부터 몽타주를 불러와서 몽타주들을 관리할 컴포넌트이다.
UDataTable을 이용해서 Excel로 부터 데이터를 불러와서 처리
*Tip
.h 쪽에는 경로를 명시해주는데
ex) #include "Components/CStateComponent.h"
.cpp에서는 경로를 명시안해줌
ex) #include "CStateComponent.h"
-> 보기가 편해서
<데이터 테이블을 이용한 상태에 따른 몽타주들 관리>
// CMontagesComponent.h
#include "Engine/DataTable.h"
// DataTable에서 불러와 값을 입력해줄 구조체를 정의함
// BlueprintType : 블루프린트와 통신하기 위해
// FTableRowBase(Engine/DataTable.h)을 상속받아야 데이터 테이블로 불러들일 데이터 타입으로 사용하겠단 의미
USTRUCT(BlueprintType)
struct FMontageData : public FTableRowBase
{
GENERATED_BODY()
// 변수 선언한거랑 엑셀 타입 이름을 일치시킴(엑셀에 변수명 정확히 일치하게 써줌)
// 열이름은 마음대로 가능(그러나 중복하면 안됨)
public:
// EditAnywhere여야 데이터 테이블로부터 불러올 수 있음(에디터로부터 불러오는 거여서)
UPROPERTY(EditAnywhere)
EStateType Type;
UPROPERTY(EditAnywhere)
class UAnimMontage* AnimMontage;
UPROPERTY(EditAnywhere)
float PlayRatio = 1.0f;
UPROPERTY(EditAnywhere)
FName StartSection;
};
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class UONLINE_04_ACTION_API UCMontagesComponent : public UActorComponent
{
GENERATED_BODY()
private:
UPROPERTY(EditDefaultsOnly, Category = "DataTable")
UDataTable* DataTable;
}
* 엑셀 파일은 원본 유지 필수(프로젝트 경로내에 저장하자!)
BP에서는 구조체의 구조가 변경되어도 자동으로 불러주지만, C++에서는 자동으로 처리되지 않으므로 엑셀파일을 유지하는 것이 필요
.CSV파일(UTF가 안붙은)로 저장한다.
* 언리얼 콘턴츠 브라우저 -> 임포트 -> .csv(파일 선택)
데이터 테이블 옵션이 뜬다. 데이터 테이블 행 유형 선택에 우리가 정의한 FTableRowBase를 상속 받은 EStruct가 나온다.
확인해보면 데이터 테이블로 데이터가 다 들어와 있다.
* .csv값 수정하면 데이터 테이블 -> 에셋 -> 리임포트 해주면 값 갱신됨
Tip) 파일의 위치를 옮기면 에셋이 참조했던 기존 위치가 바뀌므로 에셋을 새로 만들어 줘서 원본 값과 경로가 같도록 만들어주는 것이 관리가 편함
그런데도 다시 임포트했는데도 안되면 파일이 꼬인거여서 프로그램을 다시 껏다 키고 리임포트하면 해결됨
BP_CPlayer 블루프린트로 가서 MontagesComponent에 DataTable에 만든 DataTable을 세팅해주면 된다.(변수를 EditDefaultsOnly로 해놔서 가능)
// CMontagesComponent.cpp
void UCMontagesComponent::BeginPlay()
{
Super::BeginPlay();
CheckNull(DataTable);
TArray<FMontageData*> datas;
// GetAllRows() : 데이터 테이블에 있는 모든 값을 가져옴(<> 타입으로)
// TArray<T*>로 리턴됨
DataTable->GetAllRows<FMontageData>("", datas);
for (const FMontageData* data : datas)
{
if (!!data)
CLog::Print(data->AnimMontage->GetPathName());
}
}
// CMontagesComponent.h
public:
// 밑에 PlayAnimMontage() 함수 각 상태를 인자로 주는 방법은 좋은 방법이 아니다.
// 그래서 각 행동에 따라 몽타주를 플레이해줄 함수를 정의(외부에서 함수 호출용)
void PlayRoll();
void PlayBackStep();
private:
// 해당 타입의 몽타주 플레이 시켜줌
void PlayAnimMontage(EStateType InState);
// CMontagesComponent.cpp
void UCMontagesComponent::PlayRoll()
{
PlayAnimMontage(EStateType::Roll);
}
void UCMontagesComponent::PlayBackStep()
{
PlayAnimMontage(EStateType::Backstep);
}
void UCMontagesComponent::PlayAnimMontage(EStateType InState)
{
ACharacter* character = Cast<ACharacter>(GetOwner());
const FMontageData* data = Datas[(int32)InState];
// 데이터가 없을 수도 있다.
if (!!data)
{
// 몽타주가 있다면 실행시킴
if (!!data->AnimMontage)
character->PlayAnimMontage(data->AnimMontage, data->PlayRatio, data->StartSection);
}
}
몽타주에 슬롯도 세팅해준다.(DefaultGroup.FullBody) -> 전체 동작에서 할 거니까
* AnimBP 몽타주에 맞게 수정
* AnimNotify를 상속 받는 클래스 생성(저번에는 AnimNotifyState 기반으로 써봤다.)
(EndRoll, EndBackstep 2가지가 클래스가 필요)
// CPlayer.cpp
void ACPlayer::Begin_Roll()
{
// 해당 방향으로 구르기 위해 꺼놈
bUseControllerRotationYaw = false;
// 회전 방향으로 돌려놈
GetCharacterMovement()->bOrientRotationToMovement = true;
// 해당 방향으로 굴러줌
FVector start = GetActorLocation();
FVector from = start + GetVelocity().GetSafeNormal2D();
SetActorRotation(UKismetMathLibrary::FindLookAtRotation(start, from));
Montages->PlayRoll();
}
void ACPlayer::Begin_Backstep()
{
// 정면을 바라본 상태에서 뒤로 뜀
bUseControllerRotationYaw = true;
GetCharacterMovement()->bOrientRotationToMovement = false;
Montages->PlayBackStep();
}
// ex) UCAnimNotify 작업 예시
FString UCAnimNotify_EndRoll::GetNotifyName_Implementation() const
{
// 노티파이 이름
return "Roll";
}
void UCAnimNotify_EndRoll::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation)
{
Super::Notify(MeshComp, Animation);
CheckNull(MeshComp);
CheckNull(MeshComp->GetOwner());
ACPlayer* player = Cast<ACPlayer>(MeshComp->GetOwner());
CheckNull(player);
// 실행 내용
player->End_Roll();
}
* Notify를 몽타주에 할당해준다.(몽타주->노티파이-> 노티파이 추가-> 우리가 정의한 해당 노티파이 클래스명이 있음)
CPlayer.h 추가된 내용
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "Components/CStateComponent.h"
#include "CPlayer.generated.h"
UCLASS()
class UONLINE_04_ACTION_API ACPlayer : public ACharacter
{
GENERATED_BODY()
...
private:
...
UPROPERTY(VisibleDefaultsOnly)
class UCMontagesComponent* Montages;
UPROPERTY(VisibleDefaultsOnly)
class UCStateComponent* State;
private:
// 구르기 동작
void OnAvoid();
private:
UFUNCTION()
void OnStateTypeChanged(EStateType InPrevType, EStateType InNewType);
private:
void Begin_Roll();
void Begin_Backstep();
public:
void End_Roll();
void End_Backstep();
};
CPlayer.cpp 추가된 내용
...
#include "Components/CStatusComponent.h"
#include "Components/CMontagesComponent.h"
ACPlayer::ACPlayer()
{
...
CHelpers::CreateActorComponent<UCMontagesComponent>(this, &Montages, "Montages");
CHelpers::CreateActorComponent<UCStateComponent>(this, &State, "State");
}
void ACPlayer::BeginPlay()
{
...
// 이벤트로 처리(ACPlayer내부에서 사용되어서)
State->OnStateTypeChanged.AddDynamic(this, &ACPlayer::OnStateTypeChanged);
}
void ACPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
...
PlayerInputComponent->BindAction("Avoid", EInputEvent::IE_Pressed, this, &ACPlayer::OnAvoid);
}
void ACPlayer::OnAvoid()
{
CheckFalse(Status->CanMove());
CheckFalse(State->IsIdleMode());
// MoveFoward가 0보다 작으면 BackStep(뒷 구르기), 아니라면 Rolling(앞 구르기)이다.
// PlayerInputComponent가 Player의 맴버인 InputComponent에 등록되어 있다.
// GetAxisValue 함수가 호출된 시점에서 w이면 1, 입력이 없다면 0
// s라면 0보다 작은 값인 -1이 나온다.
if (InputComponent->GetAxisValue("MoveForward") < 0.0f)
{
State->SetBackstepMode();
return;
}
State->SetRollMode();
}
// 얘는 상태 바뀔때 마다 콜됨
void ACPlayer::OnStateTypeChanged(EStateType InPrevType, EStateType InNewType)
{
// 상태에 따라 처리가능
// Begin()은 여기서 처리해주고, End()함수들은 Notify에서 콜되도록 할 것이다.
switch (InNewType)
{
case EStateType::Roll: Begin_Roll(); break;
case EStateType::Backstep: Begin_Backstep(); break;
}
}
void ACPlayer::Begin_Roll()
{
// 해당 방향으로 구르기 위해 꺼놈
bUseControllerRotationYaw = false;
// 회전 방향으로 돌려놈
GetCharacterMovement()->bOrientRotationToMovement = true;
// 해당 방향으로 굴러줌
FVector start = GetActorLocation();
FVector from = start + GetVelocity().GetSafeNormal2D();
SetActorRotation(UKismetMathLibrary::FindLookAtRotation(start, from));
Montages->PlayRoll();
}
void ACPlayer::End_Roll()
{
State->SetIdleMode();
}
void ACPlayer::Begin_Backstep()
{
// 정면을 바라본 상태에서 뒤로 뜀
bUseControllerRotationYaw = true;
GetCharacterMovement()->bOrientRotationToMovement = false;
Montages->PlayBackStep();
}
void ACPlayer::End_Backstep()
{
// 정면 바라보도록 하는 것을 풀어줌
bUseControllerRotationYaw = false;
GetCharacterMovement()->bOrientRotationToMovement = true;
State->SetIdleMode();
}
CMontagesComponent.h
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "Components/CStateComponent.h"
#include "Engine/DataTable.h"
#include "CMontagesComponent.generated.h"
// DataTable에서 불러와 값을 입력해줄 구조체를 정의함
// BlueprintType : 블루프린트와 통신하기 위해
// FTableRowBase(Engine/DataTable.h)을 상속받아야 데이터 테이블로 불러들일 데이터 타입으로 사용하겠단 의미
USTRUCT(BlueprintType)
struct FMontageData : public FTableRowBase
{
GENERATED_BODY()
public:
// 변수 선언한거랑 엑셀 타입 이름을 일치시킴(엑셀에 변수명 정확히 일치하게 써줌)
// 열이름은 마음대로 가능(그러나 중복하면 안됨)
// EditAnywhere여야 데이터 테이블로부터 불러올 수 있음(에디터로부터 불러오는 거여서)
UPROPERTY(EditAnywhere)
EStateType Type;
UPROPERTY(EditAnywhere)
class UAnimMontage* AnimMontage;
UPROPERTY(EditAnywhere)
float PlayRatio = 1.0f;
UPROPERTY(EditAnywhere)
FName StartSection;
};
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class UONLINE_04_ACTION_API UCMontagesComponent : public UActorComponent
{
GENERATED_BODY()
private:
UPROPERTY(EditDefaultsOnly, Category = "DataTable")
UDataTable* DataTable;
public:
UCMontagesComponent();
// 밑에 PlayAnimMontage() 함수 각 상태를 인자로 주는 방법은 좋은 방법이 아니다.
// 그래서 각 행동에 따라 몽타주를 플레이해줄 함수를 정의(외부에서 함수 호출용)
void PlayRoll();
void PlayBackStep();
protected:
virtual void BeginPlay() override;
private:
// 해당 타입의 몽타주 플레이 시켜줌
void PlayAnimMontage(EStateType InState);
private:
// 몽타주 저장할 수 있도록 배열(StateType수 만큼)
// enum에 마지막에 추가해서 Max로 개수 파악가능
// 이렇게 하면 반복문을 돌 필요가 없어서 편함
FMontageData* Datas[(int32)EStateType::Max];
};
CMontagesComponent.cpp
#include "CMontagesComponent.h"
#include "Global.h"
#include "GameFramework/Character.h"
UCMontagesComponent::UCMontagesComponent()
{
}
void UCMontagesComponent::BeginPlay()
{
Super::BeginPlay();
CheckNull(DataTable);
TArray<FMontageData*> datas;
// GetAllRows() : 데이터 테이블에 있는 모든 값을 가져옴(<> 타입으로)
// TArray<T*>로 리턴됨
DataTable->GetAllRows<FMontageData>("", datas);
//for (const FMontageData* data : datas)
//{
// if (!!data)
// CLog::Print(data->AnimMontage->GetPathName());
//}
// 불러왔던 데이터를 타입에 맞게 저장
for (int32 i = 0; i < (int32)EStateType::Max; i++)
{
for (FMontageData* data : datas)
{
if ((EStateType)i == data->Type)
{
Datas[i] = data;
continue;
}
}//for(data)
}//for(i)
}
void UCMontagesComponent::PlayRoll()
{
PlayAnimMontage(EStateType::Roll);
}
void UCMontagesComponent::PlayBackStep()
{
PlayAnimMontage(EStateType::Backstep);
}
void UCMontagesComponent::PlayAnimMontage(EStateType InState)
{
ACharacter* character = Cast<ACharacter>(GetOwner());
const FMontageData* data = Datas[(int32)InState];
// 데이터가 없을 수도 있다.
if (!!data)
{
// 몽타주가 있다면 실행시킴
if (!!data->AnimMontage)
character->PlayAnimMontage(data->AnimMontage, data->PlayRatio, data->StartSection);
}
}
CAnimNotify_EndRoll.h
#pragma once
#include "CoreMinimal.h"
#include "Animation/AnimNotifies/AnimNotify.h"
#include "CAnimNotify_EndRoll.generated.h"
UCLASS()
class UONLINE_04_ACTION_API UCAnimNotify_EndRoll : public UAnimNotify
{
GENERATED_BODY()
public:
FString GetNotifyName_Implementation() const override;
virtual void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation) override;
};
CAnimNotify_EndRoll.cpp
#include "CAnimNotify_EndRoll.h"
#include "Global.h"
#include "Characters/CPlayer.h"
FString UCAnimNotify_EndRoll::GetNotifyName_Implementation() const
{
return "Roll";
}
void UCAnimNotify_EndRoll::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation)
{
Super::Notify(MeshComp, Animation);
CheckNull(MeshComp);
CheckNull(MeshComp->GetOwner());
ACPlayer* player = Cast<ACPlayer>(MeshComp->GetOwner());
CheckNull(player);
player->End_Roll();
}
CAnimNotify_EndBackstep.h
#pragma once
#include "CoreMinimal.h"
#include "Animation/AnimNotifies/AnimNotify.h"
#include "CAnimNotify_EndBackstep.generated.h"
UCLASS()
class UONLINE_04_ACTION_API UCAnimNotify_EndBackstep : public UAnimNotify
{
GENERATED_BODY()
public:
// 부모의 BlueprintNativeEvent로 정의되어 있었음 -> 즉, 재정의 가능
FString GetNotifyName_Implementation() const override;
virtual void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation) override;
};
CAnimNotify_EndBackstep.cpp
#include "CAnimNotify_EndBackstep.h"
#include "Global.h"
#include "Characters/CPlayer.h"
FString UCAnimNotify_EndBackstep::GetNotifyName_Implementation() const
{
return "Backstep";
}
void UCAnimNotify_EndBackstep::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation)
{
Super::Notify(MeshComp, Animation);
CheckNull(MeshComp);
CheckNull(MeshComp->GetOwner());
ACPlayer* player = Cast<ACPlayer>(MeshComp->GetOwner());
CheckNull(player);
player->End_Backstep();
}
결과
'Unreal Engine 4 > C++' 카테고리의 다른 글
<Unreal C++> 43 - Action RPG(Weapon Attachment) (0) | 2022.05.09 |
---|---|
<Unreal C++> 40 - Action RPG (Action Component & Equip) (0) | 2022.05.09 |
<Unreal C++> 33 - Action RPG(Player & Animation Setting & Avoiding(State)) (0) | 2022.05.02 |
<Unreal C++> 31 - Gun Shooting Mode (Fire Effect) (0) | 2022.05.02 |
<Unreal C++> 27 - Gun Shooting Mode(Aiming), Fire & CrossHair (0) | 2022.04.25 |