Добро пожаловать в пошаговый Unity 2D tutorial для начинающих! В этом уроке мы создадим простую игру-защиту города — City Defender, где башенки отражают падающие метеориты.
Здесь вы научитесь:Всё будет сделано максимально просто и понятно — шаг за шагом, без сложных систем и лишнего кода. Вам понадобится только Unity 6.0 LTS или любая версия с поддержкой 2D. Даже если вы впервые открываете Unity — этот урок подойдёт идеально.
Запусти Unity Hub и создай новый 2D-проект, выбрав шаблон 2D Core (Built-In Render Pipeline) — это простая и чистая основа для твоей первой 2D-игры-защиты города.
Для этого открой Unity Hub → вкладка Projects → New Project. Найди шаблон 2D Core (Built-In Render Pipeline) (он уже включает базовую 2D-графику и физику). В зависимости от версии Unity название может немного отличаться — если этого шаблона нет, смело выбирай 2D (URP/SRP) или обычный 2D.
В поле имени проекта введи CityDefender или любое другое
название, затем выбери удобную папку для сохранения. Нажми
Create project и немного подожди, пока Unity всё
подготовит.
💡 Совет: используй короткое имя проекта (например
CityDefender) — это поможет избежать ошибок в путях и
сделать структуру проекта аккуратной.
Сначала скачай изображение фона — силуэт города, который будет служить фоном для нашей сцены.
Импортируй скачанную картинку в Unity — можно через Import New Asset... (правый клик в окне Assets) или просто перетащи файл прямо в папку проекта.
Затем добавь изображение города в сцену: перетащи его из
Assets в окно Scene. Настрой размер
— например, установи Scale X = 0.5 и
Scale Y = 0.5, чтобы фон не был слишком большим.
Переименуй объект в City, чтобы потом легко его находить
в Hierarchy.
В игре будет два типа метеоров: маленькие и быстрые — с небольшим уроном, и большие и медленные — с мощным ударом по городу.
Скачай изображения метеоров ниже:
Импортируй скачанные изображения в Unity — для этого кликни правой кнопкой в окне Assets и выбери Import New Asset..., либо просто перетащи файлы прямо в проект.
Добавь изображение маленького метеора в сцену и назови объект
Meteor-Small. Настрой размер — например,
Scale X = 0.5 и Scale Y = 0.5.
Создай тег Meteor и назначь его объекту маленького метеора.
Установи Order in Layer так, чтобы метеор рисовался
перед фоном города (например, если у City стоит
0, то метеору задай 5). Так он не «пропадёт» за фоном.
Добавь компонент Circle Collider 2D (тело для регистрации попаданий) и компонент Rigidbody 2D (чтобы объект участвовал в физике).
В компоненте Rigidbody 2D у метеора установи Gravity Scale = 0 (будем двигать его вручную скриптом) и Interpolate = Interpolate для более плавного движения.
Создадим небольшой скрипт для хранения параметров метеора и его
автоудаления (чтобы он не оставался в игре бесконечно, например, если
улетит за экран). Добавь новый MonoBehaviour-скрипт и назови
его MeteorParam — имя файла и класса должны совпадать.
using UnityEngine;
public class MeteorParam : MonoBehaviour
{
public float speed;
public int hp;
public int damage;
// Метеор самоуничтожится через 10 секунд,
// чтобы не висеть в сцене бесконечно.
void Start()
{
Destroy(gameObject, 10f);
}
}
Прикрепи скрипт MeteorParam к объекту метеора как
компонент. В Inspector установи значения:
speed = 5, hp = 1, damage = 1.
Затем создай префаб: перетащи объект метеора из Hierarchy в папку Assets.
После того как префаб создан, можешь удалить маленький метеор из сцены — в дальнейшем мы будем создавать экземпляры из префаба.
Добавь изображение большого метеора в сцену и назови объект
Meteor-Big. Настрой размер — например,
Scale X = 0.5 и Scale Y = 0.5.
Назначь тег Meteor объекту большого метеора.
Установи Order in Layer = 5, чтобы
метеор рисовался перед городом.
Добавь компонент Circle Collider 2D и Rigidbody 2D.
В компоненте Rigidbody 2D установи Gravity Scale = 0 и Interpolate = Interpolate для плавного движения.
Прикрепи скрипт MeteorParam к объекту метеора как
компонент. В Inspector задай параметры:
speed = 2, hp = 10,
damage = 10.
Создай префаб: перетащи объект метеора из Hierarchy в папку Assets. После этого можешь удалить большой метеор из сцены — в дальнейшем он будет создаваться из префаба.
Создай в сцене пустой объект (Create Empty), назови его
MeteorSpawner и помести над центром экрана (чуть выше
видимой области). Внутри него создай ещё два пустых объекта и
расположи их соответственно слева и справа: назови
leftBound и rightBound. Убедись, что у всех
трёх объектов координата Z = 0.
Теперь напишем скрипт, который будет: выбирать случайную позицию по оси X между границами, спавнить маленькие и большие метеоры с разной периодичностью, и задавать им направление полёта к городу (для больших — строго в город, для маленьких — в случайную точку над линией города).
Создай новый скрипт MonoBehaviour и назови его
MeteorSpawner:
using UnityEngine;
public class MeteorSpawner : MonoBehaviour
{
// границы спавна и объект города для наведения
public Transform leftBound, rightBound, city;
// префабы метеоров
public GameObject smallMeteorPrefab, bigMeteorPrefab;
// периодичность и стартовые задержки
public float smallRepeat = 1f, bigRepeat = 5f;
public float startDelaySmall = 1f, startDelayBig = 5f;
void Start()
{
// Unity будет периодически вызывать методы спавна
InvokeRepeating(nameof(SpawnSmall), startDelaySmall, smallRepeat);
InvokeRepeating(nameof(SpawnBig), startDelayBig, bigRepeat);
}
// спавн маленького метеора
void SpawnSmall()
{
// случайный X между левым и правым ограничителем
float xPos = Random.Range(leftBound.position.x, rightBound.position.x);
Vector3 pos = new Vector3(xPos, transform.position.y, 0f);
// создать метеор
var go = Instantiate(smallMeteorPrefab, pos, Quaternion.identity);
// цель: случайный X по ширине, Y — уровень города
float xTarget = Random.Range(leftBound.position.x, rightBound.position.x);
Vector3 target = new Vector3(xTarget, city.position.y, 0f);
// нормализуем вектор направления, чтобы длина = 1 (скорость задаём отдельно)
Vector3 dir = (target - pos).normalized;
// ссылки на компоненты и задание скорости
var mp = go.GetComponent<MeteorParam>();
var rb = go.GetComponent<Rigidbody2D>();
if (rb != null && mp != null)
rb.linearVelocity = dir * mp.speed;
}
// спавн большого метеора
void SpawnBig()
{
float xPos = Random.Range(leftBound.position.x, rightBound.position.x);
Vector3 pos = new Vector3(xPos, transform.position.y, 0f);
var go = Instantiate(bigMeteorPrefab, pos, Quaternion.identity);
// большие целятся в центр города
Vector3 target = city.position;
Vector3 dir = (target - pos).normalized;
var mp = go.GetComponent<MeteorParam>();
var rb = go.GetComponent<Rigidbody2D>();
if (rb != null && mp != null)
rb.linearVelocity = dir * mp.speed;
}
}
Прикрепи скрипт MeteorSpawner к объекту
MeteorSpawner. В Inspector заполни поля:
перетащи leftBound, rightBound и объект
City в соответствующие ссылки, а также назначь префабы
smallMeteorPrefab и bigMeteorPrefab. Для
начала оставь значения по умолчанию: smallRepeat = 1,
bigRepeat = 5, startDelaySmall = 1,
startDelayBig = 5.
💡 Совет: Если метеоры «рвутся» или дрожат на экране,
убедись, что у их Rigidbody2D стоит
Interpolate = Interpolate и
Gravity Scale = 0 (мы двигаем их вручную через
linearVelocity).
Запусти сцену — метеоры должны падать хаотично в сторону города: маленькие — чаще и быстрее, большие — реже и медленнее.
MeteorSpawner с дочерними границами
leftBound и rightBound, а также
заполненными полями префабов и ссылками в инспекторе.
Пока метеоры просто пролетают сквозь город. Теперь добавим регистрацию попаданий и уменьшение здоровья. Для этого используем обработку столкновений через Trigger.
Выбери в сцене объект City и добавь к нему компонент Box Collider 2D. Это не совсем точно повторяет контур города, но нам сейчас важнее простая и наглядная схема, чем абсолютная точность.
Подстрой размеры коллайдера, чтобы он перекрывал нижнюю часть домов, и включи галочку Is Trigger.
Теперь создадим скрипт, который будет следить за здоровьем города и
перезапускать сцену, если город разрушен. Создай новый
MonoBehaviour-скрипт и назови его CityDefense.
using UnityEngine;
// нужно для перезагрузки сцены
using UnityEngine.SceneManagement;
public class CityDefense : MonoBehaviour
{
// здоровье города
public int hp = 30;
// вызывается, когда другой 2D-коллайдер входит в триггер
void OnTriggerEnter2D(Collider2D other)
{
// если объект не метеор — выходим
if (!other.CompareTag("Meteor")) return;
// получаем компонент с параметрами метеора
MeteorParam mp = other.GetComponent<MeteorParam>();
// если параметры есть — наносим урон
if (mp != null)
{
int dmg = mp.damage;
hp -= dmg;
// уничтожаем метеор после столкновения
Destroy(other.gameObject);
}
// если здоровье закончилось — перезапускаем сцену
if (hp <= 0)
{
hp = 0;
string currentScene = SceneManager.GetActiveScene().name;
SceneManager.LoadScene(currentScene);
}
}
}
Прикрепи скрипт CityDefense к объекту
City. Теперь метеоры будут наносить урон при
попадании, а при обнулении здоровья сцена перезапустится.
City с добавленным
Box Collider 2D и скриптом
CityDefense.
Метеоры пока просто падают и разрушают город — пора построить защитные башни! Начнём с лазерной башни. Она будет стрелять быстро и точно по одиночным целям, нанося небольшой урон. Это ваша первая линия обороны, предназначенная для быстрого уничтожения слабых и быстрых метеоров.
Скачай картинку лазерной башни:
Импортируй изображение башни в Unity. Затем создай объект лазерной
башни в сцене — просто перетащи её спрайт из папки
Assets в окно Scene. Назови башню,
например, Gemeni Analyzer. Отмасштабируй её (например,
X = 0.5, Y = 0.5), а в поле
Order in Layer поставь 10, чтобы башня
отображалась поверх города.
Для эффекта стрельбы будем использовать компонент
Line Renderer. Добавь его к объекту башни и настрой
цвет линии по желанию. В поле Material выбери
Sprites-Default.
Теперь добавим точку, из которой будет выходить лазер. Создай дочерний
объект (правый клик на башне → Create Empty) и назови его
LaserAnchor. Перемести его на линзу башни и убедись, что
его координата Z = 0.
Создай новый MonoBehaviour-скрипт с именем
LaserTower
и прикрепи его к башне. Вот готовый код:
using System.Collections;
using UnityEngine;
// Требуем, чтобы на объекте обязательно был LineRenderer
[RequireComponent(typeof(LineRenderer))]
public class LaserTower : MonoBehaviour
{
public Transform laserAnchor; // Точка, откуда идёт луч
public int damage = 1; // Урон за выстрел
public float shootingDelay = 1f; // Задержка между выстрелами
public float maxTargetDist = 7f; // Радиус атаки (в юнитах)
public AudioClip laserSound; //звуковой файл лазера
public float volume = 0.7f; // громкость
private LineRenderer lr;
void Awake()
{
lr = GetComponent<LineRenderer>();
// Если точка выстрела не указана — используем сам объект башни
if (laserAnchor == null) laserAnchor = transform;
// Базовые настройки линии, чтобы луч был видим
lr.useWorldSpace = true;
lr.positionCount = 0;
lr.startWidth = lr.endWidth = 0.06f;
lr.sortingOrder = 5;
}
void Start()
{
// Периодический запуск поиска целей и стрельбы
InvokeRepeating(nameof(TargetSearchAndShoot), 1f, shootingDelay);
}
void TargetSearchAndShoot()
{
// 1) Находим все метеоры по тегу "Meteor"
GameObject[] meteors = GameObject.FindGameObjectsWithTag("Meteor");
if (meteors.Length == 0) return;
// 2) Выбираем ближайший в пределах радиуса
GameObject best = null;
float bestSqr = maxTargetDist * maxTargetDist;
Vector3 myPos = transform.position;
foreach (GameObject m in meteors)
{
if (m == null) continue;
float sqr = (m.transform.position - myPos).sqrMagnitude;
if (sqr <= bestSqr)
{
best = m;
bestSqr = sqr; // нашли ближе — обновляем расстояние
}
}
if (best == null) return;
// 3) Визуальный выстрел — короткая вспышка линии
StartCoroutine(FlashLine(laserAnchor.position, best.transform.position));
// проигрываем звук на позиции камеры
if (laserSound != null)
AudioSource.PlayClipAtPoint(laserSound, Camera.main.transform.position, volume);
// 4) Уменьшаем здоровье метеора через компонент MeteorParam
var mp = best.GetComponent<MeteorParam>();
if (mp != null)
{
mp.hp -= damage;
if (mp.hp <= 0)
Destroy(best);
}
}
// Короткое включение и отключение лазерного луча
IEnumerator FlashLine(Vector3 start, Vector3 end)
{
lr.positionCount = 2;
lr.SetPosition(0, start);
lr.SetPosition(1, end);
yield return new WaitForSeconds(0.05f);
lr.positionCount = 0;
}
}
В инспекторе укажи объект LaserAnchor в поле
laserAnchor скрипта.
Также можно добавить звук лазера, чтобы сделать
выстрел более живым. Скачай файл
🎵 LaserBeep.
Импортируй его в Unity (через Assets → Import New Asset...) и
укажи этот звук в поле скрипта laserSound.
Создай префаб башни, перетащив объект из сцены в папку Assets. Теперь можно протестировать работу: запусти сцену — башня будет автоматически искать ближайшие метеоры и стрелять в них. После проверки можешь удалить башню из сцены, ведь у тебя уже есть префаб.
У нас уже есть башня против маленьких метеоров. Теперь сделаем башню с большим уроном против крупных целей. Это будет ракетница: стреляет редко, но сильно. Для начала создадим саму ракету — она будет получать цель и с самонаведением лететь к ней.
Скачай картинку ракеты:
Импортируй изображение ракеты в Unity. Создай новый объект, перетащив
спрайт ракеты из
Assets в окно Scene. Назови объект
Missile. Отмасштабируй примерно до X = 0.2,
Y = 0.2 и выставь Order in Layer на
5.
Добавь компонент Box Collider 2D и включи опцию Is Trigger. Затем добавь Rigidbody 2D и установи:
0 (ракета не падает);
Continuous (лучше для быстрых объектов);
Interpolate (более
плавное движение).
Для управления ракетой создадим простой скрипт. Ракета летит вперёд относительно себя и плавно поворачивается в сторону цели. Цель ракета будет получать от ракетной башни в момент запуска.
Создай новый MonoBehaviour-скрипт с именем
MissileMove
и вставь в него этот код:
using UnityEngine;
public class MissileMove : MonoBehaviour
{
// Цель ракеты (назначается при спавне из башни)
public GameObject target;
public float speed = 7f; // скорость полёта
public float rotationSpeed = 100f; // скорость поворота (град/сек)
public int damage = 100; // урон при попадании
public float lifeTime = 5f; // время самоуничтожения (сек)
private Rigidbody2D rb;
void Start()
{
rb = GetComponent<Rigidbody2D>();
// страховка: если ракета никуда не попадёт — исчезнет сама
Destroy(gameObject, lifeTime);
}
void Update()
{
if (target == null) return; // цель могла быть уничтожена раньше
// направление на цель
Vector3 dir = target.transform.position - transform.position;
// желаемый поворот в сторону цели (в 2D камера смотрит вдоль Z)
Quaternion targetRot = Quaternion.LookRotation(Vector3.forward, dir);
// плавно поворачиваем ракету к цели
transform.rotation = Quaternion.RotateTowards(
transform.rotation,
targetRot,
rotationSpeed * Time.deltaTime
);
}
void FixedUpdate()
{
// полёт вперёд по локальной "верхней" оси спрайта (transform.up)
rb.linearVelocity = transform.up * speed;
}
void OnTriggerEnter2D(Collider2D collision)
{
if (collision.CompareTag("Meteor"))
{
var mp = collision.GetComponent<MeteorParam>();
if (mp != null)
{
mp.hp -= damage;
if (mp.hp <= 0)
Destroy(collision.gameObject);
}
// уничтожаем ракету после попадания
Destroy(gameObject);
}
}
}
Добавь скрипт MissileMove компонентом на объект
Missile. Затем создай префаб ракеты,
перетащив объект из сцены в папку Assets. Когда
префаб готов — можно удалить ракету из сцены.
MissileMove.
Готово! В следующем шаге сделаем ракетную башню, которая будет запускать такие ракеты по выбранным целям.
Теперь добавим ракетную башню для борьбы с крупными, толстокожими целями. Эта башня стреляет реже, но наносит высокий урон и, самое главное, она приоритетно ищет только те метеоры, которые имеют большой запас прочности (HP).
Скачай картинку башни:
Импортируй картинку в Unity. Создай объект башни, перетащив спрайт в
окно Scene, и назови его, например,
Kiava Flow. Отмасштабируй примерно до
X = 0.5, Y = 0.5, а в
Order in Layer поставь 10, чтобы башня
рисовалась поверх ракеты.
Создай новый MonoBehaviour-скрипт с именем
MissileTower. В этом скрипте башня будет искать «крупные»
метеоры (по HP) в радиусе и запускать в них ракету.
using UnityEngine;
public class MissileTower : MonoBehaviour
{
// Максимальная дистанция обнаружения метеоров
public float maxTargetDist = 10f;
// Порог HP, начиная с которого метеор считаем "крупным"
public int minHpForMissile = 5;
// Префаб ракеты (создан заранее в Assets)
public GameObject missilePrefab;
// Задержка между выстрелами (сек.)
public float shootingDelay = 1f;
// Звук запуска ракеты
public AudioClip missileSound;
public float volume = 0.7f; // громкость 0..1
void Start()
{
// Периодически вызываем выстрел
InvokeRepeating(nameof(ShootBig), 1f, shootingDelay);
}
void ShootBig()
{
// Ищем все метеоры по тегу
GameObject[] mets = GameObject.FindGameObjectsWithTag("Meteor");
GameObject best = null;
float bestSqr = maxTargetDist * maxTargetDist;
Vector3 p = transform.position;
foreach (var m in mets)
{
if (m == null) continue;
var mp = m.GetComponent<MeteorParam>();
if (mp == null) continue;
// Берём только "крупных" по текущему HP
if (mp.hp < minHpForMissile) continue;
float sqr = (m.transform.position - p).sqrMagnitude;
if (sqr <= bestSqr)
{
best = m;
bestSqr = sqr;
}
}
if (best == null) return;
// Стартуем ракету из центра башни
var missile = Instantiate(missilePrefab, transform.position, Quaternion.identity);
// Назначаем цели ракеты метеор
missile.GetComponent<MissileMove>().target = best;
// Проигрываем звук запуска
if (missileSound != null)
AudioSource.PlayClipAtPoint(missileSound, Camera.main.transform.position, volume);
}
}
Добавь скрипт MissileTower компонентом на объект башни.
Скачай звук запуска ракеты:
🎵 Boom. Импортируй
его в Unity и укажи в поле скрипта missileSound.
В инспекторе у башни заполни поля: перетащи префаб ракеты в
missilePrefab и добавь звук в missileSound.
После проверки работы создай префаб башни, перетащив
объект из сцены в папку Assets.
MissileTower.
Теперь создадим подставки, на которые игрок сможет устанавливать башни. Они служат точками размещения и помогают управлять местом установки.
Скачай картинку подставки:
Импортируй картинку в Unity и создай из неё игровой объект — просто
перетащи спрайт подставки в окно Scene. Назови объект
Stand.
Далее добавь новый тег Stand и назначь его подставке (Inspector → Tag → Add Tag... → Stand). Этот тег позже понадобится для распознавания подставок при установке башен кликом мыши.
Чтобы метеоры не сталкивались с подставками, создадим отдельный физический слой:
Stand.
Затем открой настройки проекта:
Edit → Project Settings → Physics 2D. В разделе
Layer Collision Matrix сними галочку пересечения
между слоями Default и Stand. 💡 Теперь
метеоры будут просто пролетать сквозь подставки, не сталкиваясь с
ними.
Чтобы башня точно вставала на нужное место, нужно немного сместить Pivot (опорную точку спрайта):
Установи Order in Layer на 5, чтобы
подставка рисовалась под башнями. Добавь компонент
Box Collider 2D — он понадобится, чтобы выбирать
подставку кликом мыши при установке башен.
Теперь создай префаб подставки: просто перетащи объект из сцены в папку Assets. После этого можно разместить несколько подставок в сцене — например, пять штук, расположив их в ряд.
💡 Если вы размещаете подставку перед городом,
убедитесь, что она не перекрывается коллайдером других объектов. Чтобы
избежать этого, можно слегка выдвинуть подставку по оси
Z — задать небольшое отрицательное значение (например,
-0.1). Это выведет её вперёд в слое сцены, не влияя на
2D-отображение.
Stand с собственным тегом, физическим слоем и
коллайдером.
Теперь осталось самое интересное — защитить город! Сделаем небольшой скрипт, который позволит устанавливать башни на подставки. При клике левой кнопкой мыши будет ставиться лазерная башня, а при правом клике — ракетная. После установки коллайдер подставки автоматически отключится, чтобы на неё нельзя было поставить вторую башню.
Создай новый MonoBehaviour-скрипт с именем
TowerSpawner.
using UnityEngine;
public class TowerSpawner : MonoBehaviour
{
public GameObject laserTower; // Префаб лазерной башни (ставится ЛКМ)
public GameObject missileTower; // Префаб ракетной башни (ставится ПКМ)
void Update()
{
// Левая кнопка мыши — ставим лазерную башню
if (Input.GetMouseButtonDown(0))
TryPlaceTower(laserTower);
// Правая кнопка мыши — ставим ракетную башню
if (Input.GetMouseButtonDown(1))
TryPlaceTower(missileTower);
}
void TryPlaceTower(GameObject towerPrefab)
{
// Проверяем, что префаб назначен
if (towerPrefab == null) return;
// Получаем позицию клика мыши в мировых координатах
Vector2 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
// Проверяем, попали ли в объект с коллайдером
RaycastHit2D hit = Physics2D.Raycast(mousePos, Vector2.zero);
if (hit.collider == null) return;
// Если это подставка (тег Stand) — ставим башню
if (hit.collider.CompareTag("Stand"))
{
Instantiate(towerPrefab, hit.transform.position, Quaternion.identity);
// Отключаем коллайдер подставки, чтобы нельзя было поставить вторую башню
hit.collider.enabled = false;
}
}
}
В сцене создай новый пустой объект (его позиция не имеет значения) и
добавь на него компонент TowerSpawner.
В инспекторе укажи ссылки на префабы:
Laser Tower — перетащи префаб лазерной башни
Gemeni Analyzer.
Missile Tower — префаб ракетной башни
Kiava Flow.
Теперь базовая система установки башен готова! Запусти сцену, кликай по подставкам и строй защиту города. Лазеры и ракеты зажгут небо — и начнётся настоящая битва за выживание! ⚡
TowerSpawner с привязанными префабами башен.