В большинстве 2‑D‑игр встречаются ситуации, когда игроку нужно, чтобы враг появлялся в момент, когда он входит в видимую область, например, за порогом в комнате. В этой статье мы разберём пошагово, как реализовать такой эффект в Unity, используя минимальные ресурсы – идеальный вариант для мобильных проектов на Android.
Планируем: что понадобится
- Unity 2021+ (или более ранние версии, но будем использовать API 2021)
- Sprite‑файл врага (или готовый аниматор)
- Порог как
GameObjectсBoxCollider2Dв режимеTrigger - Скрипт
EnemySpawner(появление врага) - Пул объектов для экономии памяти (опционально)
Шаг 1. Создаём порог
- В иерархии проекта создаём пустой объект
Threshold. - Добавляем компонент
BoxCollider2Dи в инспекторе ставим флажок Is Trigger. - Подгоняем размер коллайдера так, чтобы он покрывал точку, где игрок «переходит» порог.
- При желании можно добавить визуальный спрайт – это поможет отладить позицию.
Шаг 2. Подготовка врага
Враги могут быть реализованы как префаб с компонентами:
Rigidbody2D(если нужен физический движок)Collider2D(обычный коллайдер)- Скрипт
EnemyAI– отвечает за движение и поведение.
Если вы хотите экономить память, создайте пул врагов (Object Pooling). Это позволяет переиспользовать уже созданные объекты вместо создания новых при каждом появлении.
Пример простого пула
using System.Collections.Generic;
using UnityEngine;
public class EnemyPool : MonoBehaviour
{ public static EnemyPool Instance;
[SerializeField] private GameObject enemyPrefab;
[SerializeField] private int poolSize = 10;
private Queue pool = new Queue;
private void Awake
{ Instance = this;
for (int i = 0; i < poolSize; i++)
{
var enemy = Instantiate(enemyPrefab);
enemy.SetActive(false);
pool.Enqueue(enemy);
}
}
public GameObject GetEnemy
{
if (pool.Count == 0)
{
var enemy = Instantiate(enemyPrefab);
enemy.SetActive(false);
pool.Enqueue(enemy);
}
var e = pool.Dequeue;
e.SetActive(true);
return e;
}
public void ReturnEnemy(GameObject enemy)
{
enemy.SetActive(false);
pool.Enqueue(enemy);
}
}
Шаг 3. Скрипт спавна врага
Создаём скрипт EnemySpawner и привязываем его к объекту Threshold. Скрипт реагирует на событие OnTriggerEnter2D и запрашивает врага из пула.
using UnityEngine;
public class EnemySpawner : MonoBehaviour
{
[Header("Параметры спавна")]
[Tooltip("Скорость появления врага после входа игрока")]
[SerializeField] private float spawnDelay = 0.5f;
[Tooltip("Класс игрока, который может спавнить врага")]
[SerializeField] private string playerTag = "Player";
private void OnTriggerEnter2D(Collider2D collision)
{
if (!collision.CompareTag(playerTag)) return;
// Отложенный вызов спавна
Invoke(nameof(SpawnEnemy), spawnDelay);
}
private void SpawnEnemy
{
// Получаем врага из пула
GameObject enemy = EnemyPool.Instance.GetEnemy;
// Устанавливаем позицию врага рядом с порогом
enemy.transform.position = transform.position + new Vector3(1.5f, 0, 0); // 1.5f = расстояние от порога
// Если у врага есть скрипт AI, можно инициализировать его
var ai = enemy.GetComponent<EnemyAI>;
if (ai != null)
{
ai.Initialize(this.transform); // передаём цель (порог)
}
}
}
Пояснения к коду
- Поток
Invokeдобавляет небольшую задержку, чтобы враг не появился мгновенно (это делает игру более реалистичной). - Проверка
CompareTagгарантирует, что спавн произойдёт только от игрока. - Вызов
EnemyPool.Instance.GetEnemyберёт уже существующий объект, если он есть, иначе создаёт новый.
Шаг 4. Настраиваем AI врага
Простой AI может просто следовать за игроком, пока он находится в пределах видимости. Ниже пример скрипта EnemyAI:
using UnityEngine;
public class EnemyAI : MonoBehaviour
{
[Header("Настройки AI")]
[SerializeField] private float moveSpeed = 2f;
[SerializeField] private float visionRange = 5f;
private Transform target; // может быть игроком или порогом
public void Initialize(Transform target)
{
this.target = target;
}
private void Update
{
if (target == null) return;
Vector3 direction = (target.position ⏤ transform.position).normalized;
float distance = Vector3.Distance(target.position, transform.position);
// Проходим к цели, если она в пределах видимости
if (distance < visionRange)
{
transform.position += direction * moveSpeed * Time.deltaTime;
}
}
private void OnBecameInvisible
{
// Когда враг уходит из камеры, возвращаем его в пул
EnemyPool.Instance.ReturnEnemy(this.gameObject);
}
}
Что делать, если игрок скрывается за стеной?
Если в игре есть стены, можно использовать Physics2D.Raycast для проверки прямой видимости. Если луч прерывается стеной, враг будет искать обходной путь (можно реализовать простой A* алгоритм на плоскости, но для Android лучше воспользоваться готовой библиотекой, например, PathfindingPlugin).
Оптимизация для Android
- Используйте Object Pooling – минимум новых объектов.
- Ограничьте количество активных врагов: если игрок заходит в несколько порогов подряд, можно держать переменную
maxActiveEnemies. - Переходим на
FixedUpdateдля физики, а неUpdate, чтобы избежать лишних вызовов. - Убедитесь, что спрайты используют
TilemapилиSprite Atlasдля уменьшения draw calls.
Полный пример сцены
- Создайте объект
GameControllerи добавьте к нему скриптEnemyPool. - В сцене разместите несколько объектов
Threshold(по порогу в комнатах). - Привяжите к каждому порогу скрипт
EnemySpawner. - Настройте тег
Playerу вашего персонажа. - Запустите игру – при входе в пороги враги появятся!
Мы показали, как реализовать появление врага в момент входа игрока в заданную область (порог) с минимальными затратами ресурсов. Главное – использовать триггерный коллайдер, пул объектов и простую логику AI. Если понадобится более сложный поиск пути, можно подключить A* библиотеку, но для большинства мобильных проектов базовый подход уже будет достаточным.
Удачного кодирования и приятной игры!