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

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

https://blog.csdn.net/linjf520/article/details/81365503

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);
        }
    }
}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287

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
}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41

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}",
        });
    }
}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95

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封装
————————————————
版权声明:本文为CSDN博主「linjf520」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/linjf520/article/details/81365503

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值