인프런 [이득우의 언리얼 프로그래밍 Part4 - 게임플레이 어빌리티 시스템] 강의 공부 노트
멀티플레이 환경에서 플레이어 캐릭터에 GAS를 설정할 때 주의할 점!
플레이어 = (플레이어 컨트롤러)+(조정하는 폰)
→ 2개의 액터 형태이기 때문에 이를 고려한 설계가 필요하다!
→ 서버에서 보관할 데이터 / 실제로 조종하는 캐릭터의 분리 필요
GAS의 데이터를 담당하는 Actor : OwnerActor
상호작용과 비주얼을 담당하는 Actor : AvatarActor
주기적으로 서버-클라이언트로 배포되는 PlayerState가 데이터를 관리하기 가장 적합하다!
따라서 OwnerActor는 PlayerState로 지정, AvatarActor는 조종할 Character로 설정.
→ PlayerState에서 ASC 생성, 이 포인터를 Character에 넘겨주는 설계가 바람직.
→ GA는 Character를 중심으로 진행, 결과는 데이터로 ASC가 보관.
CharacterPlayer의 ASC 설정
기존의 캐릭터 플레이어에 추가된 부분.
앞의 Actor 기초와 다른 부분은, OwnerActor와 AvatarActor를 구분하였다.
또한 데이터를 PlayerState에 분리하여
CharacterPlayer의 ASC는 PossesedBy로 빙의되었을 경우, PlayerState에서 ASC를 가져오도록 한다.
VSCharacterChampionPlayer .h
class PROJECTLOL_API AVSCharacterChampionPlayer : public AVSCharacterBase, public IAbilitySystemInterface
{
GENERATED_BODY()
public:
explicit AVSCharacterChampionPlayer();
virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override;
virtual void PossessedBy(AController* NewController) override;
// GAS.
protected:
UPROPERTY(EditAnywhere, Category = GAS)
TObjectPtr<class UAbilitySystemComponent> ASC;
// 시작 시 발동할 GA 목록.
UPROPERTY(EditAnywhere, Category = GAS)
TArray<TSubclassOf<class UGameplayAbility>> StartAbilites;
}
VSCharacterChampionPlayer.cpp
AVSCharacterChampionPlayer::AVSCharacterChampionPlayer()
{
// 실제 플레이어가 빙의할 때마다 PlayerState의 ASC 값을 가져올 것.
ASC = nullptr;
}
UAbilitySystemComponent* AVSCharacterChampionPlayer::GetAbilitySystemComponent() const
{
return ASC;
}
void AVSCharacterChampionPlayer::PossessedBy(AController* NewController)
{
Super::PossessedBy(NewController);
// PlayerState에서 ASC 가져오기.
AVSPlayerState* VSPlayerState = GetPlayerState<AVSPlayerState>();
if (VSPlayerState)
{
ASC = VSPlayerState->GetAbilitySystemComponent();
// OwnerActor와 AvatarActor 초기화.
ASC->InitAbilityActorInfo(VSPlayerState, this);
// 발동할 GA 부여.
for (const auto& StartAbility : StartAbilites)
{
FGameplayAbilitySpec StartSpec(StartAbility);
ASC->GiveAbility(StartSpec);
}
}
}
GameplayAbilitySpec?
GA에 대한 정보를 담은 구조체
ASC는 이 Spec 정보만 가져 GA를 다루려면 Spec의 Handle을 사용하여 컨트롤 필요.
InputID 필드가 제공되어 Spec을 검사하여 입력에 매핑된 GA 검색 가능 : FindAbilitySpecFromInputID
사용자 입력 → ASC에서 입력에 관련된 GA 검색 → GA 발견
- GA 활성화 상태 : AbilitySpecInputPressed로 입력 왔다는 신호 전달
- GA 비활성화 상태 : TryActivateAbility로 활성화
→ 입력이 떨어지면 동일하게 처리
- GA에 입력이 떨어졌다는 신호 전달 : AbilitySpecInputReleased
VSCharacterChampionPlayer.h
class PROJECTLOL_API AVSCharacterChampionPlayer : public AVSCharacterBase, public IAbilitySystemInterface
{
protected:
// GAS.
void SetupGASInputComponent();
void GASInputPressed(int32 InputID);
void GASInputReleased(int32 InputID);
}
VSCharacterChampionPlayer.cpp
void AVSCharacterChampionPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
SetupGASInputComponent();
}
void AVSCharacterChampionPlayer::SetupGASInputComponent()
{
if (IsValid(ASC) && IsValid(InputComponent))
{
UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(InputComponent);
EnhancedInputComponent->BindAction(SkillAttackAction, ETriggerEvent::Triggered, this, &AVSCharacterChampionPlayer::GASInputPressed, 0);
EnhancedInputComponent->BindAction(UltimateSkillAttackAction, ETriggerEvent::Triggered, this, &AVSCharacterChampionPlayer::GASInputPressed, 1);
}
}
void AVSCharacterChampionPlayer::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 AVSCharacterChampionPlayer::GASInputReleased(int32 InputID)
{
FGameplayAbilitySpec* Spec = ASC->FindAbilitySpecFromInputID(InputID);
if (Spec)
{
Spec->InputPressed = false;
if (Spec->IsActive())
{
// 입력 해제 됐다는 신호 전달.
ASC->AbilitySpecInputReleased(*Spec);
}
}
}
GamplayAbility의 Instancing
- NotInstanced : 인스턴싱 없이 CDO에서 일괄 처리. 기본적인 기능. 가벼움. 상태 처리에 부적합.
- InstancedPerActor : 액터마다 하나의 어빌리티 인스턴스를 만들어 처리(Primary Instance만 존재.)
- InstancedPerExecution : 발동 시 인스턴스 생성 → 가장 먼저 생성된 인스턴스가 Primary Instance
→ Network Replication을 고려했을 경우, 대부분의 경우 InstancedPerActor가 무난함.
AT (Ability Task)
GA의 실행은 한 프레임에서 이루어짐.
GA가 시작되면 EndAbility 호출 전까지는 종료되지 않음.
애니메이션과 같은 시간 소요, 상태 관리가 필요한 GA의 구현 방법
- 비동기적으로 작업 수행 후, 완료 후 통보받는 형태 → Ability Task
AT의 활용 패턴
- AT의 작업이 끝나면 브로드캐스팅되는 종료 델리게이트 선언
- GA는 AT를 생성하고 종료 델리게이트 구독
- 구독 설정 완료되면 AT 구동 : AT의 ReadyForActivation 호출
- AT의 작업 끝난 후 종료 델리게이트 구독한 GA의 콜백 함수 호출
- GA의 EndAbility로 종료.
→ 다수의 AT를 사용하여 복잡한 액션 로직 설정 가능.
VSGA_SkillAttack.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Abilities/GameplayAbility.h"
#include "VSGA_SkillAttack.generated.h"
/**
*
*/
UCLASS()
class PROJECTLOL_API UVSGA_SkillAttack : public UGameplayAbility
{
GENERATED_BODY()
public:
UVSGA_SkillAttack();
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 OnCompletedCallback();
UFUNCTION()
void OnInterruptedCallback();
};
VSGA_SkillAttack.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "GA/Attack/VSGA_SkillAttack.h"
#include "Character/LOLVampireSurvivors/VSCharacterBase.h"
#include "Character/LOLVampireSurvivors/Champion/VSCharacterChampionPlayer.h"
#include "Abilities/Tasks/AbilityTask_PlayMontageAndWait.h"
#include "ProjectLOL.h"
UVSGA_SkillAttack::UVSGA_SkillAttack()
{
// GA의 인스턴싱 정책 지정.
InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
}
void UVSGA_SkillAttack::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
AVSCharacterChampionPlayer* VSCharacterChampion = CastChecked<AVSCharacterChampionPlayer>(ActorInfo->AvatarActor.Get());
UAbilityTask_PlayMontageAndWait* PlayAttackTask = UAbilityTask_PlayMontageAndWait::CreatePlayMontageAndWaitProxy(this, TEXT("PlayAttack"), VSCharacterChampion->GetSkillAttackMontage());
// 종료 델리게이트 구독.
PlayAttackTask->OnCompleted.AddDynamic(this, &UVSGA_SkillAttack::OnCompletedCallback);
PlayAttackTask->OnInterrupted.AddDynamic(this, &UVSGA_SkillAttack::OnInterruptedCallback);
// AT 구동.
PlayAttackTask->ReadyForActivation();
}
void UVSGA_SkillAttack::InputPressed(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo)
{
LOL_LOG(LogProjectLOL, Log, TEXT("Begin"));
}
void UVSGA_SkillAttack::CancelAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateCancelAbility)
{
Super::CancelAbility(Handle, ActorInfo, ActivationInfo, bReplicateCancelAbility);
}
void UVSGA_SkillAttack::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled)
{
Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);
}
void UVSGA_SkillAttack::OnCompletedCallback()
{
bool bReplicatedEndAbility = true;
bool bWasCancelled = false;
EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicatedEndAbility, bWasCancelled);
}
void UVSGA_SkillAttack::OnInterruptedCallback()
{
bool bReplicatedEndAbility = true;
bool bWasCancelled = true;
EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicatedEndAbility, bWasCancelled);
}
Blueprint GA에 상태 Tag 설정
'UnrealEngine5 > 공부' 카테고리의 다른 글
[UE5/GAS] AnimNotify와 Trigger Tag로 공격 판정하기 (0) | 2025.05.30 |
---|---|
[UE5/GAS] 상태Tag와 AT를 활용한 GA, AT 규칙 (0) | 2025.05.29 |
[UE5/GAS] GameAbilitySystem기초_Actor와 GAS, Gameplay Tag (0) | 2025.05.27 |
[UE5/GAS] Game Ability System이란? (0) | 2025.05.26 |
[UE5] Transform Scale과 Shape Extent의 차이 (0) | 2025.04.16 |