본문 바로가기

언리얼5/공부기록

UE 어빌리티 시스템 1_2 (정리) GS 입력시스템, AbilityTask

1. 입력 정보를 게임플레이 어빌리티 시스템으로 구현방법

 

플레이어 캐릭터의 ASC 설정시 주의사항

  • 분수대 액터와 같이 플레이어 캐릭터에 설정하는 것이 가능
  • 하지만 네트웍 멀티플레이를 감안했을 때 서버에서 클라이언트로 배포되는 액터가 보다 적합.
  • 이때 많이 사용하는 액터가 주기적으로 플레이어 정보를 배포하는 PlayerState 액터임
  • 따라서 Owner를 PlayerState로 설정하고 Avatar를 Character로 설정하는 것이 일반적인 방법

언리얼에서 플레이어란 PlayerController 클래스 Pawn을 조정하는 구조로 제작되어있다.

두개의 정보를 가지고있고 GAS도 이러한 두개의 객체와 상호작용을 할수있는 구조로 작성되어야한다.

이미 GAS는 이러한 문제의 대응점을 가지고있다 이전 시간에 ASC에서 확인한

Owner Actor, Avatar Actor가 분리되어있는 이유가 이것이다.

 

 

Owner : PlayerState

Avatar : 우리가 조종할 캐릭터

 

Player를 생성할 때 GAS프레임웍은 플레이어 스테이트에서 ASC를 생성하고
생성된 ASC를 캐릭터에 포인터 변수로 가지고있는것이 좋다.

 

멀티게임 개발시 주의사항

AGASPlayerState::AGASPlayerState()
{
    ASC = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("ASC"));
    //멀티게임 전용
    ASC->SetIsReplicated(true);
}

UAbilitySystemComponent* AGASPlayerState::GetAbilitySystemComponent() const
{
    return  ASC;
}

 

해당 ASC는 서버에서 클라이언트들에게 지속적으로 데이터를 줘야한다.

 

 

GamePlayAbility Spec

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

즉 스펙은 ASC와 GA 사이의 존재하며 직접 접근을 막고 서로의 상호작용을 대신처리하기위해 필요한 단위입니다
ASC에서는 필요한 데이터, GA에서는 저장하는 데이터가 Handle이 됩니다.

 

어빌리티 시스템 컴포넌트의 입력처리

  • 게임 어빌리티 스펙에는 입력 값을 설정하는 필드 InputID가 제공됨
  • ASC에 등록된 스펙을 검사해 입력에 매핑된 GA를 찾을 수 있음:FindAbilitySpecFromInputID
  • 사용자에 입력이 들어오면 ASC에서 입력에 관련된 GA를 검색함
  • 해당 GA를 발견하면 현재 발동 중인지를 판변
    GA가 발동 중이면 입력이 왔다는 신호를 전달 : AbilitySpecInputPressed

    GA가 발동하지 않았으면 새롭게 발동시킬 : TryActivateAbility
  • 입력이 떨어지면 동일하게 처리
    GA에게 입력이 끊였다는 신호를 전달 : AbilitySpecInputReleased

PlayerState Class

 

예제에서 플레이어 스테이트의 역할은 ASC를 생성한뒤 PlayerCharacter에게 전달하기위한 용도로 사용한다

class ARENABATTLE_API AGASPlayerState : public APlayerState ,public  IAbilitySystemInterface
{
	GENERATED_BODY()
public:
	AGASPlayerState();
	virtual  class UAbilitySystemComponent* GetAbilitySystemComponent() const override;

protected:
	UPROPERTY(EditAnywhere ,Category = GAS)
	TObjectPtr<class UAbilitySystemComponent> ASC;
	
};
#include "GAS/Player/GASPlayerState.h"
#include "AbilitySystemComponent.h"


AGASPlayerState::AGASPlayerState()
{
	ASC = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("ASC"));
	ASC->SetIsReplicated(true);
	//네트웍
}

UAbilitySystemComponent* AGASPlayerState::GetAbilitySystemComponent() const
{
	return  ASC;
}

PlayerCharacter

void AGASCharacterPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    Super::SetupPlayerInputComponent(PlayerInputComponent);
    
    SetupGASInputComponent();
}

1. 플레이어 인풋컴포넌트 셋업 

PossessedBy

void AGASCharacterPlayer::PossessedBy(AController* NewController)
{
	Super::PossessedBy(NewController);
	//멀티 구현 시 
	//OnNetPlayerState Event에서 구현
	AGASPlayerState* GASPS = GetPlayerState<AGASPlayerState>();
	if(GASPS)
	{
		ASC = GASPS->GetAbilitySystemComponent();
		// Owner는 Player State , Avatar는 PlayerCharacter
		ASC->InitAbilityActorInfo(GASPS,this);

		int32 InputId = 0;
		for(const auto& StartAbility : StartAbilities)
		{
			FGameplayAbilitySpec StartSpec(StartAbility);
			//GA는 InputId를 지정할수있다.
			StartSpec.InputID = InputId++;
			ASC->GiveAbility(StartSpec);
			UE_LOG(LogTemp,Log,TEXT("First1"));
			SetupGASInputComponent();
		}
	}
}

PossessedBy는 가상함수로 폰의 빙의가 발생하는 시점에서 호출된다.

 

내가 미리 생성한 PlayerState Class를 탐색하고 해당 ASC의 레퍼런스를 포인터변수로 초기화한다.

초기화된 ASC의 엑터정보를 Owner -> PlayerState , Avatar -> CharacterPlayer로 정의한다.

 

GA 컨테이너 설명

  1. StartAbility STL 타입은 이전 정리글에서 올린것과 같이 TSubClassof<GameAbility> 배열 컨테이너이다
  2. 해당 컨테이너의 초기화 요소값은 BluePrint에서 Jump를 넣어서 최초 실행 Spec을 점프로 지정해준다.
  3. 점프GA(점프 구현 코드) 함수는 언리얼에서 지원해주기에 블루프린트에서 우리의 GA개발없이도 볼수있다.
  4. 위 정리된 글과 같이 Spec 구조체는 Input 인덱스를 지원한다 해당 인덱스가 -1이면 사용되지않는다는것을 알수있다.
  5. 해당 스펙은 점프 기능을 0 input인덱스로 가지게된다. 컨테이너의 요소값은 단 하나만 블루프린트에서 추가했기에 이외
    수정된 데이터는 없다.

SetupGASInputComponent

void AGASCharacterPlayer::SetupGASInputComponent()
{
	if(IsValid(ASC) && IsValid(InputComponent))
	{
		UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(InputComponent);
		EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Triggered, this, &AGASCharacterPlayer::GASInputPressed,0);
		EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &AGASCharacterPlayer::GasInputReleased,0);
	}
}

 

PossessBy에서 점프 GASpec을 이미 초기화했다.
해당 GA를 플레이어의 입력 신호에 따라 호출되게끔 향상된 인풋의 InputAction과 호출함수를 커넥팅해준다.

지금 위 코드로 몇가지 미리 예측할 수 있다.

정의한 점프 GA가 발동할 함수는 GASInputPressed이고 그 다음 정수 인자값은 해당 함수의 파라미터이다

해당 함수는 발동할 GASpect의 input Index값이 된다.

 

GasInputReleased

GasInputReleased는 GAS를 활용 입력을 작업하면 이전과 다르게 활성화 상태를 직접 끊어줘야 만한다

플레이어의 입력이 끊어졌을시 처리도 정의를 해줘야한다.

 

 

GASInputPressed

void AGASCharacterPlayer::GASInputPressed(int32 InputId)
{
	UE_LOG(LogTemp,Log,TEXT("First"));
	FGameplayAbilitySpec* Spec = ASC->FindAbilitySpecFromInputID(InputId);
	if(Spec)
	{
		UE_LOG(LogTemp,Log,TEXT("Input Enter"));
		Spec->InputPressed = true;
		if(Spec->IsActive())
		{
			ASC->AbilitySpecInputPressed(*Spec);
		}
		else
		{
			ASC->TryActivateAbility(Spec->Handle);
		}
	}
}

 

FindAbilitySpecFromInputID: 해당 함수를 사용해 inputindex와 동일한 Spec을 가져올수있다.

Spec의 인풋 상태를 누름으로 활성화한다 해당 함수는 수동적으로 개발자가 코드로 지정해준 기능이기에

입력이 끊났을시(OnComplete) 타입의 입력처리도 false도 만들어줘야한다

Spec의 제어를 위해 존재하는 Handle값을 인자로 TryActiveAbility사용하여 GA를 발동한다

 

요점 정리

  • 플레이어는 Controller와 Pawn 두가지 기능이 결합되어 있기에 ASC를 사용하려면
    PlayerState , PlayerCharacter 두가지 클래스 분리된 상태로 관리하는게 혼선이없다.
  • GA Spec은 ASC와 GA를 직접적인 커플링을 피하기위한 소통의 수단으로 만들어진 구조체이다.
    탐색함수는 FindAbilitySpecFromInputID() 이다
    Handle은 해당 기능을 제어 또는 발동하기위해 존재한다.
  • GA는 인풋시스템을 지원하며 GA.InputID로 탐색을 위한 정수 Id를 지원한다
  • GA는 인풋시스템을 지원하며 GA.InptPressed와 같이 입력타입을 지정하여 현제 상태를 지정할 수 있다.
  • Jump GA는 언리얼에서 미리 구현되어있고 해당 GA를 찾아보고싶으면 _CharacterJump를 솔루션에서 찾아볼수있다.

2. AbilityTask

어빌리티 태스크(AT)의 활용

  • 어빌리티 태스크는 줄여서 AT라고 한다
  • 게임플레이 어빌리티 의 실행(Activation)은 한 프레임에서 이루어진다.
  • 게임플레이 어빌리티가 시작되면 EndAbility함수가 호출되기까지는 끝나지않는다
  • 애니메이션 재생 같이 시간이 소요되고 상태를 관리해야 하는 어빌리티의 구현 방법
    비동기적으로 작업을 수행하고 끝나면 결과를 통보받는 형태로 구현.
    이를 위해 GAS는 어빌리티 태스크를 제공하고 있음.

어빌리티 태스크(AT)의 활용 패턴

  1. 어빌리티 태스크에 작업이 끝나면 브로드캐스팅되는 종료 델리게이트를 선언한다
  2. GA는 AT를 생성한 후 바로 종료 델리게이트를 구독함
  3. GA의 구독 설정이 완료되면 AT를 구동 : AT의 ReadForActivation 함수 호출
  4. AT의 작업이 끝나면 델리게이트를 구독한 GA의 콜백 함수가 호출됨
  5. GA의 콜백함수가 호출되면 GA의 EndAbility 함수를 호출해 GA를 종료

GA는 다수의 AT를 사용해 복잡한 액션 로직을 설계할수 있다.

 

 

 

GA의 다양한 인스턴싱 옵션 지정

  • 상황에 따라 다양한 인스턴스 정책을 지정할 수 있음
  • NonInstanced 인스턴싱 없이 CDO에서 일괄 처리
  • InstancedPerActor 액터마다 하나의 어빌리티 인스턴스를 만들어 처리 PrimaryInstance
  • InstancedPerExecution : 발동시 인스턴스를 생산함

 

NonInstanced는 가벼운 기능을 구현할때 사용하지만 태그를 활용한 GA를 필요로할때는 적합하지않다.

InstancedPerActor가 가장 무난한 사용이 된다.