Практические сценарии: урон, звук, эффекты при ударе

Для создания реалистичного взаимодействия объектов в 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);
    }
}

Совет: для высокоинтенсивных ударов увеличивать количество частиц и использовать более яркую подсветку.


Комбинирование эффектов в реальном времени

Эффективное сочетание урона, звука и визуальных эффектов требует следующей последовательности:

  1. Шаг симуляции: physicsWorld.stepSimulation(deltaTime).
  2. Обработка контактов: проверка импульса и дистанции.
  3. Применение урона: изменение здоровья объекта.
  4. Запуск аудио: на основе силы удара.
  5. Визуальные эффекты: генерация частиц в точке контакта.
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);

Эта техника используется для объектов вроде ящиков, скальных обломков и автомобилей.


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

При большом количестве эффектов важно:

  • Ограничивать число аудиоисточников одновременно.
  • Группировать частицы и использовать particle pooling.
  • Шаг симуляции адаптировать под FPS: physicsWorld.stepSimulation(deltaTime, maxSubSteps, fixedTimeStep).
physicsWorld.stepSimulation(deltaTime, 10, 1/60);

Такой подход сохраняет стабильность симуляции и уменьшает вероятность артефактов при сильных столкновениях.