이 튜토리얼에서는 컴포넌트(Components) 를 생성하고, 계층구조에 배치하며, 게임 플레이 중에 제어하고, 이를 사용하여 솔리드 오브젝트가 포함된 월드에서 폰(Pawn) 을 이동하는 방법을 보여줍니다.
1. 컴포넌트 생성 및 어태치하기
언리얼 엔진 을 처음 사용하신다면 프로그래밍 퀵스타트를 먼저 읽어보세요. 이 튜토리얼에서는 여러분이 언리얼 에디터에서의 프로젝트 생성 및 프로젝트에 C++ 코드 추가, 입력 환경설정에 익숙하다고 가정합니다.
자신만의 폰 클래스 생성이나 입력 환경설정에 익숙하지 않다면 플레이어 입력 튜토리얼부터 시작하는 것이 좋습니다.
-
HowTo_Components
라는 시작용 콘텐츠로 새 C++ 프로젝트를 만드는 것부터 시작합니다. 이 프로젝트에 가장 먼저 추가해야 할 것은 컴포넌트를 보유하고 레벨을 이동하며 솔리드 오브젝트와 충돌하는 커스텀 폰입니다. 이 튜토리얼에서는CollidingPawn
이라고 명명하겠습니다. -
코드 에디터(일반적으로 Visual Studio 또는 Xcode )에서
CollidingPawn.h
를 열고 클래스 정의 맨 아래에 다음 코드를 추가합니다.UPROPERTY() class UParticleSystemComponent* OurParticleSystem;
이 변수는 나중에 생성할 파티클 시스템 컴포넌트(Particle System Component) 를 추적합니다. 추적할 변수를 만들지 않고도 컴포넌트를 생성할 수 있지만 코드에서 해당 컴포넌트를 사용하려면 클래스 멤버 변수에 저장해야 합니다.
-
이제
CollidingPawn.cpp
를 열고 여러 유용한 컴포넌트를 생성하고 계층구조로 배열하는 코드를 추가하여 생성자 함수ACollidingPawn::ACollidingPawn
을 편집할 수 있습니다. 물리적 월드와 상호작용하는 스피어 컴포넌트(Sphere Component), 콜리전 셰이프를 시각적으로 표현하는 스태틱 메시 컴포넌트(Static Mesh Component), 마음대로 켜거나 끌 수 있는 파티클 시스템 컴포넌트(Particle System Component), 그리고 게임 내 관점을 제어하기 위해 카메라 컴포넌트(Camera Component) 를 어태치하는 데 사용할 수 있는 스프링 암 컴포넌트(Spring Arm Component) 를 생성할 것입니다. 그 전에, 이러한 컴포넌트를 사용하는 데 필요한 헤더 파일을 포함해야 합니다. 클래스 헤더 파일이 포함된 줄 아래에 다음을 추가할 수 있습니다.#include "UObject/ConstructorHelpers.h" #include "Particles/ParticleSystemComponent.h" #include "Components/SphereComponent.h" #include "Camera/CameraComponent.h" #include "GameFramework/SpringArmComponent.h"
-
계층구조의 루트가 될 컴포넌트를 결정합니다. 이 튜토리얼에서는 게임 월드와 상호작용하고 충돌할 수 있는 물리적 존재인 스피어 컴포넌트를 사용합니다. 액터 는 계층구조에 여러 개의 피직스 활성화 컴포넌트를 가질 수 있지만, 이 튜토리얼에서는 하나만 필요합니다.
// 루트 컴포넌트는 피직스에 반응하는 스피어입니다. USphereComponent* SphereComponent = CreateDefaultSubobject<USphereComponent>(TEXT("RootComponent")); RootComponent = SphereComponent; SphereComponent->InitSphereRadius(40.0f); SphereComponent->SetCollisionProfileName(TEXT("Pawn"));
-
다음으로 반경이 50인 스태틱 메시 에셋을 사용하여 표시되는 스피어를 생성하고 어태치하겠습니다. 방금 만든 반경 40의 스피어 컴포넌트와 완벽하게 일치하지 않으므로 스케일을 80%로 줄입니다. 또한 중심이 스피어 컴포넌트의 중심과 일치하도록 하기 위해 40유닛 아래로 이동해야 합니다.
// 스피어의 위치를 확인할 수 있도록 메시 컴포넌트를 생성하고 배치합니다. UStaticMeshComponent* SphereVisual = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("VisualRepresentation")); SphereVisual->SetupAttachment(RootComponent); static ConstructorHelpers::FObjectFinder<UStaticMesh> SphereVisualAsset(TEXT("/Game/StarterContent/Shapes/Shape_Sphere.Shape_Sphere")); if (SphereVisualAsset.Succeeded()) { SphereVisual->SetStaticMesh(SphereVisualAsset.Object); SphereVisual->SetRelativeLocation(FVector(0.0f, 0.0f, -40.0f)); SphereVisual->SetWorldScale3D(FVector(0.8f)); }
이 예시의 스태틱 메시에 대한 스태틱 메시와 같은 하드 코딩 에셋 경로는 일반적으로 모범 사례로 간주되지 않습니다. 보통 생성자에서 컴포넌트를 생성하고, 편집 가능하게 만든 다음, 언리얼 에디터에서 블루프린트 에셋을 생성하고 거기에 스태틱 메시 에셋을 구성합니다. 그러나 코드에서 직접 할 수도 있으며 프로그래머가 새로운 기능을 디버깅하거나 구축하는 데에는 더 빠를 수 있습니다.
-
이제 비활성 파티클 시스템 컴포넌트를 계층구조에 어태치할 수 있습니다. 코드에서 이 컴포넌트를 조작할 수 있으며 나중에 이를 켜거나 끄는 입력을 구성합니다. 파티클 시스템 컴포넌트는 루트가 아닌 스태틱 메시 컴포넌트에 직접 어태치되어 있습니다. 또한 플레이 중에 더 잘 보이도록 메시의 중앙 하단에서 약간 오프셋되어 있습니다.
// 활성화 또는 비활성화할 수 있는 파티클 시스템을 생성합니다. OurParticleSystem = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("MovementParticles")); OurParticleSystem->SetupAttachment(SphereVisual); OurParticleSystem->bAutoActivate = false; OurParticleSystem->SetRelativeLocation(FVector(-20.0f, 0.0f, 20.0f)); static ConstructorHelpers::FObjectFinder<UParticleSystem> ParticleAsset(TEXT("/Game/StarterContent/Particles/P_Fire.P_Fire")); if (ParticleAsset.Succeeded()) { OurParticleSystem->SetTemplate(ParticleAsset.Object); }
-
스프링 암 컴포넌트는 카메라가 따라가는 폰보다 더 느리게 가속 및 감속할 수 있도록 하여 카메라의 어태치먼트 지점이 더 부드러워집니다. 또한 삼인칭 게임에서 플레이어가 코너로 돌아갈 때와 같은 상황에서 카메라가 솔리드 오브젝트를 통과하지 못하도록 하는 기능도 있습니다. 꼭 필요한 것은 아니지만 스프링 암 컴포넌트를 사용하면 부드러운 카메라 워크를 쉽고 빠르게 게임에 적용할 수 있습니다.
// 스프링 암을 사용하여 카메라가 부드럽고 자연스러운 느낌으로 움직이게 합니다. USpringArmComponent* SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraAttachmentArm")); SpringArm->SetupAttachment(RootComponent); SpringArm->SetRelativeRotation(FRotator(-45.f, 0.f, 0.f)); SpringArm->TargetArmLength = 400.0f; SpringArm->bEnableCameraLag = true; SpringArm->CameraLagSpeed = 3.0f;
-
실제 카메라 컴포넌트는 생성하기 쉽고, 이번 사용 사례에는 특별한 세팅이 필요하지 않습니다. 스프링 암 컴포넌트는 컴포넌트 베이스에 직접 어태치하는 것 외에도 특수 기본 소켓에 어태치할 수 있습니다.
// 카메라를 만들고 스프링 암에 어태치합니다. UCameraComponent* Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("ActualCamera")); Camera->SetupAttachment(SpringArm, USpringArmComponent::SocketName);
-
이제 컴포넌트가 생성되고 어태치되었으므로 이 폰이 디폴트 플레이어에 의해 제어되도록 설정해야 합니다. 다음 코드만 있으면 됩니다.
// 디폴트 플레이어를 제어합니다. AutoPossessPlayer = EAutoReceiveInput::Player0;
이제 새 폰에 유용한 컴포넌트 컬렉션이 어태치되어 사용자 제어를 위해 구성할 준비가 되었습니다. 이제 언리얼 에디터로 돌아갑니다.
작업 중인 코드
CollidingPawn.h
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "CollidingPawn.generated.h"
UCLASS()
class HOWTO_COMPONENTS_API ACollidingPawn : public APawn
{
GENERATED_BODY()
public:
// 이 폰 프로퍼티의 디폴트값을 설정합니다.
ACollidingPawn();
protected:
// 게임 시작 시 또는 스폰 시 호출
virtual void BeginPlay() override;
public:
// 모든 프레임에서 호출
virtual void Tick( float DeltaSeconds ) override;
// 함수를 입력에 바인딩하기 위해 호출
virtual void SetupPlayerInputComponent(class UInputComponent* InInputComponent) override;
UPROPERTY()
class UParticleSystemComponent* OurParticleSystem;
};
CollidingPawn.cpp
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#include "CollidingPawn.h"
#include "UObject/ConstructorHelpers.h"
#include "Particles/ParticleSystemComponent.h"
#include "Components/SphereComponent.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/SpringArmComponent.h"
// 디폴트값 설정
ACollidingPawn::ACollidingPawn()
{
// 이 폰이 프레임마다 Tick()을 호출하도록 설정합니다. 필요 없는 경우 퍼포먼스 향상을 위해 이 설정을 끌 수 있습니다.
PrimaryActorTick.bCanEverTick = true;
// 루트 컴포넌트는 피직스에 반응하는 스피어입니다.
USphereComponent* SphereComponent = CreateDefaultSubobject<USphereComponent>(TEXT("RootComponent"));
RootComponent = SphereComponent;
SphereComponent->InitSphereRadius(40.0f);
SphereComponent->SetCollisionProfileName(TEXT("Pawn"));
// 스피어의 위치를 확인할 수 있도록 메시 컴포넌트를 생성하고 배치합니다.
UStaticMeshComponent* SphereVisual = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("VisualRepresentation"));
SphereVisual->SetupAttachment(RootComponent);
static ConstructorHelpers::FObjectFinder<UStaticMesh> SphereVisualAsset(TEXT("/Game/StarterContent/Shapes/Shape_Sphere.Shape_Sphere"));
if (SphereVisualAsset.Succeeded())
{
SphereVisual->SetStaticMesh(SphereVisualAsset.Object);
SphereVisual->SetRelativeLocation(FVector(0.0f, 0.0f, -40.0f));
SphereVisual->SetWorldScale3D(FVector(0.8f));
}
// 활성화 또는 비활성화할 수 있는 파티클 시스템을 생성합니다.
OurParticleSystem = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("MovementParticles"));
OurParticleSystem->SetupAttachment(SphereVisual);
OurParticleSystem->bAutoActivate = false;
OurParticleSystem->SetRelativeLocation(FVector(-20.0f, 0.0f, 20.0f));
static ConstructorHelpers::FObjectFinder<UParticleSystem> ParticleAsset(TEXT("/Game/StarterContent/Particles/P_Fire.P_Fire"));
if (ParticleAsset.Succeeded())
{
OurParticleSystem->SetTemplate(ParticleAsset.Object);
}
// 스프링 암을 사용하여 카메라가 부드럽고 자연스러운 느낌으로 움직이게 합니다.
USpringArmComponent* SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraAttachmentArm"));
SpringArm->SetupAttachment(RootComponent);
SpringArm->SetRelativeRotation(FRotator(-45.f, 0.f, 0.f));
SpringArm->TargetArmLength = 400.0f;
SpringArm->bEnableCameraLag = true;
SpringArm->CameraLagSpeed = 3.0f;
// 카메라를 만들고 스프링 암에 어태치합니다.
UCameraComponent* Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("ActualCamera"));
Camera->SetupAttachment(SpringArm, USpringArmComponent::SocketName);
// 디폴트 플레이어를 제어합니다.
AutoPossessPlayer = EAutoReceiveInput::Player0;
}
// 게임 시작 시 또는 스폰 시 호출
void ACollidingPawn::BeginPlay()
{
Super::BeginPlay();
}
// 모든 프레임에서 호출
void ACollidingPawn::Tick( float DeltaTime )
{
Super::Tick( DeltaTime );
}
// 함수를 입력에 바인딩하기 위해 호출
void ACollidingPawn::SetupPlayerInputComponent(class UInputComponent* InInputComponent)
{
Super::SetupPlayerInputComponent(InInputComponent);
}
2. 입력 환경설정 및 폰 이동 컴포넌트 생성하기
-
언리얼 에디터로 돌아가서 프로젝트의 입력 세팅을 구성할 차례입니다. 이 세팅은 편집(Edit) 드롭다운 메뉴의 프로젝트 세팅(Project Settings) 에 있습니다.
이동한 후 왼쪽 패널의 엔진(Engine) 섹션에서 입력(Input) 을 선택할 수 있습니다. 파티클 시스템 토글을 설정하기 위한 액션 매핑(Action Mapping), 폰 을 이동하기 위한 두 개의 축 매핑(Axis Mappings), 그리고 폰을 회전하기 위한 하나의 축 매핑(Axis Mapping) 이 필요합니다.
액션 매핑 ParticleToggle 스페이스 바 축 매핑 MoveForward W 1.0 S -1.0 MoveRight A -1.0 D 1.0 Turn 마우스 X 1.0 -
폰 클래스에서 모든 이동을 직접 처리하는 것이 아니라 무브먼트 컴포넌트(Movement Component) 를 생성하여 관리할 것입니다. 이 튜토리얼에서는 폰 이동 컴포넌트(Pawn Movement Component) 클래스를 확장하겠습니다. 파일(File) 드롭다운 메뉴에서 프로젝트에 코드 추가(Add Code to Project) 명령을 선택하는 것부터 시작합니다.
폰 클래스와 달리 폰 이동 컴포넌트는 기본적으로 표시되지 않습니다. 찾으려면 모든 클래스 표시(Show All Classes) 옵션을 체크해야 합니다.
검색창에 'movement'를 입력하면 후보를 빠르게 좁힐 수 있습니다.
폰 이동 컴포넌트에는 일반적인 피직스 함수 기능에 도움이 되는 몇 가지 강력한 기능이 있는데, 여러 폰 타입 간에 이동 코드를 공유하는 좋은 방법입니다. 컴포넌트를 사용하여 함수 기능을 분리하는 것은 프로젝트가 커짐에 따라 폰이 더욱 복잡해질 때 혼란을 줄이는 좋은 방법입니다.
폰 클래스를
CollidingPawn
이라고 했으니, 이것은CollidingPawnMovementComponent
로 부르겠습니다.
방금 입력 환경설정을 정의하고 커스텀 폰 이동 컴포넌트를 생성했습니다. 코드 에디터로 돌아가 폰 이동 컴포넌트 사용 시 폰이 어떻게 움직일지 정의하는 코드를 작성할 준비가 되었습니다.
3. 폰 이동 컴포넌트의 행동 코딩하기
-
이제 코드 에디터로 돌아가서 커스텀 폰 이동 컴포넌트를 프로그래밍할 수 있습니다. 작성해야 하는 것은 각 프레임을 이동하는 방법을 알려주는
TickComponent
함수(액터의Tick
함수와 유사)뿐입니다.CollidingPawnMovementComponent.h
에서 클래스 정의의TickComponent
를 오버라이드해야 합니다.public: virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override;
CollidingPawnMovementComponent.cpp
에서 이 함수를 정의합니다.void UCollidingPawnMovementComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); // 모든 것이 여전히 유효한지, 이동이 가능한지 확인하세요. if (!PawnOwner || !UpdatedComponent || ShouldSkipUpdate(DeltaTime)) { return; } // ACollidingPawn::Tick에서 설정한 이동 벡터를 가져온 다음 지웁니다. FVector DesiredMovementThisFrame = ConsumeInputVector().GetClampedToMaxSize(1.0f) * DeltaTime * 150.0f; if (!DesiredMovementThisFrame.IsNearlyZero()) { FHitResult Hit; SafeMoveUpdatedComponent(DesiredMovementThisFrame, UpdatedComponent->GetComponentRotation(), true, Hit); // 무언가에 부딪힌 경우 슬라이드하도록 합니다. if (Hit.IsValidBlockingHit()) { SlideAlongSurface(DesiredMovementThisFrame, 1.f - Hit.Time, Hit.Normal, Hit); } } };
이 코드를 사용하면 폰이 월드 주위로 부드럽게 움직이며 적절한 경우 표면에서 미끄러집니다. 폰에는 중력이 적용되지 않으며 최대 속도는 초당 150 언리얼 유닛 으로 하드 코딩되어 있습니다.
이
TickComponent
함수는UPawnMovementComponent
클래스가 제공하는 몇 가지 강력한 기능을 활용합니다.-
ConsumeInputVector
는 이동 입력을 저장하는 데 사용할 기본 제공 변수의 값을 보고하고 지웁니다. -
SafeMoveUpdatedComponent
는 언리얼 엔진 피직스를 사용하여 솔리드 장벽을 통과하지 않고 폰 이동 컴포넌트를 움직입니다. -
SlideAlongSurface
는 단순히 제자리에 멈추고 벽이나 경사로에 달라붙는 것이 아니라 이동으로 인해 충돌이 발생할 때 벽이나 경사로와 같은 충돌 표면을 따라 부드럽게 미끄러지는 것과 관련된 계산 및 피직스를 처리합니다.
폰 이동 컴포넌트에는 살펴볼 기능이 더 많이 있지만, 이 튜토리얼의 범위에는 필요하지 않습니다. 플로팅 폰 이동(Floating Pawn Movement), 관전자 폰 이동(Spectator Pawn Movement) 또는 캐릭터 무브먼트 컴포넌트(Character Movement Component) 같은 다른 클래스를 살펴보면 추가적인 사용 예시와 아이디어를 얻을 수 있습니다.
-
폰 이동 컴포넌트의 동작이 정의되었으므로 이제 커스텀 폰 클래스에서 모든 것을 하나로 묶는 코드를 작성할 수 있습니다.
작업 중인 코드
CollidingPawn.h
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "CollidingPawn.generated.h"
UCLASS()
class HOWTO_COMPONENTS_API ACollidingPawn : public APawn
{
GENERATED_BODY()
public:
// 이 폰 프로퍼티의 디폴트값을 설정합니다.
ACollidingPawn();
protected:
// 게임 시작 시 또는 스폰 시 호출
virtual void BeginPlay() override;
public:
// 모든 프레임에서 호출
virtual void Tick( float DeltaSeconds ) override;
// 함수를 입력에 바인딩하기 위해 호출
virtual void SetupPlayerInputComponent(class UInputComponent* InInputComponent) override;
UPROPERTY()
class UParticleSystemComponent* OurParticleSystem;
};
CollidingPawn.cpp
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#include "CollidingPawn.h"
#include "UObject/ConstructorHelpers.h"
#include "Particles/ParticleSystemComponent.h"
#include "Components/SphereComponent.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/SpringArmComponent.h"
// 디폴트값 설정
ACollidingPawn::ACollidingPawn()
{
// 이 폰이 프레임마다 Tick()을 호출하도록 설정합니다. 필요 없는 경우 퍼포먼스 향상을 위해 이 설정을 끌 수 있습니다.
PrimaryActorTick.bCanEverTick = true;
// 루트 컴포넌트는 피직스에 반응하는 스피어입니다.
USphereComponent* SphereComponent = CreateDefaultSubobject<USphereComponent>(TEXT("RootComponent"));
RootComponent = SphereComponent;
SphereComponent->InitSphereRadius(40.0f);
SphereComponent->SetCollisionProfileName(TEXT("Pawn"));
// 스피어의 위치를 확인할 수 있도록 메시 컴포넌트를 생성하고 배치합니다.
UStaticMeshComponent* SphereVisual = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("VisualRepresentation"));
SphereVisual->SetupAttachment(RootComponent);
static ConstructorHelpers::FObjectFinder<UStaticMesh> SphereVisualAsset(TEXT("/Game/StarterContent/Shapes/Shape_Sphere.Shape_Sphere"));
if (SphereVisualAsset.Succeeded())
{
SphereVisual->SetStaticMesh(SphereVisualAsset.Object);
SphereVisual->SetRelativeLocation(FVector(0.0f, 0.0f, -40.0f));
SphereVisual->SetWorldScale3D(FVector(0.8f));
}
// 활성화 또는 비활성화할 수 있는 파티클 시스템을 생성합니다.
OurParticleSystem = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("MovementParticles"));
OurParticleSystem->SetupAttachment(SphereVisual);
OurParticleSystem->bAutoActivate = false;
OurParticleSystem->SetRelativeLocation(FVector(-20.0f, 0.0f, 20.0f));
static ConstructorHelpers::FObjectFinder<UParticleSystem> ParticleAsset(TEXT("/Game/StarterContent/Particles/P_Fire.P_Fire"));
if (ParticleAsset.Succeeded())
{
OurParticleSystem->SetTemplate(ParticleAsset.Object);
}
// 스프링 암을 사용하여 카메라가 부드럽고 자연스러운 느낌으로 움직이게 합니다.
USpringArmComponent* SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraAttachmentArm"));
SpringArm->SetupAttachment(RootComponent);
SpringArm->SetRelativeRotation(FRotator(-45.f, 0.f, 0.f));
SpringArm->TargetArmLength = 400.0f;
SpringArm->bEnableCameraLag = true;
SpringArm->CameraLagSpeed = 3.0f;
// 카메라를 만들고 스프링 암에 어태치합니다.
UCameraComponent* Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("ActualCamera"));
Camera->SetupAttachment(SpringArm, USpringArmComponent::SocketName);
// 디폴트 플레이어를 제어합니다.
AutoPossessPlayer = EAutoReceiveInput::Player0;
}
// 게임 시작 시 또는 스폰 시 호출
void ACollidingPawn::BeginPlay()
{
Super::BeginPlay();
}
// 모든 프레임에서 호출
void ACollidingPawn::Tick( float DeltaTime )
{
Super::Tick( DeltaTime );
}
// 함수를 입력에 바인딩하기 위해 호출
void ACollidingPawn::SetupPlayerInputComponent(class UInputComponent* InInputComponent)
{
Super::SetupPlayerInputComponent(InInputComponent);
}
CollidingPawnMovementComponent.h
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PawnMovementComponent.h"
#include "CollidingPawnMovementComponent.generated.h"
/**
*
*/
UCLASS()
class HOWTO_COMPONENTS_API UCollidingPawnMovementComponent : public UPawnMovementComponent
{
GENERATED_BODY()
public:
virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override;
};
CollidingPawnMovementComponent.cpp
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#include "CollidingPawnMovementComponent.h"
void UCollidingPawnMovementComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
// 모든 것이 여전히 유효한지, 이동이 가능한지 확인하세요.
if (!PawnOwner || !UpdatedComponent || ShouldSkipUpdate(DeltaTime))
{
return;
}
// ACollidingPawn::Tick에서 설정한 이동 벡터를 가져온 다음 지웁니다.
FVector DesiredMovementThisFrame = ConsumeInputVector().GetClampedToMaxSize(1.0f) * DeltaTime * 150.0f;
if (!DesiredMovementThisFrame.IsNearlyZero())
{
FHitResult Hit;
SafeMoveUpdatedComponent(DesiredMovementThisFrame, UpdatedComponent->GetComponentRotation(), true, Hit);
// 무언가에 부딪힌 경우 슬라이드하도록 합니다.
if (Hit.IsValidBlockingHit())
{
SlideAlongSurface(DesiredMovementThisFrame, 1.f - Hit.Time, Hit.Normal, Hit);
}
}
};
4. 폰과 컴포넌트를 함께 사용하기
-
커스텀 폰 이동 컴포넌트를 사용하려면 먼저 폰 클래스에 변수를 추가하여 추적해야 합니다.
CollidingPawn.h
의 클래스 정의 하단의OurParticleSystem
변수를 추가한 위치 근처에 다음을 추가해야 합니다.UPROPERTY() class UCollidingPawnMovementComponent* OurMovementComponent;
-
추적할 위치가 있으면 새 변수에 저장할 충돌하는 폰 이동 컴포넌트(Colliding Pawn Movement Component) 를 생성해야 하므로,
CollidingPawn.cpp
를 열고 파일 상단,GameFramework/Pawn.h
줄이 포함된 줄 아래에 다음을 추가하여 코드가 새 클래스를 참조할 수 있도록 하겠습니다.#include "CollidingPawnMovementComponent.h"
포함된 마지막 헤더 파일이 'generated.h'로 끝나는 파일인지 확인하세요. 그렇지 않은 경우 컴파일러 오류가 발생할 수 있습니다.
폰 이동 컴포넌트를 생성하고 폰과 연결하는 것은 간단합니다.
ACollidingPawn::ACollidingPawn
하단에 다음 코드를 추가합니다.// 이동 컴포넌트의 인스턴스를 만들고 루트를 업데이트하도록 지시합니다. OurMovementComponent = CreateDefaultSubobject<UCollidingPawnMovementComponent>(TEXT("CustomMovementComponent")); OurMovementComponent->UpdatedComponent = RootComponent;
지금까지 본 다른 컴포넌트와 달리 이 컴포넌트는 자체 컴포넌트 계층구조에 어태치할 필요가 없습니다. 이는 다른 컴포넌트들이 본질적으로 물리적 위치가 필요한 모든 유형의 씬 컴포넌트 이기 때문입니다. 그러나 이동 컨트롤러(Movement Controller) 는 씬 컴포넌트가 아니며 물리적 오브젝트를 나타내지 않으므로 물리적 위치에 존재하거나 다른 컴포넌트에 물리적으로 어태치된다는 개념이 적용되지 않습니다.
-
폰에는
GetMovementComponent
라는 함수가 있으며, 폰이 현재 사용하고 있는 폰 이동 컴포넌트에 액세스할 수 있도록 엔진의 다른 클래스를 활성화하는 데 사용됩니다. 커스텀 폰 이동 컴포넌트를 반환하도록 이 함수를 오버라이드해야 합니다.CollidingPawn.h
의 클래스 정의에 다음을 추가합니다.virtual UPawnMovementComponent* GetMovementComponent() const override;
그리고
CollidingPawn.cpp
에서 오버라이드된 함수의 정의를 다음과 같이 추가합니다.UPawnMovementComponent* ACollidingPawn::GetMovementComponent() const { return OurMovementComponent; }
-
새로운 폰 이동 컴포넌트 구성을 사용해 폰이 수신할 입력을 처리하는 코드를 생성할 수 있습니다.
CollidingPawn.h
의 클래스 정의에 몇 가지 함수를 선언하는 것부터 시작합니다.void MoveForward(float AxisValue); void MoveRight(float AxisValue); void Turn(float AxisValue); void ParticleToggle();
CollidingPawn.cpp
에서 이 함수들의 정의를 다음과 같이 추가합니다.void ACollidingPawn::MoveForward(float AxisValue) { if (OurMovementComponent && (OurMovementComponent->UpdatedComponent == RootComponent)) { OurMovementComponent->AddInputVector(GetActorForwardVector() * AxisValue); } } void ACollidingPawn::MoveRight(float AxisValue) { if (OurMovementComponent && (OurMovementComponent->UpdatedComponent == RootComponent)) { OurMovementComponent->AddInputVector(GetActorRightVector() * AxisValue); } } void ACollidingPawn::Turn(float AxisValue) { FRotator NewRotation = GetActorRotation(); NewRotation.Yaw += AxisValue; SetActorRotation(NewRotation); } void ACollidingPawn::ParticleToggle() { if (OurParticleSystem && OurParticleSystem->Template) { OurParticleSystem->ToggleActive(); } }
-
남은 것은 함수를 입력 이벤트에 바인딩하는 것뿐입니다.
ACollidingPawn::SetupPlayerInputComponent
에 다음 코드를 추가합니다.InInputComponent->BindAction("ParticleToggle", IE_Pressed, this, &ACollidingPawn::ParticleToggle); InInputComponent->BindAxis("MoveForward", this, &ACollidingPawn::MoveForward); InInputComponent->BindAxis("MoveRight", this, &ACollidingPawn::MoveRight); InInputComponent->BindAxis("Turn", this, &ACollidingPawn::Turn);
-
프로그래밍이 완료되었으므로 이제 언리얼 에디터로 돌아가서 컴파일(Compile) 버튼을 눌러 변경사항을 로드할 수 있습니다.
프로그래밍 작업이 완료되었으므로 이제 커스텀 폰을 월드에 배치하고 이동할 수 있습니다.
완료된 코드
CollidingPawn.h
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "CollidingPawn.generated.h"
UCLASS()
class HOWTO_COMPONENTS_API ACollidingPawn : public APawn
{
GENERATED_BODY()
public:
// 이 폰 프로퍼티의 디폴트값을 설정합니다.
ACollidingPawn();
protected:
// 게임 시작 시 또는 스폰 시 호출
virtual void BeginPlay() override;
public:
// 모든 프레임에서 호출
virtual void Tick( float DeltaSeconds ) override;
// 함수를 입력에 바인딩하기 위해 호출
virtual void SetupPlayerInputComponent(class UInputComponent* InInputComponent) override;
UPROPERTY()
class UParticleSystemComponent* OurParticleSystem;
UPROPERTY()
class UCollidingPawnMovementComponent* OurMovementComponent;
virtual UPawnMovementComponent* GetMovementComponent() const override;
void MoveForward(float AxisValue);
void MoveRight(float AxisValue);
void Turn(float AxisValue);
void ParticleToggle();
};
CollidingPawn.cpp
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#include "CollidingPawn.h"
#include "CollidingPawnMovementComponent.h"
#include "UObject/ConstructorHelpers.h"
#include "Particles/ParticleSystemComponent.h"
#include "Components/SphereComponent.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/SpringArmComponent.h"
// 디폴트값 설정
ACollidingPawn::ACollidingPawn()
{
// 이 폰이 프레임마다 Tick()을 호출하도록 설정합니다. 필요 없는 경우 퍼포먼스 향상을 위해 이 설정을 끌 수 있습니다.
PrimaryActorTick.bCanEverTick = true;
// 루트 컴포넌트는 피직스에 반응하는 스피어입니다.
USphereComponent* SphereComponent = CreateDefaultSubobject<USphereComponent>(TEXT("RootComponent"));
RootComponent = SphereComponent;
SphereComponent->InitSphereRadius(40.0f);
SphereComponent->SetCollisionProfileName(TEXT("Pawn"));
// 스피어의 위치를 확인할 수 있도록 메시 컴포넌트를 생성하고 배치합니다.
UStaticMeshComponent* SphereVisual = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("VisualRepresentation"));
SphereVisual->SetupAttachment(RootComponent);
static ConstructorHelpers::FObjectFinder<UStaticMesh> SphereVisualAsset(TEXT("/Game/StarterContent/Shapes/Shape_Sphere.Shape_Sphere"));
if (SphereVisualAsset.Succeeded())
{
SphereVisual->SetStaticMesh(SphereVisualAsset.Object);
SphereVisual->SetRelativeLocation(FVector(0.0f, 0.0f, -40.0f));
SphereVisual->SetWorldScale3D(FVector(0.8f));
}
// 활성화 또는 비활성화할 수 있는 파티클 시스템을 생성합니다.
OurParticleSystem = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("MovementParticles"));
OurParticleSystem->SetupAttachment(SphereVisual);
OurParticleSystem->bAutoActivate = false;
OurParticleSystem->SetRelativeLocation(FVector(-20.0f, 0.0f, 20.0f));
static ConstructorHelpers::FObjectFinder<UParticleSystem> ParticleAsset(TEXT("/Game/StarterContent/Particles/P_Fire.P_Fire"));
if (ParticleAsset.Succeeded())
{
OurParticleSystem->SetTemplate(ParticleAsset.Object);
}
// 스프링 암을 사용하여 카메라가 부드럽고 자연스러운 느낌으로 움직이게 합니다.
USpringArmComponent* SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraAttachmentArm"));
SpringArm->SetupAttachment(RootComponent);
SpringArm->SetRelativeRotation(FRotator(-45.f, 0.f, 0.f));
SpringArm->TargetArmLength = 400.0f;
SpringArm->bEnableCameraLag = true;
SpringArm->CameraLagSpeed = 3.0f;
// 카메라를 만들고 스프링 암에 어태치합니다.
UCameraComponent* Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("ActualCamera"));
Camera->SetupAttachment(SpringArm, USpringArmComponent::SocketName);
// 디폴트 플레이어를 제어합니다.
AutoPossessPlayer = EAutoReceiveInput::Player0;
// 이동 컴포넌트의 인스턴스를 만들고 루트 컴포넌트를 업데이트하도록 지시합니다.
OurMovementComponent = CreateDefaultSubobject<UCollidingPawnMovementComponent>(TEXT("CustomMovementComponent"));
OurMovementComponent->UpdatedComponent = RootComponent;
}
// 게임 시작 시 또는 스폰 시 호출
void ACollidingPawn::BeginPlay()
{
Super::BeginPlay();
}
// 모든 프레임에서 호출
void ACollidingPawn::Tick( float DeltaTime )
{
Super::Tick( DeltaTime );
}
// 함수를 입력에 바인딩하기 위해 호출
void ACollidingPawn::SetupPlayerInputComponent(class UInputComponent* InInputComponent)
{
Super::SetupPlayerInputComponent(InInputComponent);
InInputComponent->BindAction("ParticleToggle", IE_Pressed, this, &ACollidingPawn::ParticleToggle);
InInputComponent->BindAxis("MoveForward", this, &ACollidingPawn::MoveForward);
InInputComponent->BindAxis("MoveRight", this, &ACollidingPawn::MoveRight);
InInputComponent->BindAxis("Turn", this, &ACollidingPawn::Turn);
}
UPawnMovementComponent* ACollidingPawn::GetMovementComponent() const
{
return OurMovementComponent;
}
void ACollidingPawn::MoveForward(float AxisValue)
{
if (OurMovementComponent && (OurMovementComponent->UpdatedComponent == RootComponent))
{
OurMovementComponent->AddInputVector(GetActorForwardVector() * AxisValue);
}
}
void ACollidingPawn::MoveRight(float AxisValue)
{
if (OurMovementComponent && (OurMovementComponent->UpdatedComponent == RootComponent))
{
OurMovementComponent->AddInputVector(GetActorRightVector() * AxisValue);
}
}
void ACollidingPawn::Turn(float AxisValue)
{
FRotator NewRotation = GetActorRotation();
NewRotation.Yaw += AxisValue;
SetActorRotation(NewRotation);
}
void ACollidingPawn::ParticleToggle()
{
if (OurParticleSystem && OurParticleSystem->Template)
{
OurParticleSystem->ToggleActive();
}
}
CollidingPawnMovementComponent.h
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PawnMovementComponent.h"
#include "CollidingPawnMovementComponent.generated.h"
/**
*
*/
UCLASS()
class HOWTO_COMPONENTS_API UCollidingPawnMovementComponent : public UPawnMovementComponent
{
GENERATED_BODY()
public:
virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override;
};
CollidingPawnMovementComponent.cpp
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#include "CollidingPawnMovementComponent.h"
void UCollidingPawnMovementComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
// 모든 것이 여전히 유효한지, 이동이 가능한지 확인하세요.
if (!PawnOwner || !UpdatedComponent || ShouldSkipUpdate(DeltaTime))
{
return;
}
// ACollidingPawn::Tick에서 설정한 이동 벡터를 가져온 다음 지웁니다.
FVector DesiredMovementThisFrame = ConsumeInputVector().GetClampedToMaxSize(1.0f) * DeltaTime * 150.0f;
if (!DesiredMovementThisFrame.IsNearlyZero())
{
FHitResult Hit;
SafeMoveUpdatedComponent(DesiredMovementThisFrame, UpdatedComponent->GetComponentRotation(), true, Hit);
// 무언가에 부딪힌 경우 슬라이드하도록 합니다.
if (Hit.IsValidBlockingHit())
{
SlideAlongSurface(DesiredMovementThisFrame, 1.f - Hit.Time, Hit.Normal, Hit);
}
}
};
5. 에디터에서 플레이하기
-
언리얼 에디터에서 컴파일(Compile) 버튼을 눌러 코드 변경사항을 로드할 수 있습니다.
-
CollidingPawn 의 인스턴스를 월드에 드롭해야 합니다. 콘텐츠 브라우저의 'C++ Classes/HowTo_Components/CollidingPawn' 아래에서 클래스를 찾을 수 있습니다.
-
플레이(Play) 를 누르면 스피어를 WASD로 움직이고, 마우스로 회전할 수 있으며, 테이블과 의자 같은 월드 오브젝트나 월드에 배치하려는 다른 물리적 오브젝트를 따라 충돌하고 미끄러집니다. 스페이스 바로 불을 붙이거나 끌 수도 있습니다!
언리얼 엔진 은 다양한 컴포넌트 를 제공합니다. 여기에서는 몇 가지 일반적인 컴포넌트만 간단히 다루었습니다. 기본 제공 컴포넌트를 탐색하거나 직접 작성해 보세요! 유연하고 강력하며, 프로젝트 코드를 체계적으로 정리하고 재사용할 수 있도록 해줍니다.
6. 직접 실습해 보세요!
여기에서 배운 것을 활용하여 다음과 같은 작업을 해보세요.
- 부모 항목을 자동으로 선회하는 컴포넌트 를 생성합니다.
- 최대 3개의 자손을 생성하는 컴포넌트를 빌드합니다. 각 자손은 설정된 시간이 지나면 스스로 사라집니다.
- 컴포넌트를 통해 완전한 액터 를 다른 액터에 어태치하는 방법을 알아봅니다.