简体   繁体   中英

How to draw same image multiple times in a canvas in javascript

I'm trying to code a simple JavaScript video game. I can load the background and player, and I'd like to draw an item multiple times. I use a class for player and this item with the canvas.drawImagemethod

In my infinite loop function I call a forEach to draw these images but just one appears on my canvas. How can I do this?

 const itemsData = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 486, 486, 486, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] const CANVAS_WIDTH = 800; const CANVAS_HEIGHT = 600; const TILE_WIDTH = 12; const TILE_HEIGHT = 12 const TILExROW = 67; const MAP_ZOOM = 100; //percentage map has benn exported const MAP_ZOOM_MULT = MAP_ZOOM / 100; const TILE_COLLISION = 487; const TILE_COLLECTOBJ = 486; const MAP_OFFSET = { x: 0, y: 0 } const PLAYER_IMG = { width: 288, height: 256, imgxRow: 12, imgxCol: 8, singleImg: { width: 21, height: 27 }, currentFrame: { horizontal: 3, vertical: 2 } } const canvas = document.getElementById("canvas"); canvas.width = CANVAS_WIDTH; canvas.height = CANVAS_HEIGHT; const ctx = canvas.getContext("2d"); const bgImage = new Image(); bgImage.src = "https://imageshack.com/i/pmtPesL0p"; const playerImage = new Image(); playerImage.src = "https://imageshack.com/i/po1VURTCp"; const itemsImage = new Image(); itemsImage.src = "https://imageshack.com/i/potVaxoWp" //class Sprite class Sprite { constructor({ position, image, frames = { currentH: 0, currentV: 0, maxH: 1, maxV: 1 } }) { this.position = position; this.image = image; this.frames = { ...frames, elapsed: 0 }; this.image.onload = () => { this.width = this.image.width / this.frames.maxH; this.height = this.image.height / this.frames.maxV; } this.isMoving = false; } draw() { ctx.drawImage( this.image, this.frames.currentH * this.width, this.frames.currentV * this.height, this.image.width / this.frames.maxH, this.image.height / this.frames.maxV, this.position.x, this.position.y, this.image.width / this.frames.maxH, this.image.height / this.frames.maxV ) } } const background = new Sprite({ position: { x: MAP_OFFSET.x, y: MAP_OFFSET.y }, image: bgImage }); const player = new Sprite({ position: { x: canvas.width / 2 - (PLAYER_IMG.width / PLAYER_IMG.imgxRow / 2), y: canvas.height / 2 - (PLAYER_IMG.height / PLAYER_IMG.imgxCol / 2) }, image: playerImage, frames: { currentH: PLAYER_IMG.currentFrame.horizontal, currentV: PLAYER_IMG.currentFrame.vertical, maxH: PLAYER_IMG.imgxRow, maxV: PLAYER_IMG.imgxCol } }); //items map const itemsMap = []; for (let i = 0; i < itemsData.length; i += TILExROW) { itemsMap.push(itemsData.slice(i, TILExROW + i)); } const items = []; itemsMap.forEach((row, i) => { row.forEach((tile, j) => { if (tile === TILE_COLLECTOBJ) { items.push(new Sprite({ position: { x: j * TILE_WIDTH * MAP_ZOOM_MULT + MAP_OFFSET.x, y: i * TILE_HEIGHT * MAP_ZOOM_MULT + MAP_OFFSET.y }, image: itemsImage, frames: { currentH: 0, currentV: 0, maxH: 4, maxV: 1 } })) } }) }); //refres canvas image seamlessly async function animate() { //draw the background background.draw(); items.forEach(item => { item.isMoving = true; item.draw(); }); //draw the player player.draw(); window.requestAnimationFrame(animate); } animate();
 <body> <canvas id="canvas" style="border:1px solid #d3d3d3;"></canvas> </body>

The problem is that your three hearts - which are instances of the Sprite() class - share the same instance of an Image object.

In the constructor of the Sprite class, you've added an onload listener:

this.image.onload = () => {
    this.width = this.image.width / this.frames.maxH;
    this.height = this.image.height / this.frames.maxV;
}

unfortunately this listener will just fire once - as it's the same image for all three Sprite instances. That means it's width and height properties won't ever get populated and ultimately causing an error in the sprite's draw() function as the width and height is undefined.

You can fix that by adding a loaded property to the image objects as the onload event fires and query this property inside the draw function. If it's true, read the width & height from the image object.

Here's your modified code:

 const itemsData = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 486, 486, 486, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] const CANVAS_WIDTH = 800; const CANVAS_HEIGHT = 600; const TILE_WIDTH = 12; const TILE_HEIGHT = 12 const TILExROW = 67; const MAP_ZOOM = 100; //percentage map has benn exported const MAP_ZOOM_MULT = MAP_ZOOM / 100; const TILE_COLLISION = 487; const TILE_COLLECTOBJ = 486; const MAP_OFFSET = { x: 0, y: 0 } const PLAYER_IMG = { width: 288, height: 256, imgxRow: 12, imgxCol: 8, singleImg: { width: 21, height: 27 }, currentFrame: { horizontal: 3, vertical: 2 } } const canvas = document.getElementById("canvas"); canvas.width = CANVAS_WIDTH; canvas.height = CANVAS_HEIGHT; const ctx = canvas.getContext("2d"); const bgImage = new Image(); bgImage.src = "https://imageshack.com/i/pmtPesL0p"; const playerImage = new Image(); playerImage.src = "https://imageshack.com/i/po1VURTCp"; const itemsImage = new Image(); itemsImage.src = "https://imageshack.com/i/potVaxoWp" //class Sprite class Sprite { constructor({ position, image, frames = { currentH: 0, currentV: 0, maxH: 1, maxV: 1 } }) { this.position = position; this.image = image; this.frames = { ...frames, elapsed: 0 }; this.image.onload = () => { this.width = this.image.width / this.frames.maxH; this.height = this.image.height / this.frames.maxV; this.image.loaded = true; } this.isMoving = false; } draw() { if (this.image.loaded) { this.width = this.image.width / this.frames.maxH; this.height = this.image.height / this.frames.maxV; } ctx.drawImage( this.image, this.frames.currentH * this.width, this.frames.currentV * this.height, this.image.width / this.frames.maxH, this.image.height / this.frames.maxV, this.position.x, this.position.y, this.image.width / this.frames.maxH, this.image.height / this.frames.maxV ) } } const background = new Sprite({ position: { x: MAP_OFFSET.x, y: MAP_OFFSET.y }, image: bgImage }); const player = new Sprite({ position: { x: canvas.width / 2 - (PLAYER_IMG.width / PLAYER_IMG.imgxRow / 2), y: canvas.height / 2 - (PLAYER_IMG.height / PLAYER_IMG.imgxCol / 2) }, image: playerImage, frames: { currentH: PLAYER_IMG.currentFrame.horizontal, currentV: PLAYER_IMG.currentFrame.vertical, maxH: PLAYER_IMG.imgxRow, maxV: PLAYER_IMG.imgxCol } }); //items map const itemsMap = []; for (let i = 0; i < itemsData.length; i += TILExROW) { itemsMap.push(itemsData.slice(i, TILExROW + i)); } const items = []; itemsMap.forEach((row, i) => { row.forEach((tile, j) => { if (tile === TILE_COLLECTOBJ) { items.push(new Sprite({ position: { x: j * TILE_WIDTH * MAP_ZOOM_MULT + MAP_OFFSET.x, y: i * TILE_HEIGHT * MAP_ZOOM_MULT + MAP_OFFSET.y }, image: itemsImage, frames: { currentH: 0, currentV: 0, maxH: 4, maxV: 1 } })) } }) }); //refres canvas image seamlessly async function animate() { //draw the background background.draw(); items.forEach(item => { item.isMoving = true; item.draw(); }); //draw the player player.draw(); window.requestAnimationFrame(animate); } animate();
 <canvas id="canvas" style="border:1px solid #d3d3d3;"></canvas>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM