blog heroalche.studio

C++とBPのクラスを一括で生成するEUWを作ってみた

著者:

Nobuyoshi Daido

投稿日 2023.11.07更新日 2024.04.01

はじめに

C++クラスやそのクラスを親にしたブループリントアセットを作成することは多々あると思います。今回はこのC++クラスの作成とブループリントアセットの作成を一括で行うEUWを作成してみたので共有します。 UE5.3を利用します。

実装の流れ

  • EUWからC++クラスを生成する
    • C++クラスを生成するUFUNCTIONの実装
    • 上記のUFUNCTIONを用いてEUWからC++クラスを生成できるようにする
  • EUWから生成したクラスを親にしたブループリントアセットを作成
    • アセットを作成し名前と保存場所を決める実装
    • 生成されたクラスをロードするUFUNCTIONを実装しアセットのReparentを行う

この流れをもとに処理を実装していきます。

1. C++クラスを生成する

最初にC++クラスを生成するUFUNCTIONを作ってそのノードをEUWで使用したいと思います。 EditorのTools→NewC++Class..からもC++クラスを作成することができるので、そのコードをエンジンから探して利用しました。 genclass_daido

必要なモジュールは以下の2つです。

Build.cs
	PrivateDependencyModuleNames.AddRange(
		new string[]
		{
			"GameProjectGeneration",
			"UnrealEd"
		}
	);

以下のコードがクラスを生成するUFUNCTIONのコードです。

.hはこんな感じです。StringのPrefixと生成したいクラスの親クラスの配列を受け取る関数を宣言しました。

GenerateCode.h
#pragma once

#include "CoreMinimal.h"
#include "GenerateCode.generated.h"

UCLASS()
class UGenerateCode : public UBlueprintFunctionLibrary
{
	GENERATED_BODY()

public:

	/**
	 * @brief C++でのベースクラスを生成
	 * @param Prefix 生成するコードクラスに付与する Prefix
	 * @param GenerateClasses 生成したい親クラスの配列
	 */
	UFUNCTION(BlueprintCallable)
	static void GenerateClasses(const FString& Prefix, TArray<TSubclassOf<UObject>> GenerateClasses);
};

.CPPの方は以下のような感じです。

GenerateCode.cpp
#include "GenerateCode.h"

#include "GameProjectUtils.h"
#include "SourceCodeNavigation.h"
#include "Interfaces/IProjectManager.h"

void UGenerateCode::GenerateClasses(const FString& Prefix, TArray<TSubclassOf<UObject>> GenerateClasses)
{
	IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
	const FString Path = FPaths::GameSourceDir() / FApp::GetProjectName() / "GameplayFramework" / "";
	PlatformFile.CreateDirectory(*Path);

	FModuleContextInfo NewModuleInfo;
	NewModuleInfo.ModuleType = EHostType::Runtime;
	NewModuleInfo.ModuleName = FApp::GetProjectName();
	NewModuleInfo.ModuleSourcePath = FPaths::ConvertRelativePathToFull(Path);

	const TSet<FString>& DisallowedHeaderNames = FSourceCodeNavigation::GetSourceFileDatabase().
		GetDisallowedHeaderNames();

	FString HeaderFilePath;
	FString CppFilePath;
	FText FailReason;

	TArray<TSubclassOf<AActor>> GeneratedClasses;
	TArray<FString> ClassNames;

	for (const TSubclassOf<UObject>& ClassToGenerate : GenerateClasses)
	{
		const FString ClassName = Prefix + ClassToGenerate->GetName();

		GameProjectUtils::AddCodeToProject(
			ClassName,
			NewModuleInfo.ModuleSourcePath,
			NewModuleInfo,
			FNewClassInfo(ClassToGenerate),
			DisallowedHeaderNames,
			HeaderFilePath,
			CppFilePath,
			FailReason
		);
	}
}

UObjectを継承しているクラスの配列を渡すとそのクラスたちを親クラスとした新しいクラスを生成するという関数を書きました。

前半部分ではクラス生成の関数に渡す引数の準備をしています。後半部分で重要なのはGameProjectUtils::AddCodeToProject()です。

const FString ClassName = Prefix + ClassToGenerate->GetName();
上記の部分では、生成する新しいクラス名は引数で受けたPrefixと生成する親クラス名をAppendしてClassNameとしてその後クラス生成の関数に渡しています。

FPaths::GameSourceDir() / FApp::GetProjectName() / "GameplayFramework" / "";
さらに上記の部分では生成したソースファイルの場所をSource/{ProjectName}/GameplayFrameworkに保存するよう直接指定しています。

そして親クラスは、第4引数のFNewClassInfo型にクラスを渡すことでそのクラスを親クラスとして新しいクラスが生成されます。

1.1 EUWからC++クラスを生成できるようにする

次にEUWに先程のUFUNCTIONを実装していきましょう。 ここではEUWについて詳しく話しませんが、今回はデータ表示用のObjectを別で持っておき、EUWのDetailsViewにSetObjectする形をとりました。
データ表示用のObjectにはPrefixとUObject型のクラス変数(紫色)の配列を持たせました。

Genclass_daido

Run Utility Widgetをしてみます。クラス名の前につけるPrefixとして "RZ" と入力し親クラスはCharacterに設定してみました。これでクラスの生成をしてみます。

今回はCharacterクラスのみの一つですがGeneratedClassesが配列なので好きなクラスを一括で追加できます。 genclass_daido4

コードが追加されていますね。コードが追加された後はLiveCodingが走るはずなので終わるのを待ちましょう。 genclass_daido5 エクスプローラー上で確認するとちゃんと指定したパスにソースファイルが生成されていました。 genclass_daido6

確認のために生成された.hだけ中身を見てみましょう。指定した親クラスとPrefixで正しくクラスが宣言されています。

RZCharacter.h
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "RZCharacter.generated.h"

UCLASS()
class PROJECTHOGE_API ARZCharacter : public ACharacter
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	ARZCharacter();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

};

余談ですが、クラスの生成にはTemplateファイルが使用されており、エンジンの以下の場所にあるTemplateファイルを利用しているようです。
\Epic Games\UE_5.3\Engine\Content\Editor\Templates
これはAActorクラスの.hのTemplateファイルです。%で囲まれた内容を置き換えているようです。

ActorClass.h.template
%COPYRIGHT_LINE%

#pragma once

#include "CoreMinimal.h"
%BASE_CLASS_INCLUDE_DIRECTIVE%
#include "%UNPREFIXED_CLASS_NAME%.generated.h"

UCLASS(%UCLASS_SPECIFIER_LIST%)
class %CLASS_MODULE_API_MACRO%%PREFIXED_CLASS_NAME% : public %PREFIXED_BASE_CLASS_NAME%
{
	GENERATED_BODY()

public:	
	// Sets default values for this actor's properties
	%PREFIXED_CLASS_NAME%();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	%CLASS_FUNCTION_DECLARATIONS%
	%CLASS_PROPERTIES%
};

詳しく見たい方は \Epic Games\UE_5.3\Engine\Source\Editor\GameProjectGeneration\Private\GameProjectUtils.cpp

を見ると良いです。

2. 生成したクラスをもとにブループリントアセットを作成する

Prefix付きのC++クラスが生成できたので、次は生成したクラスを元にブループリントアセットを作成する機能をEUWに追加したいと思います。

前に作成したEUWにブループリントアセットを作成ボタンを作成しました。

gencode2_daido1

2.1 アセットを作成し名前と保存場所を決める

アセットの作成にはObjectを親にしたアセットをあらかじめTempアセットとして作成しておき、これをDuplicate Assetノードを利用して作成していきます。

以下のDuplicate Assetノードによってアセットを保存する場所とアセット名を決めています。

2.2 アセットのReparentをする

アセットのReparentにはStringからクラスをロードするLoadClassFromString関数を作成し、この関数で生成したクラスをロードしてReparentに使用しています。

以下がその関数です。

GenerateCode.h
	UFUNCTION(BlueprintCallable)
	static TSubclassOf<UObject> LoadClassFromString(FString ClassName);
GenerateCode.cpp
TSubclassOf<UObject> UGenerateCode::LoadClassFromString(const FString ClassName)
{
	const FString ProjectNameStr = FApp::GetProjectName();
	const FString Name = "/Script/" + ProjectNameStr + "." + ClassName;

	if (UClass* ResultClass = LoadClass<UObject>(nullptr, *Name))
	{
		return ResultClass;
	}
	return nullptr;
}

EUWに入力した新しく生成するクラスのPrefixと生成するクラスのDisplayNameをAppendすることで生成する新しいクラス名を取得してLoadClassFromString関数に読み込ませています。

ちなみにブループリントアセットを作成する前にC++クラスを生成したらLiveCodingが自動で走ると思うので、LiveCodingが完了した状態でないとブループリントアセットのReparentができません。

先ほど作成した RZCharacter クラスを親にしてブループリントアセットを作成したいと思います。EUWに追加した「Blueprintアセットの生成」ボタンを押しました。

きちんと作成されていますね。親も正しくReparentされています。

genclass_daido7

おわり

このEUWの用途の一例としてプロジェクト開始時に必要なクラスを一括で追加してプロジェクトの準備を少しだけ効率化できると思います。
特に ACharacter や APlayerController などといったゲームプレイフレームワーク周りのクラスはよく作成すると思うのでこのEUWが使えるかもしれません。

ここまで読んでいただきありがとうございました。