2017年3月26日日曜日

【Unity5】キャラモーションをリミテッドアニメにする  ~ 再生フレーム指定編 ~

前回のボーントレース編に続き、今回はアニメーションクリップの再生位置を指定する方法でリミテッドアニメ化してみます。

使う素材は前回と同じく、プロ生ちゃんの【PronamaChan_Ver3.unitypackage】 と、スタンダードアセット【Characters】 に入っているアニメーションクリップ【HumanoidRun】 です。インポート方法は前回記事を参照してください。
※Unity 5.5.2f1 (64-bit)を使用しています。




キャラクターを準備


【PronamaChan】 > 【Prefabs】 > 【Normal】 > 【Standard】
にある、【PronamaChan_dynamic】を【Hierarchy】に配置。

【Standard Assets】 > 【Characters】 > 【ThirdPersonCharacter】 > 【HumanoidRun】にある
【HumanoidRun】を【Hierarchy】の【Pronamachan_Dynamic】にドラッグ&ドロップ。

プロ生ちゃんが走り去ってしまわないように
【PronamaChan_dynamic】の【Inspector】 > 【Animator】の【Apply Root Motion】のチェックを外しましょう。

今回のスクリプト【LimitedAnimationByPlayClipFrame.cs】


【project】に【C# Script】を新規追加し、「LimitedAnimationByPlayClipFrame 」という名前に変更、ダブルクリックしてファイルを開き、下記の内容に書き換えて保存。

【LimitedAnimationByPlayClipFrame.cs】
using UnityEngine;
using System.Collections;
public class LimitedAnimationByPlayClipFrame : MonoBehaviour
{
       //各要素1フレームとして、-1は更新しないフレーム、それ以外の数字は描画するフレーム位置を指定
       public int[] timesheet = new int[] { 0, -1, -1, -1, -1, 5, -1, -1, -1, -1, 10, -1, -1, -1, -1, 15, -1, -1 };
       private Animator m_animator;
       private AnimationClip clip;
       private int animationHash;
       void Awake()
       {
              //アニメーションクリップの情報をゲット
              m_animator = GetComponent<Animator>();
              clip = m_animator.GetCurrentAnimatorClipInfo(0)[0].clip;
              animationHash = m_animator.GetCurrentAnimatorStateInfo(0).shortNameHash;
              m_animator.speed = 0;
       }

       void LateUpdate()
       {
              if (IsDrawFrame(Time.frameCount))
              {
                     DrawAnimationFrame(timesheet[Time.frameCount % timesheet.Length]);
              }
       }
       //アニメーションクリップの指定時間のフレームを静止再生
       void DrawAnimationFrame(int drawFrame)
       {
              //アニメーションクリップの長さを1とした時のフレームの位置
              float normalizedtime = ((float)drawFrame / clip.frameRate / clip.length);
              m_animator.Play(animationHash, 0, normalizedtime);
       }
       //ポーズを更新するフレームかどうかチェック
       bool IsDrawFrame(int checkFrame)
       {
              if (timesheet[checkFrame % timesheet.Length] >= 0)
              {
                     return true;
              }
              return false;
       }

実際、アニメーションクリップは複数あったりするものですが、今回は実験なのでアニメーションクリップは一つだけの想定のスクリプトになってます。
結果、あまり汎用性がないのでご注意ください。

このスクリプトを【Hierarchy】の【Pronamachan_Dynamic】にアタッチし、
【Inspectr】の【Timesheet】に使用するフレームの情報を入力します。
【Size】はループするフレーム数と思ってください。以下【Element】が各フレームで、値がアニメーションクリップのフレームナンバーです。

【-1】はポーズの更新なし、つまり前の絵のままのフレームです。
【0】以上の値はフレームナンバーで、元のクリップのフレーム数より大きい値も入ります。
10フレームしかないアニメーションクリップで15と入れたら、5フレーム目が使われます。
ちなみに今回使ったアニメーションクリップ【HumanoidRun】は【clip.framerate】が「30」で長さ【clip.length】は「0.593」なので、フレーム数換算で19程度です。

もちろん数字を順番に入れる必要はないので、唐突に違うフレームを差し込んだりできます。

これで再生してみると、スカートなどの揺れ物以外はコマ落ちして再生できます。
この方法とボーントレースを組み合わせて、両者の欠点を補えれば・・・。

2017年3月25日土曜日

【Unity5】キャラモーションをリミテッドアニメにする ~ボーントレース編~

Unityのキャラクターの動きのコマを抜いて、リミテッドアニメにしてみます。

  • 【方法1】 アニメーションクリップの再生フレームを指定する
  • 【方法2】 任意のフレームで、フルアニメーションするキャラのボーントランスフォームを複製する

【方法1】は、アニメーションクリップの再生時間を指定して一時停止、数フレーム待って再び再生位置を指定して一時停止、というイメージです。これは使いたいポーズのフレームを直接指定できますが、物理で動く揺れ物のコマは落とせません。

【方法2】は仕込みに少々手間がかかりますが、揺れ物のボーンまでトレースするのでコマ落としできます。

今回はボーンをトレースしてリミテッドアニメを実現する、【方法2】について書きます。使用したUnityは【5.5.2f1 (64-bit)】です。

※【方法1】については次回記事、再生フレーム指定編へ。


素材のインポート


テストに使うキャラクターモデルはこちらの「プロ生ちゃん」Unityパッケージ。

ダウンロードしたファイルの中から【PronamaChan_Ver3.unitypackage】をプロジェクトにインポートします。

次に、テスト用のアニメーションを用意します。
スタンダードアセットの【Characters】をインポートし、そこに含まれるアニメーションを使います。

Hierarchyに配置


複製元となるプロ生ちゃんを配置します。

【PronamaChan】 > 【Prefabs】 > 【Normal】 > 【Standard】
にある、【PronamaChan_dynamic】を【Hierarchy】に配置します。

このプロ生ちゃんにアニメーションをさせます。
【Standard Assets】 > 【Characters】 > 【ThirdPersonCharacter】 > 【HumanoidRun】にある
【HumanoidRun】を【Hierarchy】の【Pronamachan_Dynamic】にドラッグ&ドロップします。

これでプロ生ちゃんが走るようになりましたが、このままだと画面の外まで走って行ってしまいます。
【PronamaChan_dynamic】の【Inspector】 > 【Animator】の【Apply Root Motion】のチェックを外しましょう。

これでプロ生ちゃんはその場で走るようになります。

リミテッドアニメーション用のキャラクタを配置します。
スクリプトで同名ボーンのトランスフォームを複製するようにしますので、複製元と同じ【PronamaChan_dynamic】を【Hierarchy】に追加し、名前は【PronamaChan_dynamic_clone】とします。

プロ生ちゃんの髪の毛やスカート、リボンは【Spring Manager】と【Spring Bone】というスクリプトで動いていますが、クローン側には必要ないのでオフにします。
【Spring Manager】だけオフにすれば十分ですので、【PronamaChan_Arm】の【lower_body】【Upper_body2】【head】についている【Spring Manager】のチェックを外します。


ライトやカメラの位置も調整します。右が複製元、左がクローンです。

ボーントレースのスクリプト


次にスクリプトを用意します。
下記のサイトを参考にさせていただきました。


【project】に【C# Script】を新規追加し、「LimitedAnimation」という名前に変更、ダブルクリックしてファイルを開き、下記の内容に書き換えて保存。

LimitedAnimation.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;//list使用のために必要
public class LimitedAnimation : MonoBehaviour
{
       public int flameInterval = 3;//抜くフレーム数
       public GameObject masterObject; //ボーンの複製元になるオブジェクト
       private GameObject cloneObject;
       public bool rotationOnly = false;
       private Dictionary<string, Transform> masterAllChildren = new Dictionary<string, Transform>();
       private Dictionary<string, Transform> cloneAllChildren = new Dictionary<string, Transform>();
       void Awake()
       {
              //スクリプトがアタッチされているオブジェクトを取得
              cloneObject = gameObject;
              //すべての子オブジェクトのトランスフォームを取得
              GetChildren(masterObject, ref masterAllChildren, true);
              GetChildren(cloneObject, ref cloneAllChildren, false);
       }

       void LateUpdate()
       {
              if (IsDrawFrame(Time.frameCount))
              {
                     foreach (KeyValuePair<string, Transform> child in cloneAllChildren)
                     {
                           child.Value.transform.rotation = masterAllChildren[child.Key].transform.rotation;
                           if (!rotationOnly)
                           {
                                  child.Value.transform.position = masterAllChildren[child.Key].transform.position;
                           }
                     }
              }
       }
       //すべての子オブジェクトのトランスフォームを取得
       void GetChildren(GameObject obj, ref Dictionary<string, Transform> allChildren, bool springBoneEnabled)//listは参照渡し
       {
              Transform children = obj.GetComponentInChildren<Transform>();
              //子要素がいなければ終了
              if (children.childCount == 0)
              {
                     return;
              }
              foreach (Transform ob in children)
              {
                     allChildren[ob.name] = ob.gameObject.GetComponent<Transform>();
                     GetChildren(ob.gameObject, ref allChildren, springBoneEnabled);
              }
       }
       //ボーンコピーするフレームかどうか判断
       bool IsDrawFrame(float nowframe)
       {
              if (nowframe % flameInterval == 0)
              {
                     return true;
              }
              return false;
       }
}

このスクリプトをクローン側の【PronamaChan_dynamic_clone】にアタッチします。
【Master Object】には複製元を登録します。【Hierarchy】の【Pronamachan_dynamic】をドラッグ&ドロップしてください。
【Flame Interval】に、抜くフレーム数を入力します。ここでは3フレームごとに動くようにします。
今回はわかりやすく複製元と並べて再生したいので、【Rotation Only】にチェックを入れます。
※【Rotation Only】のチェックを外すと複製元と位置が重なるので、レイヤーを分けるなどして複製元がカメラに映らないようにする必要があります。

これでプレビューしてみると、【Rotation Only】にチェックが入っているので上下運動は拾えていないですが、クローン側がコマ落ちアニメーションしていることがわかると思います。

連番画像の出力


連番画像で出力してみます。

こちらのサイトで紹介されているスクリプト【SaveToPng.cs】をカメラにアタッチします。


再生直後は少し挙動が荒れるのでキャプチャ開始する【StartFrame】は「30」にしました。

再生すると【StartFrame】から【EndFrame】の間だけキャプチャされ、画像がプロジェクトフォルダ直下の【ScreenShot】フォルダに保存されます。

左側のプロ生ちゃんのポーズが3フレームごとに更新されているのがお分かりいただけるでしょうか。


今回のスクリプトでは一定の時間間隔でボーントレースしました。
落とすコマ数に変化をつけようとするなら、タイムーシートのような形で配列データを用意したうえでアニメーションクリップの再生フレームも監視する必要がありそうです。

Blender上でMIDIファイルを読み込み3Dモデルの鍵盤を動かすpythonスクリプトに挑戦したこと

昔取り組んだことについての忘備録 作成日 2016/8/7 目的 MIDIファイルの仕様の理解 BlenderとPythonの学習 内容 MIDIファイルのバイナリを読み込んで解析し、ノートオン、ノートオフ情報に従って、対応する音階の...