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<SteeringEnemyAI> allEnemies = new List<SteeringEnemyAI>();
[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>();
// Запоминаем исходное состояние 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);
}
/// <summary>
/// Внешний вызов, чтобы задать новый multiplier скорости.
/// </summary>
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;
}
}