본문 바로가기

공부/이득우의 언리얼 프로그래밍

[Study] Part 2 - 캐릭터와 입력 시스템 (2/15)

728x90
반응형

 

 

 

  • 정리
    • 언리얼 캐릭터와 향상된 입력 시스템의 이해
      1. 액터와 컴포넌트 개념의 이해
      2. 컴포넌트 속성에 지정자를 추가해 블루프린트로 확장하는 방법의 이해
      3. 폰과 캐릭터를 구성하는 3대 구성 요소
      4. 언리얼 5.1에서 추가된 향상된 입력 시스템의 사용 방법

 

 

  • 액터의 구조
    • 월드에 속한 콘텐츠의 기본 단위
    • 트랜스폼을 가지며, 월드로부터 틱과 시간 서비스를 제공받는다.
    • 액터는 논리적 개념일 뿐 컴포넌트를 감싸는 포장 박스에 불과
    • 실질적인 구현은 컴포넌트가 진행하고 액터는 다수의 컴포넌트를 소유
    • 다수의 컴포넌트를 대표하는 컴포넌트를 루트 컴포넌트(Root Component)라고 한다.
    • 액터는 루트 컴포넌트를 가져야 하며, 루트 컴포넌트의 트랜스폼은 액터의 트랜스폼을 의미

 

액터의 구조

 

 

  • C++ 액터에서 컴포넌트의 생성
    • 컴포넌트는 언리얼 오브젝트이므로 UPROPERTY를 설정하고 TObjectPtr포인터를 선언한다.
      • 언리얼 5부터 헤더에 언리얼 오브젝트를 선언할 때 일반 포인터에서 TObjectPtr로 변경
    • 컴포넌트의 등록
      • CDO에서 생성한 컴포넌트는 자동으로 월드에 등록된다.
      • NewObject로 생성한 컴포넌트는 반드시 등록절차를 거쳐야 한다.
      • 등록된 컴포넌트는 월드의 기능을 사용할 수 있으며, 물리와 렌더링 처리에 합류한다.
    • 컴포넌트의 확장 설계
      • 에디터 편집 및 블루프린트로의 승계를 위한 설정
      • UPROPERTY지정자(Specifier)를 설정할 수 있다.
    • 컴포넌트 지정자
      • Visible / Edit : 크게 객체타입과 값타입으로 사용
      • Anywhere / DefaultsOnly / InstanceOInly : 에디터에서 편집 가능 영역
      • BlueprintReadOnly / BlueprintReadWrite : 블루프린트로 확장시 일기 혹은 읽기쓰기 권한을 부여
      • Category : 에디터 편집 영역(Detail)에서의 카테고리 지정

 

 

 

	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Mesh)
	TObjectPtr<class UStaticMeshComponent>Body;			// 전방선언

	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Mesh)
	TObjectPtr<class UStaticMeshComponent>Water;		// 전방선언
  • ABFountain.h
#include "Prop/ABFountain.h"
#include "Components/StaticMeshComponent.h"

// Sets default values
AABFountain::AABFountain()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	Body = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Body"));
	Water = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Water"));

	RootComponent = Body;				// 루트 컴포넌트로 지정
	Water->SetupAttachment(Body);		// Body밑으로 붙여주기
	Water->SetRelativeLocation(FVector(0.0f, 0.0f, 132.0f));

	// ConstructorHelpers를 통해 StaticMesh를 붙여주기 (붙여줄 StaticMesh의 주소를 뒤에 붙여준다.)
	static ConstructorHelpers::FObjectFinder<UStaticMesh> BodyMeshRef(TEXT("/Script/Engine.StaticMesh'/Game/ArenaBattle/Environment/Props/SM_Plains_Castle_Fountain_01.SM_Plains_Castle_Fountain_01'"));
	if (BodyMeshRef.Object)
	{
		Body->SetStaticMesh(BodyMeshRef.Object);
	}

	// ConstructorHelpers를 통해 StaticMesh를 붙여주기 (붙여줄 StaticMesh의 주소를 뒤에 붙여준다.)
	static ConstructorHelpers::FObjectFinder<UStaticMesh> WaterMeshRef(TEXT("/Script/Engine.StaticMesh'/Game/ArenaBattle/Environment/Props/SM_Plains_Fountain_02.SM_Plains_Fountain_02'"));
	if (WaterMeshRef.Object)
	{
		Water->SetStaticMesh(WaterMeshRef.Object);
	}
}
  • ABFountain.cpp

 

  • 헤더파일에서 전방 선언 및 지정자를 입력해준다.
  • cpp파일의 생성자에서는 Body와 Water에 대해 정의해주고, 둘 중 어느 컴포넌트가 루트컴포넌트인지 지정해준다.
  • 생성자 코드가 변경되었으니 가급적이면 에디터를 종료하고 Ctrl+F5를 통해 비주얼 스튜디오에서 빌드해서 실행시킨다.

 

 

C++ Classes>ArenaBattle>Prop

  • 해당 위치에 ABFountain이라는 프랍이 생성된 걸 볼 수 있다.

 

 

 

 

Class Settings

  • 아까 만들어 둔 BP_Fountain 블루프린트를 실행한 후 Class SettingsParent ClassActor에서 방금 만든 ABFountain으로 변경해준다.
  • 중복되는 Body와 Water는 삭제해주면 방금 만든 C++ 클래스로 적용이 가능하다.
  • C++로 기반을 충분히 다져놓고 추가적인 로직은 블루프린트로 필요한 만큼만 최소 한도로 확장하도록 설계하는 것이 효과적이고 유용하게 사용할 수 있다.

 

 

 

  • 폰의 기능과 설계
    • 폰은 액터를 상속받은 특별한 액터, 플레이어가 빙의해 입출력을 처리하도록 설계되어 있음
    • 길찾기 사용 가능, 일반적으로 세 가지 주요 컴포넌트로 구성
      • 기믹과 상호작용을 담당하는 충돌 컴포넌트 (루트컴포넌트)
      • 시각적인 비주얼을 담당하는 메시 컴포넌트
      • 움직임을 담당하는 컴포넌트
    • 컴포넌트 중에서 트랜스폼이 없이 기능만 제공하는 컴포넌트를 액터컴포넌트라고 한다.

 

 

폰의 기능과 설계

 

 

 

  • 캐릭터의 기본 구조
    • 캐릭터는 인간형 폰을 구성하도록 언리얼이 제공하는 전문 폰 클래스
    • 세가지 주요 컴포넌트 구성
      • 기믹과 상호작용을 담당하는 캡슐 컴포넌트 (루트 컴포넌트)
      • 애니메이션 캐릭터를 표현하는 스켈레탈 메시 컴포넌트
      • 캐릭터의 움직임을 담당하는 캐릭터 무브먼트(CharacterMovement) 컴포넌트

캐릭터의 기본 구조

 

 

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

UCLASS()
class ARENABATTLE_API AABCharacterBase : public ACharacter
{
	GENERATED_BODY()

public:
	// Sets default values for this pawn's properties
	AABCharacterBase();		// 생성자
	// 필요 없는 기능들은 다 제거
};
  • ABCharacterBase.h
#include "Character/ABCharacterBase.h"
#include "Components/CapsuleComponent.h"					// 캡슐 컴포넌트
#include "GameFramework/CharacterMovementComponent.h"		// 무브먼트 컴포넌트

// Sets default values
AABCharacterBase::AABCharacterBase()		// 생성자
{
	// Pawn 설정
	bUseControllerRotationPitch = false;
	bUseControllerRotationYaw = false;
	bUseControllerRotationRoll = false;

	// Capsule 설정
	GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);
	GetCapsuleComponent()->SetCollisionProfileName(TEXT("Pawn"));

	// Movement 설정
	GetCharacterMovement()->bOrientRotationToMovement = true;
	GetCharacterMovement()->RotationRate = FRotator(0.0f, 500.0f, 0.0f);
	GetCharacterMovement()->JumpZVelocity = 700.0f;
	GetCharacterMovement()->AirControl = 0.35f;
	GetCharacterMovement()->MaxWalkSpeed = 500.f;
	GetCharacterMovement()->MinAnalogWalkSpeed = 20.f;
	GetCharacterMovement()->BrakingDecelerationWalking = 2000.f;

	// Mesh 설정
	GetMesh()->SetRelativeLocationAndRotation(FVector(0.0f, 0.0f, -100.0f), FRotator(0.0f, -90.0f, 0.0f));
	GetMesh()->SetAnimationMode(EAnimationMode::AnimationBlueprint);
	GetMesh()->SetCollisionProfileName(TEXT("CharacterMesh"));

	// 메쉬 지정
	static ConstructorHelpers::FObjectFinder<USkeletalMesh> CharacterMeshRef(TEXT("/Script/Engine.SkeletalMesh'/Game/Characters/Mannequins/Meshes/SKM_Quinn_Simple.SKM_Quinn_Simple'"));
	if (CharacterMeshRef.Object)
	{
		GetMesh()->SetSkeletalMesh(CharacterMeshRef.Object);
	}

	// 애니메이션 지정
	static ConstructorHelpers::FClassFinder<UAnimInstance> AnimInstanceClassRef(TEXT("/Game/Characters/Mannequins/Animations/ABP_Quinn.ABP_Quinn_C"));
	if (AnimInstanceClassRef.Class)
	{
		GetMesh()->SetAnimInstanceClass(AnimInstanceClassRef.Class);
	}
}
  • ABCharacterBase.cpp
#include "Game/ABGameMode.h"
#include "ABGameMode.h"

AABGameMode::AABGameMode()
{
	//static ConstructorHelpers::FClassFinder<APawn> ThirdPersonClassRef(TEXT("/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter.BP_ThirdPersonCharacter_C"));		// 클래스형에 대해서는 Copy Reference로 들고온 에셋주소의 ''는 생략해 주고 경로만 입력해주고, 클래스 정보를 가져올 것이기 때문에 _C를 붙여 준다.

	//if (ThirdPersonClassRef.Class)
	//{
	//	DefaultPawnClass = ThirdPersonClassRef.Class;
	//}
	// DefaultPawnClass

	static ConstructorHelpers::FClassFinder<APawn> DefaultPawnClassRef(TEXT("/Script/ArenaBattle.ABCharacterPlayer"));
	if (DefaultPawnClassRef.Class)
	{
		DefaultPawnClass = DefaultPawnClassRef.Class;
	}

	static ConstructorHelpers::FClassFinder<APlayerController> PlayerControllerRef(TEXT("/Script/ArenaBattle.ABPlayerController"));		// 위와 동일하게 적용. 이 경우 기본으로 클래스 정보가 복제된 것이기 때문에 _C를 붙이지 않아도 된다.

	if (PlayerControllerRef.Class)
	{
		PlayerControllerClass = PlayerControllerRef.Class;		// 이 경우 에셋으로부터 직접 참조를 받기 때문에 헤더 파일의 의존성을 줄일 수 있다.
	}
}
  • ABGameMode.cpp

 

  • 기본 언리얼에서 제공하는 BP_ThirdPerson이 아닌 C++로 만든 캐릭터를 사용하기 위해 캐릭터 정보를 생성자에 등록시켜 준다.
  • ABCharacterBase.cpp처럼 생성자에 캐릭터 셋팅을 해준 뒤 ABGameMode.cpp에서 DefaultPawn으로 등록시켜 준다.

 

 

World Settings>GameMode>Default Pawn

 

움직이지 않고 캐릭터가 보이지 않음. 카메라 컴포넌트를 추가하지 않았기 때문

  • 이후 실행하면 움직이지 않고, 캐릭터가 보이지 않는데, 이는 실제로 캐릭터는 스폰되었지만 카메라 컴포넌트를 추가하지 않아서 캐릭터에 붙어 움직이지 않는 모습을 볼 수 있다.

 

#include "CoreMinimal.h"
#include "Character/ABCharacterBase.h"
#include "ABCharacterPlayer.generated.h"

/**
 * 
 */
UCLASS()
class ARENABATTLE_API AABCharacterPlayer : public AABCharacterBase
{
	GENERATED_BODY()
	
public:
	AABCharacterPlayer();

// Camera 셋팅 관련 컴포넌트 추가
protected:
	// Meta = private로 선언된 어떤 언리얼 오브젝트의 객체들을 블루프린트에서도 접근할 수 있도록 만들어 주는 특별한 지시자
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, Meta = (AllowPrivateAccess = "true"))
	TObjectPtr<class USpringArmComponent> CameraBoom;		// SpringArm

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, Meta = (AllowPrivateAccess = "true"))
	TObjectPtr<class UCameraComponent> FollowCamera;		// 실제 카메라 컴포넌트
};
  • ABCharacterPlayer.h
#include "Character/ABCharacterPlayer.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/SpringArmComponent.h"

AABCharacterPlayer::AABCharacterPlayer()
{
	// Camera
	CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));		// 컴포넌트 생성
	CameraBoom->SetupAttachment(RootComponent);
	CameraBoom->TargetArmLength = 400.0f;
	CameraBoom->bUsePawnControlRotation = true;

	FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));		// 컴포넌트 생성
	FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);			// 카메라암에 부착(CameraBoom) / 소켓이라는 특별한 이름지시자를 지정하면 스프링 암의 끝에 자동으로 달라붙게 된다.
	FollowCamera->bUsePawnControlRotation = false;
}
  • ABCharacterPlayer.cpp

 

  • 카메라 컴포넌트스프링 암 컴포넌트 추가하기
    • 빌드후 실행

3인칭으로 TargetArmLength = 400.0f 만큼 떨어져서 보인다.

 

 

  • 입력시스템의 동작 방식 (기존 버전)
    • 플레이어의 입력은 컨트롤러를 통해 폰으로 전달
    • 입력을 컨트롤러가 처리할 수도, 폰이 처리할 수도 있는데, 일반적으로는 폰이 처리하도록 설정

입력시스템의 동작 방식

 

 

Input Overview In Unreal Engine | Unreal Engine 5.4 Documentation | Epic Developer Community

The Input object is responsible for converting input from the player into data in a form Actors can understand and make use of.

dev.epicgames.com

  • 위와 같이 PlayerController를 통해서 Pawn으로 전달이 된다. 우선순위를 플레이어 컨트롤러가 가진다.
  • 예를들어 GTA같은 다양한 사물에 빙의하는 컨텐츠/게임의 경우 폰에서 입력에 대한 처리를 해주는 것이 좋다. PlayerController에서 구현하게 될 경우 굉장히 방대해질 것이기 때문

 

  • 향상된 입력시스템(Enhanced Input System)
    • 기존 입력시스템을 대체하고 언리얼 5.1부터 새롭게 도입
    • 사용자의 입력 설정 변경에 유연하게 대처할 수 있도록 구조를 재수립
    • 사용자 입력 처리를 네 단계로 세분화하고 각 설정을 독립적인 애셋으로 대체

Edit>Project Settings>Engine>Input

 

  • 향상된 입력시스템 동작 구성
    • 사용자의 입력 데이터를 최종 함수에 매핑하는 과정을 체계적으로 구성
    • 플랫폼에 따른 다양한 입력 장치의 설정
      • 게임 패드용 입력 매핑 컨텍스트, 키보드용 입력 매핑 컨텍스트
    • 입력 값의 변경
      • AD/WS 입력값을 Y축과 X축으로 변경, 값 반전의 처리
    • 이벤트 발생 조건의 상세 설정
      • 일반 버튼인가? 축 이동인가? 일정 이상 눌러야 하는가?

 

#include "InputActionValue.h"

protected:
	virtual void BeginPlay() override;

protected:
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

// 입력 셋팅
protected:
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, Meta = (AllowPrivateAccess = "true"))
	TObjectPtr<class UInputMappingContext> DefaultMappingContext;

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

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

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

	void Move(const FInputActionValue& Value);
	void Look(const FInputActionValue& Value);
  • ABCharacterPlayer.h
#include "InputMappingContext.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"

// Input
static ConstructorHelpers::FObjectFinder<UInputMappingContext> InputMappingContextRef(TEXT("/Script/EnhancedInput.InputMappingContext'/Game/ArenaBattle/Input/IMC_Default.IMC_Default'"));
if (nullptr != InputMappingContextRef.Object)
{
	DefaultMappingContext = InputMappingContextRef.Object;
}

static ConstructorHelpers::FObjectFinder<UInputAction> InputActionMoveRef(TEXT("/Script/EnhancedInput.InputAction'/Game/ArenaBattle/Input/Actions/IA_Move.IA_Move'"));
if (nullptr != InputActionMoveRef.Object)
{
	MoveAction = InputActionMoveRef.Object;
}

static ConstructorHelpers::FObjectFinder<UInputAction> InputActionJumpRef(TEXT("/Script/EnhancedInput.InputAction'/Game/ArenaBattle/Input/Actions/IA_Jump.IA_Jump'"));
if (nullptr != InputActionJumpRef.Object)
{
	JumpAction = InputActionJumpRef.Object;
}

static ConstructorHelpers::FObjectFinder<UInputAction> InputActionLookRef(TEXT("/Script/EnhancedInput.InputAction'/Game/ArenaBattle/Input/Actions/IA_Look.IA_Look'"));
if (nullptr != InputActionLookRef.Object)
{
	LookAction = InputActionLookRef.Object;
}

void AABCharacterPlayer::BeginPlay()
{
	Super::BeginPlay();

	APlayerController* PlayerController = CastChecked<APlayerController>(GetController());
	if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
	{
		Subsystem->AddMappingContext(DefaultMappingContext, 0);		// 0 우선순위 설정
		//Subsystem->RemoveMappingContext(DefaultMappingContext);
	}

}

void AABCharacterPlayer::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent);		// 만약 EnhancedInputComponent를 사용하지 않으면 에러를 발생하도록 CastChecked 함수를 사용

	EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Triggered, this, &ACharacter::Jump);
	EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &ACharacter::StopJumping);
	EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AABCharacterPlayer::Move);
	EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &AABCharacterPlayer::Look);
}


void AABCharacterPlayer::Move(const FInputActionValue& Value)
{
	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);
}

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

	AddControllerYawInput(LookAxisVector.X);
	AddControllerPitchInput(LookAxisVector.Y);
}
  • ABCharacterPlayer.cpp

 

  • 필요한 인풋 시스템에 대한 값들을 추가한다.

Input Action>Modifiers>Swizzle Input Axis Values

  • 입력에서의 AD, 즉 X축 값을 실제 캐릭터 이동에서는 Y축으로 변환하고, 입력에서의 WS, 즉 Y축의 값을 실제 캐릭터 이동에서는 X축으로 변환한다라고 지정

 

BP_ThirdPerson과 똑같은 움직임을 가짐

 

  • BP_ThirdPerson과 같이 동일하게 입력을 통한 움직임이 가능

 

 

 

 

 

 

 

 

해당 포스트는 인프런의 <이득우의 언리얼 프로그래밍 Part2 -언리얼 게임 프레임웍의 이해>
강의를 수강하고 정리한 내용입니다.
 

이득우의 언리얼 프로그래밍 Part2 - 언리얼 게임 프레임웍의 이해 | 이득우 - 인프런

청강문화산업대학교에서 언리얼 엔진, 게임 수학, UEFN 게임제작을 가르치고 있습니다. - 이득우의 언리얼 C++ 프로그래밍, 넥슨 코리아 공식 교육 교재 선정 2023 - 스마일게이트 언리얼 프로그래

www.inflearn.com

 

 

 

 

 

 

 

728x90