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

Тестова збірка
Джерело

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.