Skip to content

Commit

Permalink
Merge pull request #124 from futurelabunseen/116-3강-캐릭터의-입력-처리
Browse files Browse the repository at this point in the history
116 3강 캐릭터의 입력 처리
  • Loading branch information
fkdl0048 authored May 22, 2024
2 parents 245669f + 30ebc89 commit ba920a0
Show file tree
Hide file tree
Showing 35 changed files with 379 additions and 49 deletions.
76 changes: 76 additions & 0 deletions Document/StudyNote/UnrealLecture/Part4/Lecture3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# 3강: 캐릭터의 입력 처리

- 강의 목표
- 플레이어 캐릭터의 게임플레이 어빌리티 시스템 설정 방법의 학습
- 플레이어 캐릭터의 입력에 따른 게임플레이 어빌리티 발동의 구현
- 어빌리티 태스크의 활용 방법의 학습
- 게임플레이 어빌리티 시스템의 디버깅 방법의 학습

*어빌리티 태스크*

## 플레이어 캐릭터의 ASC 설정

- 일반적으로 플레이어에 ASC를 설정할 때, Owner를 PlayerState로 설정하고, Avatar를 Character로 설정한다.

### 게임플레이 어빌리티 스펙

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

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

- 게임 어빌리티 스펙에는 입력 값을 설정하는 필드 InputID가 제공된다.
- ASC에 등록된 스펙을 검사해 입력에 매핑된 GA를 찾을 수 있음: FindAbilitySpecFromInputID
- 사용자 입력이 들어오면 ASC에서 입력에 관련된 GA를 검색함
- 해당 GA를 발견하면, 현재 발동 중인지를 판별
- GA가 발동 중이면 입력이 왔다는 신호를 전달: AbilitySpecInputPressed
- GA가 발동하지 않았으면 새롭게 발동시킴: TryActivateAblity
- 입력이 떨어지면 동일하게 처리
- GA에게 입력이 떨어졌다는 신호를 전달: AbilitySpecInputReleased

### 게임플레이 어빌리티의 인스턴싱 옵션

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

### 어빌리티 태스크의 활용

- 어빌리티 태스크를 줄여 AT라고 함
- 게임플레이 어빌리티의 실행은 한 프레임에서 일어나고
- 게임플레이 어빌리티가 시작되면 EndAbility함수가 호출되기 전에는 끝나지 않음
- 애니메이션 재생 같이 시간이 소요되고 상태를 관리해야 하는 어빌리티의 구현 방법
- 비동기적으로 작업이 수행하고 끝나면 결과를 통보받는 형태로 구현
- 이를 위해 GAS는 어빌리티 태스크를 제공함
- 어빌리티 태스크의 활용 패턴
- 어빌리티 태스크에 작업이 끝나면 보르드캐스팅되는 종료 델리게이트를 선언
- GA는 TA를 생성한 후 바로 종료 델리게이트를 구독함
- GA의 구독 설정이 완료되면 AT를 구동: AT의 ReadyForActivation 함수 호출
- AT의 작업이 끝나면 델리게이트를 구독한 GA의 콜백 함수가 호출됨
- GA의 콜백함수가 호출되면 GA의 EndAbility 함수를 호출해 GA종료
- GA는 필요에 따라 다수의 AT를 사용해 복잡한 액션 로직을 설계할 수 있다

### GA의 블루프린트 상속 및 게임플레이 태그 설정

- 꼭 필요한 상황이 아니라면 GA와 TA는 가급적 자기 역할만 충실하게 구현하는 것이 좋다.
- 게임플레이 태그를 C++에서 설정하는 경우 기획 변경때마다 소스코드 컴파일을 수행해야 함
- 게임플레이 태그 설정은 블루프린트에서 설정하는 것이 의존성 분리에 도움이 된다.
- 게임플레이 태그 설정 기획
- 점프 GA의 ActivationOwendTags에 Character.IsJumpimng 게임플레이 태그 설정과 같이 사용

## 정리

- 플레이어 캐릭터의 GAS 설정 방법
- 오너액터와 아바타액터의 개념의 이해
- 플레이어 스테이트에서 ASC를 설정하는 이유
- 게임플레이 어빌리티 스펙의 이해
- GAS에 관련된 입력을 범용적으로 처리하는 방식의 학습
- GA의 다양한 인스턴싱 옵션의 확인
- GA에서 다양한 인스턴싱 옵선의 확인
- GAS의 디버깅 방법
2 changes: 1 addition & 1 deletion Document/StudyNote/UnrealLecture/Part4/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

## Section1: 게임플레이 어빌리티 시스템 캐릭터 제작 기초

- 3강: 캐릭터의 입력 처리
- [3강: 캐릭터의 입력 처리](./Lecture3.md)
- 4강: 캐릭터 콤보 공격의 구현
- 5강: 공격 판정 시스템의 구현

Expand Down
6 changes: 3 additions & 3 deletions UghProject/Config/DefaultEngine.ini
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@


[/Script/EngineSettings.GameMapsSettings]
GameDefaultMap=/Game/Ugh/Maps/GasSample.GasSample
EditorStartupMap=/Game/Ugh/Maps/GasSample.GasSample
GlobalDefaultGameMode=/Game/Ugh/Blueprints/Game/BP_UGGameMode.BP_UGGameMode_C
GameDefaultMap=/Game/Ugh/Maps/PrototypeMap.PrototypeMap
EditorStartupMap=/Game/Ugh/Maps/PrototypeMap.PrototypeMap
GlobalDefaultGameMode=/Script/UghProject.UGGameMode
GameInstanceClass=/Script/UghProject.UGGameInstance

[/Script/WindowsTargetPlatform.WindowsTargetSettings]
Expand Down
2 changes: 2 additions & 0 deletions UghProject/Config/DefaultGameplayTags.ini
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ NumBitsForContainerSize=6
NetIndexFirstBitSegment=16
+GameplayTagList=(Tag="Actor.Action.Rotate",DevComment="")
+GameplayTagList=(Tag="Actor.State.IsRotating",DevComment="")
+GameplayTagList=(Tag="Character.State.IsAttacking",DevComment="")
+GameplayTagList=(Tag="Character.State.IsJumping",DevComment="")
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file modified UghProject/Content/Ugh/Blueprints/Character/BP_Cyber.uasset
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file modified UghProject/Content/Ugh/Blueprints/Game/BP_UGGameMode.uasset
Binary file not shown.
Binary file not shown.
Binary file modified UghProject/Content/Ugh/Characters/Cyber/SK_Cyber.uasset
Binary file not shown.
Binary file not shown.
Binary file removed UghProject/Content/Ugh/Input/IMC_Default.uasset
Binary file not shown.
Binary file not shown.
Binary file modified UghProject/Content/Ugh/Maps/GasSample.umap
Binary file not shown.
Binary file added UghProject/Content/Ugh/Maps/PrototypeMap.umap
Binary file not shown.
2 changes: 1 addition & 1 deletion UghProject/Source/GasExample/GasExample.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
#include "CoreMinimal.h"

#define LOG_CALLINFO ANSI_TO_TCHAR(__FUNCTION__)
#define ABGAS_LOG(LogCat, Verbosity, Format, ...) UE_LOG(LogCat, Verbosity, TEXT("%s %s"), LOG_CALLINFO, *FString::Printf(Format, ##__VA_ARGS__))
#define GAS_LOG(LogCat, Verbosity, Format, ...) UE_LOG(LogCat, Verbosity, TEXT("%s %s"), LOG_CALLINFO, *FString::Printf(Format, ##__VA_ARGS__))

DECLARE_LOG_CATEGORY_EXTERN(LogABGAS, Log, All);
2 changes: 1 addition & 1 deletion UghProject/Source/GasExample/Prop/MyActor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ void AMyActor::BeginPlay()

void AMyActor::TimerAction()
{
ABGAS_LOG(LogABGAS, Log, TEXT("TimerAction"));
GAS_LOG(LogABGAS, Log, TEXT("TimerAction"));

FGameplayTagContainer TargetTag(ACTOR_ROTATE);

Expand Down
24 changes: 4 additions & 20 deletions UghProject/Source/UghProject/Character/UGCharacterBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,24 +39,6 @@ AUGCharacterBase::AUGCharacterBase()
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
FollowCamera->bUsePawnControlRotation = false;

static ConstructorHelpers::FObjectFinder<UInputAction> InputActionJumpRef(TEXT("/Script/EnhancedInput.InputAction'/Game/Ugh/Input/Actions/IA_Jump.IA_Jump'"));
if (nullptr != InputActionJumpRef.Object)
{
JumpAction = InputActionJumpRef.Object;
}

static ConstructorHelpers::FObjectFinder<UInputAction> InputActionMoveRef(TEXT("/Script/EnhancedInput.InputAction'/Game/Ugh/Input/Actions/IA_Move.IA_Move'"));
if (nullptr != InputActionMoveRef.Object)
{
MoveAction = InputActionMoveRef.Object;
}

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

void AUGCharacterBase::BeginPlay()
Expand All @@ -67,7 +49,10 @@ void AUGCharacterBase::BeginPlay()
{
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
{
Subsystem->AddMappingContext(MappingContext, 0);
if (MappingContext)
{
Subsystem->AddMappingContext(MappingContext, 0);
}
}
}
}
Expand All @@ -87,7 +72,6 @@ void AUGCharacterBase::SetupPlayerInputComponent(UInputComponent* PlayerInputCom
{
UE_LOG(LogTemplateCharacter, Error, TEXT("PlayerInputComponent is not an EnhancedInputComponent"));
}

}

void AUGCharacterBase::Move(const FInputActionValue& Value)
Expand Down
5 changes: 5 additions & 0 deletions UghProject/Source/UghProject/Character/UGCharacterBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ class UGHPROJECT_API AUGCharacterBase : public ACharacter
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
TObjectPtr<class UCameraComponent> FollowCamera;

protected:
// Input
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
TObjectPtr<class UInputMappingContext> MappingContext;

Expand All @@ -32,6 +34,9 @@ class UGHPROJECT_API AUGCharacterBase : public ACharacter
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
TObjectPtr<class UInputAction> LookAction;

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

public:
AUGCharacterBase();

Expand Down
97 changes: 97 additions & 0 deletions UghProject/Source/UghProject/Character/UGCharacterPlayer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Fill out your copyright notice in the Description page of Project Settings.


#include "Character/UGCharacterPlayer.h"

#include "AbilitySystemComponent.h"
#include "EnhancedInputComponent.h"
#include "Player/UGPlayerState.h"

AUGCharacterPlayer::AUGCharacterPlayer()
{
ASC = nullptr;
}

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

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

AUGPlayerState* GASPS = GetPlayerState<AUGPlayerState>();
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 AUGCharacterPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);

SetupGASInputComponent();
}

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

void AUGCharacterPlayer::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 AUGCharacterPlayer::GASInputReleased(int32 InputId)
{
FGameplayAbilitySpec* Spec = ASC->FindAbilitySpecFromInputID(InputId);
if (Spec)
{
Spec->InputPressed = false;
if (Spec->IsActive())
{
ASC->AbilitySpecInputReleased(*Spec);
}
}
}
44 changes: 44 additions & 0 deletions UghProject/Source/UghProject/Character/UGCharacterPlayer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "AbilitySystemInterface.h"
#include "Character/UGCharacterBase.h"
#include "UGCharacterPlayer.generated.h"

/**
*
*/
UCLASS()
class UGHPROJECT_API AUGCharacterPlayer : public AUGCharacterBase, public IAbilitySystemInterface
{
GENERATED_BODY()

public:
AUGCharacterPlayer();

virtual class UAbilitySystemComponent* GetAbilitySystemComponent() const override;
virtual void PossessedBy(AController* NewController) override; // 빙의가 될 때 호출
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

FORCEINLINE class UAnimMontage* GetAttackMontage() const { return AttackMontage; }

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;

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Stat, Meta = (AllowPrivateAccess = "true"))
TObjectPtr<class UAnimMontage> AttackMontage;
};
61 changes: 61 additions & 0 deletions UghProject/Source/UghProject/GAS/UGGA_Attack.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Fill out your copyright notice in the Description page of Project Settings.


#include "GAS/UGGA_Attack.h"

#include "GasExample.h"
#include "Character/UGCharacterBase.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Abilities/Tasks/AbilityTask_PlayMontageAndWait.h"
#include "Character/UGCharacterPlayer.h"

UUGGA_Attack::UUGGA_Attack()
{
InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
}

void UUGGA_Attack::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);

AUGCharacterPlayer* ABCharacter = CastChecked<AUGCharacterPlayer>(ActorInfo->AvatarActor.Get());
ABCharacter->GetCharacterMovement()->SetMovementMode(MOVE_None);

UAbilityTask_PlayMontageAndWait* PlayAttackTask = UAbilityTask_PlayMontageAndWait::CreatePlayMontageAndWaitProxy(this, TEXT("PlayAttack"), ABCharacter->GetAttackMontage());
PlayAttackTask->OnCompleted.AddDynamic(this, &UUGGA_Attack::OnCompleteCallback);
PlayAttackTask->OnInterrupted.AddDynamic(this, &UUGGA_Attack::OnInterruptedCallback);
PlayAttackTask->ReadyForActivation();
}

void UUGGA_Attack::InputPressed(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo)
{
GAS_LOG(LogABGAS, Log, TEXT("Begin"));
}

void UUGGA_Attack::CancelAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateCancelAbility)
{
Super::CancelAbility(Handle, ActorInfo, ActivationInfo, bReplicateCancelAbility);

}

void UUGGA_Attack::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled)
{
Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);

AUGCharacterBase* ABCharacter = CastChecked<AUGCharacterBase>(ActorInfo->AvatarActor.Get());
ABCharacter->GetCharacterMovement()->SetMovementMode(MOVE_Walking);
}

void UUGGA_Attack::OnCompleteCallback()
{
bool bReplicatedEndAbility = true;
bool bWasCancelled = false;
EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicatedEndAbility, bWasCancelled);
}

void UUGGA_Attack::OnInterruptedCallback()
{
bool bReplicatedEndAbility = true;
bool bWasCancelled = true;
EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicatedEndAbility, bWasCancelled);
}
Loading

0 comments on commit ba920a0

Please sign in to comment.