【UE】Unreal C++ の学習メモ(随時更新)

Unreal Engine

プリプロセッサディレクティブ

プリプロセッサは、ソースコードをコンパイラーに渡す前に実行される前処理です。

プリプロセッサ ディレクティブ | C言語

pragma once

通常、とあるヘッダーファイルを他の複数のファイルで include すると、それぞれのファイルでヘッダーファイルの内容が複数回展開され、同じ定義が複数見つかる事で重複定義エラーを引き起こす。
それを事前に防ぐするために使用されるのが「#pragma once」である。
ヘッダーファイルに「#pragma once」と記述すると、そのヘッダーファイルが 1 度だけインクルードされるようにコンパイラに指示する事が出来る。
B.h と C.h で「#include “A.h” 」と書く場合、A.h に「#pragma once」と記述すると A.h が複数回展開されなくなる。
(「#pragma once」は C++ 標準の機能ではなく、特定のコンパイラのみがサポートしている機能)

pragma region

以下のように記述する事でソースコード内の任意の範囲を折り畳む事が出来る。

#pragma region {表示名}
    //メンバ変数など
#pragma endregion

依存関係の解決

include

主に他のヘッダーファイルを現在のファイルに読み込み、その場所に展開するために使用する。

#include "{フォルダ名}/{ファイル名}.h"

using

名前空間や型の別名の定義

「using {別名} = {型名};」と記述する事で名前空間や型の別名を定義する事が出来る。
以下のコードでは FString クラスを「str」という別名で使用できるようにしている。

using str = FString;//別名を定義

void ASampleActor::Hoge()
{
    str Message = TEXT("Hello World");

    UE_LOG(LogTemp, Log, TEXT("%s"), *Message);
}

名前空間名の省略

本来なら「{名前空間名}::{関数名}()」と記述しなくてはいけない場合でも「using namespace {名前空間名};」を追加する事で「{関数名}()」のみの記述でもエラーを吐かなくなるが、その名前空間に宣言されている変数や関数と同じ名前のものがスコープ内に存在する場合は注意が必要。

#pragma once

namespace SampleNamespace
{
    void SampleFunction()
    {

    }
}
#include "SampleNamespace.h"// using のみではなく include も必要

using namespace SampleNamespace;// using で名前空間をインポート

void ASampleActor::Hoge()
{
    //本来なら「SampleNamespace::SampleFunction()」と記述しないとエラーを吐くが、using を使用する事で関数名のみの記述でもエラーを吐かなくなる
    SampleFunction();
}

前方宣言

外部のクラスのポインタや参照を使用する際、そのクラスの詳細(メンバ)を知る必要が無い場合に使用する。
include とは異なり、ヘッダーファイルを読み込む訳ではないためコンパイル時間を減らしたり、循環参照を避けたりする事が出来る。

#pragma once

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

//「#include "Camera/CameraComponent.h"」と記述してもエラーを吐かないようにする事は出来るが、依存性が強くなったり、コンパイル時間が長くなったりする恐れがある
class UCameraComponent;

UCLASS()
class SAMPLE_API ASampleActor : public AActor
{
    GENERATED_BODY()

private:
    UCameraComponent* CameraComponent;
};

マクロ

UPROPERTY

アクセス権(全体)

プロパティ指定子レベルエディタでの閲覧レベルエディタでの編集ブループリントエディタ等での閲覧ブループリントエディタ等での編集
EditAnywhere
VisibleAnywhere不可不可
EditDefaultsOnly不可不可
VisibleDefaultsOnly不可不可不可
EditInstanceOnly不可不可
VisibleInstanceOnly不可不可不可
参考:UE4 よく使うUPROPERTYメモ

アクセス権(ブループリント)

「BlueprintReadWrite」ではブループリントから Get も Set も出来るが、

「BlueprintReadOnly」では Get しか出来なくなる。

private なメンバ変数に対するアクセス権

通常、private なメンバ変数に対して「BlueprintReadWrite」等を付けると「BlueprintReadWrite should not be used on private members」というエラーが発生する。
しかし、以下のように「AllowPrivateAccess」を true に設定する事でエラーが無くなり、ブループリントからでも private なメンバ変数にアクセス出来るようになる。

private:
    UPROPERTY(BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))//「BlueprintReadWrite」のみではエラーが発生する
    int32 Num;

「AllowPrivateAccess」を true に設定した private なメンバ変数に他の C++ クラスからアクセスしようとすると「’ASampleActor::Num’: cannot access private member declared in class ‘ASampleActor’」といったエラーが発生するが、他のクラスが C++ ではなくてブループリントの場合はアクセス出来てしまう。

TitleProperty

構造体を配列などで使用する際に以下のように記述する事で、構造体の特定のメンバの値を配列の要素のタイトルにする事が出来る。

#pragma once

#include "CoreMinimal.h"
#include "SampleStruct.generated.h"

USTRUCT(BlueprintType)
struct FSampleStruct
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere)
    FString Name;
	
    UPROPERTY(EditAnywhere)
    int32 Num;
};
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "SampleStruct.h"
#include "SampleActor.generated.h"

UCLASS()
class SAMPLE_API ASampleActor : public AActor
{
    GENERATED_BODY()

private:
    UPROPERTY(EditAnywhere, meta = (TitleProperty = "Name"))// TitleProperty には構造体のメンバの名前を設定する
    TArray<FSampleStruct> SampleStructs;
};

この例では FSampleStruct 構造体の Name という名前のメンバを配列の要素のタイトルに使用。

FString 型以外にも float 型なども配列の要素のタイトルに使用できる。

正しくないメンバの名前を「TitleProperty」に設定した場合は「Invalid Title Property!」と表示される。

修飾子・キーワード

関数に対する static と const

static 関数

「UFUNCTION(BlueprintPure)」を付けた static 関数は下画像のような見た目になり、その関数を宣言しているクラスのインスタンスが無くても使用できる。
(クラスのインスタンスに依存しない)

UFUNCTION(BlueprintPure)//当然、BlueprintCallable 等でも OK
static bool StaticFunction();//前方に static を付ける

const メンバ関数

一方、関数名の後ろに const を付けた const メンバ関数は「UFUNCTION(BlueprintCallable)」であってもピュア関数のような見た目になるが、その関数を宣言しているクラスのインスタンスを Target に接続しないとコンパイルエラーが発生する。
(クラスのインスタンスに依存する)

UFUNCTION(BlueprintCallable)
bool ConstFunction() const;//後方に const を付ける

static 関数と const メンバ関数の違い

static 関数ではそのクラスの静的ではないメンバを読み取ろうとしたり、書き込もうとするとコンパイルエラーが発生するが、const メンバ関数ではそのクラスの静的ではないメンバを読み取る事が出来る。

// static 関数
bool ASampleActor::StaticFunction()
{
    // static 関数はそのクラスの静的ではないメンバを参照できないため、コンパイルエラーが発生する
    return MemberVariable;
}

// const メンバ関数
bool ASampleActor::ConstFunction() const
{
    //クラスの静的ではないメンバを読み取る事は出来るが、書き込む事は出来ない
    //MemberVariable = true;

    // static 関数ではないためコンパイルエラーは発生しない
    return MemberVariable;
}

template

テンプレートの自作

関数テンプレート

通常、関数を宣言する際は以下のように引数と戻り値の型を指定しておく必要がある。

//引数も戻り値も int32
int32 Add(int32 A, int32 B);

しかし、template を使用して以下のように関数テンプレートを宣言/定義する事で、その関数の利用者が引数と戻り値にあらゆる型を指定できるようになる。

template<typename T>
T Add(T A, T B)
{
    return A + B;
}

上記の「Add()」を使用して引数に整数型を指定すると当然、整数型どうしは加算できるためコンパイルエラーは発生しない。

int32 IntResult = Add<int32>(1, 2);

しかし、以下のように加算演算子の定義されていない構造体などを引数に指定するとコンパイルエラーが発生する。

FSampleStruct SampleStruct1;
FSampleStruct SampleStruct2;

//「error C2678: binary '+': no operator found which takes a left-hand operand of type 'T' (or there is no acceptable conversion)」が発生する
FSampleStruct StructResult = Add<FSampleStruct>(SampleStruct1, SampleStruct2);
クラステンプレート

以下のようにクラス自体をテンプレートにする事も可能。
基本的に TMap や TArray 等の「T」から始まるものはほぼクラステンプレート。

template<typename T>
class TSampleClassTemplate
{
public:
    T Value;
};

TEnumAsByte

以下のように1バイトを超えるサイズの列挙型を定義してブループリントからその列挙型を利用しようとするとピンの色が緑色ではなくなり、「unsupported_enum_type: enum size is larger than a byte」と表示される。

UENUM()
enum class ESampleEnum : int32 //ここで列挙型のサイズを32ビット(4バイト)に設定
{
    A,
    B,
    C
};

対処法としては列挙型を定義する際にその列挙型のサイズを int32(4バイト)から uint8(1バイト)に変更するか、「UENUM()」に「BlueprintType」を指定して、uint8 以外のサイズの列挙型を定義できなくさせる必要がある。
「UENUM()」に「BlueprintType」を指定してある状態で1バイト以外のサイズの列挙型を定義しようとすると「Invalid BlueprintType enum base – currently only uint8 supported」というコンパイルエラーが発生する。

UENUM(BlueprintType)//「UENUM()」に「BlueprintType」を指定するとそもそも1バイト以外のサイズの列挙型を定義できなくなる
enum class ESampleEnum : uint8 //サイズを8ビット(1バイト)に変更する
{
    A,
    B,
    C
};

列挙子が非常に多かったり、符号付き整数を使用したかったりと、どうしても列挙型自体のサイズを1バイトに変更できない場合はその列挙型を利用した変数を宣言する際に「TEnumAsByte<>」でラップし、その変数を1バイトに変換する事が出来る。

UPROPERTY(EditAnywhere, BlueprintReadWrite)
TEnumAsByte<ESampleEnum> SampleEnum;

一応、uint8 ではなく int8 を使用する事で列挙型のサイズを1バイトに抑えつつ符号付き整数を使用する事も可能。
(以下は「Runtime/ImageWrapper/Public/IImageWrapper.h」の EImageFormat のコード)

enum class EImageFormat : int8
{
    /** Invalid or unrecognized format. */
    Invalid = -1,

    /** Portable Network Graphics. */
    PNG = 0,

    /** Joint Photographic Experts Group. */
    JPEG,

    /** Single channel JPEG. */
    GrayscaleJPEG,	

    /** Windows Bitmap. */
    BMP,

    /** Windows Icon resource. */
    ICO,

    /** OpenEXR (HDR) image file format. */
    EXR,

    /** Mac icon. */
    ICNS,
	
    /** Truevision TGA / TARGA */
    TGA,

    /** Hdr file from radiance using RGBE */
    HDR,

    /** Tag Image File Format files */
    TIFF,

    /** DirectDraw Surface */
    DDS,
};

friend

フレンドクラス

通常、とあるクラスの private なメンバに他のクラスからアクセスしようとすると「error C2248: cannot access private member declared in class ”」というコンパイルエラーが発生する。
以下の例では USampleObject クラスの private な「PrivateFunction()」に ASampleActor クラスからアクセスしようとしている。

UCLASS()
class SAMPLE_API USampleObject : public UObject
{
    GENERATED_BODY()

private:
    void PrivateFunction();
};
void ASampleActor::Hoge()
{
    USampleObject* SampleObject = NewObject<USampleObject>();

    //「error C2248: 'USampleObject::PrivateFunction': cannot access private member declared in class 'USampleObject'」というコンパイルエラーが発生する
    SampleObject->PrivateFunction();
}

しかし、private なメンバにアクセスされる側のクラスで friend を用いて他のクラスをフレンドクラスに設定するとそのクラスから private なメンバにアクセス出来るようになる。

#UCLASS()
class SAMPLE_API USampleObject : public UObject
{
    GENERATED_BODY()

private:
    void PrivateFunction();

    // ASampleActor クラスをフレンドクラスに設定する
    friend class ASampleActor;
};
void ASampleActor::Hoge()
{
    USampleObject* SampleObject = NewObject<USampleObject>();

    // ASampleActor クラスは USampleObject クラスのフレンドクラスであるためコンパイルエラーは発生しない
    SampleObject->PrivateFunction();
}

フレンド関数

通常、どのクラスにも属さないフリー関数がとあるクラスの private なメンバにアクセスしようとすると「error C2248: cannot access private member declared in class ”」というコンパイルエラーが発生する。
以下の例では「AccessToSampleObjectPrivateVariable()」というフリー関数が USampleObject クラスの private な PrivateVariable 変数にアクセスしようとしている。

UCLASS()
class SAMPLE_API USampleObject : public UObject
{
    GENERATED_BODY()

private:
    FString PrivateVariable;

    void AccessToSampleObjectPrivateVariable(USampleObject* SampleObject);
};
//どのクラスにも属さないフリー関数
void AccessToSampleObjectPrivateVariable(USampleObject* SampleObject)
{
    //「error C2248: 'USampleObject::PrivateVariable': cannot access private member declared in class 'USampleObject'」というコンパイルエラーが発生する
    UE_LOG(LogTemp, Log, TEXT("%s"), *SampleObject->PrivateVariable);
}

しかし、フリー関数を宣言する際に friend を用いてそのクラスのフレンド関数に設定するとそのフリー関数から private なメンバにアクセス出来るようになる。

UCLASS()
class SAMPLE_API USampleObject : public UObject
{
    GENERATED_BODY()

private:
    FString PrivateVariable;

    //フリー関数「AccessToSampleObjectPrivateVariable()」をフレンド関数に設定する
    friend void AccessToSampleObjectPrivateVariable(USampleObject* SampleObject);
};
//どのクラスにも属さないフリー関数
void AccessToSampleObjectPrivateVariable(USampleObject* SampleObject)
{
    //「AccessToSampleObjectPrivateVariable()」は USampleObject クラスのフレンド関数であるためコンパイルエラーは発生しない
    UE_LOG(LogTemp, Log, TEXT("%s"), *SampleObject->PrivateVariable);
}

演算子

デリファレンス演算子

「UE_LOG()」等で FString 型の変数の値を渡す際に以下のように記述する事がある。

UE_LOG(LogTemp, Log, TEXT("%s"), *StringVariable);

エンジン利用者は Unreal C++ やブループリントでは基本的に TCHAR 型ではなく FString 型で文字列を管理する事が出来るが、「UE_LOG()」のように引数が FString 型ではなく TCHAR* 型のものが時々存在し、その場合は デリファレンス演算子(*)を使用する事で FString 型から TCHAR* 型に変換する事が出来る。

FString StringVariable = TEXT("Hello World");

//「error C2440: 'initializing': cannot convert from 'FString' to 'const TCHAR *'」が発生する
const TCHAR* CharPtr = StringVariable;

// FString 型から TCHAR* 型に変換しているためエラーを吐かない
const TCHAR* CharPtr = *StringVariable;

パス結合演算子

FString 型の値どうしにパス結合演算子(/)を使用する事でパスを簡単に結合する事が出来る。
以下のいずれの場合においても Path 変数には「C:/Program Files/Epic Games/UE_5.3」という文字列が入る。

//結合部分にスラッシュは無い
FString Path = FString(TEXT("C:/Program Files/Epic Games")) / FString(TEXT("UE_5.3"));
//結合部分の前にスラッシュがある
FString Path = FString(TEXT("C:/Program Files/Epic Games/")) / FString(TEXT("UE_5.3"));
//結合部分の後ろにスラッシュがある
FString Path = FString(TEXT("C:/Program Files/Epic Games")) / FString(TEXT("/UE_5.3"));

ただし、以下のように結合部分の前にも後ろにもスラッシュがある場合は「C:/Program Files/Epic Games//UE_5.3」という文字列になってしまう。
(UE 5.3.2 で確認)

//結合部分の前にも後ろにもスラッシュがある
FString Path = FString(TEXT("C:/Program Files/Epic Games/")) / FString(TEXT("/UE_5.3"));

符号なし整数型

int 型の値を宣言する際に「unsigned」を付ける事でその変数に負の値を代入できないようにする事が出来る。

unsigned int Num = 1;

通常、32ビットの int 型で表せる値は -2,147,483,648~2,147,483,647 だが、「unsigned」を付ける事で負の数を表すのに使用していた分を全て正の数を表すために使用する事が出来るため、32ビットの符号なし整数型の表せる値は 0~4,294,967,295 となる。

汎用ポインタ型

float* では float 型のポインタのみ格納でき、FSampleStruct* では FSampleStruct 型のポインタのみ格納できるというように通常のポインタ型ではその型以外のポインタは格納できないが、汎用ポインタ型(void*)を使用する事であらゆる型のポインタを格納できるようになる。

//構造体型の変数のポインタを取得
FSampleStruct SampleStruct;
FSampleStruct* SampleStructPtr = &SampleStruct;

//「FSampleStruct*」と「float*」では型が異なるためコンパイルエラーが発生する
float* FloatPtr = SampleStructPtr;

//汎用ポインタ型ではあらゆる型のポインタを格納する事が出来るためコンパイルエラーは発生しない
void* VoidPtr = SampleStructPtr;

最後に

参考記事

プリプロセッサディレクティブ

依存関係の解決

マクロ

修飾子・キーワード

お問い合わせ

    タイトルとURLをコピーしました