using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(Collider))]
public class SteeringEnemyAI : MonoBehaviour
{
[Header("Target")]
[Tooltip("Трансформ игрока")]
public Transform player;
[Header("Steering Settings")]
public float maxSpeed = 3f;
public float maxForce = 15f;
public float arrivalRadius = 1f;
[Header("Boids Settings")]
[Tooltip("Минимальное расстояние до соседей")]
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 static List<SteeringEnemyAI> allEnemies = new List<SteeringEnemyAI>();
private Rigidbody rb;
// Текущий и целевой множители скорости (Frost-эффект)
[HideInInspector] public float frostMultiplier = 1f;
private float targetMultiplier = 1f;
private float smoothVelocity;
// Слой до заморозки и слой во время Frost
private int originalLayer;
private int frostedLayer;
private void Awake()
{
rb = GetComponent<Rigidbody>();
allEnemies.Add(this);
// Запомним, на каком слое был враг изначально,
// и найдём индекс слоя "EnemyFrosted"
originalLayer = gameObject.layer;
frostedLayer = LayerMask.NameToLayer("EnemyFrosted");
if (frostedLayer < 0)
Debug.LogWarning("Слой EnemyFrosted не найден! Создайте его в настройках Unity.");
}
private void OnDestroy()
{
allEnemies.Remove(this);
}
private void FixedUpdate()
{
if (player == null) return;
// Плавно приближаем frostMultiplier к targetMultiplier
frostMultiplier = Mathf.SmoothDamp(
frostMultiplier,
targetMultiplier,
ref smoothVelocity,
0.3f
);
// Если полностью заморожены — не добавляем никаких сил
if (frostMultiplier <= 1e-4f)
return;
// ----- Steering AI как раньше, за вычетом ручного сброса скорости -----
// 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-силы: Separation, Cohesion, Alignment
Vector3 sepForce = Vector3.zero, cohForce = Vector3.zero, 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;
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;
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) Obstacle Avoidance
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) Собираем итоговую силу и применяем
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) Лёгкий перенос – при заморозке силой Frost враг медленнее гасят внешние толчки
rb.drag = Mathf.Lerp(0f, 5f, 1f - frostMultiplier);
}
/// <summary>
/// Устанавливает целевой множитель скорости (0…1) и переключает слой,
/// чтобы замороженный враг больше не сталкивался с другими врагами,
/// но мог сталкиваться с игроком.
/// </summary>
public void SetMultiplier(float m)
{
targetMultiplier = Mathf.Clamp01(m);
smoothVelocity = 0f;
frostMultiplier = targetMultiplier;
// Один раз обнуляем скорость при полной заморозке:
if (frostMultiplier <= 1e-4f)
{
Vector3 v = rb.velocity;
rb.velocity = new Vector3(0f, v.y, 0f);
}
// Переключаем игровой слой объекта и всех его потомков
int layerTo = frostMultiplier < 1f - 1e-4f
? frostedLayer
: originalLayer;
SetLayerRecursively(transform, layerTo);
}
// Рекурсивно задаём слой объекту и всем потомкам
private void SetLayerRecursively(Transform t, int layer)
{
t.gameObject.layer = layer;
foreach (Transform child in t)
SetLayerRecursively(child, layer);
}
}