Делаем свою первую браузерную 2d игру с физикой
Итогом стал небольшой прототип браузерного 2d платформера с физикой.
Под катом — руководство для новичков от новичка по созданию такой игры. Если вы — опытный игродел, заходите делиться ценными советами!
Инструменты на проекте
Классический JavaScript — Для простоты я постарался воспользоваться самым базовым синтаксисом языка. Так же в проекте нет сборщика: каждый файл подключается как есть. Благодаря этому, надеюсь, проект будет понятен широкому кругу разработчиков.
PixiJS — Мне понравился этот движок 2d графики. Каких-либо замечаний по его работе не возникло. Плюс в наличии — хорошая документация.
PhysicsJS — Одной из причин проекта было желание попробовать в деле готовый физический движок. Выбор пал на PhysicsJS. В процессе разработки иногда не хватало документации, приходилось открывать его исходники. Но свою работу он выполнил, физика тел выглядит вполне реалистично.
JQuery — Возможности библиотеки используются минимально и ее можно спокойно убрать при желании. Но лично мне JQuery нравится, я с удовольствием его использую для работы с HTML.
Архитектура приложения
Максимально 60 раз в секунду браузер вызывает метод перерисовки экрана.
//render.RootStage
function animate() {
requestAnimationFrame(animate);
//...
}
При каждой перерисовке идет обновление физической модели и последовательное рисование слоев игры: карты, игровой машины, призовых звезд.
//render.RootStage
function animate() {
requestAnimationFrame(animate);
//обновление модели
game.step();
//перерисовка слоев
for (var i=0; i< stages.length; i++)
stages[i].update();
}
Если между перерисовками экрана пользователь нажимал на кнопки управления — то модель получает об этом информацию, которая будет учтена при следующей перерисовке.
//render.RootStage
$("#moveRight").mousedown(function(){
game.car().startAccelerator();
});
$("#moveRight").mouseup(function(){
game.car().stopAccelerator();
});
Можно изобразить этот процесс в виде схемы:
1. Обновление модели.
2. Вызов PhysicsJS для расчета физики.
3. Последовательный вызов слоев на перерисовку.
4. Опрос обновленной модели и перерисовка с помощью PixiJS.
Особенности реализации
Коллизии — физический движок дает удобное API определения коллизий. Не нужно самому вспоминать математику :)
//physics.Game
var world = Physics({...});
world.add([
Physics.behavior('body-collision-detection'),
...
]);
world.on('collisions:detected', function(data){
for (var i = 0; i < data.collisions.length; i++)
onCollision(data.collisions[i]);
});
Но иногда коллизии не нужны… — например, когда собираешь призовые звезды. Мне кажется логичным включить в физический движок тип объектов, которые фиксируют факт столкновения с ними, но при этом не взаимодействуют с другими объектами (объекты-призраки). К сожалению, я не нашел в PhysicsJS такой возможности. В итоге, даже если удалять призовую звезду после коллизии, то движок уже изменил скорость игрока, замедлив его.
Уверен, есть более красивое решение, но я сделал так: после факта коллизии возвращаем игроку его характеристики до столкновения, благо PhysicsJS позволяет так себя обманывать.
//model.car.Car
function onCollision(otherBody, pos, norm){
if(otherBody.objType == model.ObjectType.POINT)
carBody.backPrevForce();
}
//physics.BodyPhysicsImpl
function backPrevForce(){
var old = body.state.old;
body.state.acc.set(old.acc.x, old.acc.y);
body.state.vel.set(old.vel.x, old.vel.y);
body.state.angular.vel = old.angular.vel;
body.state.angular.acc = old.angular.acc;
}
Результат — сбор звезд не нарушает скорости игрока.
Разные модели у движков — физический движок делает поворот объекта вокруг его центра масс, а графический движок по умолчанию разворачивает по левому верхнему углу объекта. Если этот факт не учитывать, то результат будет довольно забавным.
Кстати, что-то похожее я наблюдаю в анимации разворота автомобиля в Uber клиенте на Android: там точка поворота так же находится в левом верхнем углу, а не по центру автомобиля. Думаю, это баг, который им лень поправить :)
Решением является рисование автомобиля относительно его центра, а не левого верхнего угла.
//render.car.PlayerCar
function paintCabin(g, model){
//...
g.drawRect(model.x - model.w/2, model.y - model.h/2, model.w, model.h);
//...
}
Теперь все выглядит как надо
Показ кнопок управления на мобильном устройстве — сделал реверанс в сторону прогресса: показываю большие кнопки управления для мобильных устройств. Главное, не забыть, что зажатие кнопки делается touch событиями, и что нужно запретить появление выделения текста от этого долгого нажатия через css стиль.
//render.RootStage
$("#moveRight").on('touchstart', function(){
game.car().startAccelerator();
});
$("#moveRight").on('touchend', function(){
game.car().stopAccelerator();
});
//main.css
.moveBtn {
-webkit-user-select: none;
-moz-user-select: none;
}
Расход батареи телефона от работы игры заметно увеличивается. Думаю, виноват физический движок, который активно нагружает процессор работой.
Выводы
В целом, процесс создания игры не показался мне очень сложным. Используя готовые движки для графики и физики, можно значительно упростить разработку, почти не думая о математике. Насколько это эффективный подход — вопрос для отдельной статьи. Спасибо всем за внимание и надеюсь, мои наработки помогут вам создать что-нибудь свое, если вы давно собирались это сделать! Удачи!
[Исходники на GitHub]
Комментарии (1)
30 сентября 2016 в 10:44
0↑
↓
Phaser в миллиард раз удобней чем Pixi (при том, что это надстройка над Pixi). И из коробки предлагает несколько физических движков (Ninja, P2, Box2d). Заодно он предлагает систему плагинов (например плагин для UI: slick-ui.com).