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.pswp.cn/diannao/78805.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/78805.shtml
英文地址,請注明出處:http://en.pswp.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…