Сегодня был в районе университета СибГУТИ (Новосибирск), встретился с подписчиками моего канала на YouTube. Вопрос встречи был тематическим — создание игр. Встреча была не долгой, всего часа полтора.
Что удивительно, я относительно недавно начал рассматривать JavaScript, как ЯП для игр, но парни восприняли эту идею «на ура», и в результате основной темой беседы стала физика, точнее — ее упрощенная форма. Кому интересно, под катом итоги беседы и видео в виде компиляции моих мыслей.
Участников было совсем не много, всего 7 человек, но для меня это уже что-то. Мне самому было очень интересно то, что в моем городе нашлись ребята, которым интересна та же тема, что и мне, и которые меня знают.
Вопросы были, в целом, разные, охватывающие самые разные темы, но одним очень интересным (для меня) стал вопрос о том, как можно реализовать физику в J2ds, не прибегая к помощи сторонних физических либ.
J2ds — это двиг, который я использую для работы. Уверен, что используемые мной функции (ниже) есть и в других движках.
Так как с физикой и математикой в школе у меня былии остались трудности, я понятия не имею, как можно без учебника говорить о физике. Ни формул, ни цифр.
«Человеку проще запомнить, где хранятся данные, чем запомнить сами данные»
Это правда так. Ведь куда проще запомнить, что номера телефонов хранятся в адресной книжке, чем запоминать все эти номера. Запомнить местоположение данных куда легче и быстрее, чем эти данные, особенно когда неизвестно, какой объем у этих данных.
Не зная, что сказать, я начал разбирать с ребятами пример. С собой у меня был только планшет, но при помощи несложных манипуляций он превратился в инструмент разработки на JavaScript. Дистрибутив движка я сдернул с гита.
Для примера взяли простую физику двух шариков и пола и приступили квелосипедостроению рассуждениям.
Итак, если требуется физика шариков, можно сделать небольшой алгоритм, который и не является физикой, как таковой, но позволяет внешне создать эффект, что физика есть.
Для реализации такого алгоритма потребуются хотя бы базовые определения столкновений.
Для начала давайте определимся, что в самом простом проекте с двумя шарами и полом есть два вида столкновений:
— Шар с плоскостью (пол)
— Шар с шаром.
В качестве пола была выбрана нижняя грань активной игровой сцены. Проверить столкновение с ней очень просто:
, где _id — это объект «окружность». Если в условии поменять знак на "<", то будет проверяться столкновение с верхней границей.
Итак, определить столкновение можно, перейдем к реализации.
Для эксперимента создадим объект шар:
Этим кодом создали зеленый шарик радиусом 20 пикселей и покрасили его в зеленый цвет.
grav — гравитация, толкающая объект вниз, speed — скорость перемещения влево/вправо.
Чтобы не нагромождать игру кодом, вынесем функцию столкновения в отдельную функцию phys(). Опишем ее чуть позже
Теперь, в основном игровом цикле добавим возможность манипулировать шариком, чтобы не перезапускать каждый раз сцену:
И сразу же применим физику к объекту:
После всех манипуляций с объектом, отрисуем его:
Что ж… теперь опишем функцию phys():
Опишу алгоритм словами: проверяем, столкнулся ли шар с полом, если нет, увеличиваем его гравитацию, заставляя его падать вниз, а если столкнулся, пытаемся заставить его отскочить, инвертируя гравитацию. При этом, если она слишком маленькая — мы ее обнуляем, чтобы избежать «микроподпрыгиваний».
Запустив программу, увидим, как шарик подпрыгивает, с каждым прыжком снижая высоту. Прям как настоящий.
Применить эту функцию можно к любому объекту, просто вызвав phys(id), где id — нужный объект.
Но кроме пола, нужно еще реализовать столкновение между двумя шарами. Тут идею подал один из «гостей» мероприятия, сказав, что у объектов есть центры, сравнивая положения которых в момент столкновения, можно определить, кто выше, кто ниже, слева и т.д., дабы применить к ним соответствующие силы.
Опишем функцию phys2(). Она будет принимать два параметра: id1 и id2 объектов, которые мы проверяем на столкновения:
Думаю, второй алгоритм не менее понятен, чем первый. Он позволяет применить «физику» к двум шарам.
В наш «испытательный полигон» добавим еще один шар:
Позволим управлять им мышью (только правой кнопкой):
Применим к нему физику пола:
А теперь к обоим шарам применим физику столкновений:
Конечно, это не физика, и даже не похожа на физику, но в результате получились примитивные столкновения с несложным алгоритмом, который можно править по своему усмотрению.
Весь код целиком:
Посмотреть, как это работает можно тут.
PS: Не думаю, что подобный алгоритм целесообразно использовать где-то в реальных целях, но покопаться в этом было интересно.
Что удивительно, я относительно недавно начал рассматривать JavaScript, как ЯП для игр, но парни восприняли эту идею «на ура», и в результате основной темой беседы стала физика, точнее — ее упрощенная форма. Кому интересно, под катом итоги беседы и видео в виде компиляции моих мыслей.
Участников было совсем не много, всего 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: Не думаю, что подобный алгоритм целесообразно использовать где-то в реальных целях, но покопаться в этом было интересно.