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

от автора

В своей первой статье на Хабре, посвященной разработке браузерной игры, я показал основные этапы создания базовой сцены для 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 с примерами и пусть сами пользователи его заполняют? Ау, разработчики! Я лично готов закинуть пару примеров.

Тестовая сборка
Исходники

ссылка на оригинал статьи http://habrahabr.ru/post/264115/


Комментарии

Добавить комментарий

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