Ammo.js — это порт известного C++ движка физики Bullet Physics под JavaScript с использованием Emscripten. Он предоставляет мощный инструментарий для симуляции динамики твёрдых тел, столкновений и ограничений. Для создания сложных игровых сцен важным аспектом является правильная организация физических слоёв и фильтров, позволяющая оптимизировать расчёты и контролировать взаимодействие объектов.
В основе работы физики лежит дискретизация времени:
симуляция обновляется с фиксированным шагом deltaTime,
обычно около 1/60 секунды. Все объекты сцены добавляются в мир
физики (btDiscreteDynamicsWorld), который
управляет коллизиями и интеграцией сил.
let collisionConfiguration = new Ammo.btDefaultCollisionConfiguration();
let dispatcher = new Ammo.btCollisionDispatcher(collisionConfiguration);
let overlappingPairCache = new Ammo.btDbvtBroadphase();
let solver = new Ammo.btSequentialImpulseConstraintSolver();
let dynamicsWorld = new Ammo.btDiscreteDynamicsWorld(dispatcher, overlappingPairCache, solver, collisionConfiguration);
dynamicsWorld.setGravity(new Ammo.btVector3(0, -9.81, 0));
Здесь создаются ключевые компоненты физического мира: конфигурация коллизий, диспетчер столкновений, широкофазный алгоритм обнаружения столкновений и решатель ограничений.
Для управления сложными сценами используется система слоёв и
масок коллизий. Каждый объект получает уникальный
слой (collisionGroup) и набор слоёв, с
которыми он может сталкиваться (collisionMask). Это
позволяет:
Пример создания объекта с указанием слоя и маски:
let boxShape = new Ammo.btBoxShape(new Ammo.btVector3(1, 1, 1));
let transform = new Ammo.btTransform();
transform.setIdentity();
transform.setOrigin(new Ammo.btVector3(0, 10, 0));
let mass = 1;
let localInertia = new Ammo.btVector3(0, 0, 0);
boxShape.calculateLocalInertia(mass, localInertia);
let motionState = new Ammo.btDefaultMotionState(transform);
let rbInfo = new Ammo.btRigidBodyConstructionInfo(mass, motionState, boxShape, localInertia);
let body = new Ammo.btRigidBody(rbInfo);
let PLAYER_LAYER = 1 << 0;
let ENEMY_LAYER = 1 << 1;
let ENV_LAYER = 1 << 2;
// Игрок сталкивается только с врагами и окружением
dynamicsWorld.addRigidBody(body, PLAYER_LAYER, ENEMY_LAYER | ENV_LAYER);
Здесь 1 << 0, 1 << 1 и
1 << 2 — это битовые маски для разных слоёв. Функция
addRigidBody принимает collisionGroup и
collisionMask, определяя взаимодействия объекта с
остальными телами.
Для больших сцен полезно создавать динамические фильтры, которые включают или исключают группы объектов во время игры. Например, временное отключение коллизий между игроком и определёнными объектами для прохождения сквозь препятствия или создание “невидимых” зон взаимодействия.
function setCollisionMask(body, mask) {
dynamicsWorld.removeRigidBody(body);
dynamicsWorld.addRigidBody(body, body.getBroadphaseProxy().m_collisionFilterGroup, mask);
}
Такой подход позволяет изменять поведение объектов без пересоздания тела или его формы, минимизируя накладные расходы.
В больших сценах часто используется разделение на несколько физических миров для зон, удалённых друг от друга. Это снижает количество проверок столкновений:
btDbvtBroadphase)
для быстрой фильтрации потенциальных столкновений.Ammo.js поддерживает несколько типов коллизионных форм:
При использовании сложных форм важно учитывать массу и инерцию, чтобы симуляция оставалась стабильной. Сложные объекты с большой массой должны иметь корректно рассчитанную локальную инерцию для адекватного поведения при столкновениях.
В практических проектах Ammo.js используется для:
Правильное использование слоёв и масок позволяет создавать масштабируемые сцены, где тысячи объектов могут физически взаимодействовать без заметного падения производительности.
Фокус на структурированное разделение физических слоёв и грамотное управление масками коллизий является ключевым элементом при создании сложных динамических сцен в Ammo.js. Это не только улучшает производительность, но и открывает возможности для тонкой настройки логики взаимодействий внутри игры.