gologiusの巣

プログラミング、モデリングなどのメモです。誰かの役に立てるとうれしいです。

【Unity】Playable APIを用いてモーション遷移時に補間に考慮したいこと

※都度更新予定

モーションを補間させながら遷移させる場合、Playable APIには関数は用意されていません(Unity2017.3現在)。 よって、PlayableAPIを使用する場合、自分でコードを書かなければなりません。 まぁ今後関数が用意される可能性もありますが・・・

というわけで、PlayableAPIでモーション遷移させる場合に、 注意したほうがさそうな場合について記載します。

個人的にはモーション切り替えパターンは

  • とにかく指定したモーションにスムーズに移行してほしいパターン(棒立ち→走り始める、など)
  • 上半身が別のモーションによって上書きされるパターン(走る→走りながら撃つ、など)
  • 指定したモーションの最初のフレームから最後のフレームまで再生してほしいパターン(武器をちゃんと振ってほしい場合など)
  • 遷移前の時間を引き継いでほしいパターン(前方向に歩く→右方向に歩く場合など。)

この四つのパターンにに分けられると思っています。今回はこの四つのパターンを実装したサンプルプロジェクトを作成しました。

サンプルプロジェクト

というわけで作成したサンプルプロジェクトを置いておきます。 github.com

実体はMotionPlayerコンポーネントです。Unityちゃんにアタッチされてます。 UIからモーション再生方法をいろいろ弄れます。 ちなみに逆再生も可能。

次からモーションの切り替えパターンの説明となります。

とにかく指定したモーションにスムーズに移行してほしいパターン

切り替え時にPlayableへのinputWeightを変更しながら、モーション切り替えをします(サンプルプロジェクトのMotionMixer. crossFadeCoroutine()が該当)

・・・という基本的な考え方をこちらの記事から学びました。 tsubakit1.hateblo.jp

上半身が別のモーションによって上書きされるパターン

例えば、

layerMixer.SetLayerMaskFromAvatarMask(layerIndex=1, upperMask);

のように、Layer1に上半身のマスクを割り当てた状態で

layerMixer.SetInputWeight(layer=1, weight=1f);

のようにweight=1fにすれば上半身のモーションは上書きされますし、weight=0fにすれば、layer=0に適用されているモーションが上半身にも適用されます。

ただ、このweight切り替えの際にいきなり値を変えると、モーションが補間されすに切り替わってしまいます。 なので

private IEnumerator crossFadeLayerWeight(int layer, float duration, bool enable)
 {
     float waitTime = Time.time + duration;
     yield return new WaitWhile(() =>
     {

         float diff = waitTime - Time.time;
         float rate = Mathf.Clamp01(diff / duration);
         float weight = (enable) ? 1 - rate : rate;
         layerMixer.SetInputWeight(layer, weight);

         if (diff <= 0)
             return false;
         else
             return true;
     });
 }

のようにして値を補間させながら切り替えましょう(詳しくはMotionPlayer.setLayerEnabled( )を参照)。

※サンプルプロジェクトの場合「上半身上書き」にチェックを入れ、遷移時間のスライダを0以上にすると、遷移が見れます。

指定したモーションの最初のフレームから最後のフレームまで再生してほしいパターン

※サンプルプロジェクトの場合「完全遷移まで待機」にチェックを入れる、に該当します。

例えば武器を 1. 振り上げて 2. 振り下ろす

という二つで一つのモーションに遷移したい場合、遷移前のモーションがミックスされてしまうと、 2.振り下ろす のモーションしか再生されてないようにみえる可能性があります。

f:id:gologius:20180331223208p:plain

ですので、遷移後のモーションの一フレーム目の姿勢になるまで、再生を待ってあげる必要があります。

f:id:gologius:20180331223210p:plain

またせる処理はコルーチンを使えば大して難しくないですね。以下のように遷移先のモーション再生を停止させておき、一定時間後にモーション再生を再開します(詳しくはMotionMixer. crossFadeCoroutine()参照)

     if (waitCrossFade)
      nowPlayable.SetSpeed(0f); //遷移先のモーション時間を止めておく

      //inputWeightの変更 略

       if (waitCrossFade)
       {
           float play_speed = playParam.reverse ? -1f : 1f;
           nowPlayable.SetSpeed(play_speed); //遷移先のモーション再生時間を元に戻す
       }

遷移前の時間を引き継いでほしいパターン

※サンプルプロジェクトの場合「アニメーション時間同期」にチェックを入れる、に該当します。

例えば走るモーションが、正面、左、右の3パターンあり、すべて同じ周期で走っているとします。 この場合時間同期させながら遷移させないと、片足で移動しているように見えてしまいます。

f:id:gologius:20180331224118g:plain

なので、再生する時に、遷移前のモーションの再生中時間を

nowPlayable.SetTime(beforePlayable.GetTime());

のようにして引き継いであげる必要があります。 サンプルではMotionMixer.recconect()内に処理があります。

f:id:gologius:20180331224255g:plain

まとめ

  • Playable APIでモーション遷移する際のパターンについて書きました
  • いかんせん動画か資料がないとわかりづらい内容なので、記事を書く準備が面倒でしたね・・・
  • この記事は追記、更新しまくると思います

【Python】【Selenium】 Webサイトから自動でファイルDLする

概要

  • Webサイトにアクセス→ログイン→ボタンを押してファイルをDL

みたいな処理を自動化したい。

会社で使おうと思ったスクリプトなので、具体的なサイトは出せないのは勘弁してください。 あと特定サイトを例に挙げると、そのサイトの負荷を上げそうなのでそれも理由にあります。

環境構築

PythonSeleniumを使用します。 Pythonは環境構築が楽で、パッケージがいろいろあるのでいいですよね。

OSはWin10です。 ブラウザはクロームを使用します。

Pythonの環境

初めて環境構築する場合は、 以下のリンクからインストーラをDLして実行すると、 Python、エディタ(Spyder)など全部入ります。

初めて入れるならPython 3.X版にしましょう。 2.X版は2020年にサポートが終了します。

ちなみに私はPython2のAnacondaが入っている状態でさらにPython3版をインストールしたため、 パスがめちゃくちゃになりました。

解決方法としては、環境変数を適当に消してから再インストールすると正常に動作するようになりました。

Seleniumのインストール

コマンドプロンプト

pip install selenium

以上。

次、ここからwebdriverをDLして、適当なディレクトリに置いておきます。

パスは後で使用するので覚えておきましょう。

ちなみに、これをDLしてない or スクリプト上でパス指定が間違っている場合、以下のエラーが出ます。

WebDriverException: 'chromedriver' executable needs to be in PATH. Please see https://sites.google.com/a/chromium.org/chromedriver/home

実装

最初に書きましたが会社で使用しようとしてたので、xpathはごまかしてます。 かといって適当なサイトを例に挙げて、そのサイトの負荷上げるのも犯罪臭がするので・・・

gist.github.com

想定するシナリオは

  • Webページにアクセス
  • ダウンロードする資料の番号を入力
  • ボタンを押す
  • 別ウインドウでポップアップが出る
  • DLボタンを押す

となります。

driver.implicitly_wait(WAIT_SEC) はかなり重要です。 要素がなくても'WAIT_SEC'秒だけ待ち続けます。 これを指定することで、読み込みが完了しておらず、要素がないのでエラーになる、といった事態を避けられます。

また、driver.find_element_by_xpath()xpathを指定して要素を取得できます。 xpathの説明はこの記事が詳しいです。 上の例だと、

  • //input[@id='page'] → ページ全体(//)の中で<input id="page"></input>を取得
  • //a[@onclick='download(); → ページ全体(//)の中で<a onclick="download"></a>を取得

となります。 要素取得後はsend_keys()で値セット、click()で要素をクリックしています。 ちなみにsend_keys()は複数同時入力(Ctrl+Cなど)に対応しているので、複数形(keys)になってるみたいです。今回は使ってませんが。

ファイルのダウンロードの終了検知は、Seleniumの機能ではできなさそうでね。 ダウンロードが完了したらファイルが作成されるはずなので、ファイル名の検出などで対応する必要がありそうです。

結論

個人的にはdriver.implicitly_wait()が便利。 いちいち読み込み完了を検知するスクリプトを書かなくともよいのは便利。

あと、こんな環境が一瞬でインストールできるPythonが便利ですね。 ただ僕は型が明示的に決まる言語の方が好きです。C#とか。

  • ゲーム、アプリを作る→C#
  • 若干めんどい数学計算、機械学習、サーバー弄り系→Python

みたいな使い分けになってきてますね。

そろそろWeb系も勉強したいところ。

【Unity】一週間GameJamに参加した話

ゲーム作ったので報告しますね

概要

一週間でゲームを作るイベントです

Unity 1週間ゲームジャム | 無料ゲーム投稿サイト unityroom - Unityのゲームをアップロードして公開しよう

今回のテーマは「当てる」です

今回の目標

  • とりあえず完成させる
  • UniRxで書いてみたい
  • TimeLine使いたい

とりあえずこんなところですね。 最近は製作途中のものが多かったので、なにか完成物を作りたいと思いました。

また、UniRxは最近使い始めてなかなかいい感じに理解できてきたので、 使いたいと思っていました。 今回のソースの解説とか後日やりたいですね(やるとはいっていない)。

あとはTimeLineですね。前までいちいちAnimationClipとAnimationControlllerを作ってたのですが、 ファイル増えすぎて嫌だと思ってました(厳密には内部でAnimationClipが作成されているみたいですが)。

ゲームを考える

当たるといわれると恐らく誰もが「シューティングゲーム」を思い浮かぶでしょう。

・・・ならばその逆をいきます。自分から「当たりに」いきます。 僕の思い付きが面白いかは別にして、人とは違うオリジナルな発想というのは大事だと思うのですよ。

そしてコンセプトが決まったので

  • 誰が当たるか
  • どうやって当たるか
  • 何に当たるか
  • 当たったらどうなるか

を決める必要があります。これによってゲームの面白さがきまるといっても過言ではありません。

時間的な都合もあって

  • 誰が当たるか → 昔作った棒人間
  • どうやって当たるか → スタイリッシュなポーズで
  • 何に当たるか → メタセコイアの機能で「文字の3D化」があったのでこれで
  • 当たったらどうなるか → 音がだんだん変わっていって、もっとコンボをつなげたくなるように

になりました。

できたもの

動画は製作途中のものしかありません。

アタレ、サラバアタエラレン | 無料ゲーム投稿サイト unityroom - Unityのゲームをアップロードして公開しよう

トラブル

WebGLでビルド後、実行するとエラーで死ぬ。 ブラウザの開発者ツールからコンソールを見ると、AudioPlayableかエラー吐いていました。 おそらくTimeLineが悪いのだと推測しました。 この時点で日曜日17時50分。遅刻確定 (締め切りは日曜日20時です。過ぎてもペナルティ等はありませんが)

どうもバージョンアップで治るようです。イベントに参加する前にはアップデートしましょう(戒め)。

https://forum.unity.com/threads/timeline-animations-on-webgl-from-assetbundle-dont-play-at-all.500185/

なおこのアップデート後にLayerMixerがおかしくなりました(以前の記事参照)。

gologius.hatenadiary.com

とりあえずこのゲームには不要だったので、LayerMixerを経由しないことで対応しました。

反省

ほんとは当たったときのエフェクトとかもっと作りこみたかったです。 ヒットストップ、画面振動などなど・・・

またゲームとして、難易度アップや、敵のランダム性もなかったです。 最初はくる文字の色も変える予定だったのですが・・・。

まとめ

反省点もいろいろあります。

が、やらないより、とりあえず一つのものを作りきる、という経験は大切だと思います。

今後もこの精神でちょくちょく参加したいです。 f:id:gologius:20180301220231p:plain

【Unity】 Playable APIで上半身と下半身を別々のモーションをさせる【改良版】

※2018/03/03追記:バージョンアップでモーションが動かなくなる不具合を修正(最後に説明)

やりたいこと

前回

前回の問題は

  • RigがHumanoidの場合、挙動がおかしい
  • AnimationLayerMixerPlayable の理解が微妙
  • (記事には書いてないけど) そもそもAvaterMaskは一つでいいのでは?
    • AnimatorControllerを使う場合、AvaterMaskは上半身分のみでよい
    • 実装をもうちょいきれいにできるのでは?

でした。解決していきましょう。

実装結果

実際に実装したものがこちら(左:Humanoid、 右がGeneric)

サンプルプロジェクトはこちら

GitHub - gologius/PlayabkeMask: Playable APIでマスクをするテスト

使用方法

1「上半身分」のAvaterMaskを作成する ※前回は下半身分も作成しましたが、いらないことが判明しました。 f:id:gologius:20171120233424p:plain

f:id:gologius:20171120233300p:plain

2 Animatorがアタッチされている場所にMotionPlayerをアタッチ f:id:gologius:20180104005628p:plain

3 先ほど作成したAvaterMaskをMotionPlayerにD&Dでセットする

4 MotionPlayer.play(AnimationClip clip, int layer, bool loop)を何らかの方法で呼ぶ

  • サンプル プロジェクトでは、MotionTest内から呼び出してます
  • AnimationClipもここで指定

解説

f:id:gologius:20180104012133p:plain

  • ※この理解であっているかは不明

  • AnimationMixerPlayableはモーション切り替え時に、変更前モーションと変更後モーションをいい感じでブレンドするためのミキサー。

  • SetLayerMaskFromAvatarMask(1, upperMask)で、LayerIndexとAvaterMask(上書きする部位)を指定している
    • ※ここでいうLayerIndexはAnimationLayerMixerPlayableInputIndexと(おそらく)同じ
    • InputIndexは、ConnectInput()で指定するもの
  • なので、SetLayerMaskFromAvatarMask()したレイヤ番号と同じInputIndexに、上半身用のモーションを流しこめば、 【A】ベースとなるモーション、の該当部分(サンプルの場合上半身)が、【B】のモーションに上書きされる

疑問

AnimationLayerMixerPlayableのWeight全く弄っていないのですが、いいんでしょうかね。。。

※【2018/03/03追記】...という疑問が2018/01時点ではあったんですが、アップデートで見事に修正されました。 恐らく、関数定義がUnity2017.1時点では

AnimationLayerPlayable(int inputIndex, ...... float weight = 1f)

となってたんでしょうが、Unity2017.3では

AnimationLayerPlayable(int inputIndex, ...... float weight = 0f)

になっており、明示的にweightを指定してあげないと、全くモーションが反映されない模様です。 サンプルも修正しました

【バッチファイル】ファイルのバックアップと、バックアップ先のファイルを一定期間後に削除するバッチ

作成しました。

使い方はバッチファイル内に書きました。

バックアップと、バックアップ先のファイルを一定期間後に削除するバッチ · GitHub

【Unity】【エディタ拡張】 ObjectFieldを横に並べる

f:id:gologius:20171219225213p:plain もうすぐクリスマスなので、ObjectFieldをこんな感じで並べたくないですか?色とかつけて見やすくしたくないですか・・・?

簡単そうに見えて地味に面倒なこの作業を説明します。

なにが難しいのか

ObjectFieldの場合、ラベルが使用するマージン?を弄らないと、下のようになる。 f:id:gologius:20171219225406p:plain 隠れてしまって見えないですね。

なので、EditorGUIUtility.labelWidth = 100;でラベルが使用するマージン?を設定してあげます。

ソース

背景色を付けたり、太字にしたりする方法はソースみればわかると思います。 LayoutTest.cs · GitHub

【2017/12/23追記】値が入っている場合に色を変更する機能の追加と、バグ修正 f:id:gologius:20171223142413p:plain

  [System.Serializable]
    public class Content
    {
        public AnimationClip clip;
        public Transform trans;
    }

この部分、MonoBehaviourを継承してないので、’[System.Serializable]’を追加しないと、値を保存してくれない(実行すると値が消える)

参考

【Akeytsu】MirrorSelectedがIKで動かない

コメントいただいたので調べてみました。

症状

IKでMirrorSelected(左右対称にする)をするとバグる f:id:gologius:20171126093400p:plain f:id:gologius:20171126093505p:plain

IKの付け方 Akeytsu IKの使用方法メモ - gologiusの巣

モデルはお借りしました ImagineGirls – ImagineGirls オフィシャルサイト

原因

たぶん左右の対応がとれていない。Akeytsuではボーンの名前(L,R,Left,Right)でとっているみたい。 gologius.hatenadiary.com

けどIKはL,Rの文字に対応してない?嘘だろ・・・バグだろこれ・・・

解決策

名前を変えるだけ

f:id:gologius:20171126093705p:plain f:id:gologius:20171126093715p:plain f:id:gologius:20171126093724p:plain (※足は無視してください。ネタです。)