Реализовать систему патрулирования для врага
Условие
Реализуйте компонент патрулирования для ИИ врага.
Требования
- Враг перемещается между заданными точками патрулирования
- В каждой точке ненадолго останавливается
- Может двигаться циклично или возвращаться по тому же пути
- Плавный поворот в направлении движения
- Анимация ходьбы
- Визуализация пути в редакторе через Gizmos
Бонус
- Случайный выбор следующей точки
- Разная скорость на разных участках
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение: Система патрулирования врага AI
Эта задача требует реализации интеллектуальной системы передвижения врага между точками с поддержкой различных режимов движения, анимаций и визуализации в редакторе. Расскажу о полной архитектуре.
Архитектура системы
Основные компоненты:
- PatrolPath — хранит точки патрулирования
- PatrolController — управляет движением между точками
- AnimationController — синхронизирует анимации
- PathVisualizer — отображение пути в редакторе
PatrolPath - Определение маршрута
[System.Serializable]
public class PatrolWaypoint
{
public Vector3 Position { get; set; }
public float StopDuration { get; set; } = 1f;
public float Speed { get; set; } = 5f;
public float WaitRadius { get; set; } = 0.5f;
}
public class PatrolPath : MonoBehaviour
{
[SerializeField] private List<PatrolWaypoint> waypoints = new List<PatrolWaypoint>();
[SerializeField] private bool closeLoop = true;
[SerializeField] private bool randomOrder = false;
[SerializeField] private Color pathColor = Color.green;
[SerializeField] private float waypointSize = 0.3f;
public List<PatrolWaypoint> Waypoints => waypoints;
public bool CloseLoop => closeLoop;
public bool RandomOrder => randomOrder;
public void AddWaypoint(Vector3 position)
{
waypoints.Add(new PatrolWaypoint { Position = position });
}
public void RemoveWaypoint(int index)
{
if (index >= 0 && index < waypoints.Count)
{
waypoints.RemoveAt(index);
}
}
public PatrolWaypoint GetWaypoint(int index)
{
if (waypoints.Count == 0)
return null;
return waypoints[Mathf.Clamp(index, 0, waypoints.Count - 1)];
}
public int GetWaypointCount() => waypoints.Count;
// Визуализация в редакторе
private void OnDrawGizmos()
{
if (waypoints.Count == 0)
return;
Gizmos.color = pathColor;
// Рисуем линии между точками
for (int i = 0; i < waypoints.Count - 1; i++)
{
Gizmos.DrawLine(waypoints[i].Position, waypoints[i + 1].Position);
}
// Если замкнутый контур
if (closeLoop && waypoints.Count > 1)
{
Gizmos.DrawLine(waypoints[waypoints.Count - 1].Position, waypoints[0].Position);
}
// Рисуем сферы в точках
foreach (var waypoint in waypoints)
{
Gizmos.DrawSphere(waypoint.Position, waypointSize);
}
}
private void OnDrawGizmosSelected()
{
if (waypoints.Count == 0)
return;
Gizmos.color = Color.yellow;
foreach (var waypoint in waypoints)
{
Gizmos.DrawWireSphere(waypoint.Position, waypointSize * 1.5f);
}
}
}
PatrolController - Основная логика
public enum PatrolBehavior
{
Loop, // Циклический обход
PingPong, // Туда-сюда
Random, // Случайный выбор
Sequential // Последовательный
}
public class PatrolController : MonoBehaviour
{
[SerializeField] private PatrolPath patrolPath;
[SerializeField] private PatrolBehavior behavior = PatrolBehavior.Loop;
[SerializeField] private float rotationSpeed = 5f;
[SerializeField] private float stoppingDistance = 0.1f;
private Animator animator;
private CharacterController characterController;
private int currentWaypointIndex = 0;
private bool movingForward = true;
private float stopTimer = 0f;
private bool isStopped = false;
private Vector3 currentDirection = Vector3.zero;
private HashSet<int> visitedWaypoints = new HashSet<int>();
private void Start()
{
animator = GetComponent<Animator>();
characterController = GetComponent<CharacterController>();
if (patrolPath == null)
{
Debug.LogError("PatrolPath not assigned!");
enabled = false;
return;
}
if (patrolPath.GetWaypointCount() == 0)
{
Debug.LogError("PatrolPath has no waypoints!");
enabled = false;
return;
}
}
private void Update()
{
if (patrolPath == null || patrolPath.GetWaypointCount() == 0)
return;
PatrolWaypoint currentWaypoint = patrolPath.GetWaypoint(currentWaypointIndex);
Vector3 waypointPosition = currentWaypoint.Position;
float distanceToWaypoint = Vector3.Distance(transform.position, waypointPosition);
// Если достигли точку
if (distanceToWaypoint <= currentWaypoint.WaitRadius && !isStopped)
{
StartStop(currentWaypoint.StopDuration);
}
// Если ждём
if (isStopped)
{
stopTimer -= Time.deltaTime;
if (stopTimer <= 0)
{
isStopped = false;
MoveToNextWaypoint();
}
animator.SetFloat("Speed", 0);
return;
}
// Движение к точке
MoveTowards(waypointPosition, currentWaypoint.Speed);
}
private void MoveTowards(Vector3 targetPosition, float speed)
{
Vector3 direction = (targetPosition - transform.position).normalized;
currentDirection = direction;
// Плавный поворот
if (direction != Vector3.zero)
{
Quaternion targetRotation = Quaternion.LookRotation(direction);
transform.rotation = Quaternion.Slerp(
transform.rotation,
targetRotation,
rotationSpeed * Time.deltaTime
);
}
// Движение
Vector3 movement = direction * speed * Time.deltaTime;
characterController.Move(movement);
// Анимация
animator.SetFloat("Speed", speed);
}
private void StartStop(float duration)
{
isStopped = true;
stopTimer = duration;
animator.SetFloat("Speed", 0);
}
private void MoveToNextWaypoint()
{
switch (behavior)
{
case PatrolBehavior.Loop:
currentWaypointIndex = (currentWaypointIndex + 1) % patrolPath.GetWaypointCount();
break;
case PatrolBehavior.PingPong:
if (movingForward)
{
currentWaypointIndex++;
if (currentWaypointIndex >= patrolPath.GetWaypointCount() - 1)
{
movingForward = false;
currentWaypointIndex = patrolPath.GetWaypointCount() - 1;
}
}
else
{
currentWaypointIndex--;
if (currentWaypointIndex <= 0)
{
movingForward = true;
currentWaypointIndex = 0;
}
}
break;
case PatrolBehavior.Random:
int randomIndex = Random.Range(0, patrolPath.GetWaypointCount());
currentWaypointIndex = randomIndex;
break;
case PatrolBehavior.Sequential:
currentWaypointIndex++;
if (currentWaypointIndex >= patrolPath.GetWaypointCount())
{
currentWaypointIndex = 0; // Начинаем сначала
}
break;
}
}
public void SetPatrolPath(PatrolPath newPath)
{
patrolPath = newPath;
currentWaypointIndex = 0;
isStopped = false;
visitedWaypoints.Clear();
}
public Vector3 GetCurrentWaypointPosition()
{
return patrolPath.GetWaypoint(currentWaypointIndex).Position;
}
public int GetCurrentWaypointIndex() => currentWaypointIndex;
}
AnimationController - Синхронизация анимаций
public class EnemyAnimationController : MonoBehaviour
{
private Animator animator;
private PatrolController patrolController;
private int speedHash = Animator.StringToHash("Speed");
private int directionHash = Animator.StringToHash("Direction");
private int stopHash = Animator.StringToHash("Stop");
private void Start()
{
animator = GetComponent<Animator>();
patrolController = GetComponent<PatrolController>();
}
public void SetMovementSpeed(float speed)
{
animator.SetFloat(speedHash, speed);
}
public void SetDirection(Vector3 direction)
{
if (direction != Vector3.zero)
{
animator.SetFloat(directionHash, direction.x);
}
}
public void PlayStopAnimation()
{
animator.SetTrigger(stopHash);
}
}
Использование в сцене
public class EnemySetup : MonoBehaviour
{
private void Start()
{
// Создаём маршрут
PatrolPath path = gameObject.AddComponent<PatrolPath>();
path.AddWaypoint(new Vector3(0, 0, 0));
path.AddWaypoint(new Vector3(5, 0, 0));
path.AddWaypoint(new Vector3(5, 0, 5));
path.AddWaypoint(new Vector3(0, 0, 5));
// Добавляем патруль контроллер
PatrolController patrol = gameObject.AddComponent<PatrolController>();
patrol.SetPatrolPath(path);
}
}
Ключевые особенности
1. Гибкие режимы движения — Loop, PingPong, Random, Sequential
2. Плавный поворот — использование Quaternion.Slerp для реалистичного вращения
3. Гибкие остановки — каждая точка имеет свою длительность паузы
4. Переменная скорость — разная скорость на разных участках маршрута
5. Визуализация пути — линии и сферы в редакторе через Gizmos
6. Синхронизация анимаций — параметры Speed и Direction для Animator
7. Масштабируемость — легко добавлять новые режимы поведения
Расширение возможностей
- Обнаружение игрока — переключение на режим преследования
- Динамическое изменение маршрута — добавление точек во время игры
- Звуковые эффекты — шаги, дыхание при остановке
- Состояния врага — усталость, ранения влияют на скорость
- Слежение за игроком — если врагом обнаружен, переход в режим преследования
Эта система обеспечивает реалистичное движение врагов с минимальными затратами на производительность.