C++ を使うときに知っていると便利な機能のまとめです。
ひとつひとつは記事にするほどの情報量がないので、まとめました。
思いついたら追記して行きます。
動作環境
Unreal Engine 5.5.1
Windows 11 Home
Visual Studio 2022
更新履歴
25/01/14 記事を公開
オブジェクト名
UObject::GetName() で取得した “オブジェクト名”_1 の末尾の _1 を取り除く
スポーンするアクターの名前の末尾にユニークな番号を付ける
ガベコレ
UObject のポインタがガベコレ中かどうかを調べる
UPROPERTY を使わずにガベコレを防ぐ
ゲーム起動、シミュレーション
エディタから起動した Standalone かどうかを調べる
インターフェース
UINTERFACE の UFUNCTION をオーバーライドする
ブループリントに追加したインターフェース関数を C++ で呼び出す
ログ
UE_LOG で関数名を出力する
ログカテゴリーを .cpp のみに定義する
ログタイプを変更する(Log Verbose など)
プロジェクト設定
プロジェクト設定に自前の項目を追加する
コンソール、デバッグコマンド
コンソール変数を追加する
コンソールコマンド(デバコマ)を追加する
FString ObjectName = MyObject->GetName(); if (ObjectName.Len() > 2) { int32 LastUnderscoreIndex; if (ObjectName.FindLastChar('_', LastUnderscoreIndex)) { ObjectName = ObjectName.Mid(0, LastUnderscoreIndex); } }
ガベコレ中でもポインタは有効ですが、アクセスするとクラッシュするので、削除フラグが立っているかどうかをチェックして、フラグが立っているならアクセスしないように対応できます。
解放後のポインタはアクセスしただけでクラッシュする状態になっている場合もあるので、確保したポインタの状態を調べるのには限界があります。
どんな状態でもクラッシュしないように対応するには、自分でアロケータとガベコレの仕組みを作る必要があります…。
bool IsSetGCFlag(const UObject* const Object) { if (!IsValid(Object)) { return false; } // 既に無効になっている(trueにしても良いかも) return Object->HasAnyFlags(EObjectFlags::RF_TagGarbageTemp | EObjectFlags::RF_BeginDestroyed | EObjectFlags::RF_FinishDestroyed); }
bool IsInPIE() { //return GEditor->bIsSimulatingInEditor || GEditor->PlayWorld; return GIsPlayInEditorWorld; }
エディタなしのスタンドアローン(パッケージ起動)と、エディタから起動したスタンドアローンの識別方法。
bool IsInEditorStandalone() { #if WITH_EDITOR return GIsEditor && !GIsPlayInEditorWorld && IsRunningGame(); #else return false; #endif }
標準 C++ に慣れ親しんでいると奇妙にしか見えない UE の独自仕様。
UINTERFACE() class UHogeInterface : public UInterface { GENERATED_UINTERFACE_BODY() }; // class での宣言は不要だが、実装コードは必要 UHogeInterface::UHogeInterface(const FObjectInitializer& _) : Super(_) {} class IHogeInterface { GENERATED_IINTERFACE_BODY() public: // IInterface 側に実装コードを書いてはいけない // 宣言のみ書く UFUNCTION(BlueprintNativeEvent) int32 GetFusaFusa() const; }; UCLASS() class UMyClass : public AActor, public IHogeInterface { GENERATED_BODY() public: // _Implementation を付けると、IInterface に宣言した // UFUNCTION をオーバーライドできる。 virtual int32 GetFusaFusa_Implementation() const { return 2323; } };
動作確認までの流れ
- 下記コードの MyClass を親にしたブループリントアクターを UE エディタで新規作成。
- 1. の Class Settings > Details > Interfaces > Implemented Interfaces > Add で Hoge Interface を追加。
- ブループリント側で GetFusaFusa を実装。
- プレイ時に 1. のブループリントアクターをスポーン。
UINTERFACE() class UHogeInterface : public UInterface { GENERATED_UINTERFACE_BODY() }; // class での宣言は不要だが、実装コードは必要 UHogeInterface::UHogeInterface(const FObjectInitializer& _) : Super(_) {} class IHogeInterface { GENERATED_IINTERFACE_BODY() public: // IInterface 側に実装コードを書いてはいけない // 宣言のみ書く UFUNCTION(BlueprintNativeEvent) int32 GetFusaFusa() const; }; // このクラスを親にしたブループリントアクターを作成したら // 詳細のインターフェース>実装インターフェースに Hoge Interface を追加し // 「ブループリントアクター」を UWorld::SpawnActor() などでスポーンさせる。 // ブループリント側にインターフェースを追加しているので、↓ の C++ のアクターを // 直接スポーンするとインターフェースが見つからず GetFusaFusa() は呼ばれない。 // なので、必ずブループリントアクターをスポーンするよう注意する。 UCLASS(Blueprintable) class UMyClass : public AActor { GENERATED_BODY() public: virtual void BeginPlay() override { UClass* MyClass = this->GetClass(); const bool bImplemented = MyClass->ImplementsInterface(UHogeInterface::StaticClass()); if (bImplemented) { // UFUNCTION の関数名に Execute_ を付けることで、ブループリント側に // 実装した関数を C++ 側で呼び出せる。 // UHogeInterface を継承している MyClass のポインタを渡すことで // IHogeInterface のメンバ関数を static 関数のように呼び出すことができる。 const int32 AmountOfFusaFusa = IHogeInterface::Execute_GetFusaFusa(this); } } };
これも UE 独自仕様。
UCLASS() class UMotiMoti : public UObject { GENERATED_BODY() // この TMap の UObject* がガベコレで勝手に回収されないようにしたい TMap<int32, UObject*> ObjectMap; public: // static 関数なのにオーバーライドできる。 // override キーワードは不要。 static void AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector) { UMotiMoti* MotiMoti = CastChecked<UMotiMoti>(InThis); if (!MotiMoti->ObjectMap.IsEmpty()) { Collector.AddReferencedObjects(MotiMoti->ObjectMap, InThis); } } };
UObject 以外では FGCObject を使うことで同じことができます。
#include “Runtime/Engine/Classes/Engine/World.h”
FName FActorSpawnUtils::MakeUniqueActorName(ULevel* Level, const UClass* Class, FName BaseName, bool bGloballyUnique)
const FName LocalUniqueName = FActorSpawnUtils::MakeUniqueActorName( nullptr, this->GetClass(), FName(TEXT("MyClass")), /* bGloballyUnique */false); const FName GlobalUniqueName = FActorSpawnUtils::MakeUniqueActorName( nullptr, this->GetClass(), FName(TEXT("MyClass")), /* bGloballyUnique */true); UE_LOG(LogTemp, Log, TEXT("LocalUniqueName=%s"), *LocalUniqueName.ToString()); UE_LOG(LogTemp, Log, TEXT("GlobalUniqueName=%s"), *GlobalUniqueName.ToString());
実行結果
LocalUniqueName=MyClass_0
GlobalUniqueName=MyClass_UAID_A8A1596CCE720A4202_1376017953
LocalUniqueName=MyClass_1
GlobalUniqueName=MyClass_UAID_A8A1596CCE720A4202_1392997954
UE_LOG(LogTemp, Log, TEXT(“%hs”), __FUNCTION__);
DECLARE_LOG_CATEGORY_CLASS(Logなんとかかんとか, Log, All);
cmd に以下を入力する
log ログカテゴリー ログタイプ
ログタイプのデフォルトはほとんどが Log ですが、Verbose に変更することがよくあります。
GameplayStatics あたりにあっても良さそうなんですけどね…。
見当たらないので処理を作ります。
// 重みを設定した TArray<T> を用意しておき、以下の関数に引き渡すと // 選んだ配列のインデックスを返す。エラー時は -1 // T は float, double, int32, uint8 などを想定。 template<class T> int32 SelectIndexRandomlyWithConsideringWeight(const TArray<T>& InWeights) { // 重みの合計値を取得 T TotalWeight = T(); for (const T Weight : InWeights) { TotalWeight += Weight; } // 重みを考慮した乱数値を取得 T SelectedWeight = FMath::RandRange(T(), TotalWeight); // 乱数値に対応する InWeights のインデックスを重みを考慮して返す int32 SelectedIndex = -1; T CurrentWeight = T(); for (int32 Index = 0; Index < Weights.Num(); Index++) { CurrentWeight += Weights[Index]; if (CurrentWeight > SelectedWeight) { SelectedIndex = Index; break; } } return SelectedIndex; }
使用例
TArray<float> Weights; Weights.Add(0.1f); Weights.Add(0.5f); Weights.Add(0.2f); Weights.Add(0.3f); const int32 SelectedIndex = SelectIndexRandomlyWithConsideringWeight<float>(Weights); if (SelectedIndex < 0) { /* エラー */ } // Weights[SelectedIndex] に対応するデータを取得 const なにかのでーたー& Data = なにかのでーたー配列[SelectedIndex];
※注意
Weights と「なにかのでーたー配列」の要素数は同じで、要素は1対1で対応している必要があります。
Weight[n] に設定した重みは、なにかのでーたー配列[n] のものである…ということです。
実際の運用では以下のような構造体を定義して、Weight とデータをひとつの構造体で管理する形になると思います。
関数を呼び出す際は、構造体から重みだけ抽出したものを関数に渡すことになります。
struct なにかのでーたー構造体 { float Weight{0.0f}; なにかのでーたー Data{}; }; TArray<なにかのでーたー構造体> なにかのでーたー配列; // なにかのでーたー構造体の Weight を Weights に追加 TArray<float> Weights; Weights.Reserve(なにかのでーたー配列.Num()); for (const なにかのでーたー構造体& Nani : なにかのでーたー配列) { Weights.Add(Nani.Weight); } const int32 SelectedIndex = SelectIndexRandomlyWithConsideringWeight<float>(Weights); const なにかのでーたー構造体& Nanika = なにかのでーたー配列[SelectedIndex]; // Nanika.Data