본문 바로가기

Unreal Engine 4/C++

<Unreal C++> 48 - Action RPG (Create Enemy & Collision)

 

필요한 개념


<적 피격 처리>

 

다음은 타격시 이펙트나 카메라 흔들림을 처리하기 위해 적클래스를 생성할 것이다.
-> CEnemy, CEnemy_Dummy 클래스 생성
실질적으로 Dummy가 화면에 올라감


* Enemy는 따로 입력 받는게 없어서 SetupPlayerInputComponent()가 필요없음

CEnemy도 IICharacter를 상속받아 ChangeColor()를 구현함

<시야를 바꿀 것인지 처리>

 

* Equip 했다면, 장비에 따라 시야가 다를 것이다.

ex) OneHandSword는 캐릭터가 정면만 바라보고, 캐릭터 회전을 안줄 것이다.

반면, Unarmed는 캐릭터가 자유자재로 회전 가능하다.

 

* 두 변수를 활용

bUseControllerRotationYaw = true; // 캐릭터가 정면만 바라보도록
bOrientRotationToMovement = false; // 캐릭터가 회전 못하도록 함

 


* FEquipmentData에 추가

// 시야를 바꿀 것인지
UPROPERTY(EditAnywhere)
	bool bPawnControl = true;

 

참고)

FEquipmentData는 우리가 정의한 DataAsset에 들어갈 구조체이다.

 

* BP에서 나타난 것 확인 가능



// CEquipment.cpp

if (Data.bPawnControl == true)
{
	// 칼을 장착하면 정면을 바라보게 됨
	OwnerCharacter->bUseControllerRotationYaw = true;
	// 회전하지 못하도록 함
	OwnerCharacter->GetCharacterMovement()->bOrientRotationToMovement = false;
}

 

 

* bPawn = true (bUseControllerRotationYaw = true, bOrientRotationToMovement = false)의 경우

* bPawn = true의 경우

 

-> 카메라 회전에 따라 캐릭터가 회전하고, 캐릭터의 Yaw 회전은 안되며, 캐릭터는 정면만 바라본다.

 

 

* bPawn = false (bUseControllerRotationYaw = false, bOrientRotationToMovement = true)의 경우

OwnerCharacter->bUseControllerRotationYaw = false;
OwnerCharacter->GetCharacterMovement()->bOrientRotationToMovement = true;


-> 카메라 회전에 캐릭터가 영향 받지 않고 , 캐릭터의 Yaw 회전은 자유자재로 가능하며 캐릭터는 어디든 볼 수 있다.

 

 

<충돌체 생성 & 충돌체 함수 연결>

* 적 공격은 한가지 생각해야 할게 있음
충돌체는 BP_CAttachment에 생성하고 해당 충돌체가 충돌되었을때 CDoAction의 함수를 호출하도록 델리게이트를 생성

 

* BP_CAttachment_OneHand에서 CapsuleCollision을 생성해주고 무기 사이즈에 맞게 세팅함

* BP_CAttachment_OneHand에 CapsuleCompnent 생성

 

// CAttachment.h
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FAttachmentBeginOverlap, class ACharacter*, InAttacker, class AActor*, InAttackCauser, class ACharacter*, InOtherCharacter);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FAttachmentEndOverlap, class ACharacter*, InAttacker, class AActor*, InAttackCauser, class ACharacter*, InOtherCharacter);

public:
	// 충돌처리 공격 델리게이트
	UPROPERTY(BlueprintAssignable)
		FAttachmentBeginOverlap OnAttachmentBeginOverlap;

	UPROPERTY(BlueprintAssignable)
		FAttachmentEndOverlap OnAttachmentEndOverlap;


// 충돌체 추가위해
private:
	// 액터에서는 루트컴포넌트를 만들어야함
	// 루트 컴포넌트(여기다가 충돌체 붙임)
	UPROPERTY(VisibleDefaultsOnly)
		class USceneComponent* Scene;

private:
	// 액터에서는 루트컴포넌트를 만들어야함
	// 루트 컴포넌트(여기다가 충돌체 붙임)
		class USceneComponent* Scene;

private:
	// 충돌 컴포넌트에 원래 있는 OnComponentBeginOverlap delegate에 이 함수 연결
	UFUNCTION()
		void OnComponentBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
	UFUNCTION()
		void OnComponentEndOverlap(UPrimitiveComponent* OverlappedComponent ,AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);





Tip) 

매크로는 BP와 통신하는 것은 직렬화(Serialization) 개념이랑 연관되어있다.


* 세팅한 Collision을 가져와서 Delegate와 연결시켜줌

 

Collision 컴포넌트 들은 모두 UShapeComponent의 자식들이다.

-> 즉, UShapeComponent로 가져오면 됨

 

* Collision 가져오기

// UShapeComponent : 언리얼에서 사용되는 충돌체
// UShapeComponent는 Sphere, Cube, Capsule의 부모 컴포넌트 클래스
// GetComponents : <타입> 다 가져옴
GetComponents<UShapeComponent>(ShapeComponents);

// 컴포넌트의 델리게이트에 연결
for (UShapeComponent* component : ShapeComponents)
{
	component->OnComponentBeginOverlap.AddDynamic(this, &ACAttachment::OnComponentBeginOverlap);
	component->OnComponentEndOverlap.AddDynamic(this, &ACAttachment::OnComponentEndOverlap);
}

 



* 우리가 만든 델리게이트에 바인드된 함수들 호출됨

void ACAttachment::OnComponentBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	// OwnerCharacter말고 다른애만 공격할 수 있도록(자료형이 다른애만 공격함)
	CheckTrue(OwnerCharacter == OtherActor);
	CheckTrue(OtherActor->GetClass() == OwnerCharacter->GetClass());

	if (OnAttachmentBeginOverlap.IsBound())
		OnAttachmentBeginOverlap.Broadcast(OwnerCharacter, this, Cast<ACharacter>(OtherActor));
}

 

void ACAttachment::OnComponentEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
	if (OnAttachmentEndOverlap.IsBound())
		OnAttachmentEndOverlap.Broadcast(OwnerCharacter, this, Cast<ACharacter>(OtherActor));
}

 

 

<구조>

충돌 된다면, UShapeComponent에 기존에 있는 OnComponentBeginOverlap, OnComponentEndOverlap

델리게이트가 우리가 바인딩 시킨 함수를 호출할 것이고, 이 함수내에서 우리가 정의한 OnAttachmentBeginOverlap, OnAttachmentEndOverlap Delegate의 바인딩된 함수들을 Broadcast()로 콜함



<노티파이때 충돌체가 켜지고, 꺼져야함>

// CAttahment.h
public:
	// 충돌체가 켜지고, 꺼지는 함수
	void OnCollision();
	void OffCollision();

 

// CAttachment.cpp

void ACAttachment::OnCollision()
{
	// 켜줌
	for (UShapeComponent* component : ShapeComponents)
		component->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
}

void ACAttachment::OffCollision()
{
	// 꺼줌
	for (UShapeComponent* component : ShapeComponents)
		component->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}



<CAttachment에 CDoAction의 충돌되었을 때 호출될 함수를 연결함>

 

누가 공격했는지(InAttacker), 어떤 무기로(InAttackCauser), 데미지는 누가 입는지(InOtherCharacter)를 파라미터로 Delegate로 만들어줌

-> 이 델리게이트에 바인딩된 함수들로 피격 처리가 가능함

 

// CDoAction.h
// CAttachment 충돌때 콜 될 함수
// 재정의 해서 사용하도록 함
public:
	// CAttachment 충돌때 콜 될 함수
	UFUNCTION()
		virtual void OnAttachmentBeginOverlap(class ACharacter* InAttacker, class AActor* InAttackCauser, class ACharacter* InOtherCharacter) {};
	UFUNCTION()
		virtual void OnAttachmentEndOverlap(class ACharacter* InAttacker, class AActor* InAttackCauser, class ACharacter* InOtherCharacter) {];




* CActionData BeginPlay 할때 함수 연결해줌

if (!!Attachment)
{
	// 충돌 함수 연결해줌
	Attachment->OnAttachmentBeginOverlap.AddDynamic(DoAction, &ACDoAction::OnAttachmentBeginOverlap);
	Attachment->OnAttachmentEndOverlap.AddDynamic(DoAction, &ACDoAction::OnAttachmentEndOverlap);
}




자식에서 재정의 CDoAction_Melee

* 재정의할때 부모에서 한번 해줬으면 UFUNCTION() 안해줘도 됨

public:
	virtual void OnAttachmentBeginOverlap(class ACharacter* InAttacker, class AActor* InAttackCauser, class ACharacter* InOtherCharacter) override;
	virtual void OnAttachmentEndOverlap(class ACharacter* InAttacker, class AActor* InAttackCauser, class ACharacter* InOtherCharacter) override;





* Collision키고 끄기 위해 CAnimNotifyState_Collision 생성해 줌
충돌체는 CAttachment에 있으므로 현재 무기인 CAttachment 객체를 가져옴

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



<데미지 주기>

 

BP에서는 ApplyDamage, AnyDamage 함수를 쓰지만, (물론 BP에서도 TakeDamage() 있지만 잘 안씀)
C++에서는 TakeDamage() 함수를 사용

 

* TakeDamage()함수는 AActor 클래스에 정의되어 있으며, 데미지를 주는 쪽에서는 TakeDamage 함수를 호출해주고
데미지를 받는 쪽에서는 재정의해서 사용

// CDoAction_Melee.cpp

void ACDoAction_Melee::OnAttachmentBeginOverlap(class ACharacter* InAttacker, class AActor* InAttackCauser, class ACharacter* InOtherCharacter)
{
	CLog::Log(InOtherCharacter->GetName());

	Super::OnAttachmentBeginOverlap(InAttacker, InAttackCauser, InOtherCharacter);
	CheckNull(InOtherCharacter);

	// 데미지 추가 정보를 넘길때
	FDamageEvent e;

	// 1. 데미지, 2. 데미지 이벤트, 3 : Instigator(컨트롤러), 4 : DamageCursor(데미지 야기하는 친구)
	InOtherCharacter->TakeDamage(Datas[Index].Power, e, OwnerCharacter->GetController(), this);
}



* 데미지를 받는 쪽 CEnemy에서는 오버라이딩해준다.

float TakeDamage(float DamageAmount, struct FDamageEvent const& DamageEvent, class AController* EventInstigator, AActor* DamageCauser) override;



<Enemy가 공격 받았을때 Hitted 상태 들어가고, 색을 바꿔줌>

 

* Enemy Setting

Enemy는 OptionComponent를 제외하고 다 필요하다.

// Enemy.h
protected:
	UPROPERTY(VisibleDefaultsOnly)
		class UCActionComponent* Action; // 공격할때

	UPROPERTY(VisibleDefaultsOnly)
		class UCMontagesComponent* Montages; // 동작 필요함

	UPROPERTY(VisibleDefaultsOnly)
		class UCStatusComponent* Status; // 속도

	UPROPERTY(VisibleDefaultsOnly)
		class UCStateComponent* State; // 상태

 

 

 

CActionData.h 추가된 내용


더보기
USTRUCT(BlueprintType)
struct FEquipmentData
{
	GENERATED_BODY()

public:
	...

	// 시야를 바꿀 것인지
	UPROPERTY(EditAnywhere)
		bool bPawnControl = true;

};

 

 

 

CEquipment.cpp 추가된 내용


더보기
void ACEquipment::Equip_Implementation()
{
	...
    
	if (Data.bPawnControl == true)
	{
		// 칼을 장착하면 정면을 바라보게 됨
		OwnerCharacter->bUseControllerRotationYaw = true;
		// 회전하지 못하도록 함
		OwnerCharacter->GetCharacterMovement()->bOrientRotationToMovement = false;
	}
    
    ...
}

void ACEquipment::Unequip_Implementation()
{
	...

	// 따로 호출할 필요가 없음
	OwnerCharacter->bUseControllerRotationYaw = false;
	OwnerCharacter->GetCharacterMovement()->bOrientRotationToMovement = true;

}

 

 

 

 

CAttachment.h 추가된 내용


더보기
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "CAttachment.generated.h"

// 충돌체 공격 전달
// 1. 누가 공격했느냐, 2. 어떤 객체가 공격했는지, 3.공격받은 캐릭터
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FAttachmentBeginOverlap, class ACharacter*, InAttacker, class AActor*, InAttackCauser, class ACharacter*, InOtherCharacter);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FAttachmentEndOverlap, class ACharacter*, InAttacker, class AActor*, InAttackCauser, class ACharacter*, InOtherCharacter);

DECLARE_DYNAMIC_MULTICAST_DELEGATE(FAttachmentCollision);


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

protected:
	// 액터에서는 루트컴포넌트를 만들어야함
	// 루트 컴포넌트(여기다가 충돌체 붙임)
	UPROPERTY(BlueprintReadOnly, VisibleDefaultsOnly)
		class USceneComponent* Scene;

protected:
	// BP에서 접근할 수 있도록
	// 붙일 소켓이름을 받아서 캐릭터 Mesh에 붙여버림
	UFUNCTION(BlueprintCallable)
		void AttachTo(FName InSoketName);


	UFUNCTION(BlueprintCallable)
		void AttachToCollision(class UShapeComponent* InComponent, FName InSoketName);
	
public:	
	ACAttachment();

	// 노티파이때 충돌체가 켜지고, 꺼진다.
	void OnCollision();
	void OffCollision();

private:
	UFUNCTION()
		void OnComponentBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);

	UFUNCTION()
		void OnComponentEndOverlap(UPrimitiveComponent* OverlappedComponent ,AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);


public:
	// 충돌처리 공격 델리게이트
	UPROPERTY(BlueprintAssignable)
		FAttachmentBeginOverlap OnAttachmentBeginOverlap;

	UPROPERTY(BlueprintAssignable)
		FAttachmentEndOverlap OnAttachmentEndOverlap;

	UPROPERTY(BlueprintAssignable)
		FAttachmentCollision OnAttachmentCollision;

	UPROPERTY(BlueprintAssignable)
		FAttachmentCollision OffAttachmentCollision;


private:
	TArray<class UShapeComponent*> ShapeComponents;
};

 

 

 

 

 

 

 

CAttachment.cpp 추가된 내용


더보기
#include "CAttachment.h"
#include "Global.h"
#include "GameFramework/Character.h"
#include "Components/SkeletalMeshComponent.h"
#include "Components/ShapeComponent.h"
#include "Components/CStateComponent.h"
#include "Components/CStatusComponent.h"

ACAttachment::ACAttachment()
{
	CHelpers::CreateComponent<USceneComponent>(this, &Scene, "Scene");
}

void ACAttachment::BeginPlay()
{
	// 찾아놓음
	OwnerCharacter = Cast<ACharacter>(GetOwner());
	State = CHelpers::GetComponent<UCStateComponent>(OwnerCharacter);
	Status = CHelpers::GetComponent<UCStatusComponent>(OwnerCharacter);

	//UShapeComponent : 언리얼에서 사용되는 충돌체
	//UShapeComponent는 Sphere, Cube, Capsule의 부모 컴포넌트 클래스
	// GetComponents : <타입> 다 가져옴
	GetComponents<UShapeComponent>(ShapeComponents);

	for (UShapeComponent* component : ShapeComponents)
	{
		component->OnComponentBeginOverlap.AddDynamic(this, &ACAttachment::OnComponentBeginOverlap);
		component->OnComponentEndOverlap.AddDynamic(this, &ACAttachment::OnComponentEndOverlap);
	}


	OffCollision();

	Super::BeginPlay();
}

void ACAttachment::OnComponentBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	// OwnerCharacter말고 다른애만 공격할 수 있도록(자료형이 다른애만 공격함)
	CheckTrue(OwnerCharacter == OtherActor);
	CheckTrue(OtherActor->GetClass() == OwnerCharacter->GetClass());

	if (OnAttachmentBeginOverlap.IsBound())
		OnAttachmentBeginOverlap.Broadcast(OwnerCharacter, this, Cast<ACharacter>(OtherActor));
}

void ACAttachment::OnComponentEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
	if (OnAttachmentEndOverlap.IsBound())
		OnAttachmentEndOverlap.Broadcast(OwnerCharacter, this, Cast<ACharacter>(OtherActor));
}

void ACAttachment::AttachTo(FName InSoketName)
{
	// 얘는 자기 자신을 통째로 붙임
	AttachToComponent(OwnerCharacter->GetMesh(), FAttachmentTransformRules(EAttachmentRule::KeepRelative, true), InSoketName);
}


void ACAttachment::AttachToCollision(UShapeComponent* InComponent, FName InSoketName)
{
	// 얘는 컴포넌트 하나만 붙임
	InComponent->AttachToComponent(OwnerCharacter->GetMesh(), FAttachmentTransformRules(EAttachmentRule::KeepRelative, true), InSoketName);
}


void ACAttachment::OnCollision()
{
	// 켜줌
	for (UShapeComponent* component : ShapeComponents)
		component->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);

	if (OnAttachmentCollision.IsBound())
		OnAttachmentCollision.Broadcast();
}

void ACAttachment::OffCollision()
{
	// 꺼줌
	for (UShapeComponent* component : ShapeComponents)
		component->SetCollisionEnabled(ECollisionEnabled::NoCollision);

	if (OffAttachmentCollision.IsBound())
		OffAttachmentCollision.Broadcast();
}

 

 

CActionData.h 추가된 내용


더보기
...

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

public:
	FORCEINLINE class ACAttachment* GetAttachment() { return Attachment; }

 

 

 

 

 

 

 

CActionData.cpp 수정된 내용


더보기
...

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

		if (!!Attachment)
		{
			// 충돌 함수 연결해줌
			Attachment->OnAttachmentBeginOverlap.AddDynamic(DoAction, &ACDoAction::OnAttachmentBeginOverlap);
			Attachment->OnAttachmentEndOverlap.AddDynamic(DoAction, &ACDoAction::OnAttachmentEndOverlap);

			Attachment->OnAttachmentCollision.AddDynamic(DoAction, &ACDoAction::OnAttahmentCollision);
			Attachment->OffAttachmentCollision.AddDynamic(DoAction, &ACDoAction::OffAttahmentCollision);
		}
	}
}

 

CDoAction.h 추가된 내용


더보기
#pragma once

...

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

public:
	// CAttachment 충돌때 콜 될 함수
	UFUNCTION()
		virtual void OnAttachmentBeginOverlap(class ACharacter* InAttacker, class AActor* InAttackCauser, class ACharacter* InOtherCharacter) {};
	UFUNCTION()
		virtual void OnAttachmentEndOverlap(class ACharacter* InAttacker, class AActor* InAttackCauser, class ACharacter* InOtherCharacter) {};
	UFUNCTION()
		virtual void OnAttahmentCollision() {};
	UFUNCTION()
		virtual void OffAttahmentCollision() {};
	...
};

 

 

 

 

CDoAction_Melee.h 추가된 내용


더보기
#pragma once

...

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

public:
	virtual void OnAttachmentBeginOverlap(class ACharacter* InAttacker, class AActor* InAttackCauser, class ACharacter* InOtherCharacter) override;
	virtual void OnAttachmentEndOverlap(class ACharacter* InAttacker, class AActor* InAttackCauser, class ACharacter* InOtherCharacter) override;
	
	virtual void OnAttahmentCollision() override;
	virtual void OffAttahmentCollision() override;
};

 

 

 

 

 

 

 

CDoAction_Melee.cpp 추가된 내용


더보기
...

void ACDoAction_Melee::OnAttachmentBeginOverlap(class ACharacter* InAttacker, class AActor* InAttackCauser, class ACharacter* InOtherCharacter)
{
	Super::OnAttachmentBeginOverlap(InAttacker, InAttackCauser, InOtherCharacter);
	CheckNull(InOtherCharacter);
   
	// 데미지 추가 정보를 넘길때
	FDamageEvent e;

	// 1. 데미지, 2. 데미지 이벤트, 3 : Instigator(컨트롤러), 4 : DamageCursor(데미지 야기하는 친구)
	InOtherCharacter->TakeDamage(Datas[Index].Power, e, OwnerCharacter->GetController(), this);
}


void ACDoAction_Melee::OnAttachmentEndOverlap(class ACharacter* InAttacker, class AActor* InAttackCauser, class ACharacter* InOtherCharacter)
{
}

void ACDoAction_Melee::OnAttahmentCollision()
{
}

void ACDoAction_Melee::OffAttahmentCollision()
{
}

 

 

 

CAnimNotifyState_Collision.h


더보기
#pragma once

#include "CoreMinimal.h"
#include "Animation/AnimNotifies/AnimNotifyState.h"
#include "CAnimNotifyState_Collision.generated.h"

UCLASS()
class UONLINE_04_ACTION_API UCAnimNotifyState_Collision : public UAnimNotifyState
{
	GENERATED_BODY()

public:
	FString GetNotifyName_Implementation() const override;

	virtual void NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float TotalDuration) override;
	virtual void NotifyEnd(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation) override;
};

 

 

 

CAnimNotifyState_Collision.cpp


더보기
#include "CAnimNotifyState_Collision.h"
#include "Global.h"
#include "Actions/CActionData.h"
#include "Actions/CAttachment.h"
#include "Components/CActionComponent.h"

FString UCAnimNotifyState_Collision::GetNotifyName_Implementation() const
{
	return "Collision";
}

void UCAnimNotifyState_Collision::NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float TotalDuration)
{
	Super::NotifyBegin(MeshComp, Animation, TotalDuration);
	CheckNull(MeshComp);
	CheckNull(MeshComp->GetOwner());

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

	attachment->OnCollision();
}

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

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

	ACAttachment* attachment = action->GetCurrent()->GetAttachment();
	CheckNull(attachment);

	attachment->OffCollision();
}

 

 

CEnemy_Dummy.h


더보기
#pragma once

#include "CoreMinimal.h"
#include "Characters/CEnemy.h"
#include "CEnemy_Dummy.generated.h"

UCLASS()
class UONLINE_04_ACTION_API ACEnemy_Dummy : public ACEnemy
{
	GENERATED_BODY()
	
};

 

CEnemy.h


더보기
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "Characters/ICharacter.h"
#include "Components/CStateComponent.h"
#include "CEnemy.generated.h"

UCLASS()
class UONLINE_04_ACTION_API ACEnemy : public ACharacter, public IICharacter
{
	GENERATED_BODY()

protected:
	UPROPERTY(VisibleDefaultsOnly)
		class UCActionComponent* Action;

	UPROPERTY(VisibleDefaultsOnly)
		class UCMontagesComponent* Montages;

	UPROPERTY(VisibleDefaultsOnly)
		class UCStatusComponent* Status;

	UPROPERTY(VisibleDefaultsOnly)
		class UCStateComponent* State;

public:
	ACEnemy();
    
    	float TakeDamage(float DamageAmount, struct FDamageEvent const& DamageEvent, class AController* EventInstigator, AActor* DamageCauser) override;

protected:
	virtual void BeginPlay() override;

public:	
	virtual void Tick(float DeltaTime) override;

public:
	virtual void ChangeColor(FLinearColor InColor) override;

private:
	UFUNCTION()
		void OnStateTypeChanged(EStateType InPrevType, EStateType InNewType);
        
private:
	class UMaterialInstanceDynamic* BodyMaterial;
	class UMaterialInstanceDynamic* LogoMaterial;
};

 

 

CEnemy.cpp


더보기
#include "CEnemy.h"
#include "Global.h"
#include "Actions/CActionData.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Animation/AnimInstance.h"
#include "Components/SkeletalMeshComponent.h"
#include "Components/CActionComponent.h"
#include "Components/CMontagesComponent.h"
#include "Components/CStatusComponent.h"
#include "Materials/MaterialInstanceConstant.h"
#include "Materials/MaterialInstanceDynamic.h"
#include "Components/WidgetComponent.h"
#include "Widgets/CUserWidget_Name.h"
#include "Widgets/CUserWidget_Health.h"


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

	// Widget은 Mesh()에다가 붙이면 된다.
	CHelpers::CreateComponent<UWidgetComponent>(this, &NameWidget, "NameWidget", GetMesh());
	CHelpers::CreateComponent<UWidgetComponent>(this, &HealthWidget, "HealthWidget", GetMesh());

	CHelpers::CreateActorComponent<UCActionComponent>(this, &Action, "Action");
	CHelpers::CreateActorComponent<UCMontagesComponent>(this, &Montages, "Montages");
	CHelpers::CreateActorComponent<UCStatusComponent>(this, &Status, "Status");
	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<UAnimInstance> animInstance;
	CHelpers::GetClass<UAnimInstance>(&animInstance, "AnimBlueprint'/Game/Enemies/ABP_CEnemy.ABP_CEnemy_C'");
	GetMesh()->SetAnimInstanceClass(animInstance);

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

}

void ACEnemy::BeginPlay()
{
	UMaterialInstanceConstant* body;
	UMaterialInstanceConstant* logo;

	CHelpers::GetAssetDynamic(&body, "MaterialInstanceConstant'/Game/Materials/M_UE4Man_Body_Inst.M_UE4Man_Body_Inst'");
	CHelpers::GetAssetDynamic(&logo, "MaterialInstanceConstant'/Game/Materials/M_UE4Man_ChestLogo_Inst.M_UE4Man_ChestLogo_Inst'");

	BodyMaterial = UMaterialInstanceDynamic::Create(body, this);
	LogoMaterial = UMaterialInstanceDynamic::Create(logo, this);

	GetMesh()->SetMaterial(0, BodyMaterial);
	GetMesh()->SetMaterial(1, LogoMaterial);

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

	Super::BeginPlay();

	Action->SetUnarmedMode();
}

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

}

void ACEnemy::ChangeColor(FLinearColor InColor)
{
	BodyMaterial->SetVectorParameterValue("BodyColor", InColor);
	LogoMaterial->SetVectorParameterValue("BodyColor", InColor);
}

void ACEnemy::OnStateTypeChanged(EStateType InPrevType, EStateType InNewType)
{
	switch (InNewType)
	{
	case EStateType::Hitted: Hitted();	break;
	}
}

float ACEnemy::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
	Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);

	CLog::Log(Damage);
    
	return 0.0f;
}


void ACEnemy::RestoreColor()
{
	FLinearColor color = Action->GetCurrent()->GetEquipmentColor();

	// 현재 무기 색으로 돌려줌
	ChangeColor(color);
}

 

 

 

결과