Сцены с большим числом объектов: пулинг и переиспользование

При создании сложных физически интерактивных сцен на основе Oimo.js часто возникает необходимость работать с сотнями и тысячами объектов одновременно. Простое создание новых тел каждый раз приводит к резкому снижению производительности, так как выделение памяти и инициализация физического тела — ресурсоёмкая операция. Решением становится пулинг объектов и их переиспользование.


Принцип пулинга объектов

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

Основные преимущества пулинга:

  • Сокращение затрат на выделение и удаление памяти.
  • Снижение нагрузки на сборщик мусора.
  • Повышение стабильности производительности при множественных объектах.

Простейшая структура пула:

class ObjectPool {
    constructor(createFunc, size = 100) {
        this.pool = [];
        this.createFunc = createFunc;
        for (let i = 0; i < size; i++) {
            this.pool.push(createFunc());
        }
    }

    acquire() {
        return this.pool.length > 0 ? this.pool.pop() : this.createFunc();
    }

    release(obj) {
        this.pool.push(obj);
    }
}

Здесь createFunc — функция создания нового тела Oimo.js, а size — начальный размер пула. Метод acquire() берёт объект из пула, а release() возвращает его обратно.


Создание тел Oimo.js для пула

При работе с физическими телами важно учитывать тип тела и его параметры. Пример функции для создания кубического тела:

function createBoxBody(world) {
    const body = new OIMO.Body({
        type: "dynamic",
        size: [1, 1, 1],
        pos: [0, 0, 0],
        move: true
    });
    world.addBody(body);
    body.sleep(); // Изначально тело в "сне"
    return body;
}

Использование sleep() позволяет телу не участвовать в расчётах до момента активации, что экономит ресурсы.


Активация и деактивация тел

При извлечении тела из пула нужно:

  1. Сбросить его позицию и ориентацию.
  2. Активировать тело с помощью awake().
function activateBody(body, position, rotation) {
    body.setPosition(position);
    body.setQuaternion(rotation);
    body.awake();
}

После использования тело можно деактивировать и вернуть в пул:

function deactivateBody(pool, body) {
    body.sleep();
    pool.release(body);
}

Организация пулинга для множества объектов

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

const bulletPool = new ObjectPool(() => createBoxBody(world), 200);

function spawnBullet(position, velocity) {
    const bullet = bulletPool.acquire();
    activateBody(bullet, position, new OIMO.Quat());
    bullet.setLinearVelocity(velocity);
    activeBullets.push(bullet);
}

function updateBullets(deltaTime) {
    for (let i = activeBullets.length - 1; i >= 0; i--) {
        const bullet = activeBullets[i];
        if (bullet.getPosition()[1] < -10) { // за пределами сцены
            deactivateBody(bulletPool, bullet);
            activeBullets.splice(i, 1);
        }
    }
}

Такой подход позволяет поддерживать десятки или сотни активных объектов, не создавая новые тела каждый кадр.


Переиспользование тел с разными формами

Для более сложных объектов можно создать пул для каждого типа тела: куб, сфера, цилиндр и т.д. Для динамического изменения формы используется метод setShape(), но стоит помнить, что частая смена формы может быть дороже, чем отдельный пул для каждого типа.

const spherePool = new ObjectPool(() => {
    const body = new OIMO.Body({
        type: "dynamic",
        size: [1, 1, 1],
        shape: "sphere",
        move: true
    });
    world.addBody(body);
    body.sleep();
    return body;
}, 100);

Эффективное обновление и управление объектами

  • Хранение активных объектов в отдельном массиве позволяет быстро проходить по ним при обновлении сцены.
  • Использование методов sleep() и awake() экономит вычислительные ресурсы.
  • Оптимальная организация пула снижает количество новых аллокаций и обращений к сборщику мусора.

Рекомендации по производительности

  • Минимизировать создание тел во время основного игрового цикла.
  • Группировать обновления и проверку состояния объектов.
  • Выделять отдельные пулы для однотипных объектов вместо одного универсального пула.
  • Использовать физические тела Oimo.js с правильными параметрами move и sleep для контроля нагрузки на движок.

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