今回は自分が少し前にハマった内容で、一般的にはUnreal EngineでC++を書いておられる方には常識かもしれませんが、自分にとっての備忘録として残しておきます。また題名にはUE5とつけておりますが、基本的にはUE4環境下においても同様なはずです。
Unreal Engineにはモジュールシステムというものが存在しており、これは本来のC++には存在していない仕組みです。Unreal Engineのモジュールシステムの説明は公式ドキュメントもありますが、ヒストリアさんの記事が基本的な内容は理解しやすいです。
このモジュールについて、追加したり、参照したりするだけでも色々なルールが存在し、そのルールを知らないとC++経験者でもハマってしまうことが多々あります。というわけで今回はモジュール追加後にヘッダーファイルをなぜか認識しない時の対処方法です。
エンジンのモジュールを追加する
まずはユースケースを考えてみましょう。ブループリントに"OnlineSubsystem"を使った独自の"CreateSession"ノードをC++で作成したいとします。"CreateSession"ノードを作成するために、"OnlineSubsyste"モジュール内にある、"OnlineSessionInterface.h"が必要ということまでわかりました。
ではここでプロジェクトに存在する"{ProjectName}.Build.cs"にモジュールを追加しましょう。
using UnrealBuildTool; public class MyProject : ModuleRules { public MyProject(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput", "OnlineSubsystem", // 追加 "OnlineSubsystemUtils" // 追加 }); } }
"PublicDependencyModuleNames"にモジュールを追加します。OnlineSubsystemモジュールと一緒にOnlineSubsystemUtilsモジュールも必要そうだったので、一緒に追加しました。"PublicDependencyModuleNames"と対照的に"PrivateDependencyModuleNames"というリストも存在しますが、これらの違いをわかりやすく説明すると以下のようになります。
PublicDependencyModuleNames → .hファイルなどPublicファイル内でインクルードする際に利用
PrivateDependencyModuleNames → .cppファイルなどPrivateファイル内でインクルードする際に利用
モジュールにはPublicとPrivateの概念が存在し、Publicフォルダー内に作られたファイルは他のモジュールから参照可能ですが、Privateフォルダー内に作られたファイルは他のモジュールから参照ができないようになっています。これにより明確なアクセス制限が発生し、コンパイル時間の短縮が可能となります。可能であれば、PrivateDependencyModuleNamesに追加する方が依存関係が弱くなりますので、より柔軟なモジュール設計がしやすくなります。
次はこれでC++コードを書いていきます。
ファイルを追加する
まずはヘッダーファイルに作成したい内容のコードを書きます。UBlueprintFunctionLibraryを継承したクラスで書きます。今回はあくまでも一例なので、実際にこのコードを本番で使うわけではありません。
#include "CoreMinimal.h" #include "Kismet/BlueprintFunctionLibrary.h" #include "MyBlueprintFunctionLibrary.generated.h" UCLASS() class MYPROJECT_API UMyBlueprintFunctionLibrary : public UBlueprintFunctionLibrary { GENERATED_BODY() UFUNCTION(BlueprintCallable, Category = "OnlineSession") static void CreateSession(const UWorld* WorldContextObject, const FString& SessionId); };
次にCPPファイル側を書きます。
#include "MyBlueprintFunctionLibrary.h" #include "OnlineSubsystem.h" #include "OnlineSubsystemUtils.h" #include "OnlineSessionInterface.h" void UMyBlueprintFunctionLibrary::CreateSession(const UWorld* WorldContextObject, const FString& SessionId) { // OnlineSubsystemの取得 IOnlineSubsystem* OnlineSubsystem = Online::GetSubsystem(WorldContextObject); if (OnlineSubsystem) { // SessionInterfaceを取得 IOnlineSessionPtr OnlineSession = OnlineSubsystem->GetSessionInterface(); if (OnlineSession.IsValid()) { // OnlineSessionInterfaceからCreateSessionをSessionIdで呼び出し OnlineSession->CreateSessionIdFromString(SessionId); } } }
あくまでも仮の実装ですがこちらのコードをビルドしようとすると、インクルードしようとしているモジュールのヘッダーでエラーが発生します。おそらくヘッダーファイルパスも間違っていないかと思いますが、何度ビルドしようと思ってもビルドできません。一般的なC++を使っている場合、このエラーの原因がわからずでハマってしまうことがあります。少なくとも私はハマりました。
プロジェクトファイルの再生成
この状況を解決するには、プロジェクトファイル自体の再生成が必要になります。プロジェクトファイルの再生成は、".uproject"ファイルをExploreから右クリックして、"Generate Visual Studio project files"を選ぶことで可能となります。
もしくはUnreal Editorを起動後にメニューから更新が可能です。
これを使ってプロジェクトファイルを再生成後、Visual Studioで再読み込みを行ってから再度ビルドしてみましょう。すると、まだエラーが残っていることが確認できます。
fatal error C1083: include ファイルを開けません。'OnlineSessionInterface.h':No such file or directory
どうやら"OnlineSessionInterface.h"が正しくインクルードできていないようです。
モジュールのインクルードパスについて
このエラーは単純に"OnlineSessionInterface.h"のインクルードパスにミスがあるだけですので、修正が必要です。
モジュールを追加後、上記のプロジェクトファイル再生成を行うと自動的にインクルードパスが追加されています。ただしこのインクルードパスがどこに追加されているのかをしっかり確認を行う必要があります。
OnlineSubsystemモジュールのファイルパスは以下のようになっています。
OnlineSubsystem
- OnlineSubsystem.uplugin |
- Config |
- Source |
他のうまくインクルードができているファイルから推察すると、少なくともPublicフォルダー以下は正常にパスが通っています。つまり今回のケースでは"Interfaces"をインクルードパスに追加すればビルドを通せそうです。
#include "Interfaces/OnlineSessionInterface.h"
上記コードに修正をし、再度ビルドを行うことで無事エラーが発生せずにビルドが成功しました!これでモジュール関連で発生していたインクルード関連の問題は無事解決したことになります。