关于unity:黑魂复刻游戏的玩家控制器跳跃Unity随手记

明天实现的内容:
跳跃信号
要实现跳跃,首先要实现跳跃的输出。跳跃信号是按下的当场触发的一次性触发管制(Trigger Once Signal)。

public class PlayerInput : MonoBehaviour
{
    ...

    // 一次性信号
    public bool jump; //跳跃信号
    public bool lastJump; //记录上一次的jump信号 用于和以后jump信号做比对 了解为是否正在跳跃

    ...

        // Update is called once per frame
    void Update()
    {
        ...
        
        // 获取跳跃键输出
        bool newJump = Input.GetKey(keyJump);
        // 这样只有当按下跳跃键时jump会被设置为true 等同于GetKeyDown
        if (newJump != lastJump && newJump)
        {
            jump = true;
        }
        else
        {
            jump = false;
        }
        lastJump = newJump;
    
    }
}

以上代码如果图便宜能够间接采纳GetKeyDown来省略if判断。if判断的性能就是让jump只有在按下跳跃键的时候(不是按住,也不是松开)才为true。

跳跃动画的利用

动画机配置很简略,新增trigger参数jump来作为跳跃条件。

动画触发目前也很简略。

        // 跳跃动画
        if(pi.jump) anim.SetTrigger("jump");

跳跃时锁死Input
为了让跳跃时不能再管制角色旋转,咱们须要将Input在跳跃时锁死。好在之前咱们就曾经实现了输出模块的软开关(详见:魂类游戏的玩家输出模块)。当初的问题就是何时开关输出模块,咱们将应用动画状态机StateMachineBehaviour脚本来实现管制。对于StateMachineBehaviour脚本能够往下翻到 值得注意的 局部,外面有讲到。通过下图展现的方法增加一个StateMachineBehaviour脚本。

以下是FSMOnEnter 的代码,作用是通过重载OnStateEnter,在动画状态机执行到挂载该脚本的动画状态时向父物体及本身的所有MonoBehavior发送音讯调用名叫msg的办法。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// 应用SendMessage来在OnStateEnter时调用animator.gameObject上的其它办法
public class FSMOnEnter : StateMachineBehaviour
{
    public string[] onEnterMessage;

    // 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 onEnterMessage)
        {
            //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;
    }
}

这样一来,跳跃开始时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脚本。

接下来,退出代码,在每次进入到ground动画状态时reset掉跳跃状态的trigger。

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);
        }
    }
    
    ...
}

值得注意的:
对于StateMachineBehaviour脚本,咱们能够重载它提供的几个办法,OnStateEnter会在刚开始执行这个动画状态时调用,OnStateUpdate会在以后动画状态正在执行的每一个Update帧调用,OnStateExit会在动画状态完结时调用。还有一些别的办法。

这里咱们写了一个脚本FSMClearSignals,专门用于在进入、来到某个状态时革除trigger,咱们当前还会应用到这个脚本

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// 用于Trigger信号的革除工作
public class FSMClearSignals : StateMachineBehaviour
{
    // 进入该动画状态时须要清空的信号
    public string[] signalsClearAtEnter;
    // 来到该动画状态时须要清空的信号
    public string[] signalsClearAtExit;
    
    // 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);
        }
    }

    // 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 signal in signalsClearAtExit)
        {
            animator.ResetTrigger(signal);
        }
    }

}

动画状态机下挂载的StateMachineBehaviour脚本最好少而简略,否则一旦呈现Bug会很难抓。这就是为什么咱们在实现锁死Input时只是用FSMOnEnter来SendMessage,而不是间接用一个StateMachineBehaviour脚本把事件做完了。

咱们在动画状态机脚本中应用SendMessage其实性能不太好,前期有优化的余地。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理