언리얼5 - 캐릭터와 입력 시스템
플레이어의 입력과 폰에 대한 기초적인 내용
액터와 컴포넌트
액터의 구조
월드에 속한 콘텐츠의 기본 단위.
액터는 트랜스폼을 가지며, 월드로부터 틱과 시간 서비스를 제공받는다.
사실 액터는 논리적 개념일 뿐 컴포넌트를 감싸는 포장 박스에 불과함.
실질적인 구현은 컴포넌트가 진행하고 액터는 다수의 컴포넌트를 소유하고 있음.
다수의 컴포넌트를 대표하는 컴포넌트를 루트 컴포넌트(Root Component)라고 한다.
액터는 루트 컴포넌트를 가져야 하며, 루트 컴포넌트의 트랜스폼은 액터의 트랜스폼을 의미한다.
블루 프린트를 사용해 배경 사물 액터 추가하기
Content Browser에서 블루프린트를 추가하면 상속받을 클래스를 선택하게 됨.
단순한 배경 사물을 만들 것이기 때문에 Actor 선택 후 BP_Fountain을 만들고 저장.
저장 후 더블클릭하면 애셋을 만들 수 있는 창이 뜸
DefaultSceneRoot 라는 트랜스폼을 가지고 있는 루트 컴포넌트가 언제나 배정됨.
DefaultSceneRoot는 루트 컴포넌트를 가져야하기 때문에 임시적으로 생성된 아무 기능이 없는 컴포넌트.
Add에서 실질적인 기능을 담당하는 Static Mesh를 생성한 후 DefaultSceneRoot 위치로 드래그해서 루트로 지정.
Static Mesh가 분수대의 몸체가 될 것이기 때문에 이름은 Body로 지정.
Details 창에서 Static Mesh에 Fountain을 찾아 메쉬를 지정하면 루트 컴포넌트를 Static Mesh 컴포넌트로 지정할 수 있게 됨.
Body의 자식에 Water로 Static Mesh를 하나 더 추가. Water에는 물 효과를 나타내는 Fountain 메쉬를 찾아서 지정.
Water는 Body의 자식이 되어 계층 구조를 가지기 때문에 Water는 상대 트랜스폼을 조절할 수 있게 됨.
Water의 Z축을 132로 설정.
저장 후 컴파일. (Ctrl+S, F7)
PointLight를 추가하고 드래그하여 Body의 자식으로 추가.
Details 창에서 색깔 설정.
Viewport는 wasd로 움직이며 q,e로 높이 조절, 마우스 우클릭으로 카메라 회전.
저장,컴파일 후 레벨로 돌아오면 액터가 생성되어 있는 것을 확인 할 수 있음.
레벨에 드래그해 배치하고 플레이하면 마네킹와 상호작용하는 배경 액터를 생성할 수 있음.
C++ 액터에서 컴포넌트의 생성
컴포넌트는 언리얼 오브젝트이므로 UPROPERY를 설정하고 TObjectPtr로 포인터를 선언한다.
컴포넌트의 등록
- CDO에서 생성한 컴포넌트는 자동으로 월드에 등록된다.
- 런타임에서 NewObject로 생성한 컴포넌트는 반드시 등록 절차를 거쳐야한다. 예) RegisterComponent를 호출
- 등록된 컴포넌트는 월드의 기능을 사용할 수 있으며, 물리와 렌더링 처리에 합류한다.
컴포너트의 확장 설계
- 에디터 편집 및 블루프린트로의 승계를 위한 설정
- UPROPERY에 지정자(Specifier)를 설정할 수 있다.
컴포넌트 지정자
- Visible / Edit : 크게 객체 타입 / 값타입으로 사용
- Anywhere / DefaultsOnly / InstanceOnly : 에디터에서 편집 가능 영역. 보통 Anywhere를 많이 사용.
객체 타입인 경우 VisibleAnywhere, 값 타입인 경우 EditAnywhere를 사용해주면 됨.
컴포넌트는 객체 타입이기 때문에 VisibleAnywhere. - BlueprintReadOnly / BlueprintReadWrite : 블루프린트로 확장 시 읽기 또는 읽기쓰기 권한을 부여
- Category : 에디터 편집 영역(Detail)에서 카테고리 지정
C++ 클래스를 사용해 배경 사물 추가하기
Actor를 상속받는 ABFountain C++ 클래스 생성. 한번 빌드해줘야 ContentBrowser에 뜬다.
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ABFountain.generated.h"
UCLASS()
class ARENABATTLE_API AABFountain : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AABFountain();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
// 배경 물체 만들기
// UPROPERTY에 지정자 설정
// 객체 타입이라 VisibleAnywhere, 블루프린트에서 읽고쓰기 가능하게 BlueprintReadWrite
// Category = Mesh 언리얼 헤더 툴에 의해 카테고리 분석
// 자동으로 메쉬에 대한 카테고리에 Body에 대한 값을 편집할 수 있게 UI 제공됨
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Mesh)
TObjectPtr < class UStaticMeshComponent > Body;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Mesh)
TObjectPtr < class UStaticMeshComponent > Water;
};
ABFountain.h
// Fill out your copyright notice in the Description page of Project Settings.
#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;
// Body의 하위에 부착
Water->SetupAttachment(Body);
// Water의 상대 위치 조정
Water->SetRelativeLocation(FVector(0.0f, 0.0f, 132.0f));
// 스태틱 매쉬 지정
// 생성자 코드이기 때문에 ConstructorHelpers 사용
// 에디터에서 StaticMesh의 주소 복사해서 붙여넣기.
// 여기서는 UStaticMesh이고 생성할 때는 UStaticMeshComponent인 이유는?
static ConstructorHelpers::FObjectFinder<UStaticMesh> BodyMeshRef(TEXT("/Script/Engine.StaticMesh'/Game/ArenaBattle/Environment/Props/SM_Plains_Castle_Fountain_01.SM_Plains_Castle_Fountain_01'"));
// .Class 아니고 .Object
if (BodyMeshRef.Object)
{
Body->SetStaticMesh(BodyMeshRef.Object);
}
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);
}
}
// Called when the game starts or when spawned
void AABFountain::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void AABFountain::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
AABFountain.cpp
StaticMesh 지정할 때 제대로 된 주소를 안넣고 빌드하면 에러남
클래스를 만들었던 Prop 폴더에 액터가 생성된 것을 확인할 수 있다.
레벨에 놓아보면 추가하지 않은 라이팅을 제외하고 블루프린트로 생성한 액터와 동일.
이렇게 만들어진 C++ 클래스를 상속해 별도의 블루프린트로 액터를 만들 수 있음.
BP_Fountain을 열고 Class Setting에 들어가서 Parent Class를 방금 만든 ABFountain으로 변경
원래 Body와 Water가 있는 상태에서 클래스에서 생성했던 Body가 Water가 추가로 들어감.
원래 있던 Body과 Water는 삭제. PointLight는 자동으로 Body의 하위로 들어감.
이렇게 만들어진 블루프린트는 앞에서 만든 것들과 동일. -> C++로 기반을 충분히 다져놓고 이후 추가적인 로직은 블루프린트를 사용해 필요한 만큼만 최소 한도로 확장하도록 만드는 것이 효과적임.
캐릭터 제작
폰의 기능과 설계
폰은 액터를 상속받은 특별한 액터이며, 플레이어가 빙의해 입출력을 처리하도록 설계되어 있음.
폰은 길찾기를 사용할 수 있으며, 일반적으로 세 가지 주요 컴포넌트로 구성됨.
- 기믹과 상호작용을 담당하는 충돌 컴포넌트 (루트컴포넌트)
- 시각적인 비주얼을 담당하는 메시 컴포넌트
- 움직임을 담당하는 컴포넌트
움직임의 경우 트랜스폼이 없이 일반적인 기능만 제공.
컴포넌트 중에서 트랜스폼이 없이 기능만 제공하는 컴포넌트를 액터 컴포넌트, 트랜스폼이 있는 컴포넌트를 씬 컴포넌트라고 함.
캐릭터의 기본 구조
캐릭터는 인간형 폰을 구성하도록 언리얼이 제공하는 전문 폰 클래스를 의미.
캐릭터는 세 가지 주요 컴포넌트로 구성됨. 캐릭터 모델에는 각 컴포넌트가 이미 설정되어 있음.
- 기믹과 상호작용을 담당하는 캡슐 컴포넌트 (루트 컴포넌트)
- 애니메이션 캐릭터를 표현하는 스켈레탈 메시 컴포넌트
- 캐릭터의 움직임을 담당하는 캐릭터 무브먼트(CharacterMovement) 컴포넌트
마네킹 캐릭터 제작
ABCharacterBase의 헤더 파일과 cpp 파일에서 생성자를 제외한 플레이어에 관련된 기본 기능과 불필요한 함수는 모두 제거.
ABCharacterBase가 상속받은 Character 클래스를 보면 아래와 같이 컴포넌트들이 선언되어 있음
상속받은 클래스들은 GetMesh등의 함수를 통해 접근할 수 있음. 메쉬 컴포넌트에 관련된 다양한 설정값들을 생성자에서 미리 지정만 해주면 됨.
// Fill out your copyright notice in the Description page of Project Settings.
#include "Character/ABCharacterBase.h"
#include "Components/CapsuleComponent.h"
// Component's'임
#include "GameFramework/CharacterMovementComponent.h"
// Sets default values
AABCharacterBase::AABCharacterBase()
{
// Pawn 기본 설정
// 폰의 회전을 지정하기 위한 값
bUseControllerRotationPitch = false;
bUseControllerRotationYaw = false;
bUseControllerRotationRoll = false;
// 루트 컴포넌트, 캡슐(Capsule) 설정
// GetCapsuleComponent를 사용해 가져올 수 있음, 헤더 추가
GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);
GetCapsuleComponent()->SetCollisionProfileName(TEXT(""));
// 움직임(Movement) 설정
// 점프의 크기, 이동 속도 등
GetCharacterMovement()->bOrientRotationToMovement = true;
GetCharacterMovement()->RotationRate = FRotator(0.0f, 500.0f, 0.0f);
GetCharacterMovement()->JumpZVelocity = 700.f;
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"));
// 스켈레탈 메쉬 컴포넌트의 실제 애셋 부착
// ThirdPersonCharacter에서 제공하는 메쉬와 클래스 사용
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
// Fill out your copyright notice in the Description page of Project Settings.
#include "Game/ABGameMode.h"
#include "ABGameMode.h"
//#include "Player/ABPlayerController.h"
// PlayerController 클래스 정보 가져오기
// 애셋으로부터 직접 참조를 받으면 헤더를 추가하지 않아도 됨
AABGameMode::AABGameMode()
{
// 멤버 변수 값 설정
// DefaultPawnClass 지정
// 애셋에 담긴 클래스 정보를 얻어와서 설정해줌
// 생성자 코드이기 때문에 ConstructorHelpers 함수를 사용해서 애셋 정보를 가져올 수 있음.
// Pawn 클래스로 상속을 받았기 때문에 APawn으로 찾음
// 경로 /Script/Engine.Blueprint'/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter.BP_ThirdPersonCharacter' 에서
// 앞부분과 따옴표는 지우고 애셋 경로만 사용하며,
// 클래스 정보를 가져올 것이기 때문에 애셋 이름 뒤에 _C를 붙여줌
//static ConstructorHelpers::FClassFinder<APawn> ThirdPersonClassRef(TEXT("/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter.BP_ThirdPersonCharacter_C"));
//if (ThirdPersonClassRef.Class)
//{
// // 해당 레퍼런트 안에 있는 클래스 정보가 null이 아니라면
// // DefaultPawnClass 값을 ThirdPersonClassRef.Class로 지정
// DefaultPawnClass = ThirdPersonClassRef.Class;
//}
// ABCharacterPlayer는 우리가 설정한 ABCharacterBase를 상속받았음. 때문에 ABCharacterPlayer 직접 스폰
static ConstructorHelpers::FClassFinder<APawn> DefaultPawnClassRef(TEXT("/Script/ArenaBattle.ABCharacterPlayer"));
if (DefaultPawnClassRef.Class)
{
DefaultPawnClass = DefaultPawnClassRef.Class;
}
// PlayerControllerClass에 AABPlayerController 클래스 설정
// AABPlayerControllerClassRef는 클래스 정보가 바로 복사된 것이기 때문에 _C를 붙이지 않음
static ConstructorHelpers::FClassFinder<APlayerController> PlayerControllerClassRef(TEXT("/Script/ArenaBattle.ABPlayerController"));
if (PlayerControllerClassRef.Class)
{
PlayerControllerClass = PlayerControllerClassRef.Class;
}
//PlayerControllerClass = AABPlayerController::StaticClass();
}
ABGameMode.cpp
World Setting의 Default Pawn Class가 변경되었음.
이대로 게임을 실행하면 마네킹이 보이지 않는데, 카메라를 부착하지 않았기 때문.
Shift+F1을 눌러 Detach. 빙의를 푼 뒤 마우스 휠을 돌려 살펴보면 캐릭터가 생성된 것을 확인할 수 있음.
카메라 기능은 NPC에게는 필요없고 플레이어만 필요하기 때문에 ABCharacterPlayer 클래스에 추가로 구현.
카메라 기능 구현
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Character/ABCharacterBase.h"
#include "ABCharacterPlayer.generated.h"
/**
*
*/
UCLASS()
class ARENABATTLE_API AABCharacterPlayer : public AABCharacterBase
{
GENERATED_BODY()
public:
AABCharacterPlayer();
protected:
// Camera 설정
// UPROPERTY()의 Meta 지정자: private으로 선언된 언리얼 오브젝트 객체들을 블루프린트에서도 접근할 수 있게 함
// 스프링 암. 카메라를 지탱해주는 지지대 역할 컴포넌트
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category=Camera, Meta=(AllowPrivateAccess = "true"))
TObjectPtr<class USpringArmComponent> CameraBoom;
// 실제 카메라 컴포넌트
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category=Camera, Meta=(AllowPrivateAccess = "true"))
TObjectPtr<class UCameraComponent> FollowCamera;
};
ABCharacterPlayer.h
// Fill out your copyright notice in the Description page of Project Settings.
#include "Character/ABCharacterPlayer.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/SpringArmComponent.h"
AABCharacterPlayer::AABCharacterPlayer()
{
// 카메라
// 뼈대 오브젝트 생성
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
// 루트 컴포넌트에 부착
CameraBoom->SetupAttachment(RootComponent);
// 4미터 길이로 설정
CameraBoom->TargetArmLength = 400.0f;
// 회전 설정
CameraBoom->bUsePawnControlRotation = true;
// 카메라 생성
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
// 스프링 암에 부착
// 부착할 때 특정 위치를 지정하는 것이 아니라 소켓이라는 특별한 이름 지시자를 지정하면
// 스프링 암의 끝에 자동으로 붙음
// F12로 확인해보면 스프링 암의 소켓이름은 SpringEndpoint.
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
// 회전 설정
FollowCamera->bUsePawnControlRotation = false;
}
ABCharacterPlayer.cpp
컴파일 후 실행해보면 3인칭으로 캐릭터가 보이게 됨.
입력 시스템 개요
입력시스템의 동작 방식
플레이어의 입력은 컨트롤러를 통해 폰으로 전달됨.
입력을 컨트롤러가 처리할 수도, 폰이 처리할 수도 있는데, 일반적으로는 폰이 처리하도록 설정
- 예) GTA같은 다양한 사물에 빙의하는 게임의 경우 컨트롤러가 처리하게 되면 코드가 방대해지기 때문에 폰이 유리
빙의를 당하는 폰에서 각각 구현하는 것이 코드가 분산되기 때문에 더 효과적.
향상된 입력 시스템(Enhanced Input System)
기존 입력시스템을 대체하고 언리얼 5.1부터 새롭게 도입
기존 방식은 플레이어의 입력(Input)을 설정하는 과정에서 처리하기 전에 Input을 설정하다보니
고정되어 있어 사용자의 입력설정 변경에 유연하게 대처할 수 없었음.
새롭게 도입된 입력 시스텝은 사용자의 입력 설정 변경에 유연하게 대처할 수 있도록 구조를 재수립
사용자 입력 처리를 네 단계로 세분화하고 각 설정을 독립적인 애셋으로 대체함.
향상된 입력시스템 동작 구성
사용자의 입력 데이터를 최종 함수에 매핑하는 과정을 체계적으로 구성
입력 매핑 컨텍스트에서의 플랫폼에 따른 다양한 입력 장치 설정
- 예) 게임 패드용 입력 매핑 컨텍스트, 키보드용 입력 매핑 컨텍스트를 런타임에서 변경 가능
입력 값 변경
- 예) 액션의 입력값 변조를 통한 AD/WS 입력값을 Y축과 X축으로 변경, 값 반전의 처리
이벤트 발생 조건의 상세 설정
- 예) 액션의 이벤트 활성화 트리거를 통해 이벤트를 발생시킬 수 있음. 일반 버튼인가? 축 이동인가? 일정 이상 눌러야 하는가?
사용자
사용자가 입력하면 입력 매핑 컨텍스트라는 것을 통해 입력과 연결됨. 입력을 연결했을 때 수행하는 주체는 액션.
입력 매핑 컨텍스트
지정한 액션에 사용자 입력이 연결됨.
액션
액션에 대해서는 추가적인 설정을 통해 어떻게 들어온 입력값을 재가공할지, 그러한 입력값을 통해서 어떻게 이벤트를 활성화할 지 설정 진행.
게임 로직
이후 게임 로직에서 지정한 특정 함수와 매핑을 진행.
과거에는 게임 로직에서 다 처리했지만 4단계의 과정을 거쳐 별도의 액션을 통해 관리할 수 있게 함으로써 게임 로직의 부담을 덜고 보다 유연하게 입력을 처리할 수 있음.
입력 시스템 구현
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Character/ABCharacterBase.h"
#include "InputActionValue.h"
// EnhancedInput 모듈 추가해야함
// IntelliSense가 인식하지 못할 경우
// Intermediate와 Binaries폴더를 삭제한 후 프로젝트 재생성
#include "ABCharacterPlayer.generated.h"
/**
*
*/
UCLASS()
class ARENABATTLE_API AABCharacterPlayer : public AABCharacterBase
{
GENERATED_BODY()
public:
AABCharacterPlayer();
public:
// 언리얼 엔진의 Input System에서 입력 액션과 우리가 선언한 함수(Move, Look)를 서로 매핑시켜주는 역할
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
protected:
// 입력 매핑 컨텍스트를 할당하는 역할
// 입력을 키보드로 받을지, 패드로 받을지에 대해서는
// BeginPlay의 DefaultMappingContext를 통해 진행.
virtual void BeginPlay() override;
// Camera 설정
protected:
// UPROPERTY()의 Meta 지정자:
// private으로 선언된 언리얼 오브젝트 객체들을 블루프린트에서도 접근할 수 있게 함
// 스프링 암. 카메라를 지탱해주는 지지대 역할 컴포넌트
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category=Camera, Meta=(AllowPrivateAccess = "true"))
TObjectPtr<class USpringArmComponent> CameraBoom;
// 실제 카메라 컴포넌트
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category=Camera, Meta=(AllowPrivateAccess = "true"))
TObjectPtr<class UCameraComponent> FollowCamera;
// Input 설정
protected:
// 매핑 컨텍스트와 액션에 대한 애셋 지정
// 다른 애셋으로 변경할 수 있도록 설계하기 위해 EditAnywhere로 설정
// 1가지 매핑 컨텍스트와 3가지 액션. 3인칭 템플릿에서 제공하고 있는 것들을 사용
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Input, Meta=(AllowPrivate="true"))
TObjectPtr<class UInputMappingContext> DefaultMappingContext;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, Meta = (AllowPrivate = "true"))
TObjectPtr<class UInputAction> JumpAction;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, Meta = (AllowPrivate = "true"))
TObjectPtr <class UInputAction> MoveAction;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, Meta = (AllowPrivate = "true"))
TObjectPtr <class UInputAction> LookAction;
// 각 입력 액션에 대해서 매핑된 함수
// FInputActionValue 구조체.
void Move(const FInputActionValue& Value);
void Look(const FInputActionValue& Value);
};
AABCharacterPlayer.h
// Fill out your copyright notice in the Description page of Project Settings.
#include "Character/ABCharacterPlayer.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/SpringArmComponent.h"
// 입력 설정 관련 헤더
#include "InputMappingContext.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h" // Subsystem's'임
AABCharacterPlayer::AABCharacterPlayer()
{
// 카메라
// 뼈대 오브젝트 생성
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
// 루트 컴포넌트에 부착
CameraBoom->SetupAttachment(RootComponent);
// 4미터 길이로 설정
CameraBoom->TargetArmLength = 400.0f;
// 회전 설정
CameraBoom->bUsePawnControlRotation = true;
// 카메라 생성
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
// 스프링 암에 부착
// 부착할 때 특정 위치를 지정하는 것이 아니라 소켓이라는 특별한 이름 지시자를 지정하면
// 스프링 암의 끝에 자동으로 붙음
// F12로 확인해보면 스프링 암의 소켓이름은 SpringEndpoint.
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
// 회전 설정
FollowCamera->bUsePawnControlRotation = false;
// Input(입력)
static ConstructorHelpers::FObjectFinder<UInputMappingContext> InputMappingContextRef(TEXT("/Script/EnhancedInput.InputMappingContext'/Game/ThirdPerson/Input/IMC_Default.IMC_Default'"));
if (nullptr != InputMappingContextRef.Object)
{
DefaultMappingContext = InputMappingContextRef.Object;
}
static ConstructorHelpers::FObjectFinder<UInputAction> InputActionMoveRef(TEXT("/Script/EnhancedInput.InputAction'/Game/ThirdPerson/Input/Actions/IA_Move.IA_Move'"));
if (InputActionMoveRef.Object)
{
MoveAction = InputActionMoveRef.Object;
}
static ConstructorHelpers::FObjectFinder<UInputAction> InputActionJumpRef(TEXT("/Script/EnhancedInput.InputAction'/Game/ThirdPerson/Input/Actions/IA_Jump.IA_Jump'"));
if (InputActionJumpRef.Object)
{
JumpAction = InputActionJumpRef.Object;
}
static ConstructorHelpers::FObjectFinder<UInputAction> InputActionLookRef(TEXT("/Script/EnhancedInput.InputAction'/Game/ThirdPerson/Input/Actions/IA_Look.IA_Look'"));
if (InputActionLookRef.Object)
{
LookAction = InputActionLookRef.Object;
}
}
void AABCharacterPlayer::BeginPlay()
{
Super::BeginPlay();
// 컨트롤러가 플레이어를 대상으로 설계된 전용 캐릭터 클래스이기 때문에 CastChecked 사용
APlayerController* PlayerController = CastChecked<APlayerController>(GetController());
// Subsystem이라는 InputSystem을 가져와서 매핑 컨텍스트 애샛을 추가
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
{
// 우선순위를 지정해 입력이 서로 겹칠 때도 우선순위가 높은 입력에 대해
// 액션들이 바인딩에서 수행할 수 있도록 지정할 수 있음
Subsystem->AddMappingContext(DefaultMappingContext, 0);
// 언제든지 자유롭게 RemoveMappingContext를 사용해 런타임에서 빼거나 추가할 수 있다.
//Subsystem->RemoveMappingContext(DefaultMappingContext);
}
}
// 입력 설정
void AABCharacterPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
// 헤더 추가
// EnhancedInputComponent를 사용하지 않은 경우 에러를 발생시키도록 CastChecked 함수 사용
// 반드시 EnhancedInputComponent를 사용하도록 함.
UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent);
// 정상적으로 캐스팅되었다면 함수와 바인딩
// Move와 Look은 직접 구현, Jump와 StopJumping은 캐릭터가 제공하는 함수와 연결
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);
}
// InputActionValue에서 XY값을 가져와 무브먼트 컴포넌트와 연결
// 실질적으로 캐릭터를 이동
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.Y);
AddMovementInput(RightDirection, MovementVector.X);
}
// 컨트롤러의 회전을 설정함으로써 스프링암이 해당 컨트롤러를 바라보도록 설정
void AABCharacterPlayer::Look(const FInputActionValue& Value)
{
FVector2D LookAxisVector = Value.Get<FVector2D>();
AddControllerYawInput(LookAxisVector.X);
AddControllerPitchInput(LookAxisVector.Y);
}
AABCharacterPlayer.cpp
레퍼런스를 가져온 ThirdPerson의 input 폴더를 ArenaBattle 폴더로 복사한 뒤 IMC_Default를 열어 매핑 값들을 복사한 액션값으로 변경
주소의 ThirdPerson을 ArenaBattle로 변경
빌드 후 실행해도 정상적으로 동작.
Move에서 Modifiers 추가. Swizzle Input Axis Values
구체적으로 값을 지정할 수 있음.
입력에서의 A/D, 즉 X축 값을 실제 캐릭터 이동에서는 Y축으로 변환, 입력에서의 W/S, Y축 값을 정면 방향 X축으로 사용하는 것으로 값 변경하는 것.
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);
// Modifiers에서 값을 변조했기 때문에 그대로 X, Y 적용
AddMovementInput(ForwardDirection, MovementVector.X);
AddMovementInput(RightDirection, MovementVector.Y);
}
원래는 AddMovementInput에 강제로 값을 반대로 넣었었지만 Modifier로 값을 변조했기 때문에 입력값과 상관없이 논리적으로 맞는 방향 그대로 사용하도록 로직 수정.
언리얼 엔진은 오른손 좌표계를 사용함.
X축: 월드의 앞/뒤(Forward/Backward)
Y축: 월드의 좌/우(Right/Left)
Z축: 월드의 위/아래(Up/Down)
키보드 입력 값
W/S: MovementVector.Y
A/D: MovementVector.X
에러목록
클래스 새로 추가하면 컴파일이 엄청 오래걸림 -> 컴파일이 완료되면 크래쉬
에픽 게임즈 런처 삭제 후 재설치 -> 클래스 생성 후 컴파일은 빨라짐 -> 잠시 뒤 또 다시 크래쉬 발생
메모리 부족인듯
이때 vs가 메모리를 3000메가나 먹고 있었음
안정된 상태에서는 1200~1600메가
컨트롤러와 게임모드를 유니코드로 인코딩 변경하려고 하니까 에러 발생
확인하고 다시 저장하려고 보면 유니코드로 변경되어있음
질문
이렇게 만들어진 블루프린트는 앞에서 만든 것들과 동일. -> C++로 기반을 충분히 다져놓고 이후 추가적인 로직은 블루프린트를 사용해 필요한 만큼만 최소 한도로 확장하도록 만드는 것이 효과적임.
그럼 캐릭터의 경우도 ThirdPerson 템플릿을 사용하는 것보다 직접 만들어서 사용하는 것이 좋은가? 상속받아서 사용하면 안되나?
클래스를 직접 만들 때 레퍼런스를 가져오는 원본 메쉬와 스켈레탈 등은 어떻게 만드는지? 그것도 직접 만드는지 애셋 등을 구해서 가져다 쓰면 되는지
플레이어의 이동을 구현할 때 유니티의 InputManager의 GetAxis와 작동원리가 유사한 것 같은데
유니티의 경우 Transform을 이용해 순간이동 시키거나 rigidbody를 사용하거나 InputManager를 사용하는 등 이동 구현 방법이 다양한데 언리얼은 그런게 없는지
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.Y);
AddMovementInput(RightDirection, MovementVector.X);
이 부분에서 z축 회전방향에 Y, X방향 힘을 곱해 움직이게 하는데 키보드는 입력 값이 1,0, -1로 들어오고 패드는 1~-1로 보간되며 들어오는지?
이득우의 언리얼 프로그래밍 Part2 - 언리얼 게임 프레임웍의 이해 강의 | 이득우 - 인프런
이득우 | 대기업 현업자들이 수강하는 언리얼 C++ 프로그래밍 전문 과정입니다. 언리얼 C++ 프로그래밍을 사용해 핵&슬래시 로그라이크 게임 예제를 처음부터 끝까지 체계적으로 제작하는 방법을
www.inflearn.com