본문 바로가기

Unreal Engine 4/C++

<Unreal C++> 44 - Action RPG(CAction & Melee Attack)

 

 

필요한 개념


<CDoAction & CDoAction_Melee 생성> 

Melee : 근거리 공격 처리를 위한

CAction : 액션을 담당한 클래스(액션을 수행한다는 의미에서 CDoAction으로 이름 명명)
CAttachment에서 충돌이 정의될 것이어서 CAction은 CAttachment에 연결될 것이다.

// CDoAction.h

public:
	// 3가지의 가상함수를 가짐

	// 액션을 수행(액션에 해당하는 몽타주 플레이)
	virtual void DoAction(){};
	// 몽타주에 의해 시작
	virtual void Begin_DoAction(){};
	// 몽타주에 의해 종료
	virtual void End_DoAction(){};



그리고 이 클래스를 상속받아 액션을 수행하는 클래스를 정의할 것이다.(CDoAction_Melee class)


// CActionData.h

// 액션 같은 경우 몽타주 데이터가 더 필요함
USTRUCT(BlueprintType)
struct FDoActionData : public FEquipmentData
{
	GENERATED_BODY()

public:
UPROPERTY(EditAnywhere)
	float Power = 5.0f;

// HitStop : 타격되었을 때 일시정지(경직)을 구현할 변수(타격감)
UPROPERTY(EditAnywhere)
	float HitStop;

// 타격시 플레이 시킬 효과
UPROPERTY(EditAnywhere)
	class UParticleSystem* Effect;

// 효과 위치, 크기를 추가적으로 보정하기 위한 값
UPROPERTY(EditAnywhere)
	FTransform EffectTransform;

// 타격시 카메라의 흔들림을 구현할 클래스
UPROPERTY(EditAnywhere)
	TSubclassOf<class UCameraShake> ShakeClass;
};

 

<CAction_Melee Attack 구현>

 

UPROPERTY(BlueprintReadOnly, EditAnywhere)
TSubclassOf<class ACDoAction> DoActionClass;


를 줘서 세팅가능하도록 한다.


// 몽타주 동작이 여러개니까 여러개 세팅하도록 한다. 이것도 데이터에셋에서 수동으로 세팅함
UPROPERTY(BlueprintReadOnly, EditAnywhere)
TArray<FDoActionData> DoActionDatas;



// CActionData.cpp

// DoAction
{
	DoAction = InOwnerCharacter->GetWorld()->SpawnActorDeferred<ACDoAction>(DoActionClass, transform, InOwnerCharacter);
	DoAction->AttachToComponent(InOwnerCharacter->GetMesh(), FAttachmentTransformRules(EAttachmentRule::KeepRelative, true));
	DoAction->SetActorLabel(InOwnerCharacter->GetActorLabel() + "_DoAction");

	UGameplayStatics::FinishSpawningActor(Equipment, transform);
}




* 데이터 에셋 BP인 DA_OneHand에서 변수 세팅
전체 데이터를 다 입력하는 것이 아닌 해당 액션에 필요한 정보만 설정함(ex) 효과 이펙트나 이런거 안넣어도 됨)



* Sword_OneHand_Attack_1_Montage 만들어서 세팅

공격시 이동 불가처리 위해서 DefaultSlot을 FullBody로 세팅한다.

 

 


* CStateComponent.h에 Action(공격) 상태 추가

UENUM(BlueprintType)
enum class EStateType : uint8
{
	// Action : 공격 상태
	Idle, Roll, Backstep, Equip, Action, Max
};

 

// CDoAction.cpp

// 데이터에 맞게 실행시켜줌
void ACDoAction_Melee::DoAction()
{
	Super::DoAction();
	// 데이터 개수가 0보다 커야함
	CheckFalse(Datas.Num() > 0);
	// IdleMode여야 함
	CheckFalse(State->IsIdleMode());


	State->SetActionMode();

	const FDoActionData& data = Datas[0];
	OwnerCharacter->PlayAnimMontage(data.AnimMontage, data.PlayRatio, data.StartSection);

	data.bCanMove ? Status->SetMove() : Status->SetStop();
}





* ActionMapping 세팅된 걸로 Character에서 플레이 시켜주면 됨


CAnimInstance에서 ActionMode를 세팅하고 가져옴
ActionType을 가져오는데 ActionComonent에 있는 Delegate에 함수 연결해서 값을 변경하는게 효율적(계속 Update()하는 것 보단)

action->OnActionTypeChanged.AddDynamic(this, &UCAnimInstance::OnActionTypeChanged);




* AnimBP에서 ActionType에 따라 애니메이션 블랜드 포즈를 지정해 줌(Base포즈에)

 

블랜드 포즈에 오른쪽 마우스 클릭 시 Type추가가 가능하다.

Type 핀 추가

 

* AnimBP 노드 추가 (OneHand & BlendPose)



* 원래는 BeginAction EndAction 할때 Begin, End가 있으면 NotifyState 기반으로 생성하는데, 콤보 때문에 별도로 CNotify를 기반으로 생성함

 

<CAnimNotify_EndAction 클래스 생성>

// CAnimNotify_EndAction.cpp

void UCAnimNotify_EndAction::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation)
{
	Super::Notify(MeshComp, Animation);
	CheckNull(MeshComp);
	CheckNull(MeshComp->GetOwner());

	UCActionComponent* action = CHelpers::GetComponent<UCActionComponent>(MeshComp->GetOwner());
	CheckNull(action);

	// 애초에 없으면 이 애니메이션에 들어올 수 없어서 따로 NULL체크 안함
	action->GetCurrent()->GetDoAction()->End_DoAction();
}



* 몽타주에 노티파이 추가함

Add Notify

 

 

 

CDoAction.h


더보기
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Actions/CActionData.h"
#include "CDoAction.generated.h"

UCLASS()
class UONLINE_04_ACTION_API ACDoAction : public AActor
{
	GENERATED_BODY()

public:
	FORCEINLINE void SetDatas(TArray<FDoActionData> InDatas) { Datas = InDatas; }
	
public:	
	ACDoAction();

public:
	// 3가지의 가상함수를 가짐

	// 액션을 수행(액션에 해당하는 몽타주 플레이)
	virtual void DoAction(){};
	// 몽타주에 의해 시작
	virtual void Begin_DoAction(){};
	// 몽타주에 의해 종료
	virtual void End_DoAction(){};

protected:
	virtual void BeginPlay() override;

public:	
	virtual void Tick(float DeltaTime) override;

protected:
	UPROPERTY(BlueprintReadOnly)
		class ACharacter* OwnerCharacter;

	UPROPERTY(BlueprintReadOnly)
		class UCStateComponent* State;

	UPROPERTY(BlueprintReadOnly)
		class UCStatusComponent* Status;

protected:
	TArray<FDoActionData> Datas;

};

 

 

 

 

 

 

 

CDoAction.cpp


더보기
#include "CDoAction.h"
#include "Global.h"
#include "GameFramework/Character.h"
#include "Components/CStateComponent.h"
#include "Components/CStatusComponent.h"

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

}

void ACDoAction::BeginPlay()
{
	OwnerCharacter = Cast<ACharacter>(GetOwner());
	State = CHelpers::GetComponent<UCStateComponent>(OwnerCharacter);
	Status = CHelpers::GetComponent<UCStatusComponent>(OwnerCharacter);

	Super::BeginPlay();
}

void ACDoAction::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

 

 

CDoAction_Melee.h


더보기
#pragma once

#include "CoreMinimal.h"
#include "Actions/CDoAction.h"
#include "CDoAction_Melee.generated.h"

UCLASS()
class UONLINE_04_ACTION_API ACDoAction_Melee : public ACDoAction
{
	GENERATED_BODY()

public:
	virtual void DoAction() override;
	virtual void Begin_DoAction() override;
	virtual void End_DoAction() override;
};

 

 

 

 

 

 

 

CDoAction_Melee.cpp


더보기
#include "CDoAction_Melee.h"
#include "Global.h"
#include "GameFramework/Character.h"
#include "Components/CStateComponent.h"
#include "Components/CStatusComponent.h"

void ACDoAction_Melee::DoAction()
{
	Super::DoAction();
	// 데이터 개수가 0보다 커야함
	CheckFalse(Datas.Num() > 0);


	// IdleMode여야 함
	CheckFalse(State->IsIdleMode());
	State->SetActionMode();

	const FDoActionData& data = Datas[0];
	OwnerCharacter->PlayAnimMontage(data.AnimMontage, data.PlayRatio, data.StartSection);
}

void ACDoAction_Melee::Begin_DoAction()
{
	Super::Begin_DoAction();
}

void ACDoAction_Melee::End_DoAction()
{
	Super::End_DoAction();

	State->SetIdleMode();
	Status->SetMove();
}

 

 

 

CActionData.h 추가된 내용


더보기
...

// 액션 같은 경우 몽타주 데이터가 더 필요함
USTRUCT(BlueprintType)
struct FDoActionData : public FEquipmentData
{
	GENERATED_BODY()

public:
	UPROPERTY(EditAnywhere)
		float Power = 5.0f;

	// HitStop : 타격되었을 때 일시정지(경직)을 구현할 변수(타격감)
	UPROPERTY(EditAnywhere)
		float HitStop;

	// 타격시 플레이 시킬 효과
	UPROPERTY(EditAnywhere)
		class UParticleSystem* Effect;

	// 효과 위치, 크기를 추가적으로 보정하기 위한 값
	UPROPERTY(EditAnywhere)
		FTransform EffectTransform;

	// 타격시 카메라의 흔들림을 구현할 클래스
	UPROPERTY(EditAnywhere)
		TSubclassOf<class UCameraShake> ShakeClass;
};


UCLASS()
class UONLINE_04_ACTION_API UCActionData : public UDataAsset
{
	GENERATED_BODY()

public:
	...
	FORCEINLINE class ACDoAction* GetDoAction() { return DoAction; }

public:
	...

	UPROPERTY(BlueprintReadOnly, EditAnywhere)
		TSubclassOf<class ACDoAction> DoActionClass;

	UPROPERTY(BlueprintReadOnly, EditAnywhere)
		TArray<FDoActionData> DoActionDatas;

private:
	...
	class ACDoAction* DoAction;
};

 

 

CActionData.cpp 추가된 내용


더보기
...
#include "CDoAction.h"

void UCActionData::BeginPlay(class ACharacter* InOwnerCharacter)
{
	...
    
	//DoAction
	{
		DoAction = InOwnerCharacter->GetWorld()->SpawnActorDeferred<ACDoAction>(DoActionClass, transform, InOwnerCharacter);
		DoAction->AttachToComponent(InOwnerCharacter->GetMesh(), FAttachmentTransformRules(EAttachmentRule::KeepRelative, true));
		DoAction->SetActorLabel(InOwnerCharacter->GetActorLabel() + "_DoAction");
		DoAction->SetDatas(DoActionDatas);
		UGameplayStatics::FinishSpawningActor(Equipment, transform);
	}
}

 

CStateComponent.h 추가된 내용


더보기
...

UENUM(BlueprintType)
enum class EStateType : uint8
{
	// Action : 공격 상태
	Idle, Roll, Backstep, Equip, Action, Max
};

...

UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class UONLINE_04_ACTION_API UCStateComponent : public UActorComponent
{
	GENERATED_BODY()

public:
	...

	UFUNCTION(BlueprintPure)
		FORCEINLINE bool IsActionMode() { return Type == EStateType::Action; }

public:
	...
	void SetActionMode();

	...
};

 

 

CStateComponent.cpp 추가된 내용


더보기
...

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

 

 

CPlayer.h 추가된 내용


더보기
...

UCLASS()
class UONLINE_04_ACTION_API ACPlayer : public ACharacter
{
	GENERATED_BODY()
    
    ...

private:
	void OnOneHand();
	void OnDoAction();
};

 

 

CPlayer.cpp 추가된 내용


더보기
...

void ACPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	...
	PlayerInputComponent->BindAction("Action", EInputEvent::IE_Pressed, this, &ACPlayer::OnDoAction);
}

void ACPlayer::OnDoAction()
{
	// 얘는 따로 State를 체크하지 않음(공격상태인데 콤보일수도 있어서)
	//CheckFalse(State->IsIdleMode());

	Action->DoAction();
}

 

CAnimInstance.h 추가된 내용


더보기
...

UCLASS()
class UONLINE_04_ACTION_API UCAnimInstance : public UAnimInstance
{
	GENERATED_BODY()
	

protected:
	...
    
	UPROPERTY(BlueprintReadOnly, EditAnywhere)
		EActionType ActionType;

private:
	// 무기를 선택했을 때 (ActionType이 변경될때)
	// 델리게이션에 의해 호출될 함수를 생성
	// ActionType 바꿔 줄려고, 그냥 Update계속 하는것 보다 효율적
	UFUNCTION()
		void OnActionTypeChanged(EActionType InPrevType, EActionType InNewType);
};

 

 

CAnimInstance.cpp 추가된 내용


더보기
...

void UCAnimInstance::NativeBeginPlay()
{
	Super::NativeBeginPlay();

	ACharacter* character = Cast<ACharacter>(TryGetPawnOwner());
	CheckNull(character);

	UCActionComponent* action = CHelpers::GetComponent<UCActionComponent>(character);
	CheckNull(action);

	// 연결 시켜줌
	action->OnActionTypeChanged.AddDynamic(this, &UCAnimInstance::OnActionTypeChanged);
}

void UCAnimInstance::OnActionTypeChanged(EActionType InPrevType, EActionType InNewType)
{
	ActionType = InNewType;
}

 

 

CAnimNotify_EndAction.h


더보기
#pragma once

#include "CoreMinimal.h"
#include "Animation/AnimNotifies/AnimNotify.h"
#include "CAnimNotify_EndAction.generated.h"

UCLASS()
class UONLINE_04_ACTION_API UCAnimNotify_EndAction : public UAnimNotify
{
	GENERATED_BODY()

public:
	FString GetNotifyName_Implementation() const override;

	virtual void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation) override;
};

 

 

CAnimNotify_EndAction.cpp


더보기
#include "CAnimNotify_EndAction.h"
#include "Global.h"
#include "Characters/CPlayer.h"
#include "Actions/CActionData.h"
#include "Actions/CDoAction.h"
#include "Components/CActionComponent.h"

FString UCAnimNotify_EndAction::GetNotifyName_Implementation() const
{
	return "EndAction";
}

void UCAnimNotify_EndAction::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation)
{
	Super::Notify(MeshComp, Animation);
	CheckNull(MeshComp);
	CheckNull(MeshComp->GetOwner());

	UCActionComponent* action = CHelpers::GetComponent<UCActionComponent>(MeshComp->GetOwner());
	CheckNull(action);
	
	// 애초에 없으면 이 애니메이션에 들어올 수 없어서 따로 NULL체크 안함
	action->GetCurrent()->GetDoAction()->End_DoAction();
}

 

 

결과