[UE GAS] 캐릭터 입력처리

학습목표
- 플레이어 캐릭터의 게임플레이 어빌리티 시스템 설정 방법의 학습
- 플레이어 캐릭터의 입력에 따른 게임플레이 어빌리티 발동 구현
- 어빌리티 태스크의 활용 방법의 학습
- 게임플레이 어빌리티 시스템의 디버깅 방법의 학습
목차
목표
플레이어 캐릭터의 기획
플레이어 캐릭터의 기획
- 기존 플레이어 캐릭터(ABCharacterPlayer) 클래스를 상속
- 입력에 따라 정해진 게임플레이 어빌리티가 발동되도록 설정
- 점프 GA: 스페이스 바를 누르면 점프 어빌리티가 발동
- 공격 GA: 마우스 왼쪽 클릭시 공격 어빌리티가 발동
게임플레이 어빌리티 시스템의 기본 흐름

ASC + GA Spec + GA를 사용하여 공격 구현하기
플레이어 캐릭터의 AbilitySystemComponent(ASC) 설정
플레이어 캐릭터의 AbilitySystemComponent(ASC) 설정
- 분수대 액터와 같이 플레이어 캐릭터에 설정하는 것이 가능
- 하지만 네트웍 멀티플레이를 감안했을때, 서버에서 클라이언트로 배포되는 액터가 보다 적합
- 이 때 많이 사용하는 액터가 주기적으로 플레이어 정보를 배포하는 PlayerState 액터임
- 따라서 Owner를 PlayerState로 설정하고, Avatar를 Character로 설정하는 것이 일반적인 방법
프레임웍의 데이터를 누가 관리하는게 좋을까?? (= PlayerState에서ASC를 설정하는 이유는?)
- 네트워크 멀티플레이를 감안하면 PlayerState가 가장 적합하다.
- PlayerState은 주기적으로 서버에서 클라이언트로 배포되는 액터이기 때문이다.
- 하지만 실제 게임에서의 상호작용은 Pawn이 담당하기 때문에 PlayerState의 역할과 Pawn의 역할을 구분해주는 것이 필요하다.
- GAS에서 데이터를 담당하는 액터를 OwnerActor라고 부른다.
- OwnerActor를 PlayerState 액터로 지정하여 설계
- 우리가 플레이어 캐릭터를 생성할 때는 GAS Framework는 PlayerState에서 AbilitySystemComponent(ASC)를 생성하고 이 생성된 ASC 포인터를 캐릭터에 넘겨준다.
- 이후에 발동한 캐릭터의 어빌리티들은 대부분 모두 캐릭터의 중심으로 진행된다.
- 이 어빌리티로 인한 결과는 ASC가 데이터로 보관하고 이것들은 실시간으로 서버에서 클라이언트로 전송한다.
- 실제 게임에서 물체와 상호작용하는 비주얼적인 부분을 담당하는 액터를 Avatar Actor라고 부른다.
→ Avatar Actor는 우리가 조종할 캐릭터로 설계

게임플레이 어빌리티 스펙 Gameplay Ability Spec (GA Spec)
Gameplay Ability Spec (GA Spec)
- 게임플레이 어빌리티에 대한 정보를 담고 있는 구조체
- ASC는 직접 어빌리티를 참조하지 않고 Spec 정보만 가지고 있음
- Spec은 어빌리티의 현재 상태와 같은 다양한 정보를 가지고 있음
- ASC로부터 어빌리티를 다루고자 할 경우 Spec에 있는 Handle을 사용해 컨트롤함
- 핸들 값은 전역으로 설정되어 있으며 Spec 생성시자동으로 1씩 증가함. 기본값 -1
- 어빌리티 정보: Spec
- 어빌리티 인스턴스에 대한 레퍼런스: Spec Handle

어빌리티에 대한 정보는 Spec 데이터를 통해 확인할 수 있다.
어빌리티를 컨트롤하기 위해서는 Spec에 포함된 Spec Handle 정보를 사용해서 어빌리티를 직접 제어한다.
어빌리티 시스템 컴포넌트의 입력 처리
- 게임 어빌리티 스펙에는 입력 값을 설정하는 필드 InputID가 제공됨
- ASC에 등록된 Spec을 검사해 입력에 매핑된 GA를 찾을 수 있음: FindAbilitySpecFromInputID
- 사용자 입력이 들어오면 ASC에서 입력에 관련된 GA를 검색함
- 해당 GA를 발견하면, 현재 발동 중인지를 판별
- GA가 발동 중이면 입력이 왔다는 신호를 전달: AbiltiySpecInputPressed
- GA가 발동하지 않았다면 새롭게 발동시킴: TryActivateAbility
- 입력이 떨어지면 동일하게 처리
- GA에게 입력이 떨어졌다는 신호를 전달: AbiltiySpecInputReleased
EnhancedInputComponent의 BindAction 함수를 활용하면
범용적인 입력 처리가 가능해짐
게임플레이 어빌리티의 인스턴싱 옵션
상황에 따라 다양한 인스턴스 정책을 지정할 수 있음
NonInstanced | 인스턴싱 없이 CDO에서 일괄 처리 |
InstancedPerActor | - 액터마다 하나의 어빌리티 인스턴스를 만들어 처리. - Primary Instance만 존재함 - 네트워크 리플리케이션을 고려했을때 이 정보들도 넘어가는데 InstancedPerActor가 리플리케이션을 하기에 용이하기 때문에 많이 사용된다. 예를들어, 캐릭터 액션이 여러 개가 동시에 발동될 필요가 없기 때문에 InstancedPerActor를 사용해서 하나의 인스턴스에서시작과 끝이 명확하게 지정하여 사용하면 된다. |
InstancedPerExecution | - 어빌리티를 발동할 때마다 인스턴스를 (계속해서) 생산함 - 인스턴스가 여러개 생길 수도 있음. 가장 먼저 생성된 인스턴스를 Primary Instance라고 부름 - InstancedPerExecution는 비용이 비싸다. |
네트웍 리플리케이션까지 고려했을때
InstancedPerActor가 무난한 선택지다
어빌리티 태스크 Ability Task(AT)의 활용
Ability Task (AT)
- GA의 실행(Activation)은 한 프레임에서 이루어짐
- GA가 시작되면 EndAbility함수가 호출되기까지는 끝나지 않음
- 애니메이션 재생 같이 시간이 소요되고 상태를 관리해야 하는 어빌리티의 구현 방법
- 비동기적으로 작업을 수행하고 끝나면 결과를 통보받는 형태로 구현
- 이를 위해 GAS는 Ability Task(AT)를 제공하고 있음
Ability Task (AT)의 활용 패턴
- Ability Task(AT)에 작업이 끝나면 브로드캐스팅되는 종료 델리게이트를 선언함
- GA는 AT를 생성한 후 바로 종료 델리게이트를 구독함
- GA의 구독 설정이 완료되면 AT를 구동: AT의 ReadyForActivation 함수 호출
- AT의 작업이 끝나면 델리게이트를 구독한 GA의 콜백 함수가 호출됨
- GA의 콜백함수가 호출되면 GA의 EndAbility 함수를 호출해 GA를 종료
GA는 필요에 따라 다수의 Ability Task(AT)를 사용해 복잡한 액션 로직을 설계할 수 있음
코드 구현
ABCharacterPlayer
ABCharacterPlayer.h
더보기
#pragma once #include "CoreMinimal.h" #include "Character/ABCharacterPlayer.h" #include "AbilitySystemInterface.h" #include "ABGASCharacterPlayer.generated.h" UCLASS() class ARENABATTLEGAS_API AABGASCharacterPlayer : public AABCharacterPlayer, public IAbilitySystemInterface { GENERATED_BODY() public: AABGASCharacterPlayer(); virtual class UAbilitySystemComponent* GetAbilitySystemComponent() const override; virtual void PossessedBy(AController* NewController) override; virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; protected: void SetupGASInputComponent(); void GASInputPressed(int32 InputId); void GASInputReleased(int32 InputId); protected: UPROPERTY(EditAnywhere, Category = GAS) TObjectPtr<class UAbilitySystemComponent> ASC; UPROPERTY(EditAnywhere, Category = GAS) TArray<TSubclassOf<class UGameplayAbility>> StartAbilities; // 플레이어에게 지정할 어빌리티 목록들 UPROPERTY(EditAnywhere, Category = GAS) TMap<int32, TSubclassOf<class UGameplayAbility>> StartInputAbilities; };
ABCharacterPlayer.cpp
더보기
#include "Character/ABGASCharacterPlayer.h" #include "AbilitySystemComponent.h" #include "Player/ABGASPlayerState.h" #include "EnhancedInputComponent.h" AABGASCharacterPlayer::AABGASCharacterPlayer() { ASC = nullptr; // Attack 몽타주 static ConstructorHelpers::FObjectFinder<UAnimMontage> ComboActionMontageRef(TEXT("/Script/Engine.AnimMontage'/Game/ArenaBattleGAS/Animation/AM_ComboAttack.AM_ComboAttack'")); if (ComboActionMontageRef.Object) { ComboActionMontage = ComboActionMontageRef.Object; } } UAbilitySystemComponent* AABGASCharacterPlayer::GetAbilitySystemComponent() const { return ASC; } void AABGASCharacterPlayer::PossessedBy(AController* NewController) { // PossessedBy(AController* NewController)는 네크워크 멀티프레임웍에서는 서버에서만 호출된다. // 네크워크 멀티프레이어에서도 이것을 전달 받으려면 OnLapPlayerStateEvent함수에서 구현을 해줘야 한다. // 이 예제는 싱글 플레이어 기준이므로 위의 사항을 고려하지 않고 진행하였다. Super::PossessedBy(NewController); AABGASPlayerState* GASPS = GetPlayerState<AABGASPlayerState>(); if (GASPS) { ASC = GASPS->GetAbilitySystemComponent(); ASC->InitAbilityActorInfo(GASPS, this); for (const auto& StartAbility : StartAbilities) { FGameplayAbilitySpec StartSpec(StartAbility); ASC->GiveAbility(StartSpec); } for (const auto& StartInputAbility : StartInputAbilities) { FGameplayAbilitySpec StartSpec(StartInputAbility.Value); StartSpec.InputID = StartInputAbility.Key; ASC->GiveAbility(StartSpec); } SetupGASInputComponent(); APlayerController* PlayerController = CastChecked<APlayerController>(NewController); PlayerController->ConsoleCommand(TEXT("showdebug abilitysystem")); } } void AABGASCharacterPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) { Super::SetupPlayerInputComponent(PlayerInputComponent); SetupGASInputComponent(); } 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); } } 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); } } } void AABGASCharacterPlayer::GASInputReleased(int32 InputId) { FGameplayAbilitySpec* Spec = ASC->FindAbilitySpecFromInputID(InputId); if (Spec) { Spec->InputPressed = false; if (Spec->IsActive()) { ASC->AbilitySpecInputReleased(*Spec); } } }
BP_ABGASCharacterPlayer

ABGA_Attack
ABGA_Attack.h
더보기
#pragma once #include "CoreMinimal.h" #include "Abilities/GameplayAbility.h" #include "ABGA_Attack.generated.h" UCLASS() class ARENABATTLEGAS_API UABGA_Attack : public UGameplayAbility { GENERATED_BODY() public: UABGA_Attack(); public: virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override; virtual void InputPressed(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo) 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; protected: UFUNCTION() void OnCompleteCallback(); UFUNCTION() void OnInterruptedCallback(); };
ABGA_Attack.cpp
더보기
#include "GA/ABGA_Attack.h" #include "Character/ABCharacterBase.h" #include "Abilities/Tasks/AbilityTask_PlayMontageAndWait.h" // AbilitySystem 내장클래스 #include "ArenaBattleGAS.h" #include "GameFramework/CharacterMovementComponent.h" UABGA_Attack::UABGA_Attack() { InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor; } void UABGA_Attack::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) { Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData); AABCharacterBase* ABCharacter = CastChecked<AABCharacterBase>(ActorInfo->AvatarActor.Get()); ABCharacter->GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_None); UAbilityTask_PlayMontageAndWait* PlayAttackTask = UAbilityTask_PlayMontageAndWait::CreatePlayMontageAndWaitProxy(this, TEXT("PlayAttack"), ABCharacter->GetComboActionMontage()); // 몽타주 재생이 끝나면 아래의 콜백 함수들이 호출된다. PlayAttackTask->OnCompleted.AddDynamic(this, &UABGA_Attack::OnCompleteCallback); PlayAttackTask->OnInterrupted.AddDynamic(this, &UABGA_Attack::OnInterruptedCallback); PlayAttackTask->ReadyForActivation(); } void UABGA_Attack::InputPressed(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo) { ABGAS_LOG(LogABGAS, Log, TEXT("Begin")); } void UABGA_Attack::CancelAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateCancelAbility) { Super::CancelAbility(Handle, ActorInfo, ActivationInfo, bReplicateCancelAbility); } void UABGA_Attack::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled) { Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled); AABCharacterBase* ABCharacter = CastChecked<AABCharacterBase>(ActorInfo->AvatarActor.Get()); ABCharacter->GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_Walking); } void UABGA_Attack::OnCompleteCallback() { bool bReplicatedEndAbility = true; // 네트워크 리플리케이션 여부 bool bWasCancelled = false; // 취소됬는지 여부. false면 취소되지 않았다는 의미 EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicatedEndAbility, bWasCancelled); // 명시적으로 어빌리티가 끝났다고 지정 } void UABGA_Attack::OnInterruptedCallback() { bool bReplicatedEndAbility = true; // 네트워크 리플리케이션 여부 bool bWasCancelled = true; // Interrupted된 경우니까 true로 설정하여 취소시킴 EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicatedEndAbility, bWasCancelled); }
실행화면


Game Tag 사용하기
GA의 블루프린트 상속 및 게임플레이 태그 설정
- 꼭 필요한 상황이 아니라면 GamplayAbility(GA)와 AbilityTask(AT)는 가급적 자기 역할만 충실하게 구현하는 것이 좋음
- Gameplay Tag를 C++에서 설정하는 경우 기획 변경때마다 소스코드 컴파일을 수행해야 함
- Gameplay Tag 설정은 블루프린트에서 설정하는 것이 의존성 분리에 도움이 됨
- Gameplay Tag 설정 기획
- 점프 GA의 ActivationOwnedTags에 ' Character.State.IsJumping ' Gameplay Tag 설정
- 공격 GA의 ActivationOwnedTags에 ' Character.State.IsAttacking ' Gameplay Tag 설정
GAS 디버깅을 사용해 현재 상황의 확인 가능
~ 누르고 showdebug abilitysystem 명령어를 입력하면 디버깅 화면 확인가능
Project Settings에서 Game Tag 추가
Project Settings - Gameplay Tags
- Gameplay Tag List: Add New Tag
- Character.State.IsAttacking 추가
- Character.State.IsJumping 추가

Gameplay Tag를 상속받는 Blueprint 생성 - BPGA_Attack, BPGA_Character
BPGA_Attack, BPGA_Character 생성



BPGA_Attack

Gameplay Tag 설정
- Details - Activation Owned Tags
- Character.State.IsAttacking
BP_ABGASCharacterPlayer

플레이어 캐릭터의 입력을 GAS으로 사용
- Details - GAS - Start Input Abilities에 elements 2개 추가
- BPGA_CharacterJump 할당
- BPGA_Attack 할당
'⭐ Unreal Engine > UE Game Ability System(GAS)' 카테고리의 다른 글
[UE GAS] 캐릭터 어트리뷰트 설정 (0) | 2024.03.07 |
---|---|
[UE GAS] 공격 판정 시스템의 구현 (0) | 2024.03.06 |
[UE GAS] 캐릭터의 콤보 공격의 구현 Implementing Combo Action, 상태를가지는 점프 어빌리티의 구현하기 (0) | 2024.03.05 |
[UE GAS] 어빌리티시스템 컴포넌트 AbilitySystemComponent, 게임플레이 태그 Gameplay Tag (0) | 2024.03.03 |
[UE GAS] 어빌리티 시스템 (Game Ability System) (0) | 2024.03.03 |
댓글
이 글 공유하기
다른 글
-
[UE GAS] 공격 판정 시스템의 구현
[UE GAS] 공격 판정 시스템의 구현
2024.03.06 -
[UE GAS] 캐릭터의 콤보 공격의 구현 Implementing Combo Action, 상태를가지는 점프 어빌리티의 구현하기
[UE GAS] 캐릭터의 콤보 공격의 구현 Implementing Combo Action, 상태를가지는 점프 어빌리티의 구현하기
2024.03.05 -
[UE GAS] 어빌리티시스템 컴포넌트 AbilitySystemComponent, 게임플레이 태그 Gameplay Tag
[UE GAS] 어빌리티시스템 컴포넌트 AbilitySystemComponent, 게임플레이 태그 Gameplay Tag
2024.03.03 -
[UE GAS] 어빌리티 시스템 (Game Ability System)
[UE GAS] 어빌리티 시스템 (Game Ability System)
2024.03.03
댓글을 사용할 수 없습니다.