Добро пожаловать в Логово Силвервинга



Итак, мы решили написать игровой движок - Часть 1 - Определяем ожидания

Как и для любого большого проекта, первое что нужно сделать при написании игрового движка -- это определить требования. Они определят общее направление работ, а также дальнейшие решения.

С самого начала мы ставим совместимость и переносимость как наивысшие приоритеты для нашего проекта.

Это сразу же приводит к важному решению: строжайшему ограничению форматов данных. Хотя кажется неудобным, ограничение поддержки небольшим количеством свободных и открытых форматов с хорошей кросс-платформенной поддержкой (и рядом дополнительных ограничений) позволит обеспечить корректную работу на всех поддерживаемых платформах.

Архитектура

На высоком уровне архитектуру нашего движка можно представить так:

Посмотрим поближе.

Графика

Движок создается для 2D спрайтовых игр. Не планируется ни попиксельного рисования, ни векторных операций, ни шейдеров, ни 3D преобразований. Простое рисование картинок на экране. Этого уже достаточно для большого количества игр, но самое важное -- достаточно для большинства наших игр.

Дополнительно: будет интересно добавить поддержку произвольных трансформаций спрайтов при помощи матриц. Однако это не требуется для нашей первой игры, поэтому я вряд ли буду это делать для первой версии.

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

Движок будет обрабатывать все рисуемое на экране как слой. Для первой версии я планирую следующие типы слоев:

  • Спрайт -- простой или анимированный спрайт на экране;
  • Контейнер -- слой, содержащий другие слои; существует как удобный способ группировки и обрезки слоев, может быть полезен, например, для создания интерфейса;
  • Текст -- слой с текстом; будет поддерживать все необходимое для работы с форматированным текстом, включая переносы строк, прокрутку и обрезку.

Такой подход позволит оптимизировать отрисовку в зависимости от типа слоя и используемого рендерера. Для первой версии я также рассматриваю отдельный тайловый слой. С одной стороны, это упростит разработку тайловых игр (включая наши), с другой -- сделает движок сложнее (в том числе для портирования).

Поскольку наши игры в большинстве своем будут использовать пиксельную графику, нам потребуется и масштабирование. Использование слоев позволит сделать это прозрачно. Слои могут масштабироваться по-разному в зависимости от типа слоя.

Я скорее всего ограничу максимальное разрешение чем-то очень большим просто для надежности. Минимальное разрешение будет 320x200.

Для улучшения совместимости мы будем поддерживать как палитровые, так и полноцветные графические режимы. Мы позволим использовать как прозрачность по ключу (для всех режимов), так и по альфа-каналу (для полноцветных режимов).

Как я объяснил выше, мы жестко ограничим поддерживаемые форматы файлов. Для графики это будут PCX (сжатый и несжатый) и PNG (сжатый). Для наших игр мы, скорее всего, будем использовать PNG, однако несжатый PCX будет загружаться заметно быстрее на слабых компьютерах.

Звук

Поскольку мы расчитываем только на 2D игры, сложное аудио нам также ни к чему. Как бы круто ни было поддерживать всевозможные продвинутые вещи вроде 3D позиционирования или эффектов окружения, на первом этапе мы ограничимся поддержкой простого воспроизведения моно- и стереофонических сэмплов и музыки.

И вновь переносимость требует от нас определить поддерживаемые форматы:

  • несжатое PCM аудио с 8 или 16 разрядами глубины и частотой дискретизации 22050 или 44100Гц; формат контейнера -- только Wav;
  • сжатое Vorbis аудио с 8 или 16 разрядами глубины и частотой дискретизации 22050 или 44100Гц; формат контейнера -- только Ogg;
  • для музыки мы также рассматриваем MIDI и трекерные форматы.

Несжатые форматы намного менее требовательны к процессору и могут использоваться на медленных машинах; низкое качество звука позволит сэкономить дисковое пространство, а также снизить нагрузку перекодирования на медленных машинах.

Ввод

Здесь мы не можем позволить себе компромиссов. Нам потребуется поддержка всех основных способов ввода: клавиатура, мышь и игровые контроллеры. Поскольку поведение ввода сильно зависит от игры, мы не будем навязывать никаких переопределений управления на уровне движка. Вместо этого мы сделаем опциональную и настраиваемую библиотеку для этой цели. Разумеется, мы, прежде всего, оптимизируем эту библиотеку под наши цели.

Данные

Обычно игровые движки реализуют виртуальные файловые системы для прозрачной работы с файлами как из ресурсных архивов, так и из локальной файловой системы. Этот подход работует хорошо, однако имеет важный недостаток, связанный с тем, что пути и файловые системы в общем случае не переносимы: некоторые файловые системы чувствительны к регистру, другие нет; ограничения на длину имени файла, количество файлов в папке, допустимые символы и другие ограничения также зависят от файловой системы. Все это не только вынуждает разработчика постоянно держать в уме ограничения всех поддерживаемых систем, но и затрудняет портирование на новые системы (ведь их ограничения не будут учтены ранее написанными играми). Более того, это делает крайне простым написание игр, которые работают только на системах совместимых с используемой разработчиком.

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

Чтобы решить это потребуется:

  • найти или реализовать формат архива, подходящий как для чтения, так и для записи данных (или можно попробовать использовать для сохранения игр базу SQLite. Недостатком этого будет необходимость владения языком SQL для работы с сохранениями и настройками, а также дополнительная зависимость);
  • реализовать дополнительный API для выбора файлов из файловой системы. Это потребует некоторого платформо-зависимого UI, однако скорее всего реализация этой функции нам необходима;
  • реализовать отладочный режим, который позволит загружать файлы непосредственно из файловой системы (это необходимо для избежания перепаковки после каждого изменения).

Среда исполнения

Для создания по-настоящему платформо-независимой игры, она должна выполняться в виртуальной машине. Это может быть интерпретируемый язык, работающая с байт-кодом виртуальная машина или даже эмулятор настоящего процессора. Я сильно склоняюсь к использованию Lua для этой цели из-за его высокой переносимости и достойной производительности. Большинство прототипов движка также используют Lua. Однако есть несколько альтернатив, которые я также рассматриваю:

  • Lua -- главное преимущество -- это совместимость с INSTEAD, что упростит портирование моих старых игр.
  • WASM -- большой проблемой является множество реализаций и не стандартизированный инструментарий, а также необходимость компиляции. Переход на него имеет смысл только если производительность заметно выше, чем у Lua.
  • Pawn -- я почти ничего не знаю об этом языке, кроме того, что он компилируемый и разработан для встраиваемых систем. Переход на него, опять же, имеет смысл только если производительность заметно выше, чем у Lua.

Выбор среды исполнения крайне важен, поскольку мы в принципе не сможем изменить его. Это значит, что мне необходимо тщательно изучить преимущества и недостатки каждого варианта.

Целевые системы

Со всем вышесказанным, мы можем определить и поддерживаемые платформы. Реалистично, использование Lua [1] в качестве среды выполнения ограничит нас довольно быстрыми 32-разрядными системами. Разумеется, очень многое будет зависеть от самой игры. Воспроизведение Ogg Vorbis потребует примерно Pentium MMX. Полноцветная графика без ускорения потребует примерно того же (для сравнения, смотрите Дракона и Башню с INSTEAD-9x).

Для исходного релиза я собираюсь поддерживать:

  • Windows 95-ME (сборка для процессора 486 с FPU)
  • Windows 2000-11 (сборки только для x86, работающие на процессорах x86 и x86_64)
  • GNU/Linux (Appimage-сборки для i686 и amd64, все остальное потребует самостоятельной сборки)

Мы также рассматриваем (но скорее всего позже):

  • Windows NT < 5.0 (мы не будем ограничивать версию, однако мы можем использовать некоторые API требующие более новой ОС)
  • DOS (используя HX-DOS; оно может просто заработать, но я не уверен, что у меня будет время все оттестировать к первому выпуску)
  • OS/2
  • Atari TOS / MiNT (ARAnyM)
  • AmigaOS (68K с RTG и PowerPC; потребует довольно быструю машину) и AROS (только x86)
  • MacOS <= 9 (68K)

Разумеется, для некоторых платформ подойдут только более мощные варианты или эмуляторы.

Итог

Подведем итог:

  1. Я пишу игровой движок для 2D графики с послойной отрисовкой:
    • полностью программный рендерер для максимальной совместимости;
    • ускоренный рендерер (скорее всего основанный на OpenGL, однако я возможно реализую и специфичные рендереры для платформ, например DirectDraw для Windows);
    • я начну с SDL 1.2 из-за его хорошей поддержки платформ и, как только я реализую все необходимое, я возможно добавлю поддержку SDL 2 если это потребуется (у него есть несколько преимуществ, но очень ограниченная поддержка платформ, а также он несколько тяжелее в плане ресурсов чем 1.2).
  2. Для аудио я возьму SDLMixer с добавлением требуемых ограничений;
    • для MIDI скорее всего понадобится программный синтезатор, но мне еще предстоит найти подходящий; Это также потребует какого-то банка с подходящей лицензией и хорошим звучанием.
  3. Я найду или реализую формат архива, который будет использоваться для игровых данных; Он также может использоваться для пользовательских данных или я интегрирую SQLite для этой цели.
  4. Я рассмотрю несколько вариантов среды выполнения и выберу подходящий:
    • скорее всего я остановлюсь на Lua, однако не хотелось бы отметать другие опции пока я не убедился, что они не дают преимущества;
    • среда выполнения будет зафиксирована релизом 1.0; хотя оптимизации (собственные или перенесенные) возможны, точно не будет никаких изменений версии языка (разве что это сохранит 100% совместимость);
  5. Для API важны несколько вещей:
    • все что не поддерживается должно быть явно запрещено [2];
    • графический API должен обеспечить ожидания игры (с разумными отклонениями) или явно упасть;
    • звуковой API должен молча игнорировать ошибки и вести себя как будто все в порядке, даже если звук воспроизвести не получается [3];
    • звуковой API должен предоставить список поддерживаемых форматов аудио, чтобы игра могла отреагировать, если аудио является существенной частью игрового процесса;
    • API ввода должен предоставить хотя бы один из поддерживаемых способов ввода; Запуск движка должен завершиться с ошибкой если поддерживаемый способ ввода не найден;
      • я скорее всего сделаю библиотеку для удобного переключения и эмуляции различных способов ввода; мы не будем реализовывать это на уровне движка, поскольку это является значительной частью игрового опыта;
    • движок не будет предоставлять какой-либо пользовательский интерфейс, однако должен быть способ прозрачной настройки платформо-специфичных опций.
  6. Поведение релизной версии движка будет иметь приоритет над документацией [4].

Следующие шаги

Сейчас у меня есть только планы, набросок спецификации API и прототип реализации на SDL и Lua.

Следующим шагом я протестирую альтернативные среды исполнения и определюсь с API данных. После этого мы сможем определить окончательные спецификации API и реализовать их в достаточной мере для начала работы над игрой.

Мы откроем исходный код как только реализация перейдет в статус альфа, то есть:

  • спецификация API определяет всю необходимую функциональность;
  • API реализован в соответствии со спецификацией;
  • один из рендереров (скорее всего программный) полностью функционален, однако не обязательно оптимален;
  • мы скорее всего будем поддерживать только GNU/Linux и SDL 1.2 на этом этапе;
  • часть нашей новой игры (инструментарий, игровые механики и т.д.) реализована на движке.

Спасибо вам за прочтение, как всегда ждем ваших отзывов на почту. До новых встреч!

[1]Предполагая, что мы используем Lua. Я провел ряд тестов производительности и не собираюсь переключаться на что-то медленнее.
[2]Это необходимо для предотвращения завязки на "недокументированное поведение".
[3]Хотя это противоречит правилу "явно падать", в конкретно этом случае это оправдано требованием переносимости и совместимости -- в большинстве игр аудио -- скорее приятное дополнение, чем жесткая необходимость, поэтому лучше воспринимать его как опцию.
[4]Это решение основано на требовании совместимости. Любые игры, подразумевающие определенное поведение будут его ожидать, поэтому нам не стоит его менять. Разумеется это не касается очевидных ошибок реализации (падения, некорретная отрисовка и т.д.).