Unreal/Ureal Engine 5 정리

Chapter 03. Actor

GameStudy 2023. 8. 12. 21:25

 

 

 

 

 

3.1 언리얼의 기본 구조

3.1-1 World

  - 월드

    게임이 진행되는 공간(레벨)과 모든 오브젝트(UObject)

    그리고 시간, 물리, 랜더링을 담당하는 오브젝트.

    이 모든 것들의 상호작용이 월드 내에서 이뤄짐.

    Toolbar > Settings > World Settings를 통해 위와 관련된 내용들을 확인 가능.

 

  - 레벨

    가상 세계를 구성하는 3차원의 공간. 단위는 cm. Viewport를 통해 볼 수 있음.

    레벨은 플레이어에게 주어지는 플레이 단계를 의미함.

    플레이 버튼을 누르면 Outliner에 기존 레벨을 구성하는 액터(흰색 이름)와 함께

    접속한 플레이어와 관련된 액터(노란색 이름)들이 새롭게 생성됨.

    정확하게는 동적 생성된(런타임 중에 생성된) 액터가 노란색 이름을 가짐.

    만약 레벨이 하나라면 레벨과 월드는 같음. 레벨에 사용되는 애셋이 .umap 파일. 

 

  - 시간

    가상 공산에서 흐르는 시간. 가상 공산의 시간은 초단위.

    하지만 이 시간은 흐르는 속도를 조절 할 수 있음.

 

  - 물리

    월드 공간에 배치된 오브젝트에 작용하는 물리 작용. 

    해당 오브젝트에 Collision 정보가 있다면 물리적인 영향을 받을 수 있음. ex) Collision, ...

 

 

3.1-2 Actor

  - 액터

    월드를 구성하는 최소 단위의 개체.

    액터는 월드에 존재하므로, 트랜스폼이 항상 부여됨.

    특정 상황에 대응할 구체적인 게임 로직을 개발자가 작성함.

    해당 게임 로직은 C++ 혹은 블루프린트의 두 가지 언어로 작성 가능.

 

  - 액터 구현의 핵심

    시각적 기능: 플레이어에게 어떻게 보여질 것인가. ex) 애니메이션, ...

    물리적 기능: 액터들 간의 상호작용은 어떻게 처리할 것인가. ex) 충돌, ...

    움직임 기능: 액터가 어떻게 움직일 것인가.

      숨겨진 포탈이라는 액터는 시각적 기능은 없지만 물리적 기능은 가짐. 

      차량 액터는 시각적/물리적/움직임 모두 가짐.

    이러한 액터의 기능들을 쉽게 구현하기 위해서 언리얼은 컴포넌트 구조를 채택함.

 

 

3.1-3 Component

  - 컴포지션 

    개체 지향 설계에서 상속이 가진 Is-A 관계만 의존해서는 설계와 유지보수가 어려움.

    상속의 깊이가 길고, 최상위 부모 클래스의 코드가 변경되면 그 하위 모든 자식 클래스가 영향을 받기 때문.

    컴포지션은 개체 지향 설계에서 Has-A 관계를 구현하는 설계 방법.

    복잡한 기능을 가져야 하는 클래스를 효과적으로 설계하는데 유용함.

    모던 개체 설계 기법의 핵심은 상속을 단순화하고,

    단순한 기능을 가진 개체를 조합해서 복잡한 개체를 구성하는 것.

    그래서 컴포지션은 아주 중요함.

 

  - 언리얼 엔진의 컴포지션

    하나의 언리얼 오브젝트에는 항상 클래스 기본 오브젝트가 있음.

    언리얼 오브젝트 간의 컴포지션은 어떻게 구현할 것인가?

      CDO에 미리 다른 언리얼 오브젝트를 생성 후 조합.(필수적 포함)

      CDO에는 빈 포인터만 넣고, 런타임에서 언리얼 오브젝트를 생성 후 조합.(선택적 포함)

    언리얼 오브젝트를 생성할 때 컴포지션 정보를 구축할 수 있음.

 

  - 내가 소유한 언리얼 오브젝트를 Subobject라고 함.

    나를 소유한 언리얼 오브젝트를 Outer라고 함.

 

  - 컴포넌트

    액터를 구현할 때 다양한 경우를 유연하게 대처할 수 있게끔

    각 기능을 규격화하고, 액터는 그 규격화된 기능을 조합할 수 있도록 했음.

    이러한 규격화된 기능을 컴포넌트라고 함. 

    ex) 스태틱 메시 컴포넌트, 박스 컴포넌트, 무브먼트 컴포넌트, ...

 

  - 액터는 컴포넌트를 여러 개 가질 수 있음.

    그 컴포넌트들 중에서도 대표하는 하나의 컴포넌트가 반드시 있음.

    이를 그 액터의 루트 컴포넌트라고 함. 개발자가 지정할 수 있음.

 

  - 주요 컴포넌트

스태틱메시 컴포넌트 애니메이션 정보가 있는 애셋인 스태틱 메시를 가지고
시각적 기능과 물리적 기능을 제공하는 컴포넌트.
스켈레탈메시 컴포넌트 애니메이션 정보가 있는 애셋인 스켈레탈 메시를 가지고
시각적 기능과 물리적 기능을 제공하는 컴포넌트.
무브먼트 컴포넌트 물체에 움직임을 제공하는 컴포넌트.
콜리전 컴포넌트 특정 공간(구/박스/캡슐)에 물리적 기능을 제공하는 컴포넌트
카메라 컴포넌트 가상 세계를 플레이어의 모니터에
출력해주는 기능을 제공하는 컴포넌트

 

 

 

3.2 액터 

3.2-1 C++ 클래스 생성

  - 실습 준비

    Epic Games Launcher > 마켓 플레이스 > "Modular Military Operation Urban Training Environment" 검색

    무료 버튼 클릭 > 프로젝트 추가 > StudyProject 선택 후 프로젝트에 추가.

 

  - 블루프린트 실습

    Content Browser > StudyProject 우클릭 > 새 폴더 "WorldStatics"

    WorldStatics > 새 블루프린트 애셋 > Actor 부모 클래스 > "BP_TestElectricityPole"

 

  - Content Browser > C++ Classes > StudyProject > 좌측 빈공간 우클릭

    (앞으로는 위 과정을 "새 C++ 클래스"로 줄임)

    새 C++ 클래스 > Actor 부모 클래스 > Class Type은 앞으로 계속 Public

    "SElectricityPole" 클래스 생성. S-는 StudyProject 의 줄임말.

    Path > WorldStatics (만약 해당 폴더가 없다면 만들고 지정하면 됨.)

 

  - SElectricityPole 클래스 

    필요없는 함수들을 구태여 지금 구현할 필요가 없음. 처음엔 주석까지 모두 지우고 시작.

    생성된 클래스 이름을 보면 A가 가장 앞에 붙음. 이는 Actor 클래스를 상속 받는단 뜻.

<hide/>

// SElectricityPole.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "SElectricityPole.generated.h"

UCLASS()
class STUDYPROJECT_API ASElectricityPole : public AActor
{
    GENERATED_BODY()
    
public:	
    ASElectricityPole();

private:
    UPROPERTY()
    TObjectPtr<class UBoxComponent> BoxComponent;

    UPROPERTY()
    TObjectPtr<class UStaticMeshComponent> PoleStaticMeshComponent;

    UPROPERTY()
    TObjectPtr<class UStaticMeshComponent> LightStaticMeshComponent;

    UPROPERTY()
    TObjectPtr<class UPointLightComponent> PointLightComponent;

    UPROPERTY()
    TObjectPtr<class UParticleSystemComponent> ParticleSystemComponent;

};
<hide/>

// SElectricityPole.cpp


#include "WorldStatics/SElectricityPole.h"
#include "Components/BoxComponent.h"
#include "Components/PointLightComponent.h"
#include "Particles/ParticleSystemComponent.h"

ASElectricityPole::ASElectricityPole()
{
    PrimaryActorTick.bCanEverTick = false;

    BoxComponent = CreateDefaultSubobject<UBoxComponent>(TEXT("BoxComponent"));
    SetRootComponent(BoxComponent);

    PoleStaticMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("PoleStaticMeshComponent"));
    PoleStaticMeshComponent->SetupAttachment(GetRootComponent());

    LightStaticMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("LightStaticMeshComponent"));
    LightStaticMeshComponent->SetupAttachment(GetRootComponent());

    PointLightComponent = CreateDefaultSubobject<UPointLightComponent>(TEXT("PointLightComponent"));
    PointLightComponent->SetupAttachment(GetRootComponent());

    ParticleSystemComponent = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("ParticleSystemComponent"));
    ParticleSystemComponent->SetupAttachment(GetRootComponent());

}

 

  - TObjectPtr

    언리얼 5 버전 부터 헤더에 언리얼 오브젝트를 선언할 때 로우포인터 대신 TObjectPtr로 변경.

    위 예제를 로우 포인터로 바꿔도 정상동작함.

 

  - 특정 클래스를 상속 받는 블루프린트 애셋 생성

    Content Browser > WorldStatics > 새 블루프린트 애셋 > SElectricityPole 부모 클래스 > "BP_ElectricityPole"

    기존에 만든 BP_TestElectricityPole과 동일함을 알 수 있음.

    다만, 만들어진 컴포넌트들을 수정 할 수가 없음.

 

  - UPROPERTY()

    언리얼 오브젝트 클래스의 속성(== 멤버, 멤버변수, ...)에 작성하는 매크로.

    해당 매크로가 작성된 속성은 언리얼 런타임 시스템에 의해 관리됨.

    즉, 가비지컬렉션/리플렉션/레플리케이션 등의 장점을 취할 수 있음.

    다만 성능에는 영향을 줄 수 있으므로, 굳이 필요없는 속성은 UPROPERTY() 매크로를 달지 않음.

 

  - bCanEverTick

    이 속성이 true인 경우엔 Tick() 함수가 호출됨.

    지금은 Tick() 함수가 굳이 필요 없으므로, false로 지정함.

 

  - CreateDefaultSubobject<>()

    컴포넌트도 결국 언리얼 오브젝트임.

    내가 소유한 언리얼 오브젝트를 Subobject라고 하고 나를 소유한 언리얼 오브젝트를 Outer라고 함.

    스태틱 메시 컴포넌트는 내가 소유할 언리얼 오브젝트이기에 Subobject임.

    Subobject를 만들때는 CreateDefaultSubobject<>() 함수를 사용함.

    이렇게CDO에서 생성한 컴포넌트는 자동으로 월드에 등록됨.

    NewObject로 생성한 컴포넌트는 반드시 등록 절차를 거쳐야 함.(ex. RegisterComponent)

    등록된 컴포넌트는 월드의 기능을 사용할 수 있음. 물리와 랜더링 처리에 합류됨.

 

  - CreateDefaultSubobject<>() 함수에 전달하는 문자열

    해당 문자열은 액터에 속한 컴포넌트를 구별하기 위한 Hash 값 생성에 사용됨.

    따라서 다른 컴포넌트와 중복되지 않는 유일한 값을 지정해야함. 변수명을 보통 사용함.

 

  - VisibleAnywhere 키워드 실습

    UPROPERTY에 지정자(Specifier)를 설정할 수 있음.

    UPROPERTY() 매크로 안에 VisibleAnywhere 키워드를 작성하면

    에디터에서 해당 컴포넌트 포인터 속성을 수정할 수 있음.

    주소값만 알면(볼 수 있으면) 메모리에 접근해서 수정할 수 있다는 원리와 같음.

    다만, 초기화 버튼을 누르거나 에디터를 껏다 키면 값이 모두 초기화됨.

<hide/>

// SElectricityPole.h

..
class STUDYPROJECT_API ASElectricityPole : public AActor
{
    ...

private:
    UPROPERTY(VisibleAnywhere)
    TObjectPtr<class UBoxComponent> BoxComponent;

    UPROPERTY(VisibleAnywhere)
    TObjectPtr<class UStaticMeshComponent> PoleStaticMeshComponent;

    UPROPERTY(VisibleAnywhere)
    TObjectPtr<class UStaticMeshComponent> LightStaticMeshComponent;

    UPROPERTY(VisibleAnywhere)
    TObjectPtr<class UPointLightComponent> PointLightComponent;

    UPROPERTY(VisibleAnywhere)
    TObjectPtr<class UParticleSystemComponent> ParticleSystemComponent;

};

 

  - C++ 코드로 컴포넌트 수정하기

    초기화해도 에디터를 다시 켜도 똑같은 값임.

    그런데, LightStaticMeshComponent의 회전이 이상하게 됨. 이는 메시를 제작하는 에디터의 문제.

<hide/>

// SElectricityPole.cpp


...

ASElectricityPole::ASElectricityPole()
{
    PrimaryActorTick.bCanEverTick = false;

    BoxComponent = CreateDefaultSubobject<UBoxComponent>(TEXT("BoxComponent"));
    SetRootComponent(BoxComponent);

    PoleStaticMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("PoleStaticMeshComponent"));
    PoleStaticMeshComponent->SetupAttachment(GetRootComponent());
    PoleStaticMeshComponent->SetRelativeLocation(FVector(0.f, 0.f, -30.f));

    LightStaticMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("LightStaticMeshComponent"));
    LightStaticMeshComponent->SetupAttachment(GetRootComponent());
    LightStaticMeshComponent->SetRelativeLocation(FVector(0.f, -50.f, 935.f));
    LightStaticMeshComponent->SetRelativeRotation(FRotator(90.f, 0.f, 0.f));

    PointLightComponent = CreateDefaultSubobject<UPointLightComponent>(TEXT("PointLightComponent"));
    PointLightComponent->SetupAttachment(GetRootComponent());
    PointLightComponent->SetRelativeLocation(FVector(0.f, -50.f, 935.f));
    PointLightComponent->SetRelativeRotation(FRotator(90.f, 0.f, 0.f));
    PointLightComponent->SetAttenuationRadius(500.f);

    ParticleSystemComponent = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("ParticleSystemComponent"));
    ParticleSystemComponent->SetupAttachment(GetRootComponent());
    ParticleSystemComponent->SetRelativeLocation(FVector(-70.f, 0.f, 935.f));
}

 

3.2-2 언리얼 오브젝트의 속성 유형

  - 개체 유형: 스태틱 메시 컴포넌트의 포인터

    값 유형: uint8, int32, FVector, FRotator, ...

 

  - 언리얼이 제공하는 대표적인 값 유형

    [여기]에서 확인 가능함.

    uint8: 1byte

    int32: 정수

    float: 실수

    FString, FText, FName: 문자열

    FTransform, FVector, FRotator: 구조

 

  - 기본 타입 재정의의 필요성

    C++는 C언어를 발전시킨 언어임.

    C언어는 임베디드 전용 언어로, 극단적인 환경에서도 동작할 수 있게끔 개발됨.

    극단적인 환경이라 함은 256MB보다 더 적은 렘에서도 돌아감.

    그래서 자료형의 크기가 아주 중요함. C언어는 자료형의 크기를 컴파일러에게 자율로 맡김

    그래서 본인이 C 컴파일러를 만든다면, int 자료형의 크기를 200MB로 해도됨.

    이는 C++에도 영향을 줌. 최신 C++ 표준에서는 int 자료형의 크기를 "최소" 32bit로 규정.

    게임 개발 후에는 여러 플랫폼(Windows, IOS, Android, Console, ...)에서 동작시켜야함.

    게다가 자료형도 여러 개임(bool, int, float, double, ...)

    근데 이 자료형마다 그 크기가 플랫폼 별로 마음대로 정해져 있음.

    이를 플랫폼 파편화라고 함.

    게임 제작에서는 이런 모호함들이 유지보수에 큰 영향을 끼침.

    그래서 C++을 사용하는 언리얼에서는 기본 타입의 재정의를 진행함.

 

  - FRotator

    회전에는 FRotator라는 구조체를 사용함. 

    차례대로 Pitch, Yaw Roll이라는 세 가지 회전 요소로 구성됨.

    Pitch: 좌우를 기준 회전. 언리얼 엔진 기준 Y축 회전 표현.

    Yaw: 상하를 기준 회전. 언리얼 엔진 기준 Z축 회전 표현.

    Roll: 전후를 기준 회전. 언리얼 엔진 기준 X축 회전 표현.

항공 계열의 회전 표현

 

  - 값 유형과 UPROPERTY() 매크로 실습

    값 유형의 속성을 선언하고 UPROPERTY() 매크로를 작성하면 자동으로 기본값이 초기화됨.

    이는 Details에서 확인 가능함.

    이때 값 유형의 VisibleAnywhere 키워드 속성은 수정 불가능한걸 볼 수 있음.

<hide/>

// SElectricityPole.h

...
class STUDYPROJECT_API ASElectricityPole : public AActor
{
    ...

private:
    ...

    UPROPERTY(VisibleAnywhere)
    int32 ID;

};

 

  - 값 유형과 EditAnywhere 키워드 실습

    추가적으로 Category 키워드도 실습해보자.

    Details에서 해당 속성을 특정 카테고리에서 관리할 수 있게끔 함.

<hide/>

// SElectricityPole.h

...
class STUDYPROJECT_API ASElectricityPole : public AActor
{
    ...

private:
    ...

    UPROPERTY(EditAnywhere, Category = ASElectricityPole) // Category를 통해서 여러 속성을 같은 그룹에 묶을 수 있음.
    int32 ID;

};

 

  - UPROPERTY() 매크로의 지정자

    Visible / Edit

    에디터에서 수정 불가능 / 가능

    Anywhere / DefaultsOnly / InstanceOnly

    에디터에서 편집 가능한 영역. 어디서나 / CDO에서만 / 개체에서만.

    BlueprintReadOnly / BlueprintReadWrite

    해당 클래스를 블루프린트 클래스로 상속 받을 시, 이벤트 그래프에서 수정 불가능 / 가능

    Category

    Details에서의 카테고리 설정하는 용도.

    meta

    추가로 필요한 키워드들을 작성하는 용도.

    대표적으로 AllowPrivateAccess, BindWidget, ... 키워드를 사용함. 

 

  - 언리얼 엔진에서의 캡슐화

    무작정 private 접근 지정자로 선언하면, 컴파일 과정에서 에러가 발생할 수 있음.

    그래서 UPROPERTY() 매크로에 AllowPrivateAccess라는 메타 키워드 작성.

    에디터에서는 편집 가능함과 동시에 캡슐화까지 가능.

 

  - UPROPERTY() 매크로의 지정자 실습

<hide/>

// SElectricityPole.h

...
class STUDYPROJECT_API ASElectricityPole : public AActor
{
    ...

private:
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = ASElectricityPole, meta = (AllowPrivateAccess))
    TObjectPtr<class UBoxComponent> BoxComponent;

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = ASElectricityPole, meta = (AllowPrivateAccess))
    TObjectPtr<class UStaticMeshComponent> PoleStaticMeshComponent;

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = ASElectricityPole, meta = (AllowPrivateAccess))
    TObjectPtr<class UStaticMeshComponent> LightStaticMeshComponent;

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = ASElectricityPole, meta = (AllowPrivateAccess))
    TObjectPtr<class UPointLightComponent> PointLightComponent;

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = ASElectricityPole, meta = (AllowPrivateAccess))
    TObjectPtr<class UParticleSystemComponent> ParticleSystemComponent;

    UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = ASElectricityPole, meta = (AllowPrivateAccess))
    int32 ID;

};

 

  - bool 타입의 선언

    C/C++에는 bool 자료형에 대한 표준 자체가 없음. 그래서 그 크기가 명확하지 않음.

    그래서 언리얼에서는 header 파일에 bool 대신 uint8 + Bit field 연산자를 사용해서

    명확한 크기 지정. 참/거짓을 굳이 8bit로 할 필요 없음. cpp 파일에서는 bool 자료형 사용.

 

3.2-3 애셋 지정

  - 언리얼의 모든 애셋은 레벨 애셋을 제외하고 모두 .uasset이라는 확장자를 가짐.

    이 애셋들은 외형적으로는 동일한 체계에서 관리되지만, 애셋 파일 내부 데이터는

    애셋에 마우스를 올리거나, 애셋 아이콘 하단의 색상으로 파악가능.

 

  - 언리얼 애셋의 고유한 키 값

    C++ 코드에서 관련 애셋을 불러들이려면 애셋의 고유한 키 값을 파악하고

    애셋을 관리하는 시스템에 해당 키 값으로 질의를 던져서 애셋의 포인터를 가져와야함.

    언리얼 엔진은 애셋의 고유한 키 값으로 경로 값을 사용함. 이는 애셋 클릭 후 Ctrl + C하면 됨.

    혹은 우클릭 후 Copy Reference를 통해 가능.

 

  - Ctrl + C해서 다른 곳에 붙혀 넣어보면, 경로만 복사 되는 것이 아니라 다소 복합한 규칙이 있음.

{오브젝트 타입}'{폴더명}/{파일명}.{애셋명}'

    오브젝트 타입과 작은 따옴표를 지워서 간단한 형식으로 사용할 수도 있음.

    위 정보를 오브젝트 패스(Object Path)라고 함.

    오브젝트 패스는 프로젝트 내에서 유일함이 보장됨. 이를 이용해서 다양한 방법으로 애셋 로딩 가능.

 

  - 언리얼 애셋 지정 실습

    이전까지는 언리얼 에디터가 켜질 때마다 다시 애셋을 지정해야 했음.

    C++을 통해 지정하면 게임이 실행되며 CDO가 생성되면서 지정되기에

    모든 해당 언리얼 개체들은 자동으로 지정됨.

 

  - 오브젝트 패스를 이용한 애셋 로딩

<hide/>

// SElectricityPole.cpp


...
ASElectricityPole::ASElectricityPole()
{
    PrimaryActorTick.bCanEverTick = false;

    BoxComponent = CreateDefaultSubobject<UBoxComponent>(TEXT("BoxComponent"));
    SetRootComponent(BoxComponent);

    ...
    PoleStaticMeshComponent->SetRelativeLocation(FVector(0.f, 0.f, -30.f));
    static ConstructorHelpers::FObjectFinder<UStaticMesh> PoleStaticMesh(TEXT("/Script/Engine.StaticMesh'/Game/MOUT_Fabrication/Meshes/MOUTFabrications/Props/SM_ElectricityPole/SM_ElectricityPole_01.SM_ElectricityPole_01'"));
    if (true == PoleStaticMesh.Succeeded())
    {
        PoleStaticMeshComponent->SetStaticMesh(PoleStaticMesh.Object);
    }

    ...
    LightStaticMeshComponent->SetRelativeRotation(FRotator(90.f, 0.f, 0.f));
    static ConstructorHelpers::FObjectFinder<UStaticMesh> LightStaticMesh(TEXT("/Script/Engine.StaticMesh'/Game/MOUT_Fabrication/Meshes/MOUTFabrications/Props/SM_CeilingLights/SM_CeilingLight_Small.SM_CeilingLight_Small'"));
    if (true == LightStaticMesh.Succeeded())
    {
        LightStaticMeshComponent->SetStaticMesh(LightStaticMesh.Object);
    }

    PointLightComponent = CreateDefaultSubobject<UPointLightComponent>(TEXT("PointLightComponent"));
    PointLightComponent->SetupAttachment(GetRootComponent());
    PointLightComponent->SetRelativeLocation(FVector(0.f, -50.f, 935.f));
    PointLightComponent->SetRelativeRotation(FRotator(90.f, 0.f, 0.f));
    PointLightComponent->SetAttenuationRadius(500.f);

    ...
    ParticleSystemComponent->SetRelativeLocation(FVector(-70.f, 0.f, 935.f));
    static ConstructorHelpers::FObjectFinder<UParticleSystem> ParticleSystem(TEXT("/Script/Engine.ParticleSystem'/Game/StarterContent/Particles/P_Sparks.P_Sparks'"));
    if (true == ParticleSystem.Succeeded())
    {
        ParticleSystemComponent->SetTemplate(ParticleSystem.Object);
    }
}

 

  - 경로 정보는 게임의 실행 중에는 변경될 일이 없음.

    따라서 생성자가 호출될 때마다 지역 변수를 생성하고 대입하는건 비효율적임.

    즉 정적 지역 변수로 선언하는 것이 올바른 방법.

 

  - 다만 실제 게임 개발에서는 이렇게 하드 코딩으로 경로 정보를 넣지는 않음.

    블루프린트에서 지정하는 방식을 선호함. 경로 정보는 언제라도 아트팀에서 바꿀 여지가 있기 때문.

    블루프린트에서 지정하면 경로 정보가 바뀌더라도 쉽게 수정될 수 있음.

 

3.3 이벤트 함수와 컴포넌트 구조

3.2-1 주요 이벤트 함수

  - 게임이 시작될 때 액터는 준비/게임참여/퇴장의 과정을 거침.

    이 과정을 처리하는 함수를 이벤트 함수라고 함.

    이벤트 함수는 언리얼 엔진에 의해 자동으로 호출되는 중요한 함수임.

 

  - 주요 이벤트 함수의 호출 순서

    PostInitializeComponents()

    액터를 구성하는 모든 컴포넌트가 초기화 되었고

    액터 자신이 초기화 될 수 있는 상태일 때 호출되는 함수. 준비과정에 해당.

    BeginPlay()

    이 함수가 호출되면서 액터는 자신에게 주어진 로직을 수행하기 시작. 게임참여 과정.

    Tick(float DeltaSeconds)

    BeginPlay() 함수가 한 번 호출되는 함수라면, Tick() 함수는 매 프레임마다 호출됨.

    프레임이란 화면을 그려내는 랜더링 단위. 

    Tick() 함수의 매개변수인 DeltaSeconds는 이전 프레임부터 현재 프레임까지 걸린 시간.

    EndPlay()

    게임에서 액터가 소멸될때 호출되는 함수. 퇴장 과정.

  

  - Tick() 함수를 활용한 액터의 움직임 구현 실습

<hide/>

// SElectricityPole.h

...
class STUDYPROJECT_API ASElectricityPole : public AActor
{
    ...
    
public:	
    ...

    virtual void PostInitializeComponents() override;

    virtual void Tick(float DeltaSeconds) override;

private:
    ...

    UPROPERTY(EditInstanceOnly, BlueprintReadOnly, Category = ASElectricityPole, meta = (AllowPrivateAccess))
    float RotationSpeed;

};
<hide/>

// SElectricityPole.cpp


...

ASElectricityPole::ASElectricityPole()
{
    PrimaryActorTick.bCanEverTick = true;
    
    ...
    
}

void ASElectricityPole::PostInitializeComponents()
{
    Super::PostInitializeComponents();

    RotationSpeed = 300.f;
}

void ASElectricityPole::Tick(float DeltaSeconds)
{
    Super::Tick(DeltaSeconds);

    AddActorLocalRotation(FRotator(0.f, RotationSpeed * DeltaSeconds, 0.f));
}

 

3.3-2 Movement Component

  - 위 예제처럼 Tick() 함수를 활용해서 회전 시킬 수도 있지만,

    무브먼트 컴포넌트를 이용해서 회전 시킬 수도 있음.

 

  - 언리얼에서 제공하는 무브먼트 컴포넌트

    FloatingPawnMovement

    중력의 영향을 받지 않는 액터의 움직임을 제공. 입력에따라 자유롭게 움직임.

    RotatingMovement

    지정한 속도로 액터를 회전시킴.

    InterpMovement

    지정한 위치로 액터를 이동시킴.

    ProjectileMovement

    액터에 중력 영향을 주어서 포물선을 그리는 발사체의 움직임을 제공. 총알, 미사일 등.

 

  - RotatingMovement 실습

<hide/>

// SElectricityPole.h

...
class STUDYPROJECT_API ASElectricityPole : public AActor
{
    ...

private:
    ...

    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = ASElectricityPole, meta = (AllowPrivateAccess))
    TObjectPtr<class URotatingMovementComponent> RotatingMovementComponent;

};
<hide/>

// SElectricityPole.cpp


...
#include "GameFramework/RotatingMovementComponent.h"

ASElectricityPole::ASElectricityPole()
{
    PrimaryActorTick.bCanEverTick = false;

    ...

    RotatingMovementComponent = CreateDefaultSubobject<URotatingMovementComponent>(TEXT("RotatingMovementComponent"));
}

void ASElectricityPole::PostInitializeComponents()
{
    ...

    RotationSpeed = 300.f;
    RotatingMovementComponent->RotationRate = FRotator(0.f, RotationSpeed, 0.f);
}

void ASElectricityPole::Tick(float DeltaSeconds)
{
    Super::Tick(DeltaSeconds);

    // AddActorLocalRotation(FRotator(0.f, RotationSpeed * DeltaSeconds, 0.f));
}

 

  - 모든 컴포넌트는 액터 컴포넌트를 상속 받음.

    씬 컴포넌트는 액터 컴포넌트를 상속 받고 트랜스폼 속성이 추가된 컴포넌트.

    스태틱매시 컴포넌트나 박스 컴포넌트의 경우에는 씬 컴포넌트를 상속 받음.

    반면에 트랜스폼 속성이 필요 없는 무브먼트 컴포넌트들은 액터 컴포넌트를 상속 받음.

 

  - 프로젝트 폴더 > Source > Public 폴더의 AAPSculpture.h와 Private 폴더의 AAPSculpture.cpp 파일 삭제

    .vs / Binaries / Intermediate 폴더들과 StudyProject.sln 파일 제거.

    .uproject 파일 우클릭 > Generate Visual Studio project files 클릭.

    제거 후 에디터를 열면 메세지 로그가 뜨지만 비우기 해도됨.

    레벨에 간단한 변경 사항을 발생시키고 저장. 이러면 추후 메세지 로그 안뜸.

 

  - 숙제

    모닥불 구현

    나무 / 불타는이펙트 / 광원 / 소리