うどんてっくメモ

技術的なメモをまったりと

2020年の振り返りと2021年の抱負

あけましておめでとうございます。2020年は仕事にリアルにいろいろなことがありました。 2020年にやったことを振り返りつつ、2021年頑張ることを書いていきます。

2020年やったこと

Qiita投稿 1本

Unity Game SimulationによるUnityアプリケーションの高速シミュレーション - Qiita

Qiita投稿はこの1本のみでした。ちょっと怠けてしまったなー、という感じです。 ただ、この記事自体は結構自信があった内容で、記事を投稿した後にUnity公式の方からメールでお話をいただいたりしました。 Unity Game Simulationは結構可能性を感じる機能なので今後も注目していきたいと思います。

会社ブログ 2本

creator.game.cyberagent.co.jp

creator.game.cyberagent.co.jp

SGE(CyberAgentのゲーム・エンタメ系の子会社のグループ)の公式ブログにて2本記事を書かせていただきました。 今年はこの社内ブログの寄稿を結構頑張っていて、所属する子会社の技術ブログがなかったのを立ち上げたりしてました。 2本とも記事としてはなかなかガッツリとしたものが書けたのではないかなと思っております。

技術書典への執筆 2回

技術書典で「UniTips」というUnityの技術書を定期的に会社で出しているのですが、2020年も2冊執筆に参加しました。

creator.game.cyberagent.co.jp

creator.game.cyberagent.co.jp

Airtestによる自動テスト、UPRによるプロファイリングといったちょっとニッチな内容について執筆しました。 技術書典には「UniTips」立ち上げから本を出させてもらっており、昔に比べて技術を文章化する速度や精度も上がっているのを感じています。 今後も「UniTips」の執筆は続けていく予定です。後述していますが、個人的な本の執筆も予定しています。

CEDEC公募に挑戦

業務で色々と技術的に挑戦していることがあって、せっかくならこれを元に公募チャレンジしたい!と思って頑張ってました。 結果的には通らなかったのですが、公募の感触や社内で登壇経験がある人のヒアリングなど、得るものは多かったです。 もちろん全然諦めていないので、挑戦し続けていきたいです。

Rustを始める

色々と技術的に手広くなりたいなと思って、前々から興味のあったRustを始めました。 公式のチュートリアルから始めて、bitFlyercliを実装してみたり、簡単なAPIサーバーを実装してみたり、ゲームを実装してみたり、と実際に物を作りながら学んでいます。 特にゲーム作りで今は勉強を進めており、AmethystというRust製のゲームエンジンをよく触っています。

amethyst.rs

まだまだ初心者ですが、GitHubにもこっそり成果物を上げています。

github.com

2021年の抱負

アウトプット強化

これまでもQiita投稿、技術書典参加、会社ブログの執筆など色々とアウトプットを続けてきましたが、以下の点を2021年は頑張ろうと思います。

  • 技術書典への「UniTips」の執筆継続
  • 技術書典で個人的にRustの本を出す
  • 個人ブログ蘇生、技術記事のアウトプットを2020年よりも増やす

大きいのは個人的に本を出したいという点です。「UniTips」の執筆を通じて得たノウハウを個人的な研鑽にもつなげたいと思ってRustの本を出してみよう!となりました。 技術書を出すことで知識精度をより高めたり、技術的な名刺になったりなどのさまざまなメリットがあるため、個人的にはおすすめなアウトプットの一つです。

また、この2018年から2年ほど更新が止まっていた技術ブログを蘇生したのも思い切った点です。 2020年色々な方のアウトプットを見たり、社内で活発にアウトプットされている方と話す機会があったりして、自分のアウトプットが定点的で見直さないとなと感じることが多かったです。 そこで、今年から技術ブログを蘇生して自分の名刺としてもアウトプットを積み重ねていくことにしました。記事も増やしつつ、より有益な情報をお届けできるよう頑張ります。

技術領域を広げる

自分は学生の頃から2019年までずっとUnity中心の勉強と業務を続けていました。 2020年になって社会人3年目ともなると仕事の幅も広くなり、Jenkinsおじさんになったり、自動のUIテスト環境を構築したりなど、Unity以外の知識を多く使うようになりました。 結果として自分の技術領域が狭く、もっといろいろな技術に触れた方がいいなーと感じる機会がかなり増えました。そこで、2021年には技術でやれることを増やす、という意識をしっかり持っていきたいです。 直近ではAirtestでの自動テストだったり、Rustの習得だったり黙々と勉強中です。アウトプットと絡めていきたいですね。

最後に

2020年は業務の幅も広がり、色々なエンジニアの方のアウトプットを目にしたりするようになって、色々と自分の活動を見直すいい年でした。 まだまだひよっこエンジニアですが、少しでも技術で世の中に貢献できるよう頑張っていきます。 今年もよろしくお願いいたします!

会社の有志の方と技術書典に本を出してきました

執筆日でもある10月8日にに技術書典5が開催されました!
参加者も出展サークルもぐんぐん増えて、会場が秋葉原UDXから池袋サンシャインシティ文化会館にお引越し、来場者1万人出展サークルの人数も800人越えと今までに無い大きな規模での開催となりました。

f:id:myudon:20181008231806j:plain

ありがたいことに自分は会社のエンジニアの有志の方々と共著を出せる機会があったため、出展サークル側として参加してきました。
UniTipsというUnityの開発にまつわるTips集の執筆と、サークルとしての運営及び進行をさせていただきました。

当日は多くの方が自分たちのブースに来てくれました!前回のUniTipsについての感想などをくださる方もいて、運営としてとても嬉しかったです!

f:id:myudon:20181008231638j:plain

会社として本を出すということで、せっかくなら仕事で学んだ知見をアウトプットしたいなと考え、以前自分が参加していたプロダクトで使用していたZenjectについての章を執筆しました。

現在のプロダクトでのタスクが忙しい時期ということもあり、切羽詰まったりもしましたが、早い時期から書き溜めていた結果何とか執筆を完了することが出来ました。
この技術書典を通して良かった点と反省点があったので少し書きとめようと思います。


まず1つ良かった点は、アウトプットを通した学びが出来たことです。

何かをアウトプットすることは、自分がインプットしたことをアウトプットできる形にする工程を通すことで初めて出来ることです。
そのアウトプットの成型の過程で、自分の理解の中で何が曖昧かを見つけることが出来ます。そして曖昧である部分を丁寧にインプットしなおすことで理解を深めることにつながります。自分もこの執筆を通してZenjectに対する理解をより深めることが出来ました。

また、先輩エンジニアと共著する形をとることで、自分の技術的な知見を言語化する良い経験をすることが出来ました。

今回は執筆フローとして全体のリポジトリgithubで管理し、各々が記事を執筆してPRを出し、全員のレビューを経てからマージするという形を取りました。 自分は技術的な部分を言語化し相手に伝えることに苦手意識があったため、先輩エンジニアの方々に自分の文章にレビューをいただくのはとてもいい勉強の機会になりました。

反省点は、イベント運営としての立ち回りです。

今回は執筆と合わせて、運営もやらせていただいたのですが、自分の至らない点が多く、先輩のエンジニアの方々に手間を取らせてしまうようなことがありました。
自分はエンジニア寄りのクリエイターを目指しており、こういったアウトプットをする場での運営や進行などはしっかりこなせないとなあと目指す自分に足りない部分をしみじみと自覚しました。
こういったことで割としょげがちなのですが、次のこういったイベントで経験を活かして行きたいです。

今後もこういったアウトプットの機会には積極的に参加していきますし、自分も会社の技術活性化のためにいろんな施策を提示したり運営出来るよう頑張っていこうと思います。
次の技術書典もこういった形で参加したい!と画策中です。エンジニアとしての経験が足りないからこそ、どうしたら少しでも早く経験値を得られるかを模索していきます。
とりあえず今日は1日中立っててへとへとなので、買った本を読みながら寝ようと思います。

ShaderGraphを使って頂点シェーダーで遊ぶ

この記事はUnityゆるふわサマーアドベントカレンダー 2018の22日目の記事になります
21日目の記事は@am1tanakaさんのNavMeshAgentでよい感じにキャラクターを歩かせるでした!

qiita.com

Unity2018.2に付随するShaderGraphのアップデートとしてついにvertex shaderの適応が可能になり、頂点情報がいじれるようになりました
今回は早速その機能を使い、簡単な頂点シェーダーをShaderGraphで作って遊んでみたいと思います


ShaderGraph

もうすでに多くの人が知っているかと思いますがShaderGraphはUnity2018から導入されたノードベースのシェーダーエディタです
従来のUnityではシェーダーを実装するにあたってShaderLabによるコーディングが必要でしたが、これによってビジュアルベースでシェーダーを構築できるようになりました
UEは勿論のこと、MayaやSubstanceBlenderなどのDCCツールを活用していた方などにとっては喜びの声が大きいものでした。最初はノードの種類も少なく表現の幅や使い心地に難があったのですが、様々な改修があり、Unity2018.2ではバージョンは3.0となりました。今後も期待ができる機能の一つです

使ってみる

f:id:myudon:20180822002929p:plain

使い方は簡単で、マスターノードに追加されたPositionに頂点情報となるVectorをつなぐだけです

f:id:myudon:20180822002202p:plain

まずは簡単な例です、ワールド座標に対して時間変数の正弦余弦を加算させてぐるぐるさせます

f:id:myudon:20180822020604g:plain

ボールがぐるぐる動いてますね、もうちょっと遊んでみます

qiita.com

上記のサイトにかなり簡易的な草の動きを実装した頂点シェーダーがあるので、ShaderGraphで組んでみます

f:id:myudon:20180822015303p:plain

草が風でそよそよする感じの動きですね
頂点情報について、UV座標で重みをつけたアニメーションを行います
頂点情報をいじっている部分をピックアップしてみましょう

f:id:myudon:20180822223821p:plain

ノードとしてはUVからパラメータを引っ張ってきて、定義したプロパティノードから各種必要な定数を引っ張ってくる感じです
UVから引っ張ってきたy値と定数パラメータを用いて頂点のx成分の移動値を作成し、座標に加算しています

これを適当な板に貼り付けて動かしてみるとこうなります、寂しいので3つぐらい並べてみました

f:id:myudon:20180822020625g:plain

時間に合わせて草が揺れるようになりました
手軽にササっと作れて、動きとしてアウトプットを確認できるので学びとして楽しいですね
今回は単純な動きを作成しましたが、物理挙動に紐づく波の動きだったり動的な凹み表現だったり遊べるものは多いので興味が湧いた方は遊んでみてください


いかがでしたでしょうか、少し緩めの記事ですが、シェーダーって触ったことないけどShaderGraphちょっと面白そう!などちょっとしたきっかけになればいいかなと思っています
個人的にShaderGraphでかなりシェーダーの技術的な敷居は落ちたかなーと感じているので、これを機にエンジニアは勿論、クリエイターからの表現のアプローチが増えていくことを期待しています、自分もいろいろ遊んでいる最中なので面白いものがあったら共有していきたいですね

Unity新Prefabワークフロー、Prefab Variantsの紹介

この記事はUnityゆるふわサマーアドベントカレンダー 2018の13日目の記事になります
12日目の記事は@sonoichi-60さんのリフレクションについてまとめたでした!

qiita.com

Nested Prefabの標準搭載など、Unity BerlinにてPrefab周りについてのワークフローが改修が発表されました
機能としては2018.3に搭載予定らしく、プレビュービルドが公開されています

unity3d.com

今回はそのPrefabシステムの一つ、Prefab Variantsについての記事です


Prefab Variants

Prefab variantsは所謂プレハブの継承的なシステムです
あるPrefabからのパラメータ継承と、要素のオーバーライドを実現することが出来ます
ベースプレハブの変更に伴う派生先の更新や、複数のPrefab継承先なども可能で、Unityにおける開発フローで効率を上げることができます
UnityがPrefabシステムのマニュアルを公開しているので、興味がわいた方は下記のリンクから読んでみてください

docs.google.com

実践

使う場合は上記のプレビュービルドのリンクからプレビュー版を落としてきてください

まずベースプレハブを作成します、今回は適当にUIプレハブを作成しベースにします
プレハブ上で右クリックし、Create→Prefab Variantsで作成できます、作成したプレハブは矢印の模様がついたプレハブアイコンになります

f:id:myudon:20180805043806p:plain

作成したPrefab Variantsは同じようにシーン上で自由に設定ができます
ベースとの差分などはプラスアイコンなどで表示され、無論variantとしての保存ももちろんメニューからそれをベースに反映することもできます

f:id:myudon:20180805044321p:plain

ベース側が変更をかけた場合もしっかりvariantへと反映されます、上記のスクショみたいに差分でApplyを行うのですが、これまでみたいに変更を一括でインスペクタから反映させることもできます
その場合はApplyの時に変更点が出るようになったので確認しましょう

f:id:myudon:20180805045225p:plain

違う派生元からのベース修正もしっかりと反映して運用できます、逆に言えば様々なプレハブへの変更なども考慮しないといけないので使い方に注意は必要になってきます
派生先でNested Prefabなど複雑になってくると事故が起きるかもですね

また、ここまでのプレハブの編集について、シーン上でコンポーネントなどをいじることも可能ですが基本的にはPrefab Modeでの編集をすることになります
シーン上でプレハブの関係を壊すような操作は許可されていません、削除などの操作を試みようとするとUnityに怒られます

f:id:myudon:20180805051322p:plain

プレハブをOpenする操作を行えばPrefab Modeでプレハブを開いてくれます
Prefab ModeではPrefabの関係操作や更新を行うことが出来ます、Nested Prefabの場合はその個々のプレハブについて編集画面にジャンプすることが出来ます

f:id:myudon:20180805052516p:plain

Nested Prefabで差分を出した場合は反映先を選択することが出来ます、複雑化してくると管理が難しいかもですが操作の意味合いが明示化されていて便利ですね

f:id:myudon:20180805052710p:plain

また普通にオブジェクトとしてBreak Prefab Instanceして使いたいなってときはUnpack Prefabを使用しましょう
UnpackだけだとNested Prefabは無視してプレハブ接続を解除、Completelyのオプション付き操作を行うことで全てのプレハブ接続を解除します

f:id:myudon:20180805053114p:plain

ざっと現状のPrefab Variantsの機能を説明しました、詳しくは公式Manualを見ながら自分で触ってみるといいでしょう


Nested Prefabの公式導入、Prefab Variants、Prefab Modeなど触っていて大分ワークフロー変わったなぁと感じました
便利なのですが、しっかりと操作や効率化を念頭に入れて学習していかないとなって感じです
今後も変更が入っていくと考えられるのでこの先の動向に注目です

IncrementalCompiler時代のUnityC#Tips(C#7.2)

この記事はUnityゆるふわサマーアドベントカレンダー 2018の6日目の記事になります
5日目の記事は@splas_boomelangさんのUnity用のパッケージ(アセット)を配布する時のお作法でした!

qiita.com

Unity2018.1以降からIncrementalCompilerがPreview版として公開されるようになりました
まだUnity開発界隈では6.0の機能を徐々に会社の実プロダクトレベルで実験的に活用し始めたぐらいのフェーズかなと思いますが(async/awaitがじわじわと広まりだしたぐらいかな?) 、IncrementalComplierを導入することでC#7.2までの機能を使用することができます
6.0のasync/awaitレベルのUnity開発に対する革新的な変更はないため、そんなにインパクトがあるわけではないのですがちょっとしたTipsとしてUnity開発に使えそうなものをちょこちょこっと紹介します


IncrementalCompiler

まずIncrementalCompilerについて軽くお話します
IncrementalCompilerとは2014年よりMicrosoftオープンソース化した次世代コンパイラ「Roslyn」をUnity内で用いることでコンパイル時間の削減とC#7.2までの機能の使用を可能にするものです
上述にもあったようにまだPreview版でバージョンも0.0.42-preview,16と試験段階かなと言わざるを得ない状態です、これにより今のところ運用を見送っているプロジェクト等もあるのではないでしょうか
自分が個人規模の開発で使用してみた感じは特に問題は起きていないため、使用を続けています、その内Unityのアップグレードに従って標準環境になっていくことは見えているので早いうちから慣れておくのは悪くない選択肢かなと

Unity Incremental C# Compiler - Unity Forum

Tips

それではIncrementalCompiler導入によって使用できるC#機能の紹介です

タプル

タプルとは簡潔に説明すると複数のオブジェクトを1つのオブジェクトとしてまとめたものです 元々.Net4で導入されていたもので、パッケージとして導入し使用することは出来たのですがC#7よりC#の正式な機能として導入されるようになりました
Tuple<T1, T2>みたいな感じでジェネリクス保有する型を定義して使用します
複数の値を戻り値として受け取れるため便利な機能の一つです、自分はよく使用しています
.NetのTupleだとアクセサが固定でitem1といった抽象名でしか扱えずコードが見づらくなりがちでしたが、C#では名前を定義して使うことができます

// タプルの定義
(int age, string name) tuple = (10, "udon");

// タプル値の参照
Debug.Log(tuple.age); //10
Debug.Log(tuple.name);

// タプルで返す
(bool success, int result) Sum(int x, int y)
{
    if(x > 100 || y > 100)
    {
        return (false, -1);
    }
    else
    {
        return (true, x + y);
    }
} 

またタプルで受け取った複数のオブジェクトに対してそれらを分解する機構も作られています
上記のSumのように定義したタプルを変数宣言することなく個々の戻り値を使えるわけですね、タプルを使用するケースではタプル自体に命名する意義がない場合が多いので嬉しい配慮です

(var success, var sum) = Sum(100, 100);
Debug.Log(success); //true
Debug.Log(sum); //200

出力変数宣言

出力引数を受け取る際、事前に変数を宣言し受け取り口を作ってからそれを指定する必要がありましたが、C#7からは式の中で受け取り口の変数宣言を行うことが出来るようになりました
これに関しては劇的に使用するわけではありませんが、スマートに書けるので身に着けときましょう

Dictionary<string, string> hogeDictionary;

hogeDictionary.TryGetValue("hoge", out var value);
Debug.Log(value);

async T

async/awaitは確かに強力なソリューションの一つですが、そのネックの一つとして返り値がTaskに縛られてしまうというものがありました
特にTaskとしての機能が要らないようなケースでもasyncさせるためにはTaskとして宣言する必要があり割とパフォーマンス的な観点で見るとおやっ?ってなったり、シンプルな値返還の形を作るだけでも若干await操作がめんどくさかったりしました
C#7.0からは任意の型に対してasync宣言が行え、それをawaitで待機できるようになりました
ここで具体的な説明をしてしまうとそれだけで一つの記事になっちゃうかもなので詳しくは下記のリンク先のtask-likeについての項目を読んでみてください

非同期メソッド - C# によるプログラミング入門 | ++C++; // 未確認飛行 C


ざっくり自分が使用したり、知識として抑えているものを紹介しました
この他にも様々な機能の追加がされているので、IncrementalCompilerを個人で試しに使ってみてください(特にUniRx6等はバリバリ使用しているので見てみるといいかも)

C#MessageBoxのようなダイアログをコルーチンみたいな感じで作ってみる

この記事はUnityゆるふわサマーアドベントカレンダー 2018の2日目の記事になります
1日目の記事は@splas_boomelangさんのOculusGoのコントローラーをUnity上でエミュレートするでした!

qiita.com

ゲーム開発を行う上で、思わぬ部分でネックになりやすいのがアウトゲームです
中でもゲーム中でもよく表示するダイアログ、ソーシャルゲームを開発する上で数えきれないほど実装することになると思います
どんな場面でも必要になり様々なロジックを担保するダイアログ、この設計が手を抜くと落とし穴(カオスコード)にはまりやすいです
今回はそのダイアログの作りの一つとしてC# MessageBoxを参考にしたダイアログについて説明します


C# MessageBox

C# MessageBoxは.NETFrameworkに搭載されているダイアログ表示用のクラスです

MessageBox クラス (System.Windows.Forms)

Windows民ならよく見るこれを作ってるやつです

f:id:myudon:20180801015734g:plain

MessageBoxでは、ダイアログ自体はロジックを持たず呼び出す側が担保します
呼び出し側がダイアログを展開し、DialogResultで結果待ちをし、その結果を利用してロジックを持つ呼び出し側が処理を行うわけです
聞いてみると普通じゃんって感じがすると思うのですが、アウトゲームを作っていくうえでロジックの所在などをあまり気にせず動くものを作る意識で開発を進めていくと割とダイアログがロジックを持つパターンで実装しがちかなって思います
あまり責務を分散させずにスマートなUI処理がしたいなと思い、これをコルーチンチックに実装してみました

実装

今回紹介するのはシンプルなダイアログのケースです
ダイアログを展開し、ユーザーからのポジティブ/ネガティブな結果を受け取りダイアログをクローズ、処理に移行するという形になります
具体的なケースとしては処理確認系ダイアログが近いでしょう、〇〇しますか?と聞いてはい・いいえで答えるようなやつですね

下記が実装コード、まずはダイアログ本体です
シンプルに形成したものなので各々が使う形に実装をアレンジしていただければと思います
実装の特徴としては簡単なダイアログの実装に加えて待機用のクローズフラグとダイアログのリザルトを含めてあります

public class ExampleDialog : MonoBehaviour {

    /// <summary>
    /// ダイアログそのもののRectTransform
    /// </summary>
    [SerializeField]
    private RectTransform _dialogBody;

    /// <summary>
    /// ダイアログに表示する文章
    /// </summary>
    [SerializeField]
    private Text _description;

    /// <summary>
    /// はいボタン
    /// </summary>
    [SerializeField]
    private Button _positiveButton;

    /// <summary>
    /// いいえボタン
    /// </summary>
    [SerializeField]
    private Button _negativeButton;

    /// <summary>
    /// 処理を終えてクローズしたかの判定
    /// </summary>
    private bool _isClosed;
    public bool IsClosed => _isClosed;

    public DialogResult Result { get; private set; }

    /// <summary>
    /// プレハブからダイアログ作る
    /// </summary>
    /// <param name="parent">吊るす親</param>
    /// <param name="description">ダイアログに表示する文章</param>
    /// <returns>ダイアログのインスタンス</returns>
    public static ExampleDialog CreateDialog(RectTransform parent, string description)
    {
        var dialog = Instantiate(Resources.Load<ExampleDialog>("ExampleDialog"), parent);
        dialog.Initialize(description);
        dialog.Open();
        return dialog;
    }

    /// <summary>
    /// 初期化処理
    /// </summary>
    /// <param name="description">ダイアログに表示する文章</param>
    private void Initialize(string description)
    {
        _description.text = description;
        Result = new DialogResult();
    }

    /// <summary>
    /// ダイアログオープンアニメーション
    /// </summary>
    private void Open()
    {
        _dialogBody.DOScale(1f, 1f).OnComplete(() => SetEvent());
    }

    /// <summary>
    /// ダイアログクローズアニメーション
    /// </summary>
    private void Close()
    {
        _dialogBody.DOScale(0f, 1f).OnComplete(() => 
        {
            _isClosed = true;
            Destroy(gameObject);
        });
    }

    /// <summary>
    /// ダイアログボタンのイベント処理、リザルト確定とクローズ
    /// </summary>
    private void SetEvent()
    {
        _positiveButton.onClick.AddListener(() =>
        {
            Result.ResultType = DialogResult.ResultTypeEnum.POSITIVE;
            Close();
        });

        _negativeButton.onClick.AddListener(() =>
        {
            Result.ResultType = DialogResult.ResultTypeEnum.NEGATIVE;
            Close();
        });
    }
}

次にダイアログの結果となるクラスです
ここネガポジをリザルトとして定義して持たせていますが、個々のダイアログで得体結果は違ってくるのでそこは応用が必要になってきます
各々のUIの基盤やシステム構想、ダイアログの使用箇所などと相談して工夫を加えると良いでしょう

// ダイアログの結果
public class DialogResult
{
    public enum ResultTypeEnum
    {
        NONE, // 何もなし
        POSITIVE, // はい
        NEGATIVE, // いいえ
    }

    /// <summary>
    /// ダイアログの結果タイプ
    /// </summary>
    public ResultTypeEnum ResultType;

    // ほかに必要そうなリザルトはここに置く、インターフェイスを定義して応用しても良い
}

最後にダイアログのクローズ及び結果を待つカスタムコルーチンです
特に変わった実装はしておらず、シンプルにダイアログのクローズを監視しています
ここもダイアログの仕様などでアレンジが入る箇所ですね

// ダイアログを待つカスタムコルーチン
public class WaitForDialogClose : CustomYieldInstruction
{
    public DialogResult Result => _dialog.Result;

    private ExampleDialog _dialog;

    public override bool keepWaiting
    {
        get
        {
            // ダイアログがクローズするまで待つ
            return !_dialog.IsClosed;
        }
    }

    public WaitForDialogClose(ExampleDialog dialog)
    {
        _dialog = dialog;
    }
}

以上が今回のダイアログのシンプルな形での実装例です
全てのダイアログに対して汎用的に組み込むことができるかと言われると怪しい部分もありますが、臨機応変に応用しましょう


実験

private IEnumerator Test()
{
    var dialog = ExampleDialog.CreateDialog(_canvas, "ほげほげ?");
    var result = new WaitForDialogClose(dialog);

    yield return result;

    Debug.Log(result.Result.ResultType);
}

上記のようなコルーチンを走らせて挙動を見てみます

ダイアログの結果を受け取ることができました、後は受け取った結果を使って処理を行いましょう
また機会があればアウトゲームについてのこういった小さな技術共有をしていきたいと思います

EasyMotionRecorderを応用したモーキャプデータのモバイル共有

現在、ある案件にてUnityでモーションキャプチャを行い、そのモーションをなんとかしてスマホで共有したいという要求があり、それを現実化するために空いた時間で少しずつ開発を行っていました
その際にとあるモーションの再生録画機構のおかげで作業工程を減らすことができたためメモ

EasyMotionRecorder

github.com

EasyMotionRecorderは某Vtuberで有名なDuoがシンプルなモーションの再生及び録画機構として公開しているライブラリです
元々はAnimationClipを共有しようとか考えていたのですが、モバイルでのランタイム共有においてUnityAssetの共有がかなりネックになってしまったため独自のモーション機構から共有を試みようと思ったのがきっかけでした
基本的にはモーションのあるフレームに必要なボーン情報などをシリアル化可能な状態でまとめ、配列化して共有すればいいのですがそのまとめる工数をあまり取りたくなかったのです
ざっと実装を見た感じ、シリアル化とランタイム実行を考慮したすごく都合のいい状態でまとめてくれていたこれを採用しました

システム

f:id:myudon:20180715224748p:plain

システムは上図のようになっています、とりあえず共有ができればよかったためクライアント以外の部分はかなり簡易的な作りになっています

  1. モーキャプサイドが専用のシーンをエディタで叩いて、モーションキャプチャからモーションデータを生成(別にランタイムでもいいです)

  2. サーバーに転送しユーザー情報やモーション情報と紐づけて保存

  3. スマホ側がサーバーにリクエストして指定のモーションをダウンロード

  4. ローカルに保存しておいて任意で再生

ざっくり言うと上記のフローです
クライアントサイドとしてUnityでモーションキャプチャやモーションのダウンロードを行い、AWS上にサーバーとクラウドストレージ、RDBを用意してモーションデータや関連情報の管理をしています

実装

それでは本題であるUnityとEasyMotionRecorderでのモーションデータの共有に入ります
使用ケースで実装形式が変わるため共有のために行っている処理を抜粋して簡単に説明します
まずモーションデータの生成です、今回はモーションキャプチャーの機器としてPerception Neuronを使用しました
EasyMotionRecorderを使用することでかなり簡単にGUIをいじって生成することができます、詳しくは公式ドキュメントを読んでいただくのが早いのでここでは説明を省略します

f:id:myudon:20180716014034p:plain

Recordを終了させると_posesにHumanoidのモーションデータが記録されるため、このデータを使っていきます


次にモーションデータをシリアル化します、ここではDuoのモーションデータ形式をほぼそのまま使用しています
EasyMotionRecorderではHumanPosesという形式のデータで保存しており、中身はアニメーションフレーム単位でのモーション情報です
Vector3とQuaternionだけシリアル化の関係でfloatに分解しています、最近ではcsv吐き出しの機構をduoが追加したっぽいのでそちらを使うのでもよいでしょう
また、シリアライズに関してはシンプルな方法で記述しています、使用する開発環境やコストに合わせてシリアライズ形式は好きなものを選択してください

[System.Serializable]
public class SerializableMotionData
{
    // DB管理しない付随するモーション情報があればつける
    public string hoge;

    // duoのHumanPosesに含まれるモーション情報
    public List<HumanoidPoses.SerializeHumanoidPose> poses;

    public SerializableMotionData(string hoge, List<HumanoidPoses.SerializeHumanoidPose> poses)
    {
        this.hoge = hoge;
        this.poses = poses;
    }
}
// シリアライズの簡易例

    /// <summary>
    /// バイナリシリアライズ
    /// </summary>
    public static byte[] SerializeMotionData(SerializableMotionData obj)
    {
        MemoryStream stream = new MemoryStream();
        BinaryFormatter bf = new BinaryFormatter();

        bf.Serialize(stream, obj);
        return stream.ToArray();
    }

    /// <summary>
    /// バイナリデシリアライズ
    /// </summary>
    public static SerializableMotionData DeserializeMotionData(byte[] bytes)
    {
        MemoryStream stream = new MemoryStream(bytes);
        BinaryFormatter bf = new BinaryFormatter();
        var obj = bf.Deserialize(stream) as SerializableMotionData;
        return obj;
    }

そして投稿です、UnityWebRequestでいいんですが、今回は都合でObservableWWWを使用しています

// 任意のレスポンスを定義してやり取り
    public static async Task<TResponse> PostMotionData(byte[] poses)
    {
        var endpoint = "";
        var form = new WWWForm();

        // ここで送信するモーション情報を定義

        // モーションデータのバイナリ
        form.AddBinaryData("poses", poses);

        // using UniRx
        var www = await ObservableWWW.Post(endpoint, form);
        var response = JsonUtility.FromJson<TResponse>(www);

        return response;
    }

次に共有するモバイル側の実装です、今回使用したケースでは一回モーションの関連情報をサーバーに問い合わせてモーションをダウンロードするんですが、そこは省略し直接モーションデータをURLからダウンロードする部分を記述します
投稿同様にObservableWWWでシンプルに記述しています

// URL指定してDL
    public static async Task<SerializableMotionData> GetMotionData(string url)
    {
        // using UniRx
        var www = await ObservableWWW.GetAndGetBytes(url);
        var motion = DeserializeMotionData(www);

        return motion;
    }

モーションデータをデシリアライズし、モーションプレイヤーに流し込みます
MotionDataPlayerという再生機構は用意されているので、適当にデータを紐づけておいてそこに代入する方式をとりました

f:id:myudon:20180716024006p:plain

   // HumanPosesに対して追加
  public void SetPoses(List<SerializeHumanoidPose> poses)
    {
        Poses = poses;
    }
    // MotionDataPlayerに対して追加
    public void SetMotion(List<HumanoidPoses.SerializeHumanoidPose> poses)
    {
        _recordedMotionData.SetPoses(poses);
    }

後はMotionDataPlayerで再生してやればモバイルのランタイムでモーションを見ることができます
フレーム単位でポーズをいじってるだけなのでその辺をいじる機構を作れば色々再生周りの機構も作れるでしょう


AR100Projects

最後に自分が行ってる活動の宣伝をば
この機構の開発の発端は、イワケンこと同期の岩崎謙太(@tanaka_lit)とともに行っている「AR100-Projects」という施策の下で開発中のプロダクトです
このプロジェクトはイワケンとともにARで世界にインパクトを与える、プロダクトを量産するという思想の下に、時代がAR本格化する数年後に向けてARプロダクトを100個作ろうというとてもクリエイティブなものです
ARやVRについて圧倒的な熱意を持っているイワケンやプロダクトに協力してくれる様々な人たちとともに、ARでできることを考えつつ形にしていっています
まだまだ発展途中ですが、自分も技術者としてというよりも、クリエイターとして自分を高めていくためにこのプロジェクトを盛り上げて成功させようと考えています
開発したプロダクトは勿論、ここで得た技術知見の共有などもブログで積極的に行っていく予定なので応援していただけると幸いです