[내일배움캠프/UE] 캐릭터 동작 구현

김세희·2025년 7월 1일

✍️Today I Learned

  1. 캐릭터 클래스에 액션 바인딩 추가하기
  2. Move 구현
  3. Look 구현
  4. Jump 구현
  5. Sprint 구현

캐릭터 클래스에 액션 바인딩 추가하기

📌입력 바인딩 함수를 Enhanced Input System에서 인식하려면 리플렉션 시스템에 등록해야 한다.

// 헤더
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "CH3_Character.generated.h"

// 미리 선언
// 여기서 사용하지 않는데 헤더를 포함시키면 볼륨이 쓸데없이 커지니까
// 선언만해서 있다고 알려만 주고 헤더는 cpp에서 선언
class USpringArmComponent;
class UCameraComponent;
struct FInputActionValue;

UCLASS()
class CH3_PROJECT_API ACH3_Character : public ACharacter
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	ACH3_Character();
	// VisibleAnywhere, BlueprintReadOnly: 객체 자체를 변경하는 건 불가능하지만 내부 속성 정도는 수정 가능
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
	USpringArmComponent* SpringArmComp;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
	UCameraComponent* CameraComp;

protected:
	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

	// 입력 바인딩 함수는 리플렉션 시스템과 연동이 되어야함
	// 등록 되어있지 않으면 Enhanced Input System에서 인식을 못함
	UFUNCTION()
	// FInputActionValue: IA의 Value Type
	void Move(const FInputActionValue& Value);
	UFUNCTION()
	void Look(const FInputActionValue& Value);
	// 입력이 on/off인 동작들은 시작과 끝을 나눠주는게 좋음
	UFUNCTION()
	void StartJump(const FInputActionValue& Value);
	UFUNCTION()
	void StopJump(const FInputActionValue& Value);
	UFUNCTION()
	void StartSprint(const FInputActionValue& Value);
	UFUNCTION()
	void StopSprint(const FInputActionValue& Value);
    
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Speed")
	float SprintSpeedMultiplier;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MouseSensitivity")
	float MouseSensitivity;

private:
	float NormalSpeed;
	float SprintSpeed;

	int JumpCount = 0;

};
// cpp
// Called to bind functionality to input
void ACH3_Character::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	if (UEnhancedInputComponent* EnhancedInput = 
    		Cast<UEnhancedInputComponent>(PlayerInputComponent)) 
	{
		// 현재 캐릭터를 조작하는 컨트롤러를 가져와 ACH3_PlayerController로 캐스팅
		if (ACH3_PlayerController* PlayerController =
        		Cast<ACH3_PlayerController>(GetController())) 
		{
			if (PlayerController->MoveAction) 
			{
				// 이벤트랑 함수 연결
				EnhancedInput->BindAction(
					PlayerController->MoveAction,
					ETriggerEvent::Triggered,
					this,
					&ACH3_Character::Move
				);
			}
			if (PlayerController->LookAction)
			{
				// 이벤트랑 함수 연결
				EnhancedInput->BindAction(
					PlayerController->LookAction,
					ETriggerEvent::Triggered,
					this,
					&ACH3_Character::Look
				);
			}
			if (PlayerController->JumpAction)
			{
				// 이벤트랑 함수 연결
				EnhancedInput->BindAction(
					PlayerController->JumpAction,
					ETriggerEvent::Triggered,
					this,
					&ACH3_Character::StartJump
				);
				EnhancedInput->BindAction(
					PlayerController->JumpAction,
					ETriggerEvent::Completed,
					this,
					&ACH3_Character::StopJump
				);
			}
			if (PlayerController->SprintAction)
			{
				// 이벤트랑 함수 연결
				EnhancedInput->BindAction(
					PlayerController->SprintAction,
					ETriggerEvent::Triggered,
					this,
					&ACH3_Character::StartSprint
				);
				EnhancedInput->BindAction(
					PlayerController->SprintAction,
					ETriggerEvent::Completed,
					this,
					&ACH3_Character::StopSprint
				);
			}

		}
	}
}

Move

void ACH3_Character::Move(const FInputActionValue& Value) 
{
	// 컨트롤러 유효한지 확인
	// GetActorForwardVector 같은 값은 컨트롤러 없으면 못가져옴 
	if (!Controller) return;

	const FVector2D MoveInput = Value.Get<FVector2D>();

	if (!FMath::IsNearlyZero(MoveInput.X)) 
	{
		AddMovementInput(GetActorForwardVector(), MoveInput.X);
	}
	if (!FMath::IsNearlyZero(MoveInput.Y))
	{
		AddMovementInput(GetActorRightVector(), MoveInput.Y);
	}
}

Look

void ACH3_Character::Look(const FInputActionValue& Value)
{
	FVector2D LookInput = Value.Get<FVector2D>();

	// 좌우 회전
	// 값이 양수면 오른쪽으로 회전
	AddControllerYawInput(LookInput.X * MouseSensitivity);
	// 상하 회전
	// 값이 양수면 아래로 회전
	// IMC에서 이미 값을 반전시켜서 여기서는 신경 X
	AddControllerPitchInput(LookInput.Y * MouseSensitivity);
}

Jump

void ACH3_Character::StartJump(const FInputActionValue& Value) 
{
	// Jump 함수에 컨트롤러 체크가 이미 있고
	// 컨트롤러가 필요한 함수도 없어서 체크 안해도 됨
	//if (!Controller) return;
	
	if (Value.Get<bool>()) 
	{
		Jump();
	}
}
void ACH3_Character::StopJump(const FInputActionValue& Value)
{
	if (!Value.Get<bool>()) 
	{
		StopJumping();
		JumpCount++;
		UE_LOG(LogTemp, Display, TEXT("Jump Count: %d"), JumpCount);
	}
}

Sprint

void ACH3_Character::StartSprint(const FInputActionValue& Value) 
{
	if (GetCharacterMovement()) 
	{
		SprintSpeed = NormalSpeed * SprintSpeedMultiplier;
		GetCharacterMovement()->MaxWalkSpeed = SprintSpeed;
	}
}
void ACH3_Character::StopSprint(const FInputActionValue& Value)
{
	if (GetCharacterMovement())
	{
		UE_LOG(LogTemp, Warning, TEXT("SprintSpeed: %0.1f"), SprintSpeed);

		GetCharacterMovement()->MaxWalkSpeed = NormalSpeed;
	}
}

출처: 스파르타코딩 내일배움캠프

0개의 댓글