캐릭터 클래스에서 입력 액션 연결
1. PlayerController는 SpartaInputMappingContext(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에서 각각 동작 애니메이션 조건에 맞게 호출해서 애니메이션 출력되는 형태로 구현
이동 상태 관련 애니메이션 구현 시 Movement Component는 캐릭터의 이동 속도, 가속도, 점프, 낙하 여부 등 다양한 이동 관련 정보를 제공 해당 변수 불러와서 값에 따른 상태 정해서 사용
Character : 타입 - Character Object ReferenceCharacterMovement : 타입 - Character Movement Component Object ReferenceControl Rig 설정
추가적으로, 애니메이션 로직 (상태머신)에서 나온 포즈를 한 번 더 가공하거나, Foot IK (지형이 울퉁불퉁해도 발을 지면에 맞추는 기능) 같은 것을 적용하기 위해 Control Rig 노드를 사용
메인 애니메이션 (상태머신) → Control Rig → 최종 포즈 순으로 데이터를 흐르게 설정






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


인터페이스 (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;