Простейший физический движок

от автора

Вас интересуют игры? Хотите создать игру но не знаете с чего начать? Тогда вам сюда. В этой статье я рассмотрю простейший физический движок, с построения которого можно начать свой путь в GameDev’e. И да, движок будем писать с нуля.

Несколько раз мои друзья интересовались, как же я пишу игры / игровые движки. После очередного такого вопроса и ответа я решил сделать статью, раз эта тема так интересна.

В качестве языка программирования был выбран javascript, потому что возможности скачать IDE и компилятор у подопытного знакомого не было. Рисовать будем на canvas.

Постановка задачи

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

Алгоритм

Для начала нужно уяснить отличие компьютерной физики от реальной. Реальная физика действует непрерывно (во всяком случае обратное не доказать на текущий момент). Компьютерная физика, как и компьютер действуют дискретно, т.е. мы не можем вычислять её непрерывно, поэтому разбиваем её вычисление на шаги с определённым интервалом (я предпочитаю интервал 25 мс). Координаты объектов меняются после каждого шага и объекты выводятся на экран.

Теперь приступим к самой гравитации.

Закон всемирного тяготения (Ньютонова гравитация) гласит:

F = G * m1 * m2 / R^2 						(1) 

где:

F [Н]- сила притяжения между двумя объектами G = 6.67*10^-11 [м^3/(кг * с^2)]- гравитационная постоянная m1, m2 [кг] - массы 1 и 2 объектов R [м] - расстояние между центрами масс объектов 

Как это нам поможет в определении новых координат? А мы эту силу будем прикладывать к этим объектам, используя второй закон Ньютона:

F = m * a 							(2) 

где:

F [Н] - сила, приложенная к текущему объекту m [кг] - масса текущего объекта a [м/с^2] - ускорение текущего объекта 

Забудем на время то, что в (1) сила — скаляр, а в (2) сила — вектор. И во 2 случае будем считать силу и ускорение скалярами.

Вот и получили изменение ускорения:

a = F / m 							(3) 

Изменение скорости и координат следует из следующего:

a = v'   →   a = dv / dt   →   dv = a * dt v = s'   →   v = ds / dt   →   ds = v * dt v += dv Pos += ds 

где:

d - дифференциал (производная) v - скорость s - расстояние Pos - точка, текущие координаты объекта 

переходим от векторов к скалярам:

a.x = a * cos(α) a.y = a * sin(α) dv.x = a.x * dt dv.y = a.y * dt v.x += dv.x v.y += dv.y ds.x = v.x * dt ds.y = v.y * dt Pos.x += ds.x Pos.y += ds.y 

где:

cos(α) = dx / R sin(α) = dy / R dx = Pos2.x - Pos.x dy = Pos2.y - Pos.y R^2 = dx^2 + dy^2 

Так как другого вида силы в проекте пока нет, то используем (1) в таком виде и немножко облегчим вычисления:

F = G * m * m2 / R^2 a = G * m2 / R^2 

Код

Запускаемую страничку index.html создадим сразу и подключим код:

можно не смотреть

<!DOCTYPE html> <html>     <head>         <title>Physics</title>         <script type="text/javascript" src="script.js"></script>     </head>     <body></body> </html>

Основное внимание уйдёт на файл с кодом программы script.js. Код для отрисовки откомментирован достаточно и он не касается темы:

посмотрим и забудем на время

var canvas, context; var HEIGHT = window.innerHeight, WIDTH = window.innerWidth;  document.addEventListener("DOMContentLoaded", main, true);  function main(){ // создаём холст на весь экран и прикрепляем его на страницу 	canvas = document.createElement('canvas'); 	canvas.height = HEIGHT; 	canvas.width = WIDTH; 	canvas.id = 'canvas'; 	canvas.style.position = 'absolute'; 	canvas.style.top = '0'; 	canvas.style.left = '0'; 	document.body.appendChild(canvas); 	context = canvas.getContext("2d"); 	/******* 	другой код 	*******/ }  function Draw(){     // очищение экрана     context.fillStyle = "#000000";     context.fillRect(0, 0, WIDTH, HEIGHT);          // рисование кругов     context.fillStyle = "#ffffff";     for(var i = 0; i < star.length; i++){         context.beginPath();                  context.arc(             star[i].x - star[i].r,             star[i].y - star[i].r,             star[i].r,             0,             Math.PI * 2         );                  context.closePath();         context.fill();     } } 

Теперь самое вкусное: код, который просчитывает физику.

На каждый объект мы будем хранить только массу, координаты и скорость. Ах да, ещё надо радиус — он нам понадобится для рассчёта столкновений, но об этом в следующей статье.

Итак, «класс» объекта будет таким:

function Star(){     this.x = 0;     this.y = 0;     this.vx = 0;     this.vy = 0;     this.r = 2; // Radius     this.m = 1; }

var star = new Array(); // в этом массиве будут храниться все объекты var count = 50; // начальное количество объектов var G = 1; // задаём константу методом подбора 

Генерация случайных объектов в самом начале:

var aStar; for(var i = 0; i < count; i++){     aStar = new Star();     aStar.x = Math.random() * WIDTH;     aStar.y = Math.random() * HEIGHT;     star.push(aStar); }

Шаг вычисляться будет в следующей функции:

function Step(){     var a, ax, ay, dx, dy, r;          // важно провести вычисление каждый с каждым     for(var i = 0; i < star.length; i++) // считаем текущей         for(var j = 0; j < star.length; j++) // считаем второй         {             if(i == j) continue;             dx = star[j].x - star[i].x;             dy = star[j].y - star[i].y;                          r = dx * dx + dy * dy;// тут R^2             if(r < 0.1) r = 0.1; // избегаем деления на очень маленькое число             a = G * star[j].m / r;                          r = Math.sqrt(r); // тут R             ax = a * dx / r; // a * cos             ay = a * dy / r; // a * sin                          star[i].vx += ax;             star[i].vy += ay;         }     // координаты меняем позже, потому что они влияют на вычисление ускорения     for(var i = 0; i < star.length; i++){         star[i].x += star[i].vx;         star[i].y += star[i].vy;     }          // выводим на экран     Draw(); }

Здесь уже проведены небольшие оптимизации, и dt принял за 1, поэтому исключил из операций умножения.

Ну и долгожданный запуск таймера:

timer = setInterval(Step, 20);

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

Минусы

Сложность алгоритма растёт экспоненциально, поэтому увеличение объектов влечёт заметное проседание FPS. Решение с помощью Quad tree или других алгоритмов не поможет, но в реальных играх не объекты взаимодействуют по принципу каждый с каждым.

Тестирование производилось на машине с процессором Intel Pentium с частотой 2.4 GHz. При 1000 объектов с интервал вычисления уже превышал 20 мс.

Использование

В качестве силы можно использовать суперпозицию разных сил в (3). Например, тягу двигателя, силу сопротивления грунта и воздуха, а также соударения с другими объектами. Алгоритм можно легко расширить на три измерения, достаточно ввести z аналогично x и y.

Этот алгоритм был написан мною ещё в 9 классе на паскале, а до текущего момента переложен на все языки, которые я знаю просто потому, что могу в качестве личного Hello World’a. Даже в терминале.

Также данный алгоритм можно использовать для другого фундаментального взаимодействия — электромагнитного (G → k, m → q). Я использовал этот алгоритм для построения линий магнитной индукции системы зарядов, но об этом в другой статье.

Всем спасибо за прочтение. Надеюсь данная статья Вам немного поможет в создании собственных игр.

ссылка на оригинал статьи http://habrahabr.ru/post/265211/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *