[ Unreal Engine 5 / #7 TPS 제작 해보기 ]

SeungWoo·2024년 9월 9일
0
post-thumbnail

Asset Setting

http://naver.me/GbDca3DR

Body 
-body01
-body01_NRM ->normal
-body01_SPEC -> specular

EyeL1
-eye_iris_L

EyeR1
-eye_iris_R

Eyebase
- 수정안함

Eyeline
- eyeline

Face
-face00

Hair 
-hair base
-hair NRM ->normal
-hair SPEC -> specular

Skin 
-skin01

mat_cheek
- cheek00
  • 각 메테리얼에 맞게 Text 넣기

Add Movement Input 함수

  • 이동 하기전에 물리 법칙 영향 받는지 체크하고 결과가 나옴 ( --> 물리연산 --> 입력 연산 -> 결과 )

  • Add Movement Input 함수

    • Add Movement Input 함수는 캐릭터의 이동을 처리하는 기본적인 방법입니다.
    • 이 함수는 캐릭터의 이동 방향과 속도를 지정하여, 해당 방향으로 캐릭터를 이동시키는 역할을 합니다
  • 이동 벡터 추가

    • Add Movement Input 함수는 이동 방향(Direction)과 이동 크기(Scale Value)를 입력받습니다.
    • 방향 벡터는 캐릭터가 이동할 방향을 나타내고, 크기 값은 이동의 크기(속도)를 나타냅니다.
    • 이 함수는 주어진 방향으로 일정한 속도로 캐릭터를 움직이게 합니다.
  • 물리 기반 이동

    • Add Movement Input은 물리적으로 캐릭터를 이동시키므로, 충돌이나 중력과 같은 물리적 요소가 적용됩니다.
    • 이 함수는 CharacterMovementComponent와 함께 사용되며, 물리 엔진과의 상호작용을 통해 캐릭터의 움직임을 관리합니다.
    • 따라서 벽이나 다른 오브젝트에 충돌할 때 적절하게 반응하고, 중력의 영향을 받아 자연스럽게 움직입니다.
  • 프레임 독립성

    • Add Movement Input은 프레임 독립적으로 동작합니다.
    • 즉, 게임의 프레임 속도에 따라 이동 거리가 달라지지 않도록 자동으로 조정됩니다.
    • 이는 캐릭터의 이동이 일관되게 유지되도록 합니다.

Set Actor Location(앞서 배운 등속운동 구현)

Set Actor Location 함수는 캐릭터나 오브젝트의 위치를 직접적으로 설정하는 함수입니다.
다음은 Add Movement Input과 Set Actor Location의 주요 차이점입니다

  • 위치의 직접 설정 vs. 물리 기반 이동
    • Set Actor Location은 주어진 좌표로 즉시 오브젝트를 이동시킵니다. 이 과정에서 물리적인 충돌 검사를 무시할 수 있으며, 장애물이나 벽을 통과할 수 있습니다.
    • 반면 Add Movement Input은 물리 기반의 이동을 처리하여, 장애물과의 충돌을 감지하고, 그에 따라 반응합니다.
  • 중력 및 물리적 반응
    • Add Movement Input은 중력, 마찰, 충돌 등의 물리적 반응을 고려하여 캐릭터를 이동시킵니다.
    • Set Actor Location을 사용하면 이러한 물리적 반응을 자동으로 처리하지 않습니다.
    • 따라서 캐릭터가 공중에 있을 때 Set Actor Location을 사용하면 중력에 의해 떨어지지 않고 그 위치에 고정될 수 있습니다.
  • 프레임 의존성
    • Set Actor Location을 반복적으로 호출하여 이동을 구현할 경우, 프레임 속도에 따라 이동 속도가 달라질 수 있습니다.
    • Add Movement Input은 이 문제를 해결하기 위해 프레임 독립적으로 설계되어 있어, 일관된 이동 속도를 유지합니다.

Angle

Euler (gimbal lock)

https://www.youtube.com/watch?v=zc8b2Jo7mno

  • Gimbal Lock 현상
    • Gimbal Lock은 3축의 Euler 각을 사용하여 회전하는 시스템에서 발생할 수 있는 문제점으로, 두 회전 축이 정렬되어 회전 자유도가 하나 줄어드는 현상입니다.
    • 이로 인해 오브젝트가 원하는 방향으로 회전할 수 없게 됩니다. Gimbal Lock이 발생하는 이유는 Euler 각 회전이 특정 순서로 적용되기 때문입니다.
    • 예를 들어, Yaw, Pitch, Roll 순서로 회전이 적용된다고 가정했을 때, 만약 Pitch 각도가 ±90도에 도달하면 Yaw와 Roll 축이 같은 평면에 놓이게 됩니다.
    • 이 상태에서는 하나의 축이 고정되므로 실제로는 두 축만으로 회전하게 되어 회전 자유도가 줄어들고, 원하는 모든 방향으로 회전할 수 없는 상황이 발생합니다.
  • Gimbal Lock을 해결하는 방법

    • Quaternion(사원수) 사용
      • 사원수는 네 개의 요소(스칼라와 벡터)를 사용하여 회전을 표현하며, Gimbal Lock 문제를 근본적으로 해결할 수 있습니다.
      • 사원수 기반의 회전은 모든 축이 항상 독립적이기 때문에 회전 자유도가 상실되지 않습니다.
    • 회전 행렬 사용
      • 회전 행렬은 Gimbal Lock을 피하는 데 도움이 될 수 있지만, 행렬 자체는 여전히 오일러 각을 사용하여 회전을 표현할 수 있으므로, 행렬 변환에서 사원수를 사용하는 것이 더욱 안정적입니다.
  • Unreal Engine 와 Gimbal Lock

    • Unreal Engine에서 Gimbal Lock 문제는 사원수(Quaternion) 기반의 회전을 사용함으로써 대부분의 경우 회피됩니다.
    • 엔진은 내부적으로 회전을 다룰 때 사원수를 사용하며, 필요할 때 오일러 각도와 상호 변환합니다.
    • 또한, 애니메이션 블루프린트와 카메라 시스템에서도 Gimbal Lock이 발생하지 않도록 주의하며, 관련 연산을 처리할 때 사원수 연산을 기본적으로 사용합니다.

엔진내에 직접 비교 하기 ( 간단한 구현 )

  • 회전이나 현재 월드 Location에 대해 인식하기
  • Project Setting
  • Blueprint
    • 이동
    • 회전

Get Control Rotation

현제 월드내에 기준으로 값을 처리하는 이동 방식이라 어디를 바라보는 월드 기분 앞뒤 옆으로 이동 한다
바라보는 시점 마다 움직임을 주기 위한 Get Control Rotation 사용해서 제어 해보자

  • 제어 되고 있는 회전 값

  • 구현 된 점프 사용

C++ 구현

언리얼 공식 문서에 등록된 회전 입력 값을 처리하는 함수를 정의 해보려고 한다

Skeletal Mesh 초기화 (ConstructorHelpers)

CTPSPlayer.cpp

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

	ConstructorHelpers::FObjectFinder<USkeletalMesh> InitMesh(TEXT("/Script/Engine.SkeletalMesh'/Game/MyResource/UnityChanForUE/unitychan.unitychan'"));

	if(InitMesh.Succeeded())
	{
		GetMesh()->SetSkeletalMesh(InitMesh.Object);
		//메쉬 위치 조정
        GetMesh()->SetRelativeLocationAndRotation(FVector(0, 0, -90), FRotator(0, -90, 0));
	}
}
  • 에셋 레퍼런스 기입
  • 주의 상황
    • 경로에 보면 SkeletalMesh 부분인지 정확히 확인

카메라

  • 카메라 생성
    • SpringArm
    • Camera

CTPSPlayer.h

public:
	// Sping Arm 컴포넌트 생성
	UPROPERTY(VisibleAnywhere, Category = Camera)
	class USpringArmComponent* SpringArmComp;

	UPROPERTY(VisibleAnywhere, Category = Camera)
	class UCameraComponent* CameraComp;

CTPSPlayer.cpp

#include "GameFramework\SpringArmComponent.h"
#include "Camera\CameraComponent.h"

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

	ConstructorHelpers::FObjectFinder<USkeletalMesh> InitMesh(TEXT("/Script/Engine.SkeletalMesh'/Game/MyResource/UnityChanForUE/unitychan.unitychan'"));

	if(InitMesh.Succeeded())
	{
		GetMesh()->SetSkeletalMesh(InitMesh.Object);
		GetMesh()->SetRelativeLocationAndRotation(FVector(0, 0, -90), FRotator(0, -90, 0));
	}

	// SpringArm 초기화, 기본 값 설정
	SpringArmComp = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArmComp"));
	SpringArmComp->SetupAttachment(RootComponent);
	SpringArmComp->SetRelativeLocationAndRotation(FVector(0, 0, 50), FRotator(-20, 0, 0));
	SpringArmComp->TargetArmLength = 530;
	SpringArmComp->bUsePawnControlRotation = true;

	// 카메라 초기화 
	CameraComp = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComp"));
	CameraComp->SetupAttachment(SpringArmComp);
	CameraComp->bUsePawnControlRotation = false;

	bUseControllerRotationYaw = true;
}

움직임 + 회전

  • IMC ( Input Mapping Context )

    • Move
    • Rotate
  • IA ( Input Action )

    • Move
    • Rotate
  • 전방선언 (Forward Declaration)

    • 식별자를 정의하기 전 식별자의 존재를 컴파일러에 미리 알리는 것
    • 컴파일 시간을 단축
    • 헤더 포함 의존성을 줄임
    • 헤더파일에서 헤더파일을 포함시키는 행위는 컴파일 시간을 증가

CTPSPlayer.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "InputActionValue.h" // 추가
#include "CTPSPlayer.generated.h"

// 전방 선언
class UInputMappingContext;
class UInputAction;


UCLASS()
class MAGICIAN_API ACTPSPlayer : public ACharacter
{

	.. 생략 .. 
    
	UPROPERTY(EditAnywhere, Category = Input)
	UInputMappingContext* PlayerMappingContext;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input)
	UInputAction* MoveIA;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input)
	UInputAction* LookUpIA;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input)
	UInputAction* TurnIA;

	void Move(const FInputActionValue& value);
	void LookUp(const FInputActionValue& value);
	void Turn(const FInputActionValue& value);
    
	UPROPERTY(VisibleDefaultsOnly)
	FVector MoveDirection;
};

CTPSPlayer.cpp

... 생략...

#include "EnhancedInputSubsystems.h"
#include "EnhancedInputComponent.h"

#include "InputAction.h"

... 생략

void ACTPSPlayer::BeginPlay()
{
	Super::BeginPlay();
	
	if (APlayerController* PlayerController = Cast<APlayerController>(GetController()))
	{
    	// #include "EnhancedInputSubsystems.h 헤더 참조
        if(UEnhancedInputLocalPlayerSubsystem* Subsystem
        = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
		{
			Subsystem->AddMappingContext(PlayerMappingContext, 0);
		}
	}

}

void ACTPSPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);
	
    // EnhancedInputComponent.h 참조
    // 바인딩 해당 함수 호출 바인딩
	if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked< UEnhancedInputComponent>(PlayerInputComponent))
	{
		EnhancedInputComponent->BindAction(MoveIA, ETriggerEvent::Triggered, this, &ACTPSPlayer::Move);
		EnhancedInputComponent->BindAction(LookUpIA, ETriggerEvent::Triggered, this, &ACTPSPlayer::LookUp);
		EnhancedInputComponent->BindAction(TurnIA, ETriggerEvent::Triggered, this, &ACTPSPlayer::Turn);
		EnhancedInputComponent->BindAction(JumpIA, ETriggerEvent::Triggered, this, &ACTPSPlayer::Jump);
	}
}

Move

void ACTPSPlayer::Move(const FInputActionValue& value)
{
	// 입력 값을 FVector 형태로 가져옴
	const FVector _CurrentValue = value.Get<FVector>();
	
	// Controller가 유효할때
	if (Controller)
	{
		 //MoveDirection 벡터에 입력된 X, Y 값을 설정
		MoveDirection.Y = _CurrentValue.X; // 좌우 방향 값
		MoveDirection.X = _CurrentValue.Y; // 전후 방향 값
	}

	// 카메랑의 현재 회전에 맞춰서, MoveDirection을 변환
	// 캐릭터가 입력한 방향이 월드 좌표계가 아닌 플레이어가 바라보는 방향으로 이동
	MoveDirection = FTransform(GetControlRotation()).TransformVector(MoveDirection);
	// [ 위치 - 스케일 - 회전 ]

	// 변환된 MoveDirection 방향으로 캐릭터를 이동
	// 실제 캐릭터를 이동시키는 역할
	AddMovementInput(MoveDirection);

	MoveDirection = FVector::ZeroVector;
}

LookUp

void ACTPSPlayer::LookUp(const FInputActionValue& value)
{
	// 입력 값을 float 형태로 가져옵니다
	const float _CurrentValue = value.Get<float>();
	// 카메라의 pitch 값을 변경하여 위아래 시점 이동을 수행합니다
	AddControllerPitchInput(_CurrentValue);

}

Turn

void ACTPSPlayer::Turn(const FInputActionValue& value)
{
	// 입력 값을 float 형태로 가져옵니다
	const float _CurrentValue = value.Get<float>();
	// 카메라의 Yaw 값을 변경하여 좌우 시점 이동을 수행합니다
	AddControllerYawInput(_CurrentValue);
}

Jump

  • Input Action

  • IMC

CTPSPlayer.h

public:
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input)
	UInputAction* JumpIA;

	void InputJump(const FInputActionValue& value);

CTPSPlayer.cpp

void ACTPSPlayer::InputJump(const FInputActionValue& value)
{
	Jump();
}
profile
This is my study archive

0개의 댓글