Unreal 개발 본 캠프 32일차

HappyCircle·2026년 1월 13일

Unreal 개발

목록 보기
49/163

오늘 학습 진행 내용

C++와 Unreal Engine으로 3D 게임 개발

캐릭터 동작 구현과 입력 처리

캐릭터 클래스에서 입력 액션 연결
1. PlayerControllerSpartaInputMappingContext(IMC)를 활성화합니다.
2. 해당 IMC에는 IA_Move, IA_Jump 등의 UInputAction들이 키보드/마우스와 맵핑되어 있습니다.
3. 캐릭터가 SetupPlayerInputComponent() 함수를 통해 “각 액션이 발생했을 때 어떤 함수를 실행할지”를 등록해둡니다.
4. 이렇게 등록한 함수들이, 실제로 움직이거나 점프하는 등 캐릭터 동작을 수행
캐릭터 클래스에 각각 IMC에 맵핑해놓은 Input Action에 맞는 행동 함수 구현
InputAction 호출 시 맞는 행동 함수 호출 이런 느낌으로 구현

void ASpartaCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    Super::SetupPlayerInputComponent(PlayerInputComponent);

    // Enhanced InputComponent로 캐스팅
    if (UEnhancedInputComponent* EnhancedInput = Cast<UEnhancedInputComponent>(PlayerInputComponent))
    {
        // IA를 가져오기 위해 현재 소유 중인 Controller를 ASpartaPlayerController로 캐스팅
        if (ASpartaPlayerController* PlayerController = Cast<ASpartaPlayerController>(GetController()))
        {
            if (PlayerController->MoveAction)
            {
                // IA_Move 액션 키를 "키를 누르고 있는 동안" Move() 호출
                EnhancedInput->BindAction(
                    PlayerController->MoveAction,
                    ETriggerEvent::Triggered,
                    this,
                    &ASpartaCharacter::Move
                );
            }

실제 InputAction 받았을 때 실제 동작하는 부분들 행동 함수에 반영
이런 느낌으로 구현

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

    if (!FMath::IsNearlyZero(MoveInput.X))
    {
		    // 캐릭터가 바라보는 방향(정면)으로 X축 이동
        AddMovementInput(GetActorForwardVector(), MoveInput.X);
    }

    if (!FMath::IsNearlyZero(MoveInput.Y))
    {
			   // 캐릭터의 오른쪽 방향으로 Y축 이동
        AddMovementInput(GetActorRightVector(), MoveInput.Y);
    }

Charater.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

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

class USpringArmComponent; // 스프링 암 관련 클래스 헤더
class UCameraComponent; // 카메라 관련 클래스 전방 선언

//Enhanced Input에서 액션 값을 받을 때 사용하는 구조체
struct FInputActionValue;

UCLASS()
class CH3_CLASS2_3_4_API ACH2Character1 : public ACharacter
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	ACH2Character1();

protected:
	// 스프링 암 컴포넌트
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
	USpringArmComponent* SpringArmComp;
	// 카메라 컴포넌트
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
	UCameraComponent* CameraComp;
	//VisibleAnywhere, BlueprintReadOnly: 블루프린트에서 보기만 가능하고, C++ 코드 쪽에서만 수정 가능하게 하는 속성

	//이동 속도 관련 프로퍼티들
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement")
	float NormalSpeed; //기본 걷기 속도
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement")
	float SprintSpeedMultiplier; // "기본 속도" 대비 몇 배로 빠르게 달릴지 결정
	UPROPERTY(EditAnywhere,BlueprintReadWrite , Category = "Movement")
	float SprintSpeed; //실제 스프린트 속도
	//점프 카운트 횟수 프로퍼티
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Movement")
	int JumpCount; //점프 카운트 회수

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement")
	float MouseSensitivity;

	//입력 바인딩을 처리할 함수
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

	//IA_Move와 IA_Jump 등을 처리할 함수 원형
	//Enhanced Input에서 액션 값은 FInputActionValue로 전달
	UFUNCTION()
	void Move(const FInputActionValue& value);
	UFUNCTION()
	void StartJump(const FInputActionValue& value);
	UFUNCTION()
	void StopJump(const FInputActionValue& value);
	UFUNCTION()
	void Look(const FInputActionValue& value);
	UFUNCTION()
	void StartSprint(const FInputActionValue& value);
	UFUNCTION()
	void StopSprint(const FInputActionValue& value);

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;




};

Charater.cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "CH2Character1.h"
//카메라, 스프링 암 실제 구현이 필요한 경우라서 include
#include "Camera/CameraComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "EnhancedInputComponent.h"
#include "CH2PlayerController.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Engine/Engine.h"
// Sets default values
ACH2Character1::ACH2Character1()
{
 	// Set this character to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = false;

	//스프링 암 생성
	SpringArmComp = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
	//스프링 암을 루트 컴포넌트(캠슐 컴포넌트)에 부착
	SpringArmComp->SetupAttachment(RootComponent);
	//캐릭터와 카메라 사이의 거리 기본값 300으로 설정
	SpringArmComp->TargetArmLength = 700.0f;
	//컨트롤러 회전에 따랄 스프링 암도 회전하도록 설정
	SpringArmComp->bUsePawnControlRotation = true;

	SpringArmComp->SocketOffset = FVector(20.0f);
	//카메라 컴포넌트 생성
	CameraComp = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
	//스프링 암의 소켓 위치에 카메라를 부착
	CameraComp->SetupAttachment(SpringArmComp, USpringArmComponent::SocketName);
	//카메라는 스프링 암의 회전에 따르므로 PawnControlRotation은 꺼둠
	CameraComp->bUsePawnControlRotation = false;

	NormalSpeed = 600.0f;
	SprintSpeedMultiplier = 1.5f;
	SprintSpeed = NormalSpeed * SprintSpeedMultiplier;

	GetCharacterMovement()->MaxWalkSpeed = NormalSpeed;

	JumpCount = 0;

	MouseSensitivity = 1;
}

// Called when the game starts or when spawned
void ACH2Character1::BeginPlay()
{
	Super::BeginPlay();
	
}

// Called every frame
void ACH2Character1::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

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

	//Enhanced InputComponent로 캐스팅
	if (UEnhancedInputComponent* EnhancedInput = Cast<UEnhancedInputComponent>(PlayerInputComponent))
	{
		//IA를 가져오기 위해 현재 소유 중인 Controller를 CH2PlayerController로 캐스팅
		if (ACH2PlayerController* PlayerController = Cast<ACH2PlayerController>(GetController()))
		{
			if (PlayerController->MoveAction)
			{
				//IA_Move 액션 키를 "키를 누르고 있는 동안" Move() 호출
				EnhancedInput->BindAction(
					PlayerController->MoveAction,
					ETriggerEvent::Triggered,
					this,
					&ACH2Character1::Move
				);
			}
			if (PlayerController->JumpAction)
			{
				//IA_Jump 액션 키를 "키를 누르고 있는 동안" Jump() 호출
				EnhancedInput->BindAction(
					PlayerController->JumpAction,
					ETriggerEvent::Triggered,
					this,
					&ACH2Character1::StartJump
				);

				//IA_Jump 액션 키를 "손을 뗀 순간" StopJump() 호출
				EnhancedInput->BindAction(
					PlayerController->JumpAction,
					ETriggerEvent::Completed,
					this,
					&ACH2Character1::StopJump
				);
			}


			if (PlayerController->LookAction)
			{
				//IA_Look 액션 마우스가 "움직일 때" 호출
				EnhancedInput->BindAction(
					PlayerController->LookAction,
					ETriggerEvent::Triggered,
					this,
					&ACH2Character1::Look
				);
			}

			if (PlayerController->SprintAction)
			{
				//IA_Sprint 액션 "키를 누르고 있는 동안" StartSprint() 호출
				EnhancedInput->BindAction(
					PlayerController->SprintAction,
					ETriggerEvent::Triggered,
					this,
					&ACH2Character1::StartSprint
				);

				//IA_Sprint 액션 "손에서 뗀 순간" StopSprint() 호출
				EnhancedInput->BindAction(
					PlayerController->SprintAction,
					ETriggerEvent::Completed,
					this,
					&ACH2Character1::StopSprint
				);
			}
		}
	}

}

void ACH2Character1:: Move(const FInputActionValue& value)
{
	//컨트롤러가 있어야 방향 계산이 가능
	if (!Controller) return;

	//Value는 Axis2D로 설정된 IA_Move의 입력값 (WASD)을 담고 있음
	//예) X=1,Y=0 -> 전진 / X=-1,Y=0 -> 후진 / X=0,Y=1  -> 오른 쪽 / X=0,Y=-1 -> 왼쪽
	const FVector2D MoveInput = value.Get<FVector2D>();
	if (!FMath::IsNearlyZero(MoveInput.X))
	{
		//캐릭터가 바라보는 방향으로 X축 이동
		AddMovementInput(GetActorForwardVector(), MoveInput.X);
	}
	if (!FMath::IsNearlyZero(MoveInput.Y))
	{
		//캐릭터의 오른쪽 방향으로 Y축 이동
		AddMovementInput(GetActorRightVector(), MoveInput.Y);
	}
}
void ACH2Character1::StartJump(const FInputActionValue& value)
{
	UE_LOG(LogTemp, Warning, TEXT("StartJump CALLED"));
	//Jump 함수는 Character가 기본 제공
	if (value.Get<bool>())
	{
		Jump();

	}
	}

void ACH2Character1:: StopJump(const FInputActionValue& value)
{
	//StopJumping 함수도 Character가 기본 제공
	if (!value.Get<bool>()) 
	{
		StopJumping();
		JumpCount += 1;
		if (GEngine)
		{
			GEngine->AddOnScreenDebugMessage(
				-1,
				3.0f,
				FColor::Green,
				FString::Printf(TEXT("Start Jump! JumpCount : %d"), JumpCount)
			);
		}
	}

}
void ACH2Character1:: Look(const FInputActionValue& value)
{
	//컨트롤러가 있어야 방향 계산이 가능
	if (!Controller) return;
	//마우스의 X,Y 움직임을 2D 축으로 가져옴
	FVector2D LookInput = value.Get<FVector2D>();

	//X는 좌우 회전(Yaw), Y는 상하 회전 (Pitch)
	//좌우 회전
	AddControllerYawInput(LookInput.X* MouseSensitivity);
	//상하 회전
	AddControllerPitchInput(LookInput.Y);
}
void ACH2Character1:: StartSprint(const FInputActionValue& value)
{
	//Shift 키를 누른 순간에 이 함수가 호출된다고 가정
	//스프린트 속도를 적용
	if (GetCharacterMovement())
	{
		GetCharacterMovement()->MaxWalkSpeed = SprintSpeed;
		GetCharacterMovement()->MaxAcceleration = 4096.f;
		GetCharacterMovement()->BrakingDecelerationWalking = 4096.f;
	}
}
void ACH2Character1:: StopSprint(const FInputActionValue& value)
{
	//Shift 키를 뗀 순간 이 함수가 호출
	//평상시 속도로 복귀
	if (GetCharacterMovement())
	{
		GetCharacterMovement()->MaxWalkSpeed = NormalSpeed;
	}
}

State Machine 설계를 통한 캐릭터 동작 애니메이션 적용

State Machine에서 각각 동작 애니메이션 조건에 맞게 호출해서 애니메이션 출력되는 형태로 구현

이동 상태 관련 애니메이션 구현 시 Movement Component는 캐릭터의 이동 속도, 가속도, 점프, 낙하 여부 등 다양한 이동 관련 정보를 제공 해당 변수 불러와서 값에 따른 상태 정해서 사용

  • 애니메이션 블루프린트의 Event Graph에서 변수 두 개를 만듭니다.
    1. Character : 타입 - Character Object Reference
    2. CharacterMovement : 타입 - Character Movement Component Object Reference

Control Rig 설정
추가적으로, 애니메이션 로직 (상태머신)에서 나온 포즈를 한 번 더 가공하거나, Foot IK (지형이 울퉁불퉁해도 발을 지면에 맞추는 기능) 같은 것을 적용하기 위해 Control Rig 노드를 사용
메인 애니메이션 (상태머신) → Control Rig → 최종 포즈 순으로 데이터를 흐르게 설정

  • Control Rig를 클릭하고 Details 창에서 Control Rig Calss 설정
  • Input과 Output 카테고리 둘 다 ShouldDoIKTrace - Use Pin에 체크(IK 위치 추적 설정)

불러온 CharacterMovement와 Character 사용해서 GroundSpeed(이동 속도 관련), ShoudMove(움직임 여부), IsFalling(낙하 여부) 변수로 값 할당


특정 모션을 묶어서 하나의 스테이스 머신으로 분류해서 해당 모션에 대해 캐시 포즈 (Cached Pose)로 저장해서 다른 스테이트 머신에서 사용하는 것도 가능




아래 그림과 같이 하나의 스테이트 머신으로 구성하는 것도 가능

인터페이스 기반 아이템 클래스 설계

인터페이스

인터페이스 (Interface)란 클래스 (또는 오브젝트)가 반드시 구현해야 할 함수 목록만을 미리 정의

아이템 클래스 설계

ItemInterface 인터페이스 정의
BaseItem 아이템 부모 클래스 정의 (IItemInterface) 구현
CoinITem -> BaseItem 상속받는 코인아이템 공통 부모 클래스
BigCoinItem -> CoinItem 상속 받은 큰 코인 아이템
SmallCopinItem -> CoinItem 상속받은 작은 코인 아이템
MineItem -> BaseItem 상속받는 지뢰 아이템 클래스
HealingItem -> BaseItem 상속받는 회복 아이템 클래스
AKeyItem 문 열수 있는 아이템 클래스 정의 (IItemInterface) 구현

아이템 인터페이스 구성

언리얼 인터페이스 클래스로 생성 시 실제 객체(클래스) 관리를 위한 언리얼 측 클래스와 직접 구현해서 사용할 인터페이스 함수들을 정의 클래스가 같이 생김


// 인터페이스를 UObject 시스템에서 사용하기 위한 기본 매크로
UINTERFACE(MinimalAPI)
class UItemInterface : public UInterface
{
    GENERATED_BODY()
};

// 실제 C++ 레벨에서 사용할 함수 원형(시그니처)를 정의
class SPARTAPROJECT_API IItemInterface

아이템 인터페이스 구성 시 구현해야될 함수 목록 헤더에 미리 정의

	//플레이어가 이 아이템의 범위에 들어왔을 때 호출
	virtual void OnItemOverlap(AActor* overlapActor)= 0;
	//플레이어가 이 아이템의 범위를 벗어났을 대 호출
	virtual void OnItemEndOverlap(AActor* overlapactor) = 0;
	//아이템이 사용되었을 때 호출
	virtual void ActivateItem(AActor* Activator) = 0;
	//이 아이템의 유형(타입)을 반환(예 : "Coin", "Mine" 등)
	virtual FName GetItemType() const = 0;

인터페이스의 구현부인 BaseItem 부모 클래스에서는 인터페이스에 정의된 함수들 구현(자식 클래스에서 구현될 부분은 빈 함수로 구현)

// 플레이어가 아이템 범위에 들어왔을 때 동작
void ABaseItem::OnItemOverlap(AActor* OverlapActor)
{
		// 기본은 빈 함수 - 각 자식 클래스에서 구현
}

// 플레이어가 아이템 범위를 벗어났을 때 동작
void ABaseItem::OnItemEndOverlap(AActor* OverlapActor)
{
		// 기본은 빈 함수 - 필요하다면 자식 클래스에서 활용
}

// 아이템이 사용(Activate)되었을 때 동작
void ABaseItem::ActivateItem(AActor* Activator)
{
		// 기본은 빈 함수 - 자식 클래스에서 구현
}

// 아이템 유형을 반환
FName ABaseItem::GetItemType() const
{
		return ItemType;
}

// 아이템을 파괴(제거)하는 함수
void ABaseItem::DestroyItem()
{
		// AActor에서 제공하는 Destroy() 함수로 객체 제거
		Destroy();
}

각각 자식 클래스에 해당하는 아이템들을 각 아이템 특징에 따라 필요한 변수 혹은 BaseItem 클래스에서 구현 아이템 부분 오버라이딩해서 구현
MineItem

	AMineItem();
	//폭발까지 걸리는 시간(5초)
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mine")
	float ExplosionDelay;
	//폭발 범위
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mine")
	float ExplosionRadius;
	//폭발 데미지
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mine")
	float ExplosionDamage;
	virtual void ActivateItem(AActor* Activator) override;

MineItem 기본 값 할당, 함수 구현 오버라이딩

// Sets default values
AMineItem::AMineItem()
{
	ExplosionDelay = 5.0f;
	ExplosionRadius = 300.0f;
	ExplosionDamage = 30.0f;
	ItemType = "Mine";

}

void AMineItem::ActivateItem(AActor* Activator)
{
	// 지뢰를 "사용"한다고 가정했을 때의 로직
// 여기서는 간단히 소멸시키지만,
// 실제로는 지연 시간 후 폭발 로직을 구현하거나,
// 폭발 이펙트, 데미지 계산 등을 추가할 수 있음
	DestroyItem();
}

CoinItem

	// Sets default values for this actor's properties
	ACoinItem();

protected:
	//코인 획득 시 얻을 점수 (자식 클래스에서 상속받아 값만 바꿔줄 예정)
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
	int32 PointValue;
profile
개발합시다!

0개의 댓글