오늘의 코드카타
오늘의 공부
팀 프로젝트 진행 내용
1. 데미지를 가하는 컴포넌트 및 노티파이 스테이트 구현
- AI가 플레이어에게 데미지를 가했을 때, 체력을 감소시키는 컴포넌트를 제작하고자 함
- 추후에 이를 활용해서 플레이어가 AI에게도 데미지를 가하는 기능으로도 활용하고자 함
- 노티파이를 통해 Trace할 위치를 받아서 사용할 함수를 먼저 구현했다.
SphereTraceMulti() 함수를 통해 Trace를 실행할 경우, 충돌하는 Collision이 많기 때문에 하나의 액터에도 여러 번 충돌이 발생할 수 있다. 이에 따라 TSet을 통해 충돌이 발생한 액터는 다시 데미지 들어가는 로직이 발생하지 않게끔 했다.
void UAO_DamageComponent::PerformMeleeDamage(TSubclassOf<UGameplayEffect> DamageEffectClass, float DamageAmount,
FVector TraceStart, FVector TraceEnd, float TraceRadius)
{
if (!GetOwner()->HasAuthority())
{
return;
}
if (!DamageEffectClass)
{
return;
}
TArray<FHitResult> HitResults;
TArray<AActor*> ActorsToIgnore;
ActorsToIgnore.Add(GetOwner());
bool bHit = UKismetSystemLibrary::SphereTraceMulti(
GetWorld(),
TraceStart,
TraceEnd,
TraceRadius,
UEngineTypes::ConvertToTraceType(ECC_Pawn),
false,
ActorsToIgnore,
EDrawDebugTrace::ForDuration,
HitResults,
true);
if (!bHit)
{
return;
}
TSet<TWeakObjectPtr<AActor>> HitActors;
for (const auto& HitResult : HitResults)
{
AActor* HitActor = HitResult.GetActor();
if (!HitActor) continue;
if (HitActors.Contains(HitActor)) continue;
HitActors.Add(HitActor);
if (UAbilitySystemComponent* TargetASC = HitActor->FindComponentByClass<UAbilitySystemComponent>())
{
ApplyDamageEffect(TargetASC, DamageEffectClass, DamageAmount);
}
}
}
- 데미지를 입히는 대상에게 GameplayEffect를 통해 데미지를 가하는 함수이다.
- 데미지는 고정된 것이 아니라 SetByCaller를 통해 노티파이에서 지정한 값으로 지정했다.
void UAO_DamageComponent::ApplyDamageEffect(UAbilitySystemComponent* TargetASC,
TSubclassOf<UGameplayEffect> DamageEffectClass, float DamageAmount)
{
if (!TargetASC || !DamageEffectClass)
{
return;
}
FGameplayEffectContextHandle Context = TargetASC->MakeEffectContext();
Context.AddInstigator(GetOwner(), GetOwner());
FGameplayEffectSpecHandle Handle = TargetASC->MakeOutgoingSpec(DamageEffectClass, 1.f, Context);
if (Handle.IsValid())
{
FGameplayTag DamageTag = FGameplayTag::RequestGameplayTag(FName("Data.Damage"));
Handle.Data.Get()->SetSetByCallerMagnitude(DamageTag, DamageAmount);
OwnerASC->ApplyGameplayEffectSpecToTarget(*Handle.Data.Get(), TargetASC);
}
}
- 노티파이에서는 시작 지점부터 시작해서 끝 지점까지의 지정한 Socket 위치를 받아오고, 해당 위치들을
DamageComponent의 PerformMeleeDamage() 매개 변수로 넘겨서 데미지를 가하게끔 하고자 했다.
// AO_NotifyState_MeleeCollision.h
UCLASS()
class AO_API UAO_NotifyState_MeleeCollision : public UAnimNotifyState
{
GENERATED_BODY()
protected:
virtual void NotifyBegin(
USkeletalMeshComponent* MeshComp,
UAnimSequenceBase* Animation,
float TotalDuration,
const FAnimNotifyEventReference& EventReference) override;
virtual void NotifyEnd(
USkeletalMeshComponent* MeshComp,
UAnimSequenceBase* Animation,
const FAnimNotifyEventReference& EventReference) override;
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GAS|Damage")
TSubclassOf<UGameplayEffect> DamageEffectClass;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GAS|Damage")
FName SocketName;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GAS|Damage")
float DamageAmount = -20.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GAS|Damage")
float TraceRadius = 25.f;
private:
FVector StartLocation;
TWeakObjectPtr<AActor> OwningActor;
};
NotifyBegin()와 NotifyEnd()일 때 지정한 소켓의 위치를 저장해놓고, 이를 DamageComponent의 데미지를 실행시켜주는 함수를 실행시켜주게끔 간단하게 구현했다.
void UAO_NotifyState_MeleeCollision::NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation,
float TotalDuration, const FAnimNotifyEventReference& EventReference)
{
Super::NotifyBegin(MeshComp, Animation, TotalDuration, EventReference);
if (!MeshComp || !MeshComp->GetOwner())
{
return;
}
OwningActor = MeshComp->GetOwner();
if (!MeshComp->DoesSocketExist(SocketName))
{
return;
}
StartLocation = MeshComp->GetSocketLocation(SocketName);
}
void UAO_NotifyState_MeleeCollision::NotifyEnd(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation,
const FAnimNotifyEventReference& EventReference)
{
Super::NotifyEnd(MeshComp, Animation, EventReference);
if (OwningActor.IsValid() && DamageEffectClass)
{
if (UAO_DamageComponent* DamageComponent = OwningActor->FindComponentByClass<UAO_DamageComponent>())
{
const FVector EndLocation = MeshComp->GetSocketLocation(SocketName);
DamageComponent->PerformMeleeDamage(DamageEffectClass, DamageAmount, StartLocation, EndLocation, TraceRadius);
}
}
}
Set By Caller를 통해 캐릭터 체력을 감소하는 GameplayEffect를 제작했다.
- 해당 이펙트는
Data.Damage라는 태그를 통해 SetByCaller 설정을 하게 되었다.

- 테스트를 위해 파쿠르 몽타주에 공격하는 노티파이를 추가해서, 해당 몽타주를 실행시키면 데미지가 정상적으로 적용되는지 확인해봤다.
- 파쿠르 동작이 살짝 발을 통해 공격하는 것이 그럴듯해보여서, SocketName을 foot_l로 지정했고, Trace Radius도 200으로 크게 지정해서 테스트를 진행했다.

- 데미지가 20으로 정상적으로 적용되는 것을 확인할 수 있다.
