Создаем игру. Часть 1: Персонаж
2014-06-06
Сегодня мы приступим к созданию полноценного игрового приложения, работающего на движке Blend4Web.
Геймплей
Определимся с игровым процессом. Игрок в роли отважного воина перемещается по ограниченному количеству платформ. С неба на него постоянно обрушиваются раскалённые камни, которые нужно избегать. Со временем их количество возрастает. На локации периодически появляются бонусы, дающие различные преимущества. Цель игрока - как можно дольше продержаться. В дальнейшем мы добавим ещё несколько интересных особенностей, но пока ограничимся этим списком. Вид - от третьего лица.
В будущем, игра будет поддерживать мобильные устройства и систему набора очков. А сейчас мы создадим приложение, загрузим сцену и добавим управление анимированным персонажем с клавиатуры. Приступим!
Настройка сцены
Игровые сцены создаются в Blender'е, после чего экспортируются и загружаются в приложение. Воспользуемся подготовленными художником файлами, собранными в директории blend/. О том, как были созданы эти ресурсы, будет написано в отдельной статье.
Откроем файл character_model.blend и настроим персонажа. Переключим рендер а Blend4Web и выделим объект character_collider - физический объект персонажа.
На вкладке Physics выставим настройки, как на изображении выше. Обратите внимание, что тип физики должен быть либо Dynamic, либо Rigid Body, иначе персонаж будет неподвижен.
Объект character_collider является родителем для "графической" модели персонажа, которая, таким образом, будет перемещаться вслед за невидимой физической моделью. Обратите внимание, что нижние точки капсулы и аватара немного отличаются по высоте. Это было сделано для компенсации параметра Step Height, который приподнимает персонажа над поверхностью с целью преодоления невысоких препятствий.
Теперь откроем основной файл game_example.blend, из которого будем экспортировать сцену.
В этот файл подключены по ссылке следующие компоненты:
1) Из файла character_model.blend - группа объектов character.
2) Из файла main_scene.blend - группа объектов environment, в которой находятся статические модели сцены, а также их копии с материалом для соударения.
3) Из файла character_animation.blend - "запеченная" анимация character_idle_01_B4W_BAKED и character_run_B4W_BAKED.
Примечание
Добавить по ссылке компоненты из другого файла можно так: на панели инструментов нажмите File -> Link и выберите файл. Далее перейдите в соответствующий блок данных и выберите нужные элементы. Добавить по ссылке можно всё что угодно - от отдельной анимации до всей сцены.
В настройках сцены необходимо удостовериться, что флажок Enable Physics включён.
Сцена готова, перейдем к программированию.
Подготовка необходимых файлов
Поместим в корневую директорию проекта следующие файлы:
1) Движок b4w.min.js
2) Физический движок в виде двух файлов uranium.js и uranium.js.mem
Работать будем с файлами game_example.html и game_example.js.
Подключаем все необходимые скрипты в HTML-файле:
<!DOCTYPE html>
<html>
<head>
<title>Petigor's Tale | Blend4Web</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<script type="text/javascript" src="b4w.min.js"></script>
<script type="text/javascript" src="game_example.js"></script>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
}
div#canvas3d {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<div id="canvas3d"></div>
</body>
</html>
Откроем скрипт game_example.js и добавим следующий код:
"use strict"
if (b4w.module_check("game_example_main"))
throw "Failed to register module: game_example_main";
b4w.register("game_example_main", function(exports, require) {
var m_anim = require("animation");
var m_app = require("app");
var m_main = require("main");
var m_data = require("data");
var m_ctl = require("controls");
var m_phy = require("physics");
var m_cons = require("constraints");
var m_scs = require("scenes");
var m_trans = require("transform");
var m_cfg = require("config");
var _character;
var _character_rig;
var ROT_SPEED = 1.5;
var CAMERA_OFFSET = new Float32Array([0, 4, 1.5]);
exports.init = function() {
m_app.init({
canvas_container_id: "canvas3d",
callback: init_cb,
physics_enabled: true,
show_fps: true,
autoresize: true,
alpha: false
});
}
function init_cb(canvas_elem, success) {
if (!success) {
console.log("b4w init failure");
return;
}
m_data.load("game_example.json", load_cb);
}
function load_cb(root) {
}
});
b4w.require("game_example_main").init();
Если вы читали статью по программированию интерактивного приложения, то здесь для вас не будет почти ничего нового. На данном этапе подключены все необходимые модули, определены функции инициализации и два обработчика. Так же предусмотрена возможность изменения размера рабочего окна приложения функцией on_resize.
Для физического объекта персонажа объявлена глобальная переменная _character, для анимированного скелета - _character_rig. Так же объявлены две константы ROT_SPEED и CAMERA_OFFSET, которые мы используем в дальнейшем.
На этом этапе уже можно запустить приложение и посмотреть на статическую сцену с неподвижным персонажем.
Перемещение персонажа
Добавим следующий код в обработчик загрузки:
function load_cb(root) {
_character = m_scs.get_first_character();
_character_rig = m_scs.get_object_by_dupli_name("character",
"character_rig");
setup_movement();
setup_rotation();
setup_jumping();
m_anim.apply(_character_rig, "character_idle_01");
m_anim.play(_character_rig);
m_anim.set_behavior(_character_rig, m_anim.AB_CYCLIC);
}
Вначале мы сохраняем физическую модель персонажа в переменную _character. Анимируемый скелет сохраняется как _character_rig.
Последние три строки отвечают за установку стартовой анимации персонажа.
animation.apply() - устанавливает анимацию по соответствующему имени,
animation.play() - запускает её,
animation.set_behaviour() - изменяет поведение анимации, в нашем случае - на циклическое.
Перед тем как определить функции setup_movement(), setup_rotation() и setup_jumping(), важно понять как работает система сенсоров в Blend4Web. Рекомендуем прочитать соответствующую статью в руководстве пользователя. Здесь же мы ознакомимся с ней только в общих чертах.
Для того, чтобы сгенерировать событие при выполнении определенных условий, требуется создать массив сенсоров.
Примечание
Со списком всех возможных сенсоров можно ознакомиться в соответствующем разделе документации.
Далее нужно определить логическую функцию, описывающую, в каком состоянии (true или false) должны находится определённые сенсоры в массиве, чтобы обработчик сенсора получал положительный результат. Затем следует создать обработчик, в котором будут содержаться выполняемые действия. И, наконец, нужно вызвать функцию controls.create_sensor_manifold() для создания множества сенсоров, которое и будет отвечать за обработку значений сенсоров. Посмотрим, как это будет работать в нашем случае.
Определим функцию управления setup_movement():
function setup_movement() {
var key_w = m_ctl.create_keyboard_sensor(m_ctl.KEY_W);
var key_s = m_ctl.create_keyboard_sensor(m_ctl.KEY_S);
var key_up = m_ctl.create_keyboard_sensor(m_ctl.KEY_UP);
var key_down = m_ctl.create_keyboard_sensor(m_ctl.KEY_DOWN);
var move_array = [
key_w, key_up,
key_s, key_down
];
var forward_logic = function(s){return (s[0] || s[1])};
var backward_logic = function(s){return (s[2] || s[3])};
function move_cb(obj, id, pulse) {
if (pulse == 1) {
switch(id) {
case "FORWARD":
var move_dir = 1;
m_anim.apply(_character_rig, "character_run");
break;
case "BACKWARD":
var move_dir = -1;
m_anim.apply(_character_rig, "character_run");
break;
}
} else {
var move_dir = 0;
m_anim.apply(_character_rig, "character_idle_01");
}
m_phy.set_character_move_dir(obj, move_dir, 0);
m_anim.play(_character_rig);
m_anim.set_behavior(_character_rig, m_anim.AB_CYCLIC);
};
m_ctl.create_sensor_manifold(_character, "FORWARD", m_ctl.CT_TRIGGER,
move_array, forward_logic, move_cb);
m_ctl.create_sensor_manifold(_character, "BACKWARD", m_ctl.CT_TRIGGER,
move_array, backward_logic, move_cb);
}
Создадим 4 сенсора для нажатия клавиш - стрелка вперёд, стрелка назад, S и W. Можно было бы обойтись и двумя, но мы хотим продублировать управление на символьных клавишах и на стрелках. Заносим их в массив move_array.
Определяем логические функции. Мы хотим, чтобы движение осуществлялось при нажатии одной из двух клавиш в массиве move_array.
Это поведение реализуется с помощью логической функции следующего вида:
function(s) { return (s[0] || s[1]) }
Самое важное происходит в функции move_cb().
Здесь obj - наш персонаж. Аргумент pulse принимает значение 1, когда нажата какая-либо из обозначенных клавиш. По id, соответствующему одному из объявленных ниже множеств сенсоров, мы решаем, двигать персонажа вперед (move_dir = 1) или назад (move_dir = -1). Внутри этих же блоков переключаем два типа анимации: бег и анимация на месте.
Перемещение персонажа реализуется следующим вызовом:
m_phy.set_character_move_dir(obj, move_dir, 0);
В конце функции setup_movement() на персонаже создаются два множества сенсоров для движения вперёд и назад. Они имеют тип CT_TRIGGER, то есть срабатывают каждый раз при изменении значения сенсоров.
На этой стадии персонаж уже может ходить вперед и назад. Теперь добавим поворот.
Поворот персонажа
Определяем функцию setup_rotation():
function setup_rotation() {
var key_a = m_ctl.create_keyboard_sensor(m_ctl.KEY_A);
var key_d = m_ctl.create_keyboard_sensor(m_ctl.KEY_D);
var key_left = m_ctl.create_keyboard_sensor(m_ctl.KEY_LEFT);
var key_right = m_ctl.create_keyboard_sensor(m_ctl.KEY_RIGHT);
var elapsed_sensor = m_ctl.create_elapsed_sensor();
var rotate_array = [
key_a, key_left,
key_d, key_right,
elapsed_sensor
];
var left_logic = function(s){return (s[0] || s[1])};
var right_logic = function(s){return (s[2] || s[3])};
function rotate_cb(obj, id, pulse) {
var elapsed = m_ctl.get_sensor_value(obj, "LEFT", 4);
if (pulse == 1) {
switch(id) {
case "LEFT":
m_phy.character_rotation_inc(obj, elapsed * ROT_SPEED, 0);
break;
case "RIGHT":
m_phy.character_rotation_inc(obj, -elapsed * ROT_SPEED, 0);
break;
}
}
}
m_ctl.create_sensor_manifold(_character, "LEFT", m_ctl.CT_CONTINUOUS,
rotate_array, left_logic, rotate_cb);
m_ctl.create_sensor_manifold(_character, "RIGHT", m_ctl.CT_CONTINUOUS,
rotate_array, right_logic, rotate_cb);
}
Как видим, она очень похожа на setup_movement().
Добавился elapsed сенсор, постоянно генерирующий положительный импульс, что позволяет внутри обработчика получать время, прошедшедшее с момента отрисовки предыдущего кадра с помощью функции controls.get_sensor_value(). Это нужно для корректного вычисления скорости поворота.
Тип множеств сенсоров изменился на CT_CONTINUOUS, т.е. вызов обработчика происходит каждый кадр, а не только при изменении значения сенсоров.
Поворот персонажа вокруг вертикальной оси осуществляется функцией:
m_phy.character_rotation_inc(obj, elapsed * ROT_SPEED, 0)
Для регулирования скорости поворота определена константа ROT_SPEED.
Прыжок персонажа
Последняя функция управления - setup_jumping().
function setup_jumping() {
var key_space = m_ctl.create_keyboard_sensor(m_ctl.KEY_SPACE);
var jump_cb = function(obj, id, pulse) {
if (pulse == 1) {
m_phy.character_jump(obj);
}
}
m_ctl.create_sensor_manifold(_character, "JUMP", m_ctl.CT_TRIGGER,
[key_space], function(s){return s[0]}, jump_cb);
}
Для прыжка использована клавиша пробела, при нажатии на которою вызывается метод:
m_phy.character_jump(obj)
Теперь мы можем управлять персонажем!
Перемещение камеры
Последнее, чем мы сегодня займёмся - привязка камеры к положению персонажа.
В функцию load_cb() добавим ещё один вызов: setup_camera().
Вот как выглядит эта функция:
function setup_camera() {
var camera = m_scs.get_active_camera();
m_cons.append_semi_soft_cam(camera, _character, CAMERA_OFFSET);
}
Константа CAMERA_OFFSET для отступа камеры назначена в 1,5 метра вверх (ось Y в WebGL) и 4 метра назад (ось Z в WebGL) от положения персонажа.
Эта функция находит на сцене активную камеру и создаёт ограничитель для плавного перемещения камеры за персонажем.
На этом мы пока остановимся. Можно запустить приложение и насладиться проделанной работой!
Ссылка на приложение в отдельном окне
Исходные файлы приложения и сцены находятся в составе бесплатного дистрибутива Blend4Web SDK.
Изменения
[2014-06-06] Изначальная публикация.
[2014-06-09] Заменены иллюстрации в связи с обновлением контента игры.
[2014-06-25] Изменены пути к файлам.
[2014-10-20] Изменения в анимационном API. Арматурная анимация теперь применяется к скелету.
[2015-10-22] Удален модуль app.js. Заменена функция on_resize.