找回密码
 立即注册
首页 业界区 业界 仿神秘海域/美末环境交互的程序化动画学习 ...

仿神秘海域/美末环境交互的程序化动画学习

祖柔惠 4 天前
写在前面:
真正实现这些细枝末节的东西的时候才能感受到这种技术力的恐怖。
——致敬顽皮狗工作室
插件安装

1.png

为角色添加组件

2.png

3.png

4.png

5.png

右手同理
状态机脚本编写

6.png

BaseState.cs
  1. using UnityEngine;
  2. using System;
  3. /// <summary>
  4. /// 状态基类,定义了状态机中所有状态的基本行为规范
  5. /// 泛型参数EEState限制为枚举类型,用于表示具体的状态类型
  6. /// </summary>
  7. /// <typeparam name="EState">状态枚举类型,继承自Enum</typeparam>
  8. public abstract class BaseState<EState> where EState : Enum
  9. {
  10.     //构造函数
  11.     public BaseState(EState key)
  12.     {
  13.         StateKey = key;
  14.     }
  15.     public EState StateKey { get; private set; }
  16.     public abstract void EnterState();
  17.     public abstract void ExitState();
  18.     public abstract void UpdateState();
  19.     public abstract EState GetNextState();
  20.     public abstract void OnTriggerEnter(Collider other);
  21.     public abstract void OnTriggerStay(Collider other);
  22.     public abstract void OnTriggerExit(Collider other);
  23. }
复制代码
NewBaseState.cs
  1. using UnityEngine;
  2. using System;
  3. using System.Collections.Generic;
  4. /// <summary>
  5. /// 状态管理器泛型抽象类
  6. /// </summary>
  7. /// <typeparam name="EState">状态枚举类型,需继承自Enum</typeparam>
  8. public abstract class StateManager<EState> : MonoBehaviour where EState : Enum
  9. {
  10.     // 存储所有状态的字典,键为状态枚举,值为对应的状态实例
  11.     protected Dictionary<EState, BaseState<EState>> States = new Dictionary<EState, BaseState<EState>>();
  12.     // 当前激活的状态
  13.     protected BaseState<EState> CurrentState;
  14.     // 标志位:是否处于状态切换中
  15.     protected bool IsTransitioningState = false;
  16.     void Start()
  17.     {
  18.         CurrentState.EnterState();
  19.     }
  20.     void Update()
  21.     {
  22.         EState nextStateKey = CurrentState.GetNextState();
  23.         if (!IsTransitioningState && nextStateKey.Equals(CurrentState.StateKey))
  24.         {
  25.             // 如果当前状态和下一状态相同,则更新当前状态
  26.             CurrentState.UpdateState();
  27.         }
  28.         else if(!IsTransitioningState)
  29.         {
  30.             // 不同,则切换到下一状态
  31.             TransitionToState(nextStateKey);
  32.         }
  33.     }
  34.     /// <summary>
  35.     /// 状态切换方法,用于从当前状态切换到目标状态
  36.     /// </summary>
  37.     /// <param name="stateKey">目标状态的枚举标识</param>
  38.     protected virtual void TransitionToState(EState stateKey)
  39.     {
  40.         IsTransitioningState = true;
  41.         // 退出当前状态
  42.         CurrentState.ExitState();
  43.         // 进入目标状态
  44.         CurrentState = States[stateKey];
  45.         CurrentState.EnterState();
  46.         IsTransitioningState = false;
  47.     }
  48.     /// <summary>
  49.     /// 当碰撞体进入触发器时调用的方法,转发给当前状态处理
  50.     /// </summary>
  51.     /// <param name="other">进入触发器的碰撞体</param>
  52.     void OnTriggerEnter(Collider other)
  53.     {
  54.         CurrentState.OnTriggerEnter(other);
  55.     }
  56.     /// <summary>
  57.     /// 当碰撞体持续处于触发器中时调用的方法,转发给当前状态处理
  58.     /// </summary>
  59.     /// <param name="other">处于触发器中的碰撞体</param>
  60.     void OnTriggerStay(Collider other)
  61.     {
  62.         CurrentState.OnTriggerStay(other);
  63.     }
  64.     /// <summary>
  65.     /// 当碰撞体退出触发器时调用的方法,转发给当前状态处理
  66.     /// </summary>
  67.     /// <param name="other">退出触发器的碰撞体</param>
  68.     void OnTriggerExit(Collider other)
  69.     {
  70.         CurrentState.OnTriggerExit(other);
  71.     }
  72. }
复制代码
Animation Rigging

Rig Builder组件要放在Animator的同级
7.png

8.png

9.png

Rig放置的位置
10.png

11.png

环境交互状态机的编写

12.png

13.png

EnvironmentInteractionStateMachine
  1. using UnityEngine;
  2. using UnityEngine.Animations.Rigging;
  3. using UnityEngine.Assertions;   //调试用
  4. public class EnvironmentInteractionStateMachine : StateManager<EnvironmentInteractionStateMachine.EEnvironmentInteractionState>
  5. {
  6.     // 环境交互状态
  7.     public enum EEnvironmentInteractionState
  8.     {
  9.         Search,   // 搜索状态
  10.         Approach, // 接近状态
  11.         Rise,     // 起身状态
  12.         Touch,    // 触碰状态
  13.         Reset     // 重置状态
  14.     }
  15.     private EnvironmentInteractionContext _context;
  16.     // 约束、组件等引用
  17.     [SerializeField] private TwoBoneIKConstraint _leftIkConstraint;
  18.     [SerializeField] private TwoBoneIKConstraint _rightIkConstraint;
  19.     [SerializeField] private MultiRotationConstraint _leftMultiRotationConstraint;
  20.     [SerializeField] private MultiRotationConstraint _rightMultiRotationConstraint;
  21.     [SerializeField] private CharacterController characterController;
  22.     void Awake()
  23.     {
  24.         ValidateConstraints();
  25.         _context = new EnvironmentInteractionContext(_leftIkConstraint, _rightIkConstraint, _leftMultiRotationConstraint, _rightMultiRotationConstraint, characterController);
  26.     }
  27.     // 校验各类约束、组件是否正确赋值
  28.     private void ValidateConstraints()
  29.     {
  30.         Assert.IsNotNull(_leftIkConstraint, "Left IK constraint 没有赋值");
  31.         Assert.IsNotNull(_rightIkConstraint, "Right IK constraint 没有赋值");
  32.         Assert.IsNotNull(_leftMultiRotationConstraint, "Left multi-rotation constraint 没有赋值");
  33.         Assert.IsNotNull(_rightMultiRotationConstraint, "Right multi-rotation constraint 没有赋值");
  34.         Assert.IsNotNull(characterController, "characterController used to control character 没有赋值");
  35.     }
  36. }
复制代码
EnvironmentInteractionContext用来管理各种属性
  1. using UnityEngine;
  2. using UnityEngine.Animations.Rigging;
  3. public class EnvironmentInteractionContext
  4. {
  5.     private TwoBoneIKConstraint _leftIkConstraint;
  6.     private TwoBoneIKConstraint _rightIkConstraint;
  7.     private MultiRotationConstraint _leftMultiRotationConstraint;
  8.     private MultiRotationConstraint _rightMultiRotationConstraint;
  9.     private CharacterController _characterController;
  10.     public EnvironmentInteractionContext(
  11.         TwoBoneIKConstraint leftIkConstraint,
  12.         TwoBoneIKConstraint rightIkConstraint,
  13.         MultiRotationConstraint leftMultiRotationConstraint,
  14.         MultiRotationConstraint rightMultiRotationConstraint,
  15.         CharacterController characterController)
  16.     {
  17.         _leftIkConstraint = leftIkConstraint;
  18.         _rightIkConstraint = rightIkConstraint;
  19.         _leftMultiRotationConstraint = leftMultiRotationConstraint;
  20.         _rightMultiRotationConstraint = rightMultiRotationConstraint;
  21.         _characterController = characterController;
  22.     }
  23.     // 外部可以访问的属性
  24.     public TwoBoneIKConstraint LeftIkConstraint => _leftIkConstraint;
  25.     public TwoBoneIKConstraint RightIkConstraint => _rightIkConstraint;
  26.     public MultiRotationConstraint LeftMultiRotationConstraint => _leftMultiRotationConstraint;
  27.     public MultiRotationConstraint RightMultiRotationConstraint => _rightMultiRotationConstraint;
  28.     public CharacterController CharacterController => _characterController;
  29. }
复制代码
从ResetState开始
  1. using UnityEngine;
  2. public class ResetState : EnvironmentInteractionState
  3. {
  4.     // 构造函数
  5.     public ResetState(EnvironmentInteractionContext context, EnvironmentInteractionStateMachine.EEnvironmentInteractionState estate) : base(context, estate)
  6.     {
  7.         EnvironmentInteractionContext Context = context;
  8.     }
  9.     public override void EnterState(){}
  10.     public override void ExitState() { }
  11.     public override void UpdateState() { }
  12.     public override EnvironmentInteractionStateMachine.EEnvironmentInteractionState GetNextState()
  13.     {
  14.         return StateKey;
  15.     }
  16.     public override void OnTriggerEnter(Collider other) { }
  17.     public override void OnTriggerStay(Collider other) { }
  18.     public override void OnTriggerExit(Collider other) { }
  19. }
复制代码
EnvironmentInteractionStateMachine中加入初始化函数
  1.     void Awake()
  2.     {
  3.         //原来的代码
  4.         InitalizeStates();
  5.     }
复制代码
  1.     /// <summary>
  2.     /// 初始化状态机
  3.     /// </summary>
  4.     private void InitalizeStates()
  5.     {
  6.         //添加状态
  7.         States.Add(EEnvironmentInteractionState.Reset, new ResetState(_context, EEnvironmentInteractionState.Reset));
  8.         States.Add(EEnvironmentInteractionState.Search, new SearchState(_context, EEnvironmentInteractionState.Search));
  9.         States.Add(EEnvironmentInteractionState.Approach, new ApproachState(_context, EEnvironmentInteractionState.Approach));
  10.         States.Add(EEnvironmentInteractionState.Rise, new RiseState(_context, EEnvironmentInteractionState.Rise));
  11.         States.Add(EEnvironmentInteractionState.Touch, new TouchState(_context, EEnvironmentInteractionState.Touch));
  12.         //设置初始状态为Reset
  13.         CurrentState = States[EEnvironmentInteractionState.Reset];
  14.     }
复制代码
14.png

状态机运行正常
环境检测

15.png

1.在角色身上创建一个稍大于臂展的碰撞盒

EnvironmentInteractionStateMachine
  1.     void Awake()
  2.     {
  3.         ///原来的代码
  4.         ConstructEnvironmentDetectionCollider();
  5.     }
复制代码
  1.     /// <summary>
  2.     /// 创建一个环境检测用的碰撞体
  3.     /// </summary>
  4.     private void ConstructEnvironmentDetectionCollider()
  5.     {
  6.         // 碰撞体大小的基准值
  7.         float wingspan = characterController.height;
  8.         // 给当前游戏对象添加盒型碰撞体组件
  9.         BoxCollider boxCollider = gameObject.AddComponent<BoxCollider>();
  10.         // 设置碰撞体大小为立方体,各边长度等于翼展
  11.         boxCollider.size = new Vector3(wingspan, wingspan, wingspan);
  12.         // 设置碰撞体中心位置
  13.         // 基于角色控制器的中心位置进行偏移:
  14.         // Y轴方向上移翼展的25%,Z轴方向前移翼展的50%
  15.         boxCollider.center = new Vector3(
  16.             characterController.center.x,
  17.             characterController.center.y + (.25f * wingspan),
  18.             characterController.center.z + (.5f * wingspan)
  19.         );
  20.         // 将碰撞体设置为触发器模式(用于检测碰撞而非物理碰撞响应)
  21.         boxCollider.isTrigger = true;
  22.     }
复制代码
16.png

17.png

2.碰撞体触发器的交互机制


  • 角色进入 “触发器区域” → OnTriggerEnter 触发(一次)
  • 角色持续待在区域内 → 每帧触发 OnTriggerStay
  • 角色离开区域 → OnTriggerExit 触发(一次)
18.png

19.png

20.png

3.找到离角色更近的一侧,用来决定后面开启哪边的IK

EnvironmentInteractionContext加入:判断碰撞相交位置更靠近哪一侧
21.png
  1.     // 身体两侧
  2.     public enum EBodySide
  3.     {
  4.         RIGHT,
  5.         LEFT
  6.     }
复制代码
  1.     // 当前IK约束
  2.     public TwoBoneIKConstraint CurrentIkConstraint { get; private set; }
  3.     // 当前多旋转约束
  4.     public MultiRotationConstraint CurrentMultiRotationConstraint { get; private set; }
  5.     // 当前IK控制的目标位置
  6.     public Transform CurrentIkTargetTransform { get; private set; }
  7.     // 当前肩部骨骼
  8.     public Transform CurrentShoulderTransform { get; private set; }
  9.     // 当前身体的侧边(左或右)
  10.     public EBodySide CurrentBodySide { get; private set; }
  11.     /// <summary>
  12.     /// 根据传入位置,判断目标更靠近左侧还是右侧肩部,设置当前身体的侧边
  13.     /// </summary>
  14.     /// <param name="positionToCheck">需要检测的目标位置</param>
  15.     public void SetCurrentSide(Vector3 positionToCheck)
  16.     {
  17.         // 左肩部骨骼
  18.         Vector3 leftShoulder = _leftIkConstraint.data.root.transform.position;
  19.         // 右肩部骨骼
  20.         Vector3 rightShoulder = _rightIkConstraint.data.root.transform.position;
  21.         // 标志位:目标位置是否更靠近左侧
  22.         bool isLeftCloser = Vector3.Distance(positionToCheck, leftShoulder) <
  23.                             Vector3.Distance(positionToCheck, rightShoulder);
  24.         if (isLeftCloser)
  25.         {
  26.             CurrentBodySide = EBodySide.LEFT;
  27.             CurrentIkConstraint = _leftIkConstraint;
  28.             CurrentMultiRotationConstraint = _leftMultiRotationConstraint;
  29.         }
  30.         else
  31.         {
  32.             CurrentBodySide = EBodySide.RIGHT;
  33.             CurrentIkConstraint = _rightIkConstraint;
  34.             CurrentMultiRotationConstraint = _rightMultiRotationConstraint;
  35.         }
  36.         // 记录当前肩部骨骼 和 IK控制的目标位置
  37.         CurrentShoulderTransform = CurrentIkConstraint.data.root.transform;
  38.         CurrentIkTargetTransform = CurrentIkConstraint.data.target.transform;
  39.     }
复制代码
EnvironmentInteractionState
  1.     /// <summary>
  2.     /// 启动 IK 目标位置追踪
  3.     /// </summary>
  4.     /// <param name="intersectingCollider">相交的碰撞体,作为追踪关联对象</param>
  5.     protected void StartIkTargetPositionTracking(Collider intersectingCollider)
  6.     {
  7.         //只有碰撞体的层级为Interactable时才进行IK目标位置追踪
  8.         if (intersectingCollider.gameObject.layer == LayerMask.NameToLayer("Interactable"))
  9.         {
  10.             // 最近的碰撞点
  11.             Vector3 closestPointFromRoot = GetClosestPointOnCollider(intersectingCollider, Context.RootTransform.position);
  12.             // 设置当前更靠近的侧面(根据最近的碰撞点)
  13.             Context.SetCurrentSide(closestPointFromRoot);
  14.         }
  15.     }
  16.     /// <summary>
  17.     /// 更新 IK 目标位置
  18.     /// </summary>
  19.     /// <param name="intersectingCollider">相交的碰撞体,依据其状态更新目标位置</param>
  20.     protected void UpdateIkTargetPosition(Collider intersectingCollider)
  21.     {
  22.     }
  23.     /// <summary>
  24.     /// 重置 IK 目标位置追踪
  25.     /// </summary>
  26.     /// <param name="intersectingCollider">相交的碰撞体,针对其执行追踪重置</param>
  27.     protected void ResetIkTargetPositionTracking(Collider intersectingCollider)
  28.     {
  29.     }
复制代码
这里要用到一个新的变量RootTransform用来在GetClosestPointOnCollider()方法中传入参数positionToCheck
EnvironmentInteractionContext
  1.     // 根对象
  2.     private Transform _rootTransform;
复制代码
构造函数要加入这个变量
  1.     public EnvironmentInteractionContext(
  2.         TwoBoneIKConstraint leftIkConstraint,
  3.         TwoBoneIKConstraint rightIkConstraint,
  4.         MultiRotationConstraint leftMultiRotationConstraint,
  5.         MultiRotationConstraint rightMultiRotationConstraint,
  6.         CharacterController characterController,
  7.         Transform rootTransform)
  8.     {
  9.         _leftIkConstraint = leftIkConstraint;
  10.         _rightIkConstraint = rightIkConstraint;
  11.         _leftMultiRotationConstraint = leftMultiRotationConstraint;
  12.         _rightMultiRotationConstraint = rightMultiRotationConstraint;
  13.         _characterController = characterController;
  14.         _rootTransform = rootTransform;
  15.     }
复制代码
  1.     public Transform RootTransform => _rootTransform;
复制代码
当然,在EnvironmentInteractionStateMachine中也要传入这个变量
Awake()
  1.         _context = new EnvironmentInteractionContext(_leftIkConstraint, _rightIkConstraint, _leftMultiRotationConstraint, _rightMultiRotationConstraint, characterController,transform.root);
复制代码
写一下ResetState的GetNextState()的下一状态切换逻辑
  1.     public override EnvironmentInteractionStateMachine.EEnvironmentInteractionState GetNextState()
  2.     {
  3.         // 下一个状态为 SearchState
  4.         return EnvironmentInteractionStateMachine.EEnvironmentInteractionState.Search;
  5.         //return StateKey;
  6.     }
复制代码
注意:
22.png

SearchStateOnTriggerEnter()中调用StartIkTargetPositionTracking()启动 IK 目标位置追踪
  1.     public override void OnTriggerEnter(Collider other) {
  2.         // 进入搜索状态时,开始跟踪目标位置
  3.         StartIkTargetPositionTracking(other);
  4.     }
复制代码
测试一下功能是否正常:
23.png

24.gif

效果倒是正常,不过这是我调试好久发现的问题,只有挂载rigidbody的物体才会触发Trigger回调函数,正常来说只要一方有rigidbody就能触发,不知道为什么这里会出现这个问题,角色身上的这个触发器肯定是rigidbody,那已经满足条件了,为什么还要其他物体也要挂载rigidbody,想不明白。。。
不过实现了就好,后面再排查问题吧,先完成最要紧
4.解决一下在狭窄通道走过的时候,左右频繁触发的问题

EnvironmentInteractionContext
  1.     // 当前交互的碰撞体
  2.     public Collider CurrentIntersectingCollider { get; set; }
复制代码
EnvironmentInteractionState
  1.     /// <summary>
  2.     /// 启动 IK 目标位置追踪
  3.     /// </summary>
  4.     /// <param name="intersectingCollider">相交的碰撞体,作为追踪关联对象</param>
  5.     protected void StartIkTargetPositionTracking(Collider intersectingCollider)
  6.     {
  7.         //只有碰撞体的层级为Interactable && 当前没有可交互的碰撞体 时才进行IK目标位置追踪
  8.         // 防止频繁触发
  9.         if (intersectingCollider.gameObject.layer == LayerMask.NameToLayer("Interactable") && Context.CurrentIntersectingCollider == null)
  10.         {
  11.             // 记录当前碰撞体
  12.             Context.CurrentIntersectingCollider = intersectingCollider;
  13.             // 最近的碰撞点
  14.             Vector3 closestPointFromRoot = GetClosestPointOnCollider(intersectingCollider, Context.RootTransform.position);
  15.             // 设置当前更靠近的侧面(根据最近的碰撞点)
  16.             Context.SetCurrentSide(closestPointFromRoot);
  17.         }
  18.     }
复制代码
  1.     /// <summary>
  2.     /// 重置 IK 目标位置追踪
  3.     /// </summary>
  4.     /// <param name="intersectingCollider">相交的碰撞体,针对其执行追踪重置</param>
  5.     protected void ResetIkTargetPositionTracking(Collider intersectingCollider)
  6.     {
  7.         if(intersectingCollider == Context.CurrentIntersectingCollider)
  8.         {
  9.             Context.CurrentIntersectingCollider = null;
  10.         }
  11.     }
复制代码
SearchState
  1.     public override void OnTriggerEnter(Collider other) {
  2.         Debug.Log("Trigger:Enter");
  3.         // 进入搜索状态,开始跟踪目标位置
  4.         StartIkTargetPositionTracking(other);
  5.     }
  6.     public override void OnTriggerStay(Collider other) { }
  7.     public override void OnTriggerExit(Collider other) {
  8.         Debug.Log("Trigger:Exit");
  9.         // 退出搜索状态,停止跟踪目标位置
  10.         ResetIkTargetPositionTracking(other);
  11.     }
复制代码
5.设置IK的目标位置

EnvironmentInteractionContext
  1.     // 相交碰撞体的最近点——默认值设为无穷大
  2.     public Vector3 ClosestPointOnColliderFromShoulder { get; set; } = Vector3.positiveInfinity;
复制代码
EnvironmentInteractionState
  1.     /// <summary>
  2.     /// 设置 IK 目标位置
  3.     /// </summary>
  4.     /// <param name="targetPosition"></param>
  5.     private void SetIkTargetPosition()
  6.     {
  7.         // 最近的碰撞点
  8.         Context.ClosestPointOnColliderFromShoulder = GetClosestPointOnCollider(Context.CurrentIntersectingCollider, Context.CurrentShoulderTransform.position);
  9.     }
复制代码
  1.     /// <summary>
  2.     /// 启动 IK 目标位置追踪
  3.     /// </summary>
  4.     /// <param name="intersectingCollider">相交的碰撞体,作为追踪关联对象</param>
  5.     protected void StartIkTargetPositionTracking(Collider intersectingCollider)
  6.     {
  7.         //只有碰撞体的层级为Interactable && 当前没有可交互的碰撞体 时才进行IK目标位置追踪
  8.         // 防止频繁触发
  9.         if (intersectingCollider.gameObject.layer == LayerMask.NameToLayer("Interactable") && Context.CurrentIntersectingCollider == null)
  10.         {
  11.             // 原来的代码不变
  12.             //设置IK目标位置
  13.             SetIkTargetPosition();
  14.         }
  15.     }
复制代码
  1.     /// <summary>
  2.     /// 更新 IK 目标位置
  3.     /// </summary>
  4.     /// <param name="intersectingCollider">相交的碰撞体,依据其状态更新目标位置</param>
  5.     protected void UpdateIkTargetPosition(Collider intersectingCollider)
  6.     {
  7.         // 在接触过程中,一直更新IK目标位置
  8.         if (Context.CurrentIntersectingCollider == intersectingCollider)
  9.         {
  10.             SetIkTargetPosition();
  11.         }
  12.     }
复制代码
SearchState
  1.     public override void OnTriggerStay(Collider other) {
  2.         // 跟踪目标位置
  3.         UpdateIkTargetPosition(other);
  4.     }
复制代码
然后在EnvironmentInteractionStateMachine中加入可视化
  1.     /// <summary>
  2.     /// 当物体被选中时调用Gizmos绘制
  3.     /// </summary>
  4.     private void OnDrawGizmosSelected()
  5.     {
  6.         Gizmos.color = Color.red;
  7.         // 在最近碰撞点处绘制一个红色的球
  8.         if (_context != null && _context.ClosestPointOnColliderFromShoulder != null)
  9.         {
  10.             Gizmos.DrawSphere(_context.ClosestPointOnColliderFromShoulder, 0.03f);
  11.         }
  12.     }
复制代码
25.gif

新的问题出现了:
当角色行走的时候,由于身体会浮动,这个最近的碰撞点也在上下浮动,后面加上动画会出现手一直在墙上 上下乱摸。。。
6.解决最近碰撞点上下浮动问题

其实加一个变量记录一下角色的肩高就行,设定ik位置的时候传入该参数,这个点的高度就保持不变了
EnvironmentInteractionContext的构造函数加入一个角色的肩部高度变量
  1.     public EnvironmentInteractionContext(
  2.         TwoBoneIKConstraint leftIkConstraint,
  3.         TwoBoneIKConstraint rightIkConstraint,
  4.         MultiRotationConstraint leftMultiRotationConstraint,
  5.         MultiRotationConstraint rightMultiRotationConstraint,
  6.         CharacterController characterController,
  7.         Transform rootTransform)
  8.     {
  9.         _leftIkConstraint = leftIkConstraint;
  10.         _rightIkConstraint = rightIkConstraint;
  11.         _leftMultiRotationConstraint = leftMultiRotationConstraint;
  12.         _rightMultiRotationConstraint = rightMultiRotationConstraint;
  13.         _characterController = characterController;
  14.         _rootTransform = rootTransform;
  15.         CharacterShoulderHeight = leftIkConstraint.data.root.transform.position.y;
  16.     }
复制代码
  1.     // 角色的肩部高度,用来约束Ik的高度
  2.     public float CharacterShoulderHeight { get; private set; }
复制代码
EnvironmentInteractionState传入目标位置的参数的y轴改成角色肩高CharacterShoulderHeight
  1.     /// <summary>
  2.     /// 设置 IK 目标位置
  3.     /// </summary>
  4.     /// <param name="targetPosition"></param>
  5.     private void SetIkTargetPosition()
  6.     {
  7.         // 最近的碰撞点
  8.         Context.ClosestPointOnColliderFromShoulder = GetClosestPointOnCollider(Context.CurrentIntersectingCollider,
  9.             // 目标位置:上半身的xz位置 角色肩高的y位置(高度位置)
  10.             new Vector3(Context.RootTransform.position.x, Context.CharacterShoulderHeight, Context.RootTransform.position.z));
  11.     }
复制代码
问题解决
26.gif

7.在离开当前碰撞体后,重置Ik的目标位置为无穷大

EnvironmentInteractionState
  1.     /// <summary>
  2.     /// 重置 IK 目标位置追踪
  3.     /// </summary>
  4.     /// <param name="intersectingCollider">相交的碰撞体,针对其执行追踪重置</param>
  5.     protected void ResetIkTargetPositionTracking(Collider intersectingCollider)
  6.     {
  7.         if(intersectingCollider == Context.CurrentIntersectingCollider)
  8.         {
  9.             // 重置当前碰撞体为空
  10.             Context.CurrentIntersectingCollider = null;
  11.             // 重置IK目标位置为无穷大
  12.             Context.ClosestPointOnColliderFromShoulder = Vector3.positiveInfinity;
  13.         }
  14.     }
复制代码
效果:
27.gif

8.开始对手部的IK组件目标位置进行更新

注意:需要为ik的目标位置加一个法向的偏移,防止手部穿模(因为手是有厚度的,不是纸片人)
EnvironmentInteractionState
  1.     /// <summary>
  2.     /// 设置 IK 目标位置
  3.     /// </summary>
  4.     /// <param name="targetPosition"></param>
  5.     private void SetIkTargetPosition()
  6.     {
  7.         // 最近的碰撞点
  8.         Context.ClosestPointOnColliderFromShoulder = GetClosestPointOnCollider(Context.CurrentIntersectingCollider,
  9.             // 目标位置:上半身的xz位置 角色肩高的y位置(高度位置)
  10.             new Vector3(Context.RootTransform.position.x, Context.CharacterShoulderHeight, Context.RootTransform.position.z));
  11.         #region 让手部的IK目标移动到这个最近碰撞点
  12.         // 1. 射线方向:从“最近碰撞点”指向“当前肩部位置”的向量
  13.         Vector3 rayDirection = Context.CurrentShoulderTransform.position
  14.                              - Context.ClosestPointOnColliderFromShoulder;
  15.             // Unity 中向量的运算:Vector3 终点 - Vector3 起点
  16.         // 2. 归一化,得到单位向量
  17.         Vector3 normalizedRayDirection = rayDirection.normalized;
  18.         // 3. 偏移距离,防止手部穿模
  19.         float offsetDistance = 0.05f;
  20.         // 4. 最终要到达的位置:在“最近碰撞点”基础上,加上 沿rayDirection射线方向偏移 offsetDistance 距离
  21.         Vector3 targettPosition = Context.ClosestPointOnColliderFromShoulder
  22.             + normalizedRayDirection * offsetDistance;
  23.         // 5. 更新 IK 目标位置
  24.         Context.CurrentIkTargetTransform.position = targettPosition;
  25.         #endregion
  26.     }
复制代码
如果把权重一开始就拉到1,效果是这样的:
28.gif

当然,我们还得根据具体的状态写Ik权重的控制脚本
每个具体状态的Ik控制逻辑的脚本编写

也就是根据状态决定是否/怎样更新手部Two Bone IK Constraint的权重

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册