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分享结束!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/78805.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PyTorch进阶学习笔记[长期更新]

第一章 PyTorch简介和安装 PyTorch是一个很强大的深度学习库&#xff0c;在学术中使用占比很大。 我这里是Mac系统的安装&#xff0c;相比起教程中的win/linux安装感觉还是简单不少&#xff08;之前就已经安好啦&#xff09;&#xff0c;有需要指导的小伙伴可以评论。 第二章…

【区块链安全 | 第三十八篇】合约审计之获取私有数据(二)

文章目录 前言漏洞代码代码审计攻击步骤修复/开发建议审计思路前言 在【区块链安全 | 第三十七篇】合约审计之获取私有数据(一)中,介绍了私有数据、访问私有数据实例、Solidity 中的数据存储方式等知识,本文通过分析具体合约代码进行案例分析。 漏洞代码 // SPDX-Licens…

《微服务与事件驱动架构》读书分享

《微服务与事件驱动架构》读书分享 Building Event-Driver Microservices 英文原版由 OReilly Media, Inc. 出版&#xff0c;2020 作者&#xff1a;[加] 亚当 • 贝勒马尔 译者&#xff1a;温正东 作者简介&#xff1a; 这本书由亚当贝勒马尔&#xff08;Adam Bellemare…

小刚说C语言刷题——第22讲 二维数组

昨天我们讲了一维数组&#xff0c;今天我们来讲二维数组。 1.定义 二维数组是指在数组名后跟两个方括号的数组。 2.语法格式 数据类型 数组名[下标][下标] 例如&#xff1a;int a[5][9];//表示5行9列的数组 3.访问二维数组元素 格式&#xff1a;数组名[行坐标][列坐标]…

Vue 大文件分片上传组件实现解析

Vue 大文件分片上传组件实现解析 一、功能概述 1.1本组件基于 Vue Element UI 实现&#xff0c;主要功能特点&#xff1a; 大文件分片上传&#xff1a;支持 2MB 分片切割上传实时进度显示&#xff1a;可视化展示每个文件上传进度智能格式校验&#xff1a;支持文件类型、大小…

「逻辑推理」AtCoder AT_abc401_d D - Logical Filling

前言 这次的 D 题出得很好&#xff0c;不仅融合了数学逻辑推理的知识&#xff0c;还有很多细节值得反复思考。虽然通过人数远高于 E&#xff0c;但是通过率甚至不到 60%&#xff0c;可见这些细节正是出题人的侧重点。 题目大意 给定一个长度为 N N N 的字符串 S S S&#…

腾讯后台开发 一面

一、手撕 合并升序链表 合并两个排序的链表_牛客题霸_牛客网 顺时针翻转矩阵 顺时针旋转矩阵_牛客题霸_牛客网 二、八股 1、静态变量和实例变量 public class House {public static String buildDate "2024-10-27"; // 静态变量public String color; // 实…

Unity 动画

Apply Root Motion 勾选的话就会使用动画片段自带的位移 Update Mode &#xff08;动画重新计算骨骼位置转向缩放的数值&#xff09;&#xff1a; Normal &#xff1a; 随Update走&#xff0c;每次Update都计算Animate Physics &#xff1a;与 fixed Update() 同步&#xff0…

NDT和ICP构建点云地图 |【点云建图、Ubuntu、ROS】

### 本博客记录学习NDT&#xff0c;ICP构建点云地图的实验过程&#xff0c;参考的以下两篇博客&#xff1a; 无人驾驶汽车系统入门&#xff08;十三&#xff09;——正态分布变换&#xff08;NDT&#xff09;配准与无人车定位_settransformationepsilon-CSDN博客 PCL中点云配…

基于HTML + jQuery + Bootstrap 4实现(Web)地铁票价信息生成系统

地铁票价信息表生成系统 1. 需求分析 1.1 背景 地铁已经成为大多数人出行的首选,北京地铁有多条运营线路, 截至 2019 年 12 月,北京市轨道交通路网运营线路达 23 条、总里程 699.3 公里、车站 405 座。2019 年,北京地铁年乘客量达到 45.3 亿人次,日均客流为 1241.1 万人次…

EtherNet/IP 转 Modbus 协议网关

一、产品概述 1.1 产品用途 SG-EIP-MOD-210 网关可以实现将 Modbus 接口设备连接到 EtherNet/IP 网 络中。用户不需要了解具体的 Modbus 和 EtherNet/IP 协议即可实现将 Modbus 设 备挂载到 EtherNet/IP 接口的 PLC 上&#xff0c;并和 Modbus 设备进行数…

PostgreSQL:逻辑复制与物理复制

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…

腾讯云COS与ZKmall 开源商城的存储集成方案

ZKmall 开源商城与腾讯云对象存储&#xff08;COS&#xff09;的集成&#xff0c;可通过云端资源托管、自动化数据同步、高性能存储架构实现本地存储负载降低与访问效率提升。以下是基于搜索结果的集成路径与核心优化点&#xff1a; 一、存储架构升级&#xff1a;本地与云端协同…

HTML — 浮动

浮动 HTML浮动&#xff08;Float&#xff09;是一种CSS布局技术&#xff0c;通过float: left或float: right使元素脱离常规文档流并向左/右对齐&#xff0c;常用于图文混排或横向排列内容。浮动元素会紧贴父容器或相邻浮动元素的边缘&#xff0c;但脱离文档流后可能导致父容器高…

【软件测试学习day1】软件测试概念

前言 本篇学习&#xff0c;测试相关基础概念、常见的开发模型测和测试模型&#xff0c;搞懂4个问题&#xff1a; 什么是需求什么是 bug什么是测试用例开发模型和测试模型 目录 1. 什么是需求 1.1 为什么要有需求 1.2 测试人员眼里的需求 1.3 如何深入了解需求 2. 测试用例…

Flutter常用组件实践

Flutter常用组件实践 1、MaterialApp 和 Center(组件居中)2、Scaffold3、Container(容器)4、BoxDecoration(装饰器)5、Column(纵向布局)及Icon(图标)6、Column/Row(横向/横向布局)+CloseButton/BackButton/IconButton(简单按钮)7、Expanded和Flexible8、Stack和Po…

刘火良FreeRTOS内核实现与应用学习之7——任务延时列表

在《刘火良FreeRTOS内核实现与应用学习之6——多优先级》的基础上&#xff1a;关键是添加了全局变量&#xff1a;xNextTaskUnblockTime &#xff0c;与延时列表&#xff08;xDelayedTaskList1、xDelayedTaskList2&#xff09;来高效率的实现延时。 以前需要在扫描就绪列表中所…

图像预处理-插值方法

一.插值方法 当我们对图像进行缩放或旋转等操作时&#xff0c;需要在新的像素位置上计算出对应的像素值。 而插值算法的作用就是根据已知的像素值来推测未知位置的像素值。 1.1 最近邻插值 CV2.INTER_NEAREST 其为 warpAffine() 函数的参数 flags 的其一&#xff0c;表示最近…

智能配电保护:公共建筑安全的新 “防火墙”

安科瑞刘鸿鹏 摘要 随着城市建筑体量的不断增长和电气设备的广泛使用&#xff0c;现代建筑大楼的用电安全问题日益突出。传统配电方式面临监测盲区多、响应滞后、火灾隐患难发现等问题。为提升建筑电气系统的安全性和智能化水平&#xff0c;智慧用电系统应运而生。本文结合安…

如何解决DDoS攻击问题 ?—专业解决方案深度分析

本文深入解析DDoS攻击面临的挑战与解决策略&#xff0c;提供了一系列防御技术和实践建议&#xff0c;帮助企业加强其网络安全架构&#xff0c;有效防御DDoS攻击。从攻击的识别、防范措施到应急响应&#xff0c;为网络安全工作者提供了详细的操作指引。 DDoS攻击概览&#xff1a…