TextMeshProの使い方【後編】

こんにちは、やまだたいし( やまだ たいし (@OrotiYamatano) | Twitter )です。
TextMeshProの使い方後編です!

前編はこちら orotiyamatano.hatenablog.com


目次


設定


早速前回の続きからです。

Atlas & Material についてです。
こちらは生成されたFontアトラスとそのマテリアルの参照を表しています。
変更は出来ません。

Font Weights は
太字と斜体がフォントの外観をどのように変更するかを制御できるものです。
文字のスタイルによって表示するフォントアセットを指定することが出来るようです。
設定すれば、italic(斜体)のときはAのフォント、太字(Bold)の時はBのフォントみたいな感じで使い分けができそう。
また下の方の項目でテキストの太さと間隔を調整することもできるようです。
今回は触らないので詳細は割愛。

Fallback Font Assets は
フォントを複数のアセットに分散し管理できる機能のようです。
今回は常用漢字と英数字とひらがなだけなので8192*8192でギリギリ納まるので問題ないですが、
納まりきらない時に分散出来るらしいです。
また、1つのフォントにすべての文字が含まれていない場合に別のフォントを読み込ませることで代替表示させるために用いるようです。
別のTMP_FontAssetを指定するだけのようです。

Character Table は
その名の通りフォントアセットに含まれる文字をUnicode値で検索するために使用される辞書のようです。
打ち込んだ文字と表示される文字が別(例えば「あ」と打ち込んだのに「い」と表示される)の場合に値を編集してなおすことが出来るようです。
下記のGlyph TableのIDと紐付いているようです。

Glyph Table は
登録した文字とその設定が一覧表示されます。
特定の文字が表示されない、文字が崩れているという場合にココで検索するとどんな状態で文字が保存されているか調べることが出来ると思います。
値が変更することが出来るので文字の位置が合わないといったことがある場合ココを編集すれば良さそうです。

Glyph Adjustment Table は
特定の文字2つを選んで文字間を調整するものっぽい?
その組み合わせの場合のみ文字間が調整されるようです。
上手く動かないという話もあるようなので、あまり機能としては信用しないほうが良いかも?

f:id:OrotiYamatano:20201121014121p:plain

いざベイク!


各ベイク設定について


では、テクスチャのアトラス化をするためにベイクをやっていこうと思います。
フォントアセットのUpdate Atlas Textureをクリックします。
f:id:OrotiYamatano:20201121022141p:plain

すると、ウィンドウで開くと思います。
前半で設定した値と違っても反映されると思うので大丈夫だと思いますが、怖い人は合わせて置くと良いでしょう。
因みに私の設定はこんな感じにしました。
f:id:OrotiYamatano:20201121022623p:plain

Sampling Point Size は大元となるフォントのポイントの大きさですが、コレはベイクする速度を考えてAuto Sizingにしておくことにしました。
Paddingはベイクされるときの文字間です。小さすぎると文字を表示する際に隣の文字が表示されてしまうことがあるのでデフォルトの9のままを適用しました。
Packing Method は ベイクするスピードで遅いほど精度が高くなるのですが、特にこだわりは無いのでFastにしておきます。
Atlas Resolutionはアトラステクスチャのサイズです。今回は常用漢字ひらがな・カタカナ、英数字、ちょっとした記号なのでいっぱい入る8192*8192のサイズにしておきます。
Character Setには焼き込む文字の指定方法を指定します。後で変更するので詳細は割愛。
Select Font Asset は実際に焼き込む設定のアセットです。特に変更することはありません。
Character SequenceはCharacter Setに指定する方法によって入れる設定が変わります。詳細は割愛。
Render Modeはベイクするときの設定です。これは前編で解説しているので割愛します。SDFAAで今回は焼き込みます。
Get Kerning Pairs は カーニング情報をフォントデータからコピーするか?です。フォントによってはそもそも含まれない場合もあるのでfalseでいいです。
あるやつはtrueの方がキレイに表示できると思いますがソコまで気にする必要も無いかと……。

ベイクする文字の指定


ベイクする文字の指定方法はいくつかあります。

f:id:OrotiYamatano:20201121023730p:plain

  1. ASCII→ASCIIコードでの指定です。ASCIIコード表にある全ての文字がベイクされます。
  2. Extended ASCII→拡張ASCIIコード指定です。拡張ASCIIのすべての文字がベイクされます。
  3. ASCII Lowercase→ ASCII Lowercaseコード指定です。
  4. ASCII Uppercase→ ASCII Uppercaseコード指定です。
  5. Numbers+Symbols→番号記号またはナンバーサインと呼ばれる文字列がベイクされます。
  6. Custom Range→Decimal(10進数)で指定された文字(文字コード)をベイクします。今回これを使います。
  7. Unicode Range(Hex)→ Hex(16進数)Unicodeで指定された文字コードをベイクします。
  8. Custom Characters→指定した文字がベイクされます。(文字をそのまま入力する)
  9. Character from File→文字が入力されたテキストを指定します。そのテキストがベイクされます。(多分)

ASCII系↓ theasciicode.com.ar

Numbers+Symbols↓ coolsymbol.com


今回ベイクするのは常用漢字などです。
↓ここの文字列を使わせていただくことにしました。
gist.github.com

準備が出来たらGenerate Font Atlasをクリックします。
f:id:OrotiYamatano:20201121025834p:plain

するとベイクが始まります。
f:id:OrotiYamatano:20201121025901p:plain

始まらない場合、エラーが表示されるので、そのエラーを解決してベイクしましょう。
性能の低いPCでは固まってしまうこともあるので触らずに気長に待ちましょう。

エラーがなければすべてベイクされるはずです。
f:id:OrotiYamatano:20201121030043p:plain

Saveをクリックして保存しておきましょう。

ここまで来て言うのは何なんですが、画像を見ると included 2434/2635 とすべてベイク出来ていないようです。
いらない文字が入ってしまっていただけだとは思いますが気になる方は
↓こちらのほうを利用してCustom Charactersでベイクし直したほうが良いかも知れません。
gist.github.com


ベイクされたことでアセットのマテリアルとテクスチャが保存されたはずです。
びっしりと白い文字が割り当てられてるか確認してみましょう。
f:id:OrotiYamatano:20201121030315p:plain f:id:OrotiYamatano:20201121030329p:plain

これでもう使えます。
マテリアルとテクスチャの設定が出来るのですが、要望があれば別途記事を書きます。
ココに関しては他の方も記事を書かれているので私が書く必要はないと思いますが……。

TextMeshProの表示


TextMeshProの表示は普通のテキストとほとんど似ています。
解説する必要もほとんど無いと思うのでUIでの表示のみ解説します。

挿入したいところに右クリック> UI>Text -TxteMeshProを選択
f:id:OrotiYamatano:20201121030918p:plain

生成されたTxteMeshProのゲームオブジェクトのTxteMeshPro - Textコンポーネントを開き、MainSettings> Font Assetを生成したFont Assetに変更します。
f:id:OrotiYamatano:20201121031054p:plain

すると表示されるはずです。
後はTextMeshProのコンポーネントの値をいじって楽しんでみてください。
f:id:OrotiYamatano:20201121032021p:plain

縁取りの方法などは他の方の書かれた記事にも記載があるので、そちらの方が分かりやすいと思います。

アセットを更新しない限り、もうフォントは必要ないのでプロジェクトから消してしまっても構わないです。
注意事項としてはGitでこのアセットを管理するときにバイナリで管理されるため読み込みに失敗して、テクスチャアトラスの参照が剥がれてしまうことがあるようです。
あまりにも高頻度で参照が剥がれてしまうようでしたら今回解説したFallback Font Assets機能を使って、2048*2048のテクスチャ複数枚で管理するなどをおすすめします。

一旦これで以上になります。
閲覧ありがとうございました。

まとめ


以上でTextMeshProの使い方を終わりにします。
間違っていたら連絡ください手直しいたします。
今回はベイクするまでの設定を中心に教えましたが、TextMeshProはベイクする作業より色々装飾をつける作業のほうが楽しいので、ここで終わりにせず色々いじってみて欲しいです。
私としてはベイクについて中心的に述べてある記事が少ないことが気がかりだったために、この記事を書きました、装飾の仕方などは別の方の記事を参考にしていただければと思います。
(それはそれとして要望があれば書きます。Twitterなりでメンションくれれば即反応すると思います)
これからTextMeshProを始める人の手助けになれば幸いです。
以上、やまだたいし( やまだ たいし (@OrotiYamatano) | Twitter )でした。

TextMeshProの使い方【前編】

こんにちは、やまだたいし( やまだ たいし (@OrotiYamatano) | Twitter )です。
以前、TextMeshProをStatic SDFであればフォントを使わなくて済むのでFonts66コンプリートパックを使うことができるという記事を書かせていただきました。

orotiyamatano.hatenablog.com

しかし、結構散見されるのは、「Static SDFの使い方が分からない!」、「TextMeshProの設定が分からない」という意見です。
確かにStatic SDFのやり方は一見分かりづらい&忘れがちのため、今回調べてメモをしておこうと思ったためこの記事を書くことにしました。

目次


TextMeshProとは?


通称TMPとも略されるTextMeshPro。
そもそもコレが何なのかというのを解説させてもらうと、
Textが使えるアセットでUnity 2018.2からUnity公式で使えるようになったアセットです。
2018.2以降であればデフォルトでUnityに組み込まれています。

テキスト表示であればコレまでも使えていたではないか!と思われる方もいると思いますが、
Text Mesh Pro の方が優秀だと断言しておきます。

何が嬉しいのか


何が嬉しいのか1つめキレイ。

普通のUI Textだとテキストがぼやけるデメリットがあります。
Text Mesh ProはSDF(Signed Distance Field)(日本語訳すると符号付き距離函数とかいうやつ)を使う。
「距離情報によるテクスチャ画像のベクトル化」を行いキレイにフォントを表示するのだ。
要するにフォントデータをテクスチャに焼き込んでおいてロジック側でにじみが無いように拡大させてあげることができるのが、このSDF形式という物らしいです。
だからキレイに出力されます。

じゃあ、なぜUI Textがぼやけるのかというと、
ココに関しては設定などによっては、ぼやけなくも出来るのだが、基本的にフォントはキャッシュ(一部情報として保存)されており、
テクスチャフォントアトラス(複数の画像を1つの画像データとして保存したデータのこと)化されソレを表示している。
そのためスケールを変えるなどを行うと元のテクスチャの大きさは変わらないため、ボヤケてしまうのです。

何が嬉しいのか2つめ容量が優しい。
先程もいったようにテクスチャフォントアトラスを作る必要があるのでUI Textはフォントをバイナリ(ココでは最終的なアプリの内部情報のことを指す)に含める必要があります。

今やフォントといえばベクターフォントが多いですが、このベクターフォントはドット情報を保存するのではなく、
このベクターフォントは線の長さや向きなどを記憶させておいて、どんなにフォントを大きくしても滲まないようにしてあげるものです。

しかし、いかんせんこれは容量が多いです。
また、フォントサイズを異なる大きさで保存する場合、その大きさごとにフォントアトラスが生成されます。

(↓こんな感じ)
f:id:OrotiYamatano:20201115054553p:plain

TextMeshProはテクスチャを焼いて(焼く→生成すること。IT用語。語源は英語のBake.)おいてから、SDFで処理するため、フォントデータをバイナリに組み込む必要がアリません。
容量的に優しいですし、フォント規約にはバイナリにフォントデータを組み込んでは行けないというものもあるため、それを回避することが出来ます。

ちなみに焼き込んだデータはフォントサイズごとに用意する必要がないのでさほど重くないです。

(↓こんな感じ)
f:id:OrotiYamatano:20201115054822p:plain

何が嬉しいのか3つめ色々装飾が出来るのに軽い。

UI Textでは出来ることが限られています。
アウトラインくらいならつけることが出来ますが、アウトラインをつける処理の特性上、Text Mesh Proの方が軽いです。

ざっと述べるとこんな感じです。
単に表示させるだけならUI Textでも十分ですが、細かい動きをつけたり、見た目をよくしようとすると断然Text Mesh Proにしておいたほうが良いです。

TextMeshProの利用方法


初期設定


TMP(TextMeshPro。以下TMP)の実行方法。

まずはフォントを使えるようにしてあげないといけないのでツールバー>Component>Mesh>TextMeshPro - Textを選択します。
f:id:OrotiYamatano:20201115055824p:plain
すると、TMP Importerというウィンドウが表示されるのでImport TMP Essentialsをクリックします。
f:id:OrotiYamatano:20201115060012p:plain

するとTMPのシェーダーなどが一通りインポートされ準備完了です。

フォント


上記の設定で使うことも出来るのですが、
先程もいったように一旦テクスチャを生成しなければいけません。
またデフォルト設定でいくつか既に出来上がっているのですが、英語文字しか含まれていません。
まぁ、生成しなくても日本語文字は動的に生成されるのですが、動的にテクスチャアトラスを生成する必要があるため、
フォントデータをバイナリに組み込む必要があります。TMPの利点を最大限に活かせません。もったいないですね。

それに貴方が使いたいであろうデフォルト以外のフォントはまだ使えません。
ココからは新しくフォントを設定する方法についてです。

まず、使用したいフォントをドロック&ドロップでUnityに取り込みます。(バイナリデータに含めたくないので後で消します)
(フォント名に日本語が含まれる場合は上手く使えないことがあるようなので、フォント名を日本語から英字に変えておいた方が良いかも?)
(ちなみに私が今回使うのは株式会社ネットユーコムさんのAFSまるご風書体)

f:id:OrotiYamatano:20201117022328p:plain

右クリックをして
Create > TextMeshPro > FontAssetを指定
f:id:OrotiYamatano:20201117022726p:plain

すると、テクスチャアトラスの設定されてないSDFアセットが生成されます。

f:id:OrotiYamatano:20201117022908p:plain

生成されたSDFアセットを選択し、Inspecter Windowの設定を変更します。

何が設定できるのか項目を見ていきましょう。
まずはFace Infoから。
f:id:OrotiYamatano:20201117024115p:plain

Face Infoにはフォント内に設定されている基本情報が格納されます。
基本的に値を変えた場合、最悪文字が崩れる場合があるため変更はしないほうが良いと思います。

各項目について解説しようと思って連番をつけましたが、
フォントをTextMeshProの機能でアトラス化するときに参照する情報みたいで変更すると上手く切り取られなくなるようです。
購入フォントでは設定情報が誤っているということは、ほとんど無いと思われるので、変えないほうが良いと思われます。
(切り抜くために↓こういう情報が入ってるらしい)
f:id:OrotiYamatano:20201117025558p:plain

次はGeneration Settingについてです。
f:id:OrotiYamatano:20201117025756p:plain

(多分)描画する上で参照情報です。

  1. SourceFontFile →フォントの参照データです。ない場合はエラーになります。
  2. Atlas Population Mode → 出力設定です。Dynamicになっている場合、アトラス化済み以外のデータは随時動的に生成されます。今回はアプリにフォントデータを含めたくないのでStaticにします。(Bake済みデータしか表示されなくなるので注意)
  3. Atlas Render Mode → アトラス画像を生成するときのレンダリングロジックの選択です。選択する物によって生成スピード、文字の綺麗さなどが変わります。
    英語のネット記事を読む限りAtlas Render ModeはSDFAAが高速で安定しているようです。
    1. SMOOTH_HINTED→スムージング表現(フォントヒンディング)グリフのアウトラインの8bitまたはアンチエイリアス処理された画像から、ヒントを使用してグリフのビットマップ表現をレンダリング
    2. SMOOTH→スムージング表現 グリフのアウトラインの8ビットまたはアンチエイリアス処理された画像から、ヒントなしでグリフのビットマップ表現をレンダリング
    3. RASTER_HINTED→ラスター表現(フォントヒンディング) グリフのアウトラインのバイナリ(1ビットモノクロ)画像からグリフのビットマップ表現をヒント付きでレンダリング
    4. RASTER→ラスター表現 グリフのアウトラインのバイナリ(1ビットモノクロ)画像から、ヒントなしでグリフのビットマップ表現をレンダリング
    5. SDF→ SDFシェーダを使用するために必要なテクスチャを作成する。 グリフのアウトラインのバイナリ(1ビットモノクロ)画像から、ヒントなしでグリフの符号付き距離フィールド(SDF)表現をレンダリング。 1.SDF8→SDFのグリフのサンプリングが8倍スケールアップ版 1.SDF16→SDFのグリフのサンプリングが16倍スケールアップ版 1.SDF32→SDFのグリフのサンプリングが32倍スケールアップ版 1.SDFAA_HINTED→グリフの8ビットまたはアンチエイリアス処理された画像からグリフの符号付き距離フィールド(SDF)表現を、ヒント付きでレンダリングします。このレンダリングモードは非常に高速だが、精度が少し低下します。
    6. SDFAA → グリフの8bitまたはアンチエイリアス処理された画像からグリフの符号付き距離フィールド(SDF)表現を、ヒントなしでレンダリングします。レンダリングモードは非常に高速だが、精度が少し低下 SDFは数が大きいほど正確になります。
  4. Sampling Point Size → どうやらサンプリング元になるデータのフォントサイズを指定するようです。大きければ大きいほどキレイ?まぁ焼くスピードも遅くなると思うのでデフォにしておくことにします。私の設定は90になっているけど90は少し大きいかも?
  5. Padding → 文字間ですね。
  6. Atlas Width → アトラス画像の横幅です。今回は常用漢字と英数字を入れたいので1024では収まらないと思うので、もっと大きい数値に8192にしておきます。
  7. Atlas Height → アトラス画像の縦幅です。今回は常用漢字と英数字を入れたいので1024では収まらないと思うので、もっと大きい数値に8192にしておきます。

前半はとりあえずここまで


意外と長くなったので、前編、後編で区切らせていただこうと思います。

一旦ココまでで失礼します!
次回は実際に書き出しと描画をします。後、アセットを管理する上で気をつけることなどを書けたら良いなと思います。
何か誤っているなどアリましたら教えて下さい!
↓続き
orotiyamatano.hatenablog.com

もうソレ何番煎じ?Unity、UEの比較

こんにちは、やまだたいし( やまだ たいし (@OrotiYamatano) | Twitter )です。
これまでUnity、UEの比較記事が大量がありました。
しかし、どちらも(平等ではなく)公平に語られてないような気がします。
なので、私が出来るだけ公平に答えたいと思います。
本当はTwitterに載せようと思ったのですが、思いの外、長くなったのでブログにしました。
思いついた順で書いてるので読みにくいかも知れませんが、ご承知ください。

目次


一番最初に言いたいこと


意見が私が偏ってないと言い張る理由は、業務で半年以上UEを使っていること、
Unityもゲームをリリースする程度には触ってきたことです。
強いて言うなら、UEよりUnityの方が触っている期間が長いので肩を持ってしまっています。
(後、Unityゲーム開発者ギルドに入ってしまっているので……)

しかしながら、UEも半年は触ってきたので色々述べられるところがあると思います。

後、これだけは言っておこうと思います。

そもそも、それぞれ利点欠点があるので比較するものではない!

では、早速つらつらと比較していこうと思います。

本文


まずUnity、マルチスレッド、イベントドリブンの考え方はUniRxなど外部ライブラリの力を使わないと使えないので、そういった実装がやりづらいのが難点ですね。
UnityではモノビヘイビアとC#の力で色々制御されているのでユーザー(エンジンの使用者。以下ユーザー)が色々違反したように書けないのが良いです。
(DOTS?しらんな……)

UEは自由に書けすぎてしまうのが難点ですね、イベントドリブン、マルチスレッドは出来ますが、全てをユーザー側で厳密に管理しないといけないです。
設計思考がチームごとに厳密にないプロジェクトも多いと思うのでカオスなコードが出来上がりやすいです。

UEは命名規則もあることから、ルールでなんとかしようという取り組みがあるのが伺えます。


グラフィックはUEに軍配。様々なツールとのやりとりが出来たり、確認がしやすいのが良いですね。
Unityも頑張ってきてるのでココはなんとも言えないです。
Unityは前まではパイプラインまわりがいじれなかったのにイジれるようになったりもしたので良いですね。
後はポストプロセスをもう少し頑張って。


システムについて
Unityはユーザー側で色々考えて実装したり、ライブラリを買ったりして実装することが多いですが、
UEはデバッグコマンドやベースシステムが組み込まれてるのが良いですね。
Unityだとデバッグコマンドなどは自前実装するか、ライブラリを買うしかありません。
UEは日本語資料を増やせとは言わないので、総量として英語だけでもドキュメントを頑張って欲しいです。

Unityのシステムは基礎しか無いので自分で考える必要がありますが、
逆にその基礎は色んな会社で触られるからか、ユーザーのWEB記事が充実してます。
UEはこんなの誰が使うんだ?という機能が多すぎます。
使えない機能はなくして……。


Unityはある程度のPCスペックでエンジンが動くのが良いです。
どの環境でもほぼ同じように動く!
UEは思い通りに動かないことが多いのでイライラします。
しかしながら、UEはエンジンからサワれるので色々カスタマイズしやすいのは利点ですね。
Unityのように色々組み込み方を覚えずに済む。


コードビルドはUnityが軍配ですかね。
IL2CPPだとUnityも時間がかかりますが、UEは通常のコードビルドも結構かかってしまうのが難点です。
ただ、UEはノードベースのBlueprintはビルドは爆速で確認のイテレーションが回しやすいので
BlueprintだけのプロトタイプならUEが早く作れて良いかも?
UEのパッケージング周りは私は、あまり触ったことがないのでソコに関しては割愛。


2D処理はUnityが軍配ですね。
3DはUE。
UEはアニメーション周りが充実していて使いやすいです。
Unityは結構ネイティブ的な使い方っぽい感じがしますが、私があまり機能を知らないだけの可能性もあるので、深くは語りません。
Unityは外部のアセットになるのですが、色々2Dのアセットが充実していて動きを作りやすいのがいいですね。


個人的には初心者にはUnityをおすすめします。
ポインタ周りは初心者には辛すぎる。
UEのBlueprintだけで実装する手もありですが、スケールアップしていく中で
UEは参考資料も少なかったり、色々弄りたくなると思います。
ゲームプログラマを目指すなら、そのぐらい頑張れ!」と言いたい方もいると思いますが、
本当の初心者例えば小学生や中学生がポインタの概念を理解するのは辛いと思います。
最初作ったそのままの感覚で進められるUnityの方が強い。

しかしながら、大規模開発はUEで行われることが今は多いので、大規模コンシューマーに関わりたいなら勉強していて損はなさそう。


後、最初から大規模予定ならUEでも良いかも。
大規模開発に向いてる。
Unityは小規模チーム開発に向いてるかも。
どちらも少人数、大規模が出来ないわけじゃないですが……
どちらも同程度に使えるならという前提なら私はそう思います。

まとめというか、感想というか一言


公平に語ったつもりですが、賛否両論あると思います。
どちらが優れているとかは無いと思います。

作るのは開発者であってエンジンは手段でしか無いです。
状況に応じて判断すべきで、どちらが良いかとは一概にいえないと思います。

Unityエンジニアだから、UEエンジニアだからって現状を嘆いたり、相手を悪く言う必要はないと思います。
というか、どちらも触っている私に精神衛生上に優しくないので、なかよくしてください。

以上です。

MVRP4Uリポジトリの解説

こんにちは、やまだたいし( やまだ たいし (@OrotiYamatano) | Twitter )です。
以前 UnityのMVP、MV(R)Pを調べたけど、どれが正しいんだ?という記事を書きましたが、
結構閲覧されている割に一番見て欲しいリポジトリの中身を見られていないようだったので、
今回はそのリポジトリの中身の解説をしていきます。
(合わせて2019verにバージョンアップをしました)

github.com

目次


中身について


中身はただボタンを押すと、カウントアップするものです。

f:id:OrotiYamatano:20200908020112g:plain
中身

使用ライブラリ


Zenject(Extenject)


ZenjectとはDIフレームワークです。
DI(Dependency Injection)は依存性の注入という意味ですが、かんたんに言うと、
半自動的にインスタンスを生成してくれる機能です。(ちょっと語弊があるケド……)

シングルトン(Singleton)とかは世界に一つだけで、グローバルにどこからでもアクセスできるという利点がありますが、
逆に言うと世界のどこからでもアクセスできてしまいます。

ゲーム業界では音声の管理やキー入力の管理など好んで使われますが、業界によってはグローバル変数と同じぐらい嫌われ者です。

そこで出てきたのがサービスロケーター(Service Locator)パターンと呼ばれるもので、
コレクションとして登録しておいて必要に応じてインスタンスを生成し取り出す形です。

もう少し分かりやすく言うと、「シングルトンなファクトリークラス(サービスロケーター)にインスタンスをキャッシュしておいて必要に応じて取り出す」って感じです。

手元のインスタンスに対して代入してから使うので、グローバル変数みたいに直接にアクセスするわけではないので、多少マシです。

しかしながら、サービスロケーターでは結局ずっとキャッシュを保持し続けることになり優しくありませんし、
消す仕組みをいれたとしても「シングルトンなファクトリークラス」を参照する必要があり密結合です。

そこで出てくるのがDIパターンです。
DIパターンは先にインスタンスを生成しておいて、生成しておいたクラスをインスタンスに代入してくれます。
使用側はあまり意識せずに利用できるような仕組みです。

詳細は割愛しますが、DIパターンを書くとコード量が多くなるので、DIのコードを書かず
いい感じにDIの良さを体験できるのがDIフレームワークです。

まぁかんたんに言えば、「シングルトンを寿命管理したりスコープ管理したり出来るいい感じのヤツ」がIDフレームワークと考えてくれれば良いです。

UnityのDIフレームワークで今回利用するのがZenject(Extenject)ってわけです。
ExtenjectとはZenjectの派生リポジトリなんですが、(権利周りで)なんやかんやあって
普通最新であるはずの本家リポジトリのZenjectより最新で使いやすいのでExtenjectの方を使いましょう。

assetstore.unity.com

UniRx


UniRxとは
Uni(Unity)Rx(Reactive Extensions)と名前の通り
Unityでリアクティブプログラミングをするためのライブラリです。

作者さんのオレオレライブラリって訳ではなく、ちゃんとした考えに則って作られてます。(本家Rx系から機能を移植されるかは作者の気分次第かも?)

Rxの強みは非同期処理とObservableパターンのプログラミングがしやすくなることだと私は考えます。
Observableパターンとは例えばボタンが押した時に処理が反応するという物を作りたい時、
普通ならボタンを押下という処理からボタンの処理という関数を呼び出すのが普通です。

ですが、Observableパターンは逆で、ボタンを押されたかを監視して、押されたら処理を実行するという処理になります。
ん?イベント駆動型とどう違うんだ?と思った貴方!鋭い!

実際にはイベント駆動型の場合別スレッドで受け取る側を用意していてボタンを押した次のフレームなどに処理されますが、
Observableパターンの場合は同一フレーム内で即実行される。とだけ、ここでは言っておきます。

詳しくは調べてください。
というかそんなことに気がつく貴方はここの記事読まなくても良いのでは?

とにかく、Observableパターンはそのようなイベント駆動型と似ていて、ライブラリを使えば、コードを短く書ける利点があるだけでなく、感覚的に処理が書けます。
もちろん学習コストはかかりますが、慣れれば手放したくないという人が、ほとんどです。

これまでゲームはシングルスレッドで、Update(もしくはTick)処理にて毎フレームゲームオブジェクトを更新したり更新しなかったりするのが普通でした。
しかし、ゲームのオブジェクトの巨大化やシミュレーションするオブジェクト数の増加により単純にUpdateの呼び出しにさえコストを計算する必要が出てきました。

そこで、マルチスレッド化をしたり、Updateを必要なときだけ呼び出して、他の場合は呼び出さないと言ったような処理をしたいとき、
UniRxのようなイベントベース、メッセージベースの機能は便利に働きます。
(Unityは早くシングルスレッドやめろ)

UniRxは必要なときにしかUpdateを呼び出さない処理が組み込まれています。(UpdateAsObservable)
また、頻繁に呼ばないと行けない時に呼び出す関数(Observable.EveryUpdate)などもあり、
使い分けをすることで普通にUnityでUpdate関数を呼び出すより高速化が測れます。

後、MonoBehaviour を継承していないクラスでUpdate出来たり……とにかく便利

qiita.com

作者の一言

前回紹介したMicroCoroutineを改良して、配列をお掃除しながら走査する(かつ配列走査速度は極力最高速を維持する)ようになった

neue.cc

どうやって早くしてるのかとかはUnity触ったことのある人なら一度はお世話になる
皆おなじみテラシュールブログの解説が分かりやすいらしいです。

tsubakit1.hateblo.jp

まぁ、早くなるし、短く書けるのでオススメです。

assetstore.unity.com

MV(R)Pとは


まず、リポジトリの中身を説明する前にレイヤ別けの考え方を説明しておきます。

というか、MVP(MVC)については私より説明がうまいページがあるので、ここを読んできてください。

developers.cyberagent.co.jp

MVPをリアクティブプログラミングでやるのがMV(R)Pです。
この上のリンクのページもキレイにプログラムを書けているのですが、ライブラリを用いていないためコード数が多く複雑です。

ライブラリを使うともっとスッキリ書くことが出来ます。

後、UnityはMonoBehaviourによって支えられていますが、MonoBehaviourを継承して使っているということはMonoBehaviourに依存しているということでもあります。
きれいなソースコードを目指す人はできる限りMonoBehaviourを継承しないマッサラなクラスがきれいなので出来るだけ依存しないようにしましょう。
MonoBehaviourに依存していないコード、イコールUnityにアタッチしていないコードとなるのでUnity上でのコンポーネントのアタッチの考慮をしなくていいのでキレイです。

ソースコード


では、コードの中身を解説していきたいと思います。

Model


まずは分かりやすいModel(TestModel.cs)から。

まずメンバ変数として2つ定義します。

        private readonly IntReactiveProperty num = new IntReactiveProperty();
        public IReadOnlyReactiveProperty<int> Num => num;

いきなり、マッサラなC#しか触ってこなかった人だと分からない型が、出てきましたね?
これはUniRxで定義されている変数の型で、IntReactiveProperty は値が変更した際にイベントのようなもの(OnNext)を発行する型です。
intの派生形だと思ってもらえばいいです。
IReadOnlyReactivePropertypablic で定義されていますが見ての通りReadOnlyで変数 num を参照しています。
つまり Num を外部から使えば num の変更したタイミングと値を取得することが出来ます。

次にコンストラクタの解説に進みます。

        private TestModel(){
            num.Value = 0; //値のリセット
        }

はい。ただnumを初期化してるだけです。

次はビジネスロジックです。
といってもただ加算するものですので、ただ足すだけです。

        public void CountUp()
        {
            num.Value++;
        }

はい。
これで終わりです。

以下がModelの全文。

using UniRx;

namespace Sample.Models
{
    /// <summary>
    /// Model
    /// ビジネスロジックはmodelに書く
    /// </summary>
    public class TestModel
    {
        private readonly IntReactiveProperty num = new IntReactiveProperty();
        public IReadOnlyReactiveProperty<int> Num => num;


        private TestModel(){
            num.Value = 0; //値のリセット
        }

        // カウントアップの処理(ビジネスロジック)
        public void CountUp()
        {
            num.Value++;
        }

    }
}

簡単ですね。
UniRxを使わなくてもModelはほぼ同じような中身になると思います。

View


次はView(TestView.cs)の解説です。
ViewはUnityのオブジェクトにアタッチします。

メンバ変数の解説をします。

        [SerializeField]
        private Button countButton = null;
        [SerializeField]
        private Text text = null;

        //ボタンがタッチされたらPresenterに通知
        public IObservable<Unit> PushButtonObservable => countButton.onClick.AsObservable();

Unityお馴染みの SerializeFieldButtonText を定義します。
もちろん、Unityエディタ側で対応するオブジェクトをアタッチしておきます。

f:id:OrotiYamatano:20200912015247p:plain
スクリプトに該当オブジェクトをアタッチ

次に IObservable<Unit> で定義してあるものですが、こちらもUniRxの機能を使っています。
IObservableと書いている通り監視者です。
ボタン( countButton )が押された時( onClick )に IObservable型にキャスト( AsObservable ) して返す処理を監視します。
publicになっていますので、外部からコレを参照すると、クリックしたタイミングを取得できます。

次に見た目への反映部分の処理です。

        //見た目へ変更を加える(Presenterから呼ばれる)
        public void TextMeshUguiSet(string str)
        {
            text.text = str;
        }

はい。
ただ、pubicメソッドでテキストを代入してるだけです。
これで終わりです。

以下がViewの全文。

using System;
using UniRx;
using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.UI;

namespace Sample.Views
{
    /// <summary>
    /// Viewの設定
    /// </summary>
    public class TestView : MonoBehaviour
    {
        [SerializeField]
        private Button countButton = null;
        [SerializeField]
        private Text text = null;

        //ボタンがタッチされたらPresenterに通知
        public IObservable<Unit> PushButtonObservable => countButton.onClick.AsObservable();

        //見た目へ変更を加える(Presenterから呼ばれる)
        public void TextMeshUguiSet(string str)
        {
            text.text = str;
        }
        
    }
}

簡単ですね。
Unityにアタッチしてる部分なのでそんなに難しい処理は無いです。

Presenter


次に仲介者であるPresenter(TestPresenter.cs)を解説していきます。
まずは、メンバ変数の解説。

        //読み取りしかしない
        private readonly TestView testView;
        private readonly TestModel upButtonModel;

メンバ変数は、先程解説したTestViewとTestModelを変数として保持しています。
privateでreadonlyなので、ViewとModelは互いに存在を知りません。

コンストラクタの解説です。

        //Presenterの処理
        public TestPresenter(TestModel model, TestView view)
        {
            upButtonModel =  model ?? throw new ArgumentNullException(nameof(model));
            testView = view ? view : throw new ArgumentNullException(nameof(view));
            //ModelとViewが増えたら追記していく
            
            upButtonModel.Num.Subscribe(ViewNumUpdate); //Modelに変更があったらViewへ更新
            testView.PushButtonObservable.Subscribe(_=> CountUp());  //Viewからカウントアップ通知があったらModelを更新

        }

はい、ここでModelとViewの中身を入れてます。
こちらでZenjectの コンストラクタ/メソッドインジェクション を利用しています。
後でもう少し詳しく解説しますが、「先のModelとViewの中身を入れてるんだな~」ぐらいの認識でOKです。
ArgumentNullException は中身なければエラー吐くぐらいな感じです。
定義忘れとかのチェックのために念の為書いておきます。

で、 upButtonModel.Num.Subscribe(ViewNumUpdate); ですが、コメント文のとおりです。
Numの解説に「 Num を外部から使えば num の変更したタイミングと値を取得することが出来ます。」と書きましたが、ここで使用してます。
ViewNumUpdate はメソッドです。メソッド呼び出しをするということです。

要約するとSubscribeでupButtonModelのNumつまり、Modelのnumに変更があったら変更通知を取得でき、ViewNumUpdateメソッド呼び出しをしているということです。

testView.PushButtonObservable.Subscribe(_=> CountUp()); ですが、コチラもコメント文のとおりです。
PushButtonObservableの説明で「外部からコレを参照すると、クリックしたタイミングを取得できます。」と書きましたが、ここで使用してます。(なんせ仲介者ですからね)
こちらはCountUpメソッドを呼んでいます。

では、ViewNumUpdateメソッドとCountUpメソッドの解説です。
と言っても解説するほどの内容は無いです。

        /// <summary>
        /// Modelのカウントアップ処理を呼ぶ
        /// </summary>
        private void CountUp()
        {
            upButtonModel.CountUp();
        }

        /// <summary>
        /// ViewのTextMeshUguiSetを呼ぶ
        /// </summary>
        /// <param name="num"></param>
        private void ViewNumUpdate(int num)
        {
            testView.TextMeshUguiSet(num.ToString());
        }

はい、各Model,Viewの処理を引き継いで渡してる(仲介してる)だけです。
これで終わりです。

以下がPresenterの全文。

using System;
using Sample.Models;
using Sample.Views;
using UniRx;

namespace Sample.Presenter {
    /// <summary>
    /// Presenter
    /// Modelの変更をViewに反映し、ViewのアクションをModelへ反映
    /// </summary>
    public class TestPresenter
    {
        //読み取りしかしない
        private readonly TestView testView;
        private readonly TestModel upButtonModel;

        //Presenterの処理
        public TestPresenter(TestModel model, TestView view)
        {
            upButtonModel =  model ?? throw new ArgumentNullException(nameof(model));
            testView = view ? view : throw new ArgumentNullException(nameof(view));
            //ModelとViewが増えたら追記していく
            
            upButtonModel.Num.Subscribe(ViewNumUpdate); //Modelに変更があったらViewへ更新
            testView.PushButtonObservable.Subscribe(_=> CountUp());  //Viewからカウントアップ通知があったらModelを更新

        }
        
        /// <summary>
        /// Modelのカウントアップ処理を呼ぶ
        /// </summary>
        private void CountUp()
        {
            upButtonModel.CountUp();
        }

        /// <summary>
        /// ViewのTextMeshUguiSetを呼ぶ
        /// </summary>
        /// <param name="num"></param>
        private void ViewNumUpdate(int num)
        {
            testView.TextMeshUguiSet(num.ToString());
        }
    }
}

簡単ですね。
ViewとModelの仲介をしてるだけなので難しい処理は無いです。
見ての通りZenjectを使うとPresenterがMonoBehaviourを継承せずに済み、依存性が薄いクラスが出来上がります。

Zenjectinstaller


一気に説明します。
zenjectInstallerと名前にある通り、zenjectのインストール関連を管理しています。
Containerへのインストールですね。
今回はMonoInstallerを使用します。
定義したものはMonoBehaviourのように振る舞うようになります。
インストールの定義を書かれたものがInstallerのスクリプト

TestModelをAsCached( ContractTypeが要求されるたびにResultTypeの同じインスタンスを再利用。これは最初の使用時に遅れて生成)。
TestPresenterをAsCached( ContractTypeが要求されるたびにResultTypeの同じインスタンスを再利用。これは最初の使用時に遅れて生成)。
した上で、NonLazy(これを指定すると最初にインスタンスが生成される)

using Sample.Models;
using Sample.Presenter;
using Zenject;

namespace Sample.ZenjectInstaller
{
    public class SampleButtonInstaller : MonoInstaller
    {
        //zenjectでModelとPresenterのインストールする
        public override void InstallBindings()
        {
            Container.Bind<TestModel>().AsCached();
            Container.Bind<TestPresenter>().AsCached().NonLazy();
        }
    }
}

各オブジェクトの紐付け


TestModelとTestPresenterがMonoBehabiorのように振る舞うようになりましたが、肝心な部分を解説していません。
それぞれのオブジェクトをどうやってPresenterにつなぎ込んでいるのかです。

それはズバリ、SceneContextやZenjectBindingで解消されます!
後、マッサラなクラスやUnityにアタッチしているオブジェクト()をZenjectの SceneContextZenjectBinding に設定することで依存関係を直してれます。

f:id:OrotiYamatano:20200912022613p:plain

まとめ


ぶっちゃけ、勢いで書いてみたものの、Zenject周りの解説があってるのかとか、
サービスロケーターの説明がちゃんと正しいのかはちょっと不安です。

でも、アウトゲーム部分を作る分にはキレイなコードだと自負しています。
(インゲームに適用するのはおすすめしません)

こんな雑な説明をしていますが、一応私も業務でUnityを使ったことがある身です。
説明は下手ですが、コードは問題ないと思います。ある程度なら肥大化しても耐えうるでしょう。
Zenjectは生成時がちょっと処理負荷が重いですが、そこはちゃんと使いこなして生成タイミングをずらしたりすれば良いと思います。
疎結合のコードが出来る良いものです。
みなさんも色々設計考えながらコーディングしてみてはいかがでしょうか?
以上で解説を終わります。

おすすめの記事とか


ryo620.org

light11.hatenadiary.com

booth.pm

booth.pm

qiita.com

おまけ。


Editorフォルダ配下に途中まで、スクリプト自動生成スクリプトを書きました。
指定先のフォルダのtextを参照してクラスの雛形を作るだけです。

この辺を参考にすると作れます。

light11.hatenadiary.com

(Scriptが小文字になってるのが気に食わなくてソコだけはgifと変わってます)
(あ、削除する方は小文字で変え忘れてますね……ちゃんと消えない……まぁ、いいや)

f:id:OrotiYamatano:20200912025138p:plain

using UnityEngine;

using System.IO;
using System.Text;
using UnityEditor;

// コードの自動生成
public class MVPRU : EditorWindow
{

        private string _baseClassName = string.Empty;
        private string _sceneName = string.Empty;

        
        [MenuItem("Window/MVPRU")]
        private static void Open()
        {
            GetWindow<MVPRU>("MVPRU");
        }


        private void OnGUI()
        {
            EditorGUILayout.LabelField("SceneName");
            _sceneName = GUILayout.TextField(_sceneName);
            EditorGUILayout.LabelField("Create Base Class Name");
            _baseClassName = EditorGUILayout.TextField(_baseClassName);

            if (GUILayout.Button("CreateScript"))
            {
                string path = Application.dataPath;
                string namePath = "Scripts/" + _sceneName + "/";
                path += "/"+namePath;

                
                CreateScriptAsset(_sceneName+".Models", _baseClassName, "Model", path + "/Models",_sceneName);
                //CreateScriptAsset("Script."+_sceneName+".Presenters", _baseClassName, "Presenter", path+ "/Presenters",_sceneName);
                CreateScriptAsset(_sceneName+".Views", _baseClassName, "View", path + "/Views",_sceneName);
                
                Debug.Log($"Create Script Path : {path}");
            }


            if (GUILayout.Button("ClearScript"))
            {
                string path = Application.dataPath;
                string namePath = "script/" + _sceneName + "/";
                path += "/"+namePath;
                
                RemoveScriptAsset(_baseClassName, "Model", path + "/Models");
                RemoveScriptAsset(_baseClassName, "Presenter", path+ "/Presenters");
                RemoveScriptAsset(_baseClassName, "View", path + "/Views");
                SafeCreateDirectory(path + "/ZenjectInstaller/");
                Debug.Log($"Remove Script Path : {path}");
            }

        }

        private const string TemplateScriptFilePath = "ScriptTemplate/";

        private static void CreateScriptAsset(string nameSpace, string baseClassName, string domainName, string filePath,string sceneName)
        {
            string templateRawText = Resources.Load($"{TemplateScriptFilePath}{domainName}.cs").ToString();
            string replacedText = templateRawText.Replace("#SCRIPTNAME#", baseClassName).Replace("#NAMESPACE", nameSpace).Replace("#SCRIPTSCENENAME", sceneName);
            var encoding = new UTF8Encoding(true, false);

            if (Path.GetExtension(filePath) != "")
            {
                // If you select Non directory, then get parent directory.
                filePath = Directory.GetParent(filePath).FullName + "/";
            }

            SafeCreateDirectory(filePath);
            filePath += "/";

            string fileName = $"{baseClassName}{domainName}.cs";
            File.WriteAllText(filePath + fileName, replacedText, encoding);

            var createdScript = AssetDatabase.LoadAssetAtPath<MonoScript>(filePath + fileName);
            ProjectWindowUtil.ShowCreatedAsset(createdScript);
            AssetDatabase.Refresh();
        }

        private static void RemoveScriptAsset( string baseClassName, string domainName, string filePath)
        {
            if (Path.GetExtension(filePath) != "")
            {
                // If you select Non directory, then get parent directory.
                filePath = Directory.GetParent(filePath).FullName + "/";
            }
            filePath += "/";

            string fileName = $"{baseClassName}{domainName}.cs";

            File.Delete(filePath + fileName);

            AssetDatabase.Refresh();
            
        }
        
        
        private static DirectoryInfo SafeCreateDirectory( string path )
        {
            return Directory.Exists( path ) ? null : Directory.CreateDirectory( path );
        }
}

鈴木竹治氏のフォント「Fonts66コンプリートパック/109書体 -商用利用可」はUnityのSDFで使える?問い合わせてみた

こんにちは、やまだたいし( やまだ たいし (@OrotiYamatano) | Twitter )です。
以前、Fonts66のセールがあり今回はUnityのTextMeshProでSDFを使いたいと思ったんですが、
使えるかどうかが不明だったので問い合わせをしてみました。

目次


なぜ問い合わせをしたの?


様々な用途で使えるようですが、ゲームで使う方法については具体的に記載されていません。
以前から私も気になっていましたし、今回セールがあり他にも気になる人が多く出てきそうなことや、Unityで使わず死蔵してる人も多いと思ったので問い合わせてみました。
以前ゲームない使用については、Unity AssetStoreさんが問い合わせをされていたようですが、SDFで使えるかどうかについては詳しく記載されていませんでした。

www.asset-sale.net

ズームしても滑らかに表示されるためNGだと思います

と書いてあるけど、具体的には不明?

今回のセール


nlab.itmedia.co.jp

27万円→2980円になるセールです。
半年に1回ぐらい(?)のペースでセールをされているようです。
とても安いです。

で、使えるの?


Dynamic SDFについては、もちろん使えません。
動的にフォントSDFを生成するためにゲーム内バイナリにフォントデータが含まれることになります。
ですので、PDFと同様に使用は出来ません。

しかし、Static SDFについては使えるようです!

問い合わせをした文章


ちょっと日本語が変ですが、私が問い合わせをした文章がこちら。

個人ゲーム開発者の山田大志です。
Fonts66の利用についての問い合わせです。
私はFonts66をゲーム内での利用を検討しています。
そこでゲームエンジンUnityのTextMeshPro機能にてSDF(Signed Distance Field)形式『ビットマップ画像だけれど、輪郭をきれいに出すプログラムを利用して表示させるもの』が利用可能かを伺いたいです。
以前Fonts66のゲーム内利用については、ビットマップでの利用ならOKとの回答をしていたようですが、( https://www.asset-sale.net/entry/Vector_Fonts66 )
SDF形式は「フォントデータを一部取り出しビットマップの画像形式で保存し表示させるもの」ではあるものの、「拡大縮小で品質が大きく変わるものでは無い」のですが、利用する場合は問題があるのでしょうか?
ご回答いただけますよう、よろしくお願いします。

そして、スキルインフォーメーションズさんの回答がこちら。
(念の為、担当者名は伏せます)

山田大志様
スキルインフォメーションズのxxと申します。
この度はFonts66についてのお問い合わせ
誠にありがとうございます。
ご質問いただきました内容についてですが、
ビットマップ化された画像データをゲームに搭載いただくことは
可能ですが、フォントデータそのものをゲームに実装、搭載する
ご使用方法は不可となります。

とのことです。

つまり今回は フォントデータそのもの ではないからOKだと思われます。

SDF形式を知らない可能性が高い気がしますが、一応、

SDF形式は「フォントデータを一部取り出しビットマップの画像形式で保存し表示させるもの」ではあるものの、「拡大縮小で品質が大きく変わるものでは無い」のですが、利用する場合は問題があるのでしょうか?

と私が軽く説明した上で返答が来ているので大丈夫だと思われます。
念押しとして本日(2020/08/24)以下の文章で返信をしておきました。

回答ありがとうございます。
フォントデータそのものが含まれないのであれば
SDF形式で利用が可能ということで実装させていただきたいと思います。

何か齟齬があれば返信が来るはずなので、その際はこの記事を編集しようと思います。

生のフォントデータそのものをゲームソフトに組み込むことについては、別途契約が必要らしいので、組み込まないようにだけ気をつけましょう!
またPC1台につき1ライセンスだということにも気をつけましょう。複数台で動作確認する際は台数分購入するようにしましょう!

Unityでstatic SDFで使う時に参考にできる記事


qiita.com

ということで、このあたりを参考にfontをベイクして使ってしまいましょう!

まとめ


Static SDFなら使える!ということで回答を得ることが出来ました。
万が一、齟齬があったら訂正記事を出そうと思います。

ということで皆、格安セールのうちに!Fonts66を買おうぜ!
TxetMeshProをStaticで使う方法についてはこちら↓
orotiyamatano.hatenablog.com

祖父は戦争に行った人だった

原爆投下の日の報道についてTwitterで流れているのを見てふと祖父の事を思い出した。
今年の1月、社会人として忙しくしている時に父から祖父が亡くなった報告をうけた。

祖父は第一人称が「僕」で身長が低くどこか可愛らしい見た目をしているが、いつもパワフルで力強い人。
驚くと「アゲ~~~ッ!」と声を出して目をまん丸にして驚く人だった。
そんな祖父について思い出せるうちに日記に書いておこうと思ったので、筆をとることにした。
私は祖父は大往生したため、満足した人生だっただろうと思う。私は個人的には悲しみは少なかった。
ただ強烈な祖父の周りの人が亡くなることにより世界のどこからも祖父のエピソードが消えてしまうのは悲しく思えた。
だから、ネットの片隅に走り書きで申し訳ないが書き残して置こうと思ったのだ。


祖父は私にとっては年老いていながら力強い人だった。
老いても、ヤギを育て小さな畑を持っていて力仕事をしたり、網を海に設置して漁をしたりなど、ほぼ自給自足の生活をしていて「うまかっちゃん」という九州のラーメンが好きだった。

小学生の時から高校になるまで、ほぼ毎年夏になると、祖父の住む奄美大島加計呂麻島に祖父の家まで泊まりに行ったものだ。
私はあまり行きたがらないときもあったのだが、父と姉二人で行っていた。(従兄弟もいて賑やかなときもあった)
珍しい昆虫やブルーのカワセミ、遠くからギャーギャーと聞こえるルリカケスの鳴き声、海の匂い。すべてが鮮明に思い出せるわけではないが、いくつも思い出すことがある。

祖父の住む集落はほぼ知り合いばかりで、帰るたびに各家からお小遣いを貰っていた。どの家でもお小遣いをお貰うため5万を超えることは珍しくなかった。
「〇〇(母、もしくは父の名前)の子か?」と気さくに話しかけてもらい、「知り合いのおばあさん達はこれでアイスでも食べなさい」と
海の風が吹き込む縁側に座らされ少し古いオレンジジュースとキャラクターの載った小さい封筒に入ったお金を渡される。
古い家特有の加齢臭のような匂いと線香と海の匂いが混ざり合う。
嫌いではない時間だった。

父について回り、様々な家に挨拶に向かう。
集落の9割はおばあさん、おじいさんしか住んでいない。
コンビニはなく、台風になると食料の備蓄で耐えるしかなくなる。GoogleMapで見ても道は正しく表示されていない。
そんなド田舎である。
祖父の家はその集落でも特に古く、汲み取り式便所で中を覗き込むと10や20で収まらないほどのゴキブリで埋め尽くされていて、お風呂はガスを使わない焚き火で沸かすタイプのお風呂だ。

そんなど田舎に帰っても現代人は特にすることはないが、父に釣りやモリ突き、船の運転を習ったり、キャンプをしたり、夕方になると海水浴を行うといった夏休みを毎回過ごすのである。
もっとも体力のない私はすぐにバテてしまうのだが。


いくつかの思い出を思い出すことが出来るが、その中で何故か印象に残っているのは祖父の原爆の日にテレビを見る様子である。
祖父は海釣りに行くと80歳を超えているにも関わらず当時中学生の私より勢いよく早く錨を上げるような人だったが、その原爆の日のテレビ中継の様子は熱心に静かに見守るのである。

どこか哀愁に包まれたような祖父の姿はなぜか脳裏に焼き付いている。

祖父はお酒が好きだった。
どんな年になってもお酒を飲んでいた。
年々弱くなって行ってたが、若い頃は焼酎1升を1晩に1人で飲み干してしまうほどだったという。
お酒が飲める人を俗にザルと呼ぶが、引っかかるところさえないということで輪と呼ばれていたそうだ。

祖父がお酒を飲むと毎回同じ話をする。
毎回話すがゆえに詳しい話は逆に覚えて居ないが、毎回戦争へ行った時の話をしていた。
誤っているかも知れないが思い出せる限りを書くと以下の内容だった。

戦争時代若かった祖父は徴兵され各国へ派遣されたそうである。
軍の中でも銃の腕の良かった祖父は腕を見込まれ注目されたお陰か、頭の良さを買われ戦場から離れて(多分軍師として)勉強することになるのだ。
しかし、戦争は激化し、ついに祖父も戦場へ行くことになり様々な国に派遣されることになる。
最後の戦場では小さな小隊の隊長に任命された。
神経をすり減らしながら、小隊を進めたらしい。

祖父は毎回「1人も人を殺さなかった」と自慢していたが、
部下には殺させてしまったようなのか、悔いているようでもあった。
話しぶりからなんとなくだが、亡くした部下も少なくないようだった。

そして、祖父が上記のエピソードを削ったとしても絶対に話すシーンがある。

終戦の話だ。

満州で小隊を進めている祖父は、大きな声で呼び止められる。
「小隊長殿~~~!」
何事かと腰を抜かしたそうだ。
「戦争は終わりましたぞ~~~~!」と日本が負けたことを知らされる。

いつもこのセリフを嬉しそうに祖父は話すので終戦したことはとても喜ばしいことだったのだなと思う。

そしてしみじみと「天皇陛下は悪くない、軍のエライ奴らに騙されとったんだ、軍のエライ奴らが一番悪いヤツらだったんだ、戦争は絶対にしたらいけない」という。

それがいつも祖父が話していたエピソードだ。
今思えば、どのぐらいエライ立場だったのかとか、その当時の様子はどうだったのかとか、色々聞けばよかったなとも思うが、
度々祖父宛に取材をしていた方が居たので、きっと語り継がれているだろうと思う。


そういえば、祖父が熱心に読んでいた漫画を思い出した。なぜ祖父が読んでいたのかなどは忘れていたが、原爆の日のテレビの中継を見ている時と同じような目をしていたのを覚えている。
祖父なりに思うところがあったのだろう。
確か、↓の漫画だ。祖父が読んでいた様子を見たのは一回だけだったし小学生のころだったが、なぜか覚えている。

www.amazon.co.jp


終戦後、祖父は電力会社として奄美大島の加計呂麻半島全土に電気を巡らせ、台風のたびに自転車とヘルメットとカッパを来て駆け回ったらしい。
70歳を超えても配線がどうなってるのか全てを把握しているのは祖父だけだったらしく、夏に遊びに行った時も雨が激しい日は知らない電力社員の人が来て祖父にお願いしている様子を見たことがあった。

祖父は優しかったが父や私の叔父にとっては怒ると手がつけれれない人だったらしい。
「なんだその言葉は!日本男児たり得ぬ言葉だ!叩き切ってくれる!」と激高し、いつも近くに置いていた軍刀を抜刀したそうだ。
まぁ、祖父の近くにいる祖母に「あんた何やってるの!帰るよ!」と怒鳴られると、すっと顔色が戻り「はい」と返事をすると納刀すると背中を小さくして帰ったそうだが。
(因みにその軍刀は父が大きくなる途中で錆びついて抜刀できなくなっていたそうだ。酔っ払って同じく激高し抜刀しようとして抜けない軍刀を抜こうとしてたとかなんとか。少し滑稽だ)


そんな祖父は私が奄美大島へ帰らなくなってからも、畑を荒らした猫と喧嘩をして激高した祖父が猫を鎌で殺したとか、
ボケてきたため老人ホームへ行ったが耐えられなくなって脱走したとか、とんでもないエピソードが出たりしていたので100歳までは生きるだろうと思っていたが、
100歳手前で亡くなってしまった。

きっと何だかんだ満足した人生だっただろうと思う。
変な文章になってしまったが、 祖父と戦争に出た部下たち、祖母が安らかであることを願う。

【備忘録】怠惰な人(私)が書いたZenject(Extenject)の使い方の説明

こんにちは、やまだたいし( やまだ たいし (@OrotiYamatano) | Twitter )です。
今回はZenject(Extenject)の使い方についてです。
自分が使い方をよく忘れるので備忘録、まとめ用です。

N番煎じですので、参考にはならないと思います。

目次


Zenject(Extenject)とは


一応解説です。

DIフレームワークです。
DIとは依存性の注入(Dependency injection)です。
誤解を生みそうなくらい簡単にいうと、いい感じにクラスとかの初期化とかをやることをDIっていいます。
依存性っていうかオブジェクトの注入だよね。外部からオブジェクトを注入して初期化することをDIっていう。
(依存性って直訳するからわかりにくいだけで、切っては切り離せないものの注入って感じで訳した方が伝わりそう?)
初期化するときにDIコンテナというのを使っていい感じに自動で初期化してくれるのををDIフレームワークと言います。

厳密に詳しいこと知りたい?うるせぇ!コレは備忘録だ!説明書いてるだけありがたく思いやがれ!他の記事に行ってくれ。

他にも私が使ったことあるやつでDI使われてるフレームワークはSpringFrameworkとかZend Frameworkだな!
Webのやつだ!ただこの2つはDIフレームワークではなくて、DIも含まれるフレームワーク


私がZenjectなのになんでタイトルで(Extenject)ってついてるかって?
Zenject≒Extenjectだから。
Extenjectについては色々あるんだけど、元々のZenjectのメインメインコントリビューターのSteve Vermeulen氏がZenjectの権利関係で揉めたからとかなんとかで
Steve Vermeulen氏がメインで開発してるのにSteve Vermeulen氏がいてた会社がZenject権利を主張して色々揉めたって感じなのかな?詳しいことは知らん。
ま、それでSteve Vermeulen氏が別途つくったやつがExtenjectってわけ。なのでこの記事ではZenjectって私は呼んでるが、実質私がつかってるのはExtenjectの方。
Zenjectは更新されてないのでExtenject皆使おうね。

github.com

DI使う利点って何よ!


DIすると密結合の設計が疎結合になるって思ってる人がいるけど、それは違くて、
DIを使えれば疎結合に設計されたものを疎結合に保ち続けやすいって話。
だから、「イカしてるからDIフレームワーク導入しようぜ!」ってプロジェクトの途中で導入はやめたほうがいい。
止めはせんが、覚悟はしたほうが良い。

後単純にDIするだけでなくDIフレームワークを利用すると初期化の手間が省けてのが副産物的にうれしいって感じ。
メインは疎結合にするものだから、(後で軽く解説するけど)いろいろな初期化の手間を省くためだけに利用するのだけはやめたほうがいい。


あと、もう少し詳しく説明する。

グローバル変数で定義するのは良くないっていう理由で
シングルトンっていうデザインパターンがあるけど、
それもキレイじゃないって言われていて
サービスロケーターパターンってのを使い始められてるけど、
それすらもキレイじゃないって最近は言われてるのよね。

更にキレイに使えるようにしようってなって出てくるのがDIってわけ。

blog.a-way-out.net

場合によってはサービスロケーターやシングルトンも使うけど、基本DIでOK。

で、使い方


DIフレームワークっていうけどUnityで色々使えちゃうのよね。
逆に色々使えちゃうがために相応しくない使い方をしてしまって阿鼻叫喚する人もいる。
いくらいい感じに初期化してくれるからって何でもかんでも初期化対象にしたらゲームが始まるときとかに、初期化処理が走りまくって、とても遅くなるし、最悪の場合、ゲームが止まる。
まぁ、最初はグローバルで定義してるシングルトンやサービスロケーターを消すだけのために導入はありなんじゃないかな。

初期化処理で阿鼻叫喚した話は別の人がスライドとかで発表してた人がいたと思うので「Unity Zenject完全に理解した」とかで検索してくれ。
多分そこで言ってたと思う。記憶が正しければ。
ただ、ちょっと古い発表会なので、そのままコードは使えないこともあるので概念の理解だけに留めておいたほうがいいかも?
しらんけど。

さて、使い方を説明していく。

Zenject Binding


コンポーネントを自動でアタッチしてくれるやつ!
色んなオブジェクトにアタッチしないといけないときに、毎回[SerializeField]って定義したスクリプトに手作業でアタッチしていくのって面倒くさくない?
え、面倒くさくない?だったら手動でやってください。DI使うより初期化の動きの負荷がないのでそっちが良いと思います。この部分は終わり。

面倒くさい場合はDIで自動でアタッチされるようにしましょう。

1.アタッチするGameObjectをつくる
2. Zenject Bindingというスクリプトコンポーネントをプロジェクト上のどっかのオブジェクトにアタッチする(まぁ、普通に1のオブジェクトのほうが良いと思う)
3.Zenject BindingコンポーネントのComponentsにアタッチしたいオブジェクトを設定する。
4.[SerializeField]って書いてるところを替わりに[Inject]って定義する
5.終わり!

え、アタッチしたいオブジェクトが複数あって違うオブジェクトをそれぞれアタッチしたい?非MonoBehaviourとかのクラスを注入したい?シーン上に置かれてないやつをアタッチしたい?

うるせぇ!知るか!どういう設定でインストールするかっていうのはスクリプト書けば設定できると思うけど、そこまでは知らん!
↓でも読んどけ!

light11.hatenadiary.com

~~~~Context


シングルトンの消し方についてだ!
私はキー入力まわりをZenjectでいい感じにすることが多い。
人によってはシーン制御の共通系スクリプトとか、ネット通信周りとか、サウンドマネージャー的なやつをZenjectに任せるんじゃないか?しらんけど。

で~~~~Contextだけど、SceneContextとかProjectContextとかGameObjectContextとかあるけど、
DI対象を決めるために種類が分かれてるんだよね。

SceneContextならScene上にあるやつ全部、ProjectContextならプロジェクト上全部、GameObjectContextならアタッチしたゲームオブジェクトだな。

ぶっちゃけ面倒くさい人はProjectContextでも良いとは思うけど依存性を考えると一番はGameObjectContextよな。
しかし、私は面倒くさい!ので間をとってSceneContextを使う!一般的にもSceneContextが普通じゃないですかね?しらんけど。
シーンまたいで手軽に設定したい人はProjectContextを使うと良いんじゃないですかね。

肝心のSceneContextの出し方?他の記事をググれ。
で、実際に~~~~ContextでBindする方法だけど、それは、スクリプトを書かないといけない。

そのBind設定のスクリプト書くときに、このBindするスクリプトはプロジェクト上で一つだけ!とか複数個欲しいだとか生成するタイミングだとかは定義出来たはず。

備忘録用に私が使うキー入力のスクリプトだけ書いておく。

using xxxx.Util.Input;
using Zenject;

namespace Util.ZenjectInstaller {
    public class InputInstaller : MonoInstaller
    {
        public override void InstallBindings()
        {
            Container.Bind<IInputEventProvider>().To<InputEditor>()
                .AsCached();
        }
    }
}

何やってるかというと、[inject] IInputEventProvider hoge; って書いてるところにInputEditorをシングルトン的な感じでBindしてねってこと。
なんでIInputEventProvider とインターフェースを定義してるかっていうとInputEditorのところが差し替えやすいから。
TestInputとかInputSmartphoneとか別途作って定義おけばビルドごとに差し替えが出来たり楽よね。
NPCとかで擬似的にキー入力させたりとか?モンキーテストしたりとか、媒体によって変えたりとか?疎結合だからキーコンフィグ周りのコードもスッキリしそう。
こういうところをこんな感じでインターフェースを使って疎結合化することを依存性逆転の原則に則った設計っていうけど今回の筋からは外れるので解説は割愛。

「書いたスクリプトをどこにアタッチさせて利用できるようにするのか?」とか詳しい設定とかは他の人の記事を読んでくれ。説明面倒くさい。

qiita.com

マルチシーンで使いたいけどProjectContextは使いたくないとき


使いたいことがあったので書いておく。
それぞれのSceneでSceneContextを定義して親になるSceneContextを用意。

親のSceneContextに名前を設定する。

f:id:OrotiYamatano:20200808034224p:plain
親の名前

子供のSceneContextに親のSceneContextの名前を指定する。

f:id:OrotiYamatano:20200808034343p:plain
子供の方に親の名前を指定

後は、先に親のシーンから読み込まれるようにしておけばマルチシーン構成でもいい感じに親子関係なくBind出来るはず。

まとめ


雑にかきましたが、自分用です。
特に見る人も居ないだろうし、適当でいいよね?
他の人の参考になったら儲けもの程度に考えておきます。
この解説じゃ全然物足りないよーって人はimoさんの本がとても良いので買うと良いと思います。

booth.pm

では、アデュー。