[25. The Pawn Class]

 

// Critter.h
public:
    UPROPERTY(EditAnywhere)
    UStaticMeshComponent* MeshComponent;

    UPROPERTY(EditAnywhere)
    class UCameraComponent* Camera;

// Critter.cpp
#include "Camera/CameraComponent.h"

ACritter::ACritter()
{
    // 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;

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

    MeshComponent->SetupAttachment(GetRootComponent());

    Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
    Camera->SetupAttachment(GetRootComponent());
    Camera->SetRelativeLocation(FVector(-300.f, 0.f, 300.f));
    Camera->SetRelativeRotation(FRotator(-45.f, 0.f, 0.f));
}

 

C++ 클래스로 폰을 상속받아 만든 후 블루프린트 클래스로 만들어서 월드에 배치하면 액터를 배치했던 것 처럼 따로 조작하거나 해당 폰의 카메라로 보거나 하지 않는다.

게임 모드가 지정되지 않았기 때문이다.

 

Critter 클래스를 블루프린트 클래스로 만들어주고(Critter_BP) 스태틱 메시를 Shape_Cone으로 설정한다.

그리고 기본적으로 생성되어있던 FirstProjectGameModeBase 클래스를 블루프린트 클래스(CritterGameMode_BP)로 만들어준다.

 

 

Default Pawn Class만 Critter_BP로 바꿔주고 월드 세팅의 게임 모드를 방금 만든 CritterGameMode_BP로 변경해준다.

실행해보면 카메라가 살짝 뒤쪽에 배치되어 대각선으로 원뿔 오브젝트를 바라보게 된다.

 

[26. Pawn Movement Input #1]

 

Scale은 나중에 바인딩 될 함수에 반환되는 값이다.

프로젝트 세팅에서 바인딩을 지정해줬다면 이제 C++ 코드에서 진짜 바인딩을 해야한다.

 

// Critter.h
private:
    void MoveForward(float Value);
    void MoveRight(float Value);

// Critter.cpp
void ACritter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    Super::SetupPlayerInputComponent(PlayerInputComponent);
    
    // 프로젝트에 바인딩한 이름, 대상 객체, 대상 객체의 메소드 주소
    PlayerInputComponent->BindAxis(TEXT("MoveForward"), this, &ACritter::MoveForward);
    PlayerInputComponent->BindAxis(TEXT("MoveRight"), this, &ACritter::MoveRight);
}

 

여기까지는 바인딩만 한 것이라서 실행을 해도 아무런 변화가 없다.

 

 

// Critter.h
private:
    void MoveForward(float Value);
    void MoveRight(float Value);

    FVector CurrentVelocity;
    float MaxSpeed;
    
// Critter.cpp
ACritter::ACritter()
{
    CurrentVelocity = FVector(0.f);
    Maxspeed = 100.f
}

void ACritter::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

    FVector NewLocation = GetActorLocation() + (CurrentVelocity * DeltaTime);
    SetActorLocation(NewLocation);
}

void ACritter::MoveForward(float Value)
{
    // Clamp: 값이 최소, 최대값 범위를 넘으면 최소, 최대값을 반환해준다
    CurrentVelocity.X = FMath::Clamp(Value, -1.f, 1.f) * MaxSpeed;
}

void ACritter::MoveRight(float Value)
{
    CurrentVelocity.Y = FMath::Clamp(Value, -1.f, 1.f) * MaxSpeed;
}

 

이제 wasd 입력시 정상적으로 잘 움직인다.

하드 코딩이 아닌 에디터에서 편집하고 싶다면 maxSpeed를 public 영역으로 빼내고 UPROPERTY 매크로를 붙여서 에디터에서 편집할 수 있다.

 

[27. Pawn Movement Input #2]

 

시작에 앞서 Pawn을 상속받은 C++ 클래스인 Collider를 하나 만들어주자.

 

getter, setter 메소드는 기능이 아주 단순해서 클래스에서 정의와 선언을 한번에 해버리는 경우가 많다.

그런 경우 접두어로 FORCELINE을 붙여주면 된다. 단, 이경우 UFUNCTION과 같이 동작하지 않는다.

 

// Collider.h
UPROPERTY(VisibleAnywhere, Category = "Mesh")
UStaticMeshComponent* MeshComponent;

UPROPERTY(VisibleAnywhere, Category = "Mesh")
class USphereComponent* SphereComponent;

FORCEINLINE UStaticMeshComponent* GetMeshComponent() { return MeshComponent; }
FORCEINLINE void SetMeshComponent(UStaticMeshComponent* Mesh) { MeshComponent = Mesh; }

FORCEINLINE USphereComponent* GetSphereComponent() { return SphereComponent; }
FORCEINLINE void SetMeshComponent(USphereComponent* Sphere) { SphereComponent = Sphere; }

// Collider.cpp
#include "Components/SphereComponent.h"

ACollider::ACollider()
{
    RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootComponent"));
    SphereComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent"));

    SphereComponent->SetupAttachment(GetRootComponent());
    SphereComponent->InitSphereRadius(40.f);
    SphereComponent->SetCollisionProfileName(TEXT("Pawn"));
    
    AutoPossessPlayer = EAutoReceiveInput::Player0;
}

 

구체 컴포넌트를 만들어서 루트 컴포넌트의 하위 계층에 붙인 뒤, 반지름을 정하고 콜리전 프리셋을 붙인다.

 

 

여기까지만 만들고 C++ 클래스를 Collider_BP 블루프린트 클래스로 만들어서 들어가보면 생각했던 구체는 아닌것이 그려져있다.

어차피 시각화를 위한 구체가 아닌 충돌을 위한 구체이므로 그려지지 않아도 상관없다.

 

Hidden In Game이 체크되어있으면 플레이시 보이지 않고 해제하면 보이게 된다.

Collider_BP를 월드에 배치해서 확인할 수 있다.

 

메시를 하드코딩해서 직접 설정할수도 있지만 일반적으로 좋은 생각은 아니다.

에디터에서 변경하는 것이 좋다.

 

// Collider.h
ACollider::ACollider()
{
    static ConstructorHelpers::FObjectFinder<UStaticMesh> MeshComponentAsset(TEXT("StaticMesh'/Game/StarterContent/Shapes/Shape_Sphere.Shape_Sphere'"));

    if (MeshComponentAsset.Succeeded())
    {
	MeshComponent->SetStaticMesh(MeshComponentAsset.Object);
	MeshComponent->SetRalativeLocation(FVector(0.f, 0.f, -40.f));
	MeshComponent->SetWorldScald3D(FVector(0.8f, 0.8f, 0.8f));
    }
}

 

만약 하드코딩을 한다면 이렇게 할 수 있다. ConstructorHelpers라는 이름에서 보다시피 생성자에서만 할 수 있다.

 

[28. Pawn Movement Input #3]

 

Collider에 스프링 암과 카메라를 붙여보도록 하자.

 

// Collider.h
UPROPERTY(VisibleAnywhere, Category = "Mesh")
class UCameraComponent* Camera;

UPROPERTY(VisibleAnywhere, Category = "Mesh")
class USpringArmComponent* SpringArm;

// Collider.cpp
#include "Camera/CameraComponent.h"
#include "GameFramework/SpringArmComponent.h"

ACollider::ACollider()
{
	// .. 생략
    SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
    SpringArm->SetupAttachment(GetRootComponent());
    SpringArm->SetRelativeRotation(FRotator(-45.f, 0.f, 0.f));
    SpringArm->TargetArmLength = 400.f;
    SpringArm->bEnableCameraLag = true;
    SpringArm->CameraLagSpeed = 3.f;

    Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
    Camera->SetupAttachment(SpringArm, USpringArmComponent::SocketName);
}

 

getter, setter도 만들어주자.

 

컴파일 후 블루프린트 클래스에 들어오면 카메라가 스프링 암에 잘 붙어있게된다.

 

 

 

 

 

'언리얼 엔진 > UE4 C++' 카테고리의 다른 글

[UE C++] The Character Class #1  (0) 2022.09.05
[UE C++] The Pawn Class #2  (0) 2022.09.05
[UE C++] The Actor Class #2  (0) 2022.09.04
[UE C++] The Actor Class #1  (0) 2022.09.03
[UE C++] Intro to Unreal Engine C++  (0) 2022.09.03

[17. Intro to Collision #1]

 

복합 콜리전을 이용하면 연산량이 많아서 느려지게 된다.

 

FBX 파일에 기본적으로 콜리전 처리가 되어있지 않으면 단순 콜리전을 선택해도 아무런 변화가 없다. (콜리전이 없어서 표시가 안됨)

콜리전 메뉴에서 박스 단순화 콜리전 추가를 한 후에 해당 메시를 포함하고 있는 Floater_BP의 디테일 탭에서 피직스 카테고리에 접근한다.

 

Simulate Physics : 물체의 피직스를 시뮬레이션 함
MassInKg : 물체의 기본 질량을 무시하고 재정의
Linear Damping : 평행 이동 느리게
Angular Damping : 회전 동작 느리게

 

오브젝트의 모빌리티가 무버블, 심플 콜리전이 있는 경우 시뮬레이트 피직스(Simulate Physics) 옵션을 체크하면 에디터에서 시뮬레이트나 에디터에서 플레이를 클릭했을 때 오브젝트에 피직스가 적용된다.

 

해당 블루프린트 클래스를 월드에 배치해서 적당한 높이에 둔 후에 실행을 누르면 중력을 받아 떨어지다가 땅에 닿으면 튀어오르게 된다.

다만, 박스 단순 콜리전이 적용된 상태라서 실제 메쉬와는 일치하지 않고 박스의 충돌처리가 이루어져서 조금 어색하다.

 

[18. Collision]

 

 

Floater_BP에서 해당 메소드를 선택하면 경고가 뜬다. C++에서 블루프린트에 관련된 속성을 넣어주지 않았기 때문이다.

// Floater.h
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "ActorMeshComponents")
UStaticMeshComponent* StaticMesh;

 

BlueprintReadWrite 속성을 추가해주면 경고가 사라진다.

 

콜리전이 존재한다면 충돌처리 자체는 다 되지만, Sunulate Physics가 체크된 물체들은 물리학까지 적용받게 된다.

 

Floater_BP의 AddForce를 X값 2천만 정도로 설정하고 위치를 테이블과 의자쪽으로 옮기고 실행하면 충돌처리가 잘 일어난다.

 

기본적으로 존재하던 테이블이나 의자 등의 오브젝트의 Simulate Physics를 체크해주면 마찬가지로 물리학이 적용된다.

 

피직스의 Enable Gravity 옵션을 비활성화하면 중력이 적용되지 않아서 오브젝트가 절대 떨어지지 않는다.

 

플레이 도중 ` 키를 눌러서 콘솔창을 열고 show collision을 입력하면 오브젝트마다 콜리전 영역이 표시된다.

 

[19. Sweeping]

 

 Shape_Cube를 하나 생성해서 크기를 적당히 늘리고 Floater_BP를 뒤쪽에 배치한다.

 

Floater_BP의 이벤트그래프에서 AddForce 메소드를 지우고 월드 디테일에서 값을 위와같이 설정해준다. Simulate Physics도 체크 해제해준다.

실행해보면 Floater_BP가 메시를 뚫고 이동하는것을 볼 수 있다.

Simulate Physics를 다시 체크해주면 중력을 받아서 떨어진 후에 벽에 박히게 된다.

 

이전에 Floater의 Tick에서 작성했던 AddActorLocalOffset의 두 번째 인자가 sweep인데, true로 설정하면 Simulate Physics를 켜두지 않았을 때 관통하던것이 이제는 벽에 가로막히게 된다.

 

추가로, FHitResult는 구조체로써 다양한 정보를 담을 수 있다.

AddActorLocalOffset에 매개변수로 들어가는데, 주소값을 넘겨주므로 충돌이 발생했을 때 다양한 정보를 담게 된다.

그 중에 Location은 충돌이 발생한 지점을 FVector로 저장한다.

 

// Floater.cpp
void AFloater::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

    if (bShouldFloat)
    {
        FHitResult HitResult;
        AddActorLocalOffset(InitialDirection, true, &HitResult);

        FVector HitLocation = HitResult.Location;

        UE_LOG(LogTemp, Warning, TEXT("Hit Location: x = %f, y = %f, z = %f"), HitLocation.X, HitLocation.Y, HitLocation.Z);
    }
}

 

위와 같이 작성하고 실행하면 출력 로그에 0으로만 찍히던 것이 벽과 충돌이 발생한 시점부터 좌표가 찍히게 된다.

 

별개로, 충돌하는 두 오브젝트가 있을 때, 둘 중 한쪽만 콜리전 반응을 겹침, 또는 무시로 설정하면 충돌이 발생하지 않게 된다.

sweep과는 상관 없이 발생하지 않게 된다.

 

[20. Local vs World Offset]

 

매개변수 : FVector(좌표), bSweep(스윕여부), FHitResult(충돌체 정보)

AddActorLocalOffset : 로컬 좌표계 기준으로 좌표를 더한다.

AddActorWorldOffset : 월드 좌표계 기준으로 좌표를 더한다.

 

매개변수 : FRotator(좌표)

AddActorLocalRotation : 로컬 좌표계 기준으로 회전한다.

AddActorWorldRotation : 월드 좌표계 기준으로 회전한다.

 

pitch - y / yaw - z / roll - x 이다.

 

뷰포트에서 Ctrl+`를 눌러서 월드/로컬 기준 좌표계를 변경할 수 있다.

 

[21. Force and Torque]

 

회전하는 힘: Torque

 

AddForce와 AddTorque를 통해 물체에 힘을 가할 수 있다.

참고) AddTorqueInRadians 사용을 권장한다. AddTorque를 사용해도 어차피 AddTorqueInRadians를 호출한다.

 

// Floater.h
UPROPERTY(EditInstanceOnly, BlueprintReadWrite, Category = "Floater Variables")
FVector InitialForce;

UPROPERTY(EditInstanceOnly, BlueprintReadWrite, Category = "Floater Variables")
FVector InitialTorque;

// Floater.cpp
AFloater::AFloater()
{
	// .. 생략
    InitialForce = FVector(2000000.f, 0.f, 0.f);
    InitialTorque = FVector(2000000.f, 0.f, 0.f);
}

// Called when the game starts or when spawned
void AFloater::BeginPlay()
{
	// .. 생략
    StaticMesh->AddForce(InitialForce);
    StaticMesh->AddTorqueInRadians(InitialTorque);
}

 

위의 함수를 사용하려면 Simulate Physics가 켜져있어야 한다.

 

[22. Random Numbers]

 

FMath::FRand()는 0~1사이의 float를 반환해준다.

FMath::FRandRange()는 2개의 float를 인자로 받아서 해당 값의 범위를 반환해준다.

이 외에도 다양한 랜덤 반환값을 돌려주는 함수들이 있다.

 

[23. The Sin Function]

 

위아래로 움직이는것과 같은 진동 동작을 모델링하는데 좋다.

 

종종 연산량이 많아져서 초당 프레임이 떨어질 때, 게임이 정말 느려지는 것처럼 고르지 못하게 된다.

이 때, DeltaTime과 Tick함수를 사용하면 프레임에 상관 없이 잘 동작하게 된다.

 

예를 들어서 120 프레임을 기준으로 초당 120cm씩 움직인다면 60 프레임인 경우 초당 60cm, 120 프레임인 경우 초당 120cm로 서로 다르게 움직이게 된다. 하지만 여기에 DeltaTime을 곱해준다면 프레임에 상관없이 항상 초당 120cm를 움직이게 된다.

 

// Floater.h
private:
    float RunningTime;
    float BaseZLocation;

// Floater.cpp

AFloater::AFloater()
{
	// .. 생략
	RunningTime = 0.f;
}

void AFloater::BeginPlay()
{
	// .. 생략
	BaseZLocation = PlacedLocation.Z;
}

void AFloater::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

    if (bShouldFloat)
    {
        FVector NewLocation = GetActorLocation();

        NewLocation.Z = BaseZLocation + FMath::Sin(RunningTime) * 100.f;

        SetActorLocation(NewLocation);
        RunningTime += DeltaTime;

        UE_LOG(LogTemp, Warning, TEXT("%f"), RunningTime);
    }
}

 

Floater_BP에서 체크박스를 모두 꺼준다.

실행 시켜보면 Z축 ±100으로 움직인다.

 

 

// Floater.h
public:
    // Amplitude : how much we oscillate up and down
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Floater Variables")
    float A;

    // Period : 2 * PI / abs(B)
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Floater Variables")
    float B;

    // Phase Shift : C / B
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Floater Variables")
    float C;

    // Vertical Shift : D
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Floater Variables")
    float D;
    
//
void AFloater::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

    if (bShouldFloat)
    {
        FVector NewLocation = GetActorLocation();
        
        // 중요!
        NewLocation.Z = BaseZLocation + A * FMath::Sin(B * RunningTime - C) + D;

        SetActorLocation(NewLocation);
        RunningTime += DeltaTime;
    }
}

 

  

X축이 시간(RunningTime)일 때, 진폭은 사인 그래프에 곱한 상수 값(A), 주기는 시간에 곱한 상수 값(B), 위상 변화는 시간에서 뺀 상수 값(C), 수직 이동은 결과에 더한 상수 값(D)이다.

 

Sin그래프는 기본적으로 -1 ~ 1 의 값을 가진다.

 

진폭(A) : 그래프의 빨간 곡선을 위 아래로 늘린 것 ([-1 ~ 1] * A)

주기(B) : 실제 주기 계산은 (2 * PI) / ABS(B) 이지만 해당 식도 주기와 관련이 있다. X축(Running Time)의 증가량을 늘리기 때문에 그래프가 좀더 빨리 그려지므로 주기가 짧아지는듯한 효과가 있다 (RunningTime * B)

위상 변화(C) : 그래프의 파란 곡선과 같다. 그래프의 시작이 0이 아닌 다른 값부터 시작하게 한다 (RunningTime - C)

수직 이동(D) : 그래프의 곡선을 위, 아래로 옮긴 것. 수직인 경우에 위상 변화와 유사하다 (Sin - D)

 

[24. Deleting Classes]

 

C++ 클래스는 에디터에서 삭제할 수 없다.

언리얼 엔진과 IDE를 모두 끄고 해당 폴더에서 직접 삭제를 해주어야 한다.

 

C++ 클래스가 있는 폴더에 접근해서 삭제하고 싶은 클래스를 삭제하고, Binaries 폴더까지 삭제한다.

uproject 확장자 파일을 우클릭해서 Generate Visual Studio project files를 누르고 다시 uproject를 실행해주면 된다.

팝업창은 예를 눌러주도록 하자.

 

 

 

'언리얼 엔진 > UE4 C++' 카테고리의 다른 글

[UE C++] The Pawn Class #2  (0) 2022.09.05
[UE C++] The Pawn Class #1  (0) 2022.09.04
[UE C++] The Actor Class #1  (0) 2022.09.03
[UE C++] Intro to Unreal Engine C++  (0) 2022.09.03
[UE C++] Download UE4, and Intro to the Engine  (0) 2022.09.03

[12. Actors and Actor Components]

 

4.26 버전 기준으로 새로운 폴더를 만들어서 C++ 클래스를 넣는 경우 핫 리로드가 실패하게 된다. IDE에서 컴파일도 안된다.

 

// Floater.cpp
#include "GameplayActors/Floater.h" // 이렇게 되어있는것을

#include "Floater.h" // 이렇게 변경

 

폴더 경로를 지워주면 정상적으로 작동한다.

다른 클래스에서 해당 클래스를 포함하려 한다면 그때는 경로가 있어야 한다.

MyFloat의 헤더파일과 cpp파일이 같이 있으므로 경로가 필요 없던것 뿐이지 다른곳에서는 당연히 경로가 필요하다.

 

반대로 MyFloat에서 다른 클래스에 접근하려면 마찬가지로 경로를 지정해 주어야 한다.

 

// Floater.h
UPROPERTY(VisibleAnywhere, Category = "ActorMeshComponents")
UStaticMeshComponent* StaticMesh;

 

해당 코드만 추가해서는 아직 StaticMesh 컴포넌트가 생성되지 않는다.

 

// Floater.cpp
AFloater::AFloater()
{
 	// 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;

	StaticMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("CustomStaticMesh"));
}

 

생성자에서 CreateDefaultSubobject를 통해 새로운 컴포넌트를 생성해주면 컴포넌트가 추가된다.

 

이후 디테일 탭에서 Static Mesh를 아무거나 고르고 월드에 배치하면 해당 메시가 잘 보여진다. 여러개도 배치할 수 있다.

Static Mesh가 루트 컴포넌트이므로 End키로 바닥에 딱 붙일수도 있다. (Default Component가 루트 컴포넌트인 경우에는 절반이 파묻힘)

 

[13. Position Vector]

 

벡터는 기본적으로 공간의 한 점을 나타내는 방법이다.

 

오브젝트를 고르고 Shift를 누른 상태로 위치를 변경하면 해당 오브젝트에 카메라가 고정된 상태로 변경된다.

 

[14. The FVector #1]

 

void AFloater::BeginPlay()
{
	Super::BeginPlay();

	FVector InitialLocation = FVector(0.f, 0.f, 0.f);

	SetActorLocation(InitialLocation);
}

 

MyFloat의 위치를 어떻게 배치하던간에 실행 시 원점으로 자동 배치된다.

FVector는 구조체로 만들어져있다.

 

[15. FVector #2]

 

FVector는 일반적으로 포인터로 선언되지 않는다.

 

// Floater.h
UPROPERTY(EditInstanceOnly, BlueprintReadWrite, Category = "FloaterVectors")
FVector InitialLocation = FVector(0.f, 0.f, 0.f);

 

EditInstanceOnly는 월드상에 배치된 오브젝트의 디테일 패널에서 값을 수정 가능하게 한다.

  

 

여담으로 Edit와 Visible의 차이는 readwrite, readonly 정도의 차이이다.

위는 EditInstanceOnly, 아래는 VisibleInstanceOnly.

 

 

// Floater.h
// Location of the Actor when dragged in from the editor
UPROPERTY(VisibleInstanceOnly, BlueprintReadWrite, Category = "Floater Variables")
FVector PlacedLocation;

// 블루프린트 디테일 창에서만 볼 수 있음
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Floater Variables")
bool bInitialzeFloatLocations;

// Floater.cpp
AFloater::AFloater()
{
 	// 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;

	StaticMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("CustomStaticMesh"));

	InitialLocation = FVector(0.f);
	PlacedLocation = FVector(0.f);

	bInitialzeFloatLocations = false;
}

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

	PlacedLocation = GetActorLocation();

	if (bInitialzeFloatLocations)
	{
		SetActorLocation(InitialLocation);
	}
}

 

 

인스턴스의 디테일 창에서는 뜨지 않고 블루프린트의 디테일 창에서만 뜨게 된다.

 

해당 값을 True로 설정한 경우에만 시작시 인스턴스에서 지정한 InitializeLocation 위치로 이동할 것이고 False인 경우에는 이동하지 않게 된다.

 

UPROPERTY의 속성값에 따라 인스턴스, 블루프린트에서 표시하거나 ReadWrite, ReadOnly를 통해 읽기전용/읽고쓰기를 설정할 수 있게된다.

 

[16. FVector #3]

 

// Floater.h
UPROPERTY(VisibleDefaultsOnly, BlueprintReadWrite, Category = "Floater Variables")
FVector WorldOrigin;

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Floater Variables")
FVector InitialDirection;

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Floater Variables")
bool bShouldFloat;

// Floater.cpp
AFloater::AFloater()
{
	WorldOrigin = FVector(0.f);

	InitialDirection = FVector(0.f);

	bInitialzeFloatLocations = false;
}

void AFloater::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	if (bShouldFloat)
	{
		FHitResult HitResult;
		AddActorLocalOffset(InitialDirection, false, &HitResult);
	}
}

 

 

블루프린트 클래스에서 Should Float를 True로 바꿔주면 조건이 충족되어서 오브젝트들이 매 틱마다 FVector만큼 이동한다.

 

인스턴스에서 Initial Direction을 설정해주면 된다. 여러개의 블루프린트 클래스를 배치하고 각기 다른 값을 주면 서로 다른 방향으로 날아가게 된다.

[7. C++ Refresher and UE4 Hierarch]

 

언리얼의 계층 구조: Object -> Actor -> Pawn -> Character

오브젝트는 레벨에 배치할 수 없고 최소한 액터에서 파생된 클래스여야 한다.

 

각 클래스에서 파생된 클래스는 무수히 많다.

 

Has a 관계로써 Package->World->Level->Actor->Actor Component가 있다.

 

[8. Class Creation in Unreal Engine]

 

캐릭터를 상속받는 C++ 클래스를 만들면 기본적으로 구성이 어느정도 되어있다.

 

ACharacter를 상속받은 것을 볼 수 있는데, F12로 계속 타고 올라가면 Pawn, Actor를 지나서 Object, 심지어 그 위의 상위 클래스가 있다는것을 알 수 있다.

 

생성자의 PrimaryActorTick.bCanEverTick을 false로 두면 Tick함수가 더 이상 동작하지 않는다.

 

[9. Reflection and Garbage Collection]

 

C++에는 기본적으로 Reflection 기능이 없다.

언리얼 엔진 리플렉션 시스템에 보이길 원하는 것은 반드시 SPECIAL_MACRO(특수 매크로)가 붙어 있어야한다.

 

특수 매크로가 붙으면 블루프린트에 보여지거나, 가비지 컬렉터에 의해 관리된다.

 

언리얼 헤더 도구(uht)는 프로젝트가 컴파일 될 때, 특수 매크로의 정보를 수집하는 프로그램이다.

특수 매크로를 발견하면 클래스에 대한 추가 코드가 작성된다.

 

UCLASS(), UPROPERTY(), UFUNCTION() 괄호 안에 함수처럼 매개변수를 사용할 수 있다.

 

[10. Creating a UObject]

 

Object를 상속받은 C++ 클래스는 기본적으로 아무것도 구현이 되어있지 않고 해당 클래스 파일 기반 블루프린트 클래스도 생성할 수 없다.

 

UCLASS(Blueprintable)

 

하지만 UCLASS의 속성을 위와 같이 지정해주면 블루프린트 클래스 생성 버튼이 활성화 되어서 가능해진다.

MyObject_BP로 생성해준다.

 

 

// MyObject.h
UCLASS(Blueprintable)
class FIRSTPROJECT_API UMyObject : public UObject
{
	GENERATED_BODY()

public:
	UMyObject();

	UPROPERTY(BlueprintReadWrite)
	float MyFloat;

	UFUNCTION(BlueprintCallable)
	void MyFunction();
};


// MyObject.cpp
UMyObject::UMyObject()
{
	MyFloat = 0.f;
}

void UMyObject::MyFunction()
{

}

 

코드를 작성한 후 MyObject_BP의 이벤트그래프에 들어가서 MyFloat와 MyFunction을 검색하고 꺼내는것 까지 정상적으로 작동하는 것을 확인할 수 있다. 

 

추가로 Object 클래스이므로 당연히 월드에 배치시킬 수 없다.

 

[11. Using UObject in Blueprints]

 

UPROPERTY(BlueprintReadWrite, Category = "MyVariables")
float MyFloat;

UFUNCTION(BlueprintCallable, Category = "MyFunctions")
void MyFunction();

  

카테고리를 지정해주면 카테고리가 추가되어 그 밑으로 들어가게 된다.

이제 변수와 함수를 카테고리로 정렬할 수 있게 된다.

 

 

UPROPERTY(BlueprintReadOnly, Category = "MyVariables")
float MyFloat;

  

속성을 ReadOnly로 변경 시, Set이 사라지게 된다. 기존에 있던 Set 노드는 더 이상 유효하지 않게 된다.

키워드를 통해 Reflection 시스템이 블루프린트에 변수 및 함수를 노출하는 방법을 결정할 수 있다.

 

void UMyObject::MyFunction()
{
	UE_LOG(LogTemp, Warning, TEXT("This is our warning text!"));
}

 

 

MyActor 블루프린트 클래스에서 변수 생성을 하고 변수 유형을 위와 같이 설정해준다.

 

오브젝트 생성을 해주고, (C++의 new와 비슷함)

 

 Class는 MyObject_BP, Outer는 MyActor를 해주면 MyActor가 MyObjectBP를 생성해서 MyObjectVar에 반환 해준다.

그 후 MyFunction을 실행하면 출력 로그에 지정했던 로그가 잘 나오게 된다.

만약 오브젝트 생성후 반환받지 않고 바로 MyObjectVar를 사용하게 되면 None 에러가 발생하게 된다. 초기화 되지 않은 객체를 접근한거나 다름없는 것이다.

 

[정리]

 

  • UPROPERTY를 붙여서 GC로 관리할 수 있게 하고 리플렉션처럼 보이게 할 수 있다.

[3. The Viewport]

 

기본적으로 그리드 스냅이 지정되어있어서 오브젝트의 트랜스폼 변경시 뚝뚝 끊어지는 느낌이 있는데, 그리드 스냅을 끄면 연속적으로 잘 움직이게 된다.

그리드 스냅의 수치도 조절할 수 있다. 잘 이용하면 땅이나 원하는 위치에 고정하는 데 도움이 될 수 있다.

 

언리얼은 기본적으로 센티미터 단위이다.

 

공중에 떠있는 오브젝트를 땅에 딱 붙이고 싶다면 End키를 누르면 된다. 단, 겹치는 오브젝트가 있으면 안된다.

 

[4. Editor Overview]

 

월드 아웃라이너에서 오브젝트를 찾을 때 더블클릭을 하거나 F키를 누르면 해당 오브젝트로 이동하여 화면 중앙에 위치시켜준다. 카메라 텀블링(선회, orbit)할때 유용하다.

 

카메라 텀블링은 Alt+좌클릭+드래그이다. 뷰포트 중앙을 중심으로 선회한다.

 

Ctrl+(좌/우/좌+우 클릭)+드래그 를 이용해서 각 트랜스폼의 X, Y, Z축을 조절할 수 있다.

W(이동), E(회전), R(스케일)을 먼저 선택해 주어야 한다.

 

[5. The Level Blueprint]

 

유니티의 씬과 비슷한 개념이다. 해당 레벨에서만 사용할 수 있다.

 

 

 

 레벨 블루프린트에서 간단하게 작성한 것을 실행한 결과.

 

[6. Creating Blueprints]

 

모든 액터는 최소한 하나의 씬 컴포넌트를 가지고 있어야 한다.

 

트랜스폼의 모빌리티는 엔진이 라이팅을 만드는 방법과 관련이 있다. (유니티와 동일한 것 같음)

  • 스태틱: 움직일 수 없지만(런타임 중에 변경 불가능) 동적 그림자(Dynamic Shadow)를 생성하기 위해 추가 리소스를 사용하는 것에 대한 걱정이 없어진다.
  • 스테이셔: 조명에 사용 가능. 움직일 수 없지만 런타임 도중 밝기의 변경이 가능하다.
  • 무버블: 동적 그림자를 생성하여 렌더링이 가장 느리다.

 

Construction Script는 오브젝트가 이동하거나 변경될 때 실행되고 이벤트그래프는 게임의 모든 로직을 가질 수 있다.

 

 

이제 씬 컴포넌트에 메시를 추가하고 디테일 패널에서 Static Mesh를 SM_Rock으로 설정하고 트랜스폼의 모빌리티를 무버블로 설정해준다. 

 

이벤트그래프에 들어가서 Tick 이벤트에 AddActorWorldOffset을 만들어서 연결시켜 주고 Delta Location의 X값만 1.0으로 준다.

 

해당 블루프린트를 월드에 배치하고 실행하면, 바위가 매 틱마다 X축으로 1.0씩 이동하게 된다.

+ Recent posts