Unity Timeline 二次简单封装TimelineProxy(代理)

Overview

主要封装了Timeline的PlayableDirector

  • SetBinding的使用更友好一些(原来的API使用设置,真不知道为何官方要用:PlayableBinding.source来做key,这里我们改成string做key了)
  • 还有对timeline的Animation Track使用的binding对想同步控制:播放、暂停、继续

下面演示主要是运行中,动态生成 Animation Track(动画轨道)要控制的:新对象、切换掉旧对象、销魂就对象
下面gif中的ChangeChar就是切换角色,ChanageLight切换灯光的演示

还有:PlayableAsset, PlayableBehaviour的简单使用,放在Playable Track轨道中来使用(脚本轨道)

Running

(由于CSDN限制gif的大小不能超过5MB,所以录制过程我操作比较快,而且内容尽量缩减,gif质量降到最低了,不然大小限制)
这里写图片描述

Share

TimelineProject
开发环境:

  • vs 2017
  • unity 2017.4.3f
    • unity记得要使用Scripting Runtime Version : Stable (.NET 4.6 Equivalent)
    • 以上设置位置所在:Edite->Project Setting->Player->Inspector面板中的 Configuration

Code

(代码有丢丢多,所以将Running放前面,Code放这)

TimelineProxy

/*
 * FileName:    TimelineProxy
 * Author:      Jave.Lin
 * CreateTime:  #TIME#
 * Description: [Description]
 * 
*/
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Timeline;
using UnityEngine.Playables;
using System;

/// <summary>
/// Timeline代理的播放状态枚举
/// </summary>
public enum TimelineProxyPlayState
{
    Playing,
    Paused,
    Stopped
}
/// <summary>
/// 对外使用的:Timeline辅助类
/// </summary>
public class TimelineHelper
{
    public static TimelineProxy AddTimeline(GameObject go, string timelineName, bool stopAnima = true)
    {
        var unit = new TimelineProxy();
        var director = go.GetComponent<PlayableDirector>();
        if (null == director)
            director = go.AddComponent<PlayableDirector>();
        // 这里为了测试方便,直接从Resources下读取
        // 当然啊也是可以从AssetBundle下载、读取
        var asset = Resources.Load<PlayableAsset>(timelineName);
        unit.Init(timelineName, director, asset);
        return unit;
    }
}
/// <summary>
/// 对外使用的:Timeline二次封装的代理类
/// author  :   Jave.Lin
/// date    :   2018-08-20
/// </summary>
public class TimelineProxy : IDisposable
{
    private Dictionary<string, PlayableBinding> tracksMap;
    private Dictionary<string, Dictionary<string, PlayableAsset>> tracksClipsMap;

    private List<GameObject> animatorList;

    public bool SyncAnimator { get; private set; }
    public string Name { get; private set; }
    public PlayableDirector Director { get; private set; }
    public PlayableAsset Asset { get; private set; }
    public TimelineProxyPlayState PlayState { get; private set; }

    public TimelineProxy() { }
    public void Dispose()
    {
        if (tracksMap != null)
        {
            tracksMap.Clear();
            tracksMap = null;
        }
        if (tracksClipsMap != null)
        {
            foreach (var item in tracksClipsMap)
            {
                item.Value.Clear();
            }
            tracksClipsMap.Clear();
            tracksClipsMap = null;
        }
        if (animatorList != null)
        {
            animatorList.Clear();
            animatorList = null;
        }
        Director = null;
        Asset = null;
    }
    /// <summary>
    /// 初始化
    /// </summary>
    /// <param name="name">代理的名称</param>
    /// <param name="director">代理的Director</param>
    /// <param name="asset">代理的Asset</param>
    /// <param name="syncAnimator">是否同步代理所有动画Animator(就是代码在播放、暂停、恢复时,同步处理Animator的暂停、继续,这个暂时使用这种方式来处理,API上没有相关的处理,所以这里才有封装的必要,估计以后Unity会改进timeline的功能,提供更友好的API供我们使用)</param>
    public void Init(string name, PlayableDirector director, PlayableAsset asset, bool syncAnimator = true)
    {
        director.playableAsset = asset;
        this.Name = name;
        this.Director = director;
        this.Asset = asset;
        this.SyncAnimator = syncAnimator;
        this.PlayState = TimelineProxyPlayState.Paused;

        tracksMap = new Dictionary<string, PlayableBinding>();
        tracksClipsMap = new Dictionary<string, Dictionary<string, PlayableAsset>>();
        animatorList = new List<GameObject>();
        foreach (var o in asset.outputs)
        {
            var trackName = o.streamName;
            tracksMap.Add(trackName, o);

            var trackAsset = o.sourceObject as TrackAsset;
            var clipList = trackAsset.GetClips();
            foreach (var c in clipList)
            {
                if (!tracksClipsMap.ContainsKey(trackName))
                {
                    tracksClipsMap[trackName] = new Dictionary<string, PlayableAsset>();
                }
                var map = tracksClipsMap[trackName];
                if (!map.ContainsKey(c.displayName))
                {
                    map.Add(c.displayName, c.asset as PlayableAsset);
                }
                if (o.streamType == DataStreamType.Animation)
                {
                    var go = director.GetGenericBinding(o.sourceObject) as GameObject;
                    if (go != null)
                    {
                        var animator = go.GetComponent<Animator>();
                        if (animator != null)
                        {
                            if (syncAnimator) animator.enabled = false;
                            PushAsyncAnimatorList(go);
                        }
                    }
                }
            }
        }
    }
    /// <summary>
    /// 设置轨道绑定的对象
    /// </summary>
    /// <param name="trackName">轨道名称</param>
    /// <param name="o">需要绑定的对象</param>
    public void SetBinding(string trackName, UnityEngine.Object o)
    {
        PlayableBinding binding = default(PlayableBinding);
        if (tracksMap.TryGetValue(trackName, out binding))
        {
            Director.SetGenericBinding(binding.sourceObject, o);
            var go = o as GameObject;
            if (go != null)
            {
                var animator = go.GetComponent<Animator>();
                if (animator != null)
                {
                    if (PlayState != TimelineProxyPlayState.Playing)
                    {
                        animator.enabled = false;
                    }
                    PushAsyncAnimatorList(go);
                }
            }
        }
    }
    /// <summary>
    /// 根据指定的轨道名称,获取对应轨道绑定的对象
    /// </summary>
    /// <param name="trackName">轨道名称</param>
    /// <returns>返回绑定</returns>
    public UnityEngine.Object GetBinding(string trackName)
    {
        PlayableBinding binding = default(PlayableBinding);
        if (tracksMap.TryGetValue(trackName, out binding))
        {
            return Director.GetGenericBinding(binding.sourceObject);
        }
        return null;
    }
    /// <summary>
    /// 根据指定的轨道名称,获取对象的轨道
    /// </summary>
    /// <param name="trackName">轨道名称</param>
    /// <returns>返回对应的轨道</returns>
    public PlayableBinding GetTrack(string trackName)
    {
        PlayableBinding ret = default(PlayableBinding);
        if (tracksMap.TryGetValue(trackName, out ret))
        {
            return ret;
        }
        return ret;
    }
    /// <summary>
    /// 根据指定的轨道名称、剪辑名称,获取对象的轨道上的剪辑
    /// </summary>
    /// <typeparam name="T">返回剪辑对象(PlayableAsset)</typeparam>
    /// <param name="trackName">轨道名称</param>
    /// <param name="clipName">剪辑名称</param>
    /// <returns>返回对应的剪辑</returns>
    public T GetClip<T>(string trackName, string clipName) where T : PlayableAsset
    {
        Dictionary<string, PlayableAsset> track = null;
        if (tracksClipsMap.TryGetValue(trackName, out track))
        {
            PlayableAsset ret = null;
            if (track.TryGetValue(clipName, out ret))
            {
                return ret as T;
            }
            else
            {
                Debug.LogError($"GetClip trackName: {trackName} not found clipName: {clipName}");
            }
        }
        else
        {
            Debug.LogError($"GetClip not found trackName: {trackName}");
        }
        return null;
    }
    /// <summary>
    /// 播放
    /// </summary>
    public void Play()
    {
        PlayState = TimelineProxyPlayState.Playing;

        if (SyncAnimator) EnabledAllAnimator(true);

        Director.Play();
    }
    /// <summary>
    /// 暂停
    /// </summary>
    public void Pause()
    {
        PlayState = TimelineProxyPlayState.Paused;

        if (SyncAnimator) EnabledAllAnimator(false);

        Director.Pause();
    }
    /// <summary>
    /// 恢复
    /// </summary>
    public void Resume()
    {
        PlayState = TimelineProxyPlayState.Playing;

        if (SyncAnimator) EnabledAllAnimator(true);

        Director.Resume();
    }
    /// <summary>
    /// 停止
    /// </summary>
    public void Stop()
    {
        PlayState = TimelineProxyPlayState.Stopped;

        if (SyncAnimator) EnabledAllAnimator(false);

        Director.Stop();
    }

    private void EnabledAllAnimator(bool value)
    {
        for (int i = 0, len = animatorList.Count; i < len; i++)
        {
            var item = animatorList[i];
            if (item == null)
            {
                animatorList.RemoveAt(i);
                --len;
                --i;
                continue;
            }
            item.GetComponent<Animator>().enabled = value;
        }
    }
    private void PushAsyncAnimatorList(GameObject go)
    {
        if (!animatorList.Contains(go))
        {
            animatorList.Add(go);
        }
    }
}

PlayableAsset, PlayableBehaviour

PlayableAsset

/*
 * FileName:    TestingPlayableAsset
 * Author:      Jave.Lin
 * CreateTime:  #2018-08-02#
 * Description: [Description]
 * 
*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Playables;

[System.Serializable]
public class TestingPlayableAsset : PlayableAsset
{
    public GameObject removeObj;
    public GameObject prefab;
    public OpType opType;
    public string Name;
    public string Msg;

    // Factory method that generates a playable based on this asset
    public override Playable CreatePlayable(PlayableGraph graph, GameObject go)
    {
        Debug.Log("TestingPlayableAsset.CreatePlayable");
        var b = new TestingPlayableBehaviour();
        b.removeObj = removeObj;
        b.prefab = prefab;
        b.opType = opType;
        b.Name = Name;
        b.Msg = Msg;
        //return Playable.Create(graph);
        return ScriptPlayable<TestingPlayableBehaviour>.Create(graph, b);
    }
}

public enum OpType
{
    Add,Remove
}

PlayableBehaviour

/*
 * FileName:    TestingPlayableBehaviour
 * Author:      Jave.Lin
 * CreateTime:  #2018-08-02#
 * Description: [Description]
 * 
*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Playables;

// A behaviour that is attached to a playable
public class TestingPlayableBehaviour : PlayableBehaviour
{
    public GameObject removeObj;
    public GameObject prefab;
    public OpType opType;
    public string Name;
    public string Msg;

    // Called when the owning graph starts playing
    public override void OnGraphStart(Playable playable) {
        Debug.Log($"{Name}.OnGraphStart Msg:{Msg}");
    }

    // Called when the owning graph stops playing
    public override void OnGraphStop(Playable playable) {
        Debug.Log($"{Name}.OnGraphStop Msg:{Msg}");
    }

    // Called when the state of the playable is set to Play
    public override void OnBehaviourPlay(Playable playable, FrameData info) {
        Debug.Log($"{Name}.OnBehaviourPlay Msg:{Msg}, info:{str(info)}");
        if (removeObj != null)
        {
            Object.Destroy(removeObj);
            removeObj = null;
        }
        if (opType == OpType.Add)
        {
            if (prefab != null)
            {
                var inst = GameObject.Find("new go");
                if (inst == null)
                {
                    inst = GameObject.Instantiate(prefab);
                    inst.name = "new go";
                    inst.transform.position = new Vector3(0, -2, 0);
                    var s = Random.Range(1f, 3f);
                    inst.transform.localScale = new Vector3(s, s, s);
                }
            }
        }
        else
        {
            var inst = GameObject.Find("new go");
            if (inst != null)
            {
                Object.Destroy(inst);
            }
        }
    }

    // Called when the state of the playable is set to Paused
    public override void OnBehaviourPause(Playable playable, FrameData info) {
        Debug.Log($"{Name}.OnBehaviourPause Msg:{Msg}");//, info:{str(info)}");

    }

    // Called each frame while the state is set to Play
    public override void PrepareFrame(Playable playable, FrameData info) {
        Debug.Log($"{Name}.PrepareFrame Msg:{Msg}");//, info:{str(info)}");

    }

    private string str(FrameData info)
    {
        return string.Join(", ", new string[]
        {
            $"frameId:{info.frameId}",
            $"deltaTime:{info.deltaTime}",
            $"weight:{info.weight}",
            $"effectiveWeight:{info.effectiveWeight}",
            $"effectiveParentDelay:{info.effectiveParentDelay}",
            $"effectiveParentSpeed:{info.effectiveParentSpeed}",
            $"effectiveSpeed:{info.effectiveSpeed}",
            $"evaluationType:{info.evaluationType}",
            $"seekOccurred:{info.seekOccurred}",
            $"timeLooped:{info.timeLooped}",
            $"timeHeld:{info.timeHeld}",
        });
    }
}

Test bed

/*
 * FileName:    TestingScript
 * Author:      Jave.Lin
 * CreateTime:  #2018-08-02#
 * Description: [Description]
 * 
*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Playables;

public class TestingScript : MonoBehaviour
{
    // 需要挂载timeline的对象
    // (其实也可以实时new一个GameObject.AddComponent<PlayableDirector>()是一样的效果的)
    public GameObject timelineHolder;
    // 二次封装后的Timeline代理对象
    private TimelineProxy tu;

    // 角色类型,测试数据用,0:原始,1:新替换的
    private int charType = 0;
    // 灯光类型,测试数据用,0:原始,1:新替换的
    private int lightType = 0;

    #region timeline的播放控制
    // 开始播放timeline
    public void OnStart()
    {
        tu.Play();
    }

    // 停止播放timeline
    public void OnPause()
    {
        tu.Pause();
    }

    // 回复播放timeline
    public void OnResume()
    {
        tu.Resume();
    }
    #endregion

    #region // 运行过程中改变timeline某个轨道的binding
    // 改变角色
    public void OnChangedChar()
    {
        var srcGo = tu.GetBinding("CA") as GameObject;
        var error = false;
        GameObject go = null;

        try
        {
            if (charType == 0)
            {
                go = Object.Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/Chars/MultiCube_Char"));
                charType = 1;
            }
            else
            {

                go = Object.Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/Chars/SingleCube_Char"));
                charType = 0;
            }
            tu.SetBinding("CA", go);
        }
        catch
        {
            error = true;
        }

        if (error == false && srcGo != null)
        {
            CopyTrans(srcGo, go);
            Object.Destroy(srcGo);
        }
    }

    // 改变灯光
    public void OnChangedLight()
    {
        var srcGo = tu.GetBinding("LA") as GameObject;
        var error = false;
        GameObject go = null;

        try
        {
            if (lightType == 0)
            {
                go = Object.Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/Lights/NormalSunDirLight_red"));
                lightType = 1;
            }
            else
            {

                go = Object.Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/Lights/NormalSunDirLight_yellow"));
                lightType = 0;
            }
            tu.SetBinding("LA", go);
        }
        catch
        {
            error = true;
        }

        if (error == false && srcGo != null)
        {
            CopyTrans(srcGo, go);
            Object.Destroy(srcGo);
        }
    }
    #endregion

    private void Awake()
    {
        tu = TimelineHelper.AddTimeline(timelineHolder, "Timelines/TestingTimeline");
        // 设置timeline循环播放
        tu.Director.extrapolationMode = UnityEngine.Playables.DirectorWrapMode.Loop;

        // 设置timeline的CA轨道的绑定对象(CA: Character Animation)
        tu.SetBinding("CA", GameObject.Find("SingleCube_Char"));
        // 设置timeline的LA轨道的绑定对象(LA: Light Animation)
        tu.SetBinding("LA", GameObject.Find("NormalSunDirLight_yellow"));

        var prefab = Resources.Load<GameObject>("Prefabs/Chars/InstaniteGo");

        var asset = tu.GetClip<TestingPlayableAsset>("MyPlayableTrack", "AddObjScript");
        asset.removeObj = GameObject.Find("RemoveSphere1"); // 设置timeline中PlayableBehaviour来测试删除的对象
        asset.opType = OpType.Add;
        asset.prefab = prefab; // 设置timeline中PlayableBehaviour来测试控制的对象(根据opType来控制到底是删除还是添加,当然你也可以另创建一个PlayableBehaviour来分别控制删除还是添加的逻辑)

        asset = tu.GetClip<TestingPlayableAsset>("MyPlayableTrack", "RemoveObjScript");
        asset.removeObj = GameObject.Find("RemoveSphere2"); // 设置timeline中PlayableBehaviour来测试删除的对象
        asset.opType = OpType.Remove;
        asset.prefab = prefab; // 作用同上
    }

    private void OnDestroy()
    {
        if (tu != null)
        {
            tu.Dispose();
            tu = null;
        }
        timelineHolder = null;
    }

    private void CopyTrans(GameObject src, GameObject dest)
    {
        if (src.transform.parent != null)
        {
            dest.transform.parent = src.transform.parent;
            dest.transform.localPosition = src.transform.localPosition;
            dest.transform.localScale = src.transform.localScale;
            dest.transform.localRotation = src.transform.localRotation;
        }
        else
        {
            dest.transform.position = src.transform.position;
            dest.transform.localScale = src.transform.localScale;
            dest.transform.rotation = src.transform.rotation;
        }
    }
}

Reference

unity Timeline封装

猜你喜欢

转载自blog.csdn.net/linjf520/article/details/81365503