초보 코린이의 성장 일지
UE4 HitData, Effect, Sound, HitStop 본문
Enemy를 타격했을시 색이 변경되는 것과 알맞은 Hit 동작이 나오도록 만들어 볼 것이다.
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "CWeaponStructures.generated.h"
USTRUCT()
struct FHitData // 액션 구조체
{
GENERATED_BODY()
public:
// 에디터에서 세팅은 해야하므로, EditAnywhere.
UPROPERTY(EditAnywhere)
class UAnimMontage* Montage;
UPROPERTY(EditAnywhere)
float PlayRate = 1;
UPROPERTY(EditAnywhere)
float Power;
UPROPERTY(EditAnywhere)
float Launch = 100;
UPROPERTY(EditAnywhere)
float StopTime;
UPROPERTY(EditAnywhere)
class USoundWave* Sound;
UPROPERTY(EditAnywhere)
// UFXSystemAsset 파티클, 나이아가라 공통 부모
class UFXSystemAsset* Effect; // 파티클, 나이아가라 사용하기 위해.
UPROPERTY(EditAnywhere)
// 방향 보정치
FVector EffectLocation = FVector::ZeroVector;
UPROPERTY(EditAnywhere)
// 이팩트 크기 보정.
FVector EffectScale = FVector::OneVector;
};
USTRUCT() // 데미지의 대한 추가 정보 구조체
struct FActionDamageEvent
: public FDamageEvent
{
GENERATED_BODY()
public:
FHitData* HitData;
};
UCLASS()
class U2212_06_API UCWeaponStructures : public UObject
{
GENERATED_BODY()
};
1. HitData를 가지고 있는 구조체를 생성.
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "Weapons/CWeaponStructures.h"
#include "CDoAction.generated.h"
UCLASS(Abstract) // 객체화 되면 안되므로 Abstract
class U2212_06_API UCDoAction : public UObject
{
GENERATED_BODY()
public:
UCDoAction(); // 생성자
// 재정의 할 수 있도록, BeginPlay를 임의로 만들어놓는다
virtual void BeginPlay
(
class ACAttachment* InAttachment,
class UCEquipment* InEquipment,
class ACharacter* InOwner,
const TArray<FDoActionData>& InDoActionDatas,
const TArray<FHitData>& InHitDatas // 코드 추가된 부분
);
protected:
bool bBeginAction; // 액션에 들어갔는지,
class ACharacter* OwnerCharacter;
class UWorld* World; // Owner에 World
class UCMovementComponent* Movement;
class UCStateComponent* State;
TArray<FDoActionData> DoActionDatas; // 액션이 여러개 나올수 있으므로 배열,
TArray<FHitData> HitDatas; // 코드 추가된 부분
};
#include "CEquipment.h"
#include "GameFramework/Character.h"
#include "Components/CStateComponent.h"
#include "Components/CMovementComponent.h"
UCDoAction::UCDoAction()
{
}
// 매개변수 끝에 InHitDatas 추가
void UCDoAction::BeginPlay(ACAttachment* InAttachment, UCEquipment* InEquipment, ACharacter* InOwner, const TArray<FDoActionData>& InDoActionDatas, const TArray<FHitData>& InHitDatas)
{
// 값들을 받아 넣어준다.
OwnerCharacter = InOwner;
World = OwnerCharacter->GetWorld();
State = CHelpers::GetComponent<UCStateComponent>(OwnerCharacter);
Movement = CHelpers::GetComponent<UCMovementComponent>(OwnerCharacter);
DoActionDatas = InDoActionDatas;
HitDatas = InHitDatas; // 코드 추가된 부분
}
1. 구조체를 받아와서 매개변수를 통해 BeginPlay에 받아준다.
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "Weapons/CWeaponStructures.h"
#include "CWeaponAsset.generated.h"
UCLASS()
class U2212_06_API UCWeaponAsset : public UDataAsset
{
GENERATED_BODY()
private:
UPROPERTY(EditAnywhere) // 데이터 가져오기
TArray<FHitData> HitDatas;
public:
UCWeaponAsset();
// 외부에서 BeginPlay일때 콜해주기 위해 이름을 알기 쉽도록 만들어줬다.
void BeginPlay(class ACharacter* InOwner);
};
#include "Weapons/CWeaponAsset.h"
#include "Global.h"
#include "CAttachment.h"
#include "CEquipment.h"
#include "CDoAction.h"
#include "GameFramework/Character.h"
void UCWeaponAsset::BeginPlay(ACharacter * InOwner)
{
if (!!DoActionClass)
{
// DoAction 생성
DoAction = NewObject<UCDoAction>(this, DoActionClass);
DoAction->BeginPlay(Attachment, Equipment, InOwner, DoActionDatas, HitDatas); // 추가된 부분
if (!!Attachment)
{
// null이 아니라면, Attachment에 DoAction꺼를 다 연결해준다.
Attachment->OnAttachmentBeginCollision.AddDynamic(DoAction, &UCDoAction::OnAttachmentBeginCollision);
Attachment->OnAttachmentEndCollision.AddDynamic(DoAction, &UCDoAction::OnAttachmentEndCollision);
Attachment->OnAttachmentBeginOverlap.AddDynamic(DoAction, &UCDoAction::OnAttachmentBeginOverlap);
Attachment->OnAttachmentEndOverlap.AddDynamic(DoAction, &UCDoAction::OnAttachmentEndOverlap);
}
}
}
1. 설정들을 저장해주기 위해 구조체를 받아와서 DoAction 생성 부분에 저장해준다.
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "CWeaponStructures.generated.h"
USTRUCT()
struct FHitData // 액션 구조체
{
GENERATED_BODY()
public:
// 에디터에서 세팅은 해야하므로, EditAnywhere.
UPROPERTY(EditAnywhere)
class UAnimMontage* Montage;
UPROPERTY(EditAnywhere)
float PlayRate = 1;
UPROPERTY(EditAnywhere)
float Power;
UPROPERTY(EditAnywhere)
float Launch = 100;
UPROPERTY(EditAnywhere)
float StopTime;
UPROPERTY(EditAnywhere)
class USoundWave* Sound;
UPROPERTY(EditAnywhere)
// UFXSystemAsset 파티클, 나이아가라 공통 부모
class UFXSystemAsset* Effect; // 파티클, 나이아가라 사용하기 위해.
UPROPERTY(EditAnywhere)
// 방향 보정치
FVector EffectLocation = FVector::ZeroVector;
UPROPERTY(EditAnywhere)
// 이팩트 크기 보정.
FVector EffectScale = FVector::OneVector;
public:
// 데미지에 관련
void SendDamage(class ACharacter* InAttacker, AActor* InAttackCauser, class ACharacter* InOther);
};
USTRUCT() // 데미지의 대한 추가 정보 구조체
struct FActionDamageEvent
: public FDamageEvent
{
GENERATED_BODY()
public:
FHitData* HitData;
};
UCLASS()
class U2212_06_API UCWeaponStructures : public UObject
{
GENERATED_BODY()
};
#include "Weapons/DoActions/CDoAction_Combo.h"
#include "Global.h"
#include "GameFramework/Character.h"
#include "Components/CStateComponent.h"
void UCDoAction_Combo::OnAttachmentBeginOverlap(ACharacter* InAttacker, AActor* InAttackCauser, ACharacter* InOther)
{
Super::OnAttachmentBeginOverlap(InAttacker, InAttackCauser, InOther);
CheckNull(InOther);
////CLog::Log(InOther->GetName());
//FActionDamageEvent e;
//e.HitData = &HitDatas[0];
//InOther->TakeDamage(e.HitData->Power, e, FDamageEvent(), InAttacker->GetController(), InAttackCauser);
HitDatas[Index].SendDamage(InAttacker, InAttackCauser, InOther);
}
1. 데미지를 줄 수 있게끔 SendDamage를 호출해서 처리해준다.
1. HitDatas에서 설정하고 싶은 데이터들을 넣어준다.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "Components/CStateComponent.h"
#include "Characters/ICharacter.h"
#include "CEnemy.generated.h"
UCLASS()
class U2212_06_API ACEnemy
: public ACharacter
, public IICharacter
{
GENERATED_BODY()
private:
UFUNCTION()
void OnStateTypeChanged(EStateType InPrevType, EStateType InNewType);
private:
void Hitted();
private:
UFUNCTION()
void RestoreColor();
private:
struct FDamageData
{
float Power;
class ACharacter* Character;
class AActor* Causer;
struct FActionDamageEvent* Event; // 포인터로 사용하므로 전방선언
} Damage;
private:
FTimerHandle RestoreColor_TimerHandle;
};
#include "Characters/CEnemy.h"
#include "Global.h"
#include "CAnimInstance.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Components/CWeaponComponent.h"
#include "Components/CMontagesComponent.h"
#include "Components/CMovementComponent.h"
#include "Weapons/CWeaponStructures.h"
ACEnemy::ACEnemy()
{
// 아래부분은 Player과 동일하게 가지고 있다.
// 생성
CHelpers::CreateActorComponent<UCWeaponComponent>(this, &Weapon, "Weapon");
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);
GetCharacterMovement()->RotationRate = FRotator(0, 720, 0);
}
void ACEnemy::BeginPlay()
{
Super::BeginPlay();
Movement->OnWalk();
// 색을 세팅해주면서, 시작하면 자신이 설정한 색이 적용된다.
Create_DynamicMaterial(this);
Change_Color(this, OriginColor);
State->OnStateTypeChanged.AddDynamic(this, &ACEnemy::OnStateTypeChanged);
}
void ACEnemy::OnStateTypeChanged(EStateType InPrevType, EStateType InNewType)
{
switch (InNewType)
{
case EStateType::Hitted: Hitted(); break;
}
}
void ACEnemy::Hitted()
{
//TODO: 데미지 처리
//TODO: 사망 처리
// 맞으면 색 변경
Change_Color(this, FLinearColor::Red);
// 데미지 저장
FTimerDelegate timerDelegate;
timerDelegate.BindUFunction(this, "RestoreColor");
GetWorld()->GetTimerManager().SetTimer(RestoreColor_TimerHandle, timerDelegate, 0.2f, false);
}
void ACEnemy::RestoreColor()
{
// 색 다시 돌려주기
Change_Color(this, OriginColor);
// 사용했으면 Clear 해주는게 펜딩킬 안나는 방법
GetWorld()->GetTimerManager().ClearTimer(RestoreColor_TimerHandle);
}
float ACEnemy::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
// 부모 콜을 해주고, 부모에서 처리된 데미지를 가지고와서 처리해줘야한다. (서버가 다 부모에 있기 때문)
float damage = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
Damage.Power = damage; // 처리된 데미지
Damage.Character = Cast<ACharacter>(EventInstigator->GetPawn());
Damage.Causer = DamageCauser;
Damage.Event = (FActionDamageEvent*)&DamageEvent; // 캐스팅
State->SetHittedMode();
return damage;
}
1. Hit되는 대상에 Hit관련된 이벤트를 받아서 처리해줘야한다.
2. 또한 Hit되면 데이터들을 기록해놓고 처리되는 곳에서 처리하게끔 만들것이다. 그래서 내부적으로 구조체를 하나 더 만들어 줄 것이다
3. 데미지 저장을 해준 상태로 이벤트 처리를 하여 색을 변경해줌과 동시에 사용 후 Clear해줌으로써 펜딩킬을 방지한다.
이제 Hit시 몽타주 모션이 나오도록 만들것이다. 그래서 HitData안에 넣어두고 필요할때만 호출해줄 것이다.
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "CWeaponStructures.generated.h"
public:
// 데미지에 관련
void SendDamage(class ACharacter* InAttacker, AActor* InAttackCauser, class ACharacter* InOther);
void PlayMontage(class ACharacter* InOwner);
};
UCLASS()
class U2212_06_API UCWeaponStructures : public UObject
{
GENERATED_BODY()
};
#include "Weapons/CWeaponStructures.h"
#include "Global.h"
#include "GameFramework/Character.h"
#include "Components/CStateComponent.h"
#include "Components/CMovementComponent.h"
#include "Animation/AnimMontage.h"
void FHitData::SendDamage(ACharacter* InAttacker, AActor* InAttackCauser, ACharacter* InOther)
{
FActionDamageEvent e;
e.HitData = this; // HitData 구조체 안에서 선언하므로 자기 자신.
InOther->TakeDamage(Power, e, InAttacker->GetController(), InAttackCauser);
}
void FHitData::PlayMontage(ACharacter* InOwner)
{
if (!!Montage)
InOwner->PlayAnimMontage(Montage, PlayRate);
}
1. Hit시 모션이 나오도록 몽타주를 추가해준다.
#include "Characters/CEnemy.h"
#include "Global.h"
#include "CAnimInstance.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Components/CWeaponComponent.h"
#include "Components/CMontagesComponent.h"
#include "Components/CMovementComponent.h"
#include "Weapons/CWeaponStructures.h"
void ACEnemy::Hitted()
{
//TODO: 데미지 처리
//TODO: 사망 처리
// 맞으면 색 변경
Change_Color(this, FLinearColor::Red);
// 데미지 저장
FTimerDelegate timerDelegate;
timerDelegate.BindUFunction(this, "RestoreColor");
GetWorld()->GetTimerManager().SetTimer(RestoreColor_TimerHandle, timerDelegate, 0.2f, false);
if (!!Damage.Event && !!Damage.Event->HitData)
{
FHitData* data = Damage.Event->HitData;
data->PlayMontage(this);
}
}
1. 몽타주를 Hitted에서 동작 나오도록 호출.
2. 이제 Hit시 몽타주가 나오게된다.
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "CWeaponStructures.generated.h"
public:
// 데미지에 관련
void SendDamage(class ACharacter* InAttacker, AActor* InAttackCauser, class ACharacter* InOther);
void PlayMontage(class ACharacter* InOwner);
void PlayHitStop(UWorld* InWorld);
};
UCLASS()
class U2212_06_API UCWeaponStructures : public UObject
{
GENERATED_BODY()
};
#include "Weapons/CWeaponStructures.h"
#include "Global.h"
#include "GameFramework/Character.h"
#include "Components/CStateComponent.h"
#include "Components/CMovementComponent.h"
#include "Animation/AnimMontage.h"
void FHitData::SendDamage(ACharacter* InAttacker, AActor* InAttackCauser, ACharacter* InOther)
{
FActionDamageEvent e;
e.HitData = this; // HitData 구조체 안에서 선언하므로 자기 자신.
InOther->TakeDamage(Power, e, InAttacker->GetController(), InAttackCauser);
}
void FHitData::PlayMontage(ACharacter* InOwner)
{
if (!!Montage)
InOwner->PlayAnimMontage(Montage, PlayRate);
}
void FHitData::PlayHitStop(UWorld* InWorld)
{
CheckTrue(FMath::IsNearlyZero(StopTime));
TArray<ACharacter*> characters;
for (AActor* actor : InWorld->GetCurrentLevel()->Actors)
{
ACharacter* character = Cast<ACharacter>(actor);
if (!!character)
{
character->CustomTimeDilation = 1e-3f; // 늦춰주기
characters.Add(character); // 늦춰졌다면 복구할 리스트에 넣어주기
}
}
// 일정시간이 지나면 복구되도록, 외부에서 닫힌 상태로 실행되는 함수 부분
FTimerDelegate timerDelegate; // 목록을 저장할 변수
// 익명메서드 "람다식" 사용, 정의된 함수 외부에있는 값을 어떻게 사용할 것인가.
timerDelegate.BindLambda([=]() // 함수 파라미터
{
// 외부식을 대입으로 받아서 사용
for (ACharacter* character : characters)
character->CustomTimeDilation = 1; // 1로 다시 돌려준다.
});
FTimerHandle timerHandle;
// 사용했으면 다시 제거해준다.
InWorld->GetTimerManager().SetTimer(timerHandle, timerDelegate, StopTime, false);
}
1. 이제 HIt시 잠시 멈추게 보이는 효과를 만들어 볼 것이다.
#include "Characters/CEnemy.h"
#include "Global.h"
#include "CAnimInstance.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Components/CWeaponComponent.h"
#include "Components/CMontagesComponent.h"
#include "Components/CMovementComponent.h"
#include "Weapons/CWeaponStructures.h"
void ACEnemy::Hitted()
{
//TODO: 데미지 처리
//TODO: 사망 처리
// 맞으면 색 변경
Change_Color(this, FLinearColor::Red);
// 데미지 저장
FTimerDelegate timerDelegate;
timerDelegate.BindUFunction(this, "RestoreColor");
GetWorld()->GetTimerManager().SetTimer(RestoreColor_TimerHandle, timerDelegate, 0.2f, false);
if (!!Damage.Event && !!Damage.Event->HitData)
{
FHitData* data = Damage.Event->HitData;
data->PlayMontage(this);
data->PlayHitStop(GetWorld());
}
}
1. Enemy로와서 Hit안에 HitStop을 넣어준다.
2. 구조체안에 StopTime 시간을 조율해주면 더 느리게 보이는 효과를 줄 수 있다.
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "CWeaponStructures.generated.h"
public:
// 데미지에 관련
void SendDamage(class ACharacter* InAttacker, AActor* InAttackCauser, class ACharacter* InOther);
void PlayMontage(class ACharacter* InOwner);
void PlayHitStop(UWorld* InWorld);
void PlaySoundWave(class ACharacter* InOwner);
};
UCLASS()
class U2212_06_API UCWeaponStructures : public UObject
{
GENERATED_BODY()
};
#include "Weapons/CWeaponStructures.h"
#include "Global.h"
#include "GameFramework/Character.h"
#include "Components/CStateComponent.h"
#include "Components/CMovementComponent.h"
#include "Animation/AnimMontage.h"
void FHitData::PlaySoundWave(ACharacter* InOwner)
{
CheckNull(Sound);
UWorld* world = InOwner->GetWorld();
FVector location = InOwner->GetActorLocation();
UGameplayStatics::SpawnSoundAtLocation(world, Sound, location);
}
1. 사운드도 넣어준다.
#include "Characters/CEnemy.h"
#include "Global.h"
#include "CAnimInstance.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Components/CWeaponComponent.h"
#include "Components/CMontagesComponent.h"
#include "Components/CMovementComponent.h"
#include "Weapons/CWeaponStructures.h"
void ACEnemy::Hitted()
{
//TODO: 데미지 처리
//TODO: 사망 처리
// 맞으면 색 변경
Change_Color(this, FLinearColor::Red);
// 데미지 저장
FTimerDelegate timerDelegate;
timerDelegate.BindUFunction(this, "RestoreColor");
GetWorld()->GetTimerManager().SetTimer(RestoreColor_TimerHandle, timerDelegate, 0.2f, false);
if (!!Damage.Event && !!Damage.Event->HitData)
{
FHitData* data = Damage.Event->HitData;
data->PlayMontage(this);
data->PlayHitStop(GetWorld());
data->PlaySoundWave(this);
}
Damage.Character = nullptr;
Damage.Causer = nullptr;
Damage.Event = nullptr;
}
1. Hit시 사운드가 나오도록 처리해주고, 데미지에 필요하여 사용했던 매개변수들을 다시 비워준다.
2. 이제 Hit시 사운드가 나오는걸 알 수 있다.
이제 파티클 및 나이아가라 이펙트가 나오도록 만들어 볼 것이다.
우선 기본적인 틀부터 만들어 보겠다.
using UnrealBuildTool;
public class U2212_06 : ModuleRules
{
public U2212_06(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicIncludePaths.Add(ModuleDirectory);
PublicDependencyModuleNames.Add("Core");
PrivateDependencyModuleNames.Add("CoreUObject");
PrivateDependencyModuleNames.Add("Engine");
PrivateDependencyModuleNames.Add("InputCore");
PrivateDependencyModuleNames.Add("Niagara");
}
}
1. Build.cs로가서 Niagara를 넣어준다.
#pragma once
#include "CoreMinimal.h"
#include "Particles/ParticleSystem.h"
#include "NiagaraSystem.h"
#include "NiagaraFunctionLibrary.h"
// 파티클과 나이아가라 이펙트 관련하여 모든곳에서 사용할 수 있게끔 선언
static void PlayEffect(UWorld* InWorld, UFXSystemAsset* InAsset, const FTransform& InTransform, USkeletalMeshComponent* InMesh = nullptr, FName InSocketName = NAME_None)
{
// 파티클 or 나이아가라 캐스팅 확인
UParticleSystem* particle = Cast<UParticleSystem>(InAsset);
UNiagaraSystem* niagara = Cast<UNiagaraSystem>(InAsset);
FVector location = InTransform.GetLocation();
FRotator rotation = FRotator(InTransform.GetRotation());
FVector scale = InTransform.GetScale3D();
// Mesh가 있다는건 어딘가 소켓에 붙여서 실행되는 것.
if (!!InMesh)
{
if (!!particle)
{
UGameplayStatics::SpawnEmitterAttached(particle, InMesh, InSocketName, location, rotation, scale);
return;
}
if (!!niagara)
{
UNiagaraFunctionLibrary::SpawnSystemAttached(niagara, InMesh, InSocketName, location, rotation, scale, EAttachLocation::KeepRelativeOffset, true, ENCPoolMethod::None);
return;
}
}
// Mesh에 붙는게 아니라면 어떠한 위치에서 생성된다는 것
if (!!particle)
{
UGameplayStatics::SpawnEmitterAtLocation(InWorld, particle, InTransform);
return;
}
if (!!niagara)
{
UNiagaraFunctionLibrary::SpawnSystemAtLocation(InWorld, niagara, location, rotation, scale);
return;
}
}
1. 소켓에 붙여서 생성할 것인지, 아니면 어떠한 위치에서 생성할 것인지 체크.
2. 이제 이펙트를 생성시켜주는 코드를 작성하게 될 것이다.
https://www.youtube.com/watch?v=u5i2f3snBmY
'언리얼' 카테고리의 다른 글
UE4 Fist, Camera Shake (0) | 2023.05.11 |
---|---|
UE4 Hit Effect, Status Component, Hammer (2) | 2023.05.10 |
UE4 AnimNotify, ComboState, TakeDamage (0) | 2023.05.08 |
UE4 Equip, DoAction (2) | 2023.05.04 |
UE4 Weapon Attach, Sword Equip (0) | 2023.05.01 |