Сегодня был в районе университета СибГУТИ (Новосибирск), встретился с подписчиками моего канала на YouTube. Вопрос встречи был тематическим — создание игр. Встреча была не долгой, всего часа полтора.
Что удивительно, я относительно недавно начал рассматривать JavaScript, как ЯП для игр, но парни восприняли эту идею «на ура», и в результате основной темой беседы стала физика, точнее — ее упрощенная форма. Кому интересно, под катом итоги беседы и видео в виде компиляции моих мыслей.

image

Участников было совсем не много, всего 7 человек, но для меня это уже что-то. Мне самому было очень интересно то, что в моем городе нашлись ребята, которым интересна та же тема, что и мне, и которые меня знают.

Вопросы были, в целом, разные, охватывающие самые разные темы, но одним очень интересным (для меня) стал вопрос о том, как можно реализовать физику в J2ds, не прибегая к помощи сторонних физических либ.

J2ds — это двиг, который я использую для работы. Уверен, что используемые мной функции (ниже) есть и в других движках.

Так как с физикой и математикой в школе у меня были и остались трудности, я понятия не имею, как можно без учебника говорить о физике. Ни формул, ни цифр.

«Человеку проще запомнить, где хранятся данные, чем запомнить сами данные»
Это правда так. Ведь куда проще запомнить, что номера телефонов хранятся в адресной книжке, чем запоминать все эти номера. Запомнить местоположение данных куда легче и быстрее, чем эти данные, особенно когда неизвестно, какой объем у этих данных.

Не зная, что сказать, я начал разбирать с ребятами пример. С собой у меня был только планшет, но при помощи несложных манипуляций он превратился в инструмент разработки на JavaScript. Дистрибутив движка я сдернул с гита.

Для примера взяли простую физику двух шариков и пола и приступили к велосипедостроению рассуждениям.

Итак, если требуется физика шариков, можно сделать небольшой алгоритм, который и не является физикой, как таковой, но позволяет внешне создать эффект, что физика есть.

Для реализации такого алгоритма потребуются хотя бы базовые определения столкновений.
Для начала давайте определимся, что в самом простом проекте с двумя шарами и полом есть два вида столкновений:
— Шар с плоскостью (пол)
— Шар с шаром.

В качестве пола была выбрана нижняя грань активной игровой сцены. Проверить столкновение с ней очень просто:
if (_id.collisionScene(scene).y > 0)

, где _id — это объект «окружность». Если в условии поменять знак на "<", то будет проверяться столкновение с верхней границей.

Итак, определить столкновение можно, перейдем к реализации.
Для эксперимента создадим объект шар:
var a= createCircle(vec2df(10, 10), 20, 'green');
a.grav= 1; 
a.speed= 0;

Этим кодом создали зеленый шарик радиусом 20 пикселей и покрасили его в зеленый цвет.
grav — гравитация, толкающая объект вниз, speed — скорость перемещения влево/вправо.

Чтобы не нагромождать игру кодом, вынесем функцию столкновения в отдельную функцию phys(). Опишем ее чуть позже

Теперь, в основном игровом цикле добавим возможность манипулировать шариком, чтобы не перезапускать каждый раз сцену:

 if (input.lClick)  // если нажата ЛКМ
 {
  a.grav= 0; // обнуляем гравитацию, так как объект не нужно толкать вниз
  a.moveTo(input);  // перемещаем объект в место клика
 }

И сразу же применим физику к объекту:

phys(a);

После всех манипуляций с объектом, отрисуем его:

a.draw(scene);

Что ж… теперь опишем функцию phys():

function phys(_id)
{
 // если столкновение с нижней границей сцены
 if (_id.collisionScene(scene).y > 0)
 {
  // ихбегаем проваливания сквозь нее
  _id.pos.y= -_id.size.y + scene.height;
  // даем возможность шарику "отскочить", снизив его скоросьт (*0,7)
  _id.grav= -_id.grav * 0.7;
  // При этом, если его гравитация слишком мала, обнуляем ее, чтобы объект не трясло
  if ( (Math.abs(_id.grav) > 0) && (Math.abs(_id.grav) < 0.5) ) _id.grav= 0;
 } 
 // если столкновения нет, увеличиваем гравитацию для объекта и
 else
 {
  _id.grav+= _id.grav < 10 ? 0.2 : 0; 
 }
 // если скорость движения отлична от нуля, замедляем движение (сила трения, сопротивление и т.д.)
 _id.speed+= _id.speed > 0 ? -0.05 : 0.05; 
 // При этом если скорость станет совсем маленькой, обнуляем ее
 if (Math.abs(_id.speed) < 0.1) _id.speed= 0; 
 // Толкаме обхект с заданной скоростью и гравитацией
 _id.move(vec2df(_id.speed, _id.grav));
 // отрисовываем
 _id.draw(scene);
}

Опишу алгоритм словами: проверяем, столкнулся ли шар с полом, если нет, увеличиваем его гравитацию, заставляя его падать вниз, а если столкнулся, пытаемся заставить его отскочить, инвертируя гравитацию. При этом, если она слишком маленькая — мы ее обнуляем, чтобы избежать «микроподпрыгиваний».

Запустив программу, увидим, как шарик подпрыгивает, с каждым прыжком снижая высоту. Прям как настоящий.

Применить эту функцию можно к любому объекту, просто вызвав phys(id), где id — нужный объект.

Но кроме пола, нужно еще реализовать столкновение между двумя шарами. Тут идею подал один из «гостей» мероприятия, сказав, что у объектов есть центры, сравнивая положения которых в момент столкновения, можно определить, кто выше, кто ниже, слева и т.д., дабы применить к ним соответствующие силы.

Опишем функцию phys2(). Она будет принимать два параметра: id1 и id2 объектов, которые мы проверяем на столкновения:

function phys2(_id1, _id2)
{
 // проверим, столкнулись ли объекты
 if (_id1.collision(_id2))
 {
  // если столкнулись, передаем силу гравитации одного - другому, умножив их
  // на коэффициент, определяющий, какой из них выше, чтобы оттолкнуть объект
  // в нужном направлении
  _id2.grav= Math.abs(_id2.grav + _id1.grav) * (_id1.getPosition().y < _id2.getPosition().y ? 1 : -1);
  // второму просто присваиваем гравитацию, обрутную получившейся и немного ее снижаем
  _id1.grav= (_id2.grav) * (-1);  

  // тут можно было так же домножить скорость на коэффициент, но решили пока так:
  // Если 1 объект сврху, то позиционируем его, избегая прохождения сквозь
  // и аналогично, если он снизу
  if (_id1.getPosition().y < _id2.getPosition().y) 
   _id1.pos.y= -_id1.size.y + _id2.pos.y;
  else
   _id1.pos.y= _id2.size.y + _id2.pos.y;

 // тут, можно рассчитать угол отражения, но мы поступили проще
 // взяв разность между центрами объектов и поделив их на 100, чтобы снизить импульс
 // на выходе
 _id1.speed= (_id1.getPosition().x - _id2.getPosition().x) * 0.1;
 // второму объекту присвоили обратную силу, отталкивая его
 _id2.speed= -_id1.speed; 
 }
}

Думаю, второй алгоритм не менее понятен, чем первый. Он позволяет применить «физику» к двум шарам.

В наш «испытательный полигон» добавим еще один шар:

b= createCircle(vec2df(300, 250), 20, 'yellow');
b.grav= 1; b.speed= 0;

Позволим управлять им мышью (только правой кнопкой):

 if (input.rClick) 
 {
  b.grav= 0;
  b.moveTo(input);
 }

Применим к нему физику пола:

phys(b);

А теперь к обоим шарам применим физику столкновений:

phys2(a, b);

Конечно, это не физика, и даже не похожа на физику, но в результате получились примитивные столкновения с несложным алгоритмом, который можно править по своему усмотрению.

Весь код целиком:

Показать
<!DOCTYPE html>
<html> 
 <head>
  <script type="text/javascript" src="../j2ds/math.js"></script>
  <script type="text/javascript" src="../j2ds/input.js"></script>   
  <script type="text/javascript" src="../j2ds/dom.js"></script>
  <script type="text/javascript" src="../j2ds/j2ds.js"></script>
  <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
  <meta name="viewport" content="width=device-width,user-scalable=no" />  
  <title>Demo J2ds. Физика</title>
 </head>
<body>

<canvas id="iCanvas" width="550" height="300"></canvas>
<div id="hint"></div>

<script type="text/javascript">
// Создаем сцену
scene= createScene('iCanvas', '#ceeeee');

if (device().width < 800)
 scene.fullScreen(true);

// Инициализируем устройство ввода
initInput(scene);

// создаем шар A
a= createCircle(vec2df(10, 10), 20, 'green');
a.grav= 1; a.speed= 0;

b= createCircle(vec2df(300, 250), 20, 'yellow');
b.grav= 1; b.speed= 0;

// Описываем игровое состояние Game
function Game() {
 input.upd();

 if (input.lClick) 
 {
  a.grav= 0;
  a.moveTo(input);
 }

 if (input.rClick) 
 {
  b.grav= 0;
  b.moveTo(input);
 }

 phys(a);
 phys(b);

 phys2(a, b);

 scene.drawText(vec2df(10, 10), 'Управление: ЛКМ / ПКМ');

}

function phys2(_id1, _id2)
{
 // проверим, столкнулись ли объекты
 if (_id1.collision(_id2))
 {
  // если столкнулись, передаем силу гравитации одного - другому, умножив их
  // на коэффициент, определяющий, какой из них выше, чтобы оттолкнуть объект
  // в нужном направлении
  _id2.grav= Math.abs(_id2.grav + _id1.grav) * (_id1.getPosition().y < _id2.getPosition().y ? 1 : -1);
  // второму просто присваиваем гравитацию, обрутную получившейся и немного ее снижаем
  _id1.grav= (_id2.grav) * (-1);  

  // тут можно было так же домножить скорость на коэффициент, но решили пока так:
  // Если 1 объект сврху, то позиционируем его, избегая прохождения сквозь
  // и аналогично, если он снизу
  if (_id1.getPosition().y < _id2.getPosition().y) 
   _id1.pos.y= -_id1.size.y + _id2.pos.y;
  else
   _id1.pos.y= _id2.size.y + _id2.pos.y;

 // тут, можно рассчитать угол отражения, но мы поступили проще
 // взяв разность между центрами объектов и поделив их на 100, чтобы снизить импульс
 // на выходе
 _id1.speed= (_id1.getPosition().x - _id2.getPosition().x) * 0.1;
 // второму объекту присвоили обратную силу, отталкивая его
 _id2.speed= -_id1.speed; 
 }
}


function phys(_id)
{
 // если столкновение с нижней границей сцены
 if (_id.collisionScene(scene).y > 0)
 {
  // ихбегаем проваливания сквозь нее
  _id.pos.y= -_id.size.y + scene.height;
  // даем возможность шарику "отскочить", снизив его скоросьт (*0,7)
  _id.grav= -_id.grav * 0.7;
  // При этом, если его гравитация слишком мала, обнуляем ее, чтобы объект не трясло
  if ( (Math.abs(_id.grav) > 0) && (Math.abs(_id.grav) < 0.5) ) _id.grav= 0;
 } 
 // если столкновения нет, увеличиваем гравитацию для объекта и
 else
 {
  _id.grav+= _id.grav < 10 ? 0.2 : 0; 
 }
 // если скорость движения отлична от нуля, замедляем движение (сила трения, сопротивление и т.д.)
 _id.speed+= _id.speed > 0 ? -0.05 : 0.05; 
 // При этом если скорость станет совсем маленькой, обнуляем ее
 if (Math.abs(_id.speed) < 0.1) _id.speed= 0; 
 // Толкаме обхект с заданной скоростью и гравитацией
 _id.move(vec2df(_id.speed, _id.grav));
 // отрисовываем
 _id.draw(scene);
}


// Стартуем игру с игровым состоянием Game и FPS 60
startGame(Game, 60);
</script>

</body>
</html>


Посмотреть, как это работает можно тут.

Видео того, как я реализую этот алгоритм


PS: Не думаю, что подобный алгоритм целесообразно использовать где-то в реальных целях, но покопаться в этом было интересно.

Комментарии (0)