Паттерн игрового цикла: requestAnimationFrame + stepSimulation

Для использования Ammo.js сначала необходимо создать физический мир, определить его параметры и объекты. Основные элементы физического мира:

  • collision configuration — определяет, как объекты будут проверяться на столкновения.
  • dispatcher — отвечает за обработку столкновений между объектами.
  • broadphase — ускоряет обнаружение потенциальных столкновений.
  • solver — вычисляет физические реакции объектов.
  • dynamics world — объединяет все компоненты и управляет симуляцией.

Пример создания динамического мира:

const collisionConfiguration = new Ammo.btDefaultCollisionConfiguration();
const dispatcher = new Ammo.btCollisionDispatcher(collisionConfiguration);
const broadphase = new Ammo.btDbvtBroadphase();
const solver = new Ammo.btSequentialImpulseConstraintSolver();
const dynamicsWorld = new Ammo.btDiscreteDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration);

// Настройка гравитации
dynamicsWorld.setGravity(new Ammo.btVector3(0, -9.81, 0));

Создание физических тел

Объекты в Ammo.js представляют собой rigid bodies. Основные шаги:

  1. Создание collision shape — форма объекта.
  2. Определение mass (массы) и local inertia.
  3. Инициализация motion state.
  4. Создание rigid body construction info.
  5. Добавление объекта в мир.

Пример для куба:

const size = 1;
const mass = 1;

const shape = new Ammo.btBoxShape(new Ammo.btVector3(size / 2, size / 2, size / 2));
const localInertia = new Ammo.btVector3(0, 0, 0);
shape.calculateLocalInertia(mass, localInertia);

const transform = new Ammo.btTransform();
transform.setIdentity();
transform.setOrigin(new Ammo.btVector3(0, 10, 0));

const motionState = new Ammo.btDefaultMotionState(transform);
const rbInfo = new Ammo.btRigidBodyConstructionInfo(mass, motionState, shape, localInertia);
const body = new Ammo.btRigidBody(rbInfo);

dynamicsWorld.addRigidBody(body);

Паттерн игрового цикла с requestAnimationFrame

Для корректной работы физики и рендеринга применяется игровой цикл, который использует requestAnimationFrame. Ключевой момент — разделение:

  • обновление физики (stepSimulation)
  • синхронизация визуальных объектов с физическими
  • отрисовка сцены

Основная структура цикла:

function animate() {
    requestAnimationFrame(animate);

    // Шаг симуляции: время в секундах, maxSubSteps, fixedTimeStep
    const deltaTime = clock.getDelta(); // THREE.Clock, например
    dynamicsWorld.stepSimulation(deltaTime, 10, 1/60);

    // Обновление визуальных объектов
    updateGraphics();

    renderer.render(scene, camera);
}

animate();

Пояснения по параметрам stepSimulation:

  • deltaTime — время между кадрами.
  • maxSubSteps — максимальное количество шагов физики для одного кадра, предотвращает пропуск столкновений при лаге.
  • fixedTimeStep — фиксированный шаг симуляции (обычно 1/60 секунды).

Такой паттерн гарантирует стабильную симуляцию, даже если частота кадров нестабильна.

Синхронизация визуальных объектов

Визуальные объекты, например в Three.js, необходимо обновлять на основе состояния rigid body:

function updateGraphics() {
    const ms = body.getMotionState();
    if (ms) {
        const transform = new Ammo.btTransform();
        ms.getWorldTransform(transform);
        const p = transform.getOrigin();
        const q = transform.getRotation();
        mesh.position.set(p.x(), p.y(), p.z());
        mesh.quaternion.set(q.x(), q.y(), q.z(), q.w());
    }
}

Ключевой момент: визуальная синхронизация происходит после шага симуляции, чтобы движения объектов отображались корректно.

Управление производительностью

Ammo.js может быть ресурсоёмким при большом количестве объектов. Для оптимизации:

  • Использовать btDbvtBroadphase для динамических объектов.
  • Разделять объекты на статические и динамические.
  • Минимизировать сложные формы и использовать примитивы (box, sphere, capsule).
  • Ограничивать количество шагов через maxSubSteps и фиксированный fixedTimeStep.

Обработка столкновений

Ammo.js предоставляет доступ к контактным точкам через dispatcher:

const numManifolds = dispatcher.getNumManifolds();
for (let i = 0; i < numManifolds; i++) {
    const contactManifold = dispatcher.getManifoldByIndexInternal(i);
    const numContacts = contactManifold.getNumContacts();
    if (numContacts > 0) {
        const body0 = contactManifold.getBody0();
        const body1 = contactManifold.getBody1();
        // Логика реакции на столкновение
    }
}

Это позволяет реализовывать эффекты удара, триггеры и другие игровые механики.

Интеграция с графикой и анимацией

В связке с Three.js или Babylon.js физические объекты управляют только положением и вращением. Рендеринг остаётся отдельным слоем, обновляющимся после stepSimulation. Такой подход:

  • Разгружает GPU и CPU.
  • Позволяет использовать оптимизированные визуальные модели.
  • Поддерживает стабильную физику независимо от FPS.

Фиксированный и адаптивный шаг

Для стабильности используют фиксированный шаг симуляции, обычно 1/60 секунды. При высоком FPS шаг может повторяться несколько раз (maxSubSteps > 1). При низком FPS физика не теряет точность благодаря нескольким подшагам. Такой подход предотвращает «проваливание» объектов сквозь коллайдеры.


Статья охватывает ключевые аспекты использования Ammo.js с паттерном requestAnimationFrame + stepSimulation: создание мира, физических тел, игровой цикл, синхронизацию графики и оптимизацию производительности. Такой подход формирует стабильную и предсказуемую симуляцию физики в браузере.