当前位置: 首页 > news >正文

Unity 基于navMesh的怪物追踪惯性系统

今天做项目适合 策划想要实现一个在现有的怪物追踪系统上实现怪物拥有惯性功能

以下是解决方案分享:

怪物基类代码:

​
using UnityEngine;
using UnityEngine.AI;

[RequireComponent(typeof(NavMeshAgent))]
[RequireComponent(typeof(AudioSource))]
public abstract class MonsterBase : MonoBehaviour
{
    public enum MonsterState { Idle, Patrol, Chase }//1.待机,2.巡逻,3.追击
    
    [Header("巡逻时的速度")]
    public float patrolSpeed = 2f;
    [Header("追击玩家时的速度")]
    public float chaseSpeed = 5f;
    [Header("怪物的加速度")]
    public float acceleration = 50f; // 大加速度
    [Header("怪物的旋转速度")]
    public float angularSpeed = 120f;
    [Header("怪物与目标的停止距离")]
    public float stoppingDistance = 0.1f;
    [Header("怪屋听觉能检测到玩家的范围")]
    public float detectionRange = 10f;
    [Space(30)]
    [Header("视觉检测设置")]
    [Tooltip("扇形检测半径")]
    public float visionRange = 5f;
    [Tooltip("扇形检测角度(度)")]
    [Range(0, 360)]
    public float visionAngle = 90f;
    [Tooltip("检测层级(应包含玩家和障碍物)")]
    public LayerMask visionMask;
    
    
    [Header("声音设置")]
    public AudioClip howlSound;
    [Header("最大音量")]
    public float maxHowlVolume = 1f;
    [Header("开始嚎叫距离阈值")]
    public float howlDistance = 5f;
    
    protected NavMeshAgent agent;
    protected AudioSource audioSource;//怪物声音
    protected Transform player;
    protected MonsterState currentState = MonsterState.Patrol;
    protected Animator animator;
    protected SpriteRenderer spriteRenderer;//精灵组件
    
    private Vector2 lastPosition;//上一帧位置
    private Direction currentDirection = Direction.Down;//当前移动方向
    
    protected enum Direction { Up, Down, Left, Right }

    void Awake()
    {
        agent = GetComponent<NavMeshAgent>();
        audioSource = GetComponent<AudioSource>();
        animator = GetComponent<Animator>();
        spriteRenderer = GetComponentInChildren<SpriteRenderer>();
        player = GameObject.FindGameObjectWithTag("Player").transform;
        
        ConfigureAgent();
    }

    void ConfigureAgent()
    {
        agent.acceleration = acceleration;
        agent.angularSpeed = angularSpeed;
        agent.stoppingDistance = stoppingDistance;
        agent.updateRotation = false; // 禁用自动旋转,我们自己控制
        agent.updateUpAxis = false; // 保持2D平面
    }

    void Update()
    {
        
        UpdateState();
        UpdateAnimation();
        UpdateHowlSound();
    }
    
    /// <summary>
    /// 抽象状态机 
    /// </summary>
    protected abstract void UpdateState();

    public virtual void  UpdateAnimation()
    {
        // 计算移动方向
        Vector2 movement = (Vector2)transform.position - lastPosition;
        lastPosition = transform.position;

        if (movement.magnitude > 0.01f) // 正在移动
        {
            if (Mathf.Abs(movement.x) > Mathf.Abs(movement.y))
            {
                currentDirection = movement.x > 0 ? Direction.Right : Direction.Left;
            }
            else
            {
                currentDirection = movement.y > 0 ? Direction.Up : Direction.Down;
            }
            //TODO:考虑设计CONST表
            // animator.SetBool("IsMoving", true);
            // animator.SetInteger("Direction", (int)currentDirection);
        }
        else // 待机
        {
           // animator.SetBool("IsMoving", false);
        }
        
        // 更新Sprite朝向
        if (currentDirection == Direction.Left)
        {
            spriteRenderer.flipX = true;
        }
        else if (currentDirection == Direction.Right)
        {
            spriteRenderer.flipX = false;
        }
    }
    /// <summary>
    /// 获取当前移动方向(归一化向量)
    /// </summary>
    protected Vector3 GetMovementDirection()
    {
        if (agent != null && agent.velocity.magnitude > 0.1f)
        {
            return agent.velocity.normalized;
        }
        return transform.forward; // 默认使用面朝方向
    }
    protected bool CanSeePlayer()
    {
        if (player == null) return false;

        Vector3 directionToPlayer = player.position - transform.position;
        float distanceToPlayer = directionToPlayer.magnitude;
    
        // 距离检查
        if (distanceToPlayer > visionRange)
            return false;

        // 获取当前移动方向
        Vector3 moveDirection = GetMovementDirection();
    
        // 角度检查(基于移动方向)
        float angleToPlayer = Vector3.Angle(moveDirection, directionToPlayer);
        if (angleToPlayer > visionAngle / 2f)
            return false;

        // 视线遮挡检查
        RaycastHit hit;
        if (Physics.Raycast(transform.position, directionToPlayer.normalized, out hit, visionRange, visionMask))
        {
            return hit.collider.CompareTag("Player");
        }

        return false;
    }
   
    /// <summary>
    /// 在Scene视图中绘制视觉范围(调试用)
    /// </summary>
    /// <summary>
    /// 绘制基于移动方向的扇形检测范围
    /// </summary>
    private void OnDrawGizmos()
    {
        // 获取当前移动方向(编辑器中使用面朝方向,运行时用实际移动方向)
        Vector3 moveDir = Application.isPlaying ? GetMovementDirection() : transform.forward;
    
        // 设置扇形颜色
        Gizmos.color = new Color(1, 0.5f, 0, 0.3f); // 半透明橙色
    
        // 计算扇形边缘
        Vector3 center = transform.position;
        Vector3 forward = moveDir * visionRange;
        Vector3 left = Quaternion.Euler(0, -visionAngle / 2, 0) * forward;
        Vector3 right = Quaternion.Euler(0, visionAngle / 2, 0) * forward;

        // 绘制扇形边缘线
        Gizmos.DrawLine(center, center + left);
        Gizmos.DrawLine(center, center + right);
    
        // 绘制扇形弧线
        Vector3 lastPoint = center + left;
        int segments = 20;
        for (int i = 1; i <= segments; i++)
        {
            float t = (float)i / segments;
            float angle = Mathf.Lerp(-visionAngle / 2, visionAngle / 2, t);
            Vector3 nextPoint = center + Quaternion.Euler(0, angle, 0) * forward;
            Gizmos.DrawLine(lastPoint, nextPoint);
            lastPoint = nextPoint;
        }
    
        // 绘制移动方向指示器
        Gizmos.color = Color.red;
        Gizmos.DrawLine(center, center + moveDir * 1.5f);
    }
    void UpdateHowlSound()
    {
        if (howlSound == null) return;
        
        float distanceToPlayer = Vector3.Distance(transform.position, player.position);
        if (distanceToPlayer <= howlDistance)
        {
            if (!audioSource.isPlaying)
            {
                audioSource.clip = howlSound;
                audioSource.Play();
            }
            
            // 距离越近声音越大
            float volume = Mathf.Lerp(0.1f, maxHowlVolume, 1 - (distanceToPlayer / howlDistance));
            audioSource.volume = volume;
        }
        else if (audioSource.isPlaying)
        {
            audioSource.Stop();
        }
    }

    protected void ChangeState(MonsterState newState)
    {
        if (currentState == newState) return;
        
        currentState = newState;
        
        switch (currentState)
        {
            case MonsterState.Idle:
                agent.isStopped = true;
                break;
            case MonsterState.Patrol:
                agent.speed = patrolSpeed;
                agent.isStopped = false;
                break;
            case MonsterState.Chase:
                agent.speed = chaseSpeed;
                agent.isStopped = false;
                break;
        }
    }

    void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.CompareTag("Player"))
        {
            Debug.Log("触碰到玩家");
            //TODO:玩家死亡
            //collision.gameObject.GetComponent<PlayerController>().Die();
        }
    }

​

带有惯性的怪物实现:

using UnityEngine;
using UnityEngine.AI;

[RequireComponent(typeof(NavMeshAgent))]
[RequireComponent(typeof(Rigidbody))]
public class Angry : MonsterBase
{
    [Header("数据配置表")]
    public MonsterConfig config;
    [Header("巡逻点数组")]
    public Transform[] patrolPoints;
    [Header("每个巡逻点的停留时间")]
    public float waitTimeAtPoint = 2f;
    [Header("探测范围(圆)")] 
    public float AngrydetectionRange = 2f;
    
    [Header("移动惯性超出系数")]
    public float overshootFactor = 1.8f;
    [Header("急撒系数")]
    public float brakeForce = 4f;
    [Header("最小启动惯性移动阈值速度")]
    public float overshootMinSpeed = 0.5f;
    [Header("触发惯性移动距离倍速")]
    public float overshootTriggerDistance = 1.5f; 
    
    // 私有变量
    private int currentPatrolIndex = 0;
    private float waitTimer = 0f;
    private bool isWaiting = false;
    private bool isPlayerMove = false;
    
    // 惯性相关
    private Vector3 lastVelocity;
    private bool isOvershooting = false;
    private Rigidbody rb;
    private Vector3 overshootStartPosition;

    public void Start()
    {
        // 获取组件
        rb = GetComponent<Rigidbody>();
        agent = GetComponent<NavMeshAgent>();
        player = GameObject.FindGameObjectWithTag("Player").transform;
        
        // 初始化配置
        detectionRange = config.detectionRange;
        stoppingDistance = config.stoppingDistance;
        acceleration = config.acceleration;
        angularSpeed = config.angularSpeed;
        howlDistance = config.howlDistance;
        chaseSpeed = config.chaseSpeed;
        patrolSpeed = config.patrolSpeed;
        maxHowlVolume = config.maxHowlVolume;
        howlSound = config.howlSound;
        
        // 代理设置
        agent.autoBraking = false;
        agent.updatePosition = false;
        agent.updateRotation = false;
        
        // 刚体设置
        rb.isKinematic = false;
        rb.interpolation = RigidbodyInterpolation.Interpolate;
        rb.collisionDetectionMode = CollisionDetectionMode.Continuous;
        rb.constraints = RigidbodyConstraints.FreezeRotation | RigidbodyConstraints.FreezePositionY;
        
        // 初始巡逻目标
        if (patrolPoints.Length > 0)
        {
            SetPatrolDestination();
        }
        isPlayerMove = player.GetComponent<Movement>().IsPlayerMoving();
    }

    private void Update()
    {
        
        UpdateState();
        
        // 记录速度用于惯性(仅当代理活跃时)
        if (agent.velocity.magnitude > 0.1f && !isOvershooting && agent.isActiveAndEnabled)
        {
            lastVelocity = agent.velocity;
        }
    }

    private void FixedUpdate()
    {
        if (isOvershooting)
        {
            HandleOvershoot();
        }
        else if (agent.isOnNavMesh)
        {
            // 同步NavMeshAgent和Rigidbody位置
            agent.nextPosition = rb.position;
            rb.velocity = agent.velocity;
        }
    }

    protected override void UpdateState()
    {
        float distanceToPlayer = Vector3.Distance(transform.position, player.position);
        bool canSeePlayer = CanSeePlayer();
        // 只在非惯性状态下处理状态转换
        if (!isOvershooting)
        {
            if (canSeePlayer)
            {
                ChangeState(MonsterState.Chase);
                ChasePlayer();
            }
            else if (distanceToPlayer <= AngrydetectionRange && isPlayerMove)
            {
                ChangeState(MonsterState.Chase);
                ChasePlayer();
            }
            else if (currentState == MonsterState.Chase)
            {
                ChangeState(MonsterState.Patrol);
                // 返回巡逻时立即设置目标
                if (patrolPoints.Length > 0) SetPatrolDestination();
            }
            
            if (currentState == MonsterState.Patrol)
            {
                Patrol();
            }
        }
    }

    void ChasePlayer()
    {
        if (isOvershooting) return;
        
        agent.speed = chaseSpeed;
        agent.SetDestination(player.position);
        
        // 接近玩家时触发惯性
        float distanceToPlayer = Vector3.Distance(transform.position, player.position);
        if (distanceToPlayer < agent.stoppingDistance * overshootTriggerDistance)
        {
            StartOvershoot();
        }
    }

    void Patrol()
    {
        if (patrolPoints.Length == 0)
        {
            ChangeState(MonsterState.Idle);
            return;
        }

        if (isWaiting)
        {
            waitTimer += Time.deltaTime;
            if (waitTimer >= waitTimeAtPoint)
            {
                isWaiting = false;
                waitTimer = 0f;
                currentPatrolIndex = (currentPatrolIndex + 1) % patrolPoints.Length;
                SetPatrolDestination();
            }
            return;
        }

        // 使用路径状态和距离判断
        if (!agent.pathPending && agent.remainingDistance <= agent.stoppingDistance + 0.2f)
        {
            isWaiting = true;
        }
        // 添加目标丢失的重新设置逻辑
        else if (!agent.hasPath || agent.pathStatus != NavMeshPathStatus.PathComplete)
        {
            SetPatrolDestination();
        }
    }

    void SetPatrolDestination()
    {
        if (isOvershooting || patrolPoints.Length == 0 || currentPatrolIndex >= patrolPoints.Length) 
            return;
        
        agent.speed = patrolSpeed;
        NavMeshHit hit;
        if (NavMesh.SamplePosition(patrolPoints[currentPatrolIndex].position, out hit, 2.0f, NavMesh.AllAreas))
        {
            agent.SetDestination(hit.position);
            agent.isStopped = false;
        }
        else
        {
            Debug.LogWarning($"无法导航到巡逻点: {patrolPoints[currentPatrolIndex].name}");
            // 自动跳到下一个点
            currentPatrolIndex = (currentPatrolIndex + 1) % patrolPoints.Length;
            SetPatrolDestination();
        }
    }

    void StartOvershoot()
    {
        isOvershooting = true;
        overshootStartPosition = transform.position;
        agent.isStopped = true;
        
        // 计算惯性速度(保留原有方向但增加速度)
        lastVelocity = lastVelocity.normalized * agent.speed * overshootFactor;
        rb.velocity = lastVelocity;
    }

    void HandleOvershoot()
    {
        // 应用减速
        lastVelocity = Vector3.Lerp(lastVelocity, Vector3.zero, brakeForce * Time.fixedDeltaTime);
        rb.velocity = lastVelocity;
        
        // 检查是否该停止惯性
        if (lastVelocity.magnitude < overshootMinSpeed || 
            Vector3.Distance(overshootStartPosition, transform.position) > chaseSpeed * 3f)
        {
            EndOvershoot();
        }
    }

    void EndOvershoot()
    {
        isOvershooting = false;
        rb.velocity = Vector3.zero;
        
        if (agent.isOnNavMesh)
        {
            agent.Warp(transform.position);
            agent.isStopped = false;
            
            // 根据当前状态恢复行为
            if (currentState == MonsterState.Patrol)
            {
                SetPatrolDestination();
            }
            else if (currentState == MonsterState.Chase && 
                    Vector3.Distance(transform.position, player.position) <= AngrydetectionRange)
            {
                ChasePlayer();
            }
        }
    }

    void OnCollisionEnter(Collision collision)
    {
        if (isOvershooting)
        {
            // 撞墙反弹效果
            if (collision.gameObject.CompareTag("Wall"))
            {
                lastVelocity = Vector3.Reflect(lastVelocity, collision.contacts[0].normal) * 0.7f;
                rb.velocity = lastVelocity;
            }
            
            // 撞到玩家
            if (collision.gameObject.CompareTag("Player"))
            {
                //TODO:
                //collision.gameObject.GetComponent<PlayerHealth>().TakeDamage(1);
                EndOvershoot();
            }
        }
    }

    public override void UpdateAnimation()
    {
        Vector3 moveDirection = isOvershooting ? rb.velocity : agent.velocity;
        //TODO:添加Animation后更新启用
        // if (moveDirection.magnitude > 0.1f)
        // {
        //     animator.SetBool("IsMoving", true);
        //     
        //     // 确定方向(2.5D游戏通常只需要4方向)
        //     if (Mathf.Abs(moveDirection.x) > Mathf.Abs(moveDirection.z))
        //     {
        //         animator.SetInteger("Direction", moveDirection.x > 0 ? 2 : 3); // Right/Left
        //     }
        //     else
        //     {
        //         animator.SetInteger("Direction", moveDirection.z > 0 ? 0 : 1); // Up/Down
        //     }
        //     
        //     // 设置动画速度(惯性时更快)
        //     animator.SetFloat("MoveSpeed", moveDirection.magnitude / (isOvershooting ? chaseSpeed * 1.5f : chaseSpeed));
        // }
        // else
        // {
        //     animator.SetBool("IsMoving", false);
        // }
        //
        // // 更新惯性状态
        // animator.SetBool("IsOvershooting", isOvershooting);
    }

    void OnDrawGizmosSelected()
    {
        // 绘制巡逻路径
        if (patrolPoints != null && patrolPoints.Length > 0)
        {
            Gizmos.color = Color.yellow;
            for (int i = 0; i < patrolPoints.Length; i++)
            {
                if (patrolPoints[i] != null)
                {
                    Gizmos.DrawSphere(patrolPoints[i].position, 0.3f);
                    if (i < patrolPoints.Length - 1 && patrolPoints[i+1] != null)
                    {
                        Gizmos.DrawLine(patrolPoints[i].position, patrolPoints[i+1].position);
                    }
                }
            }
        }
        
        // 绘制当前目标
        if (currentState == MonsterState.Patrol && patrolPoints != null && currentPatrolIndex < patrolPoints.Length)
        {
            Gizmos.color = Color.red;
            Gizmos.DrawLine(transform.position, patrolPoints[currentPatrolIndex].position);
        }
    }

ok分享结束!

相关文章:

  • CAP理论 与 BASE理论
  • RAG文献阅读——用于知识密集型自然语言处理任务的检索增强生成
  • 数据库删除表数据
  • 在C盘新建文本文档
  • Go环境变量配置
  • Qt报错dependent ‘..\..\..\..\..\..\xxxx\QMainWindow‘ 或者 QtCore\QObject not exist
  • QEMU学习之路(7)— ARM64 启动Linux
  • 每天学一个 Linux 命令(16):mkdir
  • 【寻找Linux的奥秘】第四章:基础开发工具(下)
  • 信息学奥赛一本通 1498:Roadblocks | 洛谷 P2865 [USACO06NOV] Roadblocks G
  • Ubuntu 各个常见长期支持历史版本与代号
  • 低资源需求的大模型训练项目---3、综合对比与选型建议
  • 计算机基础复习资料整理
  • AI数字消费第一股,重构商业版图的新物种
  • oracle怎么查看是否走了索引
  • 系统设计面试总结:高性能相关:CDN(内容分发网络)、什么是静态资源、负载均衡(Nginx)、canal、主从复制
  • MPC控制基础解析与代码示例:赛车控制
  • 深度学习总结(10)
  • 开源实时语音交互大模型Ultravox-cn
  • zynq7020 u-boot 速通
  • 美观网站建设价格/软文广告营销
  • dw做网站的流程/广州疫情今天最新消息
  • 宁波海曙网站开发公司/河南百度seo
  • 网站站建设建技设术技术/怎么进行推广
  • 网站建设方案书安全性/seo课程
  • 手机制作网站的软件有哪些/怎么写软文推广