4. 폰과 컴포넌트 함께 사용하기

폰의 함수성, 입력 환경설정, 컴포넌트를 함께 묶는 작업입니다.

Windows
MacOS
Linux
목차
  1. 우리 커스텀 Pawn Movement Component 를 사용하기 위해서는 먼저 그에 대한 기록을 유지할 변수를 Pawn 클래스에 추가해 줘야 합니다. CollidingPawn.h 의 클래스 정의 끝, "OurParticleSystem" 변수를 추가한 곳 근처에 다음과 같이 추가해 줘야 합니다:

    class UCollidingPawnMovementComponent* OurMovementComponent;
  2. 기록을 유지할 곳이 생겼으면, 새로운 변수를 저장할 Colliding Pawn Movement Component 를 만들어 줘야 하니, CollidingPawn.cpp 를 열고 파일 상단 "GameFramework/Pawn.h" 줄 아래에 다음과 같이 추가하여 코드가 새 클래스를 참조할 수 있도록 합니다:

    #include "CollidingPawnMovementComponent.h"

    목록의 마지막 #include 가 generated.h (#include "CollidingPawn.generated.h" in this case) as it will cause a compile error otherwise.

    Pawn Movement Component 를 만들어 우리 Pawn 에 연관시켜 주는 것은 간단합니다. ACollidingPawn::ACollidingPawn 하단에 이렇게 추가해 주면 됩니다:

    // 무브먼트 컴포넌트 인스턴스를 생성하고, 루트를 업데이트하라 이릅니다.
    OurMovementComponent = CreateDefaultSubobject<UCollidingPawnMovementComponent>(TEXT("CustomMovementComponent"));
    OurMovementComponent->UpdatedComponent = RootComponent;

    지금까지 봐 온 다른 컴포넌트 와는 달리, 이 컴포넌트 는 우리 컴포넌트 계층구조에 붙일 필요가 없습니다. 왜냐면 우리 컴포넌트 는 모두 씬 컴포넌트 유형이었는데, 애초부터 물리적 위치가 필요한 것들이기 때문입니다. 하지만 Movement Controller씬 컴포넌트 가 아니라, 물리적 오브젝트를 나타내지 않기에, 물리적 위치에 존재한다든가 다른 컴포넌트 에 물리적으로 붙인다든가 하는 개념은 적용되지 않습니다.

  3. Pawn 에는 "GetMovementComponent" 라는 함수가 있는데, 엔진의 다른 클래스가 현재 Pawn 이 사용중인 Pawn Movement Component 에 접근할 수 있도록 하는 데 사용됩니다. 이것이 우리 커스텀 Pawn Movement Component 를 반환할 수 있도록 하려면 그 함수를 덮어써 줘야 할 것입니다. CollidingPawn.h 의 클래스 정의에 다음과 같이 추가해 줍니다:

    virtual UPawnMovementComponent* GetMovementComponent() const override;

    그리고 CollidingPawn.cpp 에서, 다음과 같이 덮어쓴 함수의 정의를 추가해 줘야 합니다:

    UPawnMovementComponent* ACollidingPawn::GetMovementComponent() const
    {
        return OurMovementComponent;
    }
  4. 새로운 Pawn Movement Component 구성이 완료되었으니, 우리 Pawn 이 받을 입력 처리를 위한 코드를 만들면 되겠습니다. 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();
            }
        }
  5. 남은 것은 우리 함수를 입력 이벤트에 묶는 것 뿐입니다. 다음 코드를 ACollidingPawn::SetupPlayerInputComponent 에 추가합시다:

    InputComponent->BindAction("ParticleToggle", IE_Pressed, this, &ACollidingPawn::ParticleToggle);
    
    InputComponent->BindAxis("MoveForward", this, &ACollidingPawn::MoveForward);
    InputComponent->BindAxis("MoveRight", this, &ACollidingPawn::MoveRight);
    InputComponent->BindAxis("Turn", this, &ACollidingPawn::Turn);
  6. 프로그래밍 작업은 다 되었으며, 이제 언리얼 에디터 로 돌아가 컴파일 버튼을 누르고 변경사항을 로드하면 됩니다.

프로그래밍 작업이 완료되었으니, 이제 커스텀 Pawn 을 월드에 배치하고 움직일 수 있습니다.

Finished Code

CollidingPawn.h

// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.

#pragma once

#include "GameFramework/Pawn.h"
#include "CollidingPawn.generated.h"

UCLASS()
class HOWTO_COMPONENTS_API ACollidingPawn : public APawn
{
    GENERATED_BODY()

public:
    // Sets default values for this pawn's properties
    ACollidingPawn();

protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

public:
    // Called every frame
    virtual void Tick( float DeltaSeconds ) override;

    // Called to bind functionality to input
    virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) override;

    UParticleSystemComponent* OurParticleSystem;
    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 "HowTo_Components.h"
#include "CollidingPawn.h"
#include "CollidingPawnMovementComponent.h"

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

    // Our root component will be a sphere that reacts to physics
    USphereComponent* SphereComponent = CreateDefaultSubobject<USphereComponent>(TEXT("RootComponent"));
    RootComponent = SphereComponent;
    SphereComponent->InitSphereRadius(40.0f);
    SphereComponent->SetCollisionProfileName(TEXT("Pawn"));

    // Create and position a mesh component so we can see where our sphere is
    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));
    }

    // Create a particle system that we can activate or deactivate
    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);
    }

    // Use a spring arm to give the camera smooth, natural-feeling motion.
    USpringArmComponent* SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraAttachmentArm"));
    SpringArm->SetupAttachment(RootComponent);
    SpringArm->RelativeRotation = FRotator(-45.f, 0.f, 0.f);
    SpringArm->TargetArmLength = 400.0f;
    SpringArm->bEnableCameraLag = true;
    SpringArm->CameraLagSpeed = 3.0f;

    // Create a camera and attach to our spring arm
    UCameraComponent* Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("ActualCamera"));
    Camera->SetupAttachment(SpringArm, USpringArmComponent::SocketName);

    // Take control of the default player
    AutoPossessPlayer = EAutoReceiveInput::Player0;

    // Create an instance of our movement component, and tell it to update our root component.
    OurMovementComponent = CreateDefaultSubobject<UCollidingPawnMovementComponent>(TEXT("CustomMovementComponent"));
    OurMovementComponent->UpdatedComponent = RootComponent;
}

// Called when the game starts or when spawned
void ACollidingPawn::BeginPlay()
{
    Super::BeginPlay();

}

// Called every frame
void ACollidingPawn::Tick( float DeltaTime )
{
    Super::Tick( DeltaTime );

}

// Called to bind functionality to input
void ACollidingPawn::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
    Super::SetupPlayerInputComponent(InputComponent);

    InputComponent->BindAction("ParticleToggle", IE_Pressed, this, &ACollidingPawn::ParticleToggle);

    InputComponent->BindAxis("MoveForward", this, &ACollidingPawn::MoveForward);
    InputComponent->BindAxis("MoveRight", this, &ACollidingPawn::MoveRight);
    InputComponent->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 "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 "HowTo_Components.h"
#include "CollidingPawnMovementComponent.h"

void UCollidingPawnMovementComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction)
{
    Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

    // Make sure that everything is still valid, and that we are allowed to move.
    if (!PawnOwner || !UpdatedComponent || ShouldSkipUpdate(DeltaTime))
    {
        return;
    }

    // Get (and then clear) the movement vector that we set in ACollidingPawn::Tick
    FVector DesiredMovementThisFrame = ConsumeInputVector().GetClampedToMaxSize(1.0f) * DeltaTime * 150.0f;
    if (!DesiredMovementThisFrame.IsNearlyZero())
    {
        FHitResult Hit;
        SafeMoveUpdatedComponent(DesiredMovementThisFrame, UpdatedComponent->GetComponentRotation(), true, Hit);

        // If we bumped into something, try to slide along it
        if (Hit.IsValidBlockingHit())
        {
            SlideAlongSurface(DesiredMovementThisFrame, 1.f - Hit.Time, Hit.Normal, Hit);
        }
    }
};
Select Skin
Light
Dark

새로운 언리얼 엔진 4 문서 사이트에 오신 것을 환영합니다!

문서 사이트에 대한 의견을 모을 수 있는 피드백 시스템을 포함해서 여러가지 새로운 기능을 준비하고 있습니다. 아래 Documentation Feedback 포럼(영문) 또는 언리얼 엔진 네이버 공식 카페(한글) 중 편하신 곳에 의견이나 문제점을 알려 주세요.

새 시스템이 준비되면 알려 드리겠습니다.

네이버 카페
공식 포럼