필요한 개념
<Enemy 피격>
// CEnemy.h
private:
UFUNCTION()
void OnStateTypeChanged(EStateType InPrevType, EStateType InNewType);
// CEnemy.cpp
// State가 바뀔때 함수 바인드
State->OnStateTypeChanged.AddDynamic(this, &ACEnemy::OnStateTypeChanged);
* StateComponent에 Hitted 상태를 추가함
UENUM(BlueprintType)
enum class EStateType : uint8
{
Idle, Roll, Backstep, Equip, Action, Hitted, Max
};
<데미지 적용 & 적 뒤로 밀리는 작업(Launch)>
// CEnemy.cpp
void ACEnemy::Hitted()
{
Status->SubHealth(DamageValue);
Cast<UCUserWidget_Health>(HealthWidget->GetUserWidgetObject())->Update(Status->GetHealth(), Status->GetMaxHealth());
DamageValue = 0.0f;
// Hitted 되었을 때 뒤로 밀리는 작업
// BP에서 SetOffsetWorld()를 했지만
// C에서는 Launch로 한다.
FVector start = GetActorLocation();
FVector target = DamageInstigator->GetPawn()->GetActorLocation();
// FindLookAtRotation()으로 적이 플레이어를 바라보는 회전방향이 만들어짐
// 플레이어를 바라보도록 한다.
SetActorRotation(UKismetMathLibrary::FindLookAtRotation(start, target));
DamageInstigator = NULL;
// start가 Target을 바라보는 방향이 나옴
FVector direction = target - start;
direction.Normalize();
// 뒤로 밀리는 작업
// 뒤로 밀릴꺼니까 반대로 뒤집음
// 1 : 밀리는 방향 2 : XY방향으로 밀릴건지 3 : Z방향으로 밀릴건지
// 평면상으로만 움직이게 XY방향만 킨다.
LaunchCharacter(-direction * LaunchAmount, true, false);
}
<피격 되었을 시 색 바꾸기>
// CEnemy.cpp
// Hitted() 함수 내에 정의
ChangeColor(FLinearColor(1, 0, 0, 1));
// 타이머 델리게이션으로 0.1초 뒤 원래 색으로 돌아옴
UKismetSystemLibrary::K2_SetTimer(this, "RestoreColor", 0.1f, false);
void ACEnemy::RestoreColor()
{
FLinearColor color = Action->GetCurrent()->GetEquipmentColor();
// 현재 무기 색으로 돌려줌
ChangeColor(color);
}
<Hitted 동작 플레이>
* Enemy.csv 세팅
* 몽타주 FullBody로 세팅, Enemy.csv를 DataTable로 불러들임
* BP_CEnemy_Dummy의 MontageComponent에 DataTable을 세팅
* CMontageComponent에서 Hitted 세팅
void UCMontagesComponent::PlayHitted()
{
PlayAnimMontage(EStateType::Hitted);
}
Tip) EStateType(CStateComponent)에 따른 애니메이션은 CMontageComponent에서 세팅해준다.
* CEnemy.cpp Hitted()에서 몽타주 플레이 시켜줌
Status->SetStop();
Montages->PlayHitted();
* 노티파이 생성(AnimNotify를 상속 받는 CAnimNotify_Hitted 클래스 생성)
피격 동작 노티파이에서는 Hit가 완료되면 CStateComponent를 가져와서 Idle 모드로 바꿔주면 된다.
// IdleMode 상태로 돌려줌
state->SetIdleMode();
* Hitted 노티파이를 몽타주에 적용
<연타 버그 잡기>
타격이 겹치는 거(따닥)을 막는 작업
여러번 충돌 처리가 될 수 있음을 방지
* CAttachment에 Delegate 추가(OnCollision, OffCollision때 바인딩된 함수들 Broadcast)
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FAttachmentCollision);
UPROPERTY(BlueprintAssignable)
FAttachmentCollision OnAttachmentCollision; // OnCollision 때
UPROPERTY(BlueprintAssignable)
FAttachmentCollision OffAttachmentCollision; // OffCollision 때
* CDoAction.h에 virtual로 추가
UFUNCTION()
virtual void OnAttahmentCollision() {};
UFUNCTION()
virtual void OffAttahmentCollision() {};
* CActionData에서 AddDynamic해서 바인딩 해줌
Attachment->OnAttachmentCollision.AddDynamic(DoAction, &ACDoAction::OnAttahmentCollision);
Attachment->OffAttachmentCollision.AddDynamic(DoAction, &ACDoAction::OffAttahmentCollision);
* CDoAction_Melee에서는 해당 함수를 오버라이드 해서 작성
// Collision이 충돌되었을 때
void ACDoAction_Melee::OnAttachmentBeginOverlap(class ACharacter* InAttacker, class AActor* InAttackCauser, class ACharacter* InOtherCharacter)
{
...
// 목록에 있다면 했던것을 또 하지 않음
for (const ACharacter* other : HittedCharacters)
{
if (InOtherCharacter == other)
return;
}
HittedCharacters.Add(InOtherCharacter);
...
}
void ACDoAction_Melee::OnAttahmentCollision()
{
}
// Collision을 껏을 때
void ACDoAction_Melee::OffAttahmentCollision()
{
// 충돌된 캐릭터들을 비워버림
HittedCharacters.Empty();
}
CActionComponent.cpp 수정된 내용
void UCActionComponent::SetUnarmedMode()
{
// 이미 무기가 장착되어 있다면 해제하고
if (!!Datas[(int32)Type])
{
ACEquipment* equipment = Datas[(int32)Type]->GetEquipment();
if (!!equipment)
equipment->Unequip();
}
// 이제 UnamedMode도 장비가 있는 것으로 처리된다.
ACEquipment* equipment = Datas[(int32)EActionType::Unarmed]->GetEquipment();
CheckNull(equipment);
// 색바꾸기 위해(Unarmed도 장비가 있는 것으로 바꿈)
equipment->Equip();
ChangeType(EActionType::Unarmed);
}
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()
private:
// Hitted 되었을 때 밀리는 정도
UPROPERTY(EditAnywhere, Category = "Hitted")
float LaunchAmount = 100.0f;
private:
UFUNCTION()
void OnStateTypeChanged(EStateType InPrevType, EStateType InNewType);
// 색 복원하기(타이머 델리게이션으로)
UFUNCTION()
void RestoreColor();
private:
void Hitted();
private:
// 공격한 놈의 컨트롤러
class AController* DamageInstigator;
// 데미지 양
float DamageValue;
};
CEnemy.cpp 추가된 내용
...
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)
{
DamageInstigator = EventInstigator;
// 데미지를 깍어야 하거나 증가하거나 이런경우는 TakeDamage()리턴된 값으로 처리하는 것이 맞다.
// 부모에서 적용된 것으로 쓰겠다.
DamageValue = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
State->SetHittedMode();
return Status->GetHealth();
}
void ACEnemy::Hitted()
{
Status->SubHealth(DamageValue);
Cast<UCUserWidget_Health>(HealthWidget->GetUserWidgetObject())->Update(Status->GetHealth(), Status->GetMaxHealth());
DamageValue = 0.0f;
Status->SetStop();
Montages->PlayHitted();
// Hitted 되었을 때 뒤로 밀리는 작업
// BP에서 SetOffsetWorld()를 했지만
// C에서는 Launch로 한다.
FVector start = GetActorLocation();
FVector target = DamageInstigator->GetPawn()->GetActorLocation();
// FindLookAtRotation()으로 적이 플레이어를 바라보는 회전방향이 만들어짐
// 플레이어를 바라보도록 한다.
SetActorRotation(UKismetMathLibrary::FindLookAtRotation(start, target));
DamageInstigator = NULL;
// start가 Target을 바라보는 방향이 나옴
FVector direction = target - start;
direction.Normalize();
// 뒤로 밀리는 작업
// 뒤로 밀릴꺼니까 반대로 뒤집음
// 1 : 밀리는 방향 2 : XY방향으로 밀릴건지 3 : Z방향으로 밀릴건지
// 평면상으로만 움직이게 XY방향만 킨다.
LaunchCharacter(-direction * LaunchAmount, true, false);
ChangeColor(FLinearColor(1, 0, 0, 1));
// 타이머 델리게이션으로 0.1초 뒤 원래 색으로 돌아옴
UKismetSystemLibrary::K2_SetTimer(this, "RestoreColor", 0.1f, false);
}
void ACEnemy::RestoreColor()
{
FLinearColor color = Action->GetCurrent()->GetEquipmentColor();
// 현재 무기 색으로 돌려줌
ChangeColor(color);
}
CStateComponent.h 추가된 내용
UENUM(BlueprintType)
enum class EStateType : uint8
{
Idle, Roll, Backstep, Equip, Action, Hitted, Max
};
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class UONLINE_04_ACTION_API UCStateComponent : public UActorComponent
{
GENERATED_BODY()
public:
...
UFUNCTION(BlueprintPure)
FORCEINLINE bool IsHittedMode() { return Type == EStateType::Hitted; }
public:
...
void SetHittedMode();
};
CStateComponent.cpp 추가된 내용
...
void UCStateComponent::SetHittedMode()
{
ChangeType(EStateType::Hitted);
}
CMontagesComponent.h 추가된 내용
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class UONLINE_04_ACTION_API UCMontagesComponent : public UActorComponent
{
GENERATED_BODY()
...
public:
void PlayHitted();
};
CMontagesComponent.cpp 추가된 내용
...
void UCMontagesComponent::PlayHitted()
{
PlayAnimMontage(EStateType::Hitted);
}
CAnimNotify_Hitted.h
#pragma once
#include "CoreMinimal.h"
#include "Animation/AnimNotifies/AnimNotify.h"
#include "CAnimNotify_Hitted.generated.h"
UCLASS()
class UONLINE_04_ACTION_API UCAnimNotify_Hitted : public UAnimNotify
{
GENERATED_BODY()
public:
FString GetNotifyName_Implementation() const override;
virtual void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation) override;
};
CAnimNotify_Hitted.cpp
#include "CAnimNotify_Hitted.h"
#include "Global.h"
#include "GameFramework/Character.h"
#include "Components/CStateComponent.h"
FString UCAnimNotify_Hitted::GetNotifyName_Implementation() const
{
return "Hitted";
}
void UCAnimNotify_Hitted::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation)
{
Super::Notify(MeshComp, Animation);
CheckNull(MeshComp);
CheckNull(MeshComp->GetOwner());
ACharacter* character = Cast<ACharacter>(MeshComp->GetOwner());
CheckNull(character);
UCStateComponent* state = CHelpers::GetComponent<UCStateComponent>(character);
CheckNull(state);
// IdleMode 상태로 돌려줌
state->SetIdleMode();
}
CAttachment.h 수정된 내용
...
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FAttachmentCollision);
UCLASS()
class UONLINE_04_ACTION_API ACAttachment : public AActor
{
GENERATED_BODY()
...
public:
// 노티파이때 충돌체가 켜지고, 꺼진다.
void OnCollision();
void OffCollision();
public:
UPROPERTY(BlueprintAssignable)
FAttachmentCollision OnAttachmentCollision;
UPROPERTY(BlueprintAssignable)
FAttachmentCollision OffAttachmentCollision;
};
CAttachment.cpp 수정된 내용
...
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();
}
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:
UFUNCTION()
virtual void OnAttahmentCollision() {};
UFUNCTION()
virtual void OffAttahmentCollision() {};
};
CDoAction.cpp 수정된 내용
...
void ACDoAction_Melee::OnAttachmentBeginOverlap(class ACharacter* InAttacker, class AActor* InAttackCauser, class ACharacter* InOtherCharacter)
{
Super::OnAttachmentBeginOverlap(InAttacker, InAttackCauser, InOtherCharacter);
CheckNull(InOtherCharacter);
for (const ACharacter* other : HittedCharacters)
{
if (InOtherCharacter == other)
return;
}
HittedCharacters.Add(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()
{
HittedCharacters.Empty();
}
void ACDoAction_Melee::RestoreDilation()
{
UGameplayStatics::SetGlobalTimeDilation(GetWorld(), 1.0f);
}
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 OnAttahmentCollision() override;
virtual void OffAttahmentCollision() override;
private:
TArray<ACharacter*> HittedCharacters;
};
CDoAction_Melee.cpp 수정된 내용
...
void ACDoAction_Melee::OnAttachmentBeginOverlap(class ACharacter* InAttacker, class AActor* InAttackCauser, class ACharacter* InOtherCharacter)
{
Super::OnAttachmentBeginOverlap(InAttacker, InAttackCauser, InOtherCharacter);
CheckNull(InOtherCharacter);
for (const ACharacter* other : HittedCharacters)
{
if (InOtherCharacter == other)
return;
}
HittedCharacters.Add(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()
{
HittedCharacters.Empty();
}
CActionData.cpp 수정된 내용
#include "CActionData.h"
#include "Global.h"
#include "CAttachment.h"
#include "CEquipment.h"
#include "CDoAction.h"
#include "GameFramework/Character.h"
#include "Components/SkeletalMeshComponent.h"
...
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 (!!Equipment)
{
DoAction->SetEquipped(Equipment->GetEquipped());
}
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);
}
}
}
결과
'Unreal Engine 4 > C++' 카테고리의 다른 글
<Unreal C++> 54 - Action RPG (TwoHand Mode) (0) | 2022.05.16 |
---|---|
<Unreal C++> 53 - Action RPG(Juice(타격감)) (0) | 2022.05.16 |
<Unreal C++> 50 - Action RPG (Enemy UI) (0) | 2022.05.16 |
<Unreal C++> 48 - Action RPG (Create Enemy & Collision) (0) | 2022.05.16 |
<Unreal C++> 47 - Action RPG (Character Interface) (0) | 2022.05.16 |