В прошлой статье я описывал с большим количеством воды свою мотивацию и рассказывал про создание концепции игры. Это обязательная часть, без нее вам будет трудно системно продвигаться в разработке.
1. Инструменты
Здесь я расскажу про инструменты, которые использую или использовал ранее для того, чтобы облегчить разработку.
1.1 Состав инструментов
Инструменты, которые я использую сейчас:
документация по Unity. Сюрприз-сюрприз, если вы делаете игру на Unity (тут можно подставить название любого движка/системы на самом деле), вам придется изучить документацию;
llm – чуть ниже расскажу как именно;
VsCode – подойдет на первое время, на большом проекте начинает тормозить. У меня установлены следующие расширения:
Unity
C#
C# Dev Kit
IntelliCode for C# Dev Kit
VS CodeCounter – это совершенно не обязательно, просто для отслеживания прогресса и объема проекта;
Debugger for Unity - для отладки
UnityEditor. У меня стоит версия 6 и установлены следующие пакеты:
Visual Studio Editor – обязательный пакет, если вы кодите на VS.
Input System – полезная штука для создания действий, которые пользователи могут осуществлять через контроллеры ввода (мышь, клавиатура и т.д.) Можно сразу ставить, тк пригодится довольно скоро
Unity UI, Universal RP – базовые наборы для UI, пока не обязательны
Version Control. В начале не критично, можно делать бэкапы просто копируя папку с проектом, меня начал выручать где-то через год
Test Framework – я честно, тесты начал писать только недавно.
Entities – я игрался с ECS, если вы только начинаете, вам это не надо.
Burst – пригодится в будущем, когда будете оптимизировать массовые операции разного рода.
CrashKonijn's GOAP – игрался с GOAP, мне не зашло. Не обязательный пакет
figma - для отрисовки тестовых спрайтов и моделек
Blender - для 3D моделей NPC. Если вы только начинаете, потребуется он вам еще не скоро
А что используете вы?
Что я раньше использовал и от чего отказался:
Задавал вопросы на StackOverflow, но потом переключился на llm – понял, что оно отвечает быстрее и лучше;
Обучающие ролики на youtube. Вот это мне совсем не зашло.
Искал разные статьи – просто потеря времени. Особенно горит, когда ты повторяешь все действия, а у тебя не работает. Начинаешь пересматривать скриншоты и видишь, что автор где-то добавил код, про который не рассказал в статье, а ты не можешь его повторить – нет навыков.
Немного субъективизма про обучающие видео
Понятное дело, что я не посмотрел ВЕСЬ массив видео на YouTube, поэтому ниже будет следовать довольно субъективное суждение, но те видео, что я смотрел, обычно начинались со слов «Сейчас я расскажу вам, как сделать…» - и результат вроде бы есть, но я не могу его использовать в проекте – он просто тупит.
Например, самый первый грид я делал по этому видео https://www.youtube.com/watch?v=kkAjpQAM-jE Здесь коллега предлагает создавать GameObject на каждую ячейку грида. Наверно, такой подход оправдан в шахматах, или где-то еще, где карта небольшая. Но, когда я попробовал это сделать для своей карты 100 на 100, то заметил, что не все так гладко редактор начал тормозить.
Тут важно понимать контекст – опыта 0, поэтому нет понимания того, какие решения подходят для тех или иных задач, а какие – нет. Ты просто тыкаешься и набиваешь шишки.
Для себя я выделил такое правило – если карта не требует уникальных механик (ячейки не могут летать, проваливаться или перемещаться, как самостоятельные объекты), или если карта содержит больше 1000 ячеек, то она должна быть сделана без кучи GameObject-ов – через тайлмапы.
1.2. Использование llm
Теперь чуть подробнее про то, как использовать llm.
Я немного поисследовал разные подходы, которые есть на текущий момент в плане использования лексических моделей для создания игр и хотел бы предостеречь вас от использования вайб-кодинга, если вы собираетесь делать более-менее серьезную игру (кодовая база больше 10 тысяч строчек кода) – ну то есть не просто на выходных посидеть.
Ниже иллюстрация, которая вкратце объясняет следующие несколько абзацев:

Представьте, что вы создали 3-5 тысяч строк кода с помощью llm и затем в какой-то момент получаете ошибку типа NullReferenceException, но она возникает не всегда, а при каких-то определенных условиях. Если вы не понимаете смысла строчек кода, которые вам сгенерировал алгоритм, то отладить код вы в принципе не сможете – вы не понимаете, что эта ошибка означает, не понимаете, как она возникает, не можете ее правильно воспроизвести, а это значит, что ошибки для вас превратятся в кромешный ад, хотя, по сути, их может быть и не трудно исправлять. llm может справиться с очень простой отладкой, когда вы сделали миссклик или пропустили инициализацию параметра. Но, если ошибка вызвана логическим косяком, то без детального понимания того, как работает код, исправить её будет практически невозможно. Отсюда следует вывод, что вы должны понимать, как работает ваш код, а llm – это просто ваш коллега, ментор, аналитик или код-ревьювер, как хотите.
Итого, ваша цель на первые 3-4 месяца – понять, как писать код, какие есть типовые задачи и как их решать.
Ну если вы, конечно, знаете, как можно использовать llm для вайб-кодинга, чтобы он мог писать масштабируемый код и мог отлаживать баги в среднем проекте (40+к строчек самописного кода) – напишите об этом, я хочу это проверить своими руками. И да, Claude я пробовал. Может я, конечно, сделал все неправильно, но поначалу казалось, что у тебя какой-то супер-инструмент, но потом, субъективно, после примерно 5к строчек кода он начинает делать уже косячные архитектурные решения, не понимает, где лежат модели, ошибается. После 15к строчек кода он дает, скажем так, весьма... спорные решения.
Для себя я вывел такой алгоритм взаимодействия с llm:
Вы открываете ваш дизайн-док, смотрите самый легкий базовый сервис – грид и закидываете в llm задачу и объясняете контекст. Например, хочу сделать 2D изометрический грид на Unity. Размер карты 100 на 100 ячеек. Объясни, какие есть подходы, и как лучше решить эту задачу.
Далее llm даёт ответ с описанием и примеры с кодом. Мне этот код совершенно непонятен, и я прошу построчно объяснить, как он работает. Llm объясняет, и я проверяю его объяснения на предмет логических косяков. Спрашиваю, а что, если… – тут можно детализировать различные сценарии.
Если логика мне понравилась, я прошу сохранить ее под хештегом, например, #вариант 1. Если нет – прошу уточнять. На первых порах всегда просил его сопровождать код подробным описанием алгоритма.
Затем я приступаю к написанию кода. Важно не бездумно копировать его, а прописать своими руками – так вы как бы «проговорите» его про себя. Вначале я создавал отдельный диалог, в котором просил llm объяснить базовый синтаксис – как правильно написать цикл, условия что-если, как выводить сообщения в консоль и так далее.
Начинайте новую тему в новом чате – когда контекст диалога длинный, ИИ начинает тупить, а вы - злиться.
Затем, когда код написан (причем неважно, есть ошибки или нет), я ставлю точку остановки в самом начале сценария и начинаю построчно проходить его в отладчике. Попутно делая скрины и спрашивая у GPT, что я вижу на экране. Цель этого шага – полностью понять, как работает написанный код до последней строчки. Сейчас, разумеется, я включаю отладку, только если что-то работает неправильно, но вначале, когда опыта 0, такой подход мне помогал разобраться в коде.
Затем, когда в редакторе я вижу то, что и хотел сделать, я прошу llm подсветить мне проблемные места в коде и объяснить, как их можно убрать.
Если я соглашаюсь на рефакторинг, я прошу его сделать, разложив код на паттерны проектирования (я знал такое словосочетание и знал, что использовать их – хорошо), предварительно разложив по файлам и описав структуру проекта.
Важно! Перед тем, как вы начинаете менять что-то, что уже работает – сделайте бэкап.
Скорость написания кода в таком варианте – около 50 осмысленных строчек кода в день, если вы делаете что-то новое, а не отлаживаете баги.
Следуя такому алгоритму, вы где-то через месяца два уже будете ориентироваться в написанном коде и заметите прирост эффективности.
Буду рад, если поделитесь своим опытом использования llm в работе над проектами.
2. Настройка окружения
У вас должен быть установлен UnityHub и редактор для написания кода.
Вначале создаём новый проект в UnityHub. Я выбираю Universl 2D игру
Когда редактор загрузится – установите пакеты, которые я обозначил в начале статьи. Хотя бы Visual Studio Editor и Input System.
Настройте под себя внешний вид редактора, я использую такой пресет
В основной части у меня 3 вкладки:
Project. Здесь отображаются файлы и папки проекта
Scene – сцена
Game – игровой вид
Ниже располагается панель с консолью. Если включаю профилировщик, то перетаскиваю его тоже сюда, или открываю в отдельном окне
В правой части располагаются 2 области
Hierarchy – здесь отображается иерархия объектов на сцене
Inspector – детальная информация по выбранным на сцене объектам
Затем откройте настройки Edit -> Preferences -> ExternalTools и привяжите ваш редактор кода

Затем создайте тестовый скрипт, откройте его вашим редактором кода и установите расширения. Если вы используете VS Code - установите как минимум:
C# Dev Kit
Unity Tools
Debugger for Unity
Создайте тестовый скрипт в вашем проекте.

Откройте его. Если вы все сделали правильно, то вы увидите шаблон скрипта типа такого

в котором специфичные типы данных для Unity подсвечены зеленым - в них можно провалиться через Ctrl+ЛКМ.
Теперь все готово к дальнейшей работе. Давайте сделаем простую фичу.
Все действие игры происходит в игровой зоне (карта не бесконечная). Обычно в симуляторах, где действие происходит в ограниченной области игровую зону делают в виде ячеек (сетки) - вы это можете увидеть в The Sims, SimCity, Civilization, X-Com и так далее.
Соответственно, нам надо
Создать игровую сетку
Заполнить ее чем-то
Сделать так, чтобы при клике мы получали координаты ячеек
Добавить игровую зону
3. Создание игровой области
Начнем с создания грида (сетки)
3.1 Создание грида
Вначале мы добавим объект, который будет предоставлять грид, а затем напишем простой скрипт для вывода информации в консоль.
Добавим пустой GameObject на сцену
Добавление игрового объекта на сцену В правой части экрана в карточке вы увидите свойства этого объекта и кнопку
Add Component
. В Unity игровая логика реализуется путем добавления различных «компонент» на объекты – как встроенных, предоставляемых редактором, так и ваших собственных скриптов.
Грид – это стандартный компонент, нажимаем кнопкуAddComponent
и добавляемGrid
После того, как вы это сделали, можете переключиться на вкладкуScene
и увидеть, что теперь у вас есть сеткаДобавлен грид Поиграйтесь с параметрами и посмотрите, на что они влияют
Напишем простой скрипт для вывода данных в консоль. Создаем папку Assets/Scripts - здесь у нас будут лежать все скрипты и добавляем в нее новый скрипт
Открываем его в редакторе кода и пишем следующий кодusing UnityEngine; public class TestGrid : MonoBehaviour { private Grid grid; private void Awake() { grid = this.GetComponent<Grid>(); } public void Update() { if (Input.GetMouseButtonDown(0)) { Vector3 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition); Vector3Int cellPos = grid.WorldToCell(mousePos); Debug.Log($"Клик по ячейке {cellPos}"); } } }
Этот скрипт надо повесить на тот же объект, в который мы добавляли компонент Grid ранее.
Рекомендую почитать документацию Unity, чтобы было понятнее, что тут написано.
Если совсем вкратце, то я создал приватную переменную типа Grid, инициализировал ее значение гридом из компонента, на котором висит этот скрипт и при нажатии ЛКМ вывожу значение координат, используя встроенные преобразования координат.Запускаем сцену, кликаем мышкой и видим сообщения в консоли
Вывод данных в консоль Откуда тут взялось минус 10?
Смотрим в код – координаты берутся из какого-то объекта Camera.
Идем в сцену и видим, что на сцене есть объект Main Camera, у которого в компонентах есть компонент Camera, и вот координата z этого объекта:Координаты камеры
3.2. Создание тайлмапа и размещение тайлов
Давайте теперь немного модернизируем фичу. Мы хотим визуализировать ячейки, чтобы было понятно, куда мы кликаем, а не просто тыкаться в однотонный фон.
Наша цель создать тайлмап
, который мы заполним тайлами
. Для начала давайте создадим самый простой спрайт
, который затем будет использоваться для тайлов.
Нарисуйте где угодно квадрат, я использую для этого figma:

Экспортируем это как картинку, затем идем в редактор, создаем папку Resources/Sprites и помещаем в неё это изображение

Теперь создаем еще одну папку Tiles – тут будут храниться тайлы. В этой папке создаем TilePalette
– коллекцию тайлов, отсюда можно будет выбирать разные тайлы, которыми вы будете «закрашивать» ваш грид.

Далее, дважды кликаем по объекту и видим, что иерархия сцены теперь изменилась

Мы находимся в TilePalette
. Переходим на вкладку Scene
и внизу нажимаем Open Tile Palette

Далее открываем структуру проекта и перетаскиваем спрайт в область коллекции тайлов

Вам будет предложено сохранить ассет (тайл). Давайте сохраним его как GreenTile и поместим в папку Tiles. Затем возвращаемся обратно в иерархию сцены

Открываем наш грид и добавляем в него дочерний объект с компонентом Tilemap
. Дочерний объект я тоже назвал Tilemap

Теперь открываем вкладку Scene
, в иерархии сцены выбираем Tilemap
, в окне TilePalette
кликаем на тайл и переносим указатель мыши на сцену. Вы увидите, что он стал немного другим

Кликаем – и… ничего не происходит. Это потому, что для визуализации тайлмапа необходимо добавить компонент, который отрисует его. Он называется TilemapRenderer
Нажимаем AddComponent
и добавляем его к объекту

Пробуем еще раз – на этот раз видим, что добавляются зеленые квадраты, как и ожидалось
Однако, в моем случае я нарисовал квадраты 128 на 128, и они оказались несколько больше размера ячейки

Тут есть выбор – либо подгонять размер ячейки, либо размер спрайта. Подогнать спрайт проще. Идем в папку Resources/Sprites где лежит изображение, открываем его и в правой части меняем этот показатель на действительный размер (в моем случае – 128)

Сохраняем изменения – и, видим, что картина стала такой, как и ожидалось

Давайте теперь сделаем так, чтобы начало координат (0, 0) было бы в нижнем левом углу.

Наша задача – перетащить тайлы в первую четверть координатной плоскости (правая верхняя четверть). Для этого открываем еще раз палету, выбрав Tilemap в иерархии сцены и кликнув на OpenTilePalette.
В открывшемся окне выбираем режим выделения

Затем выделяем нарисованные тайлы. Затем выбираем режим перемещения

Перемещаем выделенные тайлы на нужное место
Запускаем сцену и проверяем, что теперь тайлмап нарисован в положительных координатах

Давайте сделаем еще один шаг, и на этом все - выделим логическую игровую зону. Предположим, что мы хотим сделать логическую игровую зону, размером 10 на 10, при этом тайлмап мы можем нарисовать так, что он будет выходить за пределы зоны, или наоборот быть меньше, а весь игровой мир будет ограничен такой областью.
3.3. Создание логической игровой зоны
Для создания игровой зоны мы применим подход разделения логики и отображения. Мы будем хранить отдельно логическую модель грида и напишем небольшой сервис, который будет связывать эту модель и отображение.
В самом простом варианте грид описывается двумя параметрами - ширина и высота. Еще нам нужен метод, который бы определял, находится ли точка в рамках игровой зоны
Составим вот такой скрипт для логической модели.
using UnityEngine;
public class LogicalGrid
{
public readonly int width;
public readonly int height;
public LogicalGrid(int width, int height)
{
this.width = width;
this.height = height;
}
public bool InBounds(Vector2Int p)
=> p.x >= 0 && p.x < width && p.y >= 0 && p.y < height;
}
И вот такой контроллер, который будет связывать логические данные с событиями пользовательского ввода.
using UnityEngine;
using UnityEngine.Tilemaps;
public class GridGameController : MonoBehaviour
{
public Tilemap tilemap;
public int width = 10;
public int height = 10;
public Vector3Int originCell = Vector3Int.zero;
private LogicalGrid logic;
void Awake()
{
logic = new LogicalGrid(width, height);
}
void Update()
{
if (Input.GetMouseButtonDown(0))
{
Vector3 world = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector3Int cell = tilemap.WorldToCell(world);
Vector2Int lp = new Vector2Int(cell.x - originCell.x, cell.y - originCell.y);
if (logic.InBounds(lp))
Debug.Log($"Логическая клетка: {lp.x},{lp.y}");
else
Debug.Log("Вне игровой зоны");
}
}
}
Переместим его на наш объект с гридом и инициализируем параметры

Чтобы инициализировать Tilemap просто перетащите объект со сцены в это поле.
Запускаем сцену и кликаем по тайлам.
Как можно заметить, сейчас выводятся 2 сообщения в консоль при каждом клике.

Первое – с самого первого скрипта, оно выводит просто координаты ячеек вне зависимости от того, где они находятся. Второе – только что добавленное, которое смотрит на то, находится ли ячейка в игровой зоне. Давайте пока для теста отключим первый компонент

Убираем с него галочку.
Теперь все работает, как и ожидалось – при клике по клеткам в игровой зоне мы получаем соответствующее сообщение в консоли
Итого
Подведем итог. В этой статье я показал на очень простом примере, как создать игровую зону в виде 2D грида. Мы также слегка затронули тему разделения данных от визуала – создали отдельно модель и управляющий класс. В будущем такой подход нам сильно поможет при поворотах карты, когда данные должны оставаться неизменными, а отображение – меняться.
Это вторая статья из цикла статей по геймдеву, спасибо, что дочитали её до конца. Если вам понравилось, и вы хотите быть в курсе новостей и обновлений - можете следить за выходом релизов в X или смотреть за выходом обновлений здесь. (на англ)