今天做項目適合 策劃想要實現一個在現有的怪物追蹤系統上實現怪物擁有慣性功能
以下是解決方案分享:
怪物基類代碼:
?
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分享結束!