跳跃信号
要实现跳跃,首先要实现跳跃的输入。跳跃信号是按下的当场触发的一次性触发控制(Trigger Once Signal),这个信号转化为编程语言就是bool值,通过控制真还是假判定是否按下跳跃键
public class PlayerInput : MonoBehaviour
{
...
// 一次性信号
public bool jump; //跳跃信号
public bool lastJump; //记录上一次的jump信号 用于和当前jump信号做比对 理解为是否正在跳跃
...
void Update()
{
bool newJump = Input.GetKey(KeyB);
//判断跟上一个状态是否一致,会出现由1.没按下到按下 2.按下到松开两种情况(Blastjump是true时,这种排除)
if (newJump != Blastjump&&Blastjump != true)
{
Bjump = true;
}
else
{
Bjump = false;
}
//上一个状态
Blastjump = newJump;
}
Input.GetKey 可以使用 GetKeyDown按下的时候Bjump就是true,从而忽略if判断语句
跳跃动画的应用
使用trriger参数作为触发跳跃动画的条件
if (pi.Bjump == true)
{
anim.SetBool("jump", true);
}
跳跃时应该锁死用户输入
为了让跳跃时不能再控制角色旋转,我们需要将Input在跳跃时锁死。之前我们就已经实现了输入模块的软开关(inputEnabled)。现在的问题就是何时开关输入模块,我们将使用动画状态机StateMachineBehaviour脚本来实现控制。如下图,脚本的创建方法,需要注意:不能将大量的逻辑代码写入其中。
- 脚本的作用主要是当进入或者退出这个状态时就会触发相应的语句
以下是FSMOnEnter 的代码,作用是通过重载OnStateEnter,在动画状态机执行到挂载该脚本的动画状态时向父物体及自身的所有MonoBehavior发送消息调用名叫msg的方法。
- SendMessageUpwards时给父物体、SendMessage是给本物体
public class FSMOnEnter : StateMachineBehaviour
{
public string[] onEnterMessages;
// OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
foreach (var msg in onEnterMessages) {
//animator.gameObject.SendMessage(msg);
animator.gameObject.SendMessageUpwards(msg);
}
}
}
我们的思路就是,在跳跃动画状态开始,系统调用OnStateEnter方法时向animator所在的gameObject发送信息来调用其它脚本的方法OnJumpEnter,再通过OnJumpEnter来关闭输入模块,至于为什么这么做,因为StateMachineBehaviour脚本里面做的事越少越好,否则出问题了不好找。
public class PlayerController : MonoBehaviour
{
// ...
// 跳跃时执行的方法 通过FSMOnEnter中的SendMessage调用
public void OnJumpEnter()
{
// 关闭输入模块
pi.inputEnabled = false;
}
}
同理,在跳跃动画状态结束,系统调用OnStateExit时发送信息再将输入模块打开就行。同样我们再做一个StateMachineBehaviour脚本FSMOnExit。结构与FSMOnEnter一致。改为重载OnStateExit方法就行。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// 使用SendMessage来在OnStateExit时调用animator.gameObject上的其它方法
public class FSMOnExit : StateMachineBehaviour
{
// 要调用的方法列表
public string[] onExitMessage;
// OnStateExit is called when a transition ends and the state machine finishes evaluating this state
override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
// 对于每一个方法 发送信息调用他们
foreach (var msg in onExitMessage)
{
//animator.gameObject.SendMessage(msg);
animator.gameObject.SendMessageUpwards(msg);
}
}
}
同样在PlayerController中定义方法OnJumpExit。
public class PlayerController : MonoBehaviour
{
// ...
// 跳跃时执行的方法 通过FSMOnExit中的SendMessage调用
public void OnJumpExit()
{
// 打开输入模块
pi.inputEnabled = true;
}
}
在关闭输入模块以后,我们的位移也就终止了,因为我们的软开关是将输入设置为0来达成的,为了保证跳跃时依旧拥有原来的水平方向位移,我们需要在跳跃时将水平方向的位移量锁死,也就是不再计算新的位移量。
public class PlayerController : MonoBehaviour
{
// ...
// 角色的平面位移大小
private Vector3 planarVec; //以前叫movingVec 改名了
// 是否锁死平面移动
private bool lockPlanar = false;
// Update is called once per frame
void Update()
{
// ...
// 计算移动量 速度向量
if(!lockPlanar) planarVec = pi.dirMag * model.transform.forward * walkSpeed * (pi.run ? runMultiplier : 1.0f);
}
// 跳跃时执行的方法 通过FSMOnEnter中的SendMessage调用
public void OnJumpEnter()
{
// 关闭输入模块
pi.inputEnabled = false;
lockPlanar = true;
}
// 跳跃时执行的方法 通过FSMOnExit中的SendMessage调用
public void OnJumpExit()
{
// 打开输入模块
pi.inputEnabled = true;
lockPlanar = false;
}
}
这样子的话,按下j跳跃开始后,planarVec就会维持在上一帧,直到跳跃结束才开始计算新的值。
跳跃冲量
为了让玩家能够真正的拥有y方向的速度,需要给planarVec一个冲量
public class PlayerController : MonoBehaviour
{
// ...
// 跳跃速度
public float jumpVelocity = 5.0f;
// 角色的跳跃冲量
private Vector3 thrustVec;
// 处理刚体的操作
private void FixedUpdate()
{
// 直接修改position实现位移
rigidbody.position += planarVec * Time.fixedDeltaTime;
rigidbody.velocity += thrustVec;
thrustVec = Vector3.zero;
}
// 跳跃时执行的方法 通过FSMOnEnter中的SendMessage调用
public void OnJumpEnter()
{
// ...
// 计算跳跃的向上冲量
thrustVec.y = jumpVelocity;
}
}
其中的一些Bug
当你快速连续按两次跳跃键,跳跃也会紧挨着触发两次。也就是说,trigger会累积起来。要处理这个问题,我们可以通过在动画节点上添加一个新的StateMachineBehaviour脚本。
在FSMClearSignals 中重载OnStateEnter将参数消除掉(animator.ResetTrigger(参数名))
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// 用于Trigger信号的清除工作
public class FSMClearSignals : StateMachineBehaviour
{
// 进入该动画状态时需要清空的信号
public string[] signalsClearAtEnter;
...
// OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
foreach (var signal in signalsClearAtEnter)
{
animator.ResetTrigger(signal);
}
}
...
}
不足:我们在动画状态机脚本中使用SendMessage其实性能不太好,后期有优化的余地。