0%

Timeline跳转过渡

一、Animation Track的过渡

1、背景

当运行到Clip中间时,角色动画会由Timeline接管,即播放AnimationClip中的动画,当运行到Clip之外时,若前后片段中的Animation Extrapolation有设置为Hold,则会保持对应帧的动作,若都为None则会将动画控制权移交给状态机,通常播放Idle的动作。

2、问题

通常在游戏的剧情对话中,用户点击可以跳转到下一段对话,此时会涉及到两个AnimationClip之间的跳转过渡,若直接跳转则会出现动作的突变,即上一动画还没有播放完,就会切到下一动画的开头,影响观感。因此需要进行过渡处理。

3、解决方法

要想实现自然的过渡就需要两个Clip之间的融合,目前实现的方案是在跳转时对未播放完的片段进行克隆,将其移到下一个片段的开头,Timeline就会对两个片段自动融合。需要注意的是,运行时克隆出来的片段在运行完后Timeline不会对其销毁,因此需要将这些克隆片段暂存起来,在合适的时候删除。

4、代码

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
private void AnimationTranstion(int to)
{
DeleteCloneClip(cloneAnimationClips);
foreach (var track in animationClips)
{
if (!track.Value.TryGetValue(curSectionClip, out _)) continue;
if (!cloneAnimationClips.TryGetValue(track.Key, out _))
cloneAnimationClips[track.Key] = new List<TimelineClip>();
foreach (var clip in track.Value[curSectionClip])
{
if (clip.start < director.time && clip.end > director.time)
{
var cloneClip = CloneTimelineAnimationClip(track.Key as AnimationTrack, clip);

cloneClip.start = sectionClips[to].start - (director.time - clip.start);
TimelineClip nextClip = null;
try
{
nextClip = animationClips[track.Key][sectionClips[to]][0];
}
catch (Exception e)
{
}
finally
{
if (nextClip != null && nextClip.start - sectionClips[to].start <= clip.end - director.time)
{
cloneClip.duration = director.time - clip.start + Mathf.Min(new[]
{
(float) (clip.end - director.time),
(float) (nextClip.easeInDuration + nextClip.start - sectionClips[to].start)
});
}
else cloneClip.duration = clip.duration;

cloneAnimationClips[track.Key].Add(cloneClip);
}
}
}
}
}

private static TimelineClip CloneTimelineAnimationClip(AnimationTrack track, TimelineClip nativeClip)
{
var cloneClip = track.CreateClip(nativeClip.animationClip);
cloneClip.start = nativeClip.start;
cloneClip.duration = nativeClip.duration;
cloneClip.easeInDuration = nativeClip.easeInDuration;
cloneClip.easeOutDuration = nativeClip.easeOutDuration;
cloneClip.clipIn = nativeClip.clipIn;
cloneClip.timeScale = nativeClip.timeScale;
cloneClip.blendInDuration = nativeClip.blendInDuration;
cloneClip.blendOutDuration = nativeClip.blendOutDuration;
cloneClip.blendInCurveMode = nativeClip.blendInCurveMode;
cloneClip.blendOutCurveMode = nativeClip.blendOutCurveMode;
return cloneClip;
}

private void DeleteCloneClip(Dictionary<TrackAsset, List<TimelineClip>> dictionary)
{
foreach (var pair in dictionary)
{
foreach (var clip in pair.Value)
{
pair.Key.DeleteClip(clip);
}
}
dictionary.Clear();
}

二、Cinemachine Track的过渡

1、问题背景

和Animation Track略有不同的是,Cinemachine Track在Clip之外会自动将相机控制权交给优先级最高的虚拟相机。

当Clip存在渐变过渡时,实际上是将优先权最高的相机位置和Clip绑定的相机位置进行融合。

2、解决方法

添加一个虚拟相机,将优先级设置为最高,用于缓存真实相机的位置。在发生跳转时,将此时真实相机的位置状态赋给该虚拟相机。跳转后就是该虚拟相机与下一片段的虚拟相机进行融合。

3、代码

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
public CinemachineVirtualCamera cineMachine;
public void CinemachineTranstion()
{
if (director == null) return;
var playableAsset = director.playableAsset as TimelineAsset;
foreach (var track in playableAsset.GetOutputTracks())
{
var binding = director.GetGenericBinding(track);
if (!(track is CinemachineTrack && !track.muted && binding != null))
{
continue;
}

Camera camera = (binding as CinemachineBrain).gameObject.GetComponent<Camera>();
foreach (var clip in track.GetClips())
{
if (clip.start <= director.time && clip.end >= director.time)
{
cineMachine.transform.position = camera.transform.position;
cineMachine.transform.rotation = camera.transform.rotation;
cineMachine.transform.localScale = camera.transform.localScale;
LensSettings lens = cineMachine.State.Lens;
lens.OrthographicSize = camera.orthographicSize;
lens.NearClipPlane = camera.nearClipPlane;
lens.FarClipPlane = camera.farClipPlane;
lens.FieldOfView = camera.fieldOfView;
cineMachine.m_Lens = lens;
}
}
}
}