Replicated

[Drag Down] Action 인풋 처리 본문

언리얼 엔진/Drag Down (캡스톤 디자인)

[Drag Down] Action 인풋 처리

라구넹 2025. 4. 22. 18:15

애니메이션이 끝나기 전에 어빌리티 end 처리해서 애니메이션 도중에 움직일 수 있게 됨

애니메이션 종료는 태그를 따로 만들어서 처리하자


정확히 말하면 Animation과 Trace나 dodge 이벤트를 분리하는 거다

PlayMontageAndWait 태스크를 쓰지 않아서 이렇게 해야 되는 거기도 하지만, 이렇게 관리하는게 더 유연성이 더 좋기도 하다

 

태그 추가

 

 

void UDDGA_ActionBase::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
	Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);

	bIsEventTriggered = false;

	AnimEndTask = UAbilityTask_WaitGameplayEvent::WaitGameplayEvent( 
		this,
		DDTAG_EVENT_ANIMEND 
	);

	AnimEndTask->EventReceived.AddDynamic(this, &UDDGA_ActionBase::OnAnimEnd);
	AnimEndTask->ReadyForActivation();
}

액션 베이스 클래스의 ActivateAbility에서 WaitGameplayEvent에 등록해주고

 

void UDDGA_ActionBase::OnAnimEnd(FGameplayEventData Payload)
{
	EnableInput();

	bool bReplicatedEndAbility = true;
	bool bWasCancelled = false;
	EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicatedEndAbility, bWasCancelled);
}

애니메이션 종료 시 인풋을 Enable하도록 한다

 

void UDDGA_ActionBase::EnableInput()
{
	if (AvatarCharacter)
	{
		AvatarCharacter->EnableCharacterInput();
	}
}

void UDDGA_ActionBase::DisableInput()
{
	if ( AvatarCharacter )
	{
		AvatarCharacter->DisableCharacterInput();
	}
}

CharacterBase에서 Input 관련 함수를 만들어두고 해당 함수를 호출한다

 

void UDDGA_PushingCharacter::ActivateAbility(const FGameplayAbilitySpecHandle Handle,
	const FGameplayAbilityActorInfo* ActorInfo, 
	const FGameplayAbilityActivationInfo ActivationInfo, 
	const FGameplayEventData* TriggerEventData)
{
	Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);

	bIsEventTriggered = false;

	if ( AvatarCharacter )
	{
		if (AvatarCharacter->GetCharacterMovement()->IsFalling())
		{
			if (ASC)
			{
				ASC->TryActivateAbilityByClass(UDDGA_JumpPushingCharacter::StaticClass());
			}
			
			bool bReplicatedEndAbility = true;
			bool bWasCancelled = false;
			EndAbility(Handle, ActorInfo, ActivationInfo, true, false);
			return;
		}
	}

	DisableInput();

그리고 세부 구현 클래스의 ActivateAbility에서 Diable 해준다

 

 

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "DDCharacterBase.generated.h"

UCLASS()
class DRAGDOWN_API ADDCharacterBase : public ACharacter
{
	GENERATED_BODY()

public:
	ADDCharacterBase();

	UFUNCTION(NetMulticast, Reliable)
	void NetMulticastPlayAnimMontage(UAnimMontage* Montage, FName SectionName);

	void EnableCharacterInput();
	void DisableCharacterInput();

protected:
	TObjectPtr<class UDDAttackStateComponent> AttackStateComponent;

	UFUNCTION(Client, Reliable)
	void ClientEnableInput();

	UFUNCTION(Client, Reliable)
	void ClientDisableInput();

	void HandleEnableInput();
	void HandleDisableInput();
};

CharacterBase에선 Input을 RPC로 제어한다

이유는, RPC로 애니메이션을 제어하다보니 서버의 권위 실행이 클라이언트의 예측 실행보다 먼저 발생하고 클라이언트에선 발동을 안해버리는 경우가 있어서 Input이 다시 Enable되지 않는 경우도 있음

Disable은 잘 작동하긴 하는데, 일단 더 단단한 구조로 만들기 위해 RPC로 처리

 

void ADDCharacterBase::EnableCharacterInput()
{
	HandleEnableInput();

	if ( HasAuthority() )
	{
		ClientEnableInput();
	}
}

void ADDCharacterBase::DisableCharacterInput()
{
	HandleDisableInput();

	if ( HasAuthority() )
	{
		ClientDisableInput();
	}
}

void ADDCharacterBase::HandleEnableInput()
{
	if (GetController() == nullptr) return;
	APlayerController* PC = Cast<APlayerController>(GetController());
	if (PC == nullptr) return;

	PC->EnableInput(PC);
	EnableInput(PC);
}

void ADDCharacterBase::HandleDisableInput()
{
	if (GetController() == nullptr) return;
	APlayerController* PC = Cast<APlayerController>(GetController());
	if (PC == nullptr) return;

	PC->DisableInput(PC);
	DisableInput(PC);
}

void ADDCharacterBase::ClientEnableInput_Implementation()
{
	if ( IsLocallyControlled() )
	{
		HandleEnableInput();
	}
}

void ADDCharacterBase::ClientDisableInput_Implementation()
{
	if (IsLocallyControlled())
	{
		HandleDisableInput();
	}
}

서버가 실행되면 서버 캐릭터에 대해서 실행하고 클라이언트에 RPC를 날려주는 구조

 

 

동영상에선 확인이 안되는데 키 씹힘이 생겼다

두번은 눌러야 나간다

 

그냥 입력을 아예 안받는 대신 내부에서 처리하는 방법이 나을 듯..

그리고 이게 더 구조적으로 안전할 것 같다

 

CharacterBase에 변수 두고

 

액션에 조건 체크

 

void UDDGA_ActionBase::EnableInput()
{
	if (AvatarCharacter)
	{
		AvatarCharacter->SetActionEnabled(true);
	}
}

void UDDGA_ActionBase::DisableInput()
{
	if ( AvatarCharacter )
	{
		AvatarCharacter->SetActionEnabled(false);
	}
}
void ADDCharacterBase::ClientSetActionEnabled_Implementation(bool bInActionEnabled)
{
	if ( IsLocallyControlled() )
	{
		bIsActionEnabled = bInActionEnabled; 
	}
}

void ADDCharacterBase::SetActionEnabled(bool bInActionEnabled)
{
	bIsActionEnabled = bInActionEnabled;

	if ( HasAuthority() )
	{
		ClientSetActionEnabled(bInActionEnabled);
	}
}

액션 세팅은 RPC 처리

이제 문제 없이 작동하고, 카메라 회전은 예외로 처리해서 인풋을 받을 수 있는 더 유연한 구조이다