Вас интересуют игры? Хотите создать игру но не знаете с чего начать? Тогда вам сюда. В этой статье я рассмотрю простейший физический движок, с построения которого можно начать свой путь в 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/
Добавить комментарий