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

32次阅读

共计 5270 个字符,预计需要花费 14 分钟才能阅读完成。

明天实现的内容:
跳跃信号
要实现跳跃,首先要实现跳跃的输出。跳跃信号是按下的当场触发的一次性触发管制(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 其实性能不太好,前期有优化的余地。

正文完
 0