【类刺客信条跑酷系统】新增_跳跃机制
输入控制
PlayerController
    //是否跳跃
    public bool jump;
重构输入控制
        #region 角色输入控制
        #region 水平方向
        float h = Input.GetAxis("Horizontal");
        float v = Input.GetAxis("Vertical");
        //把moveAmount限制在0-1之间(混合树的区间)
        float moveAmount = Mathf.Clamp01(Mathf.Abs(h) + Mathf.Abs(v));
  
        //标准化 moveInput 向量
        var moveInput = new Vector3(h, 0, v).normalized;
        if(inputEnabled == false){
            h = 0;
            v = 0;
        }
        //让人物期望移动方向关联相机的水平旋转朝向
        //  这样角色就只能在水平方向移动,而不是相机在竖直方向的旋转量也会改变角色的移动方向
        desireMoveDir = cameraController.PlanarRotation * moveInput;
        //让当前角色的移动方向等于期望方向
        moveDir = desireMoveDir;
        planarVelocity = Vector3.zero;
        #endregion
        #region 竖直方向——Jump
        if(!InAction)
            jump = Input.GetButton("Jump");
添加动画
        if(jump && isGrounded){
            animator.SetTrigger("jump");
        }


重置signal——解决信号一直叠加导致的连跳

脚本加在状态机地面行走的locomotion上面
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FSMClearSignals : StateMachineBehaviour
{
    public string[] clearAtEnter;
    public string[] clearAtExit;
    // 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 clearAtEnter)
        {
            animator.ResetTrigger(signal);
        }
    }
    // OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks
    //override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    //{
    //  
    //}
    // 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 clearAtEnter)
        {
            animator.ResetTrigger(signal);
        }
    }
    // OnStateMove is called right after Animator.OnAnimatorMove()
    //override public void OnStateMove(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    //{
    //    // Implement code that processes and affects root motion
    //}
    // OnStateIK is called right after Animator.OnAnimatorIK()
    //override public void OnStateIK(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    //{
    //    // Implement code that sets up animation IK (inverse kinematics)
    //}
}
套接出FSM On Enter&Exit方法
加在状态机的Jump上

    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 (string message in onEnterMessages)
        {
            animator.SendMessageUpwards(message);
        }
    }
    public string[] onExitMessages;
    // OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
    override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        foreach (string message in onExitMessages)
        {
            animator.SendMessageUpwards(message);
        }
    }
这样就可以在PlayerJumpController.cs中写起跳时的人物移动逻辑
会传到Jump下的两个StateMachineBehaviour脚本中
    public void OnJumpEnter(){
        Debug.Log("起跳");
    }
    public void OnJumpExit(){
        Debug.Log("落地"); 
    }
锁死平面移动——限制空中转向
PlayerController
//输入控制
public bool inputEnabled = true;
public bool lockPlannar;
    public void OnJumpEnter(){
        Debug.Log("起跳");
        playerController.inputEnabled = false;
        lockPlannar = true;
    }
    public void OnJumpExit(){
        Debug.Log("落地"); 
        playerController.inputEnabled = true ;
        lockPlannar = false;
    }
        #region 角色输入控制
        #region 水平方向
        float h = Input.GetAxis("Horizontal");
        float v = Input.GetAxis("Vertical");
        //把moveAmount限制在0-1之间(混合树的区间)
        float moveAmount = Mathf.Clamp01(Mathf.Abs(h) + Mathf.Abs(v));
  
        //标准化 moveInput 向量
        var moveInput = new Vector3(h, 0, v).normalized;
        if(inputEnabled == false){
            h = 0;
            v = 0;
        }
        //让人物期望移动方向关联相机的水平旋转朝向
        //  这样角色就只能在水平方向移动,而不是相机在竖直方向的旋转量也会改变角色的移动方向
        desireMoveDir = cameraController.PlanarRotation * moveInput;
        //让当前角色的移动方向等于期望方向
        if (lockPlannar == false)
            moveDir = desireMoveDir;
关于最后的跳跃锁死,也可以不用
        if (lockPlannar == false)
            moveDir = desireMoveDir;
跳起速度
    [SerializeField] float jumpSpeed;
        #region 地面检测
        GroundCheck();
        animator.SetBool("isGrounded", IsGrounded);
        if (IsGrounded)
        {
            //设置一个较小的负值,让角色在地上的时候被地面吸住
            //只有在没有跳跃的情况下才重置ySpeed,避免跳跃被覆盖
            if (!jump)
            {
                ySpeed = -0.5f;
            }
            else
            {
                animator.SetBool("jump", jump);
                ySpeed = jumpSpeed;
                jump = false;
            }
            //在地上的速度只需要初始化角色期望方向的速度就行,只有水平分量
            planarVelocity = desireMoveDir * moveSpeed;
效果如下:

最后记得把ParkourController里的JumpDown对应按键修改了,不然冲突
            //只有高度大于autoJumpHeight 且 玩家按下Drop键才会跳下悬崖
            if (playerController.LedgeHitData.height > autoJumpDownHeight && !Input.GetButtonDown("Drop")){
                shouldJump = false;
            }
该部分完整代码:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
    [Header("玩家属性")]
    [SerializeField] float moveSpeed = 7f;
    [SerializeField] float rotationSpeed = 500f;
    [SerializeField] float jumpSpeed;
    [Header("Ground Check")]
    [SerializeField] float groundCheckRadius = 0.5f;
    //检测射线偏移量
    [SerializeField] Vector3 groundCheckOffset;
    [SerializeField] LayerMask groundLayer;
    //是否在地面
    public bool IsGrounded { get; set; }
    //是否拥有控制权:默认拥有控制权,否则角色初始就不受控
    bool hasControl = true;
    //输入控制
    public bool inputEnabled = true;
    //是否在动作中
    public bool InAction { get; private set; }
    //是否在攀岩中
    public bool IsHanging { get; set; }
    //是否跳跃
    public bool jump;
    public bool lockPlannar;
    //moveDir、velocity改成全局变量
    //当前角色的移动方向,这是实时移动方向,只要输入方向键就会更新
    Vector3 moveDir;
    //角色期望的移动方向,这个期望方向是和相机水平转动方向挂钩的,与鼠标或者手柄右摇杆一致
    Vector3 desireMoveDir;
    //水平方向的速度
    Vector3 planarVelocity;
    //竖直方向的跳跃冲量
    Vector3 thrustVelocity;
    //是否在悬崖边沿上
    public bool IsOnLedge { get; set; }
    //悬崖边沿击中相关数据
    public LedgeHitData LedgeHitData { get; set; }
    float ySpeed;
    Quaternion targetRotation;
    CameraController cameraController;
    Animator animator;
    CharacterController charactercontroller;
    EnvironmentScanner environmentScanner;
    Rigidbody rigid;
    MeleeFighter meleeFighter;
    private void Awake()
    {
        //相机控制器设置为main camera
        cameraController = Camera.main.GetComponent<CameraController>();
        //角色动画
        animator = GetComponent<Animator>();
        //角色控制器
        charactercontroller = GetComponent<CharacterController>();
        //环境扫描器
        environmentScanner = GetComponent<EnvironmentScanner>();
        //
        rigid = GetComponent<Rigidbody>();
        //近战
        meleeFighter = GetComponent<MeleeFighter>();
    }
    private void Update()
    {
        //如果没有控制权,后面的就不执行了
        if (!hasControl)
        {
            return;
        }
        //如果在动作中,不执行后面的运动逻辑并且不播放走路动画
        if (IsHanging || InAction || meleeFighter.InAtkAction)
        {
            animator.SetFloat("moveAmount", 0);
            return;
        }
        #region 角色输入控制
        #region 水平方向
        float h = Input.GetAxis("Horizontal");
        float v = Input.GetAxis("Vertical");
        //把moveAmount限制在0-1之间(混合树的区间)
        float moveAmount = Mathf.Clamp01(Mathf.Abs(h) + Mathf.Abs(v));
  
        //标准化 moveInput 向量
        var moveInput = new Vector3(h, 0, v).normalized;
        if(inputEnabled == false){
            h = 0;
            v = 0;
        }
        //让人物期望移动方向关联相机的水平旋转朝向
        //  这样角色就只能在水平方向移动,而不是相机在竖直方向的旋转量也会改变角色的移动方向
        desireMoveDir = cameraController.PlanarRotation * moveInput;
        //让当前角色的移动方向等于期望方向
        //if (lockPlannar == false)
        moveDir = desireMoveDir;
        planarVelocity = Vector3.zero;
        #endregion
        #region 竖直方向——Jump
        jump = Input.GetButton("Jump");
        #endregion
        #endregion
        #region 地面检测
        GroundCheck();
        animator.SetBool("isGrounded", IsGrounded);
        if (IsGrounded)
        {
            //设置一个较小的负值,让角色在地上的时候被地面吸住
            //只有在没有跳跃的情况下才重置ySpeed,避免跳跃被覆盖
            if (!jump)
            {
                ySpeed = -0.5f;
            }
            else
            {
                animator.SetBool("jump", jump);
                ySpeed = jumpSpeed;
                jump = false;
            }
            //在地上的速度只需要初始化角色期望方向的速度就行,只有水平分量
            planarVelocity = desireMoveDir * moveSpeed;
            #region 悬崖检测
            //在地上的时候进行悬崖检测,传给isOnLedge变量
            IsOnLedge = environmentScanner.ObstacleLedgeCheck(desireMoveDir, out LedgeHitData ledgeHitData);
            //如果在悬崖边沿,就把击中数据传给LedgeHitData变量,用来在ParkourController里面调用
            if (IsOnLedge)
            {
                LedgeHitData = ledgeHitData;
                //调用悬崖边沿移动限制
                LedgeMovement();
                //  Debug.Log("On Ledge");
            }
            #endregion
            //在地面上,速度只有水平分量
            #region 角色动画控制
            //  dampTime是阻尼系数,用来平滑动画
            //这里不应该根据输入值赋值给BlendTree动画用的moveAmount参数
            //因为动画用的moveAmount参数只需要水平方向的移动量就行了,不需要考虑y轴
            //那么也就不需要方向,只需要值
            //所以传入归一化的 velocity.magnitude / moveSpeed就行了
            animator.SetFloat("moveAmount", planarVelocity.magnitude / moveSpeed, 0.2f, Time.deltaTime);
            #endregion
        }
        else
        {
            //在空中时,ySpeed受重力控制
            ySpeed += Physics.gravity.y * Time.deltaTime;
            //简单模拟有空气阻力的平抛运动:空中时的速度设置为角色朝向速度的一半
            planarVelocity = transform.forward * moveSpeed / 2;
        }
        #endregion
        #region 角色控制器控制
        //更新y轴方向的速度
        planarVelocity.y = ySpeed;
        //先检查角色控制器是否激活
        if (charactercontroller.gameObject.activeSelf && charactercontroller.enabled && hasControl)
        {
            //帧同步移动
            //通过CharacterController.Move()来控制角色的移动,通过碰撞限制运动
            charactercontroller.Move(planarVelocity* Time.deltaTime);
        }
        //每次判断moveAmount的时候,确保只有在玩家实际移动时才会更新转向
        //没有输入并且移动方向角度小于0.2度就不更新转向,也就不会回到初始朝向
        //moveDir.magnitude > 0.2f 避免了太小的旋转角度也会更新
        if (moveAmount > 0 && moveDir.magnitude > 0.2f)
        {
            //人物模型转起来:让目标朝向与当前移动方向一致
            targetRotation = Quaternion.LookRotation(moveDir);
        }
        //更新transform.rotation:让人物从当前朝向到目标朝向慢慢转向
        transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation,
                         rotationSpeed * Time.deltaTime);
        #endregion
    }
    /// <summary>
    /// 起跳和落地角色控制权
    /// </summary>
    public void OnJumpEnter()
    {
        Debug.Log("起跳");
        inputEnabled = false;
        lockPlannar = true;
    }
    public void OnJumpExit()
    {
        Debug.Log("落地");
        inputEnabled = true;
        lockPlannar = false;
    }

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号