일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- CTF
- 언리얼엔진
- ability task
- ret2libc
- gas
- AINCAA
- TSet
- DSP
- 운영체제
- frequency-domain spectrum analysis
- 게임개발
- 게임 개발
- sampling theory
- MLFQ
- pdlc
- 유니티
- MAC
- DP
- dirty cow
- Double free
- dtft
- Security
- Rr
- linear difference equation
- 메카님
- 언리얼 엔진
- Unreal Engine
- stride
- 유스케이스
- Race condition
- Today
- Total
다양한 기록
[Unreal GAS] Character Input Handling 본문
플레이어 캐릭터의 기획
- 기존 플레이어 캐릭터 상속
- 입력에 따라 정해진 게임플레이 어빌리티가 발동되도록 설정
- 점프 GA : 스페이스바를 누르면 점프 어빌리티가 발동
- 공격 GA : 마우스 왼쪽 클릭 시 공격 어빌리티가 발동
일단 블루프린트 게임모드 하나 만들고 컨트롤러 설정
변경
플레이어 캐릭터의 ASC 설정
- 분수대 액터처럼 플레이어 캐릭터에 설정하는 것도 가능
- 하지만 네트워크 멀티플레이 감안 시, 서버->클라이언트 배포되는 액터가 더 적합함
- 주기적으로 플레이어 정보를 배포하는 PlayerState 액터가 적합
- Owner를 PlayerState로 설정, Avatar로 Character로 설정하는 것이 일반적인 방법
ASC 설정은 PossessedBy에서 해주면 됨
그리고 게임 모드에서 PlayerState랑 DefaultPawn 설정
아직 뭐 바뀌진 않음
이제 입력으로 어빌리티 발동시킬 것
사실 점프 어빌리티 정도는 언리얼 엔진이 이미 만들어놨음
가져다 써보기
* 게임플레이 어빌리티 스펙
- 게임플레이 어빌리티에 대한 정보를 담고 있는 구조체
- ASC는 직접 어빌리티를 참조하지 않고 스펙 정보만 가지고 있음
- 스펙은 어빌리티의 현재 상태와 같은 다양한 정보를 가지고 있음
- ASC로부터 어빌리티를 다루고자 할 때엔 스펙의 Handle을 사용해 컨트롤
- 핸들값은 전역으로 설정되어 있으며 스펙 생성 시 자동으로 1씩 증가, 기본값 -1
- 어빌리티 정보: 스펙 / 어빌리티 인스턴스에 대한 레퍼런스 : 스펙 핸들
* 어빌리티 시스템 컴포넌트의 입력 처리
- 어빌리티 스펙에는 입력 값을 설정하는 필드 InpitID가 제공
- ASC에 등록된 스펙을 검사해 입력에 매핑된 GA를 찾을 수 있음(FindAbilitySpecFromInputID)
- 사용자 입력이 들어오면 ASC에서 입력에 관련된 GA를 검색
- 해당 GA를 발견 시, 현재 발동 중인지를 판별
- GA가 발동 중이면 입력이 왔다는 신호 전달(AbilitySpecInputPressed)
- GA가 발동하지 않았으면 새롭게 발동(TryActivateAbility)
- 입력이 떨어지면 동일하게 처리
- GA에게 입력이 떨어졌다는 신호를 전달(AbilitySpecInputReleased)
`
// Fill out your copyright notice in the Description page of Project Settings.
#include "Character/ABGASCharacterPlayer.h"
#include "AbilitySystemComponent.h"
#include "Player/ABGASPlayerState.h"
#include "EnhancedInputComponent.h"
AABGASCharacterPlayer::AABGASCharacterPlayer()
{
ASC = nullptr;
}
UAbilitySystemComponent* AABGASCharacterPlayer::GetAbilitySystemComponent() const
{
return ASC;
}
void AABGASCharacterPlayer::PossessedBy(AController* NewController)
{
Super::PossessedBy(NewController);
AABGASPlayerState* GASPS = GetPlayerState<AABGASPlayerState>();
if (GASPS)
{
ASC = GASPS->GetAbilitySystemComponent();
ASC->InitAbilityActorInfo(GASPS, this);
int32 InputId = 0;
for (const auto& StartAbility : StartAbilities)
{
FGameplayAbilitySpec StartSpec(StartAbility);
StartSpec.InputID = InputId++;
ASC->GiveAbility(StartSpec);
SetupGASInputComponent();
}
}
}
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);
}
}
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);
}
}
}
SetupGASInputComponent 두 번 호출하는 건 서버에서만 PossessedBy 호출되어서 그냥 안전하게 두 번 해준 거
코드 상 처음 어빌리티 부여할 때 0번부터 부여하는데, 준 건 점프밖에 없고 당연히 0번으로 설정됨
활성화 및 비활성화 시 Spec을 찾기 위해 InputID 사용해줌
* EnhancedInput은 바인딩할 때 인자 같이 넘겨줄 수 있음
점프 어빌리티 보면 InstancingPolicy라는게 존재
게임플레이 어빌리티의 인스턴싱 옵션
- 상황에 따라 다양한 인스턴스 정책 지정 가능
- NonInstanced : 인스턴싱 없이 CDO에서 일괄 처리
- InstancedPerActor : 액터마다 하나의 어빌리티를 만들어 처리(Primary Instance)
- InstancedPerExecution : 발동 시 인스턴스 생산
... 네트워크 리플리케이션 고려 시 InstancedPerActor가 무난
* 어빌리티 태스크
- 줄여서 AT
- GA의 실행(Activation)은 한 프레임에서 이루어짐
- GA가 시작되면 EndAbility 함수가 호출되기까지는 끝나지 않음
- 애니메이션 재생 같이 시간이 소요되고 상태를 관리해야 하는 어빌리티?
- 비동기적으로 작업을 수행하고 끝나면 결과를 통보받기
- 이를 위해 GAS는 어빌리티 태스크 제공
- AT의 활용 패턴
1. 어빌리티 태스크에 작업이 끝나면 브로드캐스팅되는 종료 델리게이트를 선언
2. GA는 AT를 생성한 후 바로 종료 델리게이트를 구독
3. GA의 구독 설정이 완료되면 AT를 구동 : AT의 ReadyForActivation 함수 호출
4. AT의 작업이 끝나면 델리게이트를 구독한 GA의 콜백 함수가 호출됨
5. GA의 콜백함수가 호출되면 GA의 EndAbility 함수를 호출해 GA를 종료
- GA는 필요에 따라 다수의 AT를 사용해 복잡한 액션 로직 설계 가능
// Fill out your copyright notice in the Description page of Project Settings.
#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();
virtual void CancelAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateCancelAbility) override;
virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override;
virtual void EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled) override;
virtual void InputPressed(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo) override;
protected:
UFUNCTION()
void OnCompleteCallback();
UFUNCTION()
void OnInterruptedCallback();
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "GA/ABGA_Attack.h"
#include "Character/ABCharacterBase.h"
#include "Abilities/Tasks/AbilityTask_PlayMontageAndWait.h"
#include "ArenaBattleGAS.h"
#include "GameFramework/CharacterMovementComponent.h"
UABGA_Attack::UABGA_Attack()
{
InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
}
void UABGA_Attack::CancelAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateCancelAbility)
{
Super::CancelAbility(Handle, ActorInfo, ActivationInfo, bReplicateCancelAbility);
}
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::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::InputPressed(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo)
{
ABGAS_LOG(LogABGAS, Log, TEXT("Begin"));
}
void UABGA_Attack::OnCompleteCallback()
{
bool bReplicatedEndAbility = true;
bool bWasCancelled = false;
EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicatedEndAbility, bWasCancelled);
}
void UABGA_Attack::OnInterruptedCallback()
{
bool bReplicatedEndAbility = true;
bool bWasCancelled = false;
EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicatedEndAbility, bWasCancelled);
}
UAbilityTask_PlayMontageAndWait를 사용 (엔진에 이미 있는 거임)
끝날때 되면 EndAbility 불림
플레이어에선 바인드
void AABGASCharacterPlayer::PossessedBy(AController* NewController)
{
Super::PossessedBy(NewController);
AABGASPlayerState* GASPS = GetPlayerState<AABGASPlayerState>();
if (GASPS)
{
ASC = GASPS->GetAbilitySystemComponent();
ASC->InitAbilityActorInfo(GASPS, this);
int32 InputId = 0;
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"));
}
}
모든 어빌리티가 인풋을 요구하는게 아니니 분리해주는게 좋음
showdebug abilitysystem은 콘솔 커맨드인데, 어빌리티 시스템 상태를 볼 수 있음
태그 설정은 블루프린트에서 해줌
중앙 상단에 OwnedTags 보임
GA의 블루프린트 상속 및 게임플레이 태그 설정
- 꼭 필요한 상황이 아니라면 GA와 AT는 가급적 자기 역할만 충실하게 구현하는 것이 좋음
- 게임플레이 태그를 C++에서 설정하는 경우 기획 변경 때마다 소스코드 컴파일해야됨
- 게임플레이 태그 설정은 블루프린트에서 설정하는 것이 의존성 분리에 도움이 됨
'언리얼 엔진 > Unreal Ability System' 카테고리의 다른 글
[Unreal GAS] Implementing Combo Action (0) | 2025.01.12 |
---|---|
[Unreal GAS] 개요 (0) | 2025.01.11 |