언리얼엔진5 GAS - 기본 공격 적용

조창근·2024년 6월 30일
0

언리얼엔진5 GAS

목록 보기
5/8
post-thumbnail

GA를 활용하여 기본공격 구현

1. GA_BaseAttack생성

UGameplayAbility를 상속받은 USLGA_BaseAttack을 생성해줍니다.

class SOULLIKE_API USLGA_BaseAttack : public UGameplayAbility
{
	GENERATED_BODY()


	virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override;

	virtual void CancelAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateCancelAbility) override;

	virtual void EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled) override;
	
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GAS")
	TObjectPtr<UAnimMontage> AttackAnimMontage;
   
   	...

};

기본 세팅으로 ActivateAbility(), CancelAbility(), EndAbility()를 오버라이딩하여 선언해줍니다.

2. ActivateAbility() 정의 해주기

void USLGA_BaseAttack::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
	ASoulLikeCharacter* TargetCharacter = Cast<ASoulLikeCharacter>(ActorInfo->AvatarActor.Get());
	if (!TargetCharacter)
	{
		return;
	}
	TargetCharacter->GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_None);
	UAbilitySystemComponent* TargetASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(TargetCharacter);
	if(TargetASC){
		UAbilityTask_PlayMontageAndWait* PlayMontageTask = UAbilityTask_PlayMontageAndWait::CreatePlayMontageAndWaitProxy(this, TEXT("SkillMontage"), AttackAnimMontage, 1.0f);
		PlayMontageTask->OnCompleted.AddDynamic(this, &USLGA_BaseAttack::OnCompleteCallback);
		PlayMontageTask->OnInterrupted.AddDynamic(this, &USLGA_BaseAttack::OnInterruptedCallback);
		PlayMontageTask->ReadyForActivation();
	}
}

1. ActorInfo->AvatarActor.Get()로 현재 어빌리티가 소유중인 아바타 엑터를 가져온 뒤 현재 사용중인 ASoulLikeCharacter로 Cast를 진행해줍니다.

2. UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(TargetCharacter)를 통해 TargetCharacter가 가지고 있는 어빌리티시스템컴포넌트를 가지고 옵니다.
3. UAbilityTask_PlayMontageAndWait을 통해 캐릭터가 공격에니메이션을 실행할 수 있도록 해줍니다.

UAbilitySystemBlueprintLibrary알아보기

1. 사용이유

어빌리티시스템컴포넌트,어트리뷰트 등 사용자가 손쉽게 UAbilitySystemBlueprintLibrary를 통해 가져오기 위해

2. 중요 함수

  1. GetAbilitySystemComponent(AActor* Actor) -

  2. SendGameplayEventToActor(AActor* Actor, FGameplayTag EventTag, FGameplayEventData Payload)

  3. GetGameplayAbilityFromSpecHandle(UAbilitySystemComponent* AbilitySystem, const FGameplayAbilitySpecHandle& AbilitySpecHandle, bool& bIsInstance)

3. GetAbilitySystemComponent(AActor* Actor) 분석

UAbilitySystemComponent* UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(AActor *Actor)
{
	return UAbilitySystemGlobals::GetAbilitySystemComponentFromActor(Actor);
}

UAbilitySystemComponent* UAbilitySystemGlobals::GetAbilitySystemComponentFromActor(const AActor* Actor, bool LookForComponent)
{
	if (Actor == nullptr)
	{
		return nullptr;
	}

	const IAbilitySystemInterface* ASI = Cast<IAbilitySystemInterface>(Actor);
	if (ASI)
	{
		return ASI->GetAbilitySystemComponent();
	}

	if (LookForComponent)
	{
		// Fall back to a component search to better support BP-only actors
		return Actor->FindComponentByClass<UAbilitySystemComponent>();
	}

	return nullptr;
}
  1. GetAbilitySystemComponent()함수를 실행 시키면 최종적으로 GetAbilitySystemComponentFromActor()로 진행

  2. Cast(Actor)를 통해 ASI의 GetAbilitySystemComponent()를 진행하게 된고 캐릭터의 어빌리티시스템컴포넌트를 가져오게 됩니다.
    IAbilitySystemInterface가 중요하다고 알게 된 이유[링크]

    IAbilitySystemInterface의 GetAbilitySystemComponent() 분석

    UAbilitySystemComponent* AAbilitySystemTestPawn::GetAbilitySystemComponent() const
    {
    	return FindComponentByClass<UAbilitySystemComponent>();
    }
    
    template<class T>
    T* FindComponentByClass() const
    {
    	static_assert(TPointerIsConvertibleFromTo<T, const UActorComponent>::Value, "'T' template parameter to FindComponentByClass must be derived from UActorComponent");
    
    	return (T*)FindComponentByClass(T::StaticClass());
    }
    UActorComponent* AActor::FindComponentByClass(const TSubclassOf<UActorComponent> ComponentClass) const
    {
    	UActorComponent* FoundComponent = nullptr;
    
    	if (UClass* TargetClass = ComponentClass.Get())
    	{
    		for (UActorComponent* Component : OwnedComponents)
    		{
    			if (Component && Component->IsA(TargetClass))
    			{
    				FoundComponent = Component;
    				break;
    			}
    		}
    	}
    
    	return FoundComponent;
    }
    
    

    1. FindComponentByClass<UAbilitySystemComponent.>()은 템플릿 함수로 <>에 들어오는 타입으로 함수의 타입이 결정됩니다.

    2. Static Assert: 템플릿 인수 T가 UActorComponent로부터 파생된 클래스인지 확인합니다. 그렇지 않으면 컴파일 시 에러를 발생시킵니다.

    3. return (T)FindComponentByClass(T::StaticClass()) : 엑터 기반으로 타입이 결정되었으므로 엑터의 FindComponentByClass()->for (UActorComponent Component : OwnedComponents)를 통해 템플릿 타입에 맞는 컴포넌트를 찾게 됩니다.

UAbilityTask_PlayMontageAndWait 알아보기

  1. UAbilityTask란?
    GameplayAbilities는 한 프레임 내에서만 실행됩니다. 이것만으로는 많은 유연성을 제공하지 않습니다. 시간이 지남에 따라 발생하는 작업을 수행하거나 나중에 발생하는 delegate에 응답해야 하는 작업을 수행하려면 AbilityTasks라고 불리는 잠재적 작업을 사용합니다.
    액션을 발동시킬때 애니메이션과 같이 결합이된다면 일정시간이필요하고 시간이 걸리는 작업들은 테스크와 결합해서 사용할 수 있습니다.

  2. UAbilityTask_PlayMontageAndWait 코드 분석

    void UAbilityTask_PlayMontageAndWait::Activate()
    {
    	...
    
    		if (ASC->PlayMontage(Ability, Ability->GetCurrentActivationInfo(), MontageToPlay, Rate, StartSection, StartTimeSeconds) > 0.f)
    		{
    
    		...
    
    			InterruptedHandle = Ability->OnGameplayAbilityCancelled.AddUObject(this, &UAbilityTask_PlayMontageAndWait::OnGameplayAbilityCancelled);
    
    			BlendingOutDelegate.BindUObject(this, &UAbilityTask_PlayMontageAndWait::OnMontageBlendingOut);
    			AnimInstance->Montage_SetBlendingOutDelegate(BlendingOutDelegate, MontageToPlay);
    
    			MontageEndedDelegate.BindUObject(this, &UAbilityTask_PlayMontageAndWait::OnMontageEnded);
    			AnimInstance->Montage_SetEndDelegate(MontageEndedDelegate, MontageToPlay);
    		...
    
    	if (!bPlayedMontage)
    	{
    		ABILITY_LOG(Warning, TEXT("UAbilityTask_PlayMontageAndWait called in Ability %s failed to play montage %s; Task Instance Name %s."), *Ability->GetName(), *GetNameSafe(MontageToPlay),*InstanceName.ToString());
    		if (ShouldBroadcastAbilityTaskDelegates())
    		{
    			OnCancelled.Broadcast();
    		}
    	}
    
    	SetWaitingOnAvatar();
    }
    1. BlendingOutDelegate,MontageEndedDelegate등 Delegate를 활용하여 Animinstane와 결합을 해주고 있는 것을 확인할 수 있습니다.
    bool UAbilityTask::ShouldBroadcastAbilityTaskDelegates() const
    {
    	bool ShouldBroadcast = (Ability && Ability->IsActive());
    
    	...
    
    	return ShouldBroadcast;
    }
    1. ShouldBroadcastAbilityTaskDelegates()를 통해 어빌리티가 실행되는 것을 확인하고 만약 실행중이라면 OnCancelled를 Broadcast해주는 것을 확인해볼 수 있습니다.
    void UAbilityTask_PlayAnimAndWait::OnMontageInterrupted()
    {
    	if (StopPlayingMontage())
    	{
    	if (ShouldBroadcastAbilityTaskDelegates())
    	{
    		OnInterrupted.Broadcast();
    		}
    	}
    }
    
    void UAbilityTask_PlayAnimAndWait::OnMontageEnded(UAnimMontage* Montage, bool bInterrupted)
    {
    	if (!bInterrupted)
    {
    		if (ShouldBroadcastAbilityTaskDelegates())
    		{
    			OnCompleted.Broadcast();
    		}
    	}
    
    	EndTask();
    }
    1. 위 함수들도 OnCompleted, OnInterruptedBroadcast해주는 것을 확인해볼 수 있습니다.

    UAbilityTask_PlayMontageAndWait()를 통해 UAbilityTask()의 사용 방향을 알 수 있었고 Delegate를 통해 코드를 효율적으로 구성한 것을 확인해 볼 수 있었습니다.

3. Sword 충돌처리

1. SwordWeapon에 CollisionCapsule추가하기

칼 크기에 맞춰 캡슐컬리젼을 추가해줍니다.

2. Sword콜리젼의 OnComponentBeginOverlap에 실행되는 OnWeaponHit()

void USLGA_BaseAttack::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, 
const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
	...

	if(	TargetCharacter->GetSwordWeapon())
	{
		TargetCharacter->GetSwordWeapon()->WeaponCollision->OnComponentBeginOverlap.AddDynamic(this, &USLGA_BaseAttack::OnWeaponOverlap);;
	}

	...
}
void USLGA_BaseAttack::OnWeaponHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{
	if (OtherActor && OtherActor != GetOwningActorFromActorInfo() && !HitActors.Contains(OtherActor))
	{
		//TSet<Actor> HitActors
		HitActors.Add(OtherActor);
		
  		...
		캡슐 디버그 언리얼 창에 그리는 코드 
		...

			UAbilitySystemComponent* TargetASC = nullptr;
			if (Cast<ASoulLikeCharacter>(OtherActor))
			{
				
				TargetASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(Cast<ASoulLikeCharacter>(OtherActor));
  				float PlayRate = 1.0f; // 몽타주 재생 속도
				TargetASC->PlayMontageSimulated(Cast<ASoulLikeCharacter>(OtherActor)->HitAnimMontage, PlayRate);
			}
  
			...
  			적에게 GamePlayEffect를 적용하는 코드
			...	
   	    }
	}
}

1. 한 번 충돌 된 객체는 다시 충돌되지 않도록 Tset타입의 HitActors를 맴버변수로 선언해주고 if(!HitActors.Contains(OtherActor))로 중복체크를 해줍니다.

2. UAbilitySystemComponent의 PlayMontageSimulated를 통해 적 객체의 HitAnimMontage애니메이션을 실행시켜줍니다.

4. 최종 결과

적 객체는 리슨 서버로 소환된 다른 플레이어입니다.

5. 필요한 개선 사항

  1. 충돌 시 애니메이션이 본인도 실행되는 현상

  2. 캡슐 컬리젼 Overlap시에는 충돌 된 위치가 나오지 않는 현상

    =>충돌 처리에 문제가 있음을 판단하여 충돌처리 방법을 변경

    개선된 모습 미리보기

0개의 댓글