見出し画像

【#2インディーゲーム開発日誌】Enumコメントをゲームで自動表示!バフ機能などの説明をコメントで管理してみた!

こんにちは
2024年にブラストエッジゲームズで新卒で入社してエンジニアをしているTKといいます。
このコーナーでは株式会社ブラストエッジゲームズで現在絶賛開発中のインディーゲーム「GOMAN」について記事にしていきたいと思います。
※絶賛開発中のタイトルのため今後、記載されてる機能が変更される可能性がありますのでご了承ください

GOMANとは

当社で開発している「GOMAN -stuck in the avici hell」という
3Dの横スクロールアクションゲームになります。
規模としましては10名以下の少人数で開発している
オリジナルタイトルのインディーゲームになります。

実装したデバッグ機能について

今回実装したデバッグ機能は、プレイヤーと敵のどちらかにバフ・デバフを与えられる機能になります。
マスタデータに記載のあるIDからバフ・デバフを与えたり、バフ・デバフのタイプ(攻撃力アップや画面を見えづらくする視野狭窄等)を指定して個別で与えることができます。
バフタイプの、視野狭窄や、無敵という文言は、ソースコード上に書かれているコメントの文字列から取得しています。
この記事では、コメントの文字列の取得方法について解説していきます。
最初に伝えておきますがこの機能はエディターのみ使える機能になりますのでご了承ください。
しかし特別なシステムを作らずUnrealEngineのシステムを使用して使えるので便利なところもあるかと思いますのでよろしくお願いします。

この機能を実装した理由

GOMANには、様々なバフやデバフの種類があります。(およそ20種類以上あります)
敵からの攻撃を受けた際に、プレイヤーが鈍足になったり、攻撃力が低下するデバフを受けることがあります。
対して、他の要因(特定の行動をする等)で、プレイヤーが無敵になったり、敵がHPを1だけ残して攻撃を耐えるというバフを受けることもあります。
このデバッグ機能の実装初期は、バフのタイプに対して、手作業でその内容を記述していました。
しかし、ゲームのメインの開発が進むにつれてバフ・デバフの数がどんどんと増えていき、手作業では抜けや漏れ等が出てしまう為、プログラム側からコメントの内容を抽出して記述していく方針にしました。

実装の仕組み

先ず、コンストラクタで、FindFirstObject<UEnum>という関数でバフタイプを定義している列挙型のポインタを取得します。
列挙されている値を列挙型ポインタから添え字に対応した値を取得します。
その後、列挙型ポインタを使用してその添え字に対応したコメントの内容をSeekComment()の中で取得します。

const UEnum* EnumPtr = FindFirstObject<UEnum>(TEXT("EPJ_IngameBuffType"));
	if (!IsValid(EnumPtr))
	{
		UE_LOG(LogTemp, Error, TEXT("'%s' バフタイプの列挙型が取得できませんでした。"), *GetNameSafe(this));
		return;
	}
	for (int i = 0; i < EnumPtr->NumEnums() - 1; i++)
	{
		EPJ_IngameBuffType Type = StaticCast<EPJ_IngameBuffType>(EnumPtr->GetValueByIndex(i));
		if (Type == EPJ_IngameBuffType::Combi || Type == EPJ_IngameBuffType::All || Type == EPJ_IngameBuffType::NotFound)
		{
			continue;
		}
		SeekComment(EnumPtr, i);
		BuffTypes.Add(Type);
	}

SeekComment()内についてです。
列挙型のポインタ(EnumPtr)の添え字(Index)番目のコメントの内容を取得します。
この時、UnrealEngineの関数であるGetMetaData()関数を用います。

void UPJ_IngameDebugMenuBuffPageWidget::SeekComment(const UEnum* EnumPtr, int32 Index)
{
#if WITH_EDITOR
	// コメントを取り出す
	FString Comment = EnumPtr->GetMetaData(TEXT("Comment"), Index);
	// 先頭の"// "を捨てる
	Comment = Comment.RightChop(3);
	// 末尾の"\\n"を捨てる
	Comment = Comment.LeftChop(1);

	// 特定の文字列を破棄する
	FString DiscardPart;
	while (1)
	{
		DiscardPart.Empty();
		if (Comment.Contains(TEXT("(")))
		{
			Comment.Split(TEXT("("), &Comment, &DiscardPart);
		}
		else if (Comment.Contains(TEXT("(")))
		{
			Comment.Split(TEXT("("), &Comment, &DiscardPart);
		}
		else if (Comment.Contains(TEXT("特殊.")))
		{
			Comment = Comment.RightChop(3);
			DiscardPart = TEXT("特殊.");
		}

		if (DiscardPart.IsEmpty())
		{
			break;
		}
	}
	if (Comment.Contains(TEXT(",")))
	{
		Comment = Comment.LeftChop(1);
	}
	if (Comment.Contains(TEXT(".")))
	{
		Comment = Comment.LeftChop(1);
	}

	// 表示用の配列に追加する
	BuffTypeStrs.Add(Comment);

#else
	FString Str = EnumPtr->GetNameStringByIndex(Index);
	BuffTypeStrs.Add(Str);
#endif
}

上記のソースコードでは、
FString Comment = EnumPtr->GetMetaData(TEXT("Comment"), Index);
でCommentを取得しています。
それぞれの引数の意味について解説します。
このGetMetaData()関数は、対象のポインタの、メタデータのNameIndex番目のKeyの内容を返します。
メタデータとは、UnrealEngineでcppファイルをビルドした際に自動的に作成される「[File Name].gen.cpp」を指します。
今回の場合、「PJ_IngameBuffType.gen.cpp」になります。
※メタデータ内の文字列はすべて文字コードで書かれています。

	/**
	 * 指定されたキーに関連付けられたメタデータの値を返す。
	 * 
	 * @param Key 値を検索するメタデータ・タグ。
	 * param NameIndexが指定された場合、その列挙値をリンクされたメタデータから検索します。
	 そうでない場合は、enum自体のメタデータを検索します。 
	 * @param bAllowRemap trueの場合、値がiniで始まる場合、返される値は.iniからリマップされる可能性があります: iniを含む正確な文字列が必要な場合はfalseを渡してください:
	 *
	 * キーが見つからなかったり、値がない場合は空文字列を返します。
	 */

この関数を使えばコメントが取得できますがEnumとコメントがどのようにデータを入れているか調べてみます。
自動生成された「PJ_IngameBuffType.gen.cpp」の中身を見てみます。
文字コードがそのまま格納されているのでコメントなどは本来の文字で表示されないですが関数で取得する際に自動で変換されます。

改めて、GetMetaDataの説明に戻ります。
Enumからコメントを取得する場合は上記の関数のKeyにTEXT(Comment)を、NameIndexにIndexを渡すと上記のデータを参照してコメントの文字列が返ってきます。
例としてInvincible(無敵)を取得する場合は
EnumPtr[複数のEnumの定義が入っている]->GetMetaData(TEXT("Comment"), Index[InvincibleのEnumを取得する番号]);
のような形でメタデータ内から上記のデータの「Invincible.Comment」に該当する文字列を取得します。
その内容がEPJ_IngameBuffType::Invincibleのコメントの内容になります。
しかしそのまま取得すると // 無敵 のような表示になってしまいます
そのため、表示に不要な「// 」や、「.」、「()」などを削除し、表示用の配列に追加します。
他にもコメントで可読性が損なわれてる文字情報などがあれば加工したりします。

注意点

注意点として、上記で説明したメタデータからコメントを抽出する方法は、エディタ限定で動作します。
その為、このプロジェクトでは「WITH_EDITOR」プリプロセッサで、エディタの時と、そうでない時で分けています。

例えば上記のEnumはエディター上では、攻撃力/体幹攻撃力アップと表示されますが
実機では、Enumの文字列をそのまま表示しているためAttackPowerUpなどで表示されるようにしています。

void UPJ_IngameDebugMenuBuffPageWidget::SeekComment(const UEnum* EnumPtr, int32 Index)
{
#if WITH_EDITOR
	// エディタ限定の処理
#else
	FString Str = EnumPtr->GetNameStringByIndex(Index);
	BuffTypeStrs.Add(Str);
#endif
}

エディタ以外でこの機能を実装する場合は、GetNameStringByIndex(int32 InIndex)関数を用います。

まとめ

今回の要点をまとめると、

  • 列挙型のポインタと、添え字があれば、その列挙型のUnrealEngineがビルド時に作ったメタデータ(自動生成データ)を取得できる

  • ポインタのメンバ関数である「GetMetaData()」関数の第1引数に「Comment」を、第2引数に指定のEnum番号を渡すことによって、コメントアウトの内容を取得できる

  • GetMetaData関数は、エディタでのみ動作する

エディタ専用にはなりますが、デバッグツール等として、列挙型のコメントの内容を取得したい場合に、非常に便利に使うことができるので、参考にしてみてください。
また、株式会社ブラストエッジゲームズでは、一緒に開発してくれる仲間を絶賛募集中です!
会社へのエントリーもお待ちしてます!
https://www.blastedge-games.co.jp/recruit
一緒にUnreal Engineでゲームを作りましょう!
今回の知識が少しでも皆さんのゲーム開発に役立てばと思います。
では皆さん良いゲーム開発を!

いいなと思ったら応援しよう!