Робимо свою першу браузерну 2d гру з фізикою

Теплим літнім вечором відвідала думка, яка, напевно, відвідує багатьох: хочу зробити свою гру! Енергії було через край, тому робота пішла з вогником.

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;
}


Результат — збір зірок не порушує швидкості гравця.


Різні моделі у движків — фізичний движок робить поворот об'єкта навколо його центру мас, а графічний движок за замовчуванням розгортає по лівому верхньому куті об'єкта. Якщо цей факт не враховувати, то результат буде досить кумедним.


До речі, щось схоже я спостерігаю в анімації розвороту автомобіля у Über клієнта на 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]

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

0 коментарів

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