입력 시스템
플레이어가 주는 입력은 Player Controller를 통해서 폰으로 전달된다.
그렇기 때문에 입력을 처리할 때는 Player Controller가 처리하게 만들 수도 있고, 폰이 처리하도록 만들 수 있는데 이는 어떤 게임을 만드느냐에 따라 갈린다.
(GTA 같은 다양한 사물에 빙의하는 게임의 경우 빙의를 당하는 폰에서 구현해 주는 것이 좋다)
향상된 입력 시스템 (EnhancedInput System)
Unreal Engine 5.1 버전부터는 기존 입력 시스템을 대체하고 향상된 입력 시스템이 도입됐다.
기존 입력 시스템의 경우 플레이어의 입력을 설정하는 과정에서 입력을 받은 후에 처리를 하다 보니, 나중에 플레이어의 입력 설정 변경에 유연하게 대처할 수가 없었다.
향상된 입력 시스템은 플레이어의 최종 입력을 게임 로직에서 진행하도록 구조를 변경했다.
그리고 사용자의 입력 처리를 4단계로 세분화하고 각 설정을 독립적인 에셋으로 대체하여 다양한 상황에 대해 유연하게 대처할 수 있도록 효과적으로 구조를 만들었다.
사용자 -> 입력 매핑 콘텍스트 -> 액션 -> 게임 로직 순으로 동작이 진행된다.
이를 크게 4가지로 분류할 수 있다.
Input Action
Input Action은 액션이 할당되는 부분이다.
입력 액션의 설정은 Value Type과 이후에 설명할 다른 Triggers나 Modifiers로 설정한다.
Value Type은 해당 입력 액션을 트리거했을 때, 입력값을 어떤 타입으로 받을지 설정하는 것이다.
예로, 점프나 공격 같은 한 번의 트리거로 활성화되는 액션들은 Digital(bool)로 한다.
걷기, 달리기 같은 액션들은 패드 사용 시 입력 강도 같은 것으로 활용할 수 있기 때문에, Axis1D(float)로 한다.
Input Mapping Context
Input Mapping Context는 Input Action과 바인딩하는 역할을 한다.
여기서 Input Action과 바인딩하여 키를 할당한다.
플레이어는 여러 개의 Input Mapping Context를 가질 수 있으며, 여기에는 각각 우선순위가 있어 같은 키를 입력해도 어떤 액션이 동작할지 정할 수 있다.
Modifier
Modifier는 입력받은 값을 변환해 주는 기능이다.
기본적으로 입력 액션을 활성화 시, 1의 값과 XYZ 순서로 힘이 가해진다.
Dead Zone
입력을 전달하는 최소, 최대 임계값을 정한다.
- 패드의 스틱 같은 경우, 누르는 힘에 따라 입력값이 소수점으로 전달되는데, 이의 최소, 최댓값을 정할 수 있다.
FOV Scaling
Negate
입력값을 반대로 변경한다. (1을 -1로 변경)
Response Curve - Exponential
Response Curve - User Defined
Scalar
Scale By Delta Time
Smooth
Swizzle Input Axis Values
입력받는 순서를 변경
- 기본적으로 입력 시 X축에 먼저 입력값이 들어가지만, YXZ로 변경 시 Y축에 들어간다.
To World Space
Trigger
트리거는 입력 액션을 어떻게 활성화할지를 설정한다.
키 입력 시, 트리거는 총 3단계의 이벤트가 순서대로 발생한다.
"Started", "Triggered", "Completed" 이벤트의 호출이 각 트리거 설정마다 다르게 호출된다.
기본 값(None)
먼저, 트리거 배열에 아무것도 넣지 않으면 기본적으로 "Down" 트리거로 설정이 된다.
- 키를 누른 순간에 "Started" 이벤트가 발생한다.
- 키를 계속 누르고 있으면, 매 틱마다 "Triggered" 이벤트가 발생한다.
- 키를 떼는 순간에 "Completed" 이벤트가 발생한다.
Chorded Action
해당 입력 액션이 트리거 되기 위해서는 다른 특정 입력 액션이 트리거 되어있어야 한다.
Hold
임계값(Threshold)을 정하고, 이를 기준으로 이벤트 발생을 조정한다.
- 키를 누른 순간에 "Started" 이벤트가 발생한다.
- Threshold가 지나지 않았으면, 키를 누르는 동안 "OnGoing" 이벤트가 매 틱마다 발생한다.
- Threshold가 지나면, 키를 누르는 동안 "Triggered" 이벤트가 매 틱마다 발생한다.
- Threshold가 지나기 전에, 키를 떼면 "Canceled" 이벤트가 발생한다.
- Threshold가 지나고, 키를 떼면 "Completed" 이벤트가 발생한다.
- "Is One Shot"이 활성화되어 있으면, Threshold가 지나면, "Triggered", "Completed" 이벤트가 발생한다.
Hold And Release
임계값(Threshold)을 정하고, 이를 기준으로 이벤트 발생을 조정한다.
- 키를 누른 순간에 "Started" 이벤트가 발생한다.
- 키를 누르는 동안 "OnGoing" 이벤트가 매 틱마다 발생한다.
- Threshold가 지나기 전에, 키를 떼면 "Canceled" 이벤트가 발생한다.
- Threshold가 지나고, 키를 떼면 "Triggered", "Completed" 이벤트가 발생한다.
Pressed
- 키를 누른 순간에 "Started", "Triggered", "Completed" 이벤트가 차례대로 발생한다.
Released
- 키를 누른 순간에 "Started" 이벤트가 발생한다.
- 키를 누르는 동안 "OnGoing" 이벤트가 매 틱마다 발생한다.
- 키를 떼면 "Triggered", "Completed" 이벤트가 발생한다.
Pulse
- 키를 누른 순간에 "Started" 이벤트가 발생한다.
- 키를 누르는 동안 "OnGoing" 이벤트가 매 틱마다 발생한다.
- "Interval" 시간마다 "Triggered" 이벤트가 발생한다.
- "Trigger limit"을 통해 "Triggered" 이벤트가 발생할 수 있는 최대 횟수를 정한다.
- "Triggered" 이벤트가 최대 횟수에 도달했을 때, "Completed" 이벤트가 발생한다.
- "Trigger limit" 값이 없거나, "Triggered" 이벤트가 최대 횟수에 도달하기 전에 키를 떼면, "Canceled" 이벤트가 발생한다.
- "Trigger on start"은 시작 시, "Triggered" 이벤트를 발생하며 시작할 지에 대한 여부이다.
Tap
- 키를 누른 순간에 "Started" 이벤트가 발생한다.
- 키를 누르는 동안 "OnGoing" 이벤트가 매 틱마다 발생한다.
- "Tap Release Time Threshold" 시간 이내에 키를 떼면, "Triggered", "Completed" 이벤트가 발생한다.
- "Tap Release Time Threshold" 시간이 지나도 키를 누르고 있으면, "Canceled" 이벤트가 발생한다.
C++로 구현하기
향상된 입력 시스템으로 3인칭 샘플 캐릭터를 구현해 보자.
1. 입력 액션을 Attack, Move, Look, Jump 4개를 만든다.
2. 입력 매핑 콘텍스트를 만들어 각각의 입력 액션들을 넣어주고, 키들을 할당한다.
3. 코드로 돌아가 UInputMappingContext와 UInputAction으로 오브젝트들을 선언한다.
4. InputActionValue.h 헤더를 추가해 주고, 각각 입력 액션에 호출될 함수들을 선언한다.
5. SetupPlayerInputComponent 함수를 사용해서 입력 액션과 호출되는 함수들을 바인딩한다.
6. BeginPlay에서 UEnhancedInputLocalPlayerSubsystem의 subsystem을 이용하여 입력 매핑 콘텍스트를 할당한다.
사전 작업
향상된 입력 시스템을 사용하기 위한 설정이다.
엔진 버전이 5.1 버전 이후라면 기본적으로 설정되어 있다.
Build.cs 파일에서 EnhancedInput 모듈 추가
using UnrealBuildTool;
public class Test : ModuleRules
{
public Test(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput" });
PrivateDependencyModuleNames.AddRange(new string[] { });
}
}
구현
입력 액션을 만든다.
입력 매핑 콘텍스트를 만들고 각각의 입력 액션 할당 및 키 지정을 한다.
키에 알맞은 Modifiers를 추가한다. (Attack과 Jump는 Modifier 없이 그냥 1)
UInputMappingContext와 UInputAction UObject들을 전방선언한다.
InputActionValue.h 헤더를 추가하고, 입력에 호출될 함수를 선언한다.
SetupPlayerInputComponent 함수는 InputSystem에서 입력 액션과 함수를 바인딩하는 역할을 한다.
#pragma once
#include "CoreMinimal.h"
#include "BasePawn.h"
#include "InputActionValue.h" // 헤더 추가
#include "PlayerTest.generated.h"
UCLASS()
class TOONTANK_API APlayerTest : public ABasePawn
{
GENERATED_BODY()
public:
APlayerTest();
protected:
virtual void BeginPlay() override;
public:
virtual void SetupPlayerInputComponent(class UInputComponent* inputComponent) override;
protected:
// 카메라
UPROPERTY(VisibleAnywhere, Category = "Camera")
TObjectPtr<class UCameraComponent> Camera;
UPROPERTY(VisibleAnywhere, Category = "Camera")
TObjectPtr<class USpringArmComponent> SpringArm;
// 입력
UPROPERTY(EditDefaultsOnly, Category = "Input")
TObjectPtr<class UInputMappingContext> DefaultMappingContext;
UPROPERTY(EditDefaultsOnly, Category = "Input")
TObjectPtr<class UInputAction> AttackAction;
UPROPERTY(EditDefaultsOnly, Category = "Input")
TObjectPtr<class UInputAction> MoveAction;
UPROPERTY(EditDefaultsOnly, Category = "Input")
TObjectPtr<class UInputAction> LookAction;
UPROPERTY(EditDefaultsOnly, Category = "Input")
TObjectPtr<class UInputAction> JumpAction;
// 마우스 감도 (회전 속도)
UPROPERTY(EditAnywhere, Category = "Input")
float RotateRate = 30.f;
// 입력 지정 함수
void Attack(const FInputActionValue& value);
void Move(const FInputActionValue& value);
void Look(const FInputActionValue& value);
};
#include "PlayerTest.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "InputMappingContext.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
APlayerTest::APlayerTest()
{
SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
SpringArm->SetupAttachment(RootComponent);
SpringArm->bUsePawnControlRotation = true;
Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
Camera->SetupAttachment(SpringArm);
}
void APlayerTest::BeginPlay()
{
Super::BeginPlay();
ASlashPlayerController* PlayerController = Cast<ASlashPlayerController>(GetController());
if (PlayerController)
{
UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer());
if (Subsystem)
{
// 우선순위를 0으로 지정해 다양한 입력들이 겹쳐도 우선순위를 두어 액션이 수행되도록 지정
Subsystem->AddMappingContext(DefaultMappingContext, 0);
}
}
}
void APlayerTest::SetupPlayerInputComponent(UInputComponent* inputComponent)
{
Super::SetupPlayerInputComponent(inputComponent);
// 향상된 입력 시스템으로 변환
// 앞서 선언한 변수들과 입력 지정 함수들과 바인딩
UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent);
if (EnhancedInputComponent)
{
EnhancedInputComponent->BindAction(AttackAction, ETriggerEvent::Triggered, this, &ASlashCharacter::Attack);
EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &ASlashCharacter::Move);
EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &ASlashCharacter::Look);
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Triggered, this, &ACharacter::Jump);
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &ACharacter::StopJumping);
}
void APlayerTest::Attack(const FInputActionValue& value)
{
UE_LOG(LogTemp, Warning, TEXT("Input Attack!"));
}
void APlayerTest::Move(const FInputActionValue& value)
{
FVector2D MovementVector = value.Get<FVector2D>();
const FRotator Rotation = GetControlRotation();
const FVector ForwardDirection = FRotationMatrix(Rotation).GetUnitAxis(EAxis::X);
const FVector RightDirection = FRotationMatrix(Rotation).GetUnitAxis(EAxis::Y);
AddMovementInput(ForwardDirection, MovementVector.X);
AddMovementInput(RightDirection, MovementVector.Y);
}
void APlayerTest::Look(const FInputActionValue& value)
{
FVector2D LookVector = value.Get<FVector2D>();
AddControllerPitchInput((LookVector.X * -1) * RotateRate * GetWorld()->GetDeltaSeconds());
AddControllerYawInput(LookVector.Y * RotateRate * GetWorld()->GetDeltaSeconds());
}
'📘Unreal Engine > 📝Unreal Engine' 카테고리의 다른 글
[Unreal Engine] 강 참조와 약 참조 (Hard and Soft Reference) (0) | 2024.08.05 |
---|---|
[Unreal Engine] 캐릭터 컨트롤 옵션 (0) | 2024.07.25 |
[Unreal Engine] 폰(Pawn)과 캐릭터(Character) (0) | 2024.07.24 |
[Unreal Engine] 머리카락(털) 추가 / UGroomComponent (0) | 2024.05.23 |
[Unreal Engine] 로그 출력 / 뷰포트 출력 (0) | 2024.05.21 |