テラシュールブログ

旧テラシュールウェアブログUnity記事。主にUnityのTipsやAR・VR、ニコニコ動画についてのメモを残します。

【Unity】Enumeratorを使用して、List<string>から文章を一つずつ取り出す

f:id:tsubaki_t1:20181105001137g:plain

今回はEnumeratorを使用して文章を一つずつ取り出していきます。
余りに使わない技術なのでよく忘れるのですが、その度に思い出すのも面倒なのでココにメモしておきます。

 

 

リストから要素を取り出す

配列やリスト等の要素を取り出すのは非常に単純で、配列(Array)に添え字(Index)を渡してやれば良いです。

なので”文字列のリストから要素を順次取り出す”という観点で言えば、下のようなコードでも十分に問題ありません。

messageBox.text = messages[current];で現在の文字列を取り出し、current = Mathf.Min(current + 1, messages.Count - 1);で添え字を文字列の長さ以上にならないように判定しつつ更新…という感じです。

gist.github.com

 そうだ、Enumeratorを使おう

大体の場合は上記の方法を使うのですが、時々思いつきます。そうだEnumeratorを使用して要素を取得しようと。

まずprivate List<string>.Enumerator enumeratorを使います。IEnumerator enumeratorではありません。これで毎回キャストするのを回避出来ます。

あとはmessages.GetEnumerator()でListからEnumeratorを取得し、enumerator.MoveNextで要素を一つずつズラしていきます。

 

gist.github.com

使えるのはList

なお、この方法でサクッと取得できるのはListのみです。List<string>.Enumeratoを使用しているのもあります。やり方は有るのかもしれないですが、知りません。

UnityでInspectorに表示する目的だと、ArrayもListも挙動が似たり寄ったりなので差し替えるのは簡単ですが、Inspector以外で使用する場合は色々と考えて選択されていることも多いので注意です。

 

感想

何故か定期的に使いたくなるけど、毎回忘れてるのでメモ。

 

関連

tsubakit1.hateblo.jp

【Unity】ECSでシーンをロードする様にEntity群をロードする

今回はECSにおけるEntityのセーブ及びロードについてです。
つまりGameObjectをSceneやPrefabに固めるように、Entity(Entity & ComponentData)をファイルとして書き出します。

なおUnity 2019辺りのタイミングでちゃんとしたエディターサポートが始まるので、多分今回紹介する内容は良くてローレベルAPI、悪くて産廃となりそうです。ただまぁ、一応調べたのでメモとして残しておきます。

Entityをファイルに保存

現状、Entityの生成はスクリプトから行われます。
ただ昨今の常識的なゲーム開発環境では、ゲーム内オブジェクトの構築を全てスクリプトで制御するのではなく、ある程度メタファイルに情報を書き込んでロードする形を取ります。UnityのGameObjectやScene、Prefab等がコレに該当しますが、残念ながらECSはエディターレベルでのサポートがまだないので、スクリプト全フリとなるのは仕方のない事です。 とはいえ、単一種類のEntityをランダムに配置するもの(ECSでよく見る!)なら兎も角、ちゃんとしたゲームにしたいならばゲーム内に任意のEntityを配置し、それをゲーム実行時にロードするといった事は欲しい所でしょう。

なので今回はECSで配置したEntityの情報を記録(セーブ)しておき、ロードするといった方法を紹介します。

なお幾つか制限があります。

  • Pure ECS向けの機能です。
  • GameObjectEntityをシーンに配置した(つまりEntityにTransform等への参照が有る)物には使えません。
  • GameObjectEntityをEntityManager.Instantiateしたものには使えます。

実装

Entityの生成とセーブ

まずはEntityの生成と保存をやってみます。

生成は良いでしょう。何時ものランダム生成です。問題は保存の方で、SerializeUtilityHybrid.Serializeを使用します。 最初の引数にはEntityManagerを指定しますが、この中にハイブリットな(Componentを含む)Entityが存在すると失敗します 。場合によっては処理用のWorldにEntityを移してからファイルに書き込む等の工夫が必要かもしれません。もしくはPreBuildProcessでビルドする際に色々とやるか。

gist.github.com

これを実行すると、下のようにSpawnObjectにセットしたオブジェクトが大量に生成されるので、Saveを呼び出してファイルに書き出します。

ここで重要なのは、 Saveを押したタイミングで生成されるSharedComponentsオブジェクト です。このオブジェクトをProjectビューへドラッグ&ドロップしてPrefab化します。
意外と知られていない事ですが、ゲームの再生中であってもオブジェクトをドラッグ&ドロップすればPrefab化出来ます。Prefabの上書きで「ゲーム再生中の変更をなかったコトにしない」為のテクニックの一つです。

f:id:tsubaki_t1:20181102233325j:plain f:id:tsubaki_t1:20181102233608g:plain

SharedComponentsオブジェクトの中身は保存した時に利用していたSharedComponentDataの一覧です。SharedComponentsオブジェクトは登録されているコンポーネントの並び順が非常に重要なので、保存するたびにPrefabはちゃんと更新しておくことをお勧めします。

f:id:tsubaki_t1:20181102234639j:plain

セーブしたEntityをロードする

次にファイルに書き出したEntityをロードしてみます。

EntityのロードはSerializeUtilityHybrid.Deserializeを利用します。三番目の引数のsharedDataには、セーブのタイミングでPrefab化したSharedComponentsオブジェクトを指定します。

なおロードできるのは空のWorldのみなので、空のWorldを一旦生成した後MoveEntitiesFrom異世界転移してもらう形を取ります。転移すればWorldは基本的に空になるので、一旦Worldを作成した後は使い回すのが良いです。

gist.github.com

f:id:tsubaki_t1:20181102235120g:plain

なおEntityは同期的に生成されます。

f:id:tsubaki_t1:20181103000648j:plain

セーブしたEntityを非同期でロードする

上でも書いたとおりSerializeUtilityHybrid.DeserializeのEntityのロードは同期的です。せっかく別のWorldにEntityを生成するので、今度は非同期でロード出来るように変更します。

まず最初にSerializeUtilityHybrid.DeserializeSharedComponentsでsharedDataに登録されているSharedComponentDataをEntityManagerに登録しておきます。
次にEntityをロードする部分ですが、SerializeUtility.DeserializeWorldSerializeUtilityHybrid.ReleaseSharedComponentsentityManager.BeginExclusiveEntityTransaction()で取得したExclusiveEntityTransactionを使用してる辺りから察せられる通り、非同期でも動かせます。

あとは 【Unity】ECSのEntityの作成やComponentDataの追加等の操作を別スレッドから実行する - テラシュールブログ でやったような事と同じで、非同期で別WorldにEntityを作り、最後にメインのWorldに同期してやれば、殆ど非同期でロードが完了です。

gist.github.com

f:id:tsubaki_t1:20181103001615j:plain

感想

Entityのロードでした。
今回はランダムなオブジェクトを生成しましたが、実際にはGameObject&ComponentをEntity&ComponentDataに変換して保存…とすれば、かなり良い感じに動作するのではないかなと思います。コレとか正にそのノリです

www.youtube.com

ただEntityのエディターワークフローがもう少しマシになれば多分APIも追加されるでしょうから、今回の内容は「繋ぎ」程度に考えたほうが良さそうな気がします。

追記:こちらの機能でワークフロー対応したSubSceneが追加されました。

tsubakit1.hateblo.jp

関連

非同期でEntityの生成

tsubakit1.hateblo.jp

Worldという概念について

tsubakit1.hateblo.jp

ハイブリットECS

tsubakit1.hateblo.jp

【Unity】3Dアクションの連続攻撃(コンボ)で学ぶAnimatorControllerのtips

今回はボタンをタイミング良く押すと連続攻撃になる動作を作成します。
格ゲーのようなコマンド入力ではなく、3Dアクションによくあるような非常に単純なもの です。

 

 

ボタン連打でコンボ攻撃

3Dゲームなどで「ボタンを連打したら連続攻撃」という機能をよく見ます。
今回はコンボ表現の単純な紹介をしようと思いましたが、コレが思ったより重要な要素の塊だったので、解説しながら内容を紹介していきます。

なお格ゲーが作りたいならPlayableAPIで再生するアニメーションを選択する部分を自分で拡張するがお勧めです。

f:id:tsubaki_t1:20181102003515g:plain

今回の動作を作るにあたり、使用したコードは「Animatorのトリガーを呼ぶ」くらいで、殆どスクリプトを記述せず実現出来ました。

 

1.  ボタンを押したら殴る

最初のステップでは、ボタンを押したら(Attackトリガーが有効になったら)殴るモーションに入らせます。

  1. [アイドル] と [パンチ] のモーションのトランジション(遷移)を設定
  2. [アイドル] → [パンチ]のコンディションにAttackトリガーを設定
  3. [パンチ] → [アイドル] のコンディションはHasExit(終了時間あり)を設定

これで実行時にAttackトリガーを押せば[パンチ]と[アイドル]の間を行ったり来たりします。

f:id:tsubaki_t1:20181101214104j:plain

ポイントは[アイドル]→[パンチ]のHasExit(終了時間あり)を外す事です。
アイドルや走行といった他のアニメーションに即時切り替わることが期待されているモーションは、HasExitを外さないとモッサリします

 

2.  ボタンを押したら次の攻撃への実現

単発ではなく、順番に攻撃アニメーションを行うように修正を行います。

上ではアニメーション終了時に即座に戻っていましたが、今回はトリガーが押されたら次のアニメーションへとなるようにAnimatorControllerを修正します。

f:id:tsubaki_t1:20181101212637g:plain

考え方は簡単で、攻撃モーションをトリガーで繋いでいくだけです。全ての遷移にはAttackのトリガーによる切り替えを設定しておきます。

f:id:tsubaki_t1:20181101213358j:plain

ここでのポイントはトリガーはトランジションを通ると即座にOFFになる事です。複数のステートの巡回を行いつつちゃんとアニメーション終了待ちさせるといった点では、トリガーは非常に有用です。
ただし「トランジションを通らなければOFFにならない(スクリプトでOFFにする必要がある)」為、使いすぎるとトリガーのリークが起こります。

 

3.  攻撃の入力待ち時間の調整

Attackトリガーの入力待ち受け時間とモーションの繋ぎを調整します。

f:id:tsubaki_t1:20181101220236j:plain

まず注目すべきはオレンジで囲った「終了時間」の部分です。この部分がAttackトリガーの入力を受け付ける時間です。初期値では0.5となっているので、これを出来る限り後ろの方(せめて大体の攻撃モーションが終了して収束する0.8割くらいの位置)に移動します。

f:id:tsubaki_t1:20181101221519g:plain

ここで重要な考えはトリガーの判定は終了待ちで指定したタイミングを「通過した瞬間」に行われるという点です。トリガーが通過すると次に判定するのは次の周の終了待ちのタイミングなので、終了待ちを早めに設定すると簡単にタイミングを逃します。

終了待ちを行わないという選択肢もありますが、その場合「攻撃モーションをキャンセルして次の攻撃モーションが始まる」可能性があります。

 

4.  モーションの繋ぎの調整

入力待ち時間の設定が終わったらモーションの切り替えにかかる時間を調整します。
ここでのポイントは遷移間隔を短くすることです。ここが長いとモーションのブレンド時間が伸びてモーションがボンヤリします。
遷移時間を短くすることで発生する急激な軸足の変化は、オフセットをズラして調整します。

f:id:tsubaki_t1:20181101222358g:plain

なお遷移間隔とオフセットでズレた位置が次のステートの開始点です。
つまり遷移時間が0.25(初期値)で次のステートの終了待ちが0.5(初期値)の場合、0.25~0.5までにトリガーを押さないとタイミングを逃します。

5.  ボタンが押されなかったらキャンセルする

タイミングよくボタンを押したら攻撃は出来ました。次はタイミングを逃してボタンを押さなかった場合にモーションをキャンセルしてアイドル状態に戻す挙動を作ります。

f:id:tsubaki_t1:20181101230043g:plain

やり方は[パンチ]や[フック]といったモーションからアイドルへ「終了時間あり」つきでトランジションを作ります。
この時、必ず終了時間を[パンチ]→[フック]への終了待ち時間より後に設定します。
これでモーション完了時にボタンが押されていなければ[アイドル]へ戻るようになります。

f:id:tsubaki_t1:20181101223854j:plain

なお、どうしても[パンチ]→[フック]の終了時間よりも[パンチ]→[アイドル]の終了時間が前に来る(必ず先に[パンチ]→[アイドル]の遷移が始まってしまう)という場合は、中断要因を使用して強引にフックモーションを実行するという力技もあります。
(これは理解できない挙動の要因になりえるので、余り使わない方が良いと思います)

f:id:tsubaki_t1:20181101225821j:plain

 

6.  ボタンで攻撃を分岐させる

コンボ中に押すボタンによって攻撃を切り替えます。

f:id:tsubaki_t1:20181101232745g:plain

ステートマシンの途中で分岐が発生しますが、この分岐はInt型のパラメーターをもたせる事で解決します。このInt型のパラメーター(AttackType)をボタンと一致させれば、ボタンで挙動が変わるコンボが的なのを実現出来ます。

f:id:tsubaki_t1:20181101232331j:plain

ボタンに応じたトリガーという手もありますが、トリガーのリーク(トリガーが開放されず押した状態が保持されてしまう状態的な)が発生するので、パラメーターで分岐させるほうが好みです。

今回はAttackTypeのパラメーターで分岐しましたが、実際には「移動中か?」や「HPの割合」等をコンディションに渡して再生するアニメーションを切り替えるのも良いかもしれません。

 

7.   ステートマシンを整理する

さて分岐を追加したことでステートマシンが割と大変な感じになってきました。最後にサブステートを使用してステートマシンを整理しておきます。

最初のポイントは、ステートマシンはサブステートに入るとEntryから始まり、Exitで出るとサブステートから遷移するという点です。

  1. サブステートマシン[アタック]を作成
  2. [アタック]と[アイドル]のトランジションを作成。[アイドル]→[アタック]のコンディションはAttack
  3. [アタック]へ攻撃系のモーションを全て移動
  4. 攻撃系モーションの遷移先をExitへ変更
    (終了時間は1に設定)

ダイジェストすると、下のような感じです。

https://user-images.githubusercontent.com/1644563/47859170-0c8b0000-de31-11e8-9fb6-ef052c2a174c.gif

f:id:tsubaki_t1:20181101234413j:plain

まだ攻撃処理が分岐する部分(ハイキックとアッパー)がゴチャゴチャしそうなので、少し過剰ですがもう少し整理しておきます。

分岐する攻撃をサブステート[アタック(分岐)]へ移動します。
[フック]→[アタック(分岐)]へのトランジションは「Attackトリガー」だけで良いです。判断はサブステートマシン側で行います。

f:id:tsubaki_t1:20181102000524j:plain

サブステート側では、Entryから[アッパー]と[キック]へのトランジションを設定します。その際、分岐はAttackTypeのみです。

あとはアニメーション終了時にExitに持っていけば、親サブステート[アタック]のExitへ到達し、最終的に[アイドル]へ戻ります。

f:id:tsubaki_t1:20181102000822j:plain

f:id:tsubaki_t1:20181102001207g:plain

関連

blogs.unity3d.com

www.slideshare.net

AnimatorControllerの整理系

tsubakit1.hateblo.jp終了待ちキャンセル

tsubakit1.hateblo.jp

攻撃モーションが「他の全ての攻撃モーションに遷移する」場合にステートがスパゲティ化するのを何とかするアプローチ

tsubakit1.hateblo.jp

【Unity】C# Job Systemからノイズを使う

前回に引き続き、今回はノイズを使用してみます。

ノイズを使用する

下のような感じの動きを作ってみます。

f:id:tsubaki_t1:20181030205634g:plain

Mathfは多分メインスレッドでしか動作しませんが、Mathematicsだと別スレッドでも問題なく動作します。
またRandomと異なりStatic関数(状態を持たない)なので、そんなに面倒なことをしなくてもノイズが実現出来ました。

コード

gist.github.com