IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> 【Unity】NavMeshAgent与Animator及RootMotion的整合 -> 正文阅读

[游戏开发]【Unity】NavMeshAgent与Animator及RootMotion的整合

【Unity】NavMeshAgent与Animator及RootMotion的配合

Unity目前(2019.4)还没有内置开箱即用的将导航与动画整合的方案,这里提供了一个将NavMeshAgent和Animator整合的思路,并且兼容了RootMotion。

实现NavMeshAgent和Animator整合时,主要需要解决滑步问题和NavMeshAgent与RootMotion的数据同步问题,在不同的使用情境下,这两个问题有不同的解决方案。

如果是不带RootMotion的角色,需要避免角色滑步,将角色的导航移动速度换算成动画状态机的运动BlendTree控制参数即可,例如 animator.SetFloat("LocomotionSpeed", agent.velocity.magnitude/maxLocomotionSpeed) ,这里可能会有更复杂的换算公式,具体取决于BlendTree形式和其参数。

如果是带RootMotion的角色,并且通过RootMotion来使角色向目标点移动,无需担心滑步问题,但需要处理RootMotion数据和NavMeshAgent数据的同步。数据同步过程在 OnAnimatorMove() 方法中执行,主要步骤是:① 禁用NavMeshAgent组件的位置更新(agent.updatePosition = false);② 将RootMotion数据应用到NavMeshAgent组件中,驱动NavMeshAgent更新状态和进行导航模拟(agent.speed = animator.velocity.magnitudeagent.velocity = animator.velocity);③ 将导航模拟结果设置回角色的Transform上,避免RootMotion将角色移动到导航不可达区域(animator.transform.position = agent.nextPosition)。另外,需要在角色转身时对其移动速度进行衰减处理,以免当角色RootMotion速度太快并且目标点在角色身后附近时,角色陷入圆周运动无法抵达目标点。

如果是带RootMotion的角色,并且不使用NavMeshAgent目标点而是通过玩家的XY输入控制角色移动(例如使用键盘或摇杆操控主角),同样无需担心滑步问题,实现过程与上一种情况基本相同,差异是不需要处理转身时的移动速度衰减(除非有设计需求)。

一些额外的值得关注的点:① NavMeshAgent控制角色转向的速度非常慢,即使将AngualrSpeed设置为很大的值也依然很慢,可以禁用NavMeshAgent组件的旋转更新(agent.updateRotation = false),并自行控制角色旋转(建议不要使用Lerp和SLerp,他们在向背面旋转时效果不好);② 如果某一帧时NavMeshAgent组件处于stop状态(agent.isStopped == true),将其取消stop要等到下一帧才能生效,当帧为其设置目标点不会使导航生效。

主要代码

using System;
using UnityEngine;
using UnityEngine.AI;

/// <summary>
/// 可导航角色的运动驱动模式。
/// </summary>
public enum NavCharacterDriveMode
{
    /// <summary>
    /// 使用导航组件驱动角色运动。
    /// 此模式适用于不带RootMotion的角色。
    /// </summary>
    NavDestination,

    /// <summary>
    /// 使用RootMotion驱动角色运动。
    /// 此模式适用于带RootMotion的角色。
    /// 在此模式下,导航组件只用于限制角色的可达区域。
    /// 通常使用此模式驱动受玩家XY输入操控的带有RootMotion的角色。
    /// </summary>
    RootMotion,

    /// <summary>
    /// 同时使用RootMotion和导航组件驱动角色运动。
    /// 此模式适用于带RootMotion的角色。
    /// 在此模式下,由导航组件控制角色的移动路径,由RootMotion控制角色的位移。
    /// 通常使用此模式驱动操控方式为直接设置目标点(例如AI)的带有RootMotion的角色。
    /// </summary>
    Both,
}

/// <summary>
/// 带有导航组件的角色的移动控制器。
/// </summary>
[RequireComponent(typeof(Animator))]
[RequireComponent(typeof(NavMeshAgent))]
public class NavCharacterController : MonoBehaviour
{
    /// <summary>
    /// 可导航角色位移驱动模式。
    /// </summary>
    public NavCharacterDriveMode CharacterDriveMode
    {
        get => _characterDriveMode;
        set
        {
            _characterDriveMode = value;
            UpdateCharacterDriver(_characterDriveMode);
        }
    }

    /// <summary>
    /// 角色转向速度(角度/秒)。
    /// </summary>
    public float CharacterTurningSpeed
    {
        get => _characterTurningSpeed;
        set => _characterTurningSpeed = value;
    }

    /// <summary>
    /// 角色移动速度(米/秒)。
    /// </summary>
    public float CharacterMovementSpeed
    {
        get => _characterMovementSpeed;
        set => _characterMovementSpeed = value;
    }

    /// <summary>
    /// 用于控制角色移动BlendTree的基准参数值。
    /// </summary>
    public float BaseLocomotionParamValue
    {
        get => _baseLocomotionParamValue;
        set => _baseLocomotionParamValue = value;
    }

    /// <summary>
    /// 用于控制角色移动BlendTree的基准参数名。
    /// </summary>
    public string LocomotionParamName
    {
        get => _locomotionParamName;
        set => _locomotionParamName = value;
    }


    [Tooltip("可导航角色位移驱动模式。")]
    [SerializeField]
    private NavCharacterDriveMode _characterDriveMode;

    [Tooltip("角色转向速度(角度/秒)。")]
    [Range(0f, 3600f)]
    [SerializeField]
    private float _characterTurningSpeed = 720f;

    [Tooltip("角色移动速度(米/秒)。")]
    [Range(0f, 1000f)]
    [SerializeField]
    private float _characterMovementSpeed = 1.4f;

    [Tooltip("用于控制角色移动BlendTree的基准参数值。")]
    [Range(0f, 1000f)]
    [SerializeField]
    private float _baseLocomotionParamValue = 1.0f;

    [Tooltip("用于控制角色移动BlendTree的基准参数名。")]
    [SerializeField]
    private string _locomotionParamName = "MoveSpeed";

    private NavMeshAgent _agent;

    private Animator _animator;


    private void OnValidate()
    {
        if (_agent && _animator)
        {
            UpdateCharacterDriver(CharacterDriveMode);
        }
    }

    private void Awake()
    {
        _agent = GetComponent<NavMeshAgent>();
        _animator = GetComponent<Animator>();

        // 导航的转向速度太慢,所以自行控制转向,不通过导航控制转向
        _agent.updateRotation = false;
        // 更新角色位移驱动模式
        UpdateCharacterDriver(CharacterDriveMode);
    }

    private void Update()
    {
        // 仅使用RootMotion驱动位移的角色,朝向由外部驱动
        if (CharacterDriveMode == NavCharacterDriveMode.RootMotion)
        {
            return;
        }

        // todo 测试代码,设置角色目标位置
        if (CharacterDriveMode != NavCharacterDriveMode.RootMotion && Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            if (Physics.Raycast(ray.origin, ray.direction, out var hitInfo, 1000, ~0, QueryTriggerInteraction.Ignore))
            {
                // 将处于stop状态的导航组件取消stop,要等到下一帧才能生效,当帧的设置目标点的操作不会使导航生效
                if (_agent.isStopped) { _agent.isStopped = false; }
                _agent.SetDestination(hitInfo.point);
            }
        }

        // 计算角色朝向
        var turnPos = _agent.steeringTarget;
        var dir = turnPos - _animator.transform.position;
        dir.y = 0;
        var forward = _animator.transform.forward;
        var deflectionAngle = Vector3.SignedAngle(forward, dir, Vector3.up);
        var maxTurnAngle = Mathf.Abs(deflectionAngle);
        if (maxTurnAngle > 1)
        {
            // 要避免转过头
            var turnDir = deflectionAngle < 0 ? -1 : 1;
            var turnAngle = CharacterTurningSpeed * turnDir * Time.deltaTime;
            turnAngle = Mathf.Clamp(turnAngle, -maxTurnAngle, maxTurnAngle);
            forward = Quaternion.AngleAxis(turnAngle, Vector3.up) * forward;
            _animator.transform.forward = forward;
        }

        switch (CharacterDriveMode)
        {
            // 处理不带RootMotion角色地位移
            case NavCharacterDriveMode.NavDestination:
                // todo CharacterSpeed是逻辑层的角色移动速度数据,需要动态设置
                _agent.speed = CharacterMovementSpeed;

                // 将导航数据转换成动画数据,使动画效果贴合移动效果
                // todo 需要根据实际的BlendTree参数调整换算方式,这里只是最简单的线性BlendTree的换算方式
                var locomotionParamValue0 = _agent.velocity.magnitude / CharacterMovementSpeed;
                _animator.SetFloat(LocomotionParamName, locomotionParamValue0);
                break;

            // 处理带RootMotion角色地位移
            case NavCharacterDriveMode.Both:
                // 处理角色转向时的移动速度衰减
                // 处理转向速度衰减除了能使角色转向时更加自然,另一个主要目的是防止目标点在角色身后附近时,
                // RootMotion移动太快导致角色始终无法在目标点位置停止,进而导致角色绕圈,
                // 这两个公式是通过不断尝试找到的效果较好的公式,也可以根据实际情况进行替换。
                var remainingDistance = _agent.remainingDistance;
                var locomotionParamValue1 = remainingDistance > _agent.radius ?
                    // 离目标点距离大于角色半径时,使用立方插值公式进行衰减
                    BaseLocomotionParamValue * (1 - Mathf.Pow(maxTurnAngle / 180, 3)) :
                    // 离目标点距离小于角色半径时,使用EXP插值公式进行衰减
                    BaseLocomotionParamValue * remainingDistance / _agent.radius *
                    (180 - maxTurnAngle < Mathf.Epsilon ? 0 : Mathf.Pow(2, 10 * (1 - maxTurnAngle / 180) - 10));
                _animator.SetFloat(LocomotionParamName, locomotionParamValue1);
                break;

            default:
                throw new ArgumentOutOfRangeException();
        }

        // 将处于stop状态的导航组件取消stop,要等到下一帧才能生效,
        // 当帧的设置目标点的操作不会使导航生效,所以这里不再主动stop导航
        // 抵达目标点
        //if (remainingDistance < 0.01f && !_agent.isStopped)
        //{
        //    _agent.isStopped = true;
        //}
    }

    private void OnAnimatorMove()
    {
        // 对带RootMotion的角色进行的额外处理
        switch (CharacterDriveMode)
        {
            case NavCharacterDriveMode.RootMotion:
            case NavCharacterDriveMode.Both:
                // 应用RootMotion数据
                _animator.ApplyBuiltinRootMotion();
                // 将RootMotion数据应用到导航组件
                var animatorVelocity = _animator.velocity;
                _agent.speed = animatorVelocity.magnitude;
                _agent.velocity = animatorVelocity;
                // 当角色走向导航不能到达的位置时,使用导航数据将角色拉回到导航网格上
                _animator.transform.position = _agent.nextPosition;
                break;
        }
    }

    private void UpdateCharacterDriver(NavCharacterDriveMode characterDriveMode)
    {
        switch (characterDriveMode)
        {
            // 角色不带RootMotion,由导航驱动其位移
            case NavCharacterDriveMode.NavDestination:
                _agent.updatePosition = true;
                _animator.applyRootMotion = false;
                break;

            // 角色带RootMotion,由RootMotion驱动其位移
            case NavCharacterDriveMode.RootMotion:
            case NavCharacterDriveMode.Both:
                _agent.updatePosition = false;
                _animator.applyRootMotion = true;
                break;

            // 不应该执行到这段代码,只用来防止增加枚举类型后忘记补充实现
            default:
                throw new ArgumentOutOfRangeException();
        }
    }
}
  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2021-07-23 11:09:28  更:2021-07-23 11:11:03 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/4 13:56:20-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码