본문 바로가기
포트폴리오 제작/Project_P

Project_P 캐릭터에 GA 부여 및 GA 실행

by k99812 2024. 8. 27.

캐릭터 ASC에 GA 부여

APPGASCharacterPlayer 헤더파일


// GAS Section
protected:
	virtual void SetupGASPlayerInputComponent();

	void GASInputPressed(int32 InputID);

	void GASInputReleased(int32 InputID);

	UPROPERTY(EditAnywhere, Category = "GAS")
	TArray<TSubclassOf<class UGameplayAbility>> StartAbilites;

	UPROPERTY(EditAnywhere, Category = "GAS")
	TMap<EInputAbility, TSubclassOf<class UGameplayAbility>> StartInputAbilites;

 

  • virtual void SetupGASPlayerInputComponent();
    • GA를 활성화 시키는 인풋액션을 Bind하는 함수
    • SetupPlayerInputComponent 함수, PossessedBy 함수에서 한번씩 실행
    • 바인드 과정에서 GA에 부여된 ID를 인자로 넘겨줄 수 있음
  • void GASInputPressed(int32 InputID);

    void GASInputReleased(int32 InputID);
    • 입력 시작, 완료 이벤트 콜백함수 매개변수로 GA에 부여된 ID를 받음
  • TArray<TSubclassOf<class UGameplayAbility>> StartAbilites;
    • 입력과 상관없이 발동되는 GA
    • TArray 자료형을 사용해 GA를 받음
    • ex) 애니메이션 노티파이로 AttackHitCheck_GA 실행
      • 애니메이션 노티파이로 GA를 실행할 경우 입력과 상관없이 GA가 발동
  • TMap<EInputAbility, TSubclassOf<class UGameplayAbility>> StartInputAbilites;
    • 입력을 통해 발동되는 GA
    • TMap 자료형을 통해 InputID로 지정할 EInputAbility Enum 클래스와 GA를 받음
    • EInputAbility는 따로 선언한 Enum 클래스
      • int32로 선언하면 직접 번호를 입력 해야 됨
        하지만 Enum클래스를 사용하면 Enum을 통해 선택하므로 좀더 알아보기 편함

 

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, Meta = (AllowPriaveteAccess = "true"))
	TObjectPtr<class UInputAction> JumpAction;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, Meta = (AllowPriaveteAccess = "true"))
	TObjectPtr<class UInputAction> LookAction;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, Meta = (AllowPriaveteAccess = "true"))
	TObjectPtr<class UInputAction> SprintAction;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, Meta = (AllowPriaveteAccess = "true"))
	TObjectPtr<class UInputAction> LeftAttackAction;

 

  • GA 입력에 필요한 InputAction 변수들을 선언

EInputAbility

UENUM(BlueprintType)
enum class EInputAbility : uint8
{
	None UMETA(DisplayName = "None"),
	Jump = 10 UMETA(DisplayName = "Jump"),
	Sprint UMETA(DisplayName = "Sprint"),
	LeftAttack UMETA(DisplayName = "LAttack"),
	RightAttack UMETA(DisplayName = "RAttack"),
	Skill UMETA(DisplayName = "Skill")
};

 

  • Enum 클래스 선언시 UENUM(BlueprintType)를 붙여야 함
  • 첫 시작 변수는 반드시 None(다른 변수명도 가능)을 생성해 0 부터 시작해야함
  • 두번째 변수부터 숫자를 지정할 수 있음
    • ex) 10을 지정하면 그 다음부터 11, 12 순으로 이어짐
  • UMETA(DisplayName = "None") 매크로는 에디터에 Enum 변수들이 표기되는 이름을 설정

 

APPGASCharacterPlayer  Cpp 파일


생성자

APPGASCharacterPlayer::APPGASCharacterPlayer()
{
//GAS 인풋 설정
	static ConstructorHelpers::FObjectFinder<UInputAction> JumpInputActionRef(TEXT("/Script/EnhancedInput.InputAction'/Game/Project_P/Input/Actions/IA_Jump.IA_Jump'"));
	if (JumpInputActionRef.Object)
	{
		JumpAction = JumpInputActionRef.Object;
	}

	static ConstructorHelpers::FObjectFinder<UInputAction> LookInputActionRef(TEXT("/Script/EnhancedInput.InputAction'/Game/Project_P/Input/Actions/IA_Look.IA_Look'"));
	if (LookInputActionRef.Object)
	{
		LookAction = LookInputActionRef.Object;
	}

	static ConstructorHelpers::FObjectFinder<UInputAction> SprintActionRef(TEXT("/Script/EnhancedInput.InputAction'/Game/Project_P/Input/Actions/IA_Sprint.IA_Sprint'"));
	if (SprintActionRef.Object)
	{
		SprintAction = SprintActionRef.Object;
	}

	static ConstructorHelpers::FObjectFinder<UInputAction> LeftAttackActionRef(TEXT("/Script/EnhancedInput.InputAction'/Game/Project_P/Input/Actions/IA_LeftAttack.IA_LeftAttack'"));
	if (LeftAttackActionRef.Object)
	{
		LeftAttackAction = LeftAttackActionRef.Object;
	}
}

 

  • GA 입력에 필요한 InputAction 변수들을 생성자에서 ConstructorHelpers를 이용해 애셋에서 불러옴

 

PossessedBy

void APPGASCharacterPlayer::PossessedBy(AController* NewController)
{
	Super::PossessedBy(NewController);

	APPGASPlayerState* GASPlayerState = GetPlayerState<APPGASPlayerState>();
	if (GASPlayerState)
	{
		ASC = GASPlayerState->GetAbilitySystemComponent();
		if (ASC)
		{
			ASC->InitAbilityActorInfo(GASPlayerState, this);

			for (const TSubclassOf<UGameplayAbility>& StartAbility : StartAbilites)
			{
				//ASC는 직접적으로 GA를 접근, 관리하는게 아닌
				//FGameplayAbilitySpec 구조체를 통해 간접적으로 관리함
				FGameplayAbilitySpec Spec(StartAbility);

				ASC->GiveAbility(Spec);
			}

			for (const TPair<EInputAbility, TSubclassOf<class UGameplayAbility>>& StartInputAbility : StartInputAbilites)
			{
				FGameplayAbilitySpec Spec(StartInputAbility.Value);

				Spec.InputID = (int32)StartInputAbility.Key;

				ASC->GiveAbility(Spec);
			}

			//서버에서도 GA바인드 함수가 실행되도록 실행
			SetupGASPlayerInputComponent();
		}
	}

	//콘솔 커멘드 코드로 입력하는 법
	//ASC 디버그
	APlayerController* PlayerController = CastChecked<APlayerController>(NewController);
	PlayerController->ConsoleCommand(TEXT("showdebug abilitysystem"));
}

 

  • for (const TSubclassOf<UGameplayAbility>& StartAbility : StartAbilites)
    {
              FGameplayAbilitySpec Spec(StartAbility);
              ASC->GiveAbility(Spec);
    }
    • for문을 사용해 StartAbilites에 있는 인자들을 StartAbility로 받음
    • FGameplayAbilitySpec Spec(StartAbility);
      • StartAbility를 갖고있는 FGameplayAbilitySpec을 생성
      • ASC에 GA를 등록하려면 FGameplayAbilitySpec을 통해 간접적으로 등록 해야함
    • ASC->GiveAbility(Spec);
      • 만든 Spec을 가지고 ASC에 등록
  • for (const TPair<EInputAbility, TSubclassOf<class UGameplayAbility>>& StartInputAbility : StartInputAbilites)
    {
              FGameplayAbilitySpec Spec(StartInputAbility.Value);
              Spec.InputID = (int32)StartInputAbility.Key;
              ASC->GiveAbility(Spec);
    }
    • 위 반복문과 동일하게 Spec 생성
    • Spec.InputID = (int32)StartInputAbility.Key;
      • Spec에 선언되어 있는 InputID에 ID 등록
      • EInputAbility로 선언해 int32로 형변환을 해야함
  • SetupGASPlayerInputComponent();
    • GA를 사용하는 인풋 액션 바인드 함수
    • 서버에서도 GA바인드 함수가 실행되도록 PossessedBy함수에서도 실행
  • APlayerController* PlayerController = CastChecked<APlayerController>(NewController);
    PlayerController->ConsoleCommand(TEXT("showdebug abilitysystem"));
    • 에디터에서 `를 누른뒤 showdebug abilitysystem 입력하면 GAS 디버깅을 할 수 있음
    • 매번 입력하기 힘드니 위와 같은 코드로도 커맨드를 입력할 수 있음

 

SetupGASPlayerInputComponent

void APPGASCharacterPlayer::SetupGASPlayerInputComponent()
{
	if (IsValid(ASC) && IsValid(InputComponent))
	{
		UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(InputComponent);

	//Jump
		EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Triggered, this, &APPGASCharacterPlayer::GASInputPressed, (int32)EInputAbility::Jump);
		EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &APPGASCharacterPlayer::GASInputReleased, (int32)EInputAbility::Jump);

	//Sprint
		EnhancedInputComponent->BindAction(SprintAction, ETriggerEvent::Triggered, this, &APPGASCharacterPlayer::GASInputPressed, (int32)EInputAbility::Sprint);
		EnhancedInputComponent->BindAction(SprintAction, ETriggerEvent::Completed, this, &APPGASCharacterPlayer::GASInputReleased, (int32)EInputAbility::Sprint);
	
	//Attack
		EnhancedInputComponent->BindAction(LeftAttackAction, ETriggerEvent::Triggered, this, &APPGASCharacterPlayer::GASInputPressed, (int32)EInputAbility::LeftAttack);
		EnhancedInputComponent->BindAction(LeftAttackAction, ETriggerEvent::Completed, this, &APPGASCharacterPlayer::GASInputReleased, (int32)EInputAbility::LeftAttack);
	}

}

 

  • if (IsValid(ASC) && IsValid(InputComponent))
    • GA를 사용하는 인풋액션이므로 ASC와 인풋컴포넌트가 존재하는지 확인
  • EnhancedInputComponent->
    BindAction(JumpAction, ETriggerEvent::Triggered, this, &APPGASCharacterPlayer::GASInputPressed, 
    (int32)EInputAbility::Jump);

    EnhancedInputComponent->
    BindAction(JumpAction, ETriggerEvent::Completed, this, &APPGASCharacterPlayer::GASInputReleased, 
    (int32)EInputAbility::Jump);
    • Jump, Sprint, Attack GA에 대해 Tirggered, Completed 이벤트 바인드

 

GASInputPressed

void APPGASCharacterPlayer::GASInputPressed(int32 InputID)
{
	FGameplayAbilitySpec* Spec = ASC->FindAbilitySpecFromInputID(InputID);

	if (Spec)
	{
		Spec->InputPressed = true;

		if (Spec->IsActive())
		{
			//어빌리티가 실행중이면 GA의 InputPressed 함수 실행
			ASC->AbilitySpecInputPressed(*Spec);
		}
		else
		{
			//어빌리티 Activate 실행
			//어빌리티의 실행 등 ASC로부터 GA를 다루는건 Handle을 통해 컨트롤
			ASC->TryActivateAbility(Spec->Handle);
		}
	}
}

 

  • FGameplayAbilitySpec* Spec = ASC->FindAbilitySpecFromInputID(InputID);
    • PossessedBy 함수에서 ASC에 GA를 등록할때 스펙에 부여한 InputID를 통해
      ASC로 부터 Spec을 찾아올 수 있음
  • if (Spec)
    • 스펙이 존재하면
    • Spec->InputPressed = true;
      • 해당 GA가 인풋이 들어왔다고 true로 변경
      • Spec 자체에 선언되어 있음
    • if (Spec->IsActive())
      • ASC->AbilitySpecInputPressed(*Spec);
        • 어빌리티가 실행중이면 GA의 InputPressed 함수 실행
        • AbilitySpecInputPressed 함수에선 Spec.GetAbilityInstances() 함수로 인스턴스를 가져와
          해당 인스턴스로 Instance->InputPressed() 함수를 실행
    • else
      • ASC->TryActivateAbility(Spec->Handle);
        • 어빌리티 Activate 실행
        • 어빌리티의 실행 등 ASC로부터 GA를 다루는건 Handle을 통해 컨트롤

 

GASInputReleased

void APPGASCharacterPlayer::GASInputReleased(int32 InputID)
{
	FGameplayAbilitySpec* Spec = ASC->FindAbilitySpecFromInputID(InputID);

	if (Spec)
	{
		Spec->InputPressed = false;

		if (Spec->IsActive())
		{
			//어빌리티가 실행중이면 GA의 InputReleased 실행
			ASC->AbilitySpecInputReleased(*Spec);
		}
	}
}

 

  • FGameplayAbilitySpec* Spec = ASC->FindAbilitySpecFromInputID(InputID);
    • InputID로 Spec가져오기
  • if (Spec)
    • Spec->InputPressed = false;
      • 입력이 끝났다고 false로 변경
    • if (Spec->IsActive())
      • ASC->AbilitySpecInputReleased(*Spec);
        • 어빌리티가 실행중이면 GA의 InputReleased 실행

 

캐릭터 블루프린트에 GA 부여


 

  • Start Input Abilites에 + 버튼을 눌러 element 추가
  • 추가한 element 에 추가할 GA에 맞는 키값(Enum)을 부여
  • 오른쪽에 밸류값(GA) 설정