C++ ときどき ごはん、わりとてぃーぶれいく☆

Wonder Rabbit Projectのなかのひとのブログ。主にC++。

Game: Dragon's Dogma: Dark Arisen とマルチモニターやバックグラウンド動作のパッチ( mod )

この頃 DDDA ( STEAM 版 ) を遊んでいます。ゲームは楽しめているのですが、アプリケーションとしての動作に幾つか悲しみの挙動があり気にしながらプレイしていました。

  1. マルチモニター環境でフルスクリーンを ON にコンフィグすると Left-Top のモニターに出てしまう。
    • 中央や右のモニターでプレイしたい場合に悲しい(GPUドライバーでDDDAのためだけにモニターの座標空間を調整もしたくない)
    • ウィンドウモードにするとバーが目に入ってしまい悲しい&起動ごとにウィンドウの位置調整が必要で悲しい
  2. Alt + Tab などでアプリをバックグラウンド動作にすると描画も音楽も止まってしまう。
    • メモを取りながらプレイしたり、たまに SNS を覗いたりしながらプレイするスタイルだと悲しい(プレイ中の雰囲気が中断されてしまうので)

しばらくは我慢してプレイしていましたが、より快適に楽しむためにどうにかしようか、と思ったところ、既に2年前にどうにしかしていた人が居ました😃

native な dll-proxy 型の mod ですね。 README に how-to-install などありませんが、 OS の実行時ライブラリーの動的リンクに選択的に意図したライブラリーをフックさせる事でアプリの動作に後付けで native な処理をアプリ本体と同じプロセス内で実行させる手法です。 ddda.exe の配置先に配置すると mod として機能します。設定は .ini を編集、と。

導入して少々 .ini を編集すればフルスクリーンで中央のモニターに表示やバックグラウンドでも処理を止めないようにでき、快適なプレイ環境へ接近します。

最近はこのような仕組みを使わずともアプリ本体が元からユーザーが意図してインストールする追加の mod 群をロードできるゲームも多い時代になり、このような昔ながらの方法でプロセスに割り込む mod は懐かしさを感じるようになりました。計算機プログラマー的には動的リンクの基礎的な知識とその応用技法です。仕込むのが悪意のある第三者なら問題ですが、こうした技法が使用可能な事はそもそもユーザーの便利として有用性が高く、 "高すぎる無駄なセキュリティー意識" の犠牲にならない日がこの先の計算機と一般的なPCとOSの歴史でも続くと嬉しいです。もちろん、少なくともローカルに完結する範囲内で、それがユーザーの意図した介入である処理については、ですが。

UE4/C++: USaveData 系のセーブデータの Slot 群をすべて取得する方法

USaveData 派生型のセーブデータを作り UGameplayStatics::SaveGameToSlot, UGameplayStatics::LoadGameFromSlot を用いてセーブとロードを実装する手法を用いるとそれ自体の実装労力は節約できて嬉しい。しかし、これらの APIUE4 プロジェクトの開発時に文字列型の Slot が確定している前提で設計されているらしく、実行時にユーザーが任意の Slot 名称を与えるユースケースではその一覧の取得を行う手段が無い(UE4-4.21現在)。

このためもあってか、一般的に多くの UE4 プロジェクトでは、Slot を開発時に幾つかの固定の名称か、あるいは連番を文字列化して処理する仕組みが採用される例が多いように見える。実際にゲームとして流通するアプリでもセーブデータがスロット0番からN番のように連番で用意されている例を多く見かける(個人の感想です)。一般的に幾つかのセーブスロットを用意する方式で構わないアプリではそうした対応で、簡単かつユーザーにとっても十分で実用にも問題無く快適に使用できる。

しかし、そうではない、ユーザーに可能な限り任意性の高い Slot 名称を与えたい仕組みが必要なアプリを作りたい場合もある。このような場合には、次の様にして実行中の UE4 アプリのセーブデータの Slot の一覧を取得する実装をプロジェクトへ実装する:

// MySaveDataUtility.h
#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "MySaveDataUtility.generated.h"

UCLASS()
class UMySaveDataUtility : public UBlueprintFunctionLibrary
{ GENERATED_BODY()
public:
  UFUNCTION( BlueprintCallable, BlueprintPure ) static TArray< FString > GetSaveSlots();
};
// MySaveDataUtility.cpp
#pragma once

#include "MySaveDataUtility.h"
#include "Runtime/Engine/Public/PlatformFeatures.h"
#include "Runtime/Core/Public/HAL/PlatformFilemanager.h"
#include "Runtime/Core/Public/Misc/LocalTimestampDirectoryVisitor.h"
#include "Runtime/Engine/Classes/Kismet/GameplayStatics.h"

DEFINE_LOG_CATEGORY_STATIC( MySaveDataUtility , Log, All );

TArray< FString > UMySaveDataUtility::GetSaveSlots()
{
  TArray< FString > Slots;

  if ( ! IPlatformFeaturesModule::Get().GetSaveGameSystem() )
  {
    UE_LOG( MySaveDataUtility, Error, TEXT( "GetSaveSlots: GetSaveGameSystem was failed." )
    return { };
  }

  TArray< FString > Empty;
  IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
  FLocalTimestampDirectoryVisitor Visitor( PlatformFile, Empty, Empty, false );
  PlatformFile.IterateDirectory( *( FPaths::ProjectSavedDir() / TEXT( "SaveGames" ) ), Visitor );
  for ( TMap< FString, FDateTime >::TIterator i( Visitor.FileTimes ); i; ++i)
  {
    const FString FilePath = i.Key();
    const FString FileName = FPaths::GetCleanFilename( FilePath );
    
    if ( FPaths::GetExtension( FileName, false ).Equals( TEXT( "sav" ), ESearchCase::IgnoreCase) )
      Slots.Emplace( FPaths::GetBaseFilename( FileName ) );
  }

  return Slots;
}

セーブデータのディレクトリーを浚い、 .sav 拡張子のファイルベース名部分を Slot 名称として回収してセーブデータ群の Slot の一覧を取得している。泥臭い(´・ω・`)

別法としてはそもそも Slot を使わずに UGameplayStatics::SaveGameFromMemory, UGameplayStatics::LoadGameFromMemory を使い、 UE4 が用意する Saved/SaveGames へのファイル保存の仕組みを使わずにプログラマーが直接ファイルシステムなり保存先のデータベースなりオンラインのストレージなり何なりの制御を実装してセーブデータをシリアライズする方法もある。と、言っても、そのような必要な必要がある場合は、そもそも USaveData を使わずに UE4 とは独立したセーブデータのシリアライザーを実装した方が善い場合が多いかもしれない。

References

  1. Is there a way to get all savegames in BP - UE4 AnswerHub
  2. UGameplayStatics | Unreal Engine
  3. Saving Your Game | Unreal Engine

UE4/C++: AActor の Tick が来ない!・おまけ編

本編は以前書いた 「UE4/C++ Tick が来ない!そんなときのトラブルシュート備忘録」 です。今回はそのおまけ。

行動パターンで書くと「ああ、なんだ」と気づく事も多いのですが…

  1. Tick が来ない!(気づき)
  2. PrimaryActorTick.bCanEverTick = true していなかった!
  3. ctor を追加して PrimaryActorTick.bCanEverTick = true した!
  4. UE4Editor で Compile! そして Play! ... Tick がまだ来ない!!

こんなこと、少しばかりは UE4 に慣れてきた昨今でも、たまーに起こります。

(4) の段階で本来は解決しているはずの場合に、しかし Tick がまだ来ない場合、その理由は UE4Editor にあります。 UE4Editor は一度実行された ctor は Compile や Play を行っても再び呼ばれる事はありません(UE4のUObjectとctorとUE4Editorの仕組みの都合)。 BeginPlaySuper::BeginPlay し忘れていた場合などはこのトラブルは起こりませんが、 ctor に変更を加えた場合にはしばしば遭遇します。 ctor をいじった場合には UE4Editor を再起動するのが確実で安全です😂

と、いうわけで (4) の Compile の後、 UE4Editor を再起動すると Tick が来るようになります。 Tick 以外にも ctor の変更と UE4Editr での動作確認には注意が必要です。

今日もうっかり気付かずに数分間考え込みました💀 そういうわけで改めて備忘録として書いておく事にしました。

UE4/C++: FHttpModule で SetContentAsString して POST で投げたら落ちた時のメモ

次のようなごく簡単な FHttpModule の実装例を実行するとゲームまたは UE4Editor ごとプロセスが落ちてしまう。

TSharedRef< IHttpRequest > r = FHttpModule::Get().CreateRequest();
r->OnProcessRequestComplete().BindUObject( this, &UMyHttpTask::HandleRequest );
r->SetURL( TEXT( "https://example.com/" ) );
r->SetVerb( TEXT( "POST" ) );
r->SetContentAsString( TEXT( "nyanko" ) );
r->ProcessRequest();

何が間違えているだろうか?あるいは足りないだろうか?余計だろうか?この実装例で落ちる場合の答えは Crash Reporter で知れる。

f:id:USAGI-WRP:20181023003802p:plain

Assertion failed: !GetHeader("Content-Type").IsEmpty() || IsURLEncoded(RequestPayload) [File:D:\Build\++UE4+Release-4.19+Compile\Sync\Engine\Source\Runtime\Online\HTTP\Private\Curl\CurlHttp.cpp] [Line: 569]

UE4の実装のアサーションで"殺されて"いる。アサーションの冒頭の !GetHeader("Content-Type").IsEmpty() で分かるように、 Content-Type ヘッダーが「足りない」と分かる。こんな事で UE4Editor ごとアサーションで殺されるとは思わなかったので、遭遇したときには「なんぞめんどくさい何かを踏んだかのぅ…」と覚悟したものの、原因は "こんな事" だった。

次のように Content-Type を設定する実装を追加すると期待動作するようになる。

r->SetHeader( TEXT( "Content-Type" ), TEXT( "text/plain" ) );

今回の例では SetContentAsString を使っているので Content-Length は自動的に加えられる。また、蛇足としてPOSTフォームのクエリーを体裁の場合、Content-Typeapplication/x-www-form-urlencoded とする。

UE4/C++: rapidxml を UE4 で使いたい場合の例外不使用化のメモ

1つ前の記事 「UE4/C++: 実行時に取得したドキュメントが SJIS だった時に ICU で UTF8 にしたり UTF16 で TCHAR にするメモ」UE4のユーザープロジェクトレベルで ICU ライブラリーを取り込んで文字コードを操作する実装のメモを書きました。この記事の執筆初稿ではソースコードICU の例外不使用について書こうとしたところで rapidxml のそれについて書いてしまっていました(現在は ICU のコード例に修正済みです)。 rapidxml は前回のメモには直接関係無いので記事の修正時に消したのですが、それはそれで有用な事もあるかと思い、その部分だけ今回の記事へ移して残す事にしました。

// ...
// ↓ <Project>.Build.cs で `bEnableExceptions = true` しない場合は rapidcml でも例外を使わないCPP定義をする
#define RAPIDXML_NO_EXCEPTIONS
#include <rapidxml.hpp>
// ...
// RAPIDXML_NO_EXCEPTIONS を使う場合にはエラーハンドラー関数の定義が必要
// ref: http://rapidxml.sourceforge.net/manual.html#namespacerapidxml_1error_handling
namespace rapidxml
{
  void parse_error_handler( const char *what, void * )
  {
    UE_LOG( LogTemp, Error, TEXT( "rapidxml-error: what=%s" ), ANSI_TO_TCHAR( what ) )
  }
}
  • rapidxml の使い方そのものについては必要に応じて公式ドキュメントや CodeZine/επιστημη 高速軽量XMLパーサ:RapidXMLを触ってみた を見るといいと思います。
  • 前回の記事と今回の記事は HTTP で SJISXML を拾ってきて ICU と rapidxml で処理したい場合のソースからそれぞれのライブラリーを UE4 で使う上で必要な .Build.cs や例外不使用化について整理したものです。

UE4/C++: 実行時に取得したドキュメントが SJIS だった時に ICU で UTF8 にしたり UTF16 で TCHAR にするメモ

実行時にどこかのレガシー文書を HTTP で拾ってきて表示しようとしたら SJIS だった、 EUC-JP だった。あるいはどこか別の文化圏の文字コードだった。 UE4 の文字列処理系は内部表現の UTF16 または変換マクロが定義された UTF8 か ANSI じゃないと扱いが困難。そんな時に ICUUE4 のプロジェクトレベルで取り込んで UTF8 や UTF16 に変換して UE4 で扱いやすくするメモ。

手順

  1. ICU を拾ってくる
    • より具体的には ICU4C を拾ってくる
    • Windows にしかデプロイしないならバイナリーを拾ってくると楽
  2. ICU4C を UE4 プロジェクトの適当なディレクトリーにそれっぽく放り込む
    • 例えば Windows(x86_64) だけでよいのなら ThirdParty/icu63bin64, lib64, include が展開された状態等にする
    • 実際のとろこ、配置場所やディレクトリーの命名は好きにしてよい。
  3. <Project>.Build.csICU のビルド時のリンクと実行時のリンクとそのためのバイナリーのコピーを書く
    • icuuc だけリンクすれば基本的には使える
  4. 変換が必要なソースで icu::UnicodeString を使いお好みで変換する

ソース

.Build.cs

// ...
using System.IO;
// ...
  public MyProject(ReadOnlyTargetRules Target) : base(Target)
  {
    // ...
    var base_path = Path.GetDirectoryName( RulesCompiler.GetFileNameFromType( GetType() ) );
    string third_party_path = Path.Combine( base_path, "..", "..", "ThirdParty");

    string icu_path_prefix = "icu";
    string icu_version = "63";
    var icu_path = icu_path_prefix + icu_version;
    PublicIncludePaths.Add( Path.Combine( third_party_path, icu_path, "include") );
    switch ( Target.Platform )
    {
      case UnrealTargetPlatform.Win64:
        PublicLibraryPaths.Add( Path.Combine( third_party_path, icu_path, "lib64" ) );
        // ICUのライブラリー全てを使いたければ { "icudt", "icuin", "icuio", "icutu", "icuuc", "icutest" }
        var icu_filenames = new string[]{ "icuuc", "icudt" };
        string binaries_directory = Path.Combine( base_path,"..","..", "Binaries", "Win64" );
        if ( ! Directory.Exists( binaries_directory ) )
          System.IO.Directory.CreateDirectory( binaries_directory );
        foreach ( var icu_filename in icu_filenames )
        {
          string icu_lib = icu_filename + ".lib";
          string icu_dll = icu_filename + icu_version + ".dll";
          PublicAdditionalLibraries.Add( Path.Combine( third_party_path, icu_path, "lib64", icu_lib ) );
          string icu_dll_path = Path.Combine( third_party_path, icu_path, "bin64", icu_dll );
          RuntimeDependencies.Add( icu_dll_path );
          string icu_dll_destination = System.IO.Path.Combine( binaries_directory, icu_dll );
          CopyFile( icu_dll_path, icu_dll_destination );
          PublicDelayLoadDLLs.Add( icu_dll );
        }
        break;
      default:
        throw new System.Exception( System.String.Format( "Unsupported platform {0}", Target.Platform.ToString() ) );
    }
    // ...
  }
  // ...
  private void CopyFile( string source, string destination )
  {
    System.Console.WriteLine( "Copying {0} to {1}", source, destination );
    if ( System.IO.File.Exists( destination ) )
      System.IO.File.SetAttributes( destination, System.IO.File.GetAttributes( destination ) & ~System.IO.FileAttributes.ReadOnly );
    try
    { System.IO.File.Copy( source, destination, true ); }
    catch ( System.Exception e )
    { System.Console.WriteLine( "Failed to copy file: {0}", e.Message ); }
  }

↓MyActor.cpp 等の使い所で

// ...
// ↓ <Project>.Build.cs で `bEnableExceptions = true` しない場合は ICU でも例外を使わないCPP定義をする
// さしあたり unicode/unistr で SJIS -> UTF8 等を行うだけなら noexcept の定義だけ制御すればよい
#define U_NOEXCEPT 
#include <unicode/unistr.h>
// ...
  // ↓例えばHTTP で拾ってきたりした SJIS が詰まっているバッファーを受け取るなどする想定
  // ファイルオープンでもなんでも適当に必要な状況に併せて読み替える
  void Something1( const TArray< uint8 >& original_source )
  {
    // SJIS -> UTF8 とかしたい場合
    constexpr auto target_encode = "utf8";
    icu::UnicodeString us( original_source.GetData() );
    int length = us.extract( 0, us.length(), nullptr, target_encode );
    TArray< char > u8buffer;
    u8buffer.Init( 0, length + 1 );
    us.extract( 0, us.length(), (char*)u8buffer.GetData(), target_encode );
    // UTF8 になったので好きにする
    UE_LOG( LogTemp, Log, TEXT( "u8buffer=%s" ), UTF8_TO_TCHAR( u8buffer.GetData() ) )
  }
  void Something1( const TArray< uint8 >& original_source )
  {
    // SJIS -> UTF16 とかしたい場合
    constexpr auto target_encode = "utf16";
    icu::UnicodeString us( original_source.GetData() );
    int length = us.extract( 0, us.length(), nullptr, target_encode );
    TArray< TCHAR > u16buffer;
    u16buffer.Init( 0, length + 1 );
    us.extract( 0, us.length(), (char*)u16buffer.GetData(), target_encode );
    // UTF16 になったので好きにする
    UE_LOG( LogTemp, Log, TEXT( "u16buffer=%s" ), u16buffer.GetData() ) )
  }

Note: TCHARwchar_t になるので UTF16 の内部表現に一致するとは限らないとかそういう話は UE4-4.20 では問題にならないのでこのメモには特に書かない。もし UE4 ではない環境でこのメモを参考にする事があれば注意。

追記: この実装に必要最小限のリンクすべき ICU のライブラリーは何か?

実装例では icuucicudt をリンクしている。 uc の方は実行バイナリーへリンクしないと compiler は何も言わなくても linker が未定義の関数使用などでエラーを報告しビルドが完了しないのですぐにわかる。 dt の方は気付かずにリンクしないままバイナリーライブラリーも同梱しないままで実行すると icu::UnicodeString など使おうとしたソースが実行される瞬間に例えば Windows/MSVC++ 系なら delayhlp.cpp がどうちゃらなどと言ってアプリが落ちる。

アプリ -> uc -> dt と依存しているのだけど、 uc -> dt の依存解決は実行時に遅延されているので、 uc だけをリンクしバイナリーライブラリーを配置してもビルドは通っても実際には実行できないアプリになる。 ucdt へ依存している、あるいは他の何かへ依存していないかは Windows では古臭いが dependency walker を使うと把握しやすい。 GNU/Linux では ldd でわかる。

dependency walker ( Windows ) の場合

f:id:USAGI-WRP:20181024125204p:plain

ldd ( GNU/Linux; Ubuntu ) の場合

f:id:USAGI-WRP:20181024130832p:plain

UE4/C++, Blueprint: UAsyncTaskDownloadImage を拡張した実装を使いたい場合にリンクが必要となる RHI と RenderCore のメモ

状況

UAsyncTaskDownloadImage はブループリントでは癖の強めの非同期処理ノード。何かを For などでループしながら DownloadImage ノードを使い OnSuccess にテクスチャーデータだけでなく何らかのインデックスなどの付加情報を引っ掛けた処理を行わせようとすると、結果的にはループの最後の状態の値が非同期処理の実行時には採用され、意図しない処理結果となる事がある。

例えば次のような具合のノードをブループリントで組んだ場合、

f:id:USAGI-WRP:20180911185745p:plain

DownloadImage ノードの左側の同期処理される赤文字出力の PrintText では 1, 2, 3, ... が出力されるが、 DownloadImage ノードの右側の非同期処理されえる青文字出力の PrintText では 8, 8, 8, ... が出力される。内部的に IHttpRequest の非同期処理を使うため DownloadImageOnSuccess あるいは OnFail デリゲートのブロードキャストから実行される非同期処理の実行時には ForLoop は完了した最後の状態で Index8 になっている。( DownloadImage 実行時にその後の非同期処理で必要になる変数をキャプチャーしてくれる機能は無いが、ブループリントの実装都合 Index が未定義で落ちる事は無い。 )

そこで、必要に応じた変数のキャプチャーと通知の機能を仕込んだ UAsyncTaskDownloadImage の拡張を実装したくなる。幸い UE4オープンソースソフトウェアで実装を自分のプロジェクトへコピー&ペーストして、必要に応じて UAsyncTaskDownloadImageMyCustom などクラス名を変え、DownloadImage 関数の引数にキャプチャーしたい変数を追加しメンバー変数として保持しつつ、 OnSuccess あるいは OnFail デリゲートの定義を DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams など使って必要なだけ引数を拡張し、それらのデリゲートの Broadcast の呼び出し時にメンバー変数へ保持しておいた値を実引数として与えるようにすればよい。

よい、のじゃが、これだけだとビルドできない。たぶん↓のようなリンクエラーに襲われる事になる。

CompilerResultsLog: Error: AsyncTaskDownloadMap.cpp.obj : error LNK2019: unresolved external symbol "__declspec(dllimport) public: static bool __cdecl FRHIResource::Bypass(void)" (__imp_?Bypass@FRHIResource@@SA_NXZ) referenced in function "void __cdecl WriteRawToTexture_RenderThread(class FTexture2DDynamicResource *,class TArray<unsigned char,class FDefaultA
llocator> const &,bool)" (?WriteRawToTexture_RenderThread@@YAXPEAVFTexture2DDynamicResource@@AEBV?$TArray@EVFDefaultAllocator@@@@_N@Z)
CompilerResultsLog: Error: AsyncTaskDownloadMap.cpp.obj : error LNK2019: unresolved external symbol "__declspec(dllimport) public: unsigned int __cdecl FRHITexture2D::GetSizeX(void)const " (__imp_?GetSizeX@FRHITexture2D@@QEBAIXZ) referenced in function "void __cdecl WriteRawToTexture_RenderThread(class FTexture2DDynamicResource *,class TArray<unsigned char,c
lass FDefaultAllocator> const &,bool)" (?WriteRawToTexture_RenderThread@@YAXPEAVFTexture2DDynamicResource@@AEBV?$TArray@EVFDefaultAllocator@@@@_N@Z)
CompilerResultsLog: Error: AsyncTaskDownloadMap.cpp.obj : error LNK2019: unresolved external symbol "__declspec(dllimport) public: unsigned int __cdecl FRHITexture2D::GetSizeY(void)const " (__imp_?GetSizeY@FRHITexture2D@@QEBAIXZ) referenced in function "void __cdecl WriteRawToTexture_RenderThread(class FTexture2DDynamicResource *,class TArray<unsigned char,c
lass FDefaultAllocator> const &,bool)" (?WriteRawToTexture_RenderThread@@YAXPEAVFTexture2DDynamicResource@@AEBV?$TArray@EVFDefaultAllocator@@@@_N@Z)
CompilerResultsLog: Error: AsyncTaskDownloadMap.cpp.obj : error LNK2019: unresolved external symbol "__declspec(dllimport) public: static enum ENamedThreads::Type __cdecl FRenderCommand::GetDesiredThread(void)" (__imp_?GetDesiredThread@FRenderCommand@@SA?AW4Type@ENamedThreads@@XZ) referenced in function "private: void __cdecl TGraphTask<class `private: void 
__cdecl UAsyncTaskDownloadMap::HandleMapRequest(class TSharedPtr<class IHttpRequest,0>,class TSharedPtr<class IHttpResponse,1>,bool)'::`16'::EURCMacro_FWriteRawDataToTexture>::SetupPrereqs(class TArray<class TRefCountPtr<class FGraphEvent>,class TInlineAllocator<4,class FDefaultAllocator> > const *,enum ENamedThreads::Type,bool)" (?SetupPrereqs@?$TGraphTask@
VEURCMacro_FWriteRawDataToTexture@?BA@??HandleMapRequest@UAsyncTaskDownloadMap@@AEAAXV?$TSharedPtr@VIHttpRequest@@$0A@@@V?$TSharedPtr@VIHttpResponse@@$00@@_N@Z@@@AEAAXPEBV?$TArray@V?$TRefCountPtr@VFGraphEvent@@@@V?$TInlineAllocator@$03VFDefaultAllocator@@@@@@W4Type@ENamedThreads@@_N@Z)
CompilerResultsLog: Error: AsyncTaskDownloadMap.cpp.obj : error LNK2019: unresolved external symbol "__declspec(dllimport) public: static enum ESubsequentsMode::Type __cdecl FRenderCommand::GetSubsequentsMode(void)" (__imp_?GetSubsequentsMode@FRenderCommand@@SA?AW4Type@ESubsequentsMode@@XZ) referenced in function "public: static class TGraphTask<class `priva
te: void __cdecl UAsyncTaskDownloadMap::HandleMapRequest(class TSharedPtr<class IHttpRequest,0>,class TSharedPtr<class IHttpResponse,1>,bool)'::`16'::EURCMacro_FWriteRawDataToTexture>::FConstructor __cdecl TGraphTask<class `private: void __cdecl UAsyncTaskDownloadMap::HandleMapRequest(class TSharedPtr<class IHttpRequest,0>,class TSharedPtr<class IHttpRespons
e,1>,bool)'::`16'::EURCMacro_FWriteRawDataToTexture>::CreateTask(class TArray<class TRefCountPtr<class FGraphEvent>,class TInlineAllocator<4,class FDefaultAllocator> > const *,enum ENamedThreads::Type)" (?CreateTask@?$TGraphTask@VEURCMacro_FWriteRawDataToTexture@?BA@??HandleMapRequest@UAsyncTaskDownloadMap@@AEAAXV?$TSharedPtr@VIHttpRequest@@$0A@@@V?$TSharedP
tr@VIHttpResponse@@$00@@_N@Z@@@SA?AVFConstructor@1@PEBV?$TArray@V?$TRefCountPtr@VFGraphEvent@@@@V?$TInlineAllocator@$03VFDefaultAllocator@@@@@@W4Type@ENamedThreads@@@Z)
CompilerResultsLog: Error: AsyncTaskDownloadMap.cpp.obj : error LNK2019: unresolved external symbol "__declspec(dllimport) class FRHICommandListImmediate & __cdecl GetImmediateCommandList_ForRenderCommand(void)" (__imp_?GetImmediateCommandList_ForRenderCommand@@YAAEAVFRHICommandListImmediate@@XZ) referenced in function "private: virtual void __cdecl TGraphTa
sk<class `private: void __cdecl UAsyncTaskDownloadMap::HandleMapRequest(class TSharedPtr<class IHttpRequest,0>,class TSharedPtr<class IHttpResponse,1>,bool)'::`16'::EURCMacro_FWriteRawDataToTexture>::ExecuteTask(class TArray<class FBaseGraphTask *,class FDefaultAllocator> &,enum ENamedThreads::Type)" (?ExecuteTask@?$TGraphTask@VEURCMacro_FWriteRawDataToTextu
re@?BA@??HandleMapRequest@UAsyncTaskDownloadMap@@AEAAXV?$TSharedPtr@VIHttpRequest@@$0A@@@V?$TSharedPtr@VIHttpResponse@@$00@@_N@Z@@@EEAAXAEAV?$TArray@PEAVFBaseGraphTask@@VFDefaultAllocator@@@@W4Type@ENamedThreads@@@Z)
CompilerResultsLog: Error: AsyncTaskDownloadMap.cpp.obj : error LNK2001: unresolved external symbol "__declspec(dllimport) bool GRHINeedsExtraDeletionLatency" (__imp_?GRHINeedsExtraDeletionLatency@@3_NA)
CompilerResultsLog: Error: AsyncTaskDownloadMap.cpp.obj : error LNK2001: unresolved external symbol "__declspec(dllimport) private: static class TLockFreePointerListUnordered<class FRHIResource,128> FRHIResource::PendingDeletes" (__imp_?PendingDeletes@FRHIResource@@0V?$TLockFreePointerListUnordered@VFRHIResource@@$0IA@@@A)
CompilerResultsLog: Error: AsyncTaskDownloadMap.cpp.obj : error LNK2001: unresolved external symbol "__declspec(dllimport) class FDynamicRHI * GDynamicRHI" (__imp_?GDynamicRHI@@3PEAVFDynamicRHI@@EA)
CompilerResultsLog: Error: AsyncTaskDownloadMap.cpp.obj : error LNK2001: unresolved external symbol "__declspec(dllimport) class FRHICommandListExecutor GRHICommandList" (__imp_?GRHICommandList@@3VFRHICommandListExecutor@@A)
CompilerResultsLog: Error: AsyncTaskDownloadMap.cpp.obj : error LNK2001: unresolved external symbol "__declspec(dllimport) bool GIsThreadedRendering" (__imp_?GIsThreadedRendering@@3_NA)
CompilerResultsLog: Error: AsyncTaskDownloadMap.cpp.obj : error LNK2001: unresolved external symbol "__declspec(dllimport) class TAtomic<bool> GMainThreadBlockedOnRenderThread" (__imp_?GMainThreadBlockedOnRenderThread@@3V?$TAtomic@_N@@A)

リンクエラーの解消法

MyProjeect.Build.csPublicDependencyModuleNamesRHIRenderCore を追加する。

    PublicDependencyModuleNames.AddRange
      ( new string[]
        { "RHI", "RenderCore"

ちなみに、 RHI は Rendering Hardware Interface の事らしい。プラットフォームごとに D3D, OGL などを抽象化したレンダリングハードウェアのインターフェースらしいが、私もその実装まではまだ確認していない。

参考