Использование событий для игровой логики

Cannon.js — это физический движок для JavaScript, ориентированный на реалистичное моделирование тел в 3D-пространстве. Одним из мощных инструментов для создания интерактивной игровой логики является система событий. События позволяют реагировать на изменения в физическом мире, коллизии, обновления объектов и другие действия без постоянного опроса состояния объектов в цикле.

Каждое тело (Body) и мир (World) в Cannon.js поддерживают механизм событий через методы addEventListener и removeEventListener. События бывают системными (например, шаг симуляции) и пользовательскими (например, реакция на столкновение).

world.addEventListener('postStep', function() {
    console.log('Шаг симуляции завершён');
});

Метод postStep вызывается после каждого шага симуляции. Это позволяет обновлять логику игры синхронно с физикой.


События столкновений

Основное применение событий в игровых механиках — обработка столкновений. Cannon.js предоставляет событие collide, которое можно использовать на уровне тела:

ball.addEventListener('collide', function(event) {
    const bodyB = event.body; // тело, с которым произошло столкновение
    const contact = event.contact; // информация о контакте: точки, нормали, импульс
    console.log('Столкновение с телом:', bodyB.id);
});

Ключевые свойства события collide:

  • event.body — объект типа Body, участвующий в столкновении.

  • event.contact — объект ContactEquation, содержащий:

    • ni — нормаль столкновения;
    • ri, rj — точки приложения силы относительно центров масс;
    • impactVelocity — скорость столкновения.
  • event.target — тело, на котором был зарегистрирован обработчик.

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


События на уровне мира

Помимо коллизий, World генерирует события, полезные для глобальной игровой логики:

  • postStep — после каждого шага симуляции.
  • beginContact и endContact — начало и конец контакта между телами (реже используется напрямую, чаще через сторонние расширения).
  • addBody и removeBody — добавление и удаление тел из мира.
world.addEventListener('addBody', function(event) {
    console.log('Новое тело добавлено в мир:', event.body.id);
});

Эти события позволяют вести учёт объектов и управлять их состоянием централизованно.


Связь с игровой логикой

События становятся ядром игровой механики:

  1. Система очков и здоровья: при столкновении игрока с опасным объектом можно уменьшать здоровье.
  2. Триггеры и зоны действия: можно создать невидимые тела с коллайдерами, которые активируют события при пересечении.
  3. Эффекты и анимации: запуск частиц, воспроизведение звуков или анимаций при столкновениях или изменении состояния объекта.
  4. Физические реакции: изменение массы, силы или положения тела после контакта.

Пример системы триггеров:

const trigger = new CANNON.Body({ mass: 0, shape: new CANNON.Box(new CANNON.Vec3(1,1,1)) });
trigger.addEventListener('collide', function(event) {
    console.log('Игрок вошёл в зону триггера!');
});
world.addBody(trigger);

Создание пользовательских событий

Cannon.js позволяет расширять базовую модель событий, создавая собственные триггеры. Для этого можно вручную вызывать функцию dispatchEvent на объекте Body или World.

playerBody.dispatchEvent({ type: 'powerUp', data: { amount: 50 } });

playerBody.addEventListener('powerUp', function(event) {
    console.log('Сбор бонуса! Энергия увеличена на', event.data.amount);
});

Таким образом, физические объекты могут становиться источниками пользовательской логики, не нарушая основного цикла симуляции.


Практические рекомендации

  • Использовать collide для всех интерактивных объектов, требующих реакции на столкновение.
  • Использовать postStep для обновления логики, которая зависит от физики, но не от конкретного столкновения.
  • Не создавать слишком много глобальных обработчиков, чтобы не снижать производительность.
  • Для сложных триггеров применять невидимые тела с масcой 0 и отключённой гравитацией, чтобы они не влияли на физику других объектов.
  • Сохранять ссылки на обработчики, если планируется их удаление через removeEventListener.

Оптимизация работы с событиями

При больших сценах с множеством динамических объектов следует:

  • Фильтровать события по типу или тегу объекта.
  • Использовать “делегирование” — один обработчик на уровне мира вместо множества на каждом теле.
  • Избегать вычислений внутри коллбэков, требующих сложных циклов; лучше отмечать факт события и обрабатывать его в следующем шаге симуляции.

Интеграция с визуализацией

Для визуальных движков, таких как Three.js, события Cannon.js позволяют синхронизировать модель физики с 3D-сценой:

world.addEventListener('postStep', function() {
    mesh.position.copy(ball.position);
    mesh.quaternion.copy(ball.quaternion);
});

Это обеспечивает точное соответствие между физическим и графическим состояниями объектов, что критично для реалистичной игровой механики.