テラシュールブログ

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

【Unity】Timelineのアニメーションに微調整を加える(Generic編)

DCCからアニメーションをUnityに持ってきてから、Timeline上のアニメーションを微調整したいといったケースがあります。例えばドラゴンが噛み付く位置が少しズレているだとか、棍棒を叩きつける位置が微妙に違う等。特にオブジェクトの配置をゲーム上で行っている場合、DCCツールとUnityエディターを行き来するのは少し面倒そうです。

ということでTimeline上で微調整してみます。なお、今回はGeneric用のアプローチについてで、Humanoid用は次回です。

1. Timeline上でアニメーションを再生する

まず最初にGenericなモデルをタイムライン上でアニメーションしている状態です。普通にアイドルモーションしています。

この状態はボーンの位置がTimelineのAnimationClipで決められているので、首を動かしたり少しズラしたりすることは出来ません。

f:id:tsubaki_t1:20190508223205g:plain
ドラゴンのアイドルモーション

2. モーションを編集しやすくする

ボーン構造を少しズラします。ただAnimationClipを編集するのは正直行いたくないので、FKベースでアニメーションの動きを少しオーバーライドします。
この最初の手順は、リグを視覚的に見やすくします。

Aniamtion RiggingBone Rendererコンポーネントを使用して、ボーン構造をSceneView上に表示します。

メッシュのボーン構造(動かすとモデルが変形する、OptimizedGameObjectで非表示になるGameObject)をBoneRendererのTransformsにドラッグ&ドロップすれば、ボーン構造が視覚的に確認出来るようになります。また、ボーン構造それ自体を選択しやすくなり、編集しやすくなります。

f:id:tsubaki_t1:20190508223918j:plain

f:id:tsubaki_t1:20190508224040g:plain

3. アニメーションをオーバーライドする

Timeline上のアニメーションをオーバーライドします。

  1. AnimationTrackを右クリックしてAdd Override Trackを選択
  2. レコードボタンを押してレコード開始
  3. 動かしたいタイミングでキーフレームを登録

f:id:tsubaki_t1:20190508224552g:plain

これでTimeline上でアニメーションを少し編集出来ますが、少し問題があります。

4. オーバーライドしたアニメーションの範囲を限定する

上の手順でアニメーションを編集した場合、編集した内容が最初から最後まで反映されっぱなしになります。特定の瞬間だけ上書きしたいので、これは望ましくありません。

なのでアニメーションの範囲を限定し、ついでにオーバーライドするアニメーションが既存のアニメーションに自然につながるようにします。

  1. AnimationTrackでConvert to clip trackを選択
  2. 変換したAnimationClipを選択
  3. Ease in durationEase out durationに0以外を設定

f:id:tsubaki_t1:20190508225315g:plain

これでGenericなキャラクターの動きを微調整出来ます。

感想

実際には完全オーバーライドよりお「+N°回す」的な方が多いかもしれません。そういったものはLateUpdateで補正するなり、色々と。

なおこのアプローチの本質は「Genericモーションを作って上書き」なので、Humanoidの場合は別のアプローチが必要になります。それは次回。

関連

今回使用したモデル

assetstore.unity.com

【Unity】Animation Rigging、人型以外のキャラクターにもIKを!

f:id:tsubaki_t1:20190120234723j:plain

今回はUnity 2019.1のPackageManagerで使えるようになるAnimation Riggingについてです。

Animation Rigging

AnimationRiggingは、Animation C# Jobsの上でキャラクターを手続き的に動かす機能みたいです。例えばIKをセットアップしたり、動きを調整したり。

それ以外でもTimelineで動くキャラクター(Humanoid)の腰に回転を加えてみるなど、特定のシーンでの微調整といった意味では中々に面白い機能です。

f:id:tsubaki_t1:20190120234137g:plain
キャラクターの腰の動きをオーバーライドする

IKで言えばAnimator(Mecanim)に標準で付いているじゃん!という考えもありますが、MecanimのIKはHumanoid限定なのに対して、AnimationRiggingはGenericなRigを持つキャラクターでもIKを利用出来ます。正確にはIKを実装すれば使えるというのが正しく、最初から付いてるのはTwoBoneIKだけです。(サンプルだとフルボディIKがあったんだけど…)

例えば下のキャラクター(明らかに人型ではない)の足の動きはIKで作成しています。足の最終的な位置だけを示して、後はIKの機能で調整していると言った感じです。

f:id:tsubaki_t1:20190120234505g:plain
足をIKで動かす

他にもアニメーションで動くキャラクターのTransformのオーバーライドや、複数の動きのブレンドなど色々と出来るみたいです。

  • Blend Constraint
  • Chain IK Constraint
  • Damped Transform
  • Multi-Aim Constraint
  • Multi-Parent Constraint
  • Multi-Position Constraint
  • Multi-Referential Constraint
  • Multi-Rotation Constraint
  • Override Transform
  • Twist Correction
  • Two Bone IK Constraint

実際に使ってみる

実際にコレを使ってみます。今回試す機能はTwoBoneIKです。2つの関節でIK表現を行います。

アームの先端を動かすと、先端につながるようにアームの位置が補完されています。

f:id:tsubaki_t1:20190507224513g:plain
TwoBoneIKでアーム

試した環境

  • .NET 4.x
  • Unity 2019.1
  • Animation Rigging ver 0.1.4

手順

まずアームを作ります。

最も親となるGameObject「ARM」の下に、Root > Mid > Tipになるように親子構造を構築します。この時、親となるGameObjectを回すと綺麗に子のボーンが回るようにメッシュの位置を調整しておきます。

f:id:tsubaki_t1:20190507225303j:plain
親子構造でアームを作る

f:id:tsubaki_t1:20190507225722g:plain
FKで動かせる事の確認

次にRigを設定します。

ARMの下にRigというGameObjectを作成し、RigコンポーネントTwoBoneIKConstraintコンポーネントを追加します。

Rigオブジェクトの下にTargetとHintというGameObjectを生成し、TwoBoneIKConstraintに登録していきます。この時TargetはTipと大体同じ位置に配置しておくが吉です。

f:id:tsubaki_t1:20190507230439j:plain

あとはRigBuilderを作成し、先ほど作成したRigオブジェクトを登録します。これでゲーム実行時にTwoBoneIKが動作します。

f:id:tsubaki_t1:20190507231532j:plain

動画でみたい場合、下の動画が分かりやすいです。

www.youtube.com

また導入の際、パッケージからサンプルが導入出来ます。中に色々とサンプルが入っているので追ってみるのも面白そうです。

f:id:tsubaki_t1:20190507231042j:plain

【Unity】UnityPhysicsで、オブジェクト同士が接触した判定を取得する

f:id:tsubaki_t1:20190425223640g:plain

物理演算でオブジェクト同士が接触した時のイベントを発行する方法についてです。

なお、現段階では明らかに低レベルAPIのみしか提供されておらず、未完成です。

この記事は「今すぐ触ってみたい」という人向けで、正直あまり有益ではありません。

接触イベント

榴弾の発射」や「玉を地面に落とした時」、「キャラクターが特定の範囲に到達した時」など、動く物体がオブジェクトに接触した時に何らかのイベントを発行したくなります。

UnityのPhysics(GameObjectがPhysXを利用)ではOnCollisionEnterやOnTriggerEnterを設定すれば良いのですが、UnityPhysicsは現時点では低レベルAPIしか提供していないため、割と面倒くさい処理が必要になります。

使用環境

  • Unity 2019.1f2
  • Unity Physics preview1 0.0.2

セットアップ

イベントを受け取るにはColliderからの変換ではなくPhysicsShapeを使用する必要があります。その上で PhysicsShape > Advance > Raises Collision Event にチェックを入れます。

(イベントを発行しない側はColliderにIsTrigerを設定しUnityPhysicsに変換するだけで良いです)

f:id:tsubaki_t1:20190425224053j:plain

これでチェックを入れたEntityが他のオブジェクトに接触した時、イベントが発行されます。あとは発行されたイベントを総なめで内容を確認するシステムを用意します。

まずシステムの実行順ですが、StepPhysicsWorldの後に実行します。

[UpdateAfter(typeof(StepPhysicsWorld))]
public class ItemGetSystem: ComponentSystem
{

次にイベントの取得と全Colliderの取得用に、BuildPhysicsWorldStepPhysicsWorldを用意します。

    private BuildPhysicsWorld buildPhysicsWorldSystem;
    private StepPhysicsWorld stepPhysicsWorldSystem;

    protected override void OnCreate()
    {
        buildPhysicsWorldSystem = World.GetOrCreateSystem<BuildPhysicsWorld>();
        stepPhysicsWorldSystem = World.GetOrCreateSystem<StepPhysicsWorld>();
    }

実際の中身を処理する部分です。

buildPhysicsWorldSystemからPhysicsWorldを取得します。この中に現時点での全てのColliderの座標等が含まれます。

同様にstepPhysicsWorldSystemからTriggerEventsを取得します。この中にRaises Collision Eventが有効なオブジェクトが発行したイベントが格納されています。もしCollider同士の接触判定を取りたい場合はCollisionEventsです。

あとはEventTriggerをキーに接触中のオブジェクトを取得して、イベント処理するだけです。

    protected override void OnUpdate()
    {
        var physicsWorld = buildPhysicsWorldSystem.PhysicsWorld;
        var triggerEvents = stepPhysicsWorldSystem.Simulation.TriggerEvents;
        var items = GetComponentDataFromEntity<ItemTag>(true);

        foreach (var triggerEvent in triggerEvents)
        {
            // PhysicsWorldに全ての物理演算で動くEntityが含まれる。
            // triggerEventsには、Raises Collision Eventが有効なオブジェクトが接触イベントを格納する
            // TriggerEventの中身を洗えば、接触判定が取れる
            var bodyA = physicsWorld.Bodies[triggerEvent.BodyIndices.BodyAIndex];
            var bodyB = physicsWorld.Bodies[triggerEvent.BodyIndices.BodyBIndex];
            
            Process(bodyA.Entity, items);
            Process(bodyB.Entity, items);
        }
    }

全文はこちら

Unity Physicsの使用例 · GitHub

なお、Unity Physicsはステートを持たないので、「接触開始」や「接触中」「接触終了」といったイベントは取れないみたいです。

特定のオブジェクト同士でのみ接触イベントを行いたい場合、上のコードのようにComponentDataFromEntityで特定のコンポーネントを持っていないかを判断するか、もしくはCustom Flagを設定しておくとBodyA.CustomData で判断が出来ます。

f:id:tsubaki_t1:20190425221913j:plain

単純に接触したくない場合はCollision Filterを調整すると良いみたいです。この辺りのインターフェースはもう少し頑張ってほしい所です。

f:id:tsubaki_t1:20190425222055j:plain

JobSystemで動かしたい

上の処理をJobSystemで動かしてみました。

ポイントは処理の依存関係を設定することです。UnityPhysicsはECSを使用していますがECS上で動作していないので、処理の依存関係を自動的に解決してくれません。システムから持ってくる必要があります。

        inputDeps = JobHandle.CombineDependencies(
            inputDeps, 
            buildPhysicsWorldSystem.FinalJobHandle,
            stepPhysicsWorldSystem.FinalSimulationJobHandle);

全文はこんな感じになります。

using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Physics;
using Unity.Physics.Systems;

[UpdateAfter(typeof(StepPhysicsWorld)), UpdateBefore(typeof(EndFramePhysicsSystem))]
public class ItemGetSystem : JobComponentSystem
{
    BuildPhysicsWorld buildPhysicsWorldSystem;
    StepPhysicsWorld stepPhysicsWorldSystem;
    EntityCommandBufferSystem bufferSystem;

    protected override void OnCreate()
    {
        buildPhysicsWorldSystem = World.GetOrCreateSystem<BuildPhysicsWorld>();
        stepPhysicsWorldSystem = World.GetOrCreateSystem<StepPhysicsWorld>();
        bufferSystem = World.GetOrCreateSystem<BeginSimulationEntityCommandBufferSystem>();
    }

    protected override JobHandle OnUpdate(JobHandle inputDeps)
    {
        inputDeps = JobHandle.CombineDependencies(
            inputDeps, 
            buildPhysicsWorldSystem.FinalJobHandle,
            stepPhysicsWorldSystem.FinalSimulationJobHandle);

        inputDeps = new HitAndDestroyJob {
            World = buildPhysicsWorldSystem.PhysicsWorld,
            TriggerEvents = stepPhysicsWorldSystem.Simulation.TriggerEvents,
            items = GetComponentDataFromEntity<ItemTag>(true),
            commandBuffer = bufferSystem.CreateCommandBuffer(),
        }.Schedule(1, 1, inputDeps);

        bufferSystem.AddJobHandleForProducer(inputDeps);

        return inputDeps;
    }

    struct HitAndDestroyJob : IJobParallelFor
    {
        [ReadOnly] public PhysicsWorld World;
        [ReadOnly] public TriggerEvents TriggerEvents;
        [ReadOnly] public ComponentDataFromEntity<ItemTag> items;
        [ReadOnly] public EntityCommandBuffer commandBuffer;

        public void Execute(int index)
        {
            foreach (var triggerEvent in TriggerEvents)
            {
                GetItem(World.Bodies[triggerEvent.BodyIndices.BodyAIndex].Entity);
                GetItem(World.Bodies[triggerEvent.BodyIndices.BodyBIndex].Entity);
            }
        }

        void GetItem(Entity entity)
        {
            if (items.Exists(entity) == false)
                return;

            commandBuffer.DestroyEntity(entity);
            UnityEngine.Debug.Log("you get item!");
        }
    }
}

【Unity】GameObjectでもUnity Physicsを使う

f:id:tsubaki_t1:20190422011739j:plain

試しにRoll a BallをUnity Physicsで作ってみた所、このテクニック無しだと少し面倒な所があったので紹介。

GameObjectでUnity Physicsを使う

Unity PhysicsはECSのための機能です。とは言えECSはまだ機能が全くと行っていいほど足りてない玩具であり、これだけでゲームを作るのは正直辛いとです。なので基本的にGameObjectを使いつつUnity Physicsを使う方法を考えてみます。

具体的にはSkinnedMeshRendererで動くキャラクターをUnity Physicsのゲーム空間に含めることを目標とします。

キャラクターが消えるのを解決する

とりあえずConvert To EntitySubScene を設定したところ、ゲーム実行時にキャラクターが消滅しました。これは Convert To EntityやSubSceneが Convert And Destroyの設定になっているためです。この設定だとGameObjectは自動的に破棄されます。

f:id:tsubaki_t1:20190422012357g:plain

なのでConvert To Entity の設定を Convert And Inject GameObjectに変更します。この時、今までのMeshColliderやRigidbodyを設定しているとRigidbodyとPhysicsBodyの両方が設定されてしまうので、Rigidbodyは消してPhysicsBodyやPhysicsShapeのみの設定にします。

f:id:tsubaki_t1:20190422012038j:plain

表示はされますが、動きません

キャラクターが動かない問題を解決する

次にPhysicsBodyを設定しているのに動かない問題です。

これは実際には動いていますが、Transformに反映されないために起こります。これを確認するにはPhysics Debug Displayを使用するのが手っ取り早いです。

これで実際のColliderの位置を確認出来ます。試しに見てみた所、Colliderは動いていますがTransformに反映されていませんでした。

f:id:tsubaki_t1:20190422013026j:plain

f:id:tsubaki_t1:20190422012903g:plain

これの解決にはCopyTransformToProxyを設定するのが良いですが、ConvertToEntityと機能が被るので、IConvertGameObjectToEntity系の機能を自作します。

using Unity.Entities;
using UnityEngine;
using Unity.Transforms;

[DisallowMultipleComponent]
[RequiresEntityConversion]
public class CopyTransformToComponentConvert : MonoBehaviour, IConvertGameObjectToEntity
{
    public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    {
        dstManager.AddComponentData(entity, new CopyTransformToGameObject());
    }
}

あとはUnity Physicsで動かすキャラクターに上のコードCopyTransformToComponentConvertを設定してやれば、Unity PhysicsでGameObjectが動きます。

f:id:tsubaki_t1:20190422013621g:plain