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