【Unity】複雑なアニメーション遷移を制御するAnimatorの作り方

 

昔こんな記事を書きました。

 

yutakaseda3216.hatenablog.com

 

さらに、最近Gotanda.unity - connpass という集まりでもAnimatorについてのLTを行いました。

 

ここらへんで、皆一度は悩むであろうAnimatorのそれなりにマシな作り方、設計の仕方をまとめていこうと思った次第です。

 

Animatorの矢印が多くて困っている、どういう指標で作ればわからない

という方への手助けとなれば幸いです

 

既知の手法でAnimatorを作った際の問題点

 

まず、既知の手法でAnimatorを作るとこういった形になると思います

 

https://docs.unity3d.com/jp/540/uploads/Main/MecanimAnimatorControllerWindow.png

 

1つ1つAnimatorのステートを作り、それらをトランジションで繋ぐ作り方です

 

トランジションの制御にはそれぞれboolやTriggerといったパラメータを用意し、スクリプトでAnimator.SetBool()などを呼び出す...

 

 

ありがちですね

 

UIなどに使用するシンプルなアニメーションであれば、上記手法でも良いかと思います

 

しかし、キャラクターの制御などにこれをそのまま当てはめてしまうとこのようになっていくと思います

 

https://forum.unity.com/attachments/all-animations-small-jpg.67588/

 

これは極端な例ですが、矢印とパラメータが量産され視認性が悪くなっていきます

 

視認性の悪さを改善できる、サブステートマシンという機能が存在します

 

https://gametukurikata.com/wp-content/uploads/img/substate2.png

 

サブステートマシンとは、複数のステートをまとめておくことのできる機能です

 

詳細な説明はこちらの方が詳しく載っていますので割愛します

tsubakit1.hateblo.jp

 

サブステートマシンは良いグルーピングの手法ではありますが、この手法ではパラメータが量産されることを防止できません

 

また、グルーピングされた結果サブステートマシンの中の特定のステートから、他のサブステートマシンの特定のステートへ直接遷移する

などの手法はトランジションでは取りづらくなります

 

つまるところ、根本的な解決にはなっていません

 

 

必要な要件について

 

現在開発している3Dアクションゲーム、SLEEPWALKERでは

  • 大量にあるステートの全てが自由に遷移を組むことができること
  • ステートの追加、組み替えが容易であること
  • 時間に応じたモーションブレンディング設定
  • 時間に応じたエフェクト設定、etc

が必要でした

いろいろと試行錯誤し、このような非常にスムースな遷移を実現できました

 

youtu.be

 

 

具体的な設計、実装について

Animator部分

まず、Animator自体はこのようになっています

 

f:id:yutakaseda3216:20171219123349p:plain

 

トランジション、パラメータは一切ありません

デフォルトのステートはIdle(待機)モーションになるように設定してあり、AnyStateやExitなどの機能も使用していません

 

遷移は全てAnimator.CrossFadeInFixedTime()を使用しています

これについてはこちらをご覧ください

 

 

yutakaseda3216.hatenablog.com

 

 

遷移の方法について

遷移はStateMachineBehaviourを継承したスクリプトを全てのステートに付け、制御を行っています

 

docs.unity3d.com

 

StateMachineBehaviourを継承することで、現在のステートのNormalizedTime(アニメーション経過時間)を取ることができるため

 

経過時間に合わせた処理を行うことができるようになります

 

まず基底クラスを作成し、NormalizedTimeなどを取得しやすくしました

かなりレガシーコードです、整理もしてません。Rxを使用しているのと、TODOとか書いてあるので、設計自体はあまり参考にしないでください

 

gist.github.com

 

ResetTime()メソッドは、NormalizedTimeはステートにいる限り加算され続ける仕様のため、ループ設定をした際にNormalizedTimeが0.0f~1.0fで見れるようにtimeを初期化する処理です

 

気をつけないとループするアニメーションの際に設定したエフェクトなどが再生されなくなるので気をつけましょう

 

 

この基底を使いこのような設定を行うことのできるスクリプトができました

 

トランジション設定スクリプト

f:id:yutakaseda3216:20171219124202p:plain

 

遷移が可能なステートのenumを定義し、遷移優先度、遷移可能時間などを設定しています

 

ソースコードもそのまま乗せておきます、こちらもかなりレガシーなコードなのであまり参考にはせず、ニュアンスだけの理解にとどめてもらえると嬉しいです

 

gist.github.com

 

今見てもシングルトン経由したりしてやべえコードだ・・・

 

上記のようにAnimatorでアニメーション時間に関連する処理を書いていますが

AnimatorのStateと連動したStateMachineを作成し、Animatorの外側でゲームに関連する処理を書いています

 

gist.github.com

 

アニメーション時間に応じたその他の処理

 

攻撃判定発生スクリプト

f:id:yutakaseda3216:20171219124243p:plain

 

こちらも時間軸で動作を定義しています

 

SE再生スクリプト

f:id:yutakaseda3216:20171219124314p:plain

 

ループ設定などもできるようにしてあります 

 

 

まとめ

というわけで、トランジション設定をStateMachineBehaviourのスクリプトに寄せ、アニメーション時間が必要な処理もまとめてぶちこんでいます

 

 

トランジション設定を作っていくのに比べて

ので視認性、拡張性に優れます

 

また、Unity2017で追加されたPlayableを使用した完全スクリプトベースの手法も存在しますが

  • Animatorでステートの制御をぽちぽち作れること
  • ステートをビジュアル化しておけること
  • コード量の少なさ

で優位です

 

もちろん適材適所ですが、1つの指標になれば幸いです

 

 

宣伝

現在開発中のゲーム、SLEEPWALKERの体験版がweb公開されました!

よろしければぜひプレイしてみてください

 

https://www.studiophantomisland.com/

【Unity】OnPostProcessAllAssetsを使う時に特定の方法で無限ループする

 

OnPostProcessAllAssetsについて

docs.unity3d.com

 

このクラスを継承したEditorクラスは、OnPostProcessAllAssetsメソッドを定義することでアセットインポート時に処理をフックすることができる。

 

ScriptableObjectやなんやに自動登録したり、asset判別して何かする時によく使う。

 

無限ループする原因

 

  1. OnPostProcessAllAssetsを定義したクラスが2つ以上ある
  2. そのどちらも、処理の中でAssetDataBase.StartAssetEditing()とAssetDataBase.StopAssetEditing()を呼んでいる。
  3. どちらもAssetDataBase.Refresh()を呼んでいる。

 

以上です。

 

docs.unity3d.com

 

 

アセットインポート時に、さらにアセットをインポートする処理を書いてしまうとOnPostProcessAllAssetsをお互いに呼び合ってしまうので無限ループするっぽいです。

 

 

回避方法

 

  1. OnPostProcessAllAssetsは引数に変更されたアセットのパスが入っているので、パスを使用し処理を行うべきか否かを判別する
  2. OnPostProcessAllAssetsを受け取るルートクラスを作成し、個別に受け取らない

 

 

最後に

 

回避はこれで可能、原因も多分あってる。

ただどうにも釈然としないので、何かピンと来た方Twitterまでぜひご連絡ください。

 

【Unity】Timelineで使用するPlayableAssetとPlayableBehaviourを参照を追加しながら作成するエディタ拡張

 

リポジトリ

github.com

 

ここにプロジェクトとUnitypackageがあがってるのでダウンロードなりcloneなりをどうぞ

 

 

使い方

 

メニューに表示されているPhantomIsland -> Windowからエディタを開いてください。

 

 

設定できる項目は

 

  • 使用するnamespace
  • 変数の型、変数名、参照タイプ

です

 

参照タイプはシーン上のオブジェクトはExposedReferenceを選択してください。

大体ExposedReferenceだと思います、多分・・・。

 

CreateScriptボタンを押すと、保存先と名前を指定するウィンドウが出るので設定して保存して下さい。

 

保存した名前の末尾に~Assetと~Behaviourという名前が付くので、~Playableという名前がよいかも。

 

保存すると、設定した参照がすべて記入されており、PlayableAssetはいじる必要がなく、基本的にPlayableBehaviourにロジックを記述するだけでよくなっています。

 

最後に

 

まだまだTimelineは進化しそうなので、情報を追っていきますぞ。

【C#】NSubstituteを使用してクラスをモックとして偽装する

 

NSubstituteとは?

 

定義されたinterfaceを使用して、クラスの振る舞いをモックとして定義することが出来るライブラリ

 

interfaceに定義されているフィールドやメソッドの返す値や動作を一つずつ定義できるので、テストしたいクラスに渡すデータなどの偽装が可能

 

とにかく非常に便利で、使いやすく有用

 

入手はこちら

github.com

 

 

Unityで使用する際はUnity Test Tools を入手して、Pluginsの中にdllを入れると動作する

 

https://www.assetstore.unity3d.com/jp/#!/content/13802

 

基本的に実機動作はしません、UnityならEditor上のみで使用するようにしてください 

 

簡単な使い方

 

クラスとフィールド、メソッドの動作を偽装した例

 

gist.github.com

 

 

割と簡単に定義できる

 

 

ちょっとした小技

 

メソッドの中で自身のフィールドを変更したい時などは、一度データをキャッシュするとよい

 

gist.github.com

 

直接フィールドを書き換えることはできないので、気をつけよう

 

 

ある特定の機能だけ動作させたいけどデータがないよ〜という時はガンガン使っていこう

ブログ名とかを変えました。

 

 

ブログ名とか、中のデザインを一部変えました。

 

開発とかメモ -> かせの開発とかメモ。 -> かせノート。 <- New!

 

書くことはほとんど変わらないと思いますので、今後ともご愛読くだされば幸いでございます。

 

よろしくお願いします。

【プログラミング】MVC,MVPを理解するためのレイヤーアーキテクチャ

 

少しだけ長いので、根気を持ってお読みください。

 

レイヤーアーキテクチャについて

 

言葉で表現すると

  • 個々のオブジェクトの設計に依存しない、概念的な部分の設計思想
  • 各機能の「置き場所」の定め方
  • あるいは、各機能の依存先を決める手段
  • 責務を明確化する思想

 

この記事を読んでいるということは、MVPやMVCについては聞いたことがある、実践したことがある人は多そう

前提知識としてあると理解しやすい、かも

 

 

詳しく説明してくれている記事がありました

 

qiita.com

 

引用

"レイヤアーキテクチャというのはアプリケーションを責務に応じたいくつかの層としてとらえる設計手法のことです。"

 

表面的にはまさにこの通りですが、層という概念に当てはめてしまうのはいささか語弊があります

 

この記事のMVCの部分はすっ飛ばして(失礼ではありますが)レイヤーアーキテクチャの部分だけを読んでいただけると良いと思います

 

 

今回伝えたいことは、プレゼンテーション層、ドメイン層などで固定化されたレイヤーの話ではなく、必要な責務を考え、自身のアプリケーションに応じて必要なレイヤーを考え出すことをレイヤーアーキテクチャの知識として定義したいという話です

 

 

アーキテクチャの歴史

 

レイヤーアーキテクチャの理解のために、過去のアーキテクチャの歴史をまず振り返ってみます

 

オブジェクトアーキテクチャの歴史

 

いわゆる、個々の機能の設計です、大まかに3つの進化がありました

 

goto statement programming

goto文 - Wikipedia

 

プログラムの要所要所にラベルを定義し、goto文を使用して指定したラベルに飛ぶことで、ロジックを全て縦に記述しながらも強引にモジュール化を図るプログラミング手法です

 

あっちこっちに処理が飛び、人間の脳内キャッシュを膨大に使用するので今見ると非常にスパゲッティコードに見えるでしょう

 

www.linuxdigest.org

 

なぜgoto文が悪であるのかはこちらの記事を読むとわかりやすいと思います

 

 

 structured programming

構造化プログラミング - Wikipedia

 

構造化プログラミングのことです、かなり端折った説明をしますので本来の意味合いを知りたいのであれば書籍などを購入すると良いと思います

 

構造化プログラミングはgoto-lessとも呼ばれ、ロジックを上流(状態)、下流(処理)に分けることで上流の状態に応じた下流の処理を呼び出すという概念が生まれました

 

しかし、処理を分割すればするほど上流の状態が増えることになり、完全なロジックの分離はほとんど不可能でした

 

しかし、goto statementにはなかった、処理の記述に流れを作るという点において、近代のプログラミング思想の根底に関わっています

 

object oriented programming

オブジェクト指向プログラミング - Wikipedia

 

皆大好きオブジェクト指向です、もはや呪いと言っても過言ではなさそうです

 

カプセル化ポリモーフィズムなど様々な概念が存在しますが

 

データとロジックを一つのまとまりとして記述し、処理の関心を内側にのみ向け、処理に応じたメッセージを発行することで、他のオブジェクトの処理のトリガーとして影響を与えつつ、その影響に関心を持たない

 

という概念になるかと思います

 

これを端的に表したものがオブジェクトという言葉であり、またクラスという機能として表現されているものです

 

この概念を用いることで、structured programmingだけではなしえなかったロジックの完全な分離が可能になりました

 

 

オブジェクトアーキテクチャのまとめ

goto statement programmingで処理を記述するという行為を行い

structured programmingで処理の流れの概念ができ

object oriented programmingでロジックの分離を行った

 

 

しかし、これでは処理の置き場所はオブジェクトで定義できましたが、オブジェクトの置き場所が定義できていません

ここからはオブジェクトのまとまりをレイヤーとして定義するアーキテクチャについてです

 

 

多層アーキテクチャ

多層アーキテクチャ - Wikipedia

 

近代的な設計思想は多数ありますが、そこから多層アーキテクチャを抜粋します

 

 

独立したモジュールとして層を定義し、差し替え、機能改善などを容易にすることが可能な設計思想です

 

代表的なものとして三層アーキテクチャが存在します

 

 

三層アーキテクチャは、Webアプリケーションが

  • データベース
  • ミドルウェア(インターフェース)
  • ユーザー(ユーザー操作)

という要素で構成されるようになった際に提唱された概念です

 

 

データ、ロジック、プログラミング的な構成要素としては

の三種類が必要になります

 

データベース

 

データを保持、管理します

 

ビジネスロジック

 

ユーザー操作に応じた処理のロジック、及びデータベースの操作を記述し公開します

 

ユーザーインターフェース

 

ユーザーの操作を検知し、操作に応じた処理をビジネスロジックに問い合わせます

(APIを叩くということと同義です)

 

ビジネスロジックの結果を使用して自身を更新します

 

 

以上のように、各レイヤーに対して明確に責務が割り当てられており

また、依存関係が単方向になっています

 

ユーザーインターフェース -> ビジネスロジック

ビジネスロジック -> データベース

 

データベースは自身以外を知りませんし、ビジネスロジックはデータベース以外知りません

ユーザーインターフェースビジネスロジック以外知りません

 

 

単方向ですね

 

責務を明確化すると、その責務を果たすために必要な情報を持つレイヤーに対してのみ依存する形になるため、結果として関連するロジックを持つオブジェクトの凝集度が上がり、依存関係を簡潔に定義することができます

 

 

レイヤーアーキテクチャのおさらい

 

  • 個々のオブジェクトの設計に依存しない、概念的な部分の設計思想
  • 各機能の「置き場所」の定め方
  • あるいは、各機能の依存先を決める手段
  • 責務を明確化する思想

 

です

重要な部分は責務の明確化です

 

 

MVC,MVPとは一体なんなのか?

 

Model - View - Controller

 

Model - View - Presenter

 

さらにはMVVM,CleanArchitectureまで様々な概念が存在します

 

これらはすべて、レイヤーアーキテクチャに基づいた責務の割り当て方の一つの形です

 

一般的に普及していると思われるMVCの例

 

Modelの責務

  • データを管理する
  • データを更新するためのロジックを公開する
  • (必要であればサーバーと通信する)
  • データの更新を通知する

 

Viewの責務

  • ユーザー操作を提供する
  • Modelの通知、データを元に自身を公開する

 

Controllerの責務

  • ユーザー操作を監視する
  • 操作に対応したModelの更新ロジックを呼ぶ

 

 

こういった形で責務が割り振られています

 

MVPも同じく、責務が割り振られています、MVVMもCleanArchitectureも同じです

 

 

こういった責務を捉える力を持つことで、MVCやMVPなどの既存のレイヤーに縛られない責務の洗い出し、割り振りが可能になります

 

 

例.MVCを、サーバーとの通信部分を踏まえて責務の割り振りを改善する

 

一般的なMVCの責務は前述しましたが、Modelがサーバーと通信する場合、しない場合を踏まえて、サーバーと通信するという責務を切り出し、別のレイヤーとして定義します

 

名前はServiceレイヤーとし、SMVCと定義します

 

 

Serviceの責務

  • Modelを更新するためのロジックを公開する
  • Model更新のためのデータをサーバーから取得する

 

Modelの責務

  • データを管理する
  • データを更新するためのロジックを公開する
  • データ更新の際に必要であればServiceのロジックを呼ぶ
  • データの更新を通知する

 

Viewの責務

  • ユーザー操作を提供する
  • Modelの通知、データを元に自身を公開する

 

Controllerの責務

  • ユーザー操作を監視する
  • 操作に対応したModelの更新ロジックを呼ぶ

 

 

切り出しました

 

MVCにはないメリットとして

このServiceレイヤーはサーバー側にのみ依存し、またローカル用のモックと差し替えることでローカル環境での更新も可能になりました

結果、Modelはシンプルな記述のみになったと思います

 

他にもあるかもしれません 

 

 

こういった形で設計を改善することもできます、参考になったでしょうか

 

 

まとめ

 

責務を洗い出し、責務の一つ一つをオブジェクトの集合体となるレイヤーに割り振ることを

 

レイヤーアーキテクチャ

 

として定義し、広く理解され普及すると良いなと思います

 

 

知識の部分は厳密には間違いがあるかもしれませんが、伝えたいことが伝わっていれば幸いですね

 

【Unity】TestRunnerをbatchmodeで起動した際に謎xmlが吐き出されるバグ【NUnit】

 

 

 

2017/12/22追記

Unity2017.3で治ったようです、よかった

 

 

バージョン

Unity5.6.2f1

 

 

ソース

issuetracker.unity3d.com

 

 

やりたかったこと

Jenkins上でUnityTestRunnerをbatchmodeで起動して、吐き出されたxmlをNUnitPluginで解析

CI環境で自動テストを行いたかった

 

結果

上記Issueのためできなかった

xmlの吐き出される形式がTestSuiteのため、NUnitPluginがうまくパースしてくれない

 

結果、ビルドがコケる

 

Unityさんへ

早く治して・・・