一、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; } } } }