11.27 - TIL

김혁·2025년 11월 27일

TIL

목록 보기
66/84

오늘의 코드카타

  • 카운트 다운
    • 다트를 최대한 적게 던져서 타겟 점수를 달성하는 방법 찾기
    • 먼저 다트를 통해 획득 가능한 점수를 모두 저장해놓고, DP를 통해 타겟 점수까지 획득 가능한 점수를 순회하면서 가능한 적은 횟수로 되게끔 저장
    • https://school.programmers.co.kr/learn/courses/30/lessons/131129

오늘의 공부

팀 프로젝트 진행 내용

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 위치를 받아오고, 해당 위치들을 DamageComponentPerformMeleeDamage() 매개 변수로 넘겨서 데미지를 가하게끔 하고자 했다.
// 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으로 정상적으로 적용되는 것을 확인할 수 있다.

profile
게임 개발자를 향해..

0개의 댓글