Pull to refresh

Делаем свою первую браузерную 2d игру с физикой

Reading time 4 min
Views 17K
Теплым летним вечером посетила мысль, которая, наверняка, посещает многих: хочу сделать свою игру! Энергии было через край, поэтому работа пошла с огоньком.

Racing game

Итогом стал небольшой прототип браузерного 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: там точка поворота так же находится в левом верхнем углу, а не по центру автомобиля. Думаю, это баг, который им лень поправить.

Пример Uber-подобной анимации

Решением является рисование автомобиля относительно его центра, а не левого верхнего угла.

Код
//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]
Tags:
Hubs:
+13
Comments 4
Comments Comments 4

Articles