Для создания реалистичного взаимодействия объектов в Ammo.js необходимо сначала инициализировать физический мир. Классическая схема включает создание конфигурации, диспатчера столкновений, broadphase и solver:
let collisionConfiguration = new Ammo.btDefaultCollisionConfiguration();
let dispatcher = new Ammo.btCollisionDispatcher(collisionConfiguration);
let broadphase = new Ammo.btDbvtBroadphase();
let solver = new Ammo.btSequentialImpulseConstraintSolver();
let physicsWorld = new Ammo.btDiscreteDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration);
physicsWorld.setGravity(new Ammo.btVector3(0, -9.81, 0));
Ключевой момент: все объекты в мире должны быть
связаны с rigid body и collision shape. Для динамических объектов обычно
используется btBoxShape, btSphereShape или
btConvexHullShape.
let boxShape = new Ammo.btBoxShape(new Ammo.btVector3(1, 1, 1));
let transform = new Ammo.btTransform();
transform.setIdentity();
transform.setOrigin(new Ammo.btVector3(0, 5, 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 rigidBody = new Ammo.btRigidBody(rbInfo);
physicsWorld.addRigidBody(rigidBody);
Для реализации механики урона важно отслеживать коллизии и скорость
столкновения. Ammo.js позволяет использовать
getDispatcher для доступа к контактам после шага
симуляции.
physicsWorld.stepSimulation(deltaTime, 10);
let numManifolds = dispatcher.getNumManifolds();
for (let i = 0; i < numManifolds; i++) {
let contactManifold = dispatcher.getManifoldByIndexInternal(i);
let rbA = Ammo.castObject(contactManifold.getBody0(), Ammo.btRigidBody);
let rbB = Ammo.castObject(contactManifold.getBody1(), Ammo.btRigidBody);
let numContacts = contactManifold.getNumContacts();
for (let j = 0; j < numContacts; j++) {
let pt = contactManifold.getContactPoint(j);
if (pt.getDistance() < 0) {
let impulse = pt.getAppliedImpulse();
// Расчёт урона на основе импульса
let damage = impulse * 0.1;
}
}
}
Примечание: импульс пропорционален силе удара. Можно использовать дополнительные коэффициенты, например, для брони или сопротивления материала.
Для звука важно учитывать не только факт столкновения, но и силу импульса, чтобы различать слабые и сильные удары.
function playImpactSound(impulse) {
if (impulse > 5) {
let volume = Math.min(impulse / 20, 1);
let audio = new Audio('hit_strong.mp3');
audio.volume = volume;
audio.play();
} else if (impulse > 1) {
let audio = new Audio('hit_weak.mp3');
audio.volume = impulse / 5;
audio.play();
}
}
Связь с физикой реализуется через цикл обработки коллизий:
let impulse = pt.getAppliedImpulse();
playImpactSound(impulse);
Особенность: плавная линейная зависимость громкости от силы удара создаёт ощущение реализма.
Визуальные эффекты включают искры, трещины, вспышки и частицы. Их
можно привязывать к точке контакта из getContactPoint.
let position = pt.getPositionWorldOnB();
spawnParticleEffect(position, impulse);
Пример функции генерации частиц:
function spawnParticleEffect(position, impulse) {
let particleCount = Math.floor(impulse * 2);
for (let i = 0; i < particleCount; i++) {
let particle = createParticle();
particle.position.set(position.x(), position.y(), position.z());
particle.velocity.set(
(Math.random() - 0.5) * impulse,
Math.random() * impulse,
(Math.random() - 0.5) * impulse
);
scene.add(particle);
}
}
Совет: для высокоинтенсивных ударов увеличивать количество частиц и использовать более яркую подсветку.
Эффективное сочетание урона, звука и визуальных эффектов требует следующей последовательности:
physicsWorld.stepSimulation(deltaTime).function updatePhysics(deltaTime) {
physicsWorld.stepSimulation(deltaTime, 10);
let numManifolds = dispatcher.getNumManifolds();
for (let i = 0; i < numManifolds; i++) {
let manifold = dispatcher.getManifoldByIndexInternal(i);
let numContacts = manifold.getNumContacts();
for (let j = 0; j < numContacts; j++) {
let pt = manifold.getContactPoint(j);
if (pt.getDistance() < 0) {
let impulse = pt.getAppliedImpulse();
applyDamage(manifold, impulse);
playImpactSound(impulse);
spawnParticleEffect(pt.getPositionWorldOnB(), impulse);
}
}
}
}
Оптимизация: для большого количества объектов стоит использовать broadphase для фильтрации потенциальных столкновений и минимизировать количество дорогостоящих расчётов частиц и звука.
Реалистичное поведение зависит от коэффициентов трения и упругости:
rigidBody.setFriction(0.5); // трение поверхности
rigidBody.setRestitution(0.7); // упругость удара
Применение: высокая упругость создаёт эффект отскока, низкая — мягкие столкновения с большим поглощением энергии. Эти параметры критически влияют на вычисление урона и реализм звуковых эффектов.
Для объектов с нестандартной геометрией рекомендуется
btConvexHullShape или составные
btCompoundShape. Это позволяет:
let compoundShape = new Ammo.btCompoundShape();
let childTransform = new Ammo.btTransform();
childTransform.setIdentity();
let childShape = new Ammo.btBoxShape(new Ammo.btVector3(1,1,1));
compoundShape.addChildShape(childTransform, childShape);
Эта техника используется для объектов вроде ящиков, скальных обломков и автомобилей.
При большом количестве эффектов важно:
physicsWorld.stepSimulation(deltaTime, maxSubSteps, fixedTimeStep).physicsWorld.stepSimulation(deltaTime, 10, 1/60);
Такой подход сохраняет стабильность симуляции и уменьшает вероятность артефактов при сильных столкновениях.