Edited at

UnrealC++のTips

More than 1 year has passed since last update.

この記事は、Unreal Engine 4 (UE4) Advent Calendar 2016 23日目の記事です。

UnrealC++のTipsということで、情報メモをお届けします。

お役に立てれば嬉しいです。

確信のないものは疑問形になってます。


基本

使うことが多いUE4内のクラス


配列

大体以下の2つを知っていればOKです。


  • TArray

  • TMap


基本型


  • int32

  • float

後ろの数値がバイト数になる。


文字列


  • FString

  • FName

  • FText


クラス


  • UObject

  • AActor

  • APawn

  • UActorComponent

  • UWorld

  • UGameInstance

  • UGameState

  • UGameMode

  • APlayerController


公式記事

公式にも記事がありますので読みましょう。

https://www.unrealengine.com/ja/blog/ue4-libraries-you-should-know-about


UCLASS Tips


UObject継承したとき


  • クラス宣言前にUCLASS必須。

  • クラス名先頭にU必須。
    さらにクラス内宣言でGENERATED_BODY()が必須。(GENERATED_UCLASS_BODY()は古い宣言でレガシーらしい)


  • const FObjectInitializer& ObjectInitializer を引数としたコンストラクタは自分で書ける。
    引数を増やすことは無理っぽい?カスタムコンストラクタという機能があってそれで出来る?

例:

MyGameModule内にクラスを作るとする。


UCLASS()
class MYGAMEMODULE_API UMyClass : public UObject
{
GENERATED_BODY()

public:
UMyClass(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) {}
};


ヘッダーに~.generated.hを書くのが必要なとき

UCLASSやUSTRUCTが存在するヘッダーに追加する必要があり。

includeは最初ではなく、他のincludeするファイルがあったらの最後にしないといけない。

(多分 GENERATED_BODY() が必要なタイプがあるときに必要。 UENUM のときは必要なかった。Blueprintに露出する場合は必要かも?)

存在しないヘッダーに追加した場合は逆にエラーになる。


CPPファイルに必ずIncludeしなくてはいけないファイルが存在する

Moduleの名前になっているヘッダファイル。

HogeHoge.Build.cs だったら、 #include "HogeHoge.h" を一番最初のインクルードすることが必須。


PrefixHeaderはどれ?

 もしModule名がHogeHogeだったら(ビルドソースコードが HogeHoge.Build.cs だったら)、 HogeHoge.h にIncludeしておくとそのHogeHogeModule内でPrefixHeader代わりになる?


Delegate

ExecuteIfBound関数が使えるのは返り値がvoidのDelegateのみ。

以下はDelegateの書き方の例なので参考にしてください。(よく忘れる)

DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FFoobarDelegate, int32, MyId, const FString&, message);

DECLARE_MULTICAST_DELEGATE_OneParam(FHogeHogeResultDelegate, const FMyResult&);

FFoobarDelegate FoobarDelegate;

FoobarDelegate->AddDynamic(this, &HogeHogeClass::OnFoobar);
FoobarDelegate->Broadcast(id, msg);

FHogeHogeResultDelegate HogeHogeResultDelegate;
HogeHogeResultDelegate->AddUObject(this, &HogeHogeClass::OnHogeHogeResult);
HogeHogeResultDelegate->Broadcast(result);

DECLARE_DELEGATE_OneParam(FMyToolSpinBoxClicked, float)

FMyToolSpinBoxClicked OnClicked;

FReply SMyToolMainMenu::OnResetClicked()
{
return FReply::Handled();
}


グローバル変数として持たせられるクラス

ゲームを作っているときに、レベルを跨ぐような変数が欲しい時がある。

その時には UGameInstance のメンバ変数として持たせておくことで、グローバル変数のように使うことが出来る。

(これ以外レベルを跨ぐ変数を保持する方法は、多分ファイルに書き出すとかDBに持たせるとかしないと無理っぽい)


C++に定義されている関数をBlueprintでオーバーライドして実装する方法

細かい方法は、 3. Extend and Override C++ with Blueprintshttps://docs.unrealengine.com/latest/INT/Programming/Tutorials/VariablesTimersEvents/3/index.html

)の項目参照。

以下ざっくり説明。

C++側では、

UFUNCTION(BlueprintNativeEvent)

void CountdownHasFinished();
virtual void CountdownHasFinished_Implementation();

を宣言して、C++側は void CountdownHasFinished() を実装せずに、 virtual void CountdownHasFinished_Implementation()_Implementation が付いている方だけ実装。

void ACountdown::CountdownHasFinished_Implementation()

{
//Change to a special readout
CountdownText->SetText(TEXT("GO!"));
}

Blueprint側は CountdownHasFinished イベントを作成する。

Blueprint側からC++の関数を呼び出したい場合は、 right-clicking the Countdown Has Finished node and selecting Add call to parent function from the context menu. で実行線をつなげる。

また、C++側でオーバーライドしたBlueprintイベントを呼び出したい場合は、 CountdownHasFinished_Implementation ではなく、

CountdownHasFinished を呼ぶ必要があることに注意。

void ACountdown::Test()

{
// CountdownHasFinished_Implementation(); // こっちだと基底のC++の方しか呼び出されない
CountdownHasFinished(); // こっちだとオーバーライドされたBlueprintイベントが呼び出される
}


C++に定義されている関数をBlueprintでオーバーライドし、その関数をC++から呼び出す場合

UFUNCTION 宣言時に BlueprintImplementableEvent を使う。

UFUNCTION(Category = "Player Health", BlueprintImplementableEvent, BlueprintCallable)

void PlayerIsHealthyTick(float CurrentHealth);

したら、UE4Editorで function override から PlayerIsHealthyTick を選択してイベントを作成する。

これで PlayerIsHealthyTick 関数が呼ばれると、オーバーライドしたBlueprintの PlayerIsHealthyTick イベントが呼ばれるようになる。

あとC++から呼ぶときには this-> 必須らしい?

this->PlayerIsHealthyTick(1.0f);

参考:https://wiki.unrealengine.com/Blueprints,_Empower_Your_Entire_Team_With_BlueprintImplementableEvent


オブジェクトの作成


NewObject<T>

通常のnewと同じ感じ。

クラス名からしか指定できないので、Blueprintクラスは生成不可?

Actor生成で使ってはいけない。(はず)

UBillboardComponent* BillboardComponent = NewObject<UBillboardComponent>(this);


CreateDefaultSubobject<T>

コンストラクタでしか使えない。

Defaultのコンストラクタが呼ばれる。

クラス名からしか指定できないので、Blueprintクラスは生成不可?

BlueprintでComponentを追加するときの処理とかはコンストラクタでこれを呼べば可能。

SpriteComponent = CreateDefaultSubobject<UBillboardComponent>(TEXT("Sprite"));


ConstructorHelpers::FObjectFinder<T>

assetファイルから取得する。

.Objectでそのオブジェクトのポインタを取得可能。

static ConstructorHelpers::FObjectFinder<UStaticMesh> CraneArmMesh(TEXT("/Engine/EditorMeshes/Camera/SM_CraneRig_Arm.SM_CraneRig_Arm"));

PreviewMesh_CraneArm = CreateOptionalDefaultSubobject<UStaticMeshComponent>(TEXT("PreviewMesh_CraneArm"));
if (PreviewMesh_CraneArm)
{
PreviewMesh_CraneArm->SetStaticMesh(CraneArmMesh.Object);
}


StaticLoadObject

assetファイルから読み込んで取得する。


UWorld::SpawnActor

UActorを継承したクラス専用。

Worldに生成する。(レベルに置くと同義?)

Actorを新しく作るときには NewObject ではなくこっち。

通常のComponentとかに使ってはいけない。(というかこっちはコンパイルエラー出るはず)

Blueprintクラスを生成するには、 SpawnActor の引数に .ClassObject を渡す。


FString Tips


文字列変換


convert Fstring to normal c++ char[]

// FString -> TCHAR

TCHAR* chars = *MyString;

// FString -> TCHAR -> ANSI

ANSICHAR* ansiChars = TCHAR_TO_ANSI(*MyString);

参考:https://forums.unrealengine.com/showthread.php?3207-convert-Fstring-to-normal-c-char


char array to an FString

FString Fs = FString(ANSI_TO_TCHAR(arr));    // ANSI -> TCHAR -> FString

FString Fs = FString(UTF8_TO_TCHAR(arr)); // UTF-8 -> TCHAR -> FString

参考:https://answers.unrealengine.com/questions/115456/best-way-to-convert-from-a-char-array-to-an-fstrin.html


FName -> FString

FNameValue.ToString()

https://answers.unrealengine.com/questions/2616/manipulating-fname-and-fstring.html


FName、FText、FStringの相互変換について

危険な変換があるので、ドキュメントの【文字列の取り扱い】参照。

https://docs.unrealengine.com/latest/JPN/Programming/UnrealArchitecture/StringHandling/index.html


C言語でよくある通常の文字列関数使いたい

\Engine\Source\Runtime\Core\Public\Misc\CString.h

に定義されている、 FCStringFCStringAnsiFCStringWide の関数でいろいろできるようになっている。

例:

auto length = FCStringAnsi::Strlen(charPtrArray);


FTextでFormat

FString FileName;

(FText::Format(LOCTEXT("Test", "Save {FileName}.data."), FText::FromString(FileName)));


FStringの文字列操作


FString str = FString(TEXT("abcdefg"))のとき。

str.Left(5); // "abcde"

str.Right(5); // "cdefg"

str.LeftChop(5); // "ab"
str.RightChop(5); // "fg"

str.LeftPad(10); // " abcdefg"
str.RightPad(10); // "abcdefg "

拡張子を外したファイル名が欲しいなら、以下の関数で良い。

FPaths::GetBaseFilename(TEXT("hogehoge.txt"));

拡張子だけ欲しければ、

FPaths::GetExtension(TEXT("hogehoge.txt"));

StringからIntegerへの変換

FString str(TEXT("123"));

FCString::Atoi(*str);

IntegerからStringへの変換

FString::FromInt(123);

特定の文字で分割したい

FString str(TEXT("hogehoge_txt_temp"));

TArray<FString> Values;
str.ParseIntoArray(Values, TEXT("_")); // hogehoge, txt, tempに分割される


etc Tips


GCを強制的に呼ぶ

GetWorld()->ForceGarbageCollection();


Logカテゴリーの追加方法

ヘッダーでDECLARE_LOG_CATEGORY_EXTERNして、

CPPにDEFINE_LOG_CATEGORYする。

外部モジュールでも使いたい場合は、ヘッダー側の宣言にAPI宣言を前方に置いておく。


例:HogeHogeモジュール専用のログを追加したい場合

HOGEHOGE_API DECLARE_LOG_CATEGORY_EXTERN(LogHogeHoge, Display, All);

DEFINE_LOG_CATEGORY(LogHogeHoge);

そのCPPファイルだけで使いたい場合はDEFINE_LOG_CATEGORY_STATIC


WindowsのDWORDとか使いたい場合

#include "AllowWindowsPlatformTypes.h"



#include "HideWindowsPlatformTypes.h"

で囲んで、その囲んでいる中にDWORDなどの処理を書く。


ActorじゃないけどTickで動かしたい

ヒストリアさんのブログ参照。

[UE4] C++コードにゲームの初期化,終了,更新のタイミングで処理を追加する方法 | historia Inc - 株式会社ヒストリア

\Engine\Source\Runtime\Engine\Public\Tickable.hFTickableGameObjectを継承して使う。

レベルを跨いで実行したいTickにも効果あり。


CriticalSection

FCriticalSectionをメンバ変数などで変数を作る。

スコープ内だけ有効にしたい場合には、下記のような感じで使う。

FScopeLock Lock(&Critical);


スレッドセーフなインクリメントをしたい

以下の関数を使用すると安全。

int32 CurrentID; // インクリメントしたい整数(メンバ変数として持っていると仮定する)

const int32 NextId = FPlatformAtomics::InterlockedIncrement(&CurrentID);

もしくは素直にFThreadSafeCounter型を使う。

FThreadSafeCounter Counter;

const int32 nextId = Counter.Increment();


UnrealC++でComponent登録


コンストラクタ

CreateDefaultSubobject を使う。


コンストラクタ以外

NewObject した後、AttachToComponent 関数を呼ぶだけではダメで、

RegisterComponent 関数を呼ぶ必要がある。


Level内にいるActorの検索


ActorInLevel

TArray<AActor*> Characters;

UGameplayStatics::GetAllActorsOfClass(this, AMyCharacter::StaticClass(), Characters);


FilePathの検索


LoadFilePath

const FName AnimMontageAssetPath(TEXT("/Game/Animation/AnimMontageSample"));

auto AnimMontage = Cast<UAnimMontage>(StaticLoadObject(UObject::StaticClass(), nullptr, *AnimMontageAssetPath.ToString()));


glob

TArray<FString> FileNames;

IFileManager::Get().FindFiles(FileNames, *FString::Printf(TEXT("%s/Datas/*.*"), *FPaths::GameContentDir()), true, false);
for (auto FileName : FileNames)
{
auto BaseFileName = FPaths::GetBaseFilename(FileName);

// 読み込み
...
}


外部ライブラリ追加方法

VisualStudioの設定ではなく、ライブラリを使うモジュールの~.Build.cs内で追加する。

lib追加方法

PublicAdditionalLibraries

のメンバ関数Addで.libを追加する。

IncludePath追加方法

PublicIncludePaths 、もしくは PrivateIncludePaths のメンバ関数 Add でIncludePathを追加する。

ビルドの構成は TargetInfo の変数から取得できる。

例:

    private string ModulePath

{
get { return Path.GetDirectoryName(RulesCompiler.GetModuleFilename(this.GetType().Name)); }
}

// ライブラリを置いた基底パス取得
private string ThirdPartyPath
{
get { return Path.GetFullPath(Path.Combine(ModulePath, "../../../ThirdParty/")); }
}

if ((Target.Platform == UnrealTargetPlatform.Win64) || (Target.Platform == UnrealTargetPlatform.Win32))
{
string LibrariesPath = Path.Combine(ThirdPartyPath, "hogehogelib", "Windows"); // libファイルが置いてあるパスを設定
// ビルドターゲットによってリンクするライブラリを変更する
switch (Target.Configuration)
{
case UnrealTargetConfiguration.Shipping:
case UnrealTargetConfiguration.Test:
// Release版
PublicAdditionalLibraries.Add(Path.Combine(LibrariesPath, "HogeHoge_x64.lib"));
break;
case UnrealTargetConfiguration.Debug:
default:
// Debug版
PublicAdditionalLibraries.Add(Path.Combine(LibrariesPath, "HogeHoge_x64.lib"));
break;
}
}


レベルを開く

UGameplayStatics::OpenLevel(this, TEXT("/Game/Stage/Level001"));


URLを開く

FPlatformProcess::LaunchURL(*FString::Printf(TEXT("http://www.google.com")), NULL, NULL);


各ディレクトリ取得

FPaths::GameDir() // Gameフォルダのパス(Contentフォルダ?)

あとは大体名前の通りのフォルダのパスを取得できる。

FPaths::GetConfigDir()

FPath::GetSavedDir()

など。


C++側だけconstにしてBlueprintに出す

    UFUNCTION(BlueprintCallable, Category = "Exprement")

int32 Exprement()
#if CPP
const
#endif
{
//test = 111;  // コメント外すとエラー
return 201;
}

// このとき
UCLASS()
class UTest : public UClass
{
int32 test;
};


今日はUnrealC++の日

裏 Unreal Engine 4 (UE4) Advent Calendar 2016 - Qiita でも @Kaaaai さんがよく使われる処理をUnrealC++で書いてみようの巻。 - Kai's Tech というのが公開されています!是非読みましょう!


UnrealEngine4の技術書がセール中

現在、Packtのセールで電子書籍・ビデオが全部$5なので買いましょう。

Bundleとしてもまとまってます。

Unreal Engine 5 for $25 Bundles | PACKT Books

自分はMastering Unreal Engine Bundleをポチりました。

日本でもUnrealC++の技術書が出ることを願っています。(誰か書いてください)


実装が合っているはずなのに思った動作にならない時のチェックリスト


  • 本当に変数に値が入っているか

  • デフォルト動作になっていないか

  • 条件分岐、計算に使っている数値の単位が正しいか

  • 初期化・終了処理し忘れていないか


  • デバッガで動作を追ってみる


  • 使用している変数をPrintデバッグして確認


  • コードを読む、考えこむだけではなく、編集してみる


  • API仕様が間違っている可能性(辛いやつ)



  • リビルド


    • リビルドでも駄目な場合は、キャッシュを削除する。

    • ビルドキャッシュが入っているIntermediate のディレクトリは空にする

    • 各ゲーム固有処理のキャッシュは Engineじゃない方のSaved フォルダ内のデータを消しましょう



  • 気分転換する


  • 散歩


  • 風呂に入る


  • 仮眠する


  • 一旦寝て明日やる



明日は

明日はtarotarokunさんの、某MashupAwardに出す作品で得た、UE4で作ったVRゲームの動画配信ノウハウを大公開です。