Reputation: 3
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!
Upvotes: 0
Views: 1200
Reputation: 57393
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.
Upvotes: 0
Reputation: 54128
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 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>
Upvotes: 1