Warcraft II любой ценой, или Как мы переносили игру на ОС «Аврора»

Это всё, что нам сказали при постановке задачи. Мы даже не знали, с чего начать. Может, эмуляция? Таким был первый вариант. В итоге решили писать игру заново на Godot 4.4. Я сел думать над архитектурой. Где всё брать?
Нашли Wargus и Stratagus. Отлично. Но кто‑нибудь вообще понимает эти Lua‑файлы?
Меня зовут Анатолий Соболев. В проекте я был тимлидом. Реализацию организовала ИТ‑лаборатория «Проектим». Она собирает студентов в команды и даёт им реальные задачи. Вот и мы попали на площадку «Проектим», которую представляет Михаил Гуненков.
В команде было шесть человек: три разработчика, дизайнер, менеджер и тестировщик. Активная разработка заняла около двух с половиной месяцев. Вообще планировалось ровно два, но мы не могли остановиться!
Не эмуляция
Конечно, эмуляция была бы самым коротким путём. Игра уже существует, правила написаны — зачем делать всё заново?
Но Warcraft II создавался для мыши и клавиатуры. Нам требовались сенсорное управление и мобильный интерфейс, а ещё очень остро стоял вопрос производительности. Сделать сенсорную оболочку поверх эмулятора в принципе можно, но игровая логика при этом осталась бы внутри старого исполняемого файла, и отдельно управлять её подсистемами мы бы не смогли.
Поэтому мы не стали искать лёгких путей и решили реализовать игру самостоятельно. Wargus и Stratagus стали технической базой. По ним мы восстанавливали буквально всё: правила, характеристики юнитов, устройство карт, миссии и даже ИИ врагов. В общем, всё просто. Осталось только понять и переписать целую игру.
Почему Godot
Ну а где ещё? Надо же, чтобы всё на «Аврору» собрать можно было. У Godot 4.4 был порт для «Авроры» и рабочий путь сборки такого пакета. У других движков, которые мы рассматривали, такого не было. Поэтому, собственно, и выбрали Godot. Не сказал бы, что долго выбирали.
Почему не перенести архитектуру Stratagus как есть?
Ну здрасте, это ж движок. Нам что, писать движок внутри движка?
Stratagus управляет циклом игры, отображением, вводом, звуком и общим состоянием мира. В нашем проекте эту роль уже выполняет Godot.
Ладно, логику движка мы не будем переписывать, а что касается отображения визуала?
В Stratagus за расчёты и отрисовку отвечают отдельные функции: GameLogicLoop и DisplayLoop. Обе последовательно вызываются из общего SingleGameLoop и работают с состоянием одного движка.
Упрощённо это выглядит так:

На схеме всё даже выглядит вполне раздельно. Но есть нюанс.
Расчёты и отрисовка там разделены как этапы ОДНОГО цикла, и жёсткой границы между ними нет. Они используют общие объекты карты, юнитов, игроков и интерфейса. Например, игровая логика может поставить флаг обновления миникарты, а цикл отображения затем прочитает этот же флаг и выполнит обновление.
А мы как раз хотели сделать жёсткую границу между визуалом и логикой. Вот прям чтоб жёстче некуда. Чтобы они не шли за ручку в рамках одного игрового цикла. Конечно же, для рычагов оптимизации. Какая же это граница, если они в одной лодке сидят и по очереди крутят штурвал? Одного посадишь за мотор добавить газа, у второго вёсла из рук повылетают.
Так что архитектуру Stratagus мы тащить не стали. Из него взяли правила Warcraft II: порядок игровых фаз, приказы, движение, бой, экономику и поведение ИИ. А внутри Godot выстроили своё разделение: runtime хранит и рассчитывает состояние игры, presentation получает подготовленные данные и показывает их на экране.
Почему юнит — не отдельный узел Godot?Да, юнит у нас — это не узел. Казалось бы, самым очевидным решением для Godot было бы сделать каждый юнит отдельным узлом, который хранит всё сразу: здоровье, приказ, положение, спрайт и анимацию.

Узел в Godot удобен для организации отдельной сущности: в нём можно объединить сценарий, состояние, сигналы, спрайт и функцию обновления. Сказка! Казалось бы... Но в RTS одновременно работают сотни таких юнитов. Если каждый из них сам считает движение, бой и анимацию, движку приходится обходить множество узлов и вызывать их по отдельности.
Мне этого хватило, чтобы засомневаться, что это получится нормально оптимизировать.
Нам нужен был другой подход. И да, мы отказались от отдельного логического узла для каждого юнита. Опять же, чтобы получить больше рычагов влияния на производительность.
Поэтому игровой юнит у нас живёт в данных ядра. Presentation получает подготовленные записи, а общий WorldView одним проходом рисует юнитов, которые должны появиться на экране. Ядро может обрабатывать множество юнитов вместе, не обходя дерево сцен. Визуальный модуль, в свою очередь, может не рисовать объекты за пределами экрана и реже выполнять тяжёлые обновления, не вмешиваясь в расчёты матча.
Дополнительно такое разделение позволяет проверять движение, бой и механики без запуска полноценной сцены и модуля отображения. Тесты! Тесты! Тесты! Как же с ними стало проще.
Что именно мы изменили
Хорошо, границу провели. Но где именно?
Мы сделали игровое ядро единственным владельцем состояния матча. Только оно решает, где находится юнит, сколько у него здоровья, какой приказ он выполняет, кому принадлежит здание, сколько у игрока ресурсов и какие клетки скрыты туманом войны.
Сценные узлы, интерфейс и отображение этого состояния напрямую не меняют. Нажатие на экран сначала превращается в команду: идти, атаковать, добывать ресурс, строить или обучать юнита. Команда попадает в ядро, ядро выполняет очередной игровой такт и только потом сообщает остальным модулям, что изменилось.

Выглядит немного бюрократично: сначала команда, потом такт, потом событие. Зато никто не может тихо поправить мир сбоку.
Граница работает в обе стороны. Снаружи в ядро входят только явные команды и шаги сценария. ИИ находится внутри ядра, но меняет мир через те же команды. Наружу выходят события, изменившиеся данные и подготовленные снимки состояния.
Визуальная часть не может случайно добавить золото, изменить здоровье или передвинуть юнита. А ядро не знает, существует ли на экране спрайт этого юнита и с какой частотой он рисуется. Именно этого мы и добивались.
Как это устроено в Stratagus и Wargus
В Stratagus логика и отображение выполняются внутри общего цикла движка. Его подсистемы работают с общими сущностями карты, юнитов и игроков, а отрисовка является частью работы самого движка.
Lua в Wargus используется не только для хранения данных. Это ещё и исполняемый слой, который вызывает функции Stratagus. То есть описания юнитов, зданий, кнопок и других игровых объектов непосредственно подключены к возможностям движка.
Как это устроено у нас
У нас игровое ядро и отображение — два отдельных модуля с явной границей. Изменять состояние матча может только ядро. Отображение получает от него уже подготовленные изменения и может обновлять картинку в собственном темпе.
Файлы описания в нашем проекте содержат только данные. Они не управляют движком и не реализуют игровое поведение. Приказы, движение, бой, экономику и остальные правила исполняет runtime.
Из Wargus и Stratagus мы перенесли правила и поведение Warcraft II, но не архитектуру движка. В Godot мы жёстко разделили логику и отображение, чтобы их можно было оптимизировать независимо друг от друга для устройств с «Авророй».
Что находится внутри ядра
Сейчас в каталоге warcraft_runtime 153 GDScript‑файла — около 21 тысячи строк (без комментариев).
Сердце матча получилось немаленьким.
Внутри находятся:
состояние карты, игроков, юнитов, зданий и ресурсов;
игровой цикл и очередь команд;
движение и поиск пути;
приказы атаки, добычи, строительства, ремонта и обучения;
бой, экономика, улучшения, заклинания и транспорт;
туман войны и видимость;
поведение компьютерных противников;
события, снимки для сохранения и данные для интерфейса и отображения.
Само ядро тоже разделено на части:

В коде ядра нет сцен, кнопок, спрайтов и звуков. Юнит внутри ядра — это запись в состоянии матча, а не отдельный объект Godot, который каждый кадр получает собственный вызов обновления.
Сто юнитов здесь — это сто записей, а не сто маленьких сцен, каждая из которых ждёт собственного вызова обновления.
По сути, мы использовали data‑oriented‑подход: состояние хранится централизованно, а системы обрабатывают группы записей, а не вызывают игровую логику отдельных объектов.
Отдельный визуальный модуль
По другую сторону границы находится presentation. Это тоже самостоятельный модуль: 30 GDScript‑файлов и около 9 тысяч физических строк.
Он рисует ландшафт, юнитов, здания и снаряды, выбирает анимации, управляет камерой, эффектами, туманом войны, миникартой и игровым звуком. Но он не рассчитывает урон, не ищет путь и не решает, хватает ли игроку золота на постройку. Его задача — показать бой, а не решить, кто в нём победил.

Визуальный модуль хранит соответствие между логическим юнитом и его изображением. Если ядро сообщает, что юнит переместился или начал атаку, отображение обновляет нужный объект и запускает нужную анимацию. Если ничего не изменилось, повторно собирать весь мир не требуется.
Интерфейс мы также вынесли отдельно. HUD и панели получают готовые снимки: выбранные юниты, ресурсы, доступные команды. Они показывают состояние матча, но не владеют им.
Как всё собирается в один матч
Сами себя модули, увы, не соединяют.
Чтобы независимые части всё‑таки стали игрой, нужен модуль match. Он создаёт один матч и соединяет все входы и выходы.

В результате сцена отвечает за жизнь экрана, match — за соединение модулей, а ядро — только за состояние и ход игры.
Сработает ли первая карта?
И вот тут красивое архитектурное решение впервые выставило счёт.
Одним из главных рисков были карты. Привычный для Stratagus процесс сборки нам уже не подходил: логические и визуальные данные карты теперь жили по разные стороны архитектурной границы.
И нам пришлось разработать собственный путь импорта. При переносе каждая карта раскладывалась на две части: логическую и визуальную. Логическая часть описывала, что находится в игровом мире и как оно работает: где можно ходить и строить, где растёт лес, кому принадлежат юниты и здания, где лежат ресурсы. Визуальная часть описывала, как эти же клетки нужно нарисовать в Godot: какой показать ландшафт и какую графику использовать.
Ядро читало логическую часть, а модуль отображения — визуальную. Но обе части описывали одну и ту же карту, поэтому должны были совпадать до клетки. Иначе дерево могло появиться на экране, но не существовать для игровой логики. Или наоборот: ядро считало бы проход закрытым, хотя игрок видел свободное место.
Одна карта, две версии правды. Мир идей — числовая матрица мировых данных, и мир вещей — то, что видит пользователь.
Мы не знали, получится ли таким способом правильно собрать весь ландшафт, расставить объекты и связать их с отображением и анимациями. Но очень надеялись.
Поэтому запуск первой карты стал важной проверкой всей архитектуры. Она собралась целиком, ландшафт отображался правильно, а юниты сразу начали нормально двигаться. Стало понятно: выбранный подход работает, архитектура жизнеспособна.
А код кто писал?
700.000 строк!
Если собрать весь код, комментарии, которые нужно было проанализировать или написать. Это Wargus и Stratagus, код уже нашего проекта, частично документация, комментарии, тесты. Короче, всё в кучу если накидать, то вот такое число получается.
Нас тут два первокурсника и школьник за клавиатурой. Конечно же код писал ИИ. Но копки «сделать Warcraft II» у него, к сожалению, не оказалось. Мы задавали архитектуру, разбивали работу на отдельные задачи, объясняли границы модулей и проверяли результат.
Особенно ИИ пригодился при переносе механик. В Lua‑файлах Wargus обычно лежали характеристики, флаги и описания, а в C++‑коде Stratagus — само поведение движка. Чтобы перенести одну механику, эти две части нужно было связать.
Мы давали ИИ конкретную механику и просили проследить весь её путь: найти описание в Wargus, определить, какой код Stratagus его исполняет, собрать значения, порядок действий. После такого разбора ИИ писал реализацию для нашего runtime и тесты, а мы проверяли код и результат.
В общем, код ИИ писал быстро. Но решать, какой именно код должен появиться, как он вписывается в архитектуру и почему результату можно доверять, всё равно приходилось нам.
Warcraft II без мыши
Можно идеально перенести механику, а потом обнаружить, что пальцем в неё невозможно играть.
Отдельной задачей стал UI/UX. Дизайнер команды исследовал оригинальный интерфейс и управление мобильными стратегиями, а затем разработал реализацию для сенсорного экрана.

Сборки показывали фанатам Warcraft II для тестов. Больше всего вопросов было с управлением: как сделать то или это. И мы решили добавить обучение в игру. Вместо длинных инструкций камера и указатель показывают нужное действие прямо во время игры. Весьма наглядно.

Это точно Warcraft II?
Формально нет. Изменили буквально всё, вплоть до названия игры: «Военное ремесло II». Однако есть возможность подгрузить игровые файлы, если у вас приобретена официальная версия игры, и играть именно в Warcraft II.
А теперь запустите это на слабом устройстве
На десктопе все герои. Настоящий разговор начинается на слабом телефоне.
И, несмотря ни на что, самой трудной частью стала всё‑таки оптимизация. И хорошо, что мы так перестраховались на этапе выбора архитектуры. На больших картах одновременно работают компьютерные противники, поиск пути, туман войны, миникарта, анимации и множество юнитов.
При оптимизации мы по максимуму использовали возможности, которые дало разделение ядра и отображения. И вот тут архитектурная перестраховка наконец начала окупаться.
С этого места поподробнее.
Два времени: кадры и игровые такты
У игры есть два независимых темпа.
Оба про время, но каждый про своё.
Игровой такт — это один шаг мира. Во время него юниты двигаются, сражаются, добывают ресурсы, а компьютерные игроки принимают решения. Ядро должно выполнять 30 таких тактов в секунду. Это игровой квант времени. Его отсчитывает игровое ядро.
Кадр — это одна картинка на экране. Кадров может быть 60 в секунду, 30 или меньше. Этим занимается модуль отображения.
Представим, что ядро стабильно выполняет 30 игровых тактов в секунду. Если изображение обновляется с частотой 60 FPS, один игровой такт приходится примерно на два кадра. То есть, это могут быть два разных визуальных кадра из‑за анимации, но при этом одно и то же состояние мира. Так как между ними не было смены такта.
При 30 FPS на каждый кадр приходится примерно один такт. А при 15 FPS между двумя соседними кадрами ядро успевает выполнить около двух тактов.
Поэтому низкая частота кадров сама по себе не замедляет игру. Если ядро по‑прежнему выполняет 30 тактов в секунду, мир движется с обычной скоростью — просто изображение становится менее плавным.
Проблема начинается, когда компьютер не успевает считать все 30 тактов. Он захлёбывается из‑за огромного числа запросов. Тогда за время одного долгого кадра накапливается сразу несколько тактов, которые ядру нужно выполнить, чтобы догнать реальное время.

Так игра может потратить всё время на попытку догнать симуляцию и перестать нормально реагировать на касания и движение камеры.
Чем усерднее ядро догоняет, тем сильнее отстаёт. Опасная ловушка.
Как мы ограничили догон
На первой ступени ядро не замедляется. Система сокращает только работу отображения. Ядро передаёт изменившихся юнитов, здания и клетки тумана, а поле, туман войны и миникарта получают собственную частоту обновления.
Например, в автоматическом режиме качества поле при росте нагрузки может перейти с обновления каждый кадр на 15, 8 или 5 раз в секунду. Движение на экране станет менее плавным, но сам матч продолжит идти в прежнем темпе.
Камера, сенсорный ввод и основные элементы интерфейса по‑прежнему обрабатываются каждый кадр. Здорово, что логика не связана с отображением! Можно такие штуки делать. Тут мы динамически оптимизируем отображение.
Если этого оказалось мало, включается вторая защита: система перестаёт любой ценой поддерживать 30 тактов за реальную секунду.
Ядро выполняет меньше тактов за реальную секунду, и игровое время замедляется. При критической перегрузке количество тактов снижается ещё сильнее, чтобы программа не отдавала все ресурсы игровой логике и продолжало реагировать на ввод.
Куда девается долг
Допустим, за время долгого кадра ядро должно было выполнить три такта, но успело выполнить только два. Оставшийся долг отбрасывается. Игра не будет пытаться впихнуть все процессы в ушедшую секунду, да они должны были произойти секунду назад, но что делать? зависать что ли? Раз такая напряжёнка, обработаем такты в следующей секунде, а не в этой.
Поэтому ядру больше не приходится бесконечно догонять часы. Порядок игровых событий сохраняется, но за одну реальную секунду выполняется меньше 30 тактов. Игровое время начинает идти медленнее реального.
Так система снижает нагрузку со стороны игровой логики, жертвуя скоростью игрового времени.
Что дала архитектурная граница
Runtime и presentation можно оптимизировать независимо друг от друга. Отображение может реже обновлять поле, туман и миникарту, не вмешиваясь в игровые правила. Логика, в свою очередь, может ограничить количество тактов за кадр, не меняя их порядок и содержимое.
Игровое время при этом замедляется, но отрисовка продолжает работать в своём темпе: камера, интерфейс, управление и визуальные анимации не обязаны ждать очередного игрового такта. Поэтому замедление логики само по себе не означает падения FPS. Такое разделение позволяет отдельно настраивать нагрузку на логику и отображение.
А что на устройствах?
На слабых устройствах большие карты с огромным числом юнитов всё ещё могут давать просадки, и это ограничение мы не скрываем. Мы сделали всё, что могли, чтобы игра уверенно шла на любом тапке.
Игру запускали на трёх устройствах с ОС «Аврора»: F+ T1100, KVADRAT_T и AYYA T1. Для проекта подготовили экспортные шаблоны под aarch64, armv7hl и x86_64.
Что по итогу
За два с половиной месяца мы перенесли Warcraft II на Godot 4.4 и подготовили игру для ОС «Аврора».
В проект вошли две базовые кампании — 14 миссий людей и 14 миссий орков, компьютерные противники, сохранение и загрузка, музыка, звуки и русская озвучка. Карты переносятся через собственную систему импорта и проверки, а управление и интерфейс адаптированы для сенсорного экрана.
Если собрать всё в один список, звучит почти буднично. Ну да, 28 миссий, ИИ, сохранения, сенсорное управление. Садись студент, 5.
Только за этим списком осталось собственное игровое ядро, жёсткая граница между логикой и отображением, карты, разложенные на две части, два разных времени и система оптимизации.
Параллельно дизайнер разбирал оригинальное управление, исследовал механики и искал, как перенести их на сенсорный экран, не растеряв сам Warcraft II. Так у проекта появилась собственная UX/UI‑система, а не просто набор кнопок поверх игры.
Это не эмуляция и не старый Warcraft II, завёрнутый в новую оболочку. Игровую логику мы реализовали заново. Из Wargus и Stratagus брали правила, цифры и поведение, а архитектуру строили уже под Godot, сенсорное управление и слабые мобильные устройства.
Получилось ли идеально? Конечно нет. На больших картах слабому устройству всё ещё может быть тяжело. Но игра запускается, две кампании проходятся, юниты воюют, рабочие добывают золото. Орда пытается захватить Азерот. Значит, всё это было не зря.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.