黑魂复刻游戏的玩家控制器(跳跃)——Unity随手记

1,065 阅读4分钟

今天实现的内容:

跳跃信号

要实现跳跃,首先要实现跳跃的输入。跳跃信号是按下的当场触发的一次性触发控制(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来作为跳跃条件。

image.png

动画触发目前也很简单。

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

跳跃时锁死Input

为了让跳跃时不能再控制角色旋转,我们需要将Input在跳跃时锁死。好在之前我们就已经实现了输入模块的软开关(详见:魂类游戏的玩家输入模块)。现在的问题就是何时开关输入模块,我们将使用动画状态机StateMachineBehaviour脚本来实现控制。关于StateMachineBehaviour脚本可以往下翻到 值得注意的 部分,里面有讲到。通过下图展示的办法添加一个StateMachineBehaviour脚本。

image.png

以下是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;

    }
}