找回密码
 立即注册
首页 业界区 业界 类蜘蛛侠+刺客信条暗杀动作系统开发日志 ...

类蜘蛛侠+刺客信条暗杀动作系统开发日志

辜酗徇 2025-8-18 17:24:44
新版输入系统——斜向移动变快问题解决

1.png

生成对应的input管理脚本
2.png

Day 01——角色移动基类

CharacterMovementControlBase
  1. using UnityEngine;
  2. namespace Spiderman.Movement
  3. {
  4.     [RequireComponent(typeof(CharacterController))]
  5.     public abstract class CharacterMovementControlBase : MonoBehaviour
  6.     {
  7.         // 角色控制器组件,用于处理角色移动相关的物理交互
  8.         private CharacterController _controller;
  9.         // 动画组件,用于控制角色动画播放
  10.         private Animator _animator;
  11.         // 地面检测相关变量
  12.         protected bool _characterIsOnGround;
  13.         [Header("地面检测相关变量")]
  14.         [SerializeField]protected float _groundDetectionPositionOffset; // 地面检测位置偏移量
  15.         [SerializeField]protected float _detectionRang;                 // 地面检测范围
  16.         [SerializeField]protected LayerMask _whatIsGround;              // 地面层掩码
  17.         // 重力相关变量
  18.         protected readonly float CharacterGravity = -9.8f;
  19.         protected float _characterVerticalVelocity;     // 角色垂直方向速度
  20.         protected float _fallOutDeltaTime;              // 下落 delta 时间,用于计算重力作用的时间积累
  21.         protected float _fallOutTime = 0.15f;           // 下落等待时间,控制跌落动画播放时机
  22.         protected readonly float _characterVerticalMaxVelocity = 54f; // 角色最大垂直速度,低于这个值应用重力
  23.         protected Vector3 _characterVerticalDirection;  // 角色Y轴移动方向,通过charactercontroller.move来实现y轴移动
  24.         // 初始化函数,在对象实例化后、Start 之前调用,获取必要组件
  25.         protected virtual void Awake()
  26.         {
  27.             _controller = GetComponent<CharacterController>();
  28.             _animator = GetComponent();
  29.         }
  30.         protected virtual void Start()
  31.         {
  32.             _fallOutDeltaTime = _fallOutTime;
  33.         }
  34.         private void Update()
  35.         {
  36.             SetCharacterGravity();
  37.             UpdateCharacterGravity();
  38.         }
  39.         /// <summary>
  40.         /// 地面检测方法
  41.         /// </summary>
  42.         /// <returns>返回角色是否在地面的布尔值</returns>
  43.         private bool GroundDetection()
  44.         {
  45.             // 构建检测位置:基于角色当前位置,调整 Y 轴偏移(用于地面检测的位置修正)
  46.             Vector3 detectionPosition = new Vector3(
  47.                 transform.position.x,
  48.                 transform.position.y - _groundDetectionPositionOffset,
  49.                 transform.position.z
  50.             );
  51.             // 球形检测:检查在指定位置、指定半径范围内,与 _whatIsGround 层的碰撞体是否存在相交
  52.             // 参数分别为:检测中心、检测半径、地面层掩码、忽略触发器交互
  53.             return Physics.CheckSphere(
  54.                 detectionPosition,
  55.                 _detectionRang,
  56.                 _whatIsGround,
  57.                 QueryTriggerInteraction.Ignore
  58.             );
  59.         }
  60.         /// <summary>
  61.         /// 根据是否在地面设置对应的角色重力逻辑
  62.         /// </summary>
  63.         private void SetCharacterGravity()
  64.         {
  65.             // 检测角色是否在地面
  66.             _characterIsOnGround = GroundDetection();
  67.             if (_characterIsOnGround)
  68.             {
  69.                 //1.在地面
  70.                 // 1.1 重置下落等待时间
  71.                 _fallOutDeltaTime = _fallOutTime;
  72.                 // 1.2 重置垂直速度(防止落地后持续累积速度)
  73.                 if (_characterVerticalVelocity < 0)
  74.                 {
  75.                     _characterVerticalVelocity = -2f;
  76.                 }
  77.             }
  78.             else
  79.             {
  80.                 //2.不在地面
  81.                 if (_fallOutDeltaTime > 0)
  82.                 {
  83.                     // 2.1 处理楼梯/小落差:等待 0.15 秒后再应用重力
  84.                     _fallOutDeltaTime -= Time.deltaTime;
  85.                 }
  86.                 else
  87.                 {
  88.                     // 2.2 倒计时结束还没有落地?那说明不是小落差,要开始应用重力
  89.                 }
  90.                 if (_characterVerticalVelocity < _characterVerticalMaxVelocity)
  91.                 {
  92.                     _characterVerticalVelocity += CharacterGravity * Time.deltaTime;
  93.                     // 重力公式累积垂直速度
  94.                 }
  95.             }
  96.         }
  97.         /// <summary>
  98.         /// 更新角色垂直方向移动(应用重力效果)
  99.         /// </summary>
  100.         private void UpdateCharacterGravity()
  101.         {
  102.             //这里只处理 y 轴重力
  103.             // x/z 由其他移动逻辑控制
  104.             Vector3 _characterVerticalDirection = new Vector3(0, _characterVerticalVelocity, 0);
  105.             // 通过 CharacterController 应用y轴移动
  106.             _controller.Move(_characterVerticalDirection * Time.deltaTime);
  107.         }
  108.         /// <summary>
  109.         /// 斜坡方向重置:检测角色是否在坡上移动,防止下坡速度过快导致异常
  110.         /// </summary>
  111.         /// <param name="moveDirection">原始移动方向</param>
  112.         /// <returns>适配斜坡后的移动方向</returns>
  113.         private Vector3 SlopResetDirection(Vector3 moveDirection)
  114.         {
  115.             // 射线检测参数配置
  116.             Vector3 rayOrigin = transform.position + transform.up * 0.5f;   // 射线起点
  117.             Vector3 rayDirection = Vector3.down;                            // 射线方向
  118.             float maxDistance = _controller.height * 0.85f;                 // 射线最大距离
  119.             LayerMask targetLayer = _whatIsGround;                          // 检测的目标地面层
  120.             QueryTriggerInteraction triggerInteraction = QueryTriggerInteraction.Ignore; // 忽略触发器
  121.             // 执行向下的射线检测
  122.             if (Physics.Raycast(rayOrigin, rayDirection, out RaycastHit hit, maxDistance, targetLayer, triggerInteraction))
  123.             {
  124.                 // 点积判断:检测地面法线是否与角色上方向垂直(点积接近0表示垂直,非0则说明有坡度)
  125.                 if (Vector3.Dot(transform.up, hit.normal) != 0)
  126.                 {
  127.                     // 将移动方向投影到斜坡平面
  128.                     moveDirection = Vector3.ProjectOnPlane(moveDirection, hit.normal);
  129.                 }
  130.             }
  131.             return moveDirection;
  132.         }
  133.         private void OnDrawGizmos()
  134.         {
  135.             // 设置gizmos颜色为红色,使其更容易看到
  136.             Gizmos.color = Color.red;
  137.   
  138.             Vector3 detectionPosition = new Vector3(
  139.                 transform.position.x,
  140.                 transform.position.y - _groundDetectionPositionOffset,
  141.                 transform.position.z
  142.             );
  143.             Gizmos.DrawWireSphere(detectionPosition, _detectionRang);
  144.         }
  145.     }
  146. }
复制代码
PlayerMovementControl
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. namespace Spiderman.Movement
  5. {
  6.     public class PlayerMovementControl : CharacterMovementControlBase
  7.     {
  8.     }
  9. }
复制代码
Day02 带碰撞体相机脚本

GameInputManager
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class GameInputManager : MonoBehaviour
  5. {
  6.     private GameInputAction _gameInputAction;
  7.     public Vector2 Movement => _gameInputAction.Player.Movement.ReadValue<Vector2>();
  8.     public Vector2 CameraLook => _gameInputAction.Player.CameraLook.ReadValue<Vector2>();
  9.     private void Awake()
  10.     {
  11.         _gameInputAction ??= new GameInputAction(); //是空的,则创建新的实例
  12.     }
  13.     private void OnEnable()
  14.     {
  15.         _gameInputAction.Enable();
  16.     }
  17.     private void OnDisable()
  18.     {
  19.         _gameInputAction.Disable();
  20.     }
  21. }
复制代码
3.png

4.png

5.png

TP_CameraControl
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class TP_CameraControl : MonoBehaviour
  5. {
  6.     private GameInputManager _gameInputManager;
  7.     [Header("相机参数配置")]
  8.     [SerializeField] private Transform _lookTarget;             //相机跟随目标
  9.     [SerializeField] private float _controlSpeed;               //相机移动速度
  10.     [SerializeField] private Vector2 _cameraVerticalMaxAngle;   //相机上下旋转角度限制
  11.     [SerializeField] private Vector2 _cameraHorizontalMaxAngle; //相机左右旋转角度限制
  12.     [SerializeField] private float _smoothSpeed;                //平滑速度
  13.     [SerializeField] private float _cameraDistance;             //相机到跟随目标的距离
  14.     [SerializeField] private float _cameraHeight;               //相机高度
  15.     [SerializeField] private float _DistancemoothTime;         //位置跟随平滑时间
  16.     private Vector3 smoothDampVelocity = Vector3.zero;          //旋转阻尼
  17.     private Vector2 _input;                                     // 输入值
  18.     private Vector3 _cameraRotation;                            // 相机旋转方向
  19.     private bool _cameraInputEnabled = true;                    // 相机输入是否启用
  20.     private void Awake()
  21.     {
  22.         // 获取游戏输入管理组件
  23.         _gameInputManager = GetComponent<GameInputManager>();
  24.         //隐藏光标
  25.         Cursor.lockState = CursorLockMode.Locked;
  26.         Cursor.visible = false;
  27.     }
  28.     private void Update()
  29.     {
  30.         // 检测到按下ESC键或鼠标左键点击窗口,则切换相机输入状态
  31.         HandleCameraInputToggle();
  32.         // 只有在相机输入启用时才处理输入
  33.         if (_cameraInputEnabled)
  34.         {
  35.             // 实时处理相机输入
  36.             CameraInput();
  37.         }
  38.     }
  39.     private void LateUpdate()
  40.     {
  41.         // 更新相机旋转
  42.         UpdateCameraRotation();
  43.         // 更新相机位置
  44.         UpdateCameraPosition();
  45.     }
  46.     /// <summary>
  47.     /// 处理相机输入,获取并处理上下查看等输入,限制垂直角度范围
  48.     /// </summary>
  49.     private void CameraInput()
  50.     {
  51.         // 获取相机xy轴输入
  52.         _input.y += _gameInputManager.CameraLook.x * _controlSpeed;
  53.         _input.x -= _gameInputManager.CameraLook.y * _controlSpeed;
  54.         // 限制相机垂直方向角度范围,垂直方向是绕 x 轴旋转,所以平滑的是x轴输入
  55.         _input.x = Mathf.Clamp(
  56.             _input.x,
  57.             _cameraVerticalMaxAngle.x,
  58.             _cameraVerticalMaxAngle.y
  59.         );
  60.         // 限制相机水平方向角度范围,水平方向是绕 y 轴旋转,所以限制的是y轴输入
  61.         _input.y = Mathf.Clamp(
  62.             _input.y,
  63.             _cameraHorizontalMaxAngle.x,
  64.             _cameraHorizontalMaxAngle.y
  65.         );
  66.     }
  67.     /// <summary>
  68.     /// 更新相机旋转
  69.     /// </summary>
  70.     private void UpdateCameraRotation()
  71.     {
  72.         var targetRotation = new Vector3(_input.x, _input.y, 0);
  73.         _cameraRotation = Vector3.SmoothDamp(
  74.             _cameraRotation,
  75.             targetRotation,
  76.             ref smoothDampVelocity,
  77.             _smoothSpeed
  78.         );
  79.         //更新相机欧拉角
  80.         transform.eulerAngles = _cameraRotation;
  81.     }
  82.     /// <summary>
  83.     /// 更新相机位置
  84.     /// </summary>
  85.     private void UpdateCameraPosition()
  86.     {
  87.         var newPos = _lookTarget.position
  88.             + Vector3.back * _cameraDistance
  89.             + Vector3.up * _cameraHeight;
  90.         // 平滑位置移动
  91.         transform.position = Vector3.Lerp(
  92.             transform.position,
  93.             newPos,
  94.             _DistancemoothTime
  95.         );
  96.     }
  97.     /// <summary>
  98.     /// 处理相机输入状态切换
  99.     /// </summary>
  100.     private void HandleCameraInputToggle()
  101.     {
  102.         // 检测ESC键切换相机输入状态
  103.         if (Input.GetKeyDown(KeyCode.Escape))
  104.         {
  105.             _cameraInputEnabled = false;
  106.             // 显示光标并解锁
  107.             Cursor.lockState = CursorLockMode.None;
  108.             Cursor.visible = true;
  109.         }
  110.         // 检测鼠标左键点击窗口来恢复相机控制
  111.         if (Input.GetMouseButtonDown(0) && !_cameraInputEnabled)
  112.         {
  113.             _cameraInputEnabled = true;
  114.             // 隐藏光标并锁定
  115.             Cursor.lockState = CursorLockMode.Locked;
  116.             Cursor.visible = false;
  117.         }
  118.     }
  119. }
复制代码
加入摄像机碰撞逻辑

GameInputManager继承于单例模式
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using GGG.Tool.Singleton;
  5. public class GameInputManager : Singleton<GameInputManager>
  6. {
  7.     private GameInputAction _gameInputAction;
  8.     public Vector2 Movement => _gameInputAction.Player.Movement.ReadValue<Vector2>();
  9.     public Vector2 CameraLook => _gameInputAction.Player.CameraLook.ReadValue<Vector2>();
  10.     private void Awake()
  11.     {
  12.         base.Awake();
  13.         _gameInputAction ??= new GameInputAction(); //是空的,则创建新的实例
  14.     }
  15.     private void OnEnable()
  16.     {
  17.         _gameInputAction.Enable();
  18.     }
  19.     private void OnDisable()
  20.     {
  21.         _gameInputAction.Disable();
  22.     }
  23. }
复制代码
TP_CameraControl
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using GGG.Tool;
  5. public class TP_CameraControl : MonoBehaviour
  6. {
  7.     [Header("相机参数配置")]
  8.     [SerializeField] private Transform _lookTarget;             //相机跟随目标
  9.     [SerializeField] private float _controlSpeed;               //相机移动速度
  10.     [SerializeField] private Vector2 _cameraVerticalMaxAngle;   //相机上下旋转角度限制
  11.     [SerializeField] private Vector2 _cameraHorizontalMaxAngle; //相机左右旋转角度限制
  12.     [SerializeField] private float _smoothSpeed;                //平滑速度
  13.     [SerializeField] private float _cameraDistance;             //相机到跟随目标的距离
  14.     [SerializeField] private float _cameraHeight;               //相机高度
  15.     [SerializeField] private float _distanceSmoothTime;         //位置跟随平滑时间
  16.     private Vector3 smoothDampVelocity = Vector3.zero;          //旋转阻尼
  17.     private Vector2 _input;                                     // 输入值
  18.     private Vector3 _cameraRotation;                            // 相机旋转方向
  19.     private bool _cameraInputEnabled = true;                    // 相机输入是否启用
  20.     private void Awake()
  21.     {
  22.         //隐藏光标
  23.         Cursor.lockState = CursorLockMode.Locked;
  24.         Cursor.visible = false;
  25.     }
  26.     private void Update()
  27.     {
  28.         // 检测到按下ESC键或鼠标左键点击窗口,则切换相机输入状态
  29.         HandleCameraInputToggle();
  30.         // 只有在相机输入启用时才处理输入
  31.         if (_cameraInputEnabled)
  32.         {
  33.             // 实时处理相机输入
  34.             CameraInput();
  35.         }
  36.     }
  37.     private void LateUpdate()
  38.     {
  39.         // 更新相机旋转
  40.         UpdateCameraRotation();
  41.         // 更新相机位置
  42.         UpdateCameraPosition();
  43.     }
  44.     /// <summary>
  45.     /// 处理相机输入,获取并处理上下查看等输入,限制垂直角度范围
  46.     /// </summary>
  47.     private void CameraInput()
  48.     {
  49.         // 获取相机xy轴输入
  50.         _input.y += GameInputManager.MainInstance.CameraLook.x * _controlSpeed;
  51.         _input.x -= GameInputManager.MainInstance.CameraLook.y * _controlSpeed;
  52.         // 限制相机垂直方向角度范围,垂直方向是绕 x 轴旋转,所以平滑的是x轴输入
  53.         _input.x = Mathf.Clamp(
  54.             _input.x,
  55.             _cameraVerticalMaxAngle.x,
  56.             _cameraVerticalMaxAngle.y
  57.         );
  58.         // 限制相机水平方向角度范围,水平方向是绕 y 轴旋转,所以限制的是y轴输入
  59.         _input.y = Mathf.Clamp(
  60.             _input.y,
  61.             _cameraHorizontalMaxAngle.x,
  62.             _cameraHorizontalMaxAngle.y
  63.         );
  64.     }
  65.     /// <summary>
  66.     /// 更新相机旋转
  67.     /// </summary>
  68.     private void UpdateCameraRotation()
  69.     {
  70.         var targetRotation = new Vector3(_input.x, _input.y, 0);
  71.         _cameraRotation = Vector3.SmoothDamp(
  72.             _cameraRotation,
  73.             targetRotation,
  74.             ref smoothDampVelocity,
  75.             _smoothSpeed
  76.         );
  77.         //更新相机欧拉角
  78.         transform.eulerAngles = _cameraRotation;
  79.     }
  80.     /// <summary>
  81.     /// 更新相机位置
  82.     /// </summary>
  83.     private void UpdateCameraPosition()
  84.     {
  85.         var newPos = _lookTarget.position
  86.             + Vector3.back * _cameraDistance
  87.             + Vector3.up * _cameraHeight;
  88.         // 平滑位置移动
  89.         transform.position = Vector3.Lerp(
  90.             transform.position,
  91.             newPos,
  92.             DevelopmentToos.UnTetheredLerp(_distanceSmoothTime)
  93.         );
  94.     }
  95.     /// <summary>
  96.     /// 处理相机输入状态切换
  97.     /// </summary>
  98.     private void HandleCameraInputToggle()
  99.     {
  100.         // 检测ESC键切换相机输入状态
  101.         if (Input.GetKeyDown(KeyCode.Escape))
  102.         {
  103.             _cameraInputEnabled = false;
  104.             // 显示光标并解锁
  105.             Cursor.lockState = CursorLockMode.None;
  106.             Cursor.visible = true;
  107.         }
  108.         // 检测鼠标左键点击窗口来恢复相机控制
  109.         if (Input.GetMouseButtonDown(0) && !_cameraInputEnabled)
  110.         {
  111.             _cameraInputEnabled = true;
  112.             // 隐藏光标并锁定
  113.             Cursor.lockState = CursorLockMode.Locked;
  114.             Cursor.visible = false;
  115.         }
  116.     }
  117. }
复制代码
Day03 Movement

动画部分

6.png

7.png

8.png

9.png
10.png

脚本

CharacterMovementControlBase
  1.         protected Vector3 _moveDirection; // 角色移动方向
复制代码
  1.         /// <summary>
  2.         /// 脚本控制animator的根运动
  3.         /// </summary>
  4.         protected virtual void OnAnimatorMove()
  5.         {
  6.             _animator.ApplyBuiltinRootMotion();
  7.             UpdateCharacterMoveDirection(_animator.deltaPosition);
  8.         }
复制代码
  1.         /// <summary>
  2.         /// 更新角色水平移动方向——绕y轴旋转
  3.         /// </summary>
  4.         protected void UpdateCharacterMoveDirection(Vector3 direction)
  5.         {
  6.             _moveDirection = SlopResetDirection(direction);
  7.             _controller.Move(_moveDirection * Time.deltaTime);
  8.         }
复制代码
GameInputManager
  1.     public bool Run => _gameInputAction.Player.Run.triggered;
复制代码
PlayerMovementControl
  1. using GGG.Tool;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using UnityEngine;
  5. namespace Spiderman.Movement
  6. {
  7.     public class PlayerMovementControl : CharacterMovementControlBase
  8.     {
  9.         [SerializeField] float moveSpeed = 1.5f;
  10.         // 角色旋转角度(绕 Y 轴)
  11.         private float _rotationAngle;
  12.         // 旋转角速度
  13.         private float _angleVelocity = 0;
  14.         // 旋转平滑时间
  15.         [SerializeField] private float _rotationSmoothTime;
  16.         private Transform _mainCamera;
  17.         protected override void Awake()
  18.         {
  19.             base.Awake();
  20.             _mainCamera = Camera.main.transform;
  21.         }
  22.         private void LateUpdate()
  23.         {
  24.             UpdateAnimation();
  25.             CharacterRotationControl();
  26.         }
  27.         /// <summary>
  28.         /// 角色旋转控制
  29.         /// </summary>
  30.         private void CharacterRotationControl()
  31.         {
  32.             // 不在地面时直接返回,不处理旋转
  33.             if (!_characterIsOnGround)
  34.                 return;
  35.             // 处理输入存在时的旋转角度计算
  36.             if (_animator.GetBool("HasInput"))
  37.             {
  38.                 _rotationAngle =
  39.                     Mathf.Atan2(
  40.                         GameInputManager.MainInstance.Movement.x,
  41.                         GameInputManager.MainInstance.Movement.y
  42.                     ) * Mathf.Rad2Deg
  43.                     + _mainCamera.eulerAngles.y;          // 计算角色的旋转角度(弧度转角度)
  44.    
  45.             }
  46.             // 满足HasInput==true且处于“Motion”动画标签时,平滑更新角色旋转
  47.             if (_animator.GetBool("HasInput") && _animator.AnimationAtTag("Motion"))
  48.             {
  49.                 transform.eulerAngles = Vector3.up
  50.                                         * Mathf.SmoothDampAngle(
  51.                                             transform.eulerAngles.y,
  52.                                             _rotationAngle,
  53.                                             ref _angleVelocity,
  54.                                             _rotationSmoothTime
  55.                                         );
  56.             }
  57.         }
  58.         /// <summary>
  59.         /// 更新动画
  60.         /// </summary>
  61.         private void UpdateAnimation()
  62.         {
  63.             if (!_characterIsOnGround)
  64.                 return;
  65.             _animator.SetBool("HasInput", GameInputManager.MainInstance.Movement != Vector2.zero);
  66.             if (_animator.GetBool("HasInput"))
  67.             {
  68.                 if (GameInputManager.MainInstance.Run)
  69.                 {
  70.                     //按下奔跑键
  71.                     _animator.SetBool("Run",true);
  72.                 }
  73.                 //有输入
  74.                 //  Run被开启,那就Movement设置为2,否则设置为输入的两个轴的平方
  75.                 var targetSpeed = _animator.GetBool("Run") ? 2f :GameInputManager.MainInstance.Movement.sqrMagnitude;
  76.                 _animator.SetFloat(
  77.                     "Movement",
  78.                     targetSpeed / _animator.humanScale * moveSpeed,
  79.                     0.25f,
  80.                     Time.deltaTime
  81.                 );
  82.             }
  83.             else
  84.             {
  85.                 //无输入
  86.                 _animator.SetFloat("Movement", 0f, 0.25f, Time.deltaTime);
  87.                 if (_animator.GetFloat("Movement") < 0.2f)
  88.                 {
  89.                     _animator.SetBool("Run", false);
  90.                 }
  91.             }
  92.         }
  93.     }
  94. }
复制代码
Day04  事件管理器

GameEventManager
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using System;
  5. using GGG.Tool;
  6. using GGG.Tool.Singleton;
  7. public class GameEventManager : SingletonNonMono<GameEventManager>
  8. {
  9.     // 事件接口
  10.     private interface IEventHelp
  11.     {
  12.     }
  13.     // 事件类,实现 IEventHelp 接口,用于管理事件注册、调用等逻辑
  14.     private class EventHelp : IEventHelp
  15.     {
  16.         // 存储事件委托
  17.         private event Action _action;
  18.         // 构造函数,初始化时绑定初始事件逻辑
  19.         public EventHelp(Action action)
  20.         {
  21.             // 首次实例化时赋值,仅执行这一次初始绑定
  22.             _action = action;
  23.         }
  24.         // 增加事件注册的方法,将新的事件逻辑追加到委托中
  25.         public void AddCall(Action action)
  26.         {
  27.             _action += action;
  28.         }
  29.         // 调用事件的方法,若有绑定逻辑则执行
  30.         public void Call()
  31.         {
  32.             _action?.Invoke();
  33.         }
  34.         // 移除事件的方法,将指定事件逻辑从委托中移除
  35.         public void Remove(Action action)
  36.         {
  37.             _action -= action;
  38.         }
  39.     }
  40.     private class EventHelp<T> : IEventHelp
  41.     {
  42.         // 存储事件委托
  43.         private event Action<T> _action;
  44.         // 构造函数,初始化时绑定初始事件逻辑
  45.         public EventHelp(Action<T> action)
  46.         {
  47.             // 首次实例化时赋值,仅执行这一次初始绑定
  48.             _action = action;
  49.         }
  50.         // 增加事件注册的方法,将新的事件逻辑追加到委托中
  51.         public void AddCall(Action<T> action)
  52.         {
  53.             _action += action;
  54.         }
  55.         // 调用事件的方法,若有绑定逻辑则执行
  56.         public void Call(T value)
  57.         {
  58.             _action?.Invoke(value);
  59.         }
  60.         // 移除事件的方法,将指定事件逻辑从委托中移除
  61.         public void Remove(Action<T> action)
  62.         {
  63.             _action -= action;
  64.         }
  65.     }
  66.     private class EventHelp<T1, T2> : IEventHelp
  67.     {
  68.         // 存储事件委托
  69.         private event Action<T1, T2> _action;
  70.         // 构造函数,初始化时绑定初始事件逻辑
  71.         public EventHelp(Action<T1, T2> action)
  72.         {
  73.             // 首次实例化时赋值,仅执行这一次初始绑定
  74.             _action = action;
  75.         }
  76.         // 增加事件注册的方法,将新的事件逻辑追加到委托中
  77.         public void AddCall(Action<T1, T2> action)
  78.         {
  79.             _action += action;
  80.         }
  81.         // 调用事件的方法,若有绑定逻辑则执行
  82.         public void Call(T1 value1, T2 value2)
  83.         {
  84.             _action?.Invoke(value1, value2);
  85.         }
  86.         // 移除事件的方法,将指定事件逻辑从委托中移除
  87.         public void Remove(Action<T1, T2> action)
  88.         {
  89.             _action -= action;
  90.         }
  91.     }
  92.     /// <summary>
  93.     /// 事件中心,用于管理事件注册、调用
  94.     /// </summary>
  95.     private Dictionary<string, IEventHelp> _eventCenter = new Dictionary<string, IEventHelp>();
  96.     /// <summary>
  97.     /// 添加事件监听
  98.     /// </summary>
  99.     /// <param name="eventName">事件名称</param>
  100.     /// <param name="action">回调函数</param>
  101.     public void AddEventListening(string eventName, Action action)
  102.     {
  103.         if (_eventCenter.TryGetValue(eventName, out var eventHelp))
  104.         {
  105.             (eventHelp as EventHelp)?.AddCall(action);
  106.         }
  107.         else
  108.         {
  109.             // 如果事件中心不存在叫这个名字的事件,new一个然后添加
  110.             _eventCenter.Add(eventName, new EventHelp(action));
  111.         }
  112.     }
  113.     public void AddEventListening<T>(string eventName, Action<T> action)
  114.     {
  115.         if (_eventCenter.TryGetValue(eventName, out var eventHelp))
  116.         {
  117.             (eventHelp as EventHelp<T>)?.AddCall(action);
  118.         }
  119.         else
  120.         {
  121.             // 如果事件中心不存在叫这个名字的事件,new一个然后添加
  122.             _eventCenter.Add(eventName, new EventHelp<T>(action));
  123.         }
  124.     }
  125.     public void AddEventListening<T1, T2>(string eventName, Action<T1, T2> action)
  126.     {
  127.         if (_eventCenter.TryGetValue(eventName, out var eventHelp))
  128.         {
  129.             (eventHelp as EventHelp<T1, T2>)?.AddCall(action);
  130.         }
  131.         else
  132.         {
  133.             // 如果事件中心不存在叫这个名字的事件,new一个然后添加
  134.             _eventCenter.Add(eventName, new EventHelp<T1, T2>(action));
  135.         }
  136.     }
  137.     /// <summary>
  138.     /// 调用事件
  139.     /// </summary>
  140.     /// <param name="eventName">事件名称</param>
  141.     public void CallEvent(string eventName)
  142.     {
  143.         if (_eventCenter.TryGetValue(eventName, out var eventHelp))
  144.         {
  145.             (eventHelp as EventHelp)?.Call();
  146.         }
  147.         else
  148.         {
  149.             LogEventNotFound(eventName, "调用");
  150.         }
  151.     }
  152.     public void CallEvent<T>(string eventName, T value)
  153.     {
  154.         if (_eventCenter.TryGetValue(eventName, out var eventHelp))
  155.         {
  156.             (eventHelp as EventHelp<T>)?.Call(value);
  157.         }
  158.         else
  159.         {
  160.             LogEventNotFound(eventName, "调用");
  161.         }
  162.     }
  163.     public void CallEvent<T1, T2>(string eventName, T1 value, T2 value1)
  164.     {
  165.         if (_eventCenter.TryGetValue(eventName, out var eventHelp))
  166.         {
  167.             (eventHelp as EventHelp<T1, T2>)?.Call(value, value1);
  168.         }
  169.         else
  170.         {
  171.             LogEventNotFound(eventName, "调用");
  172.         }
  173.     }
  174.     /// <summary>
  175.     /// 移除事件监听
  176.     /// </summary>
  177.     /// <param name="eventName">事件名称</param>
  178.     /// <param name="action">要移除的事件回调</param>
  179.     public void RemoveEvent(string eventName, Action action)
  180.     {
  181.         if (_eventCenter.TryGetValue(eventName, out var eventHelp))
  182.         {
  183.             (eventHelp as EventHelp)?.Remove(action);
  184.         }
  185.         else
  186.         {
  187.             LogEventNotFound(eventName, "移除");
  188.         }
  189.     }
  190.     public void RemoveEvent<T>(string eventName, Action<T> action)
  191.     {
  192.         if (_eventCenter.TryGetValue(eventName, out var eventHelp))
  193.         {
  194.             (eventHelp as EventHelp<T>)?.Remove(action);
  195.         }
  196.         else
  197.         {
  198.             LogEventNotFound(eventName, "移除");
  199.         }
  200.     }
  201.     public void RemoveEvent<T1, T2>(string eventName, Action<T1, T2> action)
  202.     {
  203.         if (_eventCenter.TryGetValue(eventName, out var eventHelp))
  204.         {
  205.             (eventHelp as EventHelp<T1, T2>)?.Remove(action);
  206.         }
  207.         else
  208.         {
  209.             LogEventNotFound(eventName, "移除");
  210.         }
  211.     }
  212.     /// <summary>
  213.     /// 事件未找到时的统一日志输出
  214.     /// </summary>
  215.     /// <param name="eventName">事件名称</param>
  216.     /// <param name="operation">操作类型(移除、调用)</param>
  217.     private void LogEventNotFound(string eventName, string operation)
  218.     {
  219.         DevelopmentTools.WTF($"当前未找到{eventName}的事件,无法{operation}");
  220.     }
  221. }
复制代码
Day05 AnimationStringToHash
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. /// <summary>
  5. /// 动画参数哈希值管理类,用于统一存储Animator参数的哈希值,避免重复计算
  6. /// </summary>
  7. public class AnimationID
  8. {
  9.     // 角色移动相关动画参数哈希
  10.     public static readonly int MovementID = Animator.StringToHash("Movement");
  11.     public static readonly int LockID = Animator.StringToHash("Lock");
  12.     public static readonly int HorizontalID = Animator.StringToHash("Horizontal");
  13.     public static readonly int VerticalID = Animator.StringToHash("Vertical");
  14.     public static readonly int HasInputID = Animator.StringToHash("HasInput");
  15.     public static readonly int RunID = Animator.StringToHash("Run");
  16. }
复制代码
Day06  GameTimer
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using UnityEngine;
  5. /// <summary>
  6. /// 计时器状态枚举,描述计时器不同工作阶段
  7. /// </summary>
  8. public enum TimerState
  9. {
  10.     NOTWORKERE, // 没有工作(初始或重置后状态)
  11.     WORKERING,  // 工作中(计时进行时)
  12.     DONE        // 工作完成(计时结束)
  13. }
  14. /// <summary>
  15. /// 游戏计时器类,用于管理计时逻辑,支持启动计时、更新计时、获取状态、重置等功能
  16. /// </summary>
  17. public class GameTimer
  18. {
  19.     // 计时时长(剩余计时时间)
  20.     private float _startTime;
  21.     // 计时结束后要执行的任务(Action 委托)
  22.     private Action _task;
  23.     // 是否停止当前计时器标记
  24.     private bool _isStopTimer;
  25.     // 当前计时器的状态
  26.     private TimerState _timerState;
  27.     /// <summary>
  28.     /// 构造函数,初始化时重置计时器
  29.     /// </summary>
  30.     public GameTimer()
  31.     {
  32.         ResetTimer();
  33.     }
  34.     /// <summary>
  35.     /// 1. 开始计时
  36.     /// </summary>
  37.     /// <param name="time">要计时的时长</param>
  38.     /// <param name="task">计时结束后要执行的任务(Action 委托)</param>
  39.     public void StartTimer(float time, Action task)
  40.     {
  41.         _startTime = time;
  42.         _task = task;
  43.         _isStopTimer = false;
  44.         _timerState = TimerState.WORKERING;
  45.     }
  46.     /// <summary>
  47.     /// 2. 更新计时器(通常在 MonoBehaviour 的 Update 里调用,驱动计时逻辑)
  48.     /// </summary>
  49.     public void UpdateTimer()
  50.     {
  51.         // 如果标记为停止,直接返回,不执行计时更新
  52.         if (_isStopTimer)
  53.             return;
  54.         // 递减计时时间
  55.         _startTime -= Time.deltaTime;
  56.         // 计时时间小于 0,说明计时结束
  57.         if (_startTime < 0)
  58.         {
  59.             // 安全调用任务(如果任务不为 null 才执行)
  60.             _task?.Invoke();
  61.             // 更新状态为已完成
  62.             _timerState = TimerState.DONE;
  63.             // 标记为停止,后续不再继续计时更新
  64.             _isStopTimer = true;
  65.         }
  66.     }
  67.     /// <summary>
  68.     /// 3. 获取当前计时器的状态
  69.     /// </summary>
  70.     /// <returns>返回 TimerState 枚举值,代表当前计时器状态</returns>
  71.     public TimerState GetTimerState() => _timerState;
  72.     /// <summary>
  73.     /// 4. 重置计时器,恢复到初始状态
  74.     /// </summary>
  75.     public void ResetTimer()
  76.     {
  77.         _startTime = 0f;
  78.         _task = null;
  79.         _isStopTimer = true;
  80.         _timerState = TimerState.NOTWORKERE;
  81.     }
  82. }
复制代码
TimerManager
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using GGG.Tool;
  5. using GGG.Tool.Singleton;
  6. using UnityEngine;
  7. using UnityEngine.UIElements;
  8. /// <summary>
  9. /// 计时器管理器,采用单例模式,负责管理空闲计时器队列和工作中计时器列表,
  10. /// 实现计时器的初始化、分配、回收及更新逻辑
  11. /// </summary>
  12. public class TimerManager : Singleton<TimerManager>
  13. {
  14.     #region 私有字段
  15.     // 初始最大计时器数量,在 Inspector 中配置
  16.     [SerializeField] private int _initMaxTimerCount;
  17.     // 空闲计时器队列,存储可用的 GameTimer
  18.     private Queue<GameTimer> _notWorkingTimer = new Queue<GameTimer>();
  19.     // 工作中计时器列表,存储正在计时的 GameTimer
  20.     private List<GameTimer> _workingTimer = new List<GameTimer>();
  21.     #endregion
  22.     #region 生命周期与初始化
  23.     protected override void Awake()
  24.     {
  25.         base.Awake();
  26.         InitTimerManager();
  27.     }
  28.     /// <summary>
  29.     /// 初始化计时器管理器,创建初始数量的空闲计时器
  30.     /// </summary>
  31.     private void InitTimerManager()
  32.     {
  33.         for (int i = 0; i < _initMaxTimerCount; i++)
  34.         {
  35.             CreateTimerInternal();
  36.         }
  37.     }
  38.     /// <summary>
  39.     /// 内部创建计时器并加入空闲队列的方法
  40.     /// </summary>
  41.     private void CreateTimerInternal()
  42.     {
  43.         var timer = new GameTimer();
  44.         _notWorkingTimer.Enqueue(timer);
  45.     }
  46.     #endregion
  47.     #region 计时器分配与回收
  48.     /// <summary>
  49.     /// 尝试获取一个计时器,用于执行定时任务
  50.     /// </summary>
  51.     /// <param name="time">计时时长</param>
  52.     /// <param name="task">计时结束后执行的任务</param>
  53.     public void TryGetOneTimer(float time, Action task)
  54.     {
  55.         // 若空闲队列为空,额外创建一个计时器
  56.         if (_notWorkingTimer.Count == 0)
  57.         {
  58.             CreateTimerInternal();
  59.         }
  60.         var timer = _notWorkingTimer.Dequeue();
  61.         timer.StartTimer(time, task);
  62.         _workingTimer.Add(timer);
  63.     }
  64.     /// <summary>
  65.     /// 回收计时器(可在 GameTimer 完成任务时调用,这里逻辑已内联在更新里,也可扩展外部调用)
  66.     /// 注:当前通过 UpdateWorkingTimer 自动回收,此方法可留作扩展
  67.     /// </summary>
  68.     /// <param name="timer">要回收的计时器</param>
  69.     private void RecycleTimer(GameTimer timer)
  70.     {
  71.         timer.ResetTimer();
  72.         _notWorkingTimer.Enqueue(timer);
  73.         _workingTimer.Remove(timer);
  74.     }
  75.     #endregion
  76.     #region 计时器更新逻辑
  77.     private void Update()
  78.     {
  79.         UpdateWorkingTimer();
  80.     }
  81.     /// <summary>
  82.     /// 更新工作中计时器的状态,处理计时推进和完成后的回收
  83.     /// </summary>
  84.     private void UpdateWorkingTimer()
  85.     {
  86.         // 遍历副本,避免列表修改时迭代出错
  87.         for (int i = _workingTimer.Count - 1; i >= 0; i--)
  88.         {
  89.             var timer = _workingTimer[i];
  90.             timer.UpdateTimer();
  91.             if (timer.GetTimerState() == TimerState.DONE)
  92.             {
  93.                 RecycleTimer(timer);
  94.             }
  95.         }
  96.     }
  97.     #endregion
  98. }
复制代码
Day07 脚部拖尾特效的控制——奔跑时启用
  1. using UnityEngine;
  2. using System.Collections;
  3. public class ObjectVisibilityController : MonoBehaviour
  4. {
  5.     // 在 Inspector 中手动拖入需要控制的子物体
  6.     public GameObject targetChild;
  7.     public Animator playerAnimator;
  8.     // 存储当前目标状态,用于判断是否需要执行状态切换
  9.     private bool _currentTargetState;
  10.     // 标记是否正在等待延迟,避免重复启动协程
  11.     private bool _isWaiting = false;
  12.     private void Update()
  13.     {
  14.         // 获取动画状态的当前值
  15.         bool desiredState = playerAnimator.GetBool(AnimationID.RunID);
  16.         // 如果状态发生变化且不在等待状态,则启动延迟协程
  17.         if (desiredState != _currentTargetState && !_isWaiting)
  18.         {
  19.             StartCoroutine(ChangeStateAfterDelay(desiredState, 0.5f));
  20.         }
  21.     }
  22.     // 延迟改变状态的协程
  23.     private IEnumerator ChangeStateAfterDelay(bool newState, float delay)
  24.     {
  25.         _isWaiting = true; // 标记为正在等待
  26.         yield return new WaitForSeconds(delay); // 等待指定秒数
  27.         // 应用新状态
  28.         targetChild.SetActive(newState);
  29.         _currentTargetState = newState;
  30.         _isWaiting = false; // 重置等待标记
  31.     }
  32. }
复制代码
11.gif

Day08 IKController——头部IK跟随相机(平滑控制)

IKController
[code]using System.Collections;using System.Collections.Generic;using UnityEngine;public class IKController : MonoBehaviour{    public Animator _animator;    //IK控制点    //四肢关节点    public Transform ik_LHand;    public Transform ik_RHand;    public Transform ik_LFoot;    public Transform ik_RFoot;    //头部控制点,可以根据主相机的位置,让玩家能够从侧视角下看到头部偏转。    public Transform Head_IKPoint;      private void OnAnimatorIK(int layerIndex)    {        //四肢        if (ik_LHand != null)            IKControl(AvatarIKGoal.LeftHand, ik_LHand);        if (ik_RHand != null)            IKControl(AvatarIKGoal.RightHand, ik_RHand);        if (ik_LFoot != null)            IKControl(AvatarIKGoal.LeftFoot, ik_LFoot);        if (ik_RFoot != null)            IKControl(AvatarIKGoal.RightFoot, ik_RFoot);        //头部        if (Head_IKPoint != null)            IKHeadControl(Head_IKPoint);    }    ///     /// 头部 IK 控制(平滑转向 + 角度限制)    ///     /// 要看的对象    /// 插值速度    /// 最大允许夹角(度数)    private void IKHeadControl(Transform target,                               float turnSpeed = 3f,                               float maxAngle = 60f)    {        // 1. 计算最终想要看的点        Vector3 rawTargetPos;        Vector3 directionToCamera = target.position - transform.position;        bool isCameraInFront = Vector3.Dot(transform.forward, directionToCamera.normalized) > 0;        if (isCameraInFront)        {            rawTargetPos = target.position;        }        else        {            // 相机在背后,看向相机视线向前延伸的点            rawTargetPos = target.position + target.forward * 10f;        }        // 2. 计算与正前方向的夹角        Vector3 dirToRawTarget = (rawTargetPos - transform.position).normalized;        float angle = Vector3.Angle(transform.forward, dirToRawTarget);        // 3. 如果角度在范围内,才允许平滑转向        if (angle  float.Epsilon || Mathf.Abs(x - z) > float.Epsilon || Mathf.Abs(y - z) > float.Epsilon) {            Debug.LogWarning("The xyz scales of the Spider are not equal. Please make sure they are. The scale of the spider is defaulted to be the Y scale and a lot of values depend on this scale.");        }        rb = GetComponent();        //Initialize the two Sphere Casts        downRayRadius = downRaySize * getColliderRadius();        float forwardRayRadius = forwardRaySize * getColliderRadius();        downRay = new SphereCast(transform.position, -transform.up, downRayLength * getColliderLength(), downRayRadius, transform, transform);        forwardRay = new SphereCast(transform.position, transform.forward, forwardRayLength * getColliderLength(), forwardRayRadius, transform, transform);        //Initialize the bodyupLocal as the spiders transform.up parented to the body. Initialize the breathePivot as the body position parented to the spider        bodyY = body.transform.InverseTransformDirection(transform.up);        bodyZ = body.transform.InverseTransformDirection(transform.forward);        bodyCentroid = body.transform.position + getScale() * bodyOffsetHeight * transform.up;        bodyDefaultCentroid = transform.InverseTransformPoint(bodyCentroid);    }    void FixedUpdate() {        //** Ground Check **//        grdInfo = GroundCheck();        //** Rotation to normal **//         float normalAdjustSpeed = (grdInfo.rayType == RayType.ForwardRay) ? forwardNormalAdjustSpeed : groundNormalAdjustSpeed;        Vector3 slerpNormal = Vector3.Slerp(transform.up, grdInfo.groundNormal, 0.02f * normalAdjustSpeed);        Quaternion goalrotation = getLookRotation(Vector3.ProjectOnPlane(transform.right, slerpNormal), slerpNormal);        // Save last Normal for access        lastNormal = transform.up;        //Apply the rotation to the spider        if (Quaternion.Angle(transform.rotation,goalrotation)>Mathf.Epsilon) transform.rotation = goalrotation;        // Dont apply gravity if close enough to ground        if (grdInfo.distanceToGround > getGravityOffDistance()) {            rb.AddForce(-grdInfo.groundNormal * gravityMultiplier * 0.0981f * getScale()); //Important using the groundnormal and not the lerping normal here!        }    }    void Update() {        //** Debug **//        if (showDebug) drawDebug();        Vector3 Y = body.TransformDirection(bodyY);        //Doesnt work the way i want it too! On sphere i go underground. I jiggle around when i go down my centroid moves down to.(Depends on errortolerance of IKSolver)        if (legCentroidAdjustment) bodyCentroid = Vector3.Lerp(bodyCentroid, getLegsCentroid(), Time.deltaTime * legCentroidSpeed);        else bodyCentroid = getDefaultCentroid();        body.transform.position = bodyCentroid;        if (legNormalAdjustment) {            Vector3 newNormal = GetLegsPlaneNormal();            //Use Global X for  pitch            Vector3 X = transform.right;            float angleX = Vector3.SignedAngle(Vector3.ProjectOnPlane(Y, X), Vector3.ProjectOnPlane(newNormal, X), X);            angleX = Mathf.LerpAngle(0, angleX, Time.deltaTime * legNormalSpeed);            body.transform.rotation = Quaternion.AngleAxis(angleX, X) * body.transform.rotation;            //Use Local Z for roll. With the above global X for pitch, this avoids any kind of yaw happening.            Vector3 Z = body.TransformDirection(bodyZ);            float angleZ = Vector3.SignedAngle(Y, Vector3.ProjectOnPlane(newNormal, Z), Z);            angleZ = Mathf.LerpAngle(0, angleZ, Time.deltaTime * legNormalSpeed);            body.transform.rotation = Quaternion.AngleAxis(angleZ, Z) * body.transform.rotation;        }        if (breathing) {            float t = (Time.time * 2 * Mathf.PI / breathePeriod) % (2 * Mathf.PI);            float amplitude = breatheMagnitude * getColliderRadius();            Vector3 direction = body.TransformDirection(bodyY);            body.transform.position = bodyCentroid + amplitude * (Mathf.Sin(t) + 1f) * direction;        }        // Update the moving status        if (transform.hasChanged) {            isMoving = true;            transform.hasChanged = false;        }        else isMoving = false;    }    //** Movement methods**//    private void move(Vector3 direction, float speed) {        // TODO: Make sure direction is on the XZ plane of spider! For this maybe refactor the logic from input from spidercontroller to this function.        //Only allow direction vector to have a length of 1 or lower        float magnitude = direction.magnitude;        if (magnitude > 1) {            direction = direction.normalized;            magnitude = 1f;        }        // Scale the magnitude and Clamp to not move more than down ray radius (Makes sure the ground is not lost due to moving too fast)        if (direction != Vector3.zero) {            float directionDamp = Mathf.Pow(Mathf.Clamp(Vector3.Dot(direction / magnitude, transform.forward), 0, 1), 2);            float distance = 0.0004f * speed * magnitude * directionDamp * getScale();            distance = Mathf.Clamp(distance, 0, 0.99f * downRayRadius);            direction = distance * (direction / magnitude);        }        //Slerp from old to new velocity using the acceleration        currentVelocity = Vector3.Slerp(currentVelocity, direction, 1f - walkDrag);        //Apply the resulting velocity        transform.position += currentVelocity;    }    public void turn(Vector3 goalForward) {        //Make sure goalForward is orthogonal to transform up        goalForward = Vector3.ProjectOnPlane(goalForward, transform.up).normalized;        if (goalForward == Vector3.zero || Vector3.Angle(goalForward, transform.forward) < Mathf.Epsilon) {            return;        }        goalForward = Vector3.ProjectOnPlane(goalForward, transform.up);        transform.rotation = Quaternion.RotateTowards(transform.rotation, Quaternion.LookRotation(goalForward, transform.up), turnSpeed);    }    //** Movement methods for public access**//    // It is advised to call these on a fixed update basis.    public void walk(Vector3 direction) {        if (direction.magnitude < Mathf.Epsilon) return;        move(direction, walkSpeed);    }    public void run(Vector3 direction) {        if (direction.magnitude < Mathf.Epsilon) return;        move(direction, runSpeed);    }    //** Ground Check Method **//    private groundInfo GroundCheck() {        if (groundCheckOn) {            if (forwardRay.castRay(out hitInfo, walkableLayer)) {                return new groundInfo(true, hitInfo.normal.normalized, Vector3.Distance(transform.TransformPoint(capsuleCollider.center), hitInfo.point) - getColliderRadius(), RayType.ForwardRay);            }            if (downRay.castRay(out hitInfo, walkableLayer)) {                return new groundInfo(true, hitInfo.normal.normalized, Vector3.Distance(transform.TransformPoint(capsuleCollider.center), hitInfo.point) - getColliderRadius(), RayType.DownRay);            }        }        return new groundInfo(false, Vector3.up, float.PositiveInfinity, RayType.None);    }    //** Helper methods**//    /*    * Returns the rotation with specified right and up direction       * May have to make more error catches here. Whatif not orthogonal?    */    private Quaternion getLookRotation(Vector3 right, Vector3 up) {        if (up == Vector3.zero || right == Vector3.zero) return Quaternion.identity;        // If vectors are parallel return identity        float angle = Vector3.Angle(right, up);        if (angle == 0 || angle == 180) return Quaternion.identity;        Vector3 forward = Vector3.Cross(right, up);        return Quaternion.LookRotation(forward, up);    }    //** Torso adjust methods for more realistic movement **//    // Calculate the centroid (center of gravity) given by all end effector positions of the legs    private Vector3 getLegsCentroid() {        if (legs == null || legs.Length == 0) {            Debug.LogError("Cant calculate leg centroid, legs not assigned.");            return body.transform.position;        }        Vector3 defaultCentroid = getDefaultCentroid();        // Calculate the centroid of legs position        Vector3 newCentroid = Vector3.zero;        float k = 0;        for (int i = 0; i < legs.Length; i++) {            newCentroid += legs.getEndEffector().position;            k++;        }        newCentroid = newCentroid / k;        // Offset the calculated centroid        Vector3 offset = Vector3.Project(defaultCentroid - getColliderBottomPoint(), transform.up);        newCentroid += offset;        // Calculate the normal and tangential translation needed        Vector3 normalPart = Vector3.Project(newCentroid - defaultCentroid, transform.up);        Vector3 tangentPart = Vector3.ProjectOnPlane(newCentroid - defaultCentroid, transform.up);        return defaultCentroid + Vector3.Lerp(Vector3.zero, normalPart, legCentroidNormalWeight) + Vector3.Lerp(Vector3.zero, tangentPart, legCentroidTangentWeight);    }    // Calculate the normal of the plane defined by leg positions, so we know how to rotate the body    private Vector3 GetLegsPlaneNormal() {        if (legs == null) {            Debug.LogError("Cant calculate normal, legs not assigned.");            return transform.up;        }        if (legNormalWeight

相关推荐

您需要登录后才可以回帖 登录 | 立即注册