Это можно сделать с помощью Promise.all
. Мы дадим новое обещание для каждого изображения, которое мы хотим загрузить, разрешая при вызове onload
. Как только Promise.all
разрешится, мы можем вызвать нашу функцию initialize
и продолжить с нашей логикой. Это позволяет избежать условий гонки, когда основной игровой цикл requestAnimationFrame
вызывается из bird.onload
, но возможно, что конвейерные сущности и т. Д. Еще не загружены.
Вот минимальный полный пример:
const imageUrls = [
"http://placekitten.com/90/100",
"http://placekitten.com/90/130",
"http://placekitten.com/90/160",
"http://placekitten.com/90/190",
];
const initialize = images => {
// images are loaded here and we can go about our business
const canvas = document.createElement("canvas");
document.body.appendChild(canvas);
canvas.width = 400;
canvas.height = 200;
const ctx = canvas.getContext("2d");
Object.values(images).forEach((e, i) =>
ctx.drawImage(e, i * 100, 0)
);
};
Promise.all(imageUrls.map(e =>
new Promise((resolve, reject) => {
const img = new Image();
img.src = e;
img.onload = () => resolve(img);
img.onerror = reject;
})
)).then(initialize);
Обратите внимание, что в приведенном выше примере я использовал массив для хранения изображений. Проблема, которую это решает, заключается в том, что шаблон
var foo = ...
var bar = ...
var baz = ...
var qux = ...
foo.src = ...
bar.src = ...
baz.src = ...
qux.src = ...
foo.onload = ...
bar.onload = ...
baz.onload = ...
qux.onload = ...
чрезвычайно сложен в управлении и масштабировании. Если вы решите добавить еще одну вещь в игру, то для ее учета необходимо переписать код, и игровая логика станет очень мокрой . Ошибки становятся трудно обнаружить и устранить. Кроме того, если нам нужно конкретное изображение, мы бы предпочли обращаться к нему, например, images.bird
, а не images[1]
, сохраняя семантику отдельных переменных, но давая нам возможность циклически проходить через объект и вызывать * 1025 каждой сущности. * функция, например.
Все это мотивирует объект для агрегирования игровых объектов. Некоторая информация, которую мы хотели бы иметь для каждой сущности, могла бы включать, например, текущую позицию сущности, статус «мертв / жив», функции для перемещения и рендеринга и т. Д.
Это также хорошая идея иметь какую-тоотдельного объекта необработанных данных, который содержит все начальное игровое состояние (обычно это будет внешний файл JSON).
Очевидно, что это может превратиться в значительный рефакторинг, но это необходимый шаг, когда игра выходит за рамкималенький (и мы можем постепенно принять эти идеи дизайна). Как правило, это хорошая идея, чтобы прикусить пулю впереди.
Вот подтверждение концепции, иллюстрирующее некоторые из рассуждений выше. Надеемся, что это дает некоторые идеи о том, как вы можете управлять состоянием игры и логикой.
const entityData = [
{
name: "foo",
path: "http://placekitten.com/80/80",
x: 0,
y: 0
},
{
name: "baz",
path: "http://placekitten.com/80/150",
x: 0,
y: 90
},
{
name: "quux",
path: "http://placekitten.com/100/130",
x: 90,
y: 110
},
{
name: "corge",
path: "http://placekitten.com/200/240",
x: 200,
y: 0
},
{
name: "bar",
path: "http://placekitten.com/100/100",
x: 90,
y: 0
}
/* you can add more properties and functions
(movement, etc) to each entity
... try adding more entities ...
*/
];
const entities = entityData.reduce((a, e) => {
const img = new Image();
img.src = e.path;
a[e.name] = {...e, image: img};
return a;
}, {});
const initialize = () => {
const canvas = document.createElement("canvas");
document.body.appendChild(canvas);
canvas.width = innerWidth;
canvas.height = innerHeight;
const ctx = canvas.getContext("2d");
for (const key of Object.keys(entities)) {
entities[key].alpha = Math.random();
}
(function render () {
ctx.clearRect(0, 0, canvas.width, canvas.height);
Object.values(entities).forEach(e => {
ctx.globalAlpha = Math.abs(Math.sin(e.alpha += 0.005));
ctx.drawImage(e.image, e.x, e.y);
ctx.globalAlpha = 1;
});
requestAnimationFrame(render);
})();
};
Promise.all(Object.values(entities).map(e =>
new Promise((resolve, reject) => {
e.image.onload = resolve;
e.image.onerror = reject;
})
)).then(initialize);