UE4/C++: 4.18 以降発生するクリップボードのAPI仕様変更に伴う警告と必要な修正
状況
FGenericPlatformMisc::ClipboardCopy
または FGenericPlatformMisc::ClipboardPaste
を使用している場合、UE4-4.18 以降で API の仕様変更に伴う警告が翻訳時に発生する。
warning C4996: 'FGenericPlatformMisc::ClipboardCopy': FPlatformMisc::ClipboardCopy() has been superseded by FPlatformApplicationMisc::ClipboardCopy() Please update your code to the new API before upgrading to the next release, otherwise your project will no longer compile.
warning C4996: 'FGenericPlatformMisc::ClipboardPaste': FPlatformMisc::ClipboardPaste() has been superseded by FPlatformApplicationMisc::ClipboardPaste() Please update your code to the new API before upgrading to the next release, otherwise your project will no longer compile.
必要な修正
実装クラスが変更されただけではなく、変更先のヘッダーが CoreMinimal.h
に含まれていない点、加えて実装クラスが提供されるモジュールも独立しているためモジュールの追加も必要となる点に注意。
FGenericPlatformMisc
をFPlatformApplicationMisc
に置き換える。HAL/PlatformApplicationMisc.h
を include 追加。<project>.Build.cs
でApplicationCore
モジュールを追加。
something.cpp:
// ... #include "HAL/PlatformApplicationMisc.h" // ... // copy to clipboard FString hoge = TEXT( "hoge" ); // old FGenericPlatformMisc::ClipboardCopy( *hoge ); // new FPlatformApplicationMisc::ClipboardCopy( *hoge ); // ... // paste from clipboard FString hoge; // old FGenericPlatformMisc::ClipboardPaste( hoge ); // new FPlatformApplicationMisc::ClipboardPaste( hoge );
<project>.Build.cs
:
PublicDependencyModuleNames.AddRange ( new string[] { // ... , "ApplicationCore" // ... } );
UE4/C++: 4.18 -> 4.19 エンジンアップデートトラブル; `UMaterialInstance::GetVectorParameterValue` の仕様変更と "Material Layers"
現行プロジェクトに必要なプラグインの 4.19 対応も進んできたので、手元のプロジェクトを 4.18 から 4.19 へエンジンアップデートしてみました。エンジンの仕様変更によるトラブルがあったので対処方法と併せて紹介します。
4.18 -> 4.19 における UMaterialInstance::GetVectorParameterValue
の仕様変更
UMaterialInstance::GetVectorParameterValue
の引数仕様が後方互換性なしで変更されていました。
Engine/Source/Runtime/Engine/Classes/Materials/MaterialInstance.h:
// 4.18 virtual ENGINE_API bool GetVectorParameterValue(FName ParameterName, FLinearColor& OutValue) const override;
// 4.19 virtual ENGINE_API bool GetVectorParameterValue(const FMaterialParameterInfo& ParameterInfo, FLinearColor& OutValue, bool bOveriddenOnly = false) const override;
- 第一引数が変更:
FName ParameterName
->const FMaterialParameterInfo& ParameterInfo
- 第三引数が追加:
bool bOveriddenOnly = false
FMaterialParameterInfo
は 4.18 には存在しない 4.19 で追加された型で MaterialInstance.h と同じ場所に追加された MaterialLayersFunctions.h で定義されていた。
Engine/Source/Runtime/Engine/Classes/Materials/MaterialLayersFunctions.h:
// 本来のソースからメンバー変数と ctor を引用、日本語で概説を付けた。 USTRUCT(BlueprintType) struct ENGINE_API FMaterialParameterInfo { GENERATED_USTRUCT_BODY() // 最低限これだけ与えればよい。 4.18 まで直接与えていた FName のパラメーターに相当 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=ParameterInfo) FName Name; // グローバルパラメーターか、あるいはレイヤーパラメーターまたはブレンドパラメーターかを設定。 4.19 で追加。 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=ParameterInfo) TEnumAsByte<EMaterialParameterAssociation> Association; // グローバルパラメーターの場合は INDEX_NONE, レイヤーパラメーターまたはブレンドパラメーターの場合は有効なインデックス値を設定する。 4.19 で追加。 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=ParameterInfo) int32 Index; // ctor pattern 1 FMaterialParameterInfo(const TCHAR* InName, EMaterialParameterAssociation InAssociation = EMaterialParameterAssociation::GlobalParameter, int32 InIndex = INDEX_NONE); // ctor pattern 2 FMaterialParameterInfo(FName InName = FName(), EMaterialParameterAssociation InAssociation = EMaterialParameterAssociation::GlobalParameter, int32 InIndex = INDEX_NONE); // ほかは省略
4.18 まで FName ParameterName
として直接扱っていたパラメーターは 4.19 では FMaterialParameterInfo::Name
に放り込んで扱うようだ。
4.18 まで第一引数を明示的に FName
型で与えていなかった場合には 4.18 から 4.19 へプロジェクトのエンジンアップデートを行う場合にこの絡みを使っている C++ ソースコードがあれば手書きの対応が必要となる。
第三引数については省略すれば INDEX_NONE
がデフォルトパラメーターからセットされるため、さしあたり気にする事はない。
// 4.18 までこんな具合にコードしていた場合は material->GetVectorParameterValue( "my_vector_param", buffer );
// 4.19 では最低限こんな具合で第一引数を明示的に FName 型で与えるようにすればコンパイル可能になる material->GetVectorParameterValue( FName( "my_vector_param" ), buffer );
おまけ1: EMaterialParameterAssociation
UENUM()
enum EMaterialParameterAssociation
{
LayerParameter,
BlendParameter,
GlobalParameter,
};
おまけ2: グローバルパラメーター、レイヤーパラメーター、ブレンドパラメーターとは何か。どうしてこうなった?
UE4 では 4.19 の新機能として "Material Layers" を実装した。
従来は1つ1つのマテリアルの単位で、その中でテクスチャーサンプリングしてパラメーターを合成したりするだけだったが、 この新機能により「複数のマテリアル群からなるマテリアル(="Material Layers")」を直接的にまとめて扱えるようになった。
この新機能に対応するため、従来はマテリアルごとに 4.19 からグローバルパラメーターと明示的に呼び分けられるようになったマテリアル内のシンボル名のほか、
「レイヤーのインデックス何番目の」という情報が必要な "Material Layers" タイプのマテリアルへ従来のマテリアルと統合的に対応するため
単なる FName
だけのパラメーターから FMaterialParameterInfo
への拡張が行われた。
"Material Layers" 参考
UE4/C++: JSON/XML/portable-binary 等の汎用シリアライズライブラリー cereal に UE4 の各種型を非侵入型アダプターで対応させるライブラリー cereal-UE4 を公開しました
cereal-UE4
cereal について
近年モダンな C++er にしばしば使われていると思われる JSON / XML / Binary ( platform native の endian に依存しない portable も可 ) に対応するシリアライザーのライブラリーです。ヘッダーオンリーライブラリーなので UE4 のプロジェクトへ取り込む際にも手間がほぼかかりません。
cereal-UE4 を作った経緯
- UE4 標準の JSON シリアライザーがしんどい。
- FJsonSerializable 高レベル JSON シリアライズAPI は historia - FJsonSerializebleマクロを使ってみる で紹介されているように使用可能な状況ではとても楽、便利だが…
- 標準提供されている実装の範囲で対応している型が非常に限定的。例えば UE4 では頻出する
FVector
やFLinearColor
などのシリアライズも対応できない。 FJsonSerializable
を継承して型の対応を増やす事はできるが、わりと手間がかかる。- 継承の使用を前提とした侵入型シリアライザーなので UE4 の仕組み上
USTRUCT
型に適用できない(致命的にダメその1😅)。 - 仮想関数を持つ
FJsonSerializable
の継承を前提とした設計のため、この方法でシリアライズ対応した型は仮想関数テーブルが必要となり非 POD 型(厳密には非標準レイアウト型)となるため、TArray<FVector>
のように連続でデータメンバー"だけ"がアライメントされ"密"に並んで欲しいデータ構造に適用できない(致命的にダメその2😅)
- 標準提供されている実装の範囲で対応している型が非常に限定的。例えば UE4 では頻出する
- UE4/JSON/DOM 低レベルAPI はスマートポインターと参照が入り乱れた実装が必要となるため、手書きは複雑で疲れるし、コード量も大きくなり保守性も悪くなる。
- FJsonSerializable 高レベル JSON シリアライズAPI は historia - FJsonSerializebleマクロを使ってみる で紹介されているように使用可能な状況ではとても楽、便利だが…
と、言うわけで、 UE4 の JSON シリアライザーは現状わりと残念なので、 UE4 の外の世界では C++er にわりと人気の高い(≃楽に使えてとても便利)な cereal を UE4 に対応する補助的なライブラリーを実装する事にしました。
cereal-UE4 の仕組み
cereal には大きく分けて侵入型と非侵入型の2つの方法で任意のユーザー定義型へのシリアライザー対応を追加できます。そこで、 cereal-UE4 では非侵入型で UE4 で使われる一般的な多くの型へ対応するシリアライザーをたくさん書いて、 cereal
+ cereal-UE4
を UE4 のプロジェクトへ追加すれば簡単に cereal による UE4 型に対応した JSON / XML / Binary のシリアライズが可能なよう実装しました。全ての実装は template
関数なので、実際に使用する型のシリアライザーのみフットプリントに影響します。
もちろん、 cereal-UE4 もヘッダーオンリーライブラリーです。 cereal と併せて簡単に UE4 プロジェクトへ導入できます。
使い方
UE4 のプロジェクトのディレクトリーの適当なところへ Thirdparty
など適当なディレクトリーを用意し、
cereal
cereal-UE4
を GitHub から clone なり zip なりで調達、配置します。
<MyProject> |-Thirdparty | |-cereal | |-cereal-UE4 |- MyProject.uproject |- ...
それから、 MyProject.Build.cs に、
using System.IO;
を冒頭に追加しつつ、
public class MyProject
内の public MyProject( ReadOnlyTargetRules Target )
コンストラクターの中に、
{ var base_path = Path.GetDirectoryName( RulesCompiler.GetFileNameFromType( GetType() ) ); string third_party_path = Path.Combine( base_path, "..", "..", "Thirdparty"); PublicIncludePaths.Add( Path.Combine( third_party_path, "cereal", "include") ); PublicIncludePaths.Add( Path.Combine( third_party_path, "cereal-UE4", "include") ); }
こんな具合で Thirdparty/cereal/include
と Thirdparty/cereal-UE4/include
が UE4 プロジェクトの C++ コードファイルのコンパイルの際にインクルードパスに追加される設定を追加します。
あとは、ただの struct
でも class
でも USTRUCT
でも UCLASS
でも気にせず、シリアライズしたいクラス、例えば以下のような FMySomething
型に対して、
// これはあなたのお好きなように用意する USTRUCT( BlueprintType ) struct FMySomething { GENERATED_BODY() UPROPERTY( BlueprintReadWrite ) FString String; UPROPERTY( BlueprintReadWrite ) FDateTime DateTime; UPROPERTY( BlueprintReadWrite ) FVector Vector; UPROPERTY( BlueprintReadWrite ) TArray< int32 > ArrayI32; };
// お好きなように cereal の非侵入型シリアライザーを追加する。 // 型の定義の直後に .h へ書いてもいいし、シリアライズを特定の .cpp でしか行わないのならその .cpp へ書いてもいい // cereal #include "cereal/cereal.hpp" #include "cereal/archives/json.hpp" // cereal-UE4 #include "cereal-UE4.hxx" // std::stringstream #include <sstream> template < typename A > void serialize( A& a, FMySomething& in ) { a ( cereal::make_nvp( "String", in.String ) , cereal::make_nvp( "DateTime", in.DateTime ) , cereal::make_nvp( "Vector", in.Vector ) , cereal::make_nvp( "ArrayI32", in.ArrayI32 ) ); }
// これはどこか、 BeginPlay と EndPlay とか、お好きなタイミングでシリアライズして構わない。 // もちろん、 FMySomething はローカル変数でなくてもよいし、クラスのメンバーでもなんでもよい。 FMySomething MySomething; MySomething.String = "Hello, こんにちは."; MySomething.DateTime = FDateTime::UtcNow(); MySomething.Vector = FVector::UpVector(); MySomething.ArrayI32 = { 123, -456, 789 }; // JSON へシリアライズ std::stringstream buffer; { cereal::JSONOutputArchive a( buffer ); a( cereal::make_nvp( "MySomething", MySomething ) ); } const auto json = FString( buffer.str().data() ); UE_LOG( LogTemp, Log, TEXT( "%s" ), *json ) // おまけ: FString をファイルへ書き出したければこんな具合 if ( ! FFileHelper::SaveStringToFile ( json , *( FPaths::ProjectDir / TEXT( "MySomething.json" ) ) , FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM ) ) { UE_LOG( LogTemp, Fatal, TEXT( "Save Error" ) ); }
これで次のような MySomething.json
が書き出される
{ "MySomething": { "String": "Hello, こんにちは.", "DateTime": "2018-03-29T17:59:50.595Z", "Vector": { "X": 0.0, "Y": 0.0, "Z": 1.0 }, "ArrayI32": [ 123, -456, 789 ] } }
読み出し(デシリアライズ)したい場合は
FMySomething from_json; std::stringstream buffer; buffer << TCHAR_TO_UTF8( *json ); cereal::JSONInputArchive a( buffer ); a( from_json );
これで from_json
には to_json
と同じ中身が復元される。
おわりに
cereal-UE4 は MIT ライセンスの OSS として GitHub へ公開しました。 UE4/C++er が JSON / XML / Binary のシリアライザーについて、標準実装では不足、あるいは面倒そうな状況に対し、 cereal-UE4 が助けとなれば幸いです。対応する型や、すべての型での実装例はリポジトリーの README.md や example/ を参照して下さい。対応型の不足やバグ修正があれば PR 下さい。
UE4: std::unordered_map だと少し面倒だけど TMap なら簡単にできる Key の書き換え
// てきとーな TMap TMap< FString, int32 > m; m.Add( "hoge", 123 ); m.Add( "fuga", 234 ); m.Add( "piyo", 345 ); // マップの対を逐次参照で列挙しつつ Key を書き換える for ( auto& p : m ) p.Key = p.Key .Replace( TEXT( "g" ), TEXT( "G" ) ) .Replace( TEXT( "o" ), TEXT( "O" ) ) ; // 結果発表・w・ for ( const auto& p : m ) UE_LOG( LogTemp, Log, TEXT( "[%s]=>%d" ), *p.Key, p.Value );
結果:
[2018.03.20-06.14.36:153][ 0]LogTemp: [hOGe]=123 [2018.03.20-06.14.36:153][ 0]LogTemp: [fuGa]=234 [2018.03.20-06.14.36:153][ 0]LogTemp: [piyO]=345
方便として UE4 入門時には TMap<K,V>
を std::unordered_map<K,V>
"的"なものと捉えて扱い始めると既に std 系を習得済みの C++er が UE4/C++ へ入門する際には取っ付き易さがあります。表向きの Key-Value ストアーとしての挙動だけ見ると同様ですが、内部実装は大きく異なり、 TMap<K,V>
では要素の参照をイテレーション中に Key
を簡単に書き換えできて用途によっては便利、という小話。
ちなみに、 std::unorered_map<K,V>
でキーを書き換えたいとなると少し面倒で実行コストも単なる値の書き換えより大きくなるので、そういう用途が必要な場合は std::vector< std::pair<K,V> >
を使えば簡単かもしれない。 TMap<K,V>
の内部実装は実際のところ、std::unordered_map
よりも std::vector< std::pair<K,V> >
に近い。
UE4: バイナリー配布や実行時リンク向けのライブラリーを UE4 プロジェクトへ組み入れる方法を mecab で解説
概要
UE4 のプロジェクトの C++ コードへ UE4 とは特に関係の無い一般のライブラリーをリンクするのは少々面倒な事がある。ヘッダーオンリーなライブラリー、例えば Eigen
や stb
あるいは Boost
の一部をリンクするような場合は困らない。しかし、バイナリー配布や実行時リンク向けのライブラリー、例えば OpenCV
や pcl
などを組み入れたい場合には少々面倒。
この記事では UE4 には組み込まれていない、比較的小さくビルドも簡単で、 UE4 プロジェクトへの組み入れを試すのに手頃な mecab
を例に、具体的な手法を整理する。
Note: mecab
は日本語の形態素解析機能を提供するライブラリーですが、形態素解析については本記事の主題ではないので解説しません。
解説の範囲
mecab
のバイナリーライブラリーのビルドから↑こんなのが UE4 でテストプレイできるまで。
方法
大雑把な手順:
mecab
のソースをダウンロードmecab
をビルド- 組み入れるUE4 プロジェクト を作成(既にあるプロジェクトでも可)
- UE4 プロジェクトへ
mecab
のファイルを配置 - UE4 プロジェクトの
<project-name>.Build.cs
をいじってmecab
を扱えるように細工する
mecab
のビルドまで
ソースの入手:
- http://taku910.github.io/mecab/#download
- mecab-0.996.tar.gz 執筆時点 2018-03-13 における配布版
ビルド:
cd src
- fix: Makefile.msvc.in
X86
->X64
@DIC_VERSION@
->102
@VERSION@
->0.996
- fix: feature_index.cpp
case 't': os_ << (size_t)path
->case 't': os_ << (unsigned int)path
- fix: writer.cpp
case 'L': *os << latice->size()
->case 'L': *os << (unsigned int)latice->size()
nmake -f Makefile.msvc.in
ビルドが完了すると libmecab.dll
などのファイルが生成されています。
Note: Windows 7 64bit に MeCab (和布蕪) と Python のバインディングを導入 (2016/08/18) - Qiita を参考にしました。そのままビルドしたり、ソースではなくバイナリー配布版を入手すると x86 になります。
UE4 プロジェクトへ mecab
を組み入れる
mecab
を組み入れたい UE4 プロジェクトのディレクトリー直下に ThirdParty
ディレクトリーを生成し、次のようにサブディレクトリーを用意しましょう。 <my-project-dir>
直下です。 Sources
や Plugins
の下ではありません。
<my-project-dir> |- ThirdParty |- mecab |- Includes |- Libraries
mecab
のファイル群を配置します。
- Includes:
mecab
のsrc
の.h
ファイル群を全て放り込む - Libraries:
libmecab.lib
とlibmecab.dll
を放り込む
<my-project>.Build.cs
に細工します。
// 1. 冒頭に using を追加しておきます。 using System.IO;
// 2. ctor に public class MyProject : ModuleRules { public MyProject(ReadOnlyTargetRules Target) : base(Target) { // ... 追加する以前からある何かや今回の件とは関係の無い定義など ... // ... // ここから今回の追加部分 var base_path = Path.GetDirectoryName( RulesCompiler.GetFileNameFromType( GetType() ) ); string third_party_path = Path.Combine( base_path, "..", "..", "Thirdparty"); PublicIncludePaths.Add( Path.Combine( third_party_path, "mecab", "Includes") ); switch ( Target.Platform ) { // 他のプラットフォームにも対応したい場合は case を増やします。 // 今回は例として Win64 のみ対応します。 case UnrealTargetPlatform.Win64: PublicLibraryPaths.Add( Path.Combine( third_party_path, "mecab", "Libraries", "Win64" ) ); PublicAdditionalLibraries.Add( "libmecab.lib" ); string mecab_dll_path = Path.Combine( third_party_path, "mecab", "Libraries", "Win64", "libmecab.dll" ); RuntimeDependencies.Add( new RuntimeDependency( mecab_dll_path ) ); string binaries_directory = Path.Combine( base_path,"..","..", "Binaries", "Win64" ); if ( ! Directory.Exists( binaries_directory ) ) System.IO.Directory.CreateDirectory( binaries_directory ); string mecab_dll_destination = System.IO.Path.Combine( binaries_directory, "libmecab.dll" ); CopyFile( mecab_dll_path, mecab_dll_destination ); PublicDelayLoadDLLs.AddRange( new string[] { "libmecab.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 ); } } }
この記事に興味を持って読んでくれている方は C# の経験が豊富でなくともこの程度のコードは読めばわかると思いますので説明は省略します。
Note: Using thirdparty libraries in our UE4 mobile/desktop project – Parallelcube を参考にしました。
これで <my-project> をビルド(コンパイル)すると、 mecab
のバイナリーライブラリーも配置されプロジェクトの C++ コードから使用可能となります。
おまけ: mecab
を使う UE4/C++ コードを書いて Blueprint (UMG) から mecab
する
mecab
を Blueprint で使う UE4/C++ コードはこんな感じ:
// MyUtility.h #pragma once #include "CoreMinimal.h" UCLASS() class UMyUtility : public UBlueprintFunctionLibrary { GENERATED_BODY() public: UFUNCTION( BlueprintCallable, Category="My Utility" ) static FString Experiment( const FString& in ); }
// MyUtility.cpp #include "MyUtility.h" // warning 群だるいので(´・ω:;.:... #ifdef TEXT #undef TEXT #endif #define _APISET_RTLSUPPORT_VER 0 #define _APISET_INTERLOCKED_VER 0 #define _WIN32_WINNT_WINTHRESHOLD 0 #define _APISET_SECURITYBASE_VER 0 #define NTDDI_WIN7SP1 0 #include "mecab.h" FString UMyUtility::Experiment( const FString& in ) { // mecab 準備; (†1) 後述で追記あり static auto tagger = MeCab::createTagger( "" ); if ( ! tagger ) { auto e = MeCab::getLastError(); return FString( "Tagger Error:" ) + FString( e ); } // mecab 的にこれで返ってくる文字列は utf8 auto result = tagger->parse( TCHAR_TO_UTF8( *in ) ); // utf8 -> wchar // note: 今回は <my-project>.Build.cs で Win64 に絞っているので Windows API を使用しています。 const auto size = ::MultiByteToWideChar( CP_UTF8, 0, result, -1, nullptr, 0 ); std::vector< wchar_t > buffer( size + 1 ); ::MultiByteToWideChar( CP_UTF8, 0, result, -1, buffer.data(), buffer.size() ); return FString( buffer.data() ); }
UE4Editor で適当な UMG を作って:
Note: Canvas に Image, Text, Text Box (Multi-Line) です。
input
の On Text Committed
に MyUtility::Experiment
した結果を result
へ出力する Blueprint を書いて:
テストプレイ:
めでたし、めでたし😃
追記1: (†1) mecab
の MeCab::createTagger( "" );
の引数について
ここで渡す "mecab のコマンドライン引数" をこの例のように空文字列にした場合は、 mecab
をビルド時にカスタマイズしていない場合は "C:\Program Files (x86)\MeCab\etc\mecabrc" から mecab
の設定を読み込み、その設定から辞書を読み込む動作となります。
mecabrc
や辞書を別の場所に用意したい場合には、コマンドライン引数の解説 https://taku910.github.io/mecab/mecab.html を参考に次のように引数として mecab
のコマンドライン引数を渡します:
// mecab のコマンドライン引数をまとめて1つの文字列で渡す static auto tagger = MeCab::createTagger( "-r C:\\mecab\\etc\\mecabrc" );
なお、このように mecab
のコマンドライン引数を渡したい場合に、パス文字列に空白文字が入っている場合などは、 mecab
のコマンドライン引数パーサー ( src/param.cpp
の open
関数を参照 ) があまり器用にパースしてくれずに単一の文字列で与えるパターンではエスケープしようが失敗します。そのような場合には "いわゆる argc, argv パターン" でコマンドライン引数を分離済みの形態で与えれば期待動作します。
// mecabrc のパスが "me cab" のように空白文字を含んでいるパターン std::array< char*, 3 > arguments = { "", "-r", "C:\\me cab\\etc\\mecabrc" }; static auto tagger = MeCab::createTagger( arguments.size(), arguments.data() );
追記2: mecabrc の必要最低限の記述と辞書の配置方法
mecabrc: 適当な場所にテキストファイルを作ればよい
; セミコロン始まりがコメント行 ; 最低限、辞書のディレクトリーのパスを記述しておけばよい。 ; $(rcpath) 変数はこの mecabrc のパスに置き換えて処理される。 dicdir = $(rcpath)\..\dic\ipadic
辞書は公式のダウンロード http://taku910.github.io/mecab/#download から「IPA 辞書」を回収してきて mecabrc で定義したディレクトリーなり、 MeCab::createTagger
にコマンドライン引数で直接渡すなりするディレクトリーへ放り込めばよい。
このごちゃごちゃたくさんファイルが入っているディレクトリーを dicdir へ配置する。一般的な配置パスは mecabrc と併せて次のよう:
<somewhere> |- mecab |- etc | |- mecabrc |- dic |-ipadic | |- ... .csv ほか配布の辞書のディレクトリーの中身のたくさんのファイル |-<something the other dic 1> |-<something the other dic 2> |-<something the other dic 3> |- ...
もし例としてではなく、実際に mecab を UE4 プロジェクトで使用したい場合には、 mecabrc や dic は FPaths::ProjectDir()
から適当な相対パスを設定し、配布パッケージに含めればよいでしょう。 ThirdParty ライブラリーを組み入れる例として試すだけであればどこか適当な場所に配置してテストプレイできればよいでしょう。
// mecab は Windows 環境では / 区切りのパスは扱えないのでひと手間追加する // (微妙に混ざっていても扱える事はあるけれど) cosnt auto mecabrc = FPaths::ConvertRelativePathToFull( FPaths::ProjectDir() / TEXT( "mecab/etc/mecabrc" ) ) #ifdef _WIN32 .Replace( TEXT( "/" ), TEXT( "\\" ) ) #endif ; std::array< char*, 3 > arguments = { "", "-r", TCHAR_TO_UTF8( *mecabrc ) }; static auto tagger = MeCab::createTagger( arguments.size(), arguments.data() );
UE4: Blueprint 向けに WorldContextObject が必要な機能を提供する際に暗黙的に WorldContextObject を扱わせられるように C++ コードで対応する方法
概要
状況の例として、 Blueprint へ特定のアクターを引っ張り出させる次のような実装を提供したいとする。
設計上必ず1つ AMySpecialActor が配置されていて、
// 定義はどうあれ、どこかに定義され1つだけは常に配置される AMySpecialSomething アクター UCLASS() class AMySpecialSomething: public AActor { GENERATED_BODY() };
それを Blueprint からさくっと引っ張れる機能提供を C++ コードで実装したい、と:
動作しない、少し惜しい実装
UCLASS() class UMySpecialUtility : public UBlueprintFunctionLibrary { GENERATED_BODY() public: UFUNCTION( BlueprintCallable, Category="My Special Utility", BlueprintPure ) static AMySpecialSomething* GetSpecialSomething() { // note: ここで nullptr が取得され、この実装は意図した動作をしない。 const auto world = GEngine->GetWorld(); if ( ! IsValid( world ) ) return nullptr; TArray< AActor* > results; UGameplayStatics::GetAllActorsOfClass( world, AMySpecialSomething::StaticClass(), results ); if ( ! results.IsValidIndex( 0 ) ) return nullptr; return Cast< AMySpecialSomething >( results.GetData()[ 0 ] ); } };
コメントに書いたように、適当なアクターのメンバー関数の中で実装する場合と違い、 UBlueprintFunctionLibrary に static メンバー関数として実装した場合、この実装では期待動作しない。
動作する、 Blueprint からも扱いやすい実装
UFUNCTION( BlueprintCallable, Category="My Special Utility", BlueprintPure, meta = ( WorldContext = WorldContextObject) ) static AMySpecialSomething* GetSpecialSomething( const UObject* WorldContextObject ) { const auto world = GEngine->GetWorldFromContextObject( world_context_object, EGetWorldErrorMode::LogAndReturnNull ); if ( ! IsValid( world ) ) return nullptr; TArray< AActor* > results; UGameplayStatics::GetAllActorsOfClass( world, AMySpecialSomething::StaticClass(), results ); if ( ! results.IsValidIndex( 0 ) ) return nullptr; return Cast< AMySpecialSomething >( results.GetData()[ 0 ] ); } };
外部から WorldContextObject
の供給を受ければ問題無く動作する。ついで、その際に Blueprint 向けの UFUNCTION
の定義に WorldContext
を与えておくと、 Blueprint でこの関数を使用する際に、暗黙的に WorldContextObject
が適切に与えられ、かつ Blueprint では余計なコネクターなども綺麗に省略される状態にできる。
めでたしめでたし。
参考
UE4: Blueprint 向けに提供する C++ 関数を pure に定義するメモ
1. AActor
などの派生型でメンバー関数を pure にしたい場合
UFUNCTION
でBlueprintCallable
フラグを付けたメンバー関数をconst
定義すれば自動的に blueprint でも pure になる。
2. UBlueprintFunctionLibrary
派生型の static メンバー関数を pure にしたい場合
UFUNCTION
でBlueprintCallable
フラグに加えてBlueprintPure
フラグも付ける。
UCLASS() class UMyUtility : public UBlueprintFunctionLibrary { GENERATED_BODY() public: UFUNCTION( BlueprintCallable, Category="My Utility", BlueprintPure ) static bool AnyOf( const TArray< bool >& in ); }; bool UMyUtility::AnyOf( const TArray< bool >& in ) { for ( const auto v : in ) if ( v ) return true; return false; }
参考
だそく
Blueprint では const
メンバー関数は自動的に pure で扱ってくれる。 static
メンバー関数が非 pure で扱われる理由は無いのでわざわざ手打ちで BlueprintPure
フラグを付けずとも static
メンバー関数も自動的に pure で扱ってくれたらいいのに、と思った。理由は特に無くて気の利いた実装をまだ誰もそこに適用してPRする余力と関心が無いから、というだけな気もする。