inosyanのブログ

プログラム、家庭菜園、僕が興味を持ったことを書いていきます

UnityのTimelineを使ったムービー制作

f:id:inosyan:20180115081455j:plain 先日仕事で、Unityアセットストア向けにアセットパッケージの制作をしました。そちらはいま申請中ですので審査が通ったらまたお知らせしますが、申請に必要だった紹介ムービーをUnityを使って作成したので、今回は会社に許可をもらい、そのムービーをご紹介します。

このムービーの冒頭の10秒間の部分がUnityで制作した部分です。


WWW Script Generator

モデリングはmayaで行いました。冒頭で一部だけを光らせたいので、マテリアルを分けてあります。
f:id:inosyan:20180115081506j:plain

あとはUnityの作業です。 はじめに周りの3つの三角形が点滅し、次に中央が点滅しますが、この部分のマテリアルには、ビルドインシェーダーの Unity スタンダードシェーダー を使用しており、Emission の値を有効にしています。 f:id:inosyan:20180115081703p:plain

そして、点滅させるためにシェーダーのプロパティをスクリプトで制御しています。 そのためには PlayableAsset, PlayableBehaviour をそれぞれ継承した2つのクラスを用意し、PlayableAssetを継承したほうをTimelineに配置します。今回は点滅する箇所は、周りの三角形と中央の2箇所ありますが、共通のスクリプトを使ってenumでマテリアルを切り替えています。
f:id:inosyan:20180115081725p:plain

StartEffectAsset.cs

using UnityEngine;
using UnityEngine.Playables;

[System.Serializable]
public class StartEffectAsset : PlayableAsset
{
    public StartEffect.TargetEnum Target;
    public Material TopMaterial;
    public Material CenterMaterial;

    public override Playable CreatePlayable(PlayableGraph graph, GameObject go) {
        var handle = ScriptPlayable<StartEffect>.Create(graph);
        var beh = handle.GetBehaviour();
        beh.Target = Target;
        beh.TopMaterial = TopMaterial;
        beh.CenterMaterial = CenterMaterial;
        return handle;
    }
}

StartEffect.cs

using UnityEngine;
using UnityEngine.Playables;

public class StartEffect : PlayableBehaviour
{
    public enum TargetEnum {
        Top,
        Center,
    }

    public TargetEnum Target;
    public Material TopMaterial;
    public Material CenterMaterial;

    Material TargetMat {
        get {
            switch(Target){
                case TargetEnum.Top: return TopMaterial;
                case TargetEnum.Center: return CenterMaterial;
                default: return TopMaterial;
            }
        }
    }

    void SetEmission(Color col)
    {
        if (TargetMat && TargetMat.HasProperty("_EmissionColor"))
            TargetMat.SetColor("_EmissionColor", col);
    }

    // Called when the owning graph starts playing
    public override void OnGraphStart(Playable playable) {
    }

    // Called when the owning graph stops playing
    public override void OnGraphStop(Playable playable) {
    }

    // Called when the state of the playable is set to Play
    public override void OnBehaviourPlay(Playable playable, FrameData info) {
        AdjustEmission(0);
    }

    // Called when the state of the playable is set to Paused
    public override void OnBehaviourPause(Playable playable, FrameData info) {
        AdjustEmission(1);
    }

    // Called each frame while the state is set to Play
    public override void PrepareFrame(Playable playable, FrameData info) {
        float rate = Mathf.Clamp01((float)(playable.GetTime() / playable.GetDuration()));
        AdjustEmission(rate);
    }

    void AdjustEmission(float rate)
    {
        var dark = new Color(0, 0, 0, 1);
        var light = new Color(0.362353f, 0.7f, 0.7f, 1);
        if (rate < 0.5) 
            SetEmission(dark);
        else
        {
            var d = rate * 270 / 180 * Mathf.PI;
            rate = Mathf.Abs(Mathf.Sin(d));
            SetEmission(rate < 0.5 ? dark : light);
        }
    }
}
}

PlayableBehaviourを継承したほうのスクリプトに、具体的な動きを記述します。
public override void PrepareFrame(Playable playable, FrameData info)
の中が、Timelineのスライダーが動くたびに呼ばれるところで、このスクリプトがアタッチされている箇所にスライダーが差しかかったときに、点滅の処理をします。

タイムライン上の範囲をrateという変数で表現します。rateは時間経過とともに0から1に変化します。このように、前半は光が消えている状態です。

if (rate < 0.5) 
    SetEmission(dark);

後半はrateを0~270°の角度に置き換え、sin式を使っています。

else
{
    var d = rate * 270 / 180 * Mathf.PI;
    rate = Mathf.Abs(Mathf.Sin(d));
    SetEmission(rate < 0.5 ? dark : light);
}

図にするとこんな感じです。
f:id:inosyan:20180115081802p:plain

再生するとこのようになります。
f:id:inosyan:20180115081823g:plain

プログラムでアニメーションを表現する際には、このようにsinをよくつかいます。
続きはまた後日書きたいと思います。