用Timeline实现动画特写(下)

作者:Truly
大渣好。说书人放了无穷久的鸽子之后又回来了。

书接上一回,上一篇介绍了Timeline的部分基础知识,并实现了主角的动画播放,本篇将接着把剩余的功能实现。本篇主要介绍实现的方法,具体的手感参数还需小伙伴们根据自己的动画慢慢调哦(体力活)。与格挡结合后效果图如下:

本篇实现功能:
1.Timeline控制角色位移
2.切换特写镜头
3.触发镜头抖动和播放粒子特效
4.子弹时间
5.动态绑定轨道对象(Binding)
本文不包含格挡与切割内容,有兴趣的小伙伴自行参考底部链接。
一、Timeline Signals
Unity 2019.1版本推出了Timeline Signals功能,具有“广播”特点,帮助发送事件。接下来将通过实现部分功能来熟悉Timeline Signals。
概念速览:
1.Signal Emitter(信号发射器)
信号发射器会放在Timeline上,它包含对信号资源的引用。如果当前播放时间点比发射器所在的时间点后,发射器会把信号资源发送到信号接收器。
(1)添加:对应Track右键 -> Add Signal Emitter

(2)Signal Emitter面板简介

Retroactive: 开始播放时的进度位置在发射器后也能回溯触发。
Emit Once:循环播放时只触发一次。
Create Signal:新建信号资源。
Add Signal Receiver:添加信号接收器。
2.Signal Asset(信号资源)
信号资源是发射器和接收器之间的联系。通常信号资源会用作标识符。
已有Signal信号资源时,新建或切换Signal如下图:

3.Signal Receiver(信号接收器)
当接收器知道信号已被触发时,它会激活关联到对应信号资源的反应。Signal Receiver会自动添加到绑定对象(轨道左侧)上。添加信号反应操作如下图:

大概逻辑:播放到信号发射器处 -> 发射信号 -> 对应信号的接收器激活反应
此外,还可以通过编写Marker脚本自定义标记,有兴趣的小伙伴自行了解。
二、设置被击退位移
上一篇结尾“组合”出敌人被击退的动画,现在开始通过Transform Tween Track和Signals设置击退的位移。(也可以通过点击小红点录制实现位移)
Ps:本文命名只是为了增加辨识度,无其他特殊含义。
1.添加Transform Tween Track
(1)添加Track:Timeline编辑器空白处右键 > Transform Tween Track,命名为ETransformTrack。(Inspector窗口可重命名)
(2)添加Clip:右键该Track > Add Transform Tween Clip,命名为BackTransClip。
(3)用Track Group(空白处右键)把主角和敌人的轨道分别装起来,便于整理。

(4)Transform Tween Clip部分属性简介:

Tween Positon:是否转换Position。
Tween Rotation:是否转换Rotation。
Twee Type(转换类型):Linear线性匀速、Deceleration减速、Harmonic(中间最快)、Custiom自定义。
Start Location:Clip起始的Transform。
End Location:Clip结束的Transform。
(5)给所有轨道左侧绑定框拖进对应的对象。
2.设置Transform Tween Track
在敌人被攻击的时候,通过给Start Location和End Location赋值来实现击退位移动。
(1)方法一(速览即可):脚本中通过层层遍历找到Location,然后为其赋值,有兴趣的小伙伴自行研究。 大概思路如下:

(2)方法二(本文粗暴做法):
①在主角里新建一个空的GameObject,命名为KickEndTrans,作为敌人被击退位移结束的Transform参考。
②另外在Hierarchy再建一个空的GameObject,命名为TempKickEndTrans,拖进BackTransClip的End Location。

③播放BackTransClip(Transform Tween Clip)前通过Signal调用方法,把TempKickEndTrans置到KickEndTrans的方位上,达到设置End Location的效果。(Start Location也如此设置)
代码如下:
/// <summary> /// 设置敌人被击退后的Transform /// </summary> public void OnSignal_SetKickEndTrans()
{
//开始时敌人Transform tempTargetTrans.position = cPlayer.targetTrans.position;// cPlayer.targetTrans为当前敌人Transform tempTargetTrans.rotation = cPlayer.targetTrans.rotation;
//结束时敌人Transform tempKickEndTrans.rotation = kickEndTrans.rotation;
tempKickEndTrans.position = kickEndTrans.position;
}
④使用Signal调用方法:
新建一个Signal Track,在开始击退前的位置添加Signal Emitter,添加信号,命名为Kick。

Hierarchy中新建一个空对象,命名为SignalFunctions,拖进Signal Track左侧绑定框,在SignalFunctions的Inspector窗口可以看到自动添加的Signal Receiver。
在Signal Receiver中选择对应信号,点击“+”添加反应,然后把方法所在对象拖进Reaction里,最后选择需要调用的方法。操作如图:

位移效果:

以上便是用Signal和Transform Tween Track 设置位移的粗暴做法,其他位移也可以如此设置,参数还需小伙伴们根据自己的动画具体情况具体分析。
三、镜头切换
镜头的切换主要是通过Cinemachine Track与Cinemachine配合完成的。
(1)基本概念
Cinemachine核心组件包括Brain和Virtual Camera(虚拟相机):
Brain:负责相机的切换。
Virtual Camera(虚拟相机):负责拍摄。
Cinemachine提供了一些预设好行为方案,方便我们实现游戏中的功能。这次工程轨道中主要使用了Target Group Camera 和普通的Virtrual Camera,另外Timeline播放前人物用的是Free Look Camera(非本文主要介绍范围)。
Target Group Camera:可以在多个拍摄目标之间设置焦点,通过权重调节焦点偏移。
Virtrual Camera:普通的相机。
Free Look Camera:允许玩家控制并围绕目标旋转的相机,可以用作第三人称相机。

(2)设置Target Group Camera
脚踢敌人过程中使用的是Target Group Camera ,以主角和敌人综合得出拍摄焦点。
①添加Target Group Camera后,Hierarchy窗口会出现一个CM vcam和一个对应的TargetGroup。关系如下图:

②调节TargetGroup
拍摄焦点通过目标、对应的权重以及半径综合得出。
Target:拍摄目标,本工程把玩家身上的摄像参考点拖进第一个参数框,通过脚本把敌人的Transform放进第二个参数框。
Weight:权重,用于调节焦点的偏移。
Radius:半径,用于调节相机距离。

在脚本中把敌人的Transform赋值给Target属性第二个参数框,代码如下(在播Timeline前调用),需要using Cinemachine;
using Cinemachine;
//... /// <summary> /// Timeline开始前设置Trans /// </summary> public void SetTransBeforeTL()
{
if (cPlayer.targetTrans)
{
//把敌人Trans赋值到Target Group组件里Target的第二个参数 targetGroup.m_Targets[1].target = cPlayer.targetTrans;
//... }
}
(3)设置Cinemachine Track
①添加Track:Timeline编辑窗口空白处 > 右键 > Cinemachine Track。
②把Main Camera拖进左侧绑定框。
③添加Clip:把所需的cinemachine拖进轨道,按需排序和混合即可,播放时会随着进度自动切换,重叠处会自动过渡。

本工程的切换顺序(仅供参考):Free Look Camera(起始过渡) > Target Group Camera (脚踢过程)> Virtrual Camera(跳劈时在头顶) > Free Look Camera
四、触发镜头抖动与播放粒子特效
1.屏幕抖动
(1)需要用到的组件:
①Cinemachine Impulse Source:发出抖动信号。
②Cinemachine Impulse Listener:收到信号,则镜头抖动。
(2)在Main Camera中添加Cinemachine Impulse Source组件:Inspector > Add Component > Cinemachine Impulse Source。

部分属性:
Raw Signal:原始信号,可理解为抖动类型。使用Unity提供的信号类型:右侧齿轮 > Persets > 选择需要类型。
Amplitude Gain:振幅增益。
Frequency Gain:频率增益。
Impact Radius:影响半径。
(3)在需要抖动的Virtual Camera中添加Cinemachine Impulse Listener:
Inspector > Cinemachine 组件(底部)> Add Extension > CinemachineImpulseListener。

2.粒子特效播放方法
在命中敌人的位置添加粒子特效,然后脚本中获取粒子特效,最后在方法中调用Play()播放。
public ParticleSystem hitRed_Kick;
/// <summary> /// Kick特效 /// </summary> public void OnSignal_SetKickEffect()
{
hitRed_Kick.Play();
}
3.在攻击命中敌人的时候,通过Signal Receiver来触发镜头抖动和调用特效播放方法。
发出信号方法:CinemachineImpulseSource.GenerateImpulse();

五、子弹时间
本次是通过调节时间的缩放来实现子弹时间。
1.添加Time Dilation Track。
2.添加Time Dilation Clip。部分属性:
Time Scale:时间缩放比例,1为正常时间,0为静止。
Blend Curves:混合曲线。

3.调节思路
(1)创建两个Time Dilation Clip:一个Time Scale为0,另一个Time Scale为1。(参数自行调节)
(2)把连个Clip重叠起来,通过调节Clip重叠长度和混合曲线,达到自己想要的效果。

六、动态绑定轨道对象
终结不同的敌人代表着动画的执行角色不同,因此需要在脚本中为轨道更换绑定的目标。
1.添加事件
(1)相关变量:
using System.Collections;using System.Collections.Generic;using UnityEngine;using UnityEngine.Playables;using UnityEngine.Timeline;
public class PAnimation : MonoBehaviour{
CPlayer cPlayer;
PSlicer pSlicer; //切割脚本
Animator animator;
Animator targetAnimator;
RuntimeAnimatorController player_AC; //记录玩家AnimatorController RuntimeAnimatorController target_AC; //记录目标AnimatorController
public PlayableDirector playableDirector; //TimelineDirector; public TimelineAsset tL_StrikeBack; //反击的TimelineAsset
//信号相关 SignalFunctions signalFunctions; //信号方法脚本 Dictionary<string, PlayableBinding> bindingDict; //存储 //...}
(2)事件
①PlayableDirector.played:在PlayableDirector 开始播放时调用。
②PlayableDirector.stopped:在PlayableDirector停止播放时调用。
void Start()
{
// ... //添加事件 playableDirector.played += OnPlayableDirectorStart; //开播时执行 playableDirector.stopped += OnPlayableDirectorStopped; //停播时执行 }
2.释放技能时:
(1)指定播放片段(TimelineAsset)。
public void PlayTimeline()
{
//指定TimelineAsset(播放片段) playableDirector.playableAsset = tL_StrikeBack;
//储存Binding SaveBindings();
playableDirector.Play();
}
(2)用字典储存每个轨道:PlayableBinding.streamName 获取轨道名字,作为字典的Key。

代码如下(需要using UnityEngine.Playables;和using UnityEngine.Timeline;):
using UnityEngine.Playables;using UnityEngine.Timeline;//... //储存绑定的Track左侧,换新TimelineAsset时调用 public void SaveBindings()
{
foreach (PlayableBinding binding in playableDirector.playableAsset.outputs)
{
if (!bindingDict.ContainsKey(binding.streamName))
{
bindingDict.Add(binding.streamName, binding);
}
}
}
3.Timeline播放前
(1)置空AnimatorController避免播放TimeLine过程中执行其他状态。(也可在动画状态机中添加表示正在播放Timeline的状态来解决)
(2)通过检索字典给轨道绑定对象:
绑定轨道左侧对象:PlayableDirector.GetGenericBinding(Object key, Object value);。
Key:可用PlayableBinding.sourceObject获取。
Value:需要绑定的对象。
代码如下(含注释):
/// <summary> /// 播放前执行 /// </summary> /// <param name="aDirector"></param> void OnPlayableDirectorStart(PlayableDirector aDirector)
{
cPlayer.characterController.enabled = false; //关闭CharacterController,不然有冲突 animator.applyRootMotion = true; //应用RootMotion
//暂存AnimatorController player_AC = animator.runtimeAnimatorController;
//把AnimatorController暂时置空,避免播Timeline时动画状态机还在执行其他状态。 animator.runtimeAnimatorController = null;
//敌人AnimatorController暂时置空,播完置回去。 targetAnimator = cPlayer.targetTrans.GetComponent<Animator>();
target_AC = targetAnimator.runtimeAnimatorController;
targetAnimator.runtimeAnimatorController = null;
//把玩家Animator绑进轨道 playableDirector.SetGenericBinding(bindingDict["PlayerAniTrack"].sourceObject, animator);
playableDirector.SetGenericBinding(bindingDict["PTransformTrack"].sourceObject, animator.transform);
//把敌人Animator绑进轨道 playableDirector.SetGenericBinding(bindingDict["EnemyAniTrack"].sourceObject, cPlayer.targetTrans.GetComponent<Animator>());
playableDirector.SetGenericBinding(bindingDict["ETransformTrack"].sourceObject, cPlayer.targetTrans);
//Clip 参考Trans设置 signalFunctions.SetTransBeforeTL();
}
4.动画播放完后,用PlayableDirector.Evaluate()估算Timeline中各对象,否则会回到开播的位置,然后把播放的TimelineAsset置为null。代码如下(含注释):
/// <summary> /// 播放完执行 /// </summary> /// <param name="aDirector"></param> void OnPlayableDirectorStopped(PlayableDirector aDirector)
{
playableDirector.Evaluate(); //评估对象所在位置,否则回到播放前位置 playableDirector.playableAsset = null; //playableDirector里的TimlineAsset置Null
//重置玩家播放Timeline前的状态 cPlayer.characterController.enabled = true;
cPlayer.isStrikingBack = false;
animator.applyRootMotion = false;
animator.runtimeAnimatorController = player_AC;
//重置敌人播放Timeline前的状态 if (targetAnimator)
{
targetAnimator.runtimeAnimatorController = target_AC;
//... }
}
5.踩过的坑
(1)角色身上CharacterController在激活状态,播放Timeline时角色会不能位移。
(2)结束播放时,没有执行PlayableDirector.Evaluate(),角色回到开始播放位置。
(3)播放结束,没有把PlayableDirector.playableAsset置空,非Apply Root Motion时角色不能移动。
结语:本文结合了Timeline Signals和各种Track实现了一系列的功能,虽然已花了较长时间查询资料,但是仍存在很多不完善的地方,时间关系只好先介绍到这里(自己开的坑,头发掉满地也只能含泪勉强填完)。希望对大家初步了解Timeline有所启发,如有问题欢迎讨论。

相关资料
1.Timeline Signals:https://connect.unity.com/p/shi-yong-unity-2019-1zhong-de-timeline-signals
2.Timeline动态赋值:https://zhuanlan.zhihu.com/p/29585350
3.Ezy-Slice切割教程:https://www.bilibili.com/video/av65373505
4.从SkinnedMeshRenderer获取正确显示的普通Mesh:https://blog.csdn.net/Traip/article/details/88095446
5.格挡:https://zhuanlan.zhihu.com/p/83607025
链接:https://pan.baidu.com/share/init?surl=K1RX_qmmPfcjQazJHpNBJA
提取码:stll

咱们的游戏开发交流群也欢迎强势插入:869551769
希望参与线下游戏开发学习的,欢~~~~~~迎访问:http://www.levelpp.com/