16 августа 2014 г.

Пример интеграции рендерера pixi.js с Box2DJS

Интеграция физического движка Box2DJS с рендерерами совсем не сложна. Для примера можно взять популярный pixi.js.

Для начала создадим мир Box2d

// определяем ограничительный прямоугольник мира
var worldAABB = new b2AABB();
worldAABB.minVertex.Set(-1000, -1000);
worldAABB.maxVertex.Set(1000, 1000);

// определяем гравитацию
var gravity = new b2Vec2(0, 300);

// разрешаем телам быть в покое
var doSleep = true;

// создаем мир
var world = new b2World(worldAABB, gravity, doSleep);

// определяем время 1 шага эмуляции
var timeStep = 1 / 60;

// количество итераций для рассчетов эмуляции
var iteration = 1;

Затем, выполняем стандартный код для pixi.js из примеров, немного его расширив и модифицировав.

// создаем экземпляр сцены pixi
var stage = new PIXI.Stage(0x004466);

// создаем экземпляр рендерера (я определил 2d, но можно выставить WebGL или автомат)
var renderer = new PIXI.CanvasRenderer(400, 300);

// добавляем рендерер в DOM
document.body.appendChild(renderer.view);

requestAnimFrame(animate);

// создаем спрайты pixi

// создаем текстуру стен из файла
var bricksTexture = PIXI.Texture.fromImage("bricks.png");

// создаем спрайты стен
var leftWall = new PIXI.Sprite(bricksTexture);
var rightWall = new PIXI.Sprite(bricksTexture);

// определяем точки привязки стен
leftWall.anchor.x = 0.5;
leftWall.anchor.y = 0.5;
rightWall.anchor.x = 0.5;
rightWall.anchor.y = 0.5;

stage.addChild(leftWall);
stage.addChild(rightWall);

// создаем текстуру земли из файла
var grassTexture = PIXI.Texture.fromImage("grass.png");

// создаем спрайт земли
var ground = new PIXI.Sprite(grassTexture);

// определяем точку привязки земли
ground.anchor.x = 0.5;
ground.anchor.y = 0.5;

stage.addChild(ground);

// создаем текстуру кролика из файла
var bunnyTexture = PIXI.Texture.fromImage("bunny.png");

// создаем спрайт кролика
var bunny = new PIXI.Sprite(bunnyTexture);

// определяем точку привязки кролика
bunny.anchor.x = 0.5;
bunny.anchor.y = 0.5;

stage.addChild(bunny);

Затем, для большего удобства, определим функцию добавления тела в мир Box2d

function addBody(sprite, x, y, width, height, density) {
    // определение формы тела
    var shapeDef = new b2BoxDef();
    // размеры (из-за особенностей реализации Box2d, ополовиниваем размеры)
    shapeDef.extents.Set(width * 0.5, height * 0.5);
    // определение тела
    var bodyDef = new b2BodyDef();
    bodyDef.AddShape(shapeDef);
    bodyDef.position.Set(x, y);
    // если тело не статическое (имеет плотность)
    if (density) {
        shapeDef.density = density;
        // трение
        shapeDef.friction = 0.4;
        // упругость
        shapeDef.restitution = 1.2;
        // немного повернем
        bodyDef.rotation = 0.8;
    }
    body = world.CreateBody(bodyDef);
    // приколотим спрайт к телу
    body.m_userData = sprite;
}

Теперь можно очень резво добавить 4 тела сразу.

addBody(ground, 200, 292, 400, 16);
addBody(leftWall, 5, 150, 10, 300);
addBody(rightWall, 395, 150, 10, 300);
addBody(bunny, 200, 150, 25, 37, 0.5);

Для рисования тел мира Box2d на холсте определим специальную функцию.

function draw() {
    var body, sprite;
    for (body = world.m_bodyList; body; body = body.m_next) {
        // выбираем спрайт из тела
        sprite = body.GetUserData();
        if (sprite) {
            sprite.position = body.GetCenterPosition();
            sprite.rotation = body.GetRotation();
        }
    }
}

Эта функция очень прямолинейна. Вроде и объяснять здесь нечего.

Наконец, рисуем всё наше творчество на холсте раз в 17 мс примерно.

function animate() {
    requestAnimFrame(animate);

    // вычисляем шаг эмуляции Box2d
    world.Step(timeStep, iteration);

    // рисуем спрайты pixi, руководствуясь новыми параметрами мира Box2d
    draw();

    // рисуем сцену pixi
    renderer.render(stage);
}

На этапе разработки сцены может понадобиться отладочная отрисовка Box2d. Для этого после определения var renderer следует назначить контекст рисования:

world.SetDebugDraw({
    ctx : renderer.view.getContext('2d'),
    width : 400,
    height : 300
});

А в функции animate убрать вызовы draw() и renderer.render(stage) добавив при этом:

world.DebugDraw();

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

2 комментария:

Анонимный комментирует...

for (body = world.m_bodyList; body; body = body.m_next) {...}
Что-то туплю, как ьакой цикл на CoffeeScript написать?

Иван комментирует...

Можно что-то типа:
body = world.m_bodyList
while body
# do staff
body = body.m_next

Отправить комментарий