0

So, I followed a tutorial to make a game. Obviously, that isn't the best way to learn to code so I began to modify it. Currently the game has enemies that slowly move towards the player, but these enemies are colored circles and nothing else. I'd like to add a picture that I can put on the enemies, but I have no Idea how. Here's some code that you might like to know:

The enemy class (update is called every frame):

class Enemy { constructor(x, y, radius, color, velocity) { this.x = x; this.y = y; this.radius = radius; this.color = color; this.velocity = velocity; } draw() { ctx.beginPath(); ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true); ctx.fillStyle = this.color; ctx.fill(); } update() { this.draw(); this.x = this.x + this.velocity.x; this.y = this.y + this.velocity.y; } } 

The function for creating enemies:

function spawnEnemies() { setInterval(() => { const radius = Math.random() * (30 - 4) + 4; let x; let y; if (Math.random() < 0.5) { x = Math.random() < 0.5 ? 0 - radius : canvas.width + radius; y = Math.random() * canvas.height; } else { y = Math.random() < 0.5 ? 0 - radius : canvas.height + radius; x = Math.random() * canvas.width; } const color = `hsl(${Math.random() * 360}, 50%, 50%)`; const angle = Math.atan2(canvas.height / 2 - y, canvas.width / 2 - x); const velocity = { x: Math.cos(angle), y: Math.sin(angle) } enemies.push(new Enemy(x, y, radius, color, velocity)); }, 1000) } 

This code is ran in the animate function:

enemies.forEach((enemy, index) => { enemy.update(); const dist = Math.hypot(player.x - enemy.x, player.y - enemy.y); if (isHacking) { if (dist - enemy.radius - player.radius < 11) { setTimeout(() => { for (let i = 0; i < enemy.radius * 2; i++) { particles.push(new Particle(enemy.x, enemy.y, Math.random() * 2, enemy.color, { x: (Math.random() - 0.5) * (Math.random() * 8), y: (Math.random() - 0.5) * (Math.random() * 8)} )); }}, 0) score += 25; scoreEl.innerHTML = score; setTimeout(() => { enemies.splice(index, 1); projectiles.splice(proIndex, 1); }, 0) } } else if (dist - enemy.radius - player.radius < 1) { cancelAnimationFrame(animationId); modal.style.display = 'flex'; modalScore.innerHTML = score; } 

Also, this is my first time posting on stack overflow, so if there's something I should've done that I didn't, or vice versa, please let me know!

3
  • Where are you stuck adding these images, exactly? If you haven't made any attempt yet, maybe take a look at How to add image to canvas or draw image on canvas Commented Mar 21, 2022 at 16:28
  • I get stuck attempting to make an image appear ONLY in the circle. So, if I upload a rectangular picture, I want to do a border-radius sort of thing. Commented Mar 21, 2022 at 19:29
  • I would pre-crop the image to be a circular png with transparency if possible -- better performance. But if you want to do it in canvas, is canvas clip image in a circle what you need? (use context.arc() rather than quadraticCurveTo) Commented Mar 21, 2022 at 19:32

2 Answers 2

1

There are many ways to do what you want. Circle with image.

From what I can guess from your question and comments you want a dynamic circle that shows a loaded image.

Create a pattern using the image.

const imgPat = ctx.createPattern(image, "no-repeat"); 

Use the image size to workout the max radius you can have without going outside the image.

const minRadius = Math.min(image.width, image.height) / 2; 

To draw the circle at any radius you will need to use the 2D context setTransform function. The following function will do that

function drawImageCircle(imgPat, minRadius, x, y, radius) { // get scale of circle image const scale = radius / minRadius; // transform to put origin at top left of bounding rectangle scaling to fit image pattern ctx.setTransform(scale, 0, 0, scale, x - radius, y - radius); ctx.fillStyle = imgPat; ctx.beginPath(); ctx.arc(minRadius, minRadius, minRadius, 0, Math.PI * 2); ctx.fill(); // reset the default transform ctx.setTransform(1, 0, 0, 1, 0, 0); } 

Demo

Demo loads an image. Then gets its size creates a pattern from it and animates it changing the radius and putting a 4 pixel outline around it.

const image = new Image; image.src = "https://i.sstatic.net/C7qq2.png?s=256&g=1"; image.addEventListener("load", imageReady); const ctx = canvas.getContext("2d"); var w = canvas.width, h = canvas.height, cw = w / 2, ch = h / 2; var imgPat, minRadius; function imageReady() { imgPat = ctx.createPattern(image, "no-repeat"); minRadius = Math.min(image.width, image.height) / 2; requestAnimationFrame(renderLoop); } function drawImageCircle(imgPat, minRadius, x, y, radius) { const scale = radius / minRadius; ctx.setTransform(scale, 0, 0, scale, x - radius, y - radius); ctx.strokeStyle = "#F00"; ctx.lineWidth = 8 / scale; ctx.fillStyle = imgPat; ctx.beginPath(); ctx.arc(minRadius, minRadius, minRadius, 0, Math.PI * 2); ctx.stroke(); ctx.fill(); ctx.setTransform(1, 0, 0, 1, 0, 0); } function renderLoop(time) { ctx.setTransform(1,0,0,1,0,0); ctx.clearRect(0, 0, w, h); var x = Math.cos(time / 500) * (cw - 80) + cw; var y = Math.sin(time / 600) * (ch - 80) + ch; var rad = Math.sin(time / 333) * 10 + 30; drawImageCircle(imgPat, minRadius, x, y, rad); requestAnimationFrame(renderLoop); }
canvas { background: #888; border: 2px solid black; }
<canvas id="canvas" width="256" height="256"></canvas>

Sign up to request clarification or add additional context in comments.

Comments

0

If supportable by your use case, I suggest using a png with pre-cropped circular transparency to improve performance and avoid having to code this.

But let's carry on and answer your question from the comment. I felt this was different enough from Canvas clip image with two quadraticCurves to add a new answer, but that thread shows the general approach: use context.clip() after a path with a context.arc, then finish with context.drawImage.

Since you're drawing other things as well, wrap your clip with context.save() and context.restore() to prevent your clip from affecting everything you draw afterwards.

Here's a minimal example:

const canvas = document.createElement("canvas"); canvas.height = canvas.width = 100; const {width: w, height: h} = canvas; document.body.appendChild(canvas); const ctx = canvas.getContext("2d"); const img = new Image(); img.src = `https://picsum.photos/${w}/${h}`; img.decode().then(() => { ctx.fillRect(10, 0, 20, 20); // normal drawing before save() ctx.save(); ctx.beginPath(); ctx.arc(w / 2, h / 2, w / 2, 0, Math.PI * 2); ctx.clip(); ctx.drawImage(img, 0, 0); ctx.restore(); ctx.fillRect(0, 10, 20, 20); // back to normal drawing after restore() });

If you have multiple images, I suggest using promises as described in Images onload in one function.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.