0. 개요

이 프로젝트에서 각 타입의 어빌리티는 각각의 디버프를 가진다.

불 계열의 어빌리티가 화상 디버프를 가지게 했듯, 전기 계열의 어빌리티는 적을 제자리에서 기절시키는 스턴 디버프를 가지게 할 것이다.

 

 

1. 스턴 - GA_Electrocute

(1) 디버프 적용을 위해 Make Damage Effect Params from Class Defaults로 주 대상에게 데미지 이펙트 적용하기

Damage Effect Params 구조체는 기본 데미지에 관련된 변수와 디버프의 정보에 대한 변수를 가지고 있다.

이 함수는 클래스 디폴트의 값을 가져와 파라미터를 만들게 한다.

Apply Damage Effect 함수는 게임플레이 이펙트를 가져와 데미지와 디버프에 대해 Magnitude를 Set By Caller로 지정하여 이펙트를 적용한다.

	// Set By Caller 설정
	// 데미지
	UAbilitySystemBlueprintLibrary::AssignTagSetByCallerMagnitude(SpecHandle, Params.DamageType, Params.BaseDamage);

	// 디버프
	UAbilitySystemBlueprintLibrary::AssignTagSetByCallerMagnitude(SpecHandle, GameplayTags.Debuff_Chance, Params.DebuffChance);
	UAbilitySystemBlueprintLibrary::AssignTagSetByCallerMagnitude(SpecHandle, GameplayTags.Debuff_Damage, Params.DebuffDamage);
	UAbilitySystemBlueprintLibrary::AssignTagSetByCallerMagnitude(SpecHandle, GameplayTags.Debuff_Frequency, Params.DebuffFrequency);
	UAbilitySystemBlueprintLibrary::AssignTagSetByCallerMagnitude(SpecHandle, GameplayTags.Debuff_Duration, Params.DebuffDuration);
	
	// 이펙트 적용
	TargetASC->ApplyGameplayEffectSpecToSelf(*SpecHandle.Data);
	return EffectContextHandle;
}

즉, 위의 과정에 의해 주 대상이 전기 타입 디버프를 받게 된다.

 

 

(2) 추가 대상에게 디버프 적용하기

추가 타겟에 대하여 루프 내에서 데미지 이펙트 파라미터를 만들어 적용하게 한다.

 


위의 과정으로 인해 어빌리티가 종료될 때 1틱의 데미지가 더 들어가게 된다.

기본적으로 이 어빌리티의 데미지가 낮기 때문에 무시할 수 있으므로 넘어간다.


 

 

(3) 스턴 정의하기

기본적으로 프로젝트 내의 모든 액터가 스턴 상태를 가질 수 있도록 멤버 변수를 가지게 된다.

해당 멤버 변수를 통해 애니메이션 블루프린트를 제어하여 스턴 상태의 애니메이션을 실행하게 한다.

 

(3-1) AuraCharacterBase에 멤버 변수 선언

	UPROPERTY(Replicated, BlueprintReadOnly)
	bool bIsStunned = false;

서버 - 클라이언트 간 복제되는 변수이므로 Replicated, 블루프린트에서 노출만 되도록 BlueprintReadOnly로 지정한다.

 

 

(3-2) 스턴 상태에서 발동할 콜백 함수 선언

	void StunTagChanged(const FGameplayTag CallbackTag, int32 NewCount);

캐릭터가 스턴 태그를 받으면 호출할 콜백 함수를 선언한다.

 

 

(3-3) AuraCharacter에서 플레이어 캐릭터가 스턴 태그를 대기하기

void AAuraCharacter::InitAbilityActorInfo()
{
    AAuraPlayerState* AuraPlayerState = GetPlayerState<AAuraPlayerState>();
    check(AuraPlayerState);
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // ASC 생성 완료를 알리는 델리게이트 호출
    OnASCRegistered.Broadcast(AbilitySystemComponent);

    // 스턴 태그 대기
    AbilitySystemComponent->RegisterGameplayTagEvent(FAuraGameplayTags::Get().Debuff_Stun, EGameplayTagEventType::NewOrRemoved).AddUObject(this, &AAuraCharacter::StunTagChanged);

게임플레이 어빌리티 시스템에 내장된 함수인 RegisterGameplayTagEvent를 사용하여 특정 태그가 생기거나 제거되면 이벤트가 발동되도록 할 수 있다.

해당 함수를 사용하여 스턴 상태 콜백 함수를 바인딩한다.

 

 

(3-4) AuraEnemy에서 몬스터 캐릭터가 스턴 태그를 대기하기

 

void AAuraEnemy::InitAbilityActorInfo()
{
    AbilitySystemComponent->InitAbilityActorInfo(this, this);
    Cast<UAuraAbilitySystemComponent>(AbilitySystemComponent)->AbilityActorInfoSet();

    if (HasAuthority())
    {
        InitializeDefaultAttributes();
    }
    // ASC가 초기화 되었음을 알리는 델리게이트 호출
    OnASCRegistered.Broadcast(AbilitySystemComponent);

    // 스턴 태그 대기
    AbilitySystemComponent->RegisterGameplayTagEvent(FAuraGameplayTags::Get().Debuff_Stun, EGameplayTagEventType::NewOrRemoved).AddUObject(this, AAuraEnemy::StunTagChanged);

몬스터 클래스를 대상으로도 스턴 태그가 들어오거나 제거되면 콜백 함수가 호출되도록 설정한다.

 


- StunTagChanged 함수가 Virtual로 되어 있지 않기 때문에 부모인 AuraCharacterBase의 StunTagChanged 함수만 호출된다. 이는 모든 캐릭터에 동일한 효과를 적용하게 하기 위해 의도된 사양이다.


 

 

(4) StunTagChanged 함수

이제 스턴이 발동되면 캐릭터에 적용하고 싶은 효과를 이 함수에 구현하면 된다.

그 전에, 몬스터가 피격당하면 HitReact를 발동시키는 로직을 확인한다.

void AAuraEnemy::HitReactTagChanged(const FGameplayTag CallbackTag, int32 NewCount)
{
    // 태그가 있을 때만 작동
    bHitReacting = NewCount > 0;
    GetCharacterMovement()->MaxWalkSpeed = bHitReacting ? 0.f : BaseWalkSpeed;
    
    if (AuraAIController && AuraAIController->GetBlackboardComponent())
    {
        // 블랙보드 키 설정
        AuraAIController->GetBlackboardComponent()->SetValueAsBool(FName("HitReacting"), bHitReacting);
    }
}

HitReact 태그가 들어오면 bHitReacting가 true가 되면서 캐릭터의 최대 이동속도를 0으로 지정하고 있다.

몬스터 캐릭터는 블랙보드 컴포넌트의 값을 바꾸지만, 플레이어 캐릭터는 블랙보드를 가질 필요가 없으므로 각 캐릭터 유형에 따라 기능을 달리 구현해야 한다.

따라서 StunTagChanged 함수를 가상 함수로 만들고, 공통적인 기능을 부모 클래스의 StunTagChanged로 이관한다.

virtual void StunTagChanged(const FGameplayTag CallbackTag, int32 NewCount);

 

 

(4-1) AuraEnemy의 기본 이동속도 멤버 변수를 AuraCharacterBase로 이관

	// 기본 이동속도
	UPROPERTY(EditAnywhere, BlueprintReadOnly, category = "Combat")
	float BaseWalkSpeed = 600.f;

모든 캐릭터의 기본 이동속도가 600이 되었으므로 몬스터의 기본 이동속도를 생성자에서 조정해준다.

AAuraEnemy::AAuraEnemy()
{
    GetMesh()->SetCollisionResponseToChannel(ECC_Visibility, ECR_Block);

    AbilitySystemComponent = CreateDefaultSubobject<UAuraAbilitySystemComponent>("AbilitySystemComponent");

    // 멀티에서의 복제
    AbilitySystemComponent->SetIsReplicated(true);
    AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Minimal);
//~~~~~~~~~~~~~~~~~~~
    BaseWalkSpeed = 250.f

 

 

(4-2) AuraCharacterBase의 StunTagChanged 함수

void AAuraCharacterBase::StunTagChanged(const FGameplayTag CallbackTag, int32 NewCount)
{
	bIsStunned = NewCount > 0;
	GetCharacterMovement()->MaxWalkSpeed = bIsStunned ? 0.f : BaseWalkSpeed;
}

플레이어 캐릭터와 몬스터 캐릭터가 공통적으로 가지는 부분이다.

 

 

(4-3) bIsStunned의 복제

	virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const;
void AAuraCharacterBase::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);

	DOREPLIFETIME(AAuraCharacterBase, bIsStunned);
}

UPROPERTY에 Replicated로 설정되어 복제를 하고자 하는 멤버 변수가 있는 클래스는 반드시 위의 함수를 오버라이드하여 복제를 지정해줘야 한다.

 

 

2. 애니메이션 블루프린트

(1) ABP_Enemy에서 AuraEnemy를 변수로 승격

bIsStunned는 캐릭터의 멤버 변수이므로, 해당 캐릭터가 유효해야 접근할 수 있다.

애니메이션 틱마다 bIsStunned 값을 가져와 멤버 변수로 저장한다.

 

 

(2) ABP_Enemy에서 스턴 스테이트 추가

각 트랜지션에 대해 Stunned 불리언에 따라 스테이트가 전환되도록 설정한다.

스턴 스테이트에는 시퀀스 플레이어를 연결하여 ABP_Enemy를 상속받는 자식 클래스에서 플레이어에 애니메이션을 각자 등록하게 한다.

 

 

(3) ABP_Enemy를 상속받는 자식 클래스에서 에셋 오버라이드

모든 자식 클래스에 대해 시퀀스 플레이어에 스턴 애니메이션을 지정한다.

 

 

(4) ABP_Aura에서 스턴 변수 가져오기

 

 

(5) ABP_Aura에 스턴 스테이트 에일리어스 추가

체크된 스테이트에서 Stunned 상태로 진입할 수 있도록 스테이트 에일리어스를 추가한다.

 

 

3. 비헤이비어 트리 수정

이제 bIsStunned가 true인 상태에서 AI가 공격, 움직임 등을 하지 않도록 수정해야 한다.

 

(1) 블랙보드 키 추가

스턴 상태를 관리하는 블랙보드 키를 추가한다.

 

 

(2) 블랙보드 데코레이터 추가

스턴 상태가 아닐 때 AI가 작동하게 하기 위해 위와 같이 설정한다.

 

 

(3) AuraEnemy 클래스에서 StunTagChanged 함수 오버라이드

	virtual void StunTagChanged(const FGameplayTag CallbackTag, int32 NewCount) override;
void AAuraEnemy::StunTagChanged(const FGameplayTag CallbackTag, int32 NewCount)
{
    Super::StunTagChanged(CallbackTag, NewCount);

    if (AuraAIController && AuraAIController->GetBlackboardComponent())
    {
        // 블랙보드 키 설정
        AuraAIController->GetBlackboardComponent()->SetValueAsBool(FName("Stunned"), bIsStunned);
    }
}

이동속도 제한은 부모의 함수에서 작동하고 있으므로, AI의 블랙보드 키를 바꿔주는 로직을 작성한다.

 

 

 

4. 테스트

(1) 몬스터의 기절 테스트

블랙보드 키를 열어놓고 기절과 동시에 Stunned 키가 true가 되고, 일정 시간 이후에 다시 false로 변경되는지 확인한다.

 

 

(2) 플레이어 캐릭터의 기절 테스트

창 고블린의 공격 유형을 전기로 바꾸고 디버프 확률을 100으로 지정한 후에 테스트한다.

캐릭터가 기절한 상태에서 이동은 불가하지만 회전은 가능한 것으로 보인다.

또한 파이어볼트 등의 어빌리티도 사용이 가능하다.

 

 

3. 디버그

(1) 플레이어가 스턴 상태일 때 어빌리티 사용 금지

Activation Blocked Tags는 아바타 액터에 해당 태그가 부여될 경우 어빌리티의 활성화를 방지한다.

위의 태그를 GA_Electorcute에도 추가한다.

 

 

(2) 플레이어 캐릭터의 스턴 애니메이션의 루프 설정

 

 

 

(3) 플레이어가 스턴 상태일 때 입력 방지

void UAuraAttributeSet::Debuff(const FEffectProperties& Props)
{
    const FAuraGameplayTags& GameplayTags = FAuraGameplayTags::Get();
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
	// 데미지 타입에 따른 디버프의 태그 가져오기
    const FGameplayTag DebuffTag = GameplayTags.DamageTypesToDebuff[DamageType];
    Effect->InheritableOwnedTagsContainer.AddTag(DebuffTag);
    
    // 스턴 상태
    if (DebuffTag.MatchesTagExact(FAuraGameplayTags::Get().Debuff_Stun))
    {
        Effect->InheritableOwnedTagsContainer.AddTag(GameplayTags.Player_Block_CursorTrace);
        Effect->InheritableOwnedTagsContainer.AddTag(GameplayTags.Player_Block_InputHeld);
        Effect->InheritableOwnedTagsContainer.AddTag(GameplayTags.Player_Block_InputPressed);
        Effect->InheritableOwnedTagsContainer.AddTag(GameplayTags.Player_Block_InputReleased);
    }

이제 테스트를 진행하면 서버에서는 정상적으로 입력 방지가 작동하지만, 클라이언트에서는 그러지 않는다는 것을 볼 수있다.

 

(3-1) bIsStunned 변수 - Rep Notify 사용

	// 스턴
	UPROPERTY(ReplicatedUsing=OnRep_Stunned, BlueprintReadOnly)
	bool bIsStunned = false;
	UFUNCTION()
	virtual void OnRep_Stunned();

해당 함수를 가상 함수로 만들어 플레이어 캐릭터에만 적용한다.

 

 

(3-2) AuraCharacter에서 OnRep_Stunned 함수 오버라이드

	UFUNCTION()
	virtual void OnRep_Stunned() override;
void AAuraCharacter::OnRep_Stunned()
{
    if (auto* AuraASC = Cast<UAuraAbilitySystemComponent>(AbilitySystemComponent))
    {
        const auto& GameplayTags = FAuraGameplayTags::Get();

        FGameplayTagContainer BlockedTags;
        BlockedTags.AddTag(GameplayTags.Player_Block_CursorTrace);
        BlockedTags.AddTag(GameplayTags.Player_Block_InputHeld);
        BlockedTags.AddTag(GameplayTags.Player_Block_InputPressed);
        BlockedTags.AddTag(GameplayTags.Player_Block_InputReleased);

        if (bIsStunned)
        {
            AuraASC->AddLooseGameplayTags(BlockedTags);
        }
        else
        {
            AuraASC->RemoveLooseGameplayTags(BlockedTags);
        }
    }
}

bIsStunned 변수가 복제될 때마다 클라이언트에서 이 함수가 호출된다.