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

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

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 などを抽象化したレンダリングハードウェアのインターフェースらしいが、私もその実装まではまだ確認していない。

参考

UE4/C++,Blueprint: BPへ関数のパラメーターを参照だけど入力としてノードを与えたい場合にどうするか

問題

UFUNCTION( BlueprintCallable ) static void Something1( FVector& VR , const FIntVector VC );

このような UE4/C++ で生成される Blueprint ノードは、 VC を入力ピン、 VR を出力ピンにしてしまう。 VR も入力ピンの意図で実装した場合に Blueprint で問題となる。

参照の関数パラメーターを Blueprint で入力ピンにする方法

// 仮引数に UPARAM(ref) を付ける
UFUNCTION( BlueprintCallable ) static void Something2( UPARAM(ref) FVector& VR , const FIntVector VC );

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

参考

UE4: Camera の GetActorLocation で得られる位置と1フレームの Tick の中のタイミングのメモ

状況例

「サードパーソンテンプレートのようなプロジェクトを作り、プレイヤーキャラクターとカメラのちょうど中間に何かしらアクターを移動させたい。」

実装手順

中間の位置に出したいアクターの Blueprint へ次のように実装する。

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

おそらく遭遇する問題

カメラの GetActorLocation が1フレーム程度遅延した位置を返すため、中間に出したいアクターが少しずれた位置へ出てしまい、ブレ感が発生してしまう。

https://imgur.com/yLIq7Bh

View post on imgur.com

この問題は ACharactor が基底型 APawnAPlayerController 型の Controller メンバーを介して Rotation 等の入力を処理する場合(サードパーソンテンプレートのプレイヤーキャラクターなどでの初期設定状態)に発生する。入力をイベントが受け付けた段階での処理、またはキャラクターや中間に起きたいアクターの Tick では Controller には変化量が保持されているだけで、まだ実際の変化は適用されていない。(なぜ、あるいは詳細は APawn::AddControllerPitchInput, APlayerController::AddPitchInput, AController::SetControlRotation とその呼び出し元の実装を探索すると理解が早いかもしれません。 )

無駄な対処法

AddTickPrerequisiteActor でどうにかしようとする。

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

どうにもなりません。

解決方法: 中間に起きたいアクターの Tick でブレずに(遅延せずに)カメラの位置を取得する方法

中間に起きたいアクターの TickGroupPostUpdateWork に変更する。

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

UE4Editor の標準状態では TickGroup は非表示状態になっているので、 Actor Tick の「▽」的なボタンを展開して変更する。

https://imgur.com/LIGuhZH

View post on imgur.com

遅延しなくなりました😃

Reference

UE4/C++: Stats ( Profiler ) 機能の組み込みと使い方メモ

Runtime/Core/Public/Stats/Stats.h で宣言・定義される Stats ( Profiler ) 機能を C++ コードに仕込んで UE4Editor のプロファイラーでお手軽に負荷など調べる方法のメモ。

仕込み

// MySourceFile.h
#pragma once
#include "CoreMinimal.h"

// 略
// MySourceFile.cpp
#include "MySourceFile.h"

// グループ定義
DECLARE_STATS_GROUP( TEXT( "MySomething" ), STATGROUP_MySomething, STATCAT_Advanced );
// 具体的な計測項目の定義。複数使いたければ使いたいだけ定義する
DECLARE_CYCLE_STAT( TEXT( "AMySomething::DoSomething" ), STAT_MySomething_DoSomething , STATGROUP_MySomething);

// 略

void AMySomething::DoSomething()
{
  SCOPE_CYCLE_COUNTER( STAT_MySomething_DoSomething );
  // ↑のスコープが終わるまでが記録される
}

使い方

記録をファイルへ取る

UE4Editor の Output Log の Cmd または Development/Debug で自動的にゲームに組み込まれるコンソール機能に

  • 記録開始: stat startfile
  • 記録終了: stat stopfile

を投げる。ファイルは実行状態に併せて自動的に命名されて、プロジェクトのディレクトリー以下の Saved/Profiling/UnrealStats/ 以下によしなに保存される。ファイルの拡張子は .ue4st

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

記録したファイルを見る

  1. UE4Editor のメニューから Window > Developer Tools > Session Frontend を表示
  2. Session Frontend ウィンドウ内の Profiler タブを表示
  3. Profiler タブ内の Load ボタンから記録ファイルを開く

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

付記事項

  • UE4のログマクロなどと同じ形式で複数ファイルへ跨って使用したい場合には必要に応じて DECLARE_CYCLE_STAT_EXTERN を使う。(参考2)

参考

  1. Profiling, How To Count CPU Cycles Of Specific Blocks Of Your Game Code - Epic Wiki
  2. Custom Profiler Stats - UE4 AnswerHub

UE4: PS4 の DualShock4 コントローラーを Windows PC に Bluetooth 接続して UE4 の Input で使う方法

1. PS4 DS4 を Bluetooth で PC に接続

  1. DS4 を SHARE + PS の同時長押しで Bluetooth ペアリングモードにする
    • このモードになるとインジケーターがピピッ的に断続的に発光する状態になる
  2. Windows PC の設定 "Bluetooth & other devices" からペアリングする(日本語でもたぶんそれっぽいカタカナになっているだけでしょう、たぶん)
    • 設定画面最上部の "Add Bluetooth or other device" をポチッとして "Bluetooth" を選択し、 Wireless Controller が認識されたらポチるだけです。

これだけで DS4 を Windows PC で使用可能になります。動作テストは Monster Hunter: World などで適当にどうぞ。STEAM を使っている場合には、 STEAM の設定機能から DS4 のインジケーターの発光色を変更するなどもできます。

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

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

3. UE4 のプロジェクトから使えるようにする

方法が幾つかある様子ですが、簡単に期待動作が得られたものは次の3つのうちでは1つだけでした。

  1. UE4 標準の Built-In プラグイン Windows RawInput を使う (期待動作せず)
  2. UE4 標準の Built-In プラグイン Steam Controller を使う(ようわからんかった)
  3. katze7514/UEDirectInputPadPlugin プラグインを使う(まともに動く方法はこれだった)

3.1. Windows RawInput で遭遇した問題

Windows RawInputのドキュメントを参考に試してみたものの…

  1. 軸の入力がおかしい。 Offset が必要なだけかと思いましたが、 Offset を設定してもセンターを調整できない状態でした💀
  2. DS4 の ○ ボタンなどの応答がおかしい。押してもイベントが発火しない事の方が多く、何度か押しているとたまに Pressed が来る状態でこれはもうダメだと思いました💀

こんなところのソースに興味は無いし、他の方法も試せるのでこれはもう諦めました。(DirectInputの複数デバイス、アナログ入力、フォースフィードバックの実装を書いた事はたぶん人生で3回くらいはありますが、また書きたいとも思いません😅)

Note: Steam が動作中は DirectInput コントローラーを Steam が専有するため、 UE4Editor で開いたプロジェクトどころか、Windows の Game Controllers やそこから開けるコントローラーのキャリブレーションとテストができるプロパティーウィンドウなどもデバイスの認識はしているものの入力は一切受け取れない状態になります。 Windows RawInput を試す場合は Steam が起動していない状態を確認しましょう。(これにもちょっとやられました…💀)

3.2. Steam Controller を使おうと思ったものの…

Steam Controller プラグインを有効にしても Windows RawInput のような設定画面も用意されておらず、 Steam の入力イベントもバックボタンとタッチがあるのみなので、一般的な軸やボタン群は自動的に標準の XInput パッドへ割り当てでもされるのかな、と勝手に期待して実行してみるも反応せず💀 Steam Online Subsystem プラグインも必要なのだろうか、など有効にしてみたものの、特に変化もなく💀

UE4側に有用でわかりやすいドキュメントもなさそうだし、STEAMのドキュメントからはUE4プラグインの内部処理でやってくれるであろう取得方法とかしかでてきませんし、使い方がようわからんし、ソース見るのはもう1つ見つけていた方法を試してもダメだった後にしようと思いとりあえず諦めました💀

3.3. katze7514/UEDirectInputPadPlugin で幸せになる

DirectInput 汎用のプラグインのため、 PS4 の DS4 を使う場合にも一部リマップやリマップ相当の対処は必要でしたが、ほぼ期待動作してくれました😃 若干期待動作しなかった機能はリマップ相当の代替手段で対処すれば、見かけ上は期待動作になります。

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

↑の絵のようにリマップすると、はじめのリマップノード2つ DS4 のクロスとサークルへの割り当ては期待動作しますが、残念ながら3つめのリマップノードの右スティックのY軸への割り当ての Negative は動作しないようでした。この部分はプロジェクトの設定の Engine の Input 設定を反転させるなり、それを使う実装側で反転させる係数を書ければその場しのぎはできます。

ただし、XInputのコントローラーと PS4 の DS4 のコントローラーどちらにも同様の動作を設定したい場合は接続状態を確認して係数を反転させてあげないと、一般的な3Dアプリの操作系でいえばカメラのY軸が接続するコントローラーによって順方向と逆方向がリバースしてしまうという怪奇現象になり、少々面倒です。これは後ほど Issues へ Negative が期待動作していない旨をポストしておこうと思います。パッチまで投げるかは…気分と必要の次第としつつ。

追記: Issue投げました → Set Key Map の Negative が期待動作しない #5

追記追記: プラグインを開発された katze7514 さんから「その用途なら Set Key MapNegative じゃなくて Set Axis ReverseReverse でできるよ!」と↑の Issue で教えて頂きました。私がプラグインの仕様を勘違いしてしまっていただけで綺麗に意図した動作を組み込めました😃 )

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

↑今回の目的で組むべきだった本来のノードとパラメーター。

A. おまけ、接続したコントローラーの VID, PID の確認の仕方

Windows RawInput プラグインを使う場合に確認が必要となります。

  1. "Device Manager" の "Human Interface Devices" を展開して HID-compliant game controller を探す
  2. プロパティーを表示する
  3. "Details" の "Property" を "Hardware Ids" に変更して VID とか PID っぽい値を確認する

DS4(CUH-ZCT2J)の場合は VID=054C PID=09CC のようです。 VID は Vendor ID 、 PID は Product ID で、それぞれ販売元と製品を一意に認識するための ID です。この番号はデバイスに自由に与えられるものでは無く(野生のUSBデバイスを作ればものとしては作れるけれど)、通常一般的に流通している USB 製品にはすべて割り当てが存在します。DS4でも型式が違えば、PIDが異なる可能性があります。また、Horiのゲームパッドなど販売元が異なるコントローラーではVIDも異なる可能性があります。横着せず、実際に挿したデバイスの VID, PID を調べてから進みましょう。

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

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

ゲームエンジンをいじりたいならいいんですが、立派なフレームワークの上でそれほどマイナーでもなかろうゲームコントローラーが標準機能で安心して使えない状況はちょっとショッキングな事態でした。これまでゲームコントローラーの動作試験は XInput のゲームパッドだけで行っていたので気づきませんでしたが、 UE4PS4 の DS4 を使いたいというありがち…であろうと思われるニーズで少々苦労があるとは思いもしませんでした。

とりま、動いたのでとりあえずヨシという事にします😃
(はいはいヨシネコさんでいいですよ…DirectInputとかSTEAM Controllerの実装書くモチベーションは特にありませんしー。 )

今回は katze7514/UEDirectInputPadPlugin の存在に感謝しつつありがたく使わせて頂く事にします。

UE4/C++: DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam と TArray

問題

C++ コードの実装に DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam マクロを使い、パラメーターに TArray を入れて Delay (Blueprintノード右上に時計マークの出るアレ)を経て結果を処理できる非同期動作のデータのジェネレーターを UE4 の勉強と練習として作ってみました。しかし、不思議な事に C++ ソースコードコンパイル(ビルド)は完了し、 UE4Editor も動作するのですが、実際にその実装を Blueprint で試すと、 Blueprint がコンパイルエラーとなり使用できない状況に遭遇しました。

// 例えばこんな動的マルチキャストデリゲートを使おうとする。
// このようにパラメーターに TArray を直接入れると Blueprint で使う段階になってから問題が起こる。
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam( FGenerateMyDataDelegate, TArray< uint8 >, MyData );
// ↑を使ったクラスの例
UCLASS()
class MyExperimental_API UAsyncTaskGenerateMyData
  : public UBlueprintAsyncActionBase
{ GENERATED_UCLASS_BODY()
public:
  UPROPERTY( BlueprintAssignable ) FGenerateMyDataDelegate OnSuccess;
  UPROPERTY( BlueprintAssignable ) FGenerateMyDataDelegate OnFail;
  // 実際にはこのクラスを作って投げる機構なども付ける
  // UE4 標準の AsyncTaskDownloadImage のソースなど参考にするとよい
};

Blueprint で発生するエラーは↓のようになる。

f:id:USAGI-WRP:20180817035243p:plain
f:id:USAGI-WRP:20180817035250p:plain

LogBlueprint: Error: [Compiler VW0_BP] Generate My Data  Signature Error: The function/event 'OnSuccess_C9A93DFE4DF51C0C6C9496873C6AAC62' does not match the necessary signature - has the delegate or function/event changed?
LogBlueprint: Error: [Compiler VW0_BP] Generate My Data  Signature Error: The function/event 'OnFail_C9A93DFE4DF51C0C6C9496873C6AAC62' does not match the necessary signature - has the delegate or function/event changed?

他の型を与えた場合について

ちなみに、パラメーターに TArray ではなく、代替として次のような USTRUCT ラッパーを定義して与えると Blueprint でもコンパイルエラーが発生せずに意図通り動作する。

USTRUCT( BlueprintType )
struct MyDataWrapper
{ GENERATED_BODY()
  TArray< uint8 > MyData;
};
// ラッパーを噛ませたマクロを使うと Blueprint も意図通りに動作可能になる。
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam( FGenerateMyDataDelegate, MyDataWrapper, MyData );

また、 UTexture* など適当なコンポーネント(のポインター)を DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam のパラメーターへ与えた場合も Blueprint は意図通り動作する。

解決

// マクロに対して const & でパラメーターを与えれば TArray を使用し、 Blueprint も意図通りに使用可能になる。
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam( FGenerateMyDataDelegate, const TArray<uint8>&, MyData );

なるほどー😃 C++er にはピンと来る答えです。教えて貰う前に気付けるのが真の C++er とか嫌味を言われそうですが、気にしない。

参考

  1. DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam with TArray - UE4 AnswerHub
    • 今回の記事は UE4 公式の Answers へポストした質問と回答を元に書きました。
    • クールな回答でさくっと教えてくれた Sveitar さん、ありがとうございました😃

MHW, STEAM: モンスターハンターワールドSTEAM版のユーザー視点からの観測とTips

モンスターハンターワールドのSTEAM版、7月のうちにお布施のつもりで購入してありました😃 お布施のつもり、というのは PS4 版で既にトロフィーをコンプリートしてエンドコンテンツも全て楽しく遊ばせて頂いてしまっている状態で、事前からどうもセーブデータの移行はできないらしいと噂されていたためです。

そんなわけで、改めてPCでやり込むかはさておき、序盤をSTEAM版でしばらくプレイしてみて気づいた事を幾つかメモします。

1 マルチモニター環境での表示モニターの変更方法

最初に困ったのがこれ。横に3つモニターを並べている環境で、右側のモニターがタスクバーの居る1番にしてあるのですが、ゲームや仕事などで中心的な操作対象は真ん中のモニターに出したかったのです。MHWを起動すると案の定モニターの認識上最初のモニターにしてある右側へ画面が出ました。

リアル・フルスクリーン・モードで起動した事には今時は古風で残念なものの、日本のゲーム屋さんが作るウィンドウズのアプリだしなー…とやや諦め気味に思ったものの、これはPCの操作性上好ましい疑似・フルスクリーン・モード(MHWの設定表示ではボーダーレス・ウィンドウ・モード)がコンフィグレーションに用意されており、少し心が安らぎました。

他に、ウィンドウモードもあったので、試しに一度ウィンドウモードで再起動して、それからウィンドウを真ん中のモニターへ移動させて、それからまた疑似フルスクリーンモードへ切り替えて再起動させてあげたところ、疑似フルスクリーンモードで真ん中のモニターで起動させる事に成功しました。

めでたしめでたし。

2 疑似フルスクリーンでの解像度に注意

FHDモードで使用中のモニターへ疑似フルスクリーンモードでモンスターハンターワールドを起動した際にも、内部的なレンダリングの解像度はコンフィグレーションの解像度設定が生きており、モンスターハンターワールドの解像度設定もFHDにしておかないと、「一見するとFHDなのになんか汚い」という状態になってしまいます。汚さですぐに気づきそうなものだけど、これはちょっとした注意どころでした。

f:id:USAGI-WRP:20180812045837p:plain
https://twitter.com/USAGI_WRP/status/1028318674812198912
↑こういう間抜けな事になります(´・ω・`)

3 アンチエイリアス効いてなくない?

続けて絵作り設定からアンチエイリアスについて。どうも TAA (一般的に考えればたぶん Temporal-AA )が実質的に効いていないバグがあるのではないか?という疑問が感じられました(よくよく観察すると効いているっぽい事がわかりましたが…)。

↑のツイートより後、この記事を書くにあたり改めて確認してみた絵が↓

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

こうしてよーくよーく見ると TAA も一応仕事をしている様子。たそもそもこれ、 TAA と表示して TXAA では無いらしい事と、この絵で None と TAA をよく比較して眺めてみた様子からは、 PC ゲームの少なくとも近年の NVIDIA GPU で一般的になった TXAA ではなく独自に Temporarl AA の基礎的な実装をしたものなのかな、という気もします(MHWの元プラットフォームのPS4GPUAMDらしいし・w・)。

Temporal AA の基礎的なコンセプト、実装は描画対象のフレームとそれよりも前のフレームをブレンドしてギザギザを隠すもの。スクリーンショットの比較でも、動きの無い掲示板やその張り紙では効果が目視ではわからない状態ですが、常に微妙に動いたりアクションしているオトモの身体や装備に注目すれば AA が効いているように、あるいは寧ろ効きすぎてぼやけてしまっている程の部分に気づきます。静止したオブジェクトやシーンでは None と違いが少なくとも目視では無く、動く部分もボケすぎる心配もあるモンスターハンターワールドの TAA は AA を設定するユーザーの求める絵作りに対しては中途半端で微妙かな、と個人の感想としては思います。

一方、 FXAA は一般的な FXAA で全体に綺麗な AA 処理がされていて、 AA を設定するユーザーにとっては目的に適った絵ができているように見えます。モンスターハンターワールドでは半透明の描画はディザリングで代替されている様子なので、半透明処理の工夫や相性と AA の組み合わせによっては汚い絵ができてしまう懸念もありませんし、モンスターハンターワールドで AA が欲しいユーザーは FXAA を使うのが良いのかな、と個人の感想としては思います。

4 操作関連あれこれ

4-A プレイ開始からしばらくカメラがリバース問題

どうしようもないので、リバースのまましばらく耐えて進めましょう…。モンスターハンターワールドのカメラの回転の初期設定、順方向は FPS 視点が基準になっています。このゲームには FPS モードは無くて基本的に常に TPS なんですけどね😅 そして、ゲーム開始からムービーシーンと接続して操作可能になる最初のプレイアブルシーンではコンフィグを開けません💀 しばらく進めるとコンフィグのボタンも反応するようになるので、それまでどうにか耐えて進みましょう。

4-A-Appendix ここでちょっと FPS / TPS カメラと 順方向 / 逆方向 のだそく

ちなみに、 FPS 視点基準と表現したカメラ操作は、プレイヤーが「右っぽいボタン(レバー)」を操作すると視点(カメラ≃目玉のある位置)は動かずに注視点(見つめる場所、≃視線)が「右側」へ移動します。これは FPS としては自然な順方向でしょう。

一方、 TPS 視点基準では、カメラ(≃プレイヤーキャラクターを俯瞰・衛星軌道的で捉える第三者の視点≠プレイヤーキャラクターの目玉の位置)を操作するのであって、仮にプレイヤーキャラクターの視線を操作しても、シーンの中でプレイヤーキャラクターが目玉や首や場合によっては身体の向きを変えるだけで、 TPS 視点におけるプレイヤーキャラクターを含めたシーンの視錐台は変わりません。プレイヤーが操作するカメラはプレイヤーキャラクターの目ではなく、プレイヤーキャラクターを捉えるドローンカメラの位置を操作する事になります(一般的にはプレイヤーキャラクターを原点とした経度・緯度・高度からなる極座標系で周るように操作できるようにする)。そうすると、 TPS を基準とした順方向の操作結果は FPS で注視点を回すのとは逆向きになります。「右っぽいボタン(レバー)」を操作するとプレイヤーを捉えるカメラの緯度を右側へ回す事になるので、これは一般的にプレイヤーを背後から捉えている状態を基準に考えると、「画面(視線)は左側へ回っていく」ことになります。つまり、 FPS 操作の操作方向と結果画面の動く方向と TPS のそれはこういう理屈では逆転(リバース)します。あくまでもこういう理屈では。

しかし、読んでくれている方の中にも「いやいや…」と思う方も少なからずいらっしゃるはず。プレイヤーによってはこうした理屈とは異なる反応を脳が学習・示す事も少なくはなく発生するらしく、人によっては同じゲームで FPS と TPS の両方を切り替えて操作するようなものであれ、「常に」結果の画面の移動方向がレバーを操作する方向と同じ(あるいは逆転)にならないと脳が混乱してしまう、という事も起こります。FPS でも TPS でも操作ではなく「結果画面の動く方向」を FPS 基準または TPS 基準のどちらかに固定しないと頭痛で楽しくプレイできない、という方たちがそのタイプ。

加えて複雑な事に、私にも理屈はわからないのですが、上下、左右で順方向と逆方向を個別に異なる設定にしないと頭痛が…という方も少なくなくいらっしゃるようです。いぜん、少数ながら複数の友人に確認してみても人それぞれかなりばらつきがありました…。

世の中には、

  1. FPS ならレバーの操作方向は視線の操作方向、 TPS ならレバーのの操作方向は背後から捉えるカメラの操作方向( FPS でも TPS でも画面ではなく操作対象は何かで視錐台の変化を捉える人)
  2. FPS でも TPS でもレバーの操作方向は結果画面の順操作方向(画面が TPS でも脳は FPS で捉える人)
  3. FPS でも TPS でもレバーの操作方向は結果画面の逆操作方向(画面が FPS でも脳は TPS で捉える人)

の3つのパターン+(1),(2),(3)を上下方向と左右方向で独立して異なる組み合わせで捉える人たち、さらにもしかしたらこれも理屈はわかりませんが(1)の逆の操作パターンが脳にしっくりくる人もいるかもしれません。私は(1)タイプで、他の操作系では数秒で強い頭痛、目眩、吐き気などでゲームプレイどころではなくなってしまいます…。たぶん、他のパターンの人たちも別の操作パターンでは程度の違いこそあれそういった症状がでる人が多いかと思います。

3Dアプリ作る時に、いつもどうデフォルトにするか、どうコンフィグレーションを付けるか、悩みます。特に、ユーザー層がゲーマーならどうデフォルトにしてもコンフィグを付けておけばOKなんですが、非ゲーマー向けだと違和感が生じる人への説明と設定方法も工夫が必要で、こうして文章で説明どころか図解してもなかなか話が通じません…、そしてどうコンフィグレーションすればあなたにピッタリの操作感になるかもしれないので設定してみてくれ…というのも説明が難しいという💀 ソフトウェア開発者ですら、かつ日常的にゲームを楽しんでいるはずの人を相手にしても、これを説明するのには非常に大変な事もあります💀💀

とりあえず、せめて、ゲームプレイ開始前にコンフィグできるようにお願いします💀💀💀

4-B キーボードとマウスの操作があばばばば

わかりやすい動画が STEAM のレビューに貼ってあったので↓参考

www.youtube.com

この問題、キーボードとマウスで操作する派にWASDを諦めろ!というのはたいへん酷なはなしなので(先のカメラ設定の感覚くらい脳に染み込んでいる人も多いハズ…)、私なりの解消アドバイスとしては、

  1. 「W」「A」「S」「D」 はそのまま
  2. 「マウス右」に「エイム(元V)」を変更
  3. 「SPACE」 に「特殊攻撃(元マウス右、調べるその他超複合ボタン)」を変更
  4. 「X」 に 「しゃがむ(元SPACE)」を変更
  5. 「SHIFT」に「リロード(元R)」を変更
  6. 「CTRL」に「ダッシュ(元SHIFT)」を変更
  7. 「V」に「武器を構える(元CTRL)」を変更(これは元々「マウス左」も割り当てられているので、キーは念の為に一応割り当てた程度)

私はWASDについて、Aを薬指、WSを中指、Dを人差し指で操作しています。そのため、リロードがRのままだと右へ移動しながらリロードできません。同様にR+SPACEでボウガンの近接攻撃も右移動と同時に動作できません。そこで、SPACEの親指とも同時押ししやすく、WASDで使用しない小指だけで移動しながらでも押せるキーとして、SHIFTへリロードを変更しています。これはSHIFTではなくCTRLでも操作しやすいかもしれません。この影響を受けつつも、元SHIFTのダッシュもWASDと同時に押せないと意味が無いのでCTRLへダッシュを変更しています。

これで起爆竜弾、ボウガン殴り、竜の一矢(実用する事があるかはさておき)など、比較的激しいアクションを要求されるモンスターハンターワールドでもWASD移動と同時に使える設定になったと思います。(序盤の任務プレイとトレーニングルームで検討した程度なので、ラスボスさんやその後までプレイしたらもっと別の上手い設定が欲しくなるかもしれませんが、とりあえず、ということで。)

4-C そもそもマウス操作の応答に大きめのディレイが生じる

マウス(あるいは任意のポインティングデバイス)の位置を操作したら、画面のカーソルの位置が倍率はともかく操作どおりに応答してくれる。ふつー、そう思うじゃないですか・w・ モンスターハンターワールドでは謎のディレイが体感ですが恐らく数十ミリ秒単位で入ってマウス操作にカーソル位置がディレイして適用されます。

感覚的にしんどいのでマウス操作は私には無理ですね😅

これは…諦めました💀

エイム時には1フレームかせいぜい数フレーム程度のディレイに感じられたので、戦闘中に問題になる事はなさそうですが、メニューや装備変更などマウス操作する際にはいらいらしそうです…。

5. PS4と・・・

無理です諦めましょう。

PS4とセーブデータを移行する手段は少なくとも公式には存在しません(セーブデータの解析からツールを作ればある程度はできそうな予感はしますが、MOかつ他のプレイヤーに影響する事はまず無かろうとは言え、何かと問題の引き金になりかねないので健全なプレイヤーは考えない方が良いでしょう)😭

また、マルチプレイのマッチングもSTEAM版はSTEAM版のプレイヤー同士のみで、PS4版のプレイヤーとは一緒に遊べないようです。これはもう、本当にどうしようもないので諦めましょう😭

さておき

と、問題と対応方法を並べてみましたが、モンスターハンターワールドはゲームとしてはよくできたコンテンツを十分に楽しめる良作と私は思います。STEAM版ではPS4版よりも綺麗な見栄えでプレイできますし、キーボードの存在を前提にした使いやすさはゲームパッドを主として使用するプレイヤーにも恩恵があります。スクリーンショットや動画の撮影と共有のためにわざわざシステムメニューを介在してちまちま操作する必要もなく、Twitterへの共有などもコピー、ウィンドウ切り替え、ペースト、ポストだけでできてPS4よりもテンポよく処理できます。低性能ではないWindowsのPCを使える方で、モンスターハンターワールド未プレイの方、興味があればSTEAM版をぜひ楽しんで欲しいなとおすすめします。

私の中でのモンスターハンターワールドのゲームとしての総合評価は☆5満点で☆4です。少なくともエンディングまでのストーリーと付随するフリークエストなどはとても楽しいし、音楽も世界観にあったよいものに感じています。エンドコンテンツややりこみ要素については一概におすすめできませんが、少なくともPS4版ではHR360、トロフィーのコンプリート、これまでのすべての追加モンスターを私は楽しみました。そろそろどうかなーとは思いますが、それでも4ヶ月間楽しませて貰えた良作です。お値段もSTEAM版にせよ、PS4版にせよ、ずいぶん公式価格で安くなりましたしね。ゲームとしておすすめな事は確かです。

さて、それでは改めまして、一狩り行きましょうか😃