Pull to refresh

Создание игры на Blend4Web. Путь программиста

Reading time 8 min
Views 13K
В своей первой статье на Хабре, посвященной разработке браузерной игры, я показал основные этапы создания базовой сцены для Blend4Web. Пара примитивов, несколько текстур плюс встроенные возможности платформы позволили с легкостью воплотить задуманную идею — воду с рефракцией и каустикой.

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

Так уж случилось, что я пишу код для игр исключительно на C#, а JavaScript мне мало знаком. Но изучить еще один язык программирования — несложная задача. Опыт работы с Unity позволил создать мне основные архитектурные заготовки, которые кочуют из проекта в проект, что позволяет развернуть базовый костяк нового приложения буквально за 15 минут.

Уже традиционно, игровой проект я разбиваю на несколько сцен:
  • App содержит глобальные объекты управления игровым процессом, показом рекламы, социальными функциями.
  • Splash — буферный уровень, где я обычно показываю процесс загрузки игровых данных, скачиваю рекламу или подключаюсь к социальным службам.
  • MainMenu — главное меню программы.
  • Level1x — текущий игровой уровень без GUI.
  • GameGUI — собственно игровой GUI, подгружаемый текущим уровнем.

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

Конечно, нечто подобное я хотел бы использовать и для новой игры. Но нужно учитывать возможности JavaScript и особенности работы с Blend4Web. Как выяснилось, многое из опыта программирования для Unity оказалось неприемлемо для Blend4Web. Теперь обо всем по порядку.

Платформа Blend4Web предназначена для разработки приложений WebGL. Соответственно, нужно создать небольшую обвертку в HTML. Типичный код выглядит следующим образом:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
    <script type="text/javascript" src="b4w.full.min.js"></script>
    <script type="text/javascript" src="js/game_app.js"></script>
    <style>
        body {
            margin: 0;
            padding: 0;
            overflow: hidden;
        }
        #canvas3d {
            position: absolute;
            width: 1024px;
            height: 768px;
            overflow: hidden;
        }
    </style>
</head>
<body>
    <div id="canvas3d"></div>
</body>
</html>

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

Интересно, что разработчики предлагают два варианта скомпилированного движка: b4w.min.js и b4w.full.min.js. По названию ясно, что первый файл — это облегченная версия. Вот только непонятно, что из нее убрали (информацию в справке так и не нашел), поэтому я решил использовать b4w.full.min.js. Кстати, для работы с физикой нужно подключать еще один файл uranium.js.

Взгляните на строку script type=«text/javascript» src=«js/game_app.js». В ней объявляется файл game_app.js — это основной скрипт для управления игрой. Я уже показывал в прошлых статьях простейшие примеры программирования для Blend4Web. Но мне бы хотелось выжать из game_app.js больше, чем простую инициализацию движка. Здесь я собираюсь сконцентрировать все глобальные функции игры.

Само программирование начинается с регистрации своего модуля в пространстве b4w. Модуль app здесь уже присутствует. Именно поэтому к названию своих скриптов я решил прибавлять префикс “game_”, чтобы в дальнейшем не иметь каких-либо проблем.
"use strict"
b4w.register("game_app", function(exports, require) {
var m_app = b4w.require("app");
var m_data = b4w.require("data");
var m_main  = require("main");
exports.init = function() {
    m_app.init({
        canvas_container_id: "canvas3d",
        callback: init_cb,
        physics_enabled: false,
        autoresize: true
    });
}

Это первый этап — где регистрируется новый модуль game_app и описывается функция инициализации движка:
  • canvas_container_id — название контейнера для вывода должно совпадать с таким же в HTML.
  • callback — функция, которая будет вызвана после выполнения инициализации.
  • physics_enabled — использование физики. Пока мне она в игре не нужна, поэтому я поставил false. Учтите, что активация физики требует подключения к проекту файла uranium.js.
  • autoresize — опция, заставляющая движок автоматически изменять размеры экрана в соответствие с окном браузера. В моем случае она бесполезна, но если вы распахиваете канву на всю страницу, то без нее не обойтись.

После инициализации движка вызывается функция init_cb, которая в свою очередь загружает сцену с главным меню игры.
// инициализация закончилась
function init_cb(canvas_elem, success) {
    loadMainMenu();
}

// загрузка главного меню
function loadMainMenu() {
    m_data.load("mainmenu.json", mainmenu_cb);
}

//загрузка меню завершена
function mainmenu_cb() {
}

Хочу остановиться на использовании GUI. Его в Blend4Web нет. Это сначала напрягает, но есть два пути решения проблемы.

Первый вариант предлагают сами разработчики фреймворка — использовать возможности обычного HTML. В целом все официальные уроки доказывают, что это работает, но я предпочитаю свой вариант — Blender. Нет проблем создать сцену с кнопками и обработчиками. Возможно, это решение неверное и придется вернуться к HTML, но пока пусть все остается как есть.

Необходимо учитывать еще один нюанс при загрузке сцены функцией data.load. Насколько я понял — это единственный вариант, что есть в API и очень своеобразный.
m_data.load("mainmenu.json");
m_data.load("scene.json");

Как вы думаете, что произойдет при выполнении этих двух строк кода? Если вы считаете, что замена одной сцены на другую, то ошибаетесь. В действительности, в mainmenu.json добавятся элементы scene.json. Функция data.load не уничтожает предыдущую сцену, а подгружает в нее новую. Этот подход позволяет динамически загружать объекты, что очень важно при разработке веб-приложения, а дополнительные параметры функции помогают управлять этим процессом (см. справку).

Поэтому, чтобы заменить одну сцену другой — нужно уничтожить предыдущую. Для этого есть команда data.unload (id). В качестве ID можно указать порядковый номер, который сцены получают во время загрузки. К примеру, примитивный код замены может быть следующим:
data.load("mainmenu.json");
data.unload ();
data.load("scene.json");

Только вот вопрос, функция load работает с кэшем или постоянно подгружает данные из сети?

Еще один момент, который меня интересовал на данном этапе — как сделать “полоску загрузки”. Это меня настолько озадачило, что я перерыл официальный форум и уже собирался задать сей вопрос разработчикам, но, к счастью, не успел. Все оказалось очень просто и в справке SDK имеются соответствующие функции. Поэтому банальный вывод — смотри документацию!

Разработчики предлагают специальный модуль, который так и называется preloader. Здесь есть готовые функции для создания самых различных “полосок”. Заметьте, не банальной передачи процентов загрузки файла, а полноценные, уже визуальные решения. Но я пока решил не заморачиваться и выбрал самый простой вариант.

Это пример, как создается “полоска загрузки” средствами Blend4Web:
var m_preloader = require("preloader");
//инициализация прелоадера с соответствующими параметрами
function init_cb(canvas_elem, success) {
    m_preloader.create_simple_preloader({
            bg_color:"#00000000",
            bar_color:"#FFF",
            background_container_id: "preloader",
            canvas_container_id: "canvas3d",
            preloader_fadeout: true});
    load();
}

//загрузка файла
function load() {
var p_cb = preloader_cb;
m_data.load("scene.json", load_cb,p_cb,true);
}

//обновление “полоски”
function preloader_cb(percentage) {
       m_preloader.update_preloader(percentage);
}

Так же в HTML нужно добавить отдельный контейнер для загрузчика:
<body>
    <div id="preloader"></div>
    <div id="canvas3d"></div>
</body>

Итак, главное меню загружено, пользователь щелкает по кнопке “Start” и переходит в игру. Эту цепочку и нужно создать.

В своей работе я всегда руководствуюсь простым правилом — не смешивать в одном “флаконе” действия от разных блоков. Один скрипт обеспечивает общее управление приложением, другой отвечает за GUI, третий за социальные функции и т.д. Такой подход позволяет быстрее находить ошибки или выполнять апгрейд программы. То же самое я хочу использовать и при программирование игры для WebGL. Поэтому обработка функций главного меню должна быть в отдельном файле.

Оказалось, что сделать это несложно. Принцип тот же, что и при создании первого скрипта.
b4w.register("game_mainmenu", function(exports, require) {
var m_mouse     = require("mouse");
var m_scenes     = require("scenes");
var mg_app     = require("game_app");

exports.mainmenu_cb = function() {
    addEventListener("mousedown", main_canvas_down);
    m_mouse.enable_mouse_hover_outline();
}

function main_canvas_down(e) {
    var x = m_mouse.get_coords_x(e);
       var y = m_mouse.get_coords_y(e);
       var obj = m_scenes.pick_object(x, y);
    var obj_name = m_scenes.get_object_name(obj);
    
    if (obj_name == "btStart") {
        m_mouse.disable_mouse_hover_outline();
        mg_app.loadScene(); 
    }
}

});

Это код целиком. Его задача — определить щелчок мыши по объекту-кнопке в меню и запустить загрузку игрового уровня.

Сначала давайте рассмотрим взаимосвязь обоих скриптов. Обратите внимание на кусок кода из файла game_app:
function loadMainMenu() {
    var p_cb = preloader_cb;
m_data.load("mainmenu.json", m_mainmenu.mainmenu_cb,p_cb,true);
}

Итак, loadMainMenu пытается загрузить сцену с кнопкой. При завершении этого процесса будет запущена m_mainmenu.mainmenu_cb. Только в данном случае реализация этой функции находится в другом модуле, который становится доступным после его подключения в заголовке:
var m_mainmenu     = require("game_mainmenu");

Сцена загружена и управление переходит к функции уже другого скрипта game_mainmenu:
exports.mainmenu_cb = function() {
    addEventListener("mousedown", main_canvas_down);
    m_mouse.enable_mouse_hover_outline();
}

Blend4Web предлагает собственную событийную модель. Скрипт подписывается на определенное событие, которое генерирует движок. В моем случае — это щелчок мыши по канве. В показанной ранее функции две команды:
  • addEventListener() — подписка на событие;
  • m_mouse.enable_mouse_hover_outline() — выделение объекта при нахождении курсора над ним.

Blend4Web имеет заготовки для выделения объектов в сцене. Настройка выполняется непосредственно в Blender. В панели Object нужно включить опции Selectable и Enable Outlining для примитива (см. рис). Кроме того, можно настроить толщину и цвет рамки, а также ее мерцание в панели Render. Теперь при прохождении курсора над таким объектом он будет отмечаться контуром.

image

Идем дальше. Игрок щелкнул мышкой в пределах рабочего контейнера. Генерируется событие “mousedown” и выполняется функция main_canvas_down:
function main_canvas_down(e) {
//получаем координаты мыши в пространстве XY
    var x = m_mouse.get_coords_x(e);
       var y = m_mouse.get_coords_y(e);

//Опознаем 3D объект в данных координатах
       var obj = m_scenes.pick_object(x, y);

//Узнаем его имя и загружаем сцену
    var obj_name = m_scenes.get_object_name(obj);
    if (obj_name == "btStart") {
        m_mouse.disable_mouse_hover_outline();
        mg_app.loadScene(); 
    }
}

Как видите, скрипт game_mainmenu обеспечивает работу только главного меню. Сама загрузка уровня выполняется в скрипте game_app (вызов mg_app.loadScene()), т.е. мне удалось корректно задать скриптам задуманный функционал.

Выводы


Программировать оказалось совсем несложно. Мне удалось частично воспроизвести свою схему работы приложения в Unity и для Blend4Web: разделить сцены по смыслу, определить функциональность скриптов. В некоторых случаях Blend4Web преподносил приятные сюрпризы — это готовый прелоадер и окантовка объектов в сцене (признаюсь, подобное для Unity нужно создавать самому). Единственное, что не понравилось — это отсутствие маленьких примеров. Полагаю, мое нытье на эту тему уже примелькалось, но это реально тормозило процесс.

Может быть стоит создать что-то типа wiki с примерами и пусть сами пользователи его заполняют? Ау, разработчики! Я лично готов закинуть пару примеров.

Тестовая сборка
Исходники
Tags:
Hubs:
+9
Comments 2
Comments Comments 2

Articles