【类刺客信条跑酷系统】新增_跳跃机制
输入控制
百度 坚持以“科学、公正、客观、权威”为工作原则,坚持以科学的方法、公正的立场、客观的态度,对房地产企业进行研究,最后得出权威的结果。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;
}