using System.Collections.Generic; using UnityEngine; [RequireComponent(typeof(Rigidbody))] [RequireComponent(typeof(Collider))] public class SteeringEnemyAI : MonoBehaviour { [Header("Target")] public Transform player; [Header("Steering Settings")] public float maxSpeed = 3f; public float maxForce = 15f; public float arrivalRadius = 1f; [Header("Boids Settings")] public float separationRadius = 1.5f; public float separationStrength = 7f; [Range(0f,1f)] public float cohesionWeight = 0.5f; [Range(0f,1f)] public float alignmentWeight = 0.5f; [Header("Obstacle Avoidance")] public float avoidDistance = 2f; public float avoidStrength = 10f; public LayerMask obstacleMask; [Header("Rotation")] public float angularSpeed = 10f; private Rigidbody rb; private static List allEnemies = new List(); [HideInInspector] public float frostMultiplier = 1f; private float targetMultiplier = 1f; private float smoothVelocity; // Эти поля нужны, чтобы запомнить исходные настройки Rigidbody private bool wasKinematic; private float baseMass, baseDrag; private CollisionDetectionMode baseCdMode; // При морозе мы вручную двигать противника private Vector3 manualVelocity; private void Awake() { rb = GetComponent(); // Запоминаем исходное состояние Rigidbody wasKinematic = rb.isKinematic; baseMass = rb.mass; baseDrag = rb.drag; baseCdMode = rb.collisionDetectionMode; allEnemies.Add(this); } private void OnDestroy() { allEnemies.Remove(this); } public void Init(Transform playerTransform) { player = playerTransform; } private void FixedUpdate() { if (player == null) return; // плавно меняем frostMultiplier → targetMultiplier frostMultiplier = Mathf.SmoothDamp( frostMultiplier, targetMultiplier, ref smoothVelocity, 0.3f ); // Если полная остановка if (frostMultiplier <= 1e-4f) { // полностью глушим движение if (!rb.isKinematic) EnterKinematic(); manualVelocity = Vector3.zero; MoveManually(); return; } // расчёт динамического режима if (rb.isKinematic) ExitKinematic(); // 1) желаемое движение к игроку Vector3 toTarget = player.position - transform.position; float dist = toTarget.magnitude; Vector3 desired = toTarget.normalized * maxSpeed * frostMultiplier; if (dist < arrivalRadius) desired *= dist / arrivalRadius; // 2) boids-силы Vector3 sepForce = Vector3.zero; Vector3 cohForce = Vector3.zero; Vector3 aliForce = Vector3.zero; int count = 0; float sepRad2 = separationRadius * separationRadius; foreach (var other in allEnemies) { if (other == this) continue; Vector3 diff = transform.position - other.transform.position; float d2 = diff.sqrMagnitude; if (d2 < sepRad2 && d2 > 0.001f) { sepForce += diff.normalized / d2; count++; } } if (count > 0) { sepForce = sepForce.normalized * separationStrength * frostMultiplier; // cohesion Vector3 center = Vector3.zero; foreach (var other in allEnemies) { if (other == this) continue; if ((other.transform.position - transform.position).sqrMagnitude < sepRad2) center += other.transform.position; } center /= count; cohForce = (center - transform.position).normalized * separationStrength * cohesionWeight * frostMultiplier; // alignment Vector3 avgVel = Vector3.zero; foreach (var other in allEnemies) { if (other == this) continue; if ((other.transform.position - transform.position).sqrMagnitude < sepRad2) avgVel += other.rb.velocity; } avgVel /= count; aliForce = (avgVel.normalized * maxSpeed * frostMultiplier - rb.velocity) * alignmentWeight * frostMultiplier; } // 3) avoid obstacles Vector3 avoidForce = Vector3.zero; Vector3 forward = rb.velocity.sqrMagnitude > 0.01f ? rb.velocity.normalized : transform.forward; if (Physics.Raycast( transform.position, forward, out RaycastHit hit, avoidDistance, obstacleMask )) { avoidForce = Vector3.Cross(hit.normal, Vector3.up).normalized * avoidStrength * frostMultiplier; } // 4) финальное Steering Vector3 steer = desired + sepForce + cohForce + aliForce + avoidForce - rb.velocity; steer = Vector3.ClampMagnitude(steer, maxForce * frostMultiplier); rb.AddForce(steer, ForceMode.Acceleration); // 5) ограничение скорости Vector3 horiz = new Vector3(rb.velocity.x, 0f, rb.velocity.z); if (horiz.magnitude > maxSpeed * frostMultiplier) { horiz = horiz.normalized * maxSpeed * frostMultiplier; rb.velocity = new Vector3(horiz.x, rb.velocity.y, horiz.z); } // 6) дальнейшее гашение внешних толчков: увеличим drag rb.drag = baseDrag + (1f - frostMultiplier) * 10f; // 7) поворот к игроку Vector3 lookDir = Vector3.ProjectOnPlane(toTarget, Vector3.up); if (lookDir.sqrMagnitude > 0.001f) { Quaternion targetRot = Quaternion.LookRotation(lookDir); transform.rotation = Quaternion.Slerp( transform.rotation, targetRot, angularSpeed * Time.fixedDeltaTime ); } } // Переходим в “кинематический” режим: физика не будет нас двигать private void EnterKinematic() { rb.isKinematic = true; rb.collisionDetectionMode = CollisionDetectionMode.ContinuousSpeculative; } // Восстанавливаем обычную физику private void ExitKinematic() { rb.isKinematic = wasKinematic; rb.collisionDetectionMode = baseCdMode; rb.drag = baseDrag; } // Когда кинематический — двигаем сами private void MoveManually() { // manualVelocity устанавливается в FixedUpdate Vector3 delta = manualVelocity * Time.fixedDeltaTime; rb.MovePosition(rb.position + delta); } /// /// Внешний вызов, чтобы задать новый multiplier скорости. /// public void SetMultiplier(float m) { targetMultiplier = Mathf.Clamp01(m); smoothVelocity = 0f; frostMultiplier = targetMultiplier; // Сразу сбрасываем текущую скорость Vector3 v = rb.velocity; Vector3 horiz = new Vector3(v.x, 0f, v.z) * frostMultiplier; rb.velocity = new Vector3(horiz.x, v.y, horiz.z); // Подготовим manualVelocity на случай kinematic manualVelocity = (player != null ? (player.position - transform.position).normalized : Vector3.zero) * maxSpeed * frostMultiplier; } }