- 정리
- 언리얼 캐릭터와 향상된 입력 시스템의 이해
- 액터와 컴포넌트 개념의 이해
- 컴포넌트 속성에 지정자를 추가해 블루프린트로 확장하는 방법의 이해
- 폰과 캐릭터를 구성하는 3대 구성 요소
- 언리얼 5.1에서 추가된 향상된 입력 시스템의 사용 방법
- 언리얼 캐릭터와 향상된 입력 시스템의 이해
- 액터의 구조
- 월드에 속한 콘텐츠의 기본 단위
- 트랜스폼을 가지며, 월드로부터 틱과 시간 서비스를 제공받는다.
- 액터는 논리적 개념일 뿐 컴포넌트를 감싸는 포장 박스에 불과
- 실질적인 구현은 컴포넌트가 진행하고 액터는 다수의 컴포넌트를 소유
- 다수의 컴포넌트를 대표하는 컴포넌트를 루트 컴포넌트(Root Component)라고 한다.
- 액터는 루트 컴포넌트를 가져야 하며, 루트 컴포넌트의 트랜스폼은 액터의 트랜스폼을 의미
- C++ 액터에서 컴포넌트의 생성
- 컴포넌트는 언리얼 오브젝트이므로 UPROPERTY를 설정하고 TObjectPtr로 포인터를 선언한다.
- 언리얼 5부터 헤더에 언리얼 오브젝트를 선언할 때 일반 포인터에서 TObjectPtr로 변경
- 컴포넌트의 등록
- CDO에서 생성한 컴포넌트는 자동으로 월드에 등록된다.
- NewObject로 생성한 컴포넌트는 반드시 등록절차를 거쳐야 한다.
- 등록된 컴포넌트는 월드의 기능을 사용할 수 있으며, 물리와 렌더링 처리에 합류한다.
- 컴포넌트의 확장 설계
- 에디터 편집 및 블루프린트로의 승계를 위한 설정
- UPROPERTY에 지정자(Specifier)를 설정할 수 있다.
- 컴포넌트 지정자
- Visible / Edit : 크게 객체타입과 값타입으로 사용
- Anywhere / DefaultsOnly / InstanceOInly : 에디터에서 편집 가능 영역
- BlueprintReadOnly / BlueprintReadWrite : 블루프린트로 확장시 일기 혹은 읽기쓰기 권한을 부여
- Category : 에디터 편집 영역(Detail)에서의 카테고리 지정
- 컴포넌트는 언리얼 오브젝트이므로 UPROPERTY를 설정하고 TObjectPtr로 포인터를 선언한다.
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를 통해 비주얼 스튜디오에서 빌드해서 실행시킨다.
- 해당 위치에 ABFountain이라는 프랍이 생성된 걸 볼 수 있다.
- 아까 만들어 둔 BP_Fountain 블루프린트를 실행한 후 Class Settings의 Parent Class를 Actor에서 방금 만든 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으로 등록시켜 준다.
- 이후 실행하면 움직이지 않고, 캐릭터가 보이지 않는데, 이는 실제로 캐릭터는 스폰되었지만 카메라 컴포넌트를 추가하지 않아서 캐릭터에 붙어 움직이지 않는 모습을 볼 수 있다.
#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
- 카메라 컴포넌트와 스프링 암 컴포넌트 추가하기
- 빌드후 실행
- 입력시스템의 동작 방식 (기존 버전)
- 플레이어의 입력은 컨트롤러를 통해 폰으로 전달됨
- 입력을 컨트롤러가 처리할 수도, 폰이 처리할 수도 있는데, 일반적으로는 폰이 처리하도록 설정
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부터 새롭게 도입
- 사용자의 입력 설정 변경에 유연하게 대처할 수 있도록 구조를 재수립
- 사용자 입력 처리를 네 단계로 세분화하고 각 설정을 독립적인 애셋으로 대체
- 향상된 입력시스템 동작 구성
- 사용자의 입력 데이터를 최종 함수에 매핑하는 과정을 체계적으로 구성
- 플랫폼에 따른 다양한 입력 장치의 설정
- 게임 패드용 입력 매핑 컨텍스트, 키보드용 입력 매핑 컨텍스트
- 입력 값의 변경
- 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
- 필요한 인풋 시스템에 대한 값들을 추가한다.
- 입력에서의 AD, 즉 X축 값을 실제 캐릭터 이동에서는 Y축으로 변환하고, 입력에서의 WS, 즉 Y축의 값을 실제 캐릭터 이동에서는 X축으로 변환한다라고 지정
- BP_ThirdPerson과 같이 동일하게 입력을 통한 움직임이 가능
해당 포스트는 인프런의 <이득우의 언리얼 프로그래밍 Part2 -언리얼 게임 프레임웍의 이해>
강의를 수강하고 정리한 내용입니다.
이득우의 언리얼 프로그래밍 Part2 - 언리얼 게임 프레임웍의 이해 | 이득우 - 인프런
청강문화산업대학교에서 언리얼 엔진, 게임 수학, UEFN 게임제작을 가르치고 있습니다. - 이득우의 언리얼 C++ 프로그래밍, 넥슨 코리아 공식 교육 교재 선정 2023 - 스마일게이트 언리얼 프로그래
www.inflearn.com
'공부 > 이득우의 언리얼 프로그래밍' 카테고리의 다른 글
[Study] Part 2 - 캐릭터 애니메이션 설정 (4/15) (2) | 2024.06.01 |
---|---|
[Study] Part 2 - 캐릭터 컨트롤 설정 (3/15) (0) | 2024.05.28 |
[Study] Part 2 - 언리얼 엔진 게임 제작 기초 (1/15) (0) | 2024.05.16 |
[Study] Part 1 - 언리얼 빌드 시스템 (15/15) (0) | 2024.05.12 |
[Study] Part 1 - 언리얼 오브젝트 관리 II - 패키지 (14/15) (0) | 2024.05.09 |