Устройство движка для квестов Trickster Games

Всем привет! В результате недавних трудов у меня появились исходники движка для квестов компании Trickster Games. Выкладывать я их не буду, потому что не знаю насколько он лицензионно чистый. Я не юрист, в таких вопросах не разбираюсь. Поэтому расскажу своими словами — как устроен движок для квестов Trickster Games.

Поколений движков было два — первый использовался для игр Танита. Морское Приключение, Приключения Барона Мюнхгаузена на Луне и использовал lua в качестве скриптового языка.

Танита. Морское приключение и Приключения Барона Мюнхгаузена на Луне

Начиная с игры «Петрович и все, все, все» движок переходит на использование Python 2.xx вместо lua. Я пришёл в Trickster Games, когда начали делать Петрович, так что про предыдущие движки и игры практически ничего не знаю.

Итак, приступим!

Описание редактора игрового движка Trickster Games

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

Итак, у нас есть редактор (это первый уровень игры «Петрович и все все все»):

Редактор Trickster Games

Это тот же самый движок, но запущенный в специальном режиме.
Слева у нас окно дерева объектов, снизу свойства объекта, а справа самый большой экран — экран предпросмотра уровня. Фон заполняется жёлтым цветом, чтобы на сцене сразу были видны пустые места. В режиме игры, фон заполняется чёрным цветом, так что это не бросается в глаза в обычном режиме.

Почти все объекты наследуются от ItemBase и рисуются относительно координат родителя. При перемещении родительского элемента на сцене, его дети перемещаются вместе с ним. В дереве объектов элементы можно перетаскивать с помощью drag-n-drop или добавлять новые правой кнопкой мыши. У каждого элемента есть замочек и глазик — блокировать перемещение/выделение при клике на сцену и показать/спрятать элемент при отрисовке сцены. Отрисовка элементов идёт по дереву от корневого узла вниз.

Уровень (Location)

Собственно уровень игры, рутовый элемент нашей сцены, содержит параметры width, height — ширина локации, высота локации. Вот её панель свойств:

Свойства локации в игровом редакторе Trickster Games

Может содержать в качестве детей только слои.

Слой (Layer)

Слой локации, имеет параметр parallax — это отставание при прокрутке. С помощью него создаётся эффект глубины при скроллинге локации по горизонтали или вертикали. Вот её панель свойств:
Свойства слоя в игровом редакторе Trickster Games Может содержать в качестве детей статические изображения, регионы, пути, анимированные объекты и точки.

Статическое изображение (LayerImage)

Фоновое изображение, ничего не делает, просто рисуется с соответствующим свойствами (параллакс) слоя. Имеет довольно скудный набор свойств:

Свойства статического изображения в игровом редакторе Trickster Games

Анимированный объект (AnimatedObject)

Можно сказать, что это наш главный элемент, базовый кирпичик нашего движка. У него есть позиция, набор состояний, к нему привязываются звуки, регионы, а также скрипт.

Свойства игрового объекта в игровом редакторе Trickster Games

Он может наследоваться от особенных классов, например, Playable Character — это наш главный герой, который умеет ходить по локации, или Takeable Item — это вещь, которую можно забрать себе в инвентарь. Он может загружаться согласно определённым условиям из скрипта, если поставить флажок «Conditional object loading». Пример условной загрузки элемента «Item_parfume» (условия описываются в родительском элементе):

class FloorLayer:
    def should_load_Item_parfume(self):
        return not world.parfume_taken

Также у него есть pivot — начало его координат и позиция на локации абсолютная и относительная. К нему могут привязываться дочерние AnimatedObject, Region и StateSequence. Содержит список состояний. Представляет собой конечный автомат, который меняет своё состояние в зависимости от внешних условий. Может загружать дочерние элементы типа AnimatedObject по некоторым условиям.

Состояние объекта (AnimatedSequence)

Анимация и состояние AnimatedObject. Анимация неотделима от состояния объекта. Пока AnimatedObject находится в определённом состоянии, проигрывается анимация этого состояния из одного или нескольких кадров. К любому кадру можно привязать автоматическое проигрывание звука.
Свойства состояния объекта в игровом редакторе Trickster Games

У состояния есть свойства on_enter, on_exit, link, on_update — для привязки пользовательских функций. on_enter выполнятся при переходе в это состояние, on_exit — при выходе из него, link — выполняется каждый кадр, и если возвращает строку, то состояние меняется на указанное, on_update вызывается каждым кадр с неким dt (количество миллисекунд между кадрами).

Регион (Region)

Некий замкнутый набор точек. Определяет область для хождения главного героя на сцене (на скриншоте редактора это R_Floor) и области для клика мышкой. Имеет свойство
«.click» или «.click_with_» для проверки клика на этот регион, второе свойство динамическое и проверяется клик с предметом из трея. Предоставляет метод для поиска кратчайшего пути между точками для хождения главного героя. Может иметь несколько типов (не помню, кто за что отвечает) и менять курсор мыши при наведении.
Свойства региона в игровом редакторе Trickster Games

Путь (Path)

Путь состоящий из нескольких точек. Можно привязать элемент к этому пути, и объект будет плавно по нему перемещаться.
Свойства пути в игровом редакторе Trickster Games

Точка (Point)

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

Описание игрового движка Trickster Games

Движок написан на C++, для рендера используется DirectX 9.0, для связки Python 2.xx и C++ используется Boost.Python. Для сборки и компиляции исходников движка применяется (БУДЬТЕ ПРОКЛЯТЫ ЕГО СОЗДАТЕЛИ!) Boost Jam и Visual Studio 2007.

Базовый элемент в игровом движке — это GameObject, который имеет матрицу трансформации себя относительно родителя, список детей типа GameObject, и используется для создания дерева сцены. Простыми словами, для отрисовки дерева сцены вызывается GameObject::update(dt), который последовательно вызывает обновления всех детей, начиная от корня дерева вниз по иерархии. От него торчат уши в Python. Python-часть движка инициализируется следующим кодом:

std::string setup(
"sys.path = [r'Data\\Scripts1.pak', r'Data\\Scripts2.pak', "
                    "r'Data\\Scripts3.pak', r'Data\\Scripts3.pak\\CommonClasses', "
                    "r'Data\\Scripts4.pak', r'Data\\Scripts4.pak\\World', '.', 'Lib/std']\n");
run_string(setup);

Затем вызываем метод on_init из файла Lib/engine.py:

boost::python::call<void>(bp::object(py["Lib"].attr("engine").attr("on_init")).ptr(), long(application::Application::window()));
py_on_frame = py["Lib"].attr("engine").attr("on_frame");

И после делаем on_frame из того же файла каждый кадр:

boost::python::call<void>(py_on_frame.ptr(), dt / 1000.0f, just_redraw, cursor_position, mouse_button_state);

Всё, никакой магии.

Форматы файлов

При подготовке релиза происходит преобразование .py файлов в .pyc, PNG в DDS, и всё это запаковывается в .zip файл.

Анимация

Анимация делалась в Adobe Flash и экспортировалась в набор PNG файлов, которые при запаковке релиза превращаются в DDS — это родной формат для DirectX SDK.

Звуки

Звуки — это .wav файлы

Видео

Для видео использовался формат OGV (Theora Vorbis).

Вроде всё, что мог, описал. Спрашивайте, если что-то осталось непонятным.

Комментариев нет. Будьте первым!
Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Блог Евгения Жирнова