언리얼 튜토리얼 게임 조종 카메라 편을 진행하면서 언리얼 엔진에서 카메라를 활성화시키고 전환하는 방법을 알아봅시다.

 

사용 엔진 버전 : 4.25 - 4.26

 

타임라인

0:00 인트로

0:17 프로젝트 생성

0:33 카메라 배치

2:26 카메라 전환 기능 구현

5:22 직접 해보기 1 - 이동하는 카메라

6:18 직접 해보기 2 - 배열에 카메라 저장해서 전환하기

7:54 직접 해보기 3 - 구조체에 카메라, 전환 시간, 블랜딩 시간 저장해서 사용하기

 

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

Camera.main에서 Null Reference가 발생하는 문제


작성 기준 버전 :: 2018.3.2f1


유니티 스크립트 작업 중에 Camera.main을 호출하면 해당 씬에서의 주 카메라가 반환된다. Camera.main으로 반환받는 주 카메라를 통해서 스크린의 좌표를 월드의 좌표로 변경하거나 월드 좌표를 스크린의 좌표로 변경하는 증의 작업을 주로 하게 된다.


하지만 가끔 이 Camera.main이 제대로된 주 카메라를 반환하지 않고 null 값을 반환하는 문제가 발생하는 경우가 발생한다.


이런 문제는 새로 생성한 씬에 자동으로 들어있는 기본 카메라를 지우고 새 카메라를 만들었을 때 주로 발생한다.



위의 이미지는 새로운 SampleScene을 만들고, 기본적으로 들어 있던 Main Camera를 지우고 New Camera를 만든 상황이다.


public class MainCameraTest : MonoBehaviour
{
    void Start()
    {
        Debug.Log(Camera.main);
    }
}


이 상황에서 위와 같이 Start() 시점에 Camera.main을 호출하는 스크립트를 만들고 게임 오브젝트에 이 스크립트를 컴포넌트로 붙여서 실행해보자.


 

그렇게 하면 위 이미지처럼 Camera.main이 null 값을 반환하는 것을 확인할 수 있다.


 

이런 문제가 발생하는 이유는 스크립트에서 Camera.main을 호출해서 메인 카메라를 찾아낼 때, "MainCamera" 태그가 붙어있는 카메라를 찾아내는 방식을 사용하기 때문이다. 그렇기 때문에 기존에 있는 메인 카메라를 지우고 새로운 카메라를 만들었다면 새로 만든 카메라의 Tag를 "MainCamera"로 바꿔줘야 한다.


새로 만들어진 카메라에 MainCamera 태그를 달고 다시 플레이를 해보면 새로 만든 카메라가 Camera.main으로 반환되는 것을 확인할 수 있다.


카메라에 메인 카메라 태그를 붙일 때도 주의해야 하는 점은, 만약 여러 개의 카메라에 메인 카메라 태그를 붙이면 Camera.main으로 호출했을 때 어떤 카메라가 반환될지 확정할 수 없기 때문에 문제가 발생할 수 있다는 점이다.

반응형

제대로 따라가기 (3) C++ 프로그래밍 튜토리얼 :: 컴포넌트와 콜리전


작성버전 :: 4.21.0


언리얼 엔진 튜토리얼인 컴포넌트와 콜리전에서는 컴포넌트를 만들어 계층구조에 넣고 게임플레이 도중 제어하는 법과, 컴포넌트를 사용하여 폰이 입체 오브젝트로 된 월드를 돌아다니도록 만드는 법을 배울 수 있다..


튜토리얼대로 하면 문제가 발생해서 제대로 따라갈 수 없는 부분으로 동작이 가능하게 수정해야하는 부분은 빨간 블럭으로 표시되어 있다.

이번 튜토리얼에서 새로 배우게 되는 내용은 글 제일 끝에 "이번 섹션에서 배운 것"에 정리된다.

출처: https://wergia.tistory.com/ [베르의 프로그래밍 노트]

제대로 따라가기 (3) C++ 프로그래밍 튜토리얼 :: 컴포넌트와 콜리전


작성버전 :: 4.21.0


언리얼 엔진 튜토리얼인 컴포넌트와 콜리전에서는 컴포넌트를 만들어 계층구조에 넣고 게임플레이 도중 제어하는 법과, 컴포넌트를 사용하여 폰이 입체 오브젝트로 된 월드를 돌아다니도록 만드는 법을 배울 수 있다..


튜토리얼대로 하면 문제가 발생해서 제대로 따라갈 수 없는 부분으로 동작이 가능하게 수정해야하는 부분은 빨간 블럭으로 표시되어 있다.

이번 튜토리얼에서 새로 배우게 되는 내용은 글 제일 끝에 "이번 섹션에서 배운 것"에 정리된다.

출처: https://wergia.tistory.com/ [베르의 프로그래밍 노트]

제대로 따라가기 (3) C++ 프로그래밍 튜토리얼 :: 컴포넌트와 콜리전


작성버전 :: 4.21.0


언리얼 엔진 튜토리얼인 컴포넌트와 콜리전에서는 컴포넌트를 만들어 계층구조에 넣고 게임플레이 도중 제어하는 법과, 컴포넌트를 사용하여 폰이 입체 오브젝트로 된 월드를 돌아다니도록 만드는 법을 배울 수 있다..


튜토리얼대로 하면 문제가 발생해서 제대로 따라갈 수 없는 부분으로 동작이 가능하게 수정해야하는 부분은 빨간 블럭으로 표시되어 있다.

이번 튜토리얼에서 새로 배우게 되는 내용은 글 제일 끝에 "이번 섹션에서 배운 것"에 정리된다.

출처: https://wergia.tistory.com/ [베르의 프로그래밍 노트]

제대로 따라가기 (4) C++ 프로그래밍 튜토리얼 :: 플레이어 제어 카메라

작성버전 :: 4.21.0

언리얼 엔진 튜토리얼인 플레이어 제어 카메라에서는 카메라를 활성화시키고 전환하는 법을 배울 수 있다. 제대로 따라가기 :: 컴포넌트와 콜리전에서 카메라 컴포넌트와 스프링 암 컴포넌트를 다루는 법에 대해서 확실히 숙지했다면 이번 파트는 넘어가도 상관은 없다.


튜토리얼대로 하면 문제가 발생해서 제대로 따라갈 수 없는 부분으로 동작이 가능하게 수정해야하는 부분은 빨간 블럭으로 표시되어 있다.


이번 튜토리얼에서 새로 배우게 되는 내용은 글 제일 끝에 "이번 섹션에서 배운 것"에 정리된다.



1. 폰에 카메라 붙이기(문서)


새로운 프로젝트를 만들고, Pawn 클래스를 상속받는 "PawnWithCamera" 클래스를 생성해보자.




다음은, PawnWithCamera.h의 클래스 정의 아래에 다음 코드를 추가한다.


protected:
    UPROPERTY(EditAnywhere)
    USpringArmComponent* OurCameraSpringArm;
    UCameraComponent* OurCamera;


위의 변수들을 사용해서 Camera Component가 붙은 Spring Arm Component를 만든다. 스프링 암은 카메라가 이동하면서 유연한 느낌으로 따라붙을 수 있게 도와준다.


여기서 USpringArmComponent와 UCameraComponent가 정의되지 않았다고 에러가 발생하는 문제는 앞에 class를 붙여주면 해결된다.


protected:
    UPROPERTY(EditAnywhere)
    class USpringArmComponent* OurCameraSpringArm;
    class UCameraComponent* OurCamera;


PawnWithCamera.cpp의 APawnWithCamera::APawnWithCamera() 생성자 함수에서 실제 컴포넌트를 생성하는 작업을 할 차례이다.


RootComponent = CreateDefaultSubobject(TEXT("RootComponent"));
OurCameraSpringArm = CreateDefaultSubobject(TEXT("CameraSpringArm"));
OurCameraSpringArm->SetupAttachment(RootComponent);
OurCameraSpringArm->SetRelativeLocationAndRotation(FVector(.0f, .0f, 50.f), FRotator(-60.f, .0f, .0f));
OurCameraSpringArm->TargetArmLength = 400.f;
OurCameraSpringArm->bEnableCameraLag = true;
OurCameraSpringArm->CameraLagSpeed = 3.0f;


RootComponent에 대입하는 CreateDefaultSubobject() 함수의 문제는 템플릿 인자에 USceneComponent를 넣어주면 해결된다.


RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootComponent"));


OurCameraSpringArm에 대입하는 CreateDefaultSubobject() 함수의 문제는 템플릿 인자에 USpringArmComponent를 넣어주면 해결된다.


OurCameraSpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraSpringArm"));


OurCameraSpringArm에 불완전한 형식은 사용할 수 없다는 에러가 뜨는 문제는 PawnWithCamera.cpp 상단 전처리기 리스트에서 "Engine/Classes/GameFramework/SpringArmComponent.h"를 포함시켜주면 해결된다.


#include "Engine/Classes/GameFramework/SpringArmComponent.h"


위의 코드에 따르면, 비어있는 Scene Component가 루트로 생성되며, 거기에 Spring Arm Component를 만들어 붙인다. 그리고 Spring Arm의 기본 피치(Pitch)를 -60도로(60도 아래쪽으로) 설정하고, 위치는 루트의 50유닛 위로 정한다. Spring Arm Component의 길이와 유연성을 위한 변수도 설정해주었다.


Spring Arm의 설정이 끝났다면 Spring Arm Component 끝의 소켓에 Camera Componenet를 만들어서 연결해주면 된다.


OurCamera = CreateDefaultSubobject(TEXT("GameCamera"));
OurCamera->SetupAttachment(OurCameraSpringArm, USpringArmComponent::SocketName);


OurCamera에 대입하는 CreateDefaultSubobject() 함수의 문제는 템플릿 인자에 UCameraComponent를 넣어주면 해결된다.


OurCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("GameCamera"));


OurCamera에 불완전한 형식은 사용할 수 없다는 에러가 뜨는 문제는 PawnWithCamera.cpp 상단 전처리기 리스트에서 "Engine/Classes/Camera/CameraComponent.h"를 포함시켜주면 해결된다.


#include "Engine/Classes/Camera/CameraComponent.h"


마지막으로 로컬 플레이어 스폰시 자동으로 Pawn을 조종하도록 다음 코드를 추가한다.


AutoPossessPlayer = EAutoReceiveInput::Player0;



2. 카메라 제어 입력 환경설정(문서)


어떤 방식으로 카메라를 제어할지 결정하고 그에 맞게 입력을 구성해야 한다. 이 프로젝트에서는 마우스 오른쪽 버튼을 클릭하면 카메라를 따라다니는 거리를 줄이고 시야를 확대하고, 마우스로는 카메라의 각도를, WASD로는 폰을 이동을 제어하도록 하자.


에디터에서 편집 드롭다운 메뉴에서 프로젝트 세팅을 선택하자.


 

[프로젝트 세팅>엔진>입력]에서 액션 매핑을 다음과 같이 설정하자.



 

3. 입력에 반응하는 C++ 코드 작성(문서)


이제 게임에서 사용할 수 있는 입력 매핑이 생겼으니, 입력 매핑으로부터 받은 데이터를 저장할 멤버 변수들을 구성할 차례이다.


업데이트 중에 이동과 마우스 방향 축을 알아야 하는데 이 값으로는 FVector2D 타입이 적합하다. 그리고 줌인 중인지 줌아웃 중인지도 알아야하며, 얼마나 줌된 상태인지를 알아야한다. 그것을 위해서 PawnWithCamera.h의 클래스 정의에 다음과 같이 멤버 변수 선언을 추가해주자.


FVector2D MovementInput;
FVector2D CameraInput;
float ZoomFactor;
bool bZoomingIn;


그 다음엔, 입력에 대한 기록을 유지할 함수도 그 아래에 추가하자.


void MoveForward(float AxisValue);
void MoveRight(float AxisValue);
void PitchCamera(float AxisValue);
void YawCameara(float AxisValue);
void ZoomIn();
void ZoomOut();


그리고 PawnWithCamera.cpp에서 위 함수들의 구현을 작성하면 된다.


void APawnWithCamera::MoveForward(float AxisValue)
{
    MovementInput.X = FMath::Clamp(AxisValue, -1.0f, 1.0f);
}

void APawnWithCamera::MoveRight(float AxisValue)
{
    MovementInput.Y = FMath::Clamp(AxisValue, -1.0f, 1.0f);
}

void APawnWithCamera::PitchCamera(float AxisValue)
{
    CameraInput.Y = AxisValue;
}

void APawnWithCamera::YawCamera(float AxisValue)
{
    CameraInput.X = AxisValue;
}

void APawnWithCamera::ZoomIn()
{
    bZoomingIn = true;
}

void APawnWithCamera::ZoomOut()
{
    bZoomingIn = false;
}


입력 데이터를 저장할 코드를 모두 구현했으니, 이제 APawnWithCamera::SetupPlayerInputComponent() 함수에서 입력 이벤트와 함수를 바인딩할 차례이다.


InputComponent->BindAction("ZoomIn", IE_Pressed, this, &APawnWithCamera::ZoomIn);
InputComponent->BindAction("ZoomOut", IE_Released, this, &APawnWithCamera::ZoomOut);

InputComponent->BindAxis("MoveForward", this, &APawnWithCamera::MoveForward);
InputComponent->BindAxis("MoveRight", this, &APawnWithCamera::MoveRight);
InputComponent->BindAxis("CameraPitch", this, &APawnWithCamera::PitchCamera);

InputComponent->BindAxis("CameraYaw", this, &APawnWithCamera::YawCamera);


만약 InputComponent의 함수를 호출하려고 할 때, 불완전한 형식은 사용할 수 없다는 에러가 발생한다면 PawnWithCamera.cpp 전처리기에 "Engine/Classes/Components/InputComponent.h"를 포함하면 된다.


#include "Engine/Classes/Components/InputComponent.h"


바인딩이 모두 끝났다면, 이제 입력을 통해 들어오는 변수 값에 따라서 Tick() 함수에서 매프레임 Pawn과 Camera를 업데이트하도록 처리하자.


{
    if (bZoomingIn)
    {
        ZoomFactor += DeltaTime * 2.0f;
    }
    else
    {
        ZoomFactor -= DeltaTime * 4.0f;
    }
    ZoomFactor = FMath::Clamp(ZoomFactor, 0.0f, 1.0f);
    OurCamera->FieldOfView = FMath::Lerp(90.0f, 60.0f, ZoomFactor);
    OurCameraSpringArm->TargetArmLength = FMath::Lerp(400.0f, 300.0f, ZoomFactor);
}


이 코드에서는 줌인/줌아웃할 때, 걸이는 시간, FOV 값, 스프링 암의 거리 등을 하드코딩해서 사용하고 있지만, 이 값들을 멤버 변수로 만들어서 UPROPERTY(EditAnywhere)로 설정해서 에디터에 노출시키면 프로그래머가 아닌 개발자들도 에디터에서 값을 변경할 수 있고, 프로그래머도 값을 바꿀때마다 컴파일을 새로 할 필요가 없게 만들 수 있다.


{
    FRotator NewRotation = GetActorRotation();
    NewRotation.Yaw += CameraInput.X;
    SetActorRotation(NewRotation);
}

{
    FRotator NewRotation = OurCameraSpringArm->GetComponentRotation();
    NewRotation.Pitch = FMath::Clamp(NewRotation.Pitch + CameraInput.Y, -80.0f, -15.0f);
    OurCameraSpringArm->SetWorldRotation(NewRotation);
}


이 코드 블록은 Pawn의 요(Yaw)를 마우스 X축으로 직접 회전시키되, 카메라 시스템은 마우스 Y축의 피치(Pitch) 변화에만 반응한다. 액터나 그 서브클래스를 회전시키면, 실제로 루트 레벨의 컴포넌트가 회전되어 거기에 붙어있는 모든 오브젝트에 간접적으로 영향을 미친다.


{
    if (!MovementInput.IsZero())
    {
        MovementInput = MovementInput.GetSafeNormal() * 100.0f;
        FVector NewLocation = GetActorLocation();
        NewLocation += GetActorForwardVector() * MovementInput.X * DeltaTime;
        NewLocation += GetActorRightVector() * MovementInput.Y * DeltaTime;
        SetActorLocation(NewLocation);
    }
}


GetActorForwardVector()와 GetActorRightVector()를 사용하면 액터가 향하는 방향을 기준으로 이동하는 것이 가능하다. 카메라가 액터와 같은 방향을 향하고 있기 때문에 전방 키가 항상 플레이어가 바라보는 방향이 앞쪽이 되게 해준다.


모든 코딩 작업이 끝났다. 언리얼 에디터로 돌아가서 컴파일 한 뒤, 레벨에 배치해보자.


추가로 폰에 스태틱 메시나 비주얼 컴포넌트를 추가해서 자유롭게 플레이해보자.


폰이 움직일때는 카메라가 부드럽게 따라가지만 회전할 때는 카메라가 즉각 반응하는 것을 느낄 수 있을 것이다. Camera Rotation Lag를 켜거나 Camera Lag Speed를 수정해서 조작감에 어떤 영향을 미치는지 확인해보자.








 

이번 섹션에서 배운 것


1. USceneComponent


SetRelativeLocationAndRotation(FVector(), FRotator());


루트 오브젝트로부터의 위치와 회전을 동시에 설정할 수 있는 함수.


2. FVector2D


FVector2D Vector2D;


FVector의 2D 버전 구조체. FVector는 3차원 상의 X, Y, Z 좌표를 가지지만 FVector2D는 2차원 상의 X, Y 좌표만을 가진다.


3. UCameraComponent


UCameraComponent* CameraComponent;


CameraComponent->FieldOfView = 60.0f;


원근감 모드(Projection Mode)에서의 수평 시야각을 Field of view라고 한다. 수평 시야각이 넓어지면 물체가 확대되어서 보이기 때문에 주로 FPS게임에서 저격 소총의 줌 효과에 주로 사용된다.


4. FMath::Lerp()


FMath::Lerp(ValueA, ValueB, Factor);


선형 보간 함수이다. ValueA와 ValueB 사이의 Factor(0.0~1.0)값의 위치에 해당 하는 값을 구해준다.


ex) ValueA = 0, ValueB = 2일 때, Factor = 0.5이면 1을 돌려준다.


5. AActor


GetActorForwardVector();


액터의 Forward 방향을 구하는 함수


GetActorRightVector();


액터의 Right 방향으로 구하는 함수

반응형
  1. BeautyfullCastle 2018.12.16 15:08 신고

    감사합니다. 잘 보고 있습니다.

    오타가 있어 제보 드립니다.

    * Cameara -> Camera
    void APawnWithCamera::YawCameara(float AxisValue)

    * "ZoomIn" -> "ZoomOut"
    InputComponent->BindAction("ZoomIn", IE_Released, this, &APawnWithCamera::ZoomOut);

    * 입력 탭에 ZoomOut 바인딩이 없습니다.

    • wergia 2018.12.17 00:27 신고

      오타 제보 감사합니다.
      빠르게 작업하려고 하다보니 오타가 발생했나봅니다.
      수정했습니다.

+ Recent posts