Создание небольшой игры с помощью tengine

  • Tutorial
Совсем недавно я опубликовал пост о моем небольшом проекте tengine. Для меня довольно неожиданным было то, что многие проявили интерес к его идеям и наработкам. А раз есть интерес – это повод продолжить публикации.

Предлагаемая вам статья является туториалом по созданию небольшой игры. Необходимость в нем очевидна: идеологически, создать что-либо в tengine без редактора уровней (MapEditor3, далее me3) фактически невозможно. Именно с помощью него ресурсы привязываются к объектам игры, генерируются файлы с константами-идентификаторами, создается сцена, звуковые схемы и т.д. И самое главное – me3 генерирует готовые бинарные файлы уровней, необходимые для работы логики tengine: файл с общими данными (o-файл), файлы с данными по каждому уровню (l[0..n]-файлы) и файлы с текстовыми данными (t[0..n]-файлы)



Структура папок проекта:


Для начала создадим папку space_invaders и структуру папок внутри нее:
  • _win32
    В этой папке находятся файлы проекта для MS VS
  • assets
    В этой папке хранятся подготовленные для tengine ресурсы: сконвертированные утилитой bmpcvtr.exe графические ресурсы, сгенерированные me3 данные и звуковые (*.wav) файлы
  • game
    В этой папке находятся исходный код нашей игры
  • mapeditor
    В этой папке находится файл проекта me3. Для удобства работы, на время создание игры, в эту папку переносится файл MapEditor3.exe из «tengine\tools\editor»
  • rawres
    В этой папке находятся не сконвертированные графические ресурсы (*.bmp или *.png), звуки, файлы шрифтов т.д.


Начало работы с me3:


После запуска me3, следует настроить пути для работы с ресурсами: «Файл->Настройка путей» и устанавливаем путь к папке «rawres». Затем переходим в «Файл->Настройки редактора» и устанавливаем глобальные настройки: тип графических ресурсов. В текущей версии me3 есть возможность работы только c bmp или png форматами. Если bpp < 32, цвет 0xff00ff считается прозрачным, если bpp == 32, за прозрачность отвечает альфа-канал. Установим тип ресурсов в png.

После установки глобальных настроек можно приступать к созданию сцены: «Файл->Новый проект». Теперь создаем первую сцену (карту): «Проект->Создать карту». Появляется окно с установками новой карты:
  • Размеры экрана (в единицах фрагментов карты)
    Здесь мы настраиваем размеры экрана. Величина эта абстрактная: для скроллера величина экрана большой роли не играет, но для игр-одноэкранок это важный параметр. Идея разделить карту на количество экранов была внедрена после запроса дизайнера, ему так было проще ориентироваться, создавая области игрового мира. Экраны в редакторе разделяются сеткой, но ее можно отключить («Вид->Сетка»). Фрагмент карты по умолчанию равняется 8х8 пикселей. Устанавливаем высоту 38 и ширину 50, пусть у нас игровой экран будет 800х600.
  • Размеры карты (в экранах)
    Игра у нас планируется на один экран, но я хочу показать для демонстрации еще и функционал скроллинга карты, звезды заднего фона будут двигаться, посему карту устанавливаем: 2 экрана в высоту и один в ширину.
  • Количество зон
    Эта настройка используется для больших карт, у которых можно включать-выключать игровые зоны. Что это такое я описал в документации tengine. У нас зоны использоваться не будут, вернее будет только одна зона.

Уровень 0 создан, но я хочу остановиться на одном важном моменте. Пока карта пустая, нужно задуматься – нужны ли нам такие мелкие фрагменты карты (8х8), при таком большом экране. Скорей всего нет. Потому упростим работу рендеру и увеличим их размеры, тем самым уменьшив их количество. Для этого перейдем в меню «Фон Игры->Настройка элементов фона», установим новую ширину и высоту фрагмента в 32 пикселя, ставим галочку на «Сохранить размер карты…» и жмем «Применить» (согласен, немного неудобно и не интуитивно). Проверить, все ли нормально с размерами можно, посмотрев настройки карты: «Проект->Настройка карты».

Немного дополнительно информации о картах (сценах): tengine умеет загружать несколько карт и рисовать их одновременно (один поверх второго) на указанную плоскость отрисовки (render plane). Загруженные карты в tengine называются логическими слоями (layers). Архитектурно поддерживается до 4 независимых плоскостей отрисовки (на каждой платформе реализация плоскости отрисовки своя). На одну плоскость отрисовки можно привязать теоретически бесконечное количество логических слоев, но только один из них может быть основным (primary layer) и может отрисовывать тайлы фона, иметь объекты с подгружаемой анимацией, проигрывать музыкальные схемы (вызовы описанных функционалов в неосновных слоях либо игнорируются, или выдают assert-ошибку). Кроме этих особенностей, логические слои никак не пересекаются логикой между собою. Пример использования нескольких слоев: слой игрового меню, слой игрового интерфейса и слой самой игры делает физически удобное разделение подсистем на независимые модули.

Работа с фоном


Для начала создадим фон. Для этого нужно перейти в режим фона: нажимаем F2 (или иконку на панели с изображением фона). Появляется окно-палитра объектов фона. Ассоциировать графикой пустые ячейки палитры можно несколькими способами: поэлементно (навести курсор на нужный элемент и нажать «Enter» или дважды кликнуть мышью) или функцией «разрезать готовую картинку». Воспользуемся более легким способом: ставим курсор на любой элемент палитры, который будет являться первым элементом разрезанной картинки, выбираем слой 0 миксера (Активный слой) и нажимаем иконку с ножницами. В появившемся меню выбираем нужную картинку и нажимаем «Применить». Как результат – видим группу ячеек, ассоциированных с графическим ресурсом.

Немного о слоях миксера. Фон может состоять из множества слоев, для этого нужно установить нужное количество слоев в настройках фона: «Фон игры->Настройка элементов фона». Слои рисуются в порядке возрастания, или, другими словами: слой 1 перекрывает слой 0. Таким образом, можно добиться более интересных комбинаций тайловой карты. Установка «Активный слой: все» рисует все слои, показывая, как они будут рисоваться на карте.

Итак, палитра объектов фона готова. Переносятся на карту они очень просто: выделяем мышкой нужную область объектов, копируем (ctrl+c), ставим курсор в нужное место карты и вставляем (ctrl+v). Более того, операция копирования-вставки работает и на самой палитре, вы можете копировать и вставлять объекты в любое место палитры, вы можете копировать и вставлять тайлы фона в любом месте карты. Есть еще одна полезная функция «размножить» (или «замостить»): копируем некую область объектов фона, выделяем другую область карты (или всю карту ctlr+a) и нажимаем ctrl+alt+v, как результат – вся выделенная область будет заполнена повторяющимся фрагментом скопированной области.

Немного дополнительной информации об элементах фона: объекту фона палитры можно задать некоторые свойства. Для этого нужно создать типы свойств фона в менеджере свойств фона (меню «Фон игры->Типы свойств фона»). В свойствах можно задать логическую «проходимость» элементов, а так же присвоить некий цифровой дополнительный параметр, который можно будет использовать в игровой логике. После создания типов свойств, в палитре объектов фона можно назначить свойства, нажав «Enter» или дважды кликнуть мышью по выделенному объекту и в появившемся окошке выбрать нужный тип (выпадающий список «Тип фрагмента»). С этого момента все тайлы карты этого типа будут иметь указанные свойства (проверяется нажатием ctrl+p на выделенном тайле под курсором мышки).

Единственное условие всех этих манипуляций: режим фона должен быть активный (иконка «Режим фона» в состоянии «нажата» на панели инструментов).

Создание звуковой схемы


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

Сначала переключаемся в режим объектов: нажимаем F1 (или иконку на панели с изображением объектов). В нижнем окошке палитры объектов переключаемся на вкладку «Системные», хватаем объект «SOUND SCHEME» и бросаем в любое место карты. Затем заходим в свойства объекта на карте (ctrl+p или правой кнопкой мышки вызвать контекстное меню объекта и выбрать «Свойства»). В появившемся окне добавляем звуковые файлы, даем им имена-идентификаторы и устанавливаем им параметры SFX (эффект) или BGM (фоновая музыка).

Создание игровых объектов


Перед созданием игровых объектов нужно создать список типов анимации и состояний. Для начала запускаем менеджер типов анимации («Проект->Типы анимации») и добавим два типа, которых нам должно хватить для нашей небольшой игры: «ANIM_ACTIVE» и» ANIM_DAMAGED». Теперь создадим типы состояния объектов. Запускаем менеджер типов состояний («Проект->Состояния») и добавляем два состояния: «STATE_ACTIVE» (на которое завязываем анимацию «ANIM_ACTIVE») и «STATE_DAMAGED» (завязываем анимацию «ANIM_DAMAGED»).

Немного дополнительной информации о состояниях: вся логика игровых объектов завернута вокруг состояний. Именно состояния задают объекту логическое (логическое, так как названия «вверх», «вниз», «вправо» и «влево» введены только для разыменования четырех вариантов направлений) направление движения, особенно, когда объект прикреплен к путевым нодам, а также задают имя анимации, которое сейчас нужно проигрывать.

Вторыми по важности параметрами после состояний для объекта являются свойства. Они создаются в менеджере свойств в виде шаблона («Проект->шаблон свойств»). Добавим два важных свойства: «PRP_ENABLE» и «PRP_SPEED». Свойств объекту можно придумать сколько угодно, но только на эти два tengine реагирует внутренней логикой. Если «PRP_ENABLE» не равняется 1, объект не рисуется и не участвует в логике игрового процесса. Свойство «PRP_SPEED» задает скорость перемещения по путевым нодам. Если эти свойства не созданы, считается, что «PRP_ENABLE» == 1, «PRP_SPEED» == 0.

Также создадим одно событие, оно нам потребуется, для определения окончания анимации смерти космического пришельца, чтобы знать момент, когда можно убирать его из сцены. Для этого запускаем менеджер событий («Проект->Типы событий») и создаем тип события «EVENT_END_ANIM_DAMAGE_INVADER».

Важно знать: добавить или убрать типы анимации, состояний, событий или свойств можно в любое время на любой стадии разработки проекта.
Вот теперь можно создать наш первый тип игрового объекта. Сначала переключаемся в режим объектов: нажимаем F1 (или иконку на панели с изображением объектов). В нижнем окошке палитры объектов переключаемся на любую вкладку «Общие» (ее можно переименовать). Правой кнопкой мыши в окне вызываем контекстное меню и выбираем пункт «Добавить объект». Пусть первый объект у нас будет космическим пришельцем.

Окно создания игрового объекта состоит из двух основных вкладок: «Общие» и «Редактор анимации».
Вкладка «Общие» содержит настройки:
  • Поле ввода имени объекта
  • Будет ли объект декорацией. Если да, то какого типа: переднего плана (рисуется всегда перед игровыми объектами игры) или заднего плана (рисуется всегда за объектами игры). Декорации не имеют свойств и не принимают участие в игровой логике.
  • Использовать ли графику для этого объекта. Иногда объект нужен только для некоторой логики игрового процесса (например, только для колижн-области) и не нуждается в графическом отображении
  • Игнорировать ли графические ресурсы, используемые объектом, если на карте нет ни одного экземпляра данного типа объектов. По умолчанию, при генерации данных карты, включена оптимизация, которая добавляет в список загружаемых ресурсов только ресурсы тех объектов, экземпляры которых находятся на карте. Данная опция позволяет отключить оптимизацию для определенной группы объектов.
  • Редактирование значений свойств по умолчанию объектов этого типа. Каждый экземпляр объекта этого типа при установке на карту получает значения, установлены в этом шаблоне. Важно: изменение значений свойств по умолчанию не влияет на свойства объектов-экземпляров данного типа уже поставленных на карту, для принудительного изменения значений есть дополнительный функционал в выпадающем списке контекстного меню палитры объектов.
  • Состояние по умолчанию устанавливает состояние, которое устанавливается экземпляру объекта, поставленному на карту.

Назовем объект SPACE_INVADER, установим ему состояние по умолчанию STATE_ACTIVE и перейдем во вторую вкладку, где сможем создать анимацию.

Вкладка «Редактор анимации». Самое первое, что нужно сделать – это выбрать из списка нужное имя анимации, и создать первый кадр. Выбираем анимацию «ANIM_ACTIVE», спускаемся чуть ниже поля выбора анимации к окошку кадров, правой кнопкой мыши вызываем контекстное меню и выбираем пункт «Добавить пустой кадр». Мы создали первый кадр анимации. Теперь нужно нарезать графические элементы для графической составляющей кадра. Справа от пустого кадра мы видим вспомогательное окно. Убеждаемся, что в этом окне активная вкладка «Графика» содержит активную подвкладку «Стандартная». Правой кнопкой мыши по пока еще пустому окну-палитре графических элементов вызываем контекстное меню и выбираем «Редактировать». Появилось окно редактирования графического изображения объекта. Здесь создаются (вырезаются) графические элементы, из которых будут создаваться кадры анимации. Для начала создадим новую группу, в которую мы будем собирать элементы пришельцев. Для этого, правой кнопкой мыши по окну групп графических элементов (самое левое окошко с надписью «Общие») вызываем контекстное меню и выбираем «Создать группу». Называем группу «INVADERS». Теперь делаем активной эту группу (левая кнопка мыши) и правой кнопкой мыши вызываем в группе контекстное меню, в котором выбираем «Добавить» (или нажимаем кнопку Insert). В группе появилась пустая ячейка. Делаем ее активной. Переходим в окно «Файл-источник» и выбираем файл «characters.png». После этого, в появившейся картинке выделяем область изображения пришельца первой фазы анимации (для удобства, используйте клавиши-стрелки для перемещения выделенной области, а также комбинацию ctlr+стрелки для изменения размеров выделенной области, также используйте ползунки для изменения масштаба изображения). Добавляем еще один кадр в группу «INVADERS» и выделяем область изображения пришельца второй фазы анимации. Нажимаем «Применить». Мы снова в редакторе анимации. Теперь самое время создать кадры анимации пришельца. Выделяем в палитре графических элементов нужное изображение пришельца, ставим мышку на поле кадра, вызываем правой кнопкой контекстное меню и выбираем пункт «Поставить». По аналогии с первым кадром, добавляем еще один пустой кадр и ставим второй кадр пришельца. Давайте посмотрим, что у нас получилось. Нажимаем «Старт» проигрывателя и смотрим как работает наша первая анимация. Слишком быстро меняются кадры? Выставим время для каждого кадра. Нажимаем «Стоп». Переключаемся мышкой в окошке кадров по каждому кадру и выставляем в поле «Время кадра» группы «Текущий кадр» в 200 (ms). Проверяем снова. Теперь анимация работает как надо.

Пришло время добавить в кадр немного логической составляющей: коллижн-область (зона столкновения). Она будет нужна для определения зоны попадания пули в пришельца. Важно знать, что в tengine графика объекта не принимает участие в логике вычислений столкновений или зон «видимости». Для этих нужд введены два логических параметра (эти понятия условны, так как tengine никак не оперирует с этими зонами):
  • Рабочая зона объекта. Предполагается, что «рабочая зона объекта» будет служить в игровом процессе как зона столкновений, размеры этой области задаются в каждом кадре анимации отдельно.
  • Зона видимости объекта. Предполагается, что «зона видимости объекта» принимает участие в логике взаимодействий объектов или является размерами кадра анимации. Размеры этой зоны один для всех кадров анимации.

Есть еще один способ определения зоны столкновения: использование дополнительной растровой карты «столкновения». Подробнее о работе с этим функционалом описано в документации (prHasAlphaCollideMap() и prGetAlphaCollideMapValue() функции).

Выбираем первый кадр анимации. Переключаем вкладку вспомогательного окна с «Графика» на «Логика». Для удобства работы, изображение графики исчезает. В данном случае нам нужно видеть изображение, чтобы обрисовать его колижн-зоной. Ставим галочку на опции «Графика и логика» (нижняя часть окна создания анимации). В вспомогательном окне ставим галочку на свойствах «рабочая область объекта», активируя тем самым ее редактирование. Вводим параметры W = 40, H = 30, потом хватаем мышкой получившийся прямоугольник и ставим его так, чтобы «пришелец» оказался внутри него (для удобства перемещения объектов можно использовать ctrl+стрелки). Копируем выделенную область (если выделение пропало, кликаем мышкой по краю прямоугольника, тем самым выделяя его) с помощью комбинации клавиш ctrl+c, переключаемся на второй кадр и вставляем (ctrl+v) колижн-зону и для этого кадра. Таким образом мы получаем анимацию космического пришельца с колиж-зоной в каждом кадре. Осталась одна мелочь: создать иконку для палитры выбора объектов. Выбираем первый кадр, вызываем контекстное меню этого кадра и выбираем опцию «Создать иконку». Вот теперь все, можно нажимать «Применить». В палитре объектов появился наш первый объект.

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

Для упрощения работы с объектами в редакторе есть менеджер объектов (ctrl+o). В нем удобнее следить за количеством объектов, менять их позицию в списке отрисовки, свойства и т.д.

По аналогии создаем для пришельца анимацию смерти («ANIM_DAMAGED»). Единственное, на последнем кадре анимации в поле «Событие» выбираем из выпадающего списка значение «EVENT_END_ANIM_DAMAGE_INVADER». Также создаем объекты пушку «TURRET», базу «BASE» и ракету «MISSILE». Ставим их на карту. Причем ракет на карту ставим несколько, устанавливаем им свойства PRP_ENABLE = 0, нам они понадобятся для пула.

Немного о пулах: идеология tengine запрещает порождать объекты. Поэтому все объекты, которые планируется «создавать» в процессе игры должны находиться на карте в достаточном количестве и иметь свойство PRP_ENABLE = 0. Также пишется дополнительная логика в логике игры для использования этого пула.

Текстовые поля и создание шрифтов


Немного дополнительной информации о работе с текстами в ме3:
  • Поддержка до 8 языков. Установить, сколько языков будет использоваться в приложении можно в вкладке настройках «Файл->Настройка редактора->вкладка Текстовый редактор». Здесь можно переименовать языки («LNG_0..n») в более удобные названия и отметить галочками какие языки принимают участие в текущем проекте. Ширина и высота выводимой области – размеры окна текстового окна по умолчанию
  • Поддержка шрифтов (о создании шрифтом будет рассказано немного позже)
  • Отдельная подсистема-редактор для создания текстовых сообщений

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

Создадим еще одну карту. Мы будем использовать ее для вывода текстовых сообщений. Это будет логический слой игрового меню. «Проект->Создать карту», ставим размеры 25х19 (800х608), размеры карты – 1х1 экран. Пускай в нашей игре будут доступны два языка: русский и английский. Переходим в «Файл->Настройки редактора», выбираем вкладку «Текстовый редактор». Нажимаем на кнопку «Редактировать» под окошком доступных языков и в появившемся окошке меняем язык «DEFAULT» на «LNG_ENG», «LNG_1» на «LNG_RUS». Тут я обнаружил досадную ошибку редактора, изначально в появившемся окошке менеджера нет выбора языков. Нужно ввести в окошке поиска любую букву и удалить ее, только тогда можно будет увидеть список. Это будет исправлено в следующей версии me3. После этого нажимаем «Применить» и отмечаем галочками наши языки «LNG_ENG» и «LNG_RUS». Снова нажимаем «Применить». Теперь нужно проверить, есть ли в проекте доступные для использования шрифты. Если в папке «rawres» нет файлов с расширением «*.fnt» — нужно создать шрифт.

Немного о шрифтах: все шрифты для работы с tengine и me3 создаются отдельной утилитой «Bitmap font generator». Она была выбрана ввиду своей бесплатности и легкости в освоении, находится в папке «tengine\tools\BMFont». Подробнее об утилите описано на сайте www.angelcode.com/products/bmfont, но вкратце, создание шрифта состоит из нескольких шагов:
  • «Options->Font settings», выбрать нужный шрифт, определить набор символов: ANSI или Unicode, установить размер. Больше никаких значений в окне настроек не менять.
  • В основном окне приложения нужно отметить мышкой символы, которые будут участвовать в конечном шрифте (не забудьте символ «пробел»). Если в настройках был выбран Unicode, используйте переключение между наборов символов в правом окне. Помните, чем больше символом – тем больше будет финальный атлас с растровыми изображениями символов.
  • Зайти в настройки и установить размер генерируемой текстуры, битность, предустановку альфа канала и тип данных формата fnt. Значения «Spacing» для большей оптимизации можно установить в 0, 0. Размер текстуры должен быть достаточно большой: если символы не помещаются в одну текстуру, их генерируется несколько. В свою очередь, tengine и me3 умеют работать только с одной текстурой на один шрифт, потому нужно следить за тем, чтобы все символы поместились в одну текстуру, если при генерации создается более чем одна текстура, нужно увеличить размеры. Проверить, помещаются ли символы в текстуру можно с помощью «Options->Visualize». Битность глубины цвета можно использовать как 8, так и 32. В случае 8 бит, нужно будет в любом графическом редакторе залить все пространство, которое должно быть прозрачным 0xff00ff цветом. В случае 32 битности, нужно выбрать в предустановках («Presets») установку «Outlined text with alpha». Формат данных нужно установить только в «Binary». Тип текстуры — png (внимание: если в настройках me3 выбран формат графических ресурсов «BMP», следует сконвертировать текстуру в bmp формат (32bpp) любым графическим конвертером)
  • Генерация шрифта происходит вызовом «Options->Save bitmap font as..». После генерации в указанной папке должно появиться два файла: имя.fnt и имя.png.
  • Опционально, для уменьшения размера графического ресурса, можно открыть сгенерированную текстуру в любом графическом редакторе и удалить пустое пространство.

Создадим текстовое сообщение «Игра окончена»: «Проект->Текстовый редактор», переключаемся на вкладку «LNG_ENG», нажимаем «Создать». Называем этот текст «TXT_GAMEOVER», устанавливаем нужный шрифт, пишем текст «GAME OVER», смотрим, как текст будет выглядеть в визуализаторе. Тут же, в редакторе, можно задать выравнивание, установить цвет и размер канвы для текущего сообщения. Закрываем окно текстового редактора, переключаемся на вкладку «LNG_RUS», видим, что тут текста еще нет, нажимаем «Редактировать», пишем «ИГРА ОКОНЧЕНА». Таким образом, мы локализовали сообщение «TXT_GAMEOVER» на два языка.

Теперь создаем текстовый объект на карте. Переходим на вкладку «Системные» палитры объектов, ставим на карту объект TEXTBOX. Это будет текстовое поле для статического текста TXT_GAMEOVER. Заходим в свойства этого объекта, выбираем «Статический текст», называем его TXTBOX_GAMEOVER, нажимаем кнопку «…», выбираем нужный текст и нажимаем «Применить». Объект наследует цвет и размеры канвы текстового сообщения, но его можно изменить индивидуально для каждого объекта предлагаемыми настройками.

Создадим еще один текстовый объект для вывода fps. Заходим в его свойства, даем ему имя TXTBOX_FPS, устанавливаем пункт «динамический текст», «строк» устанавливаем в 1, «букв на строку» — 10. Шрифт устанавливаем «system», далее устанавливаем цвет фона и размеры выводимой для текса области: 64х18

Изменить текущий язык можно в «Вид->Текущий язык».

Генерация карт игры и констант для использования в tengine


Мы создали все, что нам понадобится в игре. Пришло время генерации карт и констант для tengine. Для начала, можно в «Проект->Информация о карте» посмотреть по каждой карте какие ресурсы используются и в каком количестве. Также, тут можно узнать предупреждения о потенциальных проблемах. После этого нужно сгенерировать *.h файл с константами. Для этого запускаем «Проект->Список переменных» и сохраняем константы в папку «game» как «constants.h». После этого запускаем «Файл->Сохранить карту(ы)» и указываем папку «assets\data». Теперь все готово для работы с tengine, осталось только сконвертировать ресурсы.

Конвертация ресурсов


Создадим в корневом каталоге нашего проекта пустой файл «make_res_bmp.bat» с таким содержимым:

for %%i in (rawres\*.png) do ..\..\tools\bmpcvtr.exe %%i -5551 -2n -oassets/data
copy rawres\arial_28.fnt assets\data\
copy rawres\system.fnt assets\data\
copy rawres\*.wav assets\data\
pause

Мы конвертим все *.png файлы в формат r5g5b5a1, автоматически устанавливаем все размеры текстурам, кратные 2^n и копируем их в «assets/data» папку, затем копируем файлы шрифтов и звуков в ту же папку. Обратите внимание на то, что в 32bpp ресурсе (arial_28_0.png) при конвертации в формат 5551 создалась дополнительные данные с информацией о альфа-канале, таким образом, даже при 5551 формате полупрозрачность не теряется. Подробнее о параметрах утилиты «bmpcvtr.exe» можно узнать, запустив ее без входных параметров. Запускаем файл «make_res_bmp.bat».

Написание кода игры


Теперь осталось только написать логику игры и скомпилить проект. Смотрите исходники, дополнительная информация находится в «tengine\src\tengine\manual»

Все исходники данной статьи лежат в репозитории tengine tengine/samples/space_invaders

Бинарник готовой игры space_invaders_demo1.zip
Share post

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 21

    +3
    Плюсанул, конечно, но документация фреймворка (да и статья) плохо структурирована. Из-за этого вряд ли кто-то захочет повторить успех.
      0
      Критика принимается.
      Никогда не писал туториалы, потому получилось в виде «на что смотрю — то и пою». Документация проектика — вообще больная тема, времени зачастую хватает только дыры латать. Этот пост — более логическое продолжение первого, чтобы была хоть какая логическая завершенность: описание вообще о проекте и небольшой рассказ в стиле «оно еще и работает».
      +4
      Диаграммы? Скрины? Архитектура? Что прошлый пост грешил этим, что этот.
      Вместо всего этого текста аля «Файл->Новый проект...» лучше предоставить скрины, на которых отмечено будет, какие шаги делать)

      Ну и да, показали бы хоть скрины получившейся игры или демо видео.
        +2
        … а картинки? :(
          0
          Как-то об этом моменте я не подумал.
          Критика принимается.

          Обязательно исправлюсь в следующих публикациях.
          Не судите строго — для меня еще все ново тут, учимся, набиваем шишки…
            +1
            Не-не, все хорошо!
            Но хотелось бы иллюстраций.

            Всегда любил книжки с картинками :)
          0
          33 FPS… Рыдаю…
          Нет правда обидно, у меня карточка конечно не ахти, GT220, но 2д инвайдерсы… 33 фпс…
          Пора делать апгрейд… а то скоро и поиграть то будет невочто…
            0
            Люобознательный читатель посморел бы в настройки проекта и понял бы, что там стоит ограничение в 30fps. Это очень полезная настройка для игровой логики, даже если учитывать, что все завязываем на время между тиками.

            В мире существуют два подхода для работы с частотой обновления логики игрового мира и скорости отрисовки кадров, посылаемых с конвеера. Один из них как раз основан на так называемой «жесткой fps». Вот именно его я и использую, так как, с моей точки зрения, в данном проекте это целесообразно.
              0
              Любознательный читатель не видит прямой связи с ограничением 30 фпс и рабочей частотой 33 фпс. Но так как вы на это указали, любознательный читатель сообщит вам, о том что у вас неверно работает таймер.
                0
                Тут все же осмелюсь утверждать, что все относительно. Мне важно знать сколько кадров в секунду отрабатывает логика, а не отрисоовывает видеокарта. Последнее мне куда менее важнее при разработке логики игры. Тут сразу поправлю себя: мы полагаем. что движок написан хорошо и оптимизирован (конечно, в реальности все куда хуже, но сейчас не об этом). По этому нас интересует именно работа той части, которую ми пишем, собственно логику игры.
                  0
                  И я как сторонний наблюдатель, отмечаю, что и кадры и логика работают не на 30 заявленных фпс, а на 33, что указывает, что у вас неверно работает таймер и возвращает наш разговор к моему предыдущему посту =) Только вот число ошибок нарастает. Теперь еще и логика.

                    0
                    Раз уж мы следуем букве, в настройках установленно 30мс на кадр. 1000/30 = 33.33… Почему именно так? Это исторически сложилось, геймдизейнер понимает куда более «30 миллисекунд длится один кадр, заказывай художникам анимацию, опираясь на эту цифру». А потом и я привык. Я еще с самого начала говорил, что идеология всего этого проекта немного не стандартная.
                      0
                      Да я вижу, что нестандартная =)
                      Хорошо допустим вы используете целочисленный вычисления для расчет фпс.
                      1000 / 30 = 33
                      откинем дробную часть и посмотри какова будет погрешность
                      33*30 = 990
                      как мы видим 10 миллисекунд (1000-990), что никак не 3 кадра суммарная продолжительность которых составляет 90 миллисекунд.
                      Итого: 3:0 в мою пользу, у вас неверно работает таймер, из за этого неверно (асинхронно) работает логика, вы не умеет считать…
                        0
                        Возможно вы правы в своей арифметике. Я поступаю проще:

                        Есть функция, она вызывается вызывается постоянно из движка с дельтой времени.
                        void game_step(s32 ms)
                        {
                        fps_timer += ms;
                        }

                        Есть вторая функция, она вызывается калбэком по истечении указанного дизайнером времени (30мс)
                        void onUpdateObject(u32 layer, s32 iId, BOOL* const oDraw)
                        {
                        fps_tick++;
                        if(fps_timer >= 1000)
                        {
                        // вот тут у нас получается ~33fps
                        fps_tick = fps_timer = 0;
                        }
                        }
                          0
                          Не, мало кода.
                          Непонятно как исчисляется ваша дельта и откуда она берется.
                          Предположу, что это время которое было затрачено на выполнение итерации программного цикла.
                          Вторая фция какая-то обособленная и вообще выглядит так будто висит на отдельном таймере. Тогда непонятно зачем мы считаем fps_timer если гарантированно знаем, что onUpdateObject сработает раз в 30 мс…

                          вообще fps надо считать както так

                          #include

                          fps_timer = clock()+1000;
                          fps = 0;
                          while(не вышли){

                          … логика
                          … рисуем кадр

                          fps++;
                          if (fps_timer
                            0
                            ms из функции game_step() вычисляется, измеряя длительность кадра. В этом кадре происходит логика игры и логика движка, тут же вызываются калбеки типа onUpdateObject, все это в одном потоке. Длительность кадра я не могу предсказать, потому я собираю дельты в логике игры, ожидая, пока накопися секунда (>=1000ms). Функция onUpdateObject вызывается перед тем как поместить данные отрисовки в конвеер, потому считается, что новый кадр наступил (пока она не вызывается, рендер рисует буфер последнего кадра, с какой частотой он его рисует мне не очень интересно на этом уровне разработки игры). Поэтому целесообразно посчитать количество вызовов onUpdateObject за секунду, чтобы узнать, нужный мне fps
                              0
                              там немного недопостилось

                              вообще fps надо считать както так

                              #include

                              fps_timer = clock()+1000;
                              fps = 0;
                              while(не вышли){

                              … логика
                              … рисуем кадр

                              fps++;
                              if (fps_timer<clock()) {
                              вывод fps
                              fps = 0;
                              fps_timer = clock()+1000;
                              }
                              }

                              В отличии от вашего подхода это удалит одно сложение на кадр в работе вашего движка. Если же вам хочется лимитов привязанных к таймеру то можно както так сделать

                              #include

                              fps_timer = clock()+1000;
                              fps = 0;
                              limit = 1000/30;
                              limit_timer = clock() + limit;
                              while(не вышли){
                              … возможно какието системные действия
                              if (limit_timer<clock()
                              … логика
                              … рисуем кадр
                              fps++;
                              limit_timer = clock()+limit;
                              }
                              if (fps_timer<clock()) {
                              вывод fps
                              fps = 0;
                              fps_timer = clock()+1000;
                              }
                              }
                          0
                          как мы видим 10 миллисекунд (1000-990), что никак не 3 кадра суммарная продолжительность которых составляет 90 миллисекунд.

                          Вы какую-то глупость говорите. Отняли, поделили, где взялся рубль?
                          Кадр — 30 мс. Значит в секунде 33 кадра (990 мс), а каждую третью секунду ещё влезает 34-й. 100 кадров за 3 секунды. Если поставить. Время на кадр 33.3 секунды, а не 30, то будет 30 fps.
                          Счёт тут ни при чём. Вы просто изначально отстаивали ошибочный вариант.
              0
              Блин, ни одно картинки :(
                0
                Ждал видео в конце, после просмотра которого появится стимул осилить весь текст)
                  0
                  Может это хоть чуточку порадует: игра Grandmaster
                  Кодилось все на Unity3d, но уровни и все, что с ними связано, создавались на me3, описанном выше

                Only users with full accounts can post comments. Log in, please.