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

USAGI.NETWORKのなかのひとのブログ。主にC++。

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 に含まれていない点、加えて実装クラスが提供されるモジュールも独立しているためモジュールの追加も必要となる点に注意。

  1. FGenericPlatformMiscFPlatformApplicationMisc に置き換える。
  2. HAL/PlatformApplicationMisc.h を include 追加。
  3. <project>.Build.csApplicationCore モジュールを追加。

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;
  1. 第一引数が変更: FName ParameterName -> const FMaterialParameterInfo& ParameterInfo
  2. 第三引数が追加: 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

GitHub - usagi/cereal-UE4: cereal ( C++ serialization library ) adapter for UE4 ( Unreal Engine ) types

cereal について

近年モダンな C++er にしばしば使われていると思われる JSON / XML / Binary ( platform native の endian に依存しない portable も可 ) に対応するシリアライザーのライブラリーです。ヘッダーオンリーライブラリーなので UE4 のプロジェクトへ取り込む際にも手間がほぼかかりません。

cereal-UE4 を作った経緯

  • UE4 標準の JSONリアライザーがしんどい。
    • FJsonSerializable 高レベル JSON シリアライズAPIhistoria - FJsonSerializebleマクロを使ってみる で紹介されているように使用可能な状況ではとても楽、便利だが…
      • 標準提供されている実装の範囲で対応している型が非常に限定的。例えば UE4 では頻出する FVectorFLinearColor などのシリアライズも対応できない。
      • FJsonSerializable を継承して型の対応を増やす事はできるが、わりと手間がかかる。
      • 継承の使用を前提とした侵入型シリアライザーなので UE4 の仕組み上 USTRUCT 型に適用できない(致命的にダメその1😅)。
      • 仮想関数を持つ FJsonSerializable の継承を前提とした設計のため、この方法でシリアライズ対応した型は仮想関数テーブルが必要となり非 POD 型(厳密には非標準レイアウト型)となるため、 TArray<FVector> のように連続でデータメンバー"だけ"がアライメントされ"密"に並んで欲しいデータ構造に適用できない(致命的にダメその2😅)
    • UE4/JSON/DOM 低レベルAPI はスマートポインターと参照が入り乱れた実装が必要となるため、手書きは複雑で疲れるし、コード量も大きくなり保守性も悪くなる。

と、言うわけで、 UE4JSONリアライザーは現状わりと残念なので、 UE4 の外の世界では C++er にわりと人気の高い(≃楽に使えてとても便利)な cereal を UE4 に対応する補助的なライブラリーを実装する事にしました。

cereal-UE4 の仕組み

cereal には大きく分けて侵入型と非侵入型の2つの方法で任意のユーザー定義型へのシリアライザー対応を追加できます。そこで、 cereal-UE4 では非侵入型で UE4 で使われる一般的な多くの型へ対応するシリアライザーをたくさん書いて、 cereal + cereal-UE4UE4 のプロジェクトへ追加すれば簡単に cereal による UE4 型に対応した JSON / XML / Binary のシリアライズが可能なよう実装しました。全ての実装は template 関数なので、実際に使用する型のシリアライザーのみフットプリントに影響します。

もちろん、 cereal-UE4 もヘッダーオンリーライブラリーです。 cereal と併せて簡単に UE4 プロジェクトへ導入できます。

使い方

UE4 のプロジェクトのディレクトリーの適当なところへ Thirdparty など適当なディレクトリーを用意し、

  1. cereal
  2. 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/includeThirdparty/cereal-UE4/includeUE4 プロジェクトの 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 とは特に関係の無い一般のライブラリーをリンクするのは少々面倒な事がある。ヘッダーオンリーなライブラリー、例えば Eigenstb あるいは Boost の一部をリンクするような場合は困らない。しかし、バイナリー配布や実行時リンク向けのライブラリー、例えば OpenCVpcl などを組み入れたい場合には少々面倒。

この記事では UE4 には組み込まれていない、比較的小さくビルドも簡単で、 UE4 プロジェクトへの組み入れを試すのに手頃な mecab を例に、具体的な手法を整理する。

Note: mecab は日本語の形態素解析機能を提供するライブラリーですが、形態素解析については本記事の主題ではないので解説しません。

解説の範囲

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

mecab のバイナリーライブラリーのビルドから↑こんなのが UE4 でテストプレイできるまで。

方法

大雑把な手順:

  1. mecab のソースをダウンロード
  2. mecab をビルド
  3. 組み入れるUE4 プロジェクト を作成(既にあるプロジェクトでも可)
  4. UE4 プロジェクトへ mecab のファイルを配置
  5. UE4 プロジェクトの <project-name>.Build.cs をいじって mecab を扱えるように細工する

mecab のビルドまで

ソースの入手:

ビルド:

  1. cd src
  2. fix: Makefile.msvc.in
    1. X86 -> X64
    2. @DIC_VERSION@ -> 102
    3. @VERSION@ -> 0.996
  3. fix: feature_index.cpp
    1. case 't': os_ << (size_t)path -> case 't': os_ << (unsigned int)path
  4. fix: writer.cpp
    1. case 'L': *os << latice->size() -> case 'L': *os << (unsigned int)latice->size()
  5. 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> 直下です。 SourcesPlugins の下ではありません。

<my-project-dir>
  |- ThirdParty
    |- mecab
      |- Includes
      |- Libraries

mecab のファイル群を配置します。

  • Includes: mecabsrc.h ファイル群を全て放り込む
  • Libraries: libmecab.liblibmecab.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 を作って:

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

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

Note: Canvas に Image, Text, Text Box (Multi-Line) です。

inputOn Text CommittedMyUtility::Experiment した結果を result へ出力する Blueprint を書いて:

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

テストプレイ:

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

めでたし、めでたし😃

追記1: (†1) mecabMeCab::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.cppopen 関数を参照 ) があまり器用にパースしてくれずに単一の文字列で与えるパターンではエスケープしようが失敗します。そのような場合には "いわゆる 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コマンドライン引数で直接渡すなりするディレクトリーへ放り込めばよい。

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

このごちゃごちゃたくさんファイルが入っているディレクトリーを dicdir へ配置する。一般的な配置パスは mecabrc と併せて次のよう:

<somewhere>
  |- mecab
      |- etc
      |  |- mecabrc
      |- dic
          |-ipadic
          |   |- ... .csv ほか配布の辞書のディレクトリーの中身のたくさんのファイル
          |-<something the other dic 1>
          |-<something the other dic 2>
          |-<something the other dic 3>
          |- ...

もし例としてではなく、実際に mecabUE4 プロジェクトで使用したい場合には、 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() };

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

それを 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 では余計なコネクターなども綺麗に省略される状態にできる。

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

めでたしめでたし。

参考

  1. Metadata Specifiers | Unreal Engine
  2. GEngine - GetWorld() return NULL ?? - UE4 AnswerHub

UE4: Blueprint 向けに提供する C++ 関数を pure に定義するメモ

1. AActor などの派生型でメンバー関数を pure にしたい場合

  • UFUNCTIONBlueprintCallable フラグを付けたメンバー関数を const 定義すれば自動的に blueprint でも pure になる。

2. UBlueprintFunctionLibrary 派生型の static メンバー関数を pure にしたい場合

  • UFUNCTIONBlueprintCallable フラグに加えて 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;
}

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

参考

だそく

Blueprint では const メンバー関数は自動的に pure で扱ってくれる。 static メンバー関数が非 pure で扱われる理由は無いのでわざわざ手打ちで BlueprintPure フラグを付けずとも static メンバー関数も自動的に pure で扱ってくれたらいいのに、と思った。理由は特に無くて気の利いた実装をまだ誰もそこに適用してPRする余力と関心が無いから、というだけな気もする。