[Unreal Engine] 캐릭터와 입력 시스템

김민정·2025년 5월 7일

UnrealEngine

목록 보기
8/10
post-thumbnail

[이득우의 언리얼 프로그래밍 Part2] 강의와 Unreal Engine 문서를 참고하여 공부한 내용입니다.

참고 자료

언리얼 엔진의 향상된 입력

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/enhanced-input-in-unreal-engine


향상된 입력 시스템

  • 언리얼에서 플레이어의 입력은 컨트롤러를 통해서 폰으로 전달된다.
  • 플레이어의 최종 입력을 게임 로직에서 진행하도록 향상된 입력 시스템으로 변경했다.
  • 동작 구성 : [사용자] 사용자 입력 -> [입력 매핑 컨텍스트] 입력 연결 -> [액션] 입력값 변조 & 이벤트 활성화 -> [게임 로직] 함수 호출

사전 설정

PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "EnhancedInput" });
  • 블루프린트에서 사용하기 위해 에디터 내에서 플러그인으로 Enhanced Input을 추가한다.
  • C++에서 사용하기 위해 해당 시스템을 사용할 모듈의 Bulid.cs 파일에 EnhancedInput을 추가한다.

캐릭터 기본 움직임

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

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

  • 사용자의 입력을 실제 플레이어 캐릭터의 움직임에 맵핑하기 위한 입력 맵핑 컨텍스트, 입력 값을 변조할 수 있고 이벤트를 활성화시키는 입력 액션을 멤버 변수로 선언한다.
  • 해당 멤버면수는 블루프린트에서 수정할 수 있도록 EditAnywhere 지정자를 활용해 노출시킨다.

입력 액션 (Input Action)

  • 콘텐츠 브라우저에서 우클릭한 후, Input Action을 만들 수 있다.
  • Input Action은 특정 키가 연결되어 있는 것이 아닌, 해당 입력에 대한 정보로 구성되어 있다.

  • Value Type
    : 사용자의 입력 값을 나타낼 타입 정보이다.

  • Modifiers
    : 사용자의 입력 값을 변환해주는 장치이다. 입력 값을 반전시키거나, 축을 교체하는 등 다양한 조정이 가능하다.

입력 맵핑 컨텍스트 (Input Mapping Context)

  • 콘텐츠 브라우저에서 우클릭한 후, Input Mapping Context를 만들 수 있다.
  • Input Action과 실제 키를 바인딩하고, 입력 값 조정 또한 가능하다.

  • 예시로 Swizzle Input Axis Values 옵션은 입력 값을 받을 때 축 순서를 지정할 수 있다.

기본 움직임을 위한 입력 값 조정

  • 언리얼 엔진은 왼손 좌표계를 사용하고, Z up이다.
  • ACharacter를 상속받은 클래스의 기본 화살표 컴포넌트를 확인하면, 캐릭터가 바라보는 방향(실제 정면 방향)은 X축인걸 확인할 수 있다.

  • WASD 키 입력을 받았을 때 W와 S키는 Y축, A와 D키는 X축에 영향을 준다.
  • 하지만 우리는 W와 D키를 사용해서 언리얼 엔진에서 앞/뒤로 이동하고 A와 D키를 사용해서 좌/우로 이동해야 한다.
    => 즉 X축을 Y축으로, Y축을 X축으로 변환시켜야 한다.
  • 기본 움직임 입력을 담당하는 Input Action에서 Swizzle Input Axis Values 옵션을 활성화하고, YXZ 순서로 축을 설정한다.

  • 공식 문서의 방향 입력을 참고하여 입력 맵핑 컨텍스트에 적용한다.
void Move(const FInputActionValue& Value);
  • 입력 액션에 따라 실제로 로직(기본 움직임, 대시 등)을 수행할 함수를 선언한다.
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
void AFQPlayerBase::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent);

	EnhancedInputComponent->BindAction(mMoveAction, ETriggerEvent::Triggered, this, &AFQPlayerBase::Move);
}
  • SetupPlayerInputComponent 가상 함수를 오버라이드하여 입력 액션과 그에 따라 호출될 함수를 바인딩한다.
void AFQPlayerBase::Move(const FInputActionValue& Value)
{
	FVector2D MovementVector = Value.Get<FVector2D>();
	UE_LOG(LogTemp, Warning, TEXT("MovementVector : %f, %f"), MovementVector.X, MovementVector.Y);
	
	const FRotator Rotation = Controller->GetControlRotation();
	const FRotator YawRotation(0, Rotation.Yaw, 0);

	const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
	const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);

	AddMovementInput(ForwardDirection, MovementVector.X);
	AddMovementInput(RightDirection, MovementVector.Y);
}
  • Z축의 회전 각도를 사용해서 정면 방향과 오른쪽 방향을 구하고, 축에 따른 입력 값이 해당 방향으로 나아갈 때 영향을 받을 수 있도록 설정한다.

캐릭터 대시

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

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, Meta = (AllowPrivateAccess = "true"))
float mDashSpeed;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, Meta = (AllowPrivateAccess = "true"))
float mDashDuration;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, Meta = (AllowPrivateAccess = "true"))
float mDashCoolTime;

FVector mDashDirection;

// 대시할 수 있는 상태인지 확인하는 플래그
uint8 mbCanDash : 1;
// 대시를 하는 중인지 확인하는 플래그
uint8 mbIsDashing : 1;

FTimerHandle mDashTimer;
FTimerHandle mDashCoolTimer;
  • 대시 입력 액션을 할당할 수 있도록 UPROPERTY로 Input Action을 추가한다.
  • 대시를 할 때 사용할 속성(속도, 대시 시간, 쿨타임)을 할당할 수 있도록 UPROPERTY로 추가한다.
void Dash();
void StartDash();
void EndDash();
void ResetDash();
void AFQPlayerBase::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent);

	EnhancedInputComponent->BindAction(mMoveAction, ETriggerEvent::Triggered, this, &AFQPlayerBase::Move);
	EnhancedInputComponent->BindAction(mDashAction, ETriggerEvent::Started, this, &AFQPlayerBase::Dash);
}
  • 대시 로직을 수행할 함수들을 선언하고, 대시 키를 입력받을 순간 1번만 호출될 수 있도록 바인딩한다.
void AFQPlayerBase::Dash()
{
	mbCanDash = true;

	if (mbCanDash)
	{
		StartDash();
	}
}
  • 대시 키를 입력받았을 때 호출되는 함수로, 대시를 할 수 있는 상태로 플래그를 전환시킨다.
void AFQPlayerBase::StartDash()
{
	if (!mbIsDashing)
	{
		mbCanDash = false;
		mbIsDashing = true;

		mDashDirection = GetLastMovementInputVector().GetSafeNormal();
		if (mDashDirection.IsZero())
		{
			mDashDirection = GetActorForwardVector(); 
		}

		GetCharacterMovement()->MaxWalkSpeed = mDashSpeed;
		GetCharacterMovement()->MaxAcceleration = mDashSpeed * 2;

		GetWorld()->GetTimerManager().SetTimer(mDashTimer, this, &AFQPlayerBase::EndDash, mDashDuration, false);
		GetWorld()->GetTimerManager().SetTimer(mDashCoolTimer, this, &AFQPlayerBase::ResetDash, mDashCoolTime, false);
	}
}
  • 대시하는 도중에 대시 키 입력을 받을 수 없도록 분기 처리해주었다.
  • 현재 플레이어가 바라보는 정면 방향을 구하고, 이동 속도와 가속도를 변경한다.
  • FTimerHandle을 사용해서 대시 시간이 끝났을 때 실행할 함수와 쿨타임이 끝났을 때 실행될 함수를 바인딩해준다.
void AFQPlayerBase::Tick(float DeltaSeconds)
{
	Super::Tick(DeltaSeconds);

	if (mbIsDashing)
	{
		AddMovementInput(mDashDirection, mDashSpeed * DeltaSeconds);
	}
}
  • Tick 함수 내에서 AddMovementInput을 사용하여 대시 속도만큼 움직임을 더해준다.
void AFQPlayerBase::EndDash()
{
	mbIsDashing = false;

	GetCharacterMovement()->MaxWalkSpeed = 200.0f;
	GetCharacterMovement()->MaxAcceleration = 2048.0f;
}

void AFQPlayerBase::ResetDash()
{
	mbCanDash = true;
}
  • 대시가 끝났다면 대시 중을 나타내는 플래그를 전환해주고, 걷는 속도와 가속도를 기본 속도로 변경한다.
  • 대시 쿨타임이 끝났다면 대시 가능 상태를 나타내는 플래그를 전환해준다.
void AFQPlayerBase::Move(const FInputActionValue& Value)
{
	if (mbIsDashing)
	{
		return;
	}

	FVector2D MovementVector = Value.Get<FVector2D>();
	
	const FRotator Rotation = Controller->GetControlRotation();
	const FRotator YawRotation(0, Rotation.Yaw, 0);

	const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
	const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);

	AddMovementInput(ForwardDirection, MovementVector.X);
	AddMovementInput(RightDirection, MovementVector.Y);
}
  • 이동을 하는 중 대시 키 입력을 받으면 대시 기능이 우선적으로 실행되도록 분기 처리하였다.

Dash 안에 StartDash 함수를 굳이 만든 이유?
추후 멀티플레이 환경으로 로직을 수정하기 위해서이다.
: 위치를 변경했을 때 서버와 클라이언트가 동기화되기 위해서는 서버에서 해당 로직을 수행해야 하기 때문에 대시 가능 상태인지 확인하는 플래그가 필요하다고 생각했다.

결과물

profile
📝 공부노트

0개의 댓글