[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