Продолжаем разработку 3D-игры с использованием Модели-Представления-Контроллера (MVC). В этом видео уроке мы добавим в проект интеллектуальных противников — врагов, которые патрулируют территорию, обходят препятствия и взаимодействуют с игроком.
🧩 Что такое архитектура MVC в Unity?
MVC — это шаблон проектирования, который разделяет логику приложения на три компонента:
- Модель (Model) — хранит данные и состояние (например, позиция, здоровье, цель движения).
- Представление (View) — ответственно за визуализацию и отображение элементов (рендер, анимация, физика.
- Контроллер (Controller) — управляет логикой, принимает решения и передаёт команды Модели и Представлению.
👾 Создание и настройка противников (MVC)
1. EnemyModel.cs — Модель противника
Этот сценарий отвечает за состояние врага (позиция, таймеры, здоровье).
csharp
// EnemyModel.cs
using UnityEngine;
public class EnemyModel
{
public Vector3 CurrentDirection { get; set; }
public float DirectionTimer { get; set; }
public bool IsDead { get; set; } = false;
public float MaxTimeInDirection { get; private set; }
public EnemyModel(float initialMaxTime)
{
MaxTimeInDirection = initialMaxTime;
CurrentDirection = Vector3.forward;
}
public void UpdateTimer(float deltaTime)
{
if (!IsDead)
{
DirectionTimer -= deltaTime;
}
}
public bool IsTimeExpired()
{
return DirectionTimer <= 0f;
}
public void SetNewDirection(Vector3 newDir)
{
CurrentDirection = newDir.normalized;
DirectionTimer = MaxTimeInDirection;
}
}
2. EnemyView.cs — Представление противника
Здесь находится визуализация и физика: движение, вращение, обход.
csharp
// EnemyView.cs
using UnityEngine;
public class EnemyView : MonoBehaviour
{
[Header("Настройки Движения (View)")]
public float moveSpeed = 3f;
public float rotationSmoothFactor = 0.15f;
public float obstacleCheckDistance = 1.5f;
public LayerMask obstacleLayer;
[Header("Эффекты Смерти")]
public GameObject deathEffectPrefab;
private Rigidbody rb;
void Awake()
{
rb = GetComponent<Rigidbody>();
}
// --- Методы, вызываемые Контроллером ---
public void Move(Vector3 direction, float deltaTime)
{
if (direction.sqrMagnitude < 0.01f) return;
Vector3 movement = direction * moveSpeed * deltaTime;
if (rb != null)
{
rb.MovePosition(rb.position + movement);
}
else
{
transform.position += movement;
}
}
public void RotateToFace(Vector3 direction)
{
Quaternion targetRotation = Quaternion.LookRotation(new Vector3(direction.x, 0, direction.z));
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotationSmoothFactor);
}
public bool CheckObstacles(Vector3 direction)
{
RaycastHit hit;
if (Physics.Raycast(transform.position, direction, out hit, obstacleCheckDistance, obstacleLayer))
{
Debug.DrawRay(transform.position, direction * obstacleCheckDistance, Color.red);
if (((1 << hit.collider.gameObject.layer) & obstacleLayer) != 0)
{
return true;
}
}
else
{
Debug.DrawRay(transform.position, direction * obstacleCheckDistance, Color.green);
}
return false;
}
public void ExecuteDeath()
{
if (deathEffectPrefab != null)
{
Instantiate(deathEffectPrefab, transform.position, Quaternion.identity);
}
Destroy(gameObject, 0.5f);
}
}
3. EnemyController.cs — Контроллер противника
Именно здесь происходит управленческое взаимодействие между Моделью и Представлением.
csharp
// EnemyController.cs
using UnityEngine;
[RequireComponent(typeof(EnemyView))]
public class EnemyController : MonoBehaviour
{
private EnemyModel model;
private EnemyView view;
// Параметры, которые должны быть в Inspector (перенесены из старого скрипта)
[Header("Настройки Патрулирования (Контроллер)")]
public float maxTimeInDirection = 5f;
void Awake()
{
view = GetComponent<EnemyView>();
// Инициализируем модель с параметром времени, полученным из Inspector
model = new EnemyModel(maxTimeInDirection);
}
void Start()
{
ChooseNewState();
}
void Update()
{
if (model.IsDead) return;
// 1. Обновление состояния Модели
model.UpdateTimer(Time.deltaTime);
// 2. Принятие решений (Логика ИИ)
if (model.IsTimeExpired() || view.CheckObstacles(model.CurrentDirection))
{
ChooseNewState();
}
// 3. Применение движения (Controller -> View)
view.Move(model.CurrentDirection, Time.deltaTime);
}
void ChooseNewState()
{
// 1. Выбираем новое направление (Модель)
Vector3 newDirection = GetRandomDirection();
model.SetNewDirection(newDirection);
// 2. Поворачиваемся лицом к новому направлению (View)
view.RotateToFace(model.CurrentDirection);
}
Vector3 GetRandomDirection()
{
float randomX = Random.Range(-1f, 1f);
float randomZ = Random.Range(-1f, 1f);
Vector3 newDir = new Vector3(randomX, 0f, randomZ).normalized;
if (newDir.sqrMagnitude < 0.01f)
{
return Vector3.forward;
}
return newDir;
}
// Обработка внешних событий (например, взрыва)
private void OnTriggerEnter(Collider other)
{
if (!model.IsDead && other.CompareTag("Explosion"))
{
ApplyDamage();
}
}
public void ApplyDamage()
{
if (!model.IsDead)
{
model.IsDead = true;
// View выполняет визуальное уничтожение
view.ExecuteDeath();
}
}
}
⚙️ Настройка в Unity
📌 Действия в редакторе
- Создайте пустой объект на сцене и назовите его Enemy.
- Добавьте компонент Rigidbody к объекту врага:Заморозьте поворот по всем осям (X, Y, Z) в Constraints.
- Назначьте слой препятствий для объектов, которые враг должен обходить (например, “Wall”).
- Настройте тег Enemy для создания объекта взрыва.
- Подключите эффект смерти (например, Particle System).
Готовы к следующим урокам? Ещё больше уроков о создании игр на нашем канале! 🎮
Подпишитесь, чтобы не пропустить новые видео о MVC и логике противников!
#Unity #GameDev #MVC #Патрулирование #3DИгра