Некоторые дополнительные очки.
Существующий ответ решит вашу проблему, однако у меня есть время и я заметил некоторые моменты в вашем коде, которые можно улучшить.
Производительность - король
При написании игр (или, если уж на то пошло, анимации контента) вы попадете в точку, где сложность анимации (количество анимированных и нарисованных элементов) достигает стадии, когда устройство больше не можетсделать это на полной частоте кадров.Это становится большей проблемой, когда вы пытаетесь охватить больший диапазон устройств.
Чтобы получить максимальную скорость на строку кода в Javascript, вы должны знать о некоторых простых правилах в отношении объектов и того, как онисоздаются и уничтожаются (освобождают память для других объектов).
Управляется JavaScript
Это означает, что программисту не нужно беспокоиться о памяти.Вы создаете объект, и память для него найдена для вас.Когда вам больше не нужен объект, javascript очистит память, чтобы освободить его для других объектов.
Это облегчает программирование в Javascript.Однако в анимации это может стать проблемой, поскольку код, который управляет выделением и очисткой памяти (AKA удаляет выделенную память или сборщик мусора GC), требует времени.Если вашей анимации требуется много времени для вычисления и рендеринга каждого кадра, тогда GC вынужден блокировать ваш сценарий и очистку.
Такое управление памятью является крупнейшим источником Jank в анимациях (играх) Javascript.
Также вводится дополнительная обработка для создания объектов, так как при создании нового объекта должна быть расположена и выделена свободная память.Это ухудшается на устройствах низкого уровня с небольшим количеством RAM.Создание новых объектов чаще заставит GC освободить память для нового объекта (украсть драгоценные циклы ЦП).Это делает производительность на младших устройствах не линейным, а скорее логарифмическим.
Типы объектов.
Объекты (игрок, пикапы, патроны, FX) можно классифицировать по тому, как долго онижить и сколько может существовать за один раз.Время жизни объекта означает, что вы можете воспользоваться преимуществами того, как JS управляет памятью, для оптимизации объектов и использования памяти.
Один экземпляр объекта.
Это объекты, которые существуют только один раз ванимация.То есть иметь время жизни от начала до конца (в игре, которая может быть от начала уровня до конца уровня).
Например, игрок, отображение счета.
Пример
Наилучший способ создания этих объектов - это создание одноэлементной или объектной фабрики.В приведенном ниже примере с маркерами для создания игрока используется одноэлементная
фабрика объектов EG
function Shooter() {
// Use closure to define the properties of the object
var x = 100;
var y = 500;
const size = 50;
const style = {
fillStyle : "blue",
strokeStyle : "black",
lineWidth : 5,
}
// the interface object defines functions and properties that
// need to be accessed from outside this function
const API = {
draw() {
ctx.fillStyle = style.fillStyle;
ctx.strokeStyle = style.strokeStyle;
ctx.lineWidth = style.lineWidth;
ctx.fillRect(x, y, size, size);
ctx.strokeRect(x, y, size, size);
// it is quicker to do the above two lines as
/*
ctx.beginPath(); // this function is done automatically
// for fillRect and strokeRect. It
ctx.rect(x, y, size, size);
ctx.fill();
ctx.stroke();
*/
}
}
return API;
}
Вы используете его, как и любой другой объект
const player = new Shooter();
// or
const player = Shooter(); / You dont need the new for this type of object
// to draw
player.draw();
Много экземпляров объекта.
Это объекты с очень коротким сроком службы, возможно, несколько кадров.Они также могут существовать сотнями (например, искры FX во взрыве или быстрые огненные пули)
В вашем коде у вас есть только одна пуля, но я могу представить, что у вас может быть много, или, скорее, чемпули, это могут быть gribble или искры FX.
Instantiation
Создание объектов требует циклов ЦП.Современный JS имеет много оптимизаций и, следовательно, нет большой разницы в том, как вы создаете объекты.Но есть разница, и использование лучшего метода окупается, особенно если вы делаете это 1000 раз в секунду.(последняя написанная мной игра JS обрабатывает до 80 000 объектов FX в секунду, большинство живут не более 3-6 кадров)
Для многих недолговечных объектов задайте прототип или используйте синтаксис класса (это сокращает время создания наоколо 50%).Храните элементы в пуле, когда они не используются, чтобы остановить попадания в GC и уменьшить накладные расходы на создание экземпляров.Будьте умны при рендеринге и не тратьте впустую время, ожидая, пока графический процессор сделает бессмысленные изменения состояния.
Память
Эти недолговечные объекты являются основным источником замедления, и JANK из-за накладных расходов на управление памятью создает и удаляет их.
Для борьбы с накладными расходами на управление памятью вы можете сделать это самостоятельно.Лучший способ сложен (используйте предварительно выделенный (при начале уровня) пузырьковый массив и задайте максимальное число для каждого объекта на уровне).
Пул объектов
Самое простое решение, которое обеспечит вам 90% лучших и дает неограниченные возможности (зависит от общего объема ОЗУ), - это использование пулов объектов.
Пул - это массив неиспользуемых объектов, которые вы обычно позволяете GCудалять.Активные объекты хранятся в массиве.Они делают свое дело, и когда они сделаны, они перемещаются из массива объектов в пул.
Когда вам нужен новый объект, а не создаете его с помощью new Bullet()
, вы сначала проверяете, есть ли у пула такой объект.Если это так, вы берете старый объект из пула, сбрасываете его свойства и помещаете его в активный массив.Если пул пуст, вы создаете новый объект.
Это означает, что вы никогда не удаляете объект (за время существования уровня / анимации).Поскольку вы проверяете пул каждый раз, когда создаете максимальный объем памяти, который будут использовать маркеры, он будет на самом деле меньше, чем каждый раз при создании новых объектов (GC не удаляет сразу)
Рендеринг
2D-контекст - это отличный API для рендеринга, но он плохо используется, и вы можете убить частоту кадров, не изменяя визуализированный вид.
Если у вас много объектов, которые используют один и тот же стиль.Не отображайте их как отдельный путь.Определив один путь, добавьте объекты, затем заполните и обведите.
Пример
Пример пула быстрого огня.Все пули имеют одинаковый стиль.Этот интерфейс скрывает маркеры от основного кода.Вы можете получить доступ только к API пули
const bullets = (() => { // a singleton
function Bullet() { }
const radius = 5;
const startLife = 100;
this.radius = 5;
const style = {
fillStyle : "orange",
strokeStyle : "green",
lineWidth : 2,
}
// to hold the interface
Bullets.prototype = {
init(x,y,dx,dy) { // dx,dy are delta
this.life = startLife;
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
},
draw() {
ctx.arc(this.x, this.y, radius, 0 , Math.PI * 2);
},
move() {
this.x += this.dx;
this.y += this.dy;
this.life --;
}
};
const pool = []; // holds unused bullets
const bullets = []; // holds active bullets
// The API that manages the bullets
const API = {
fire(x,y,dx,dy) {
var b;
if(pool.length) {
b = bullets.pop();
} else {
b = new Bullet();
}
b.init(x,y,dx,dy);
bullets.push(bullets); // put on active array
},
update() {
var i;
for(i = 0; i < bullets.length; i ++) {
const b = bullets[i];
b.move();
if(b.life <= 0) { // is the bullet is no longer needed move to the pool
pool.push(bullets.splice(i--, 1)[0]);
}
}
},
draw() {
ctx.lineWidth = style.lineWidth;
ctx.fillStyle = style.fillStyle;
ctx.strokeStyle = style.strokeStyle;
ctx.beginPath();
for(const b of bullets) { b.draw() }
ctx.fill();
ctx.stroke();
},
get count() { return bullets.length }, // get the number of active
clear() { // remove all
pool.push(...bullets); // move all active to the pool;
bullets.length = 0; // empty the array;
},
reset() { // cleans up all memory
pool.length = 0;
bullets.length = 0;
}
};
return API;
})();
Для использования
... в функции огня
// simple example
bullets.fire(gun.x, gun.y, gun.dirX, gun.dirY);
... в главном цикле рендеринга
bullets.update(); // update all bullets
if(bullets.count) { // if there are bullets to draw
bullets.draw();
}
... если перезапустить уровень
bullets.clear(); // remove bullets from previouse play
... если в конце уровня освободить память
bullets.clear();
Где-то между объектом.
Это объекты, которые лежат где-то посередине между двумя вышеуказанными типами,
Например, бонусы, фоновые предметы, агенты ИИ противника.
Если объект не создаетсяв больших количествах, и иметь жизни, которые могут длиться от секунды до менее полного уровня, вам нужно убедиться, что они могут быть созданы с использованием оптимального метода.(Лично я использую пулы (пузырьковый массив) для всех, кроме объектов, которые живут для жизни уровня, но это может привести к большому количеству кода)
Определить прототип
Существует два способа эффективного создания этих объектов.Используя синтаксис класса (лично ненавижу это дополнение к JS) или определите прототип вне функции создания экземпляра.
Пример
function Gun(player, bullets) {
this.owner = player;
this.bullets = bullets; // the bullet pool to use.
this.x = player.x + player.size / 2 + 10;
this.y = player.y + player.size / 2;
this.width = 20;
this.height = 10;
const style = {
fillStyle : "grey",
strokeStyle : "brown",
lineWidth : 1,
};
}
// Moving the API to the prototype improves memory use and makes creation a little quicker
Gun.prototype = {
update() {
this.x = this.owner.x + this.owner.size / 2 + 10;
this.y = this.owner.y + this.owner.size / 2;
},
draw() {
ctx.lineWidth = this.style.lineWidth;
ctx.fillStyle = this.style.fillStyle;
ctx.strokeStyle = this.style.strokeStyle;
ctx.beginPath();
ctx.rect(this.x,this.y,this.width,this.height);
ctx.fill();
ctx.stroke();
},
shoot() {
this.bullets.fire(this.x, this.y, 10, 0);
},
}
Надеюсь, это поможет.:)