Как стать автором
Обновить

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

Время на прочтение 4 мин
Количество просмотров 64K
Вас интересуют игры? Хотите создать игру но не знаете с чего начать? Тогда вам сюда. В этой статье я рассмотрю простейший физический движок, с построения которого можно начать свой путь в 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 * dt;
            star[i].vy += ay * dt;
        }
    // координаты меняем позже, потому что они влияют на вычисление ускорения
    for(var i = 0; i < star.length; i++){
        star[i].x += star[i].vx * dt;
        star[i].y += star[i].vy * dt;
    }
    
    // выводим на экран
    Draw();
}


Ну и долгожданный запуск таймера:
var dt = 0.02; // шаг вычисления
timer = setInterval(Step, dt * 1000);


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

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

Минусы


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

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

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


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

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

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

Всем спасибо за прочтение. Надеюсь данная статья Вам немного поможет в создании собственных игр.
Курс держим примерное на такое — спиральная галактика.
Теги:
Хабы:
+22
Комментарии 38
Комментарии Комментарии 38

Публикации

Истории

Работа

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн