-
[UE5] GAS를 활용한 캐릭터의 입력 처리UE5 2025. 1. 2. 12:30
플레이어 캐릭터의 ASC 설정
- 분수대 액터와 같이 플레이어 캐릭터에 설정하는 것이 가능
- 하지만 네트워크 멀티플레이를 감안했을 때, 서버에서 클라이언트로 배포되는 액터가 보다 적합
- 이 때 많이 사용하는 액터가 주기적으로 플레이어 정보를 배포하는 PlayerState 액터
- 따라서 Owner를 PlayerState로 설정하고, Avatar를 Character로 설정하는 것이 일반적인 방법
// ABGASCharacterPlayer.cpp AABGASPlayerState* GASPS = GetPlayerState<AABGASPlayerState>(); ASC = GASPS->GetAbilitySystemComponent(); // Owner는 PlayerState, Avatar는 Character(this) ASC->InitAbilityActorInfo(GASPS, this);
어빌리티 시스템 컴포넌트의 입력 처리
- 게임 어빌리티 스펙에는 입력 값을 설정하는 필드 InputID가 제공됨
- ASC에 등록된 스펙을 검사해 입력에 매핑된 GA를 찾을 수 있음 : FindAbilitySpecFromInputID
- 사용자 입력이 들어오면 ASC에서 입력에 관련된 GA를 검색함
- 해당 GA를 발견하면, 현재 발동 중인지를 판별
- GA가 발동 중이면 입력이 왔다는 신호를 전달 : AbilitySpecInputPressed
- GA가 발동하지 않았으면 새롭게 발동시킴 : TryActivateAbility
void AABGASCharacterPlayer::GASInputPressed(int32 InputId) { FGameplayAbilitySpec* Spec = ASC->FindAbilitySpecFromInputID(InputId); if (Spec) { Spec->InputPressed = true; if (Spec->IsActive()) { ASC->AbilitySpecInputPressed(*Spec); } else { ASC->TryActivateAbility(Spec->Handle); } } }
- 입력이 떨어지면 동일하게 처리
- GA에게 입력이 떨어졌다는 신호를 전달 : AbilitySpecInputReleased
void AABGASCharacterPlayer::GASInputReleased(int32 InputId) { FGameplayAbilitySpec* Spec = ASC->FindAbilitySpecFromInputID(InputId); if (Spec) { Spec->InputPressed = false; if (Spec->IsActive()) { ASC->AbilitySpecInputReleased(*Spec); } } }
- EnhancedInputComponent의 BindAction 함수를 활용하면 범용적인 입력 처리가 가능해짐
void AABGASCharacterPlayer::SetupGASInputComponent() { if (IsValid(ASC) && IsValid(InputComponent)) { UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(InputComponent); EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Triggered, this, &AABGASCharacterPlayer::GASInputPressed, 0); EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &AABGASCharacterPlayer::GASInputReleased, 0); EnhancedInputComponent->BindAction(AttackAction, ETriggerEvent::Triggered, this, &AABGASCharacterPlayer::GASInputPressed, 1); } }
게임플레이 어빌리티의 인스턴싱 옵션
- 상황에 따라 다양한 인스턴스 정책을 지정할 수 있음
- NonInstanced : 인스턴싱 없이 CDO에서 일괄 처리
- 메모리 사용량이 낮고, 능력의 상태를 따로 관리할 필요가 없을 때 적합
- 상태를 저장할 공간이 없으므로 능력 실행 간 데이터를 공유하거나 독립적으로 관리할 수 없음
- InstancedPerActor : 액터마다 하나의 어빌리티 인스턴스를 만들어 처리 ( Primary Instance )
- 능력 실행 간 필요한 데이터를 인스턴스 변수로 저장 가능
- 서버와 클라이언트에서 동일한 애겉에 대해 독립적인 능력 인스턴스를 유지하므로 상태 동기화가 간단
- 능력을 반복적으로 실행하거나 복잡한 상태를 유지해야 하는 경우 적합
- 액터당 하나의 인스턴스를 공유하므로 불필요한 메모리 사용을 줄임
- InstancedPerExecution : 발동시 인스턴스를 생산함
- 완벽히 독립적인 상태 관리 : 여러 번 능력을 발동해도 서로 상태가 충돌하지 않음
- 능력이 비결정적이거나 실행마다 고유한 상태를 필요로 하는 경우 적합 ( 예: 투사체 발사 )
- 발동할 때마다 새로운 인스턴스를 생성하므로 메모리와 성능 오버헤드가 발생
- 네트워크에서 각 실행마다 상태를 복제 및 동기화해야 하므로 복잡도가 증가
- 능력 실행 횟수가 많을 경우 부하가 커짐
- 네트워크 리플리케이션까지 고려했을 때 InstnacedPerActor가 무난한 선택지
UABGA_Attack::UABGA_Attack() { InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor; }
어빌리티 태스크(AT)의 활용
- 어빌리티 태스크는 줄여서 AT라고 함
- 게임플레이 어빌리티(GA)의 실행(Activation)은 한 프레임에서 이루어짐
- 게임플레이 어빌리티(GA)가 시작되면 EndAbility함수가 호출되기까지는 끝나지 않음
- 애니메이션 재생 같이 시간이 소요되고 상태를 관리해야 하는 어빌리티의 구현 방법
- 비동기적으로 작업을 수행하고 끝나면 결과를 통보받는 형태로 구현
- 이를 위해 GAS는 어빌리티 태스크를 제공하고 있음
- 어빌리티 태스크(AT)의 활용 패턴
1. 어빌리티 태스크에 작업이 끝나면 브로드캐스팅되는 종료 델리게이트를 선언함
2. GA는 AT를 생성한 후 바로 종료 델리게이트를 구독함
3. GA의 구독 설정이 완료되면 AT를 구동 : AT의 ReadyForActivation 함수 호출
4. AT의 작업이 끝나면 델리게이트를 구독한 GA의 콜백 함수가 호출됨
5. GA의 콜백함수가 호출되면 GA의 EndAbility 함수를 호출해 GA를 종료
void UABGA_Attack::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) { Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData); UAbilityTask_PlayMontageAndWait* PlayAttackTask = UAbilityTask_PlayMontageAndWait::CreatePlayMontageAndWaitProxy(this, TEXT("PlayAttack"), ABCharacter->GetComboActionMontage()); PlayAttackTask->OnCompleted.AddDynamic(this, &UABGA_Attack::OnCompletedCallback); PlayAttackTask->OnInterrupted.AddDynamic(this, & UABGA_Attack::OnInterruptedCallback); PlayAttackTask->ReadyForActivation(); } void UABGA_Attack::OnCompletedCallback() { bool bReplicatedEndAbility = true; bool bWasCanceled = false; EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicatedEndAbility, bWasCanceled); } void UABGA_Attack::OnInterruptedCallback() { bool bReplicatedEndAbility = true; bool bWasCanceled = true; EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicatedEndAbility, bWasCanceled); }
- GA는 필요에 따라 다수의 AT를 사용해 복잡한 액션 로직을 설계할 수 있음
void UMyStrongAttackAbility::ActivateAbility( const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) { Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData); // 강공격 애니메이션 재생 UAbilityTask_PlayMontageAndWait* PlayMontageTask = UAbilityTask_PlayMontageAndWait::CreatePlayMontageAndWaitProxy( this, FName("StrongAttackMontage"), StrongAttackMontage); PlayMontageTask->OnCompleted.AddDynamic(this, &UMyStrongAttackAbility::OnMontageCompleted); PlayMontageTask->Activate(); } void UMyStrongAttackAbility::OnMontageCompleted() { // 일정 시간 대기 후 폭발 생성 UAbilityTask_WaitDelay* WaitDelayTask = UAbilityTask_WaitDelay::WaitDelay(this, ExplosionDelay); WaitDelayTask->OnFinish.AddDynamic(this, &UMyStrongAttackAbility::SpawnExplosion); WaitDelayTask->Activate(); } void UMyStrongAttackAbility::SpawnExplosion() { // 폭발 이펙트 생성 UAbilityTask_SpawnActor* SpawnExplosionTask = UAbilityTask_SpawnActor::SpawnActor( this, ExplosionActorClass, ExplosionLocation, FRotator::ZeroRotator); SpawnExplosionTask->OnSuccess.AddDynamic(this, &UMyStrongAttackAbility::ApplyExplosionDamage); SpawnExplosionTask->Activate(); } void UMyStrongAttackAbility::ApplyExplosionDamage(AActor* SpawnedActor) { // 폭발 범위 내 데미지 적용 UAbilityTask_ApplyRadialDamage* ApplyDamageTask = UAbilityTask_ApplyRadialDamage::ApplyRadialDamage( this, DamageAmount, ExplosionLocation, DamageRadius, nullptr, {}, GetAvatarActorFromActorInfo()); ApplyDamageTask->Activate(); // 능력 종료 EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, true, false); }
GA의 블루프린트 상속 및 게임플레이 태그 설정
- 꼭 필요한 상황이 아니라면 GA와 AT는 가급적 자기 역할만 충실하게 구현하는 것이 좋음
- 게임플레이 태그를 C++에서 설정하는 경우 기획 변경때마다 소스코드 컴파일을 수행해야 함
- 게임플레이 태그 설정은 블루프린트에서 설정하는 것이 의존성 분리에 도움이 됨
- 게임플레이 태그 설정 기획
- 점프 GA의 ActivationOwnedTags에 Character.State.IsJumping 게임플레이 태그 설정
- 공격 GA의 ActivationOwnedTags에 Character.State.IsAttacking 게임플레이 태그 설정
- GAS 디버깅을 사용해 현재 상황의 확인 가능
void AABGASCharacterPlayer::PossessedBy(AController* NewController) { APlayerController* PlayerController = CastChecked<APlayerController>(NewController); PlayerController->ConsoleCommand(TEXT("showdebug abilitysystem")); }
'UE5' 카테고리의 다른 글
[UE5] 캐릭터 어트리뷰트 설정 (0) 2025.01.06 [UE5] GAS를 활용한 캐릭터 공격 (0) 2025.01.03 [UE5] GAS(GamePlay Ability System)의 시작 (1) 2024.12.30 [UE5] 네트워크 멀티플레이어 최적화 (0) 2024.12.26 [UE5] 움직임 리플리케이션 (1) 2024.12.20