Следуя туториалу с тем же точным кодом, но выводя эту ошибку на консоль при попытке нарисовать изображение на холсте:
SpriteSheet.js:30 Uncaught (in promise) TypeError: Failed to execute 'drawImage' on 'CanvasRenderingContext2D': The provided value is not of type '(CSSImageValue or HTMLImageElement or SVGImageElement or HTMLVideoElement or HTMLCanvasElement or ImageBitmap or OffscreenCanvas)
Понятия не имею, почему это происходит? Первое изображение хорошо прорисовывается на холсте, но второе не отображается и получает эту ошибку. Использование обещаний, так что все изображения должны быть загружены перед их использованием, верно? Он отлично работает, если я изменю конец файла script.js на drawBackground(level.backgrounds[1], context, sprites);
script.js
import SpriteSheet from './SpriteSheet.js';
import {loadImage, loadLevel} from './loaders.js';
function drawBackground(background, context, sprites) {
background.ranges.forEach(([x1, x2, y1, y2]) => {
for (let x = x1; x < x2; ++x) {
for (let y = y1; y < y2; ++y) {
sprites.drawTile(background.tile, context, x, y);
}
}
});
}
function loadBackgroundSprites() {
return loadImage('SEA01.png').then(image => {
const sprites = new SpriteSheet(image, 16, 16);
sprites.define('ocean', 0, 0);
return sprites;
});
return loadImage('ground.png').then(image=> {
const sprites = new SpriteSheet(image, 16, 16);
sprites.define('ground', 12, 0);
});
}
const canvas = document.getElementById('gamearea');
const context = canvas.getContext('2d');
Promise.all([
loadBackgroundSprites(),
loadLevel('1-1')
]).then(([sprites,level]) => {
console.info(level);
drawBackground(level.backgrounds[0], context, sprites);
});
loader.js
export function loadImage(url) {
return new Promise(resolve => {
const image = new Image();
image.addEventListener('load', () => {
resolve(image);
});
image.src = url;
});
}
export function loadLevel(name) {
return fetch(`/levels/${name}.json`)
.then(r => r.json());
}
spritesheet.js
export default class SpriteSheet {
constructor(image, w = 16, h = 16) {
this.image = image;
this.width = w;
this.height = h;
this.tiles = new Map();
}
define(name, x, y) {
const buffer = document.createElement('canvas');
buffer.height = this.height;
buffer.width = this.width;
buffer
.getContext('2d')
.drawImage(
this.image,
this.width * x,
this.height * y,
this.width,
this.height,
0,
0,
this.width,
this.height);
this.tiles.set(name, buffer);
}
draw(name, context, x, y) {
const buffer = this.tiles.get(name);
context.drawImage(buffer, x, y);
}
drawTile(name, context, x, y) {
this.draw(name, context, x * this.width, y * this.height);
}
}
1-1.json
{
"backgrounds": [
{
"tile": "ocean",
"ranges": [
[
0, 50,
0, 25
]
]
},
{
"tile": "ground",
"ranges": [
[
18, 25,
10, 15
]
]
}
]
}
Заранее спасибо.
Обновлено:
Я обновил свой код, чтобы загружать изображения в разные функции, но получаю ту же ошибку:
script.js
import SpriteSheet from './SpriteSheet.js';
import {loadImage, loadLevel} from './loaders.js';
const canvas = document.getElementById('gamearea');
const context = canvas.getContext('2d');
function drawBackground(background, context, sprites) {
background.ranges.forEach(([x1, x2, y1, y2]) => {
for (let x = x1; x < x2; ++x) {
for (let y = y1; y < y2; ++y) {
sprites.drawTile(background.tile, context, x, y);
}
}
});
}
function drawMario(background, context, mario) {
background.ranges.forEach(([x1, x2, y1, y2]) => {
for (let x = x1; x < x2; ++x) {
for (let y = y1; y < y2; ++y) {
mario.drawTile(background.tile, context, x, y);
}
}
});
}
function loadBackgroundSprites() {
return loadImage('SEA01.png').then(image => {
const sprites = new SpriteSheet(image, 16, 16);
sprites.define('ocean', 0, 0);
return sprites;
});
}
function loadDirtSprite() {
return loadImage('ground.png').then(image => {
const mario = new SpriteSheet(image, 16, 16);
mario.define('ground', 12, 0);
return mario;
});
}
Promise.all([
loadBackgroundSprites(),
loadLevel('1-1'),
loadDirtSprite()
]).then(([sprites, level, mario]) => {
level.backgrounds.forEach(background => {
drawBackground(background, context, sprites, mario);
});
mario.draw('ground', context, 1, 16);
});
Это определенно связано с тем, что упомянул @ traktor53: this.tiles.get('ground') вернет undefined, потому что loadImage('ground.png') никогда не будет вызван.
@ traktor53 это вообще не связано, потому что, если я помещаю их в другую функцию и вызываю из своего обещания, я все равно получаю ту же ошибку. И нет, больше никаких ошибок, только эта. Я обновил свой OP другим кодом, но с той же ошибкой



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


заменен ответ:
Ошибка №1.
Упомянутый в комментарии к вопросу, исходный код содержит ошибку в loadBackgroundSprites:
function loadBackgroundSprites() {
return loadImage('SEA01.png').then(image => {
const sprites = new SpriteSheet(image, 16, 16);
sprites.define('ocean', 0, 0);
return sprites;
}); // <-- bad indent
return loadImage('ground.png').then(image=> {
const sprites = new SpriteSheet(image, 16, 16);
sprites.define('ground', 12, 0);
});
}
Строка посередине, отмеченная как «плохой отступ», - это место, где заканчивается оператор return, начатый в начале loadBackgroundSprites, и выполнение возвращается вызывающему. Оператор return после строки «плохой отступ» никогда не выполняется и генерирует консольное сообщение.
⚠ unreachable code after return statement [Learn More]
Поскольку предупреждение не является фатальным, функция возвращается без ошибок.
Первое исправление этой ошибки - удалить недостижимый код. Требуются два дополнительных исправления, как описано в следующей ошибке.
loadImageОшибка №2.
Экземпляры класса SpriteSheet содержат одно изображение, переданное конструктору в качестве аргумента. Это изображение должно содержать массив прямоугольных плиток фиксированной ширины и высоты, соответствующие значениям 2-го и 3-го параметров, используемых в вызове конструктора. Это не то, что вы делаете.
Фактически, определение плитки в экземпляре SpriteSheet копирует отдельную плитку из изображения листа спрайтов в новый объект с именем Canvas для использования функциями рисования.
Предлагаемое средство для работы с листами спрайтов в том виде, в каком оно разработано, - создать фоновое изображение листа спрайтов, которое содержит как минимум два фрагмента 16x16 для океана и земли. Их положение в изображении листа спрайтов (считая плитки слева и сверху вниз) определяет, как определение плитки должно быть закодировано в loadBackgroundSprites.
Для этого решения удалите ссылки на код dirtSprite. И удалите ссылки на изображения на «SEA01.png» и «ground.png» - loadBackgroundSprites должен загружать URL для комбинированного листа спрайтов, содержащего обе плитки.
В цепочке Promise.all используйте версию кода forEach для рендеринга спрайтов в соответствии с файлом json:
Promise.all([
loadBackgroundSprites(),
loadLevel('1-1')
]).then(([sprites,level]) => {
console.info(level);
level.backgrounds.forEach(background => {
drawBackground(background, context, sprites)
});
Но как сделать так, чтобы объект спрайтов содержал плитку для ground? Обе функции loadDirtSprite() определяют землю для объекта спрайтов. Почему тогда он не работает?
Я снова обновил свой OP с помощью рефакторинга кода. Та же ошибка .. Мне хочется сдаться.
Ну ладно, разобрался. Похоже, мой цикл не проходит через второй элемент «земля» в JSON. Все еще пытаюсь понять, почему, но если я жестко закодирую его напрямую, без использования JSON, он будет работать нормально.
Вы совсем не поняли моего ответа. Я посмотрю, смогу ли я улучшить его за выходные.
Спасибо, это действительно поможет!
Я заменил ответ. Есть проблема со спрайт-листами, которую я обнаружил только сегодня :-)
Я знаю, что это было довольно давно. Но когда вы создали новое обещание в модуле loader.js - обязательно добавьте .catch ((err) => console.info (err))
Также после обновления, если вы все еще видите проблему, попробуйте Ctrl + Shift + R обновить статический http-кеш, который все еще может кэшировать ваш старый код. Если это поможет, дайте мне знать!
loader.js
export function loadImage(url) {
return new Promise(resolve => {
const image = new Image();
image.addEventListener('load', () => {
resolve(image);
});
image.src = url;
}).catch((err) => console.info(err)); <--------ADD THIS
}
function loadBackgroundSpritesимеет два оператора возврата, второй из которых генерирует «недостижимый код» предупреждение (код для загрузки и создания «наземного» спрайта не выполняется). Это связано с проблемой? У вас есть другие ошибки консоли?