HTML5 Canvas Image Moving Flickering - javascript

In canvas, there is a image, which moves on events of arrow keys.
But it flickers in firefox and works fine on chrome and ie.
I don't want a solution like to clear only the portion of canvas, because I have other things added in canvas too.
Below is my code:
var
velY = 0,
velX = 0,
speed = 2,
friction = 0.90,
keys = [],
canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
playerDirection="img/player/player_back.png";
function update() {
if (keys[38]) {
if (velY > -speed) {
velY--;
playerDirection="img/player/player_back.png";
}
}
if (keys[40]) {
if (velY < speed) {
velY++;
playerDirection="img/player/player_front.png";
}
}
if (keys[39]) {
if (velX < speed) {
velX++;
playerDirection="img/player/player_right.png";
}
}
if (keys[37]) {
if (velX > -speed) {
velX--;
playerDirection="img/player/player_left.png";
}
}
velY *= friction;
y += velY;
velX *= friction;
x += velX;
if (x >= canvas.width - player.width) {
x = canvas.width - player.width;
} else if (x <= 5) {
x = 5;
}
if (y > canvas.height - player.height) {
y = canvas.height - player.height;
} else if (y <= 5) {
y = 5;
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
playerObj = new Image();
playerObj.onload = function()
{
ctx.drawImage(playerObj, x, y);
};
playerObj.src = playerDirection;
}
update();
function handlerKeyDown(e)
{
keys[e.keyCode] = true;
}
function handlerKeyUp(e) {
keys[e.keyCode] = false;
}
Thanks in adavance.

This is what I meant with "caching the images":
var images = {}; // This will contain the Image objects
// Other definitions...
function update() {
if (keys[38]) {
if (!images.back) {
// The image object hasn't been defined yet.
images.back = new Image();
images.back.src = "img/player/player_back.png";
}
playerObj = images.back;
}
// Other checks on the pressed keys
// ...
// Computations on the position...
if (!playerObj.naturalWidth) {
// If naturalWidth/Height is 0, then the image hasn't been loaded yet.
// We update the onload listener to draw in the right position.
playerObj.onload = function() {
ctx.drawImage(playerObj, x, y);
};
// The image has already been loaded, so we just draw it
} else ctx.drawImage(playerObj, x, y);
}
(As an warning on your code, it seems that you want to handle multiple pressed keys, but the last one in the sequence up-down-right-left always "wins" over the others. Is that really what you want?)

Related

can not rotate a rectangular shape in canvas

I am new in HTML5 canvas. I am trying to build game area using HTML canvas where someone can drive around a car inside
Canvas area like if I press the left arrow key the car will turn to the left and if press the right arrow key the car will turn to the right side. And up and down arrow keys are responsible for moving the car upward or downward. And the car must stay within the canvas area. Here, I use rotate method to turn the car right or left. But it is not working, actually the car is getting away from the canvas and behaving like a crap.
Anyone has any idea to solve the problem? I am totally stuck here. Thanks in advance.
Initially I am displaying a rectangle instead of car.
My js file is given below.
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
canvas.width = innerWidth - 100;
canvas.height = innerHeight - 100;
const canvasW = canvas.width;
const canvasH = canvas.height;
let upPressed = false,
downPressed = false,
rightPressed = false,
leftPressed = false;
class PlayerCar {
constructor(carX, carY, carWidth, carHeight) {
this.carX = carX;
this.carY = carY;
this.carWidth = carWidth;
this.carHeight = carHeight;
}
draw() {
ctx.fillStyle = "blue";
ctx.beginPath();
ctx.rect(this.carX, this.carY, this.carWidth, this.carHeight);
ctx.fill();
ctx.closePath();
}
}
const playerCar = new PlayerCar(100, 100, 40, 60);
playerCar.draw();
function navigation() {
const handleKeyDown = (e) => {
if (e.key === "ArrowUp") {
upPressed = true;
}
if (e.key === "ArrowDown") {
downPressed = true;
}
if (e.key === "ArrowRight") {
rightPressed = true;
}
if (e.key === "ArrowLeft") {
leftPressed = true;
}
};
const handleKeyUp = (e) => {
if (e.key === "ArrowUp") {
upPressed = false;
}
if (e.key === "ArrowDown") {
downPressed = false;
}
if (e.key === "ArrowRight") {
rightPressed = false;
}
if (e.key === "ArrowLeft") {
leftPressed = false;
}
};
document.addEventListener("keydown", handleKeyDown);
document.addEventListener("keyup", handleKeyUp);
}
function animate() {
requestAnimationFrame(animate);
ctx.clearRect(0, 0, canvasW, canvasH);
if (upPressed) {
playerCar.carY -= 5;
}
if (downPressed) {
playerCar.carY += 5;
}
if (leftPressed) {
ctx.save();
ctx.translate(
playerCar.carX + playerCar.width / 2,
playerCar.carY + playerCar.height / 2
);
ctx.rotate((Math.PI / 180) * 0.2);
playerCar.carX -= 5;
ctx.restore();
}
if (rightPressed) {
ctx.save();
ctx.translate(
playerCar.carX + playerCar.width / 2,
playerCar.carY + playerCar.height / 2
);
ctx.rotate((Math.PI / 180) * -0.2);
playerCar.carX += 5;
ctx.restore();
}
if (playerCar.carX < 0) playerCar.carX = 0;
if (playerCar.carX > canvasW - playerCar.carWidth)
playerCar.carX = canvasW - playerCar.carWidth;
if (playerCar.carY < 0) playerCar.carY = 0;
if (playerCar.carY > canvasH - playerCar.carHeight)
playerCar.carY = canvasH - playerCar.carHeight;
playerCar.draw();
}
function startGame() {
animate();
}
startGame();
navigation();
I would first move all of the update info to a method in the car class vice doing it in the animate loop.
This is where cos and sin come in and getting familiar with angles. You also must understand the canvas relation the the object it draws is always at (x, y) of (0, 0) unless you translate it to the center. To do that draw your object in this manner:
ctx.fillStyle = "blue";
ctx.save();
ctx.beginPath();
ctx.translate(this.carX, this.carY)
ctx.rotate(this.angle)
ctx.rect(-this.carWidth/2, -this.carHeight/2, this.carWidth, this.carHeight);
ctx.fill();
ctx.closePath();
ctx.restore();
Use of save() and restore() are a must unless you want to translate and rotate you object back to its original. It's the same ting but simpler. So now I am translating the car around the canvas and the car itself is drawn at negative half the width and height to ensure the canvas (0, 0) corner is in the center of the car. This is because canvas always rotates from the top-left corner.
Now create a method called update() and put you control logic in there:
update() {
if (rightPressed) {
this.angle += this.rot
} else if (leftPressed) {
this.angle -= this.rot
}
if (upPressed) {
this.carX += Math.cos(this.angle+toRadians(-90)) * 5;
this.carY += Math.sin(this.angle+toRadians(-90)) * 5;
}
}
Be aware I added angle and rot to the constructor also. What this is doing is when you press left or right the car rotate accordingly. As for pressing up we are going to translate it by the rotation. Since this would normally make the car drive to the right we have to also add -90 degrees to the current angle to ensure the car drives forwards. Just remove +toRadians(-90) and see what happens.
Multiplying it by 5 is an arbitrary number for speed. You can even make it part of the constructor and set it there. i.e. this.speed = 5
Doing the same thing for downPressed but instead use +toRadians(90)
toRadians() is just a simple function added to your code to convert degrees to radians.
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
canvas.width = 600;
canvas.height = 600;
const canvasW = canvas.width;
const canvasH = canvas.height;
let upPressed = false,
downPressed = false,
rightPressed = false,
leftPressed = false;
class PlayerCar {
constructor(carX, carY, carWidth, carHeight) {
this.carX = carX;
this.carY = carY;
this.carWidth = carWidth;
this.carHeight = carHeight;
this.angle = 0;
this.rot = 0.1; //control how fast it turns
}
draw() {
ctx.fillStyle = "blue";
ctx.save();
ctx.beginPath();
ctx.translate(this.carX, this.carY)
ctx.rotate(this.angle)
ctx.rect(-this.carWidth/2, -this.carHeight/2, this.carWidth, this.carHeight);
ctx.fill();
ctx.closePath();
ctx.restore();
}
update() {
if (rightPressed) {
this.angle += this.rot
} else if (leftPressed) {
this.angle -= this.rot
}
if (upPressed) {
this.carX += Math.cos(this.angle+toRadians(-90)) * 5;
this.carY += Math.sin(this.angle+toRadians(-90)) * 5;
}
if (downPressed) {
this.carX += Math.cos(this.angle+toRadians(90)) * 5;
this.carY += Math.sin(this.angle+toRadians(90)) * 5;
}
}
}
function toRadians(deg) {
return (deg * Math.PI) / 180;
}
const playerCar = new PlayerCar(100, 100, 40, 60);
playerCar.draw();
function navigation() {
const handleKeyDown = (e) => {
if (e.key === "ArrowUp") {
upPressed = true;
}
if (e.key === "ArrowDown") {
downPressed = true;
}
if (e.key === "ArrowRight") {
rightPressed = true;
}
if (e.key === "ArrowLeft") {
leftPressed = true;
}
};
const handleKeyUp = (e) => {
if (e.key === "ArrowUp") {
upPressed = false;
}
if (e.key === "ArrowDown") {
downPressed = false;
}
if (e.key === "ArrowRight") {
rightPressed = false;
}
if (e.key === "ArrowLeft") {
leftPressed = false;
}
};
document.addEventListener("keydown", handleKeyDown);
document.addEventListener("keyup", handleKeyUp);
}
function animate() {
requestAnimationFrame(animate);
ctx.clearRect(0, 0, canvasW, canvasH);
if (playerCar.carX < 0) playerCar.carX = 0;
if (playerCar.carX > canvasW - playerCar.carWidth)
playerCar.carX = canvasW - playerCar.carWidth;
if (playerCar.carY < 0) playerCar.carY = 0;
if (playerCar.carY > canvasH - playerCar.carHeight)
playerCar.carY = canvasH - playerCar.carHeight;
playerCar.draw();
playerCar.update();
}
function startGame() {
animate();
}
startGame();
navigation();
<canvas></canvas>
To be clear on why your code is not doing what you expect think about this. You are trying to translate and rotate the context without ever accessing the object. If you were to add ctx.fillRect() to your animate loop would you think it's going to just know that you want to change the car? Same goes for translate and rotate. Try adding a fillStyle and fillRect to your rightPressed
if (rightPressed) {
ctx.save();
playerCar.carX += 5;
ctx.translate(
playerCar.carX + playerCar.width / 2,
playerCar.carY + playerCar.height / 2
);
ctx.rotate(45);
ctx.fillStyle = 'grey'
ctx.fillRect(0, 0, canvas.width, canvas.height)
ctx.restore();
}
You will see when you press right the context does what you want just not the context of the object. This is why adding it directly top the class should be done. Now you are specifically targeting the object you want.

Motion effect for standing object

I have trying to achieve motion effect for the standing object.
I assume that my object will be able to go to the right or the left for example.
And I wanna make illusion like that object is still moving. Even if currently is not moving. (Meanwhile background can moving still...)
'use strict';
const pressedKeys = [];
const canvas = document.querySelector('#game');
const ctx = canvas.getContext('2d');
canvas.width = 300;
canvas.height = 150;
class Player
{
xPosition = 150;
yPosition = 50;
speed = 5;
isMoving = false;
update(pressedKeys)
{
if (pressedKeys['ArrowLeft']) {
this.xPosition -= this.speed;
} else if (pressedKeys['ArrowRight']) {
this.xPosition += this.speed;
}
}
draw(ctx)
{
ctx.fillStyle = '#2976f2';
ctx.fillRect(this.xPosition, this.yPosition, 30, 30);
}
}
const player = new Player();
function animate()
{
window.requestAnimationFrame(animate);
ctx.clearRect(0, 0, canvas.width, canvas.height)
if (player.isMoving) {
player.update(pressedKeys);
}
player.draw(ctx);
}
animate();
window.addEventListener('keyup', function (event) {
delete pressedKeys[event.key];
player.isMoving = false;
})
window.addEventListener('keydown', function (event) {
switch (event.key) {
case 'ArrowLeft':
case 'ArrowRight':
pressedKeys[event.key] = true;
player.isMoving = true;
break;
}
})
canvas {
border: 1px solid blue;
}
<canvas id="game"></canvas>
Usually this kind of effect is done by constantly duplicating the desired object, move it to the exact same screen position and ultimately fade it out over time e.g. within one second.
In your case though we can simplify things a bit since you want to keep that "motion blurred" look even if it ain't moving.
So first we need to another property to your Player class oldX. It holds the position of the object before a movement occured. By subtracting oldX from x we can determine if the object is moving to the left or to the right - so we know where to put the trailing duplicates.
If we know the direction, we can start creating duplicates using a simple for loop like:
for (var a = 0; a < 7; a++) {
ctx.fillRect(this.x - (this.x - this.oldX) / this.speed * a * 2, this.y, 30, 30);
}
this will create seven equal looking squares - so it won't look good yet. The duplicate next to the original should have almost the same color while the last one should almost blend with the background. To do this we can use the globalAlpha property of the canvases context. A value of 1 is opaque while 0 is completely transparent.
Putting it all together:
const keys = [];
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
canvas.width = 300;
canvas.height = 150;
class Player {
x = 150;
y = 50;
oldX = 150;
speed = 5;
moving = false;
update(keys) {
this.oldX = this.x;
if (keys['ArrowLeft']) {
this.x -= this.speed;
} else if (keys['ArrowRight']) {
this.x += this.speed;
}
}
draw(ctx) {
ctx.fillStyle = '#2976f2';
ctx.fillRect(this.x, this.y, 30, 30);
ctx.save();
for (var a = 0; a < 7; a++) {
ctx.globalAlpha = 0.5 - (a / 7) * 0.5;
ctx.fillRect(this.x - (this.x - this.oldX) / this.speed * a * 2, this.y, 30, 30);
}
ctx.restore();
}
}
const player = new Player();
function animate() {
window.requestAnimationFrame(animate);
ctx.clearRect(0, 0, canvas.width, canvas.height)
if (player.moving) {
player.update(keys);
}
player.draw(ctx);
}
animate();
window.addEventListener('keyup', function(event) {
delete keys[event.key]
player.moving = false;
})
window.addEventListener('keydown', function(event) {
switch (event.key) {
case 'ArrowLeft':
case 'ArrowRight':
keys[event.key] = true;
player.moving = true;
break;
}
})
canvas {
border: 1px solid blue;
}
<canvas id="game"></canvas>

How to detect when mouse is outside of a certain circle?

When a mouse is hovering a image. It gets detect by this if statement:
if ((distance(circles[this.index].x, circles[this.index].y, mouse.x, mouse.y)) < circles[this.index].radius)
I also want to detect when a mouse it outside a image.
After that previous if statement I cannot use else the reason is because:
When I generate multiple images on screen and when my mouse if hovering over 1 image. It does hover of that image and the code detects it but it also doesnt hover of all the other images. That is the reason that is display 4 times "outside circle" and 1 time "inside circle"
As seen in the log:
Console.log output:
Mouse inside circle
Mouse outside circle 4
Mouse inside circle
Mouse outside circle 4
Im looking for a way the detect when the mouse is leaving a circle.
You can find the code I'm working with below:
PS: it it important that it detect in what (index) circle the mouse is and leaves.
I want to create a huge amount of pictures, but in the code below I used 5 for demo purpeses.
var mouse = {
x: innerWidth / 2,
y: innerHeight / 2
};
// Mouse Event Listeners
addEventListener('mousemove', event => {
mouse.x = event.clientX;
mouse.y = event.clientY;
});
//Calculate distance between 2 objects
function distance(x1, y1, x2, y2) {
let xDistance = x2 - x1;
let yDistance = y2 - y1;
return Math.sqrt(Math.pow(xDistance, 2) + Math.pow(yDistance, 2));
}
// Sqaure to circle
function makeCircleImage(radius, src, callback) {
var canvas = document.createElement('canvas');
canvas.width = canvas.height = radius * 2;
var ctx = canvas.getContext("2d");
var img = new Image();
img.src = src;
img.onload = function() {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
// we use compositing, offers better antialiasing than clip()
ctx.globalCompositeOperation = 'destination-in';
ctx.arc(radius, radius, radius, 0, Math.PI*2);
ctx.fill();
callback(canvas);
};
}
function Circle( x, y, radius, index ) {
//Give var for circle
this.x = x;
this.y = y;
this.dx = 1;
this.dy = 1;
this.radius = radius;
this.index = index;
}
// use prototyping if you wish to make it a class
Circle.prototype = {
//Draw circle on canvas
draw: function () {
var
x = (this.x - this.radius),
y = (this.y - this.radius);
// draw is a single call
c.drawImage( this.image, x, y );
},
//Updates position of images
update: function () {
var
max_right = canvas.width + this.radius,
max_left = this.radius * -1;
this.x += this.dx;
if( this.x > max_right ) {
this.x += max_right - this.x;
this.dx *= -1;
}
if( this.x < max_left ) {
this.x += max_left - this.x;
this.dx *= -1;
}
if ((distance(circles[this.index].x, circles[this.index].y, mouse.x, mouse.y)) < circles[this.index].radius) {
// Mouse inside circle
console.log("Mouse inside circle")
} else{
//The mouse is in one circle
//And out of 4 other circles
console.log("Mouse outside circle")
}
},
init: function(callback) {
var url = "https://t4.ftcdn.net/jpg/02/26/96/25/240_F_226962583_DzHr45pyYPdmwnjDoqz6IG7Js9AT05J4.jpg";
makeCircleImage( this.radius, url, function(img) {
this.image = img;
callback();
}.bind(this));
}
};
//Animate canvas
function animate() {
c.clearRect(0, 0, window.innerWidth, window.innerHeight);
circles.forEach(function( circle ) {
circle.update();
});
circles.forEach(function( circle ) {
circle.draw();
});
requestAnimationFrame(animate);
}
//Init canvas
var canvas = document.querySelector('canvas');
var c = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
//init circle objects
var circles = [
new Circle(10, 100, 50,0),
new Circle(10, 200, 30,1),
new Circle(10, 300, 50,2),
new Circle(10, 400, 50,3),
new Circle(10, 500, 50,4)
];
var ready = 0;
circles.forEach(function(circle) {
circle.init(oncircledone);
});
function oncircledone() {
if(++ready === circles.length) {
animate()
}
}
<canvas></canvas>
just add another property to circle
function Circle(x, y, radius, index) {
//Give var for circle
this.x = x;
this.y = y;
this.dx = 1;
this.dy = 1;
this.radius = radius;
this.index = index;
this.mouseInside = false
}
and then the update logic change to this
if ((distance(this.x, this.y, mouse.x, mouse.y)) < circles[this.index].radius) {
if (!this.mouseInside) {
this.mouseInside = true
console.log(`mouse enter circele at ${this.index}`)
}
}
else if (this.mouseInside) {
this.mouseInside = false
console.log(`mouse leave circele at ${this.index}`)
}
check if circles overlap and the you can decide if you want to update
var overlapsCircles = circles.filter(circle => {
var diffrentId = circle.index != this.index
var overlapping =
distance(this.x, this.y, circle.x, circle.y) < this.radius
return diffrentId && overlapping
})
if (overlapsCircles.length > 0) {
var overlapCircle = overlapsCircles.map(circle => circle.index)
console.log('overlap circle with index ' + overlapCircle)
}
var mouse = {
x: innerWidth / 2,
y: innerHeight / 2
};
// Mouse Event Listeners
addEventListener('mousemove', event => {
mouse.x = event.clientX;
mouse.y = event.clientY;
});
//Calculate distance between 2 objects
function distance(x1, y1, x2, y2) {
let xDistance = x2 - x1;
let yDistance = y2 - y1;
return Math.sqrt(Math.pow(xDistance, 2) + Math.pow(yDistance, 2));
}
// Sqaure to circle
function makeCircleImage(radius, src, callback) {
var canvas = document.createElement('canvas');
canvas.width = canvas.height = radius * 2;
var ctx = canvas.getContext("2d");
var img = new Image();
img.src = src;
img.onload = function () {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
// we use compositing, offers better antialiasing than clip()
ctx.globalCompositeOperation = 'destination-in';
ctx.arc(radius, radius, radius, 0, Math.PI * 2);
ctx.fill();
callback(canvas);
};
}
function Circle(x, y, radius, index) {
//Give var for circle
this.x = x;
this.y = y;
this.dx = 1;
this.dy = 1;
this.radius = radius;
this.index = index;
this.mouseInside = false
}
// use prototyping if you wish to make it a class
Circle.prototype = {
//Draw circle on canvas
draw: function () {
var
x = (this.x - this.radius),
y = (this.y - this.radius);
// draw is a single call
c.drawImage(this.image, x, y);
},
//Updates position of images
update: function () {
var
max_right = canvas.width + this.radius,
max_left = this.radius * -1;
this.x += this.dx;
if (this.x > max_right) {
this.x += max_right - this.x;
this.dx *= -1;
}
if (this.x < max_left) {
this.x += max_left - this.x;
this.dx *= -1;
}
if ((distance(this.x, this.y, mouse.x, mouse.y)) < circles[this.index].radius) {
if (!this.mouseInside) {
this.mouseInside = true
console.log(`mouse enter circele at ${this.index}`)
}
}
else if (this.mouseInside) {
this.mouseInside = false
console.log(`mouse leave circele at ${this.index}`)
}
},
init: function (callback) {
var url = "https://t4.ftcdn.net/jpg/02/26/96/25/240_F_226962583_DzHr45pyYPdmwnjDoqz6IG7Js9AT05J4.jpg";
makeCircleImage(this.radius, url, function (img) {
this.image = img;
callback();
}.bind(this));
}
};
//Animate canvas
function animate() {
c.clearRect(0, 0, window.innerWidth, window.innerHeight);
circles.forEach(function (circle) {
circle.update();
});
circles.forEach(function (circle) {
circle.draw();
});
requestAnimationFrame(animate);
}
//Init canvas
var canvas = document.querySelector('canvas');
var c = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
//init circle objects
var circles = [
new Circle(10, 100, 50, 0),
new Circle(10, 200, 30, 1),
new Circle(10, 300, 50, 2),
new Circle(10, 400, 50, 3),
new Circle(10, 500, 50, 4)
];
var ready = 0;
circles.forEach(function (circle) {
circle.init(oncircledone);
});
function oncircledone() {
if (++ready === circles.length) {
animate()
}
}
<canvas id="ctx"></canvas>
Ambiguities
It is not clear what you need in regard to circles and some point (in this answer point is a substitute for mouse and only requires that it have the properties x and y to be valid ).
The lack of information in your question concerns the facts
that many circles can be under the point at the same time.
and that more than one circle can move from under to out or out to under the point per frame.
the wording of the question suggest you are after just one circle which conflicts with the above 2 concerns.
Assumptions
I will assume that the interaction with the circles are more than just a simple on under event like interaction. That they may include animation related behaviors that are triggered by the state related to the point.
I assume that the visual order of the circles will determine how you select circles of interest.
That all circles per frame that meet the required conditions and can be accessed quickly.
That performance is important as you wish to have many circles that interact with a point.
That there is only one point (mouse, touch, other source) per frame that interacts with the circles
There is no requirement for circle circle interaction
Solution
The example below covers the above assumptions and resolves any ambiguities in the question. It is designed to be efficient and flexible.
The circles are stored in an array that has had its properties extended called circles
Rendering and state sets
The function circles.updateDraw(point) updates and draws all the circles. The argument point is a point to check the circle against. It defaults to the mouse.
All circles are drawn with an outline. Circles under the point (eg mouse) are filled with green, Circles just moved to under the point (eg onMouseOver) are filled with yellow, circle that have just move out from under are filled with red.
There are 3 arrays as properties of circles that contain circles as define...
circles.under All circles under the point
circles.outFromUnder All circles just out from under the point
circles.newUnder All circles new to under the point
These array are populated by the function circles.updateDraw(point)
Query all circles point state
Circles also have 3 functions that refer to the above arrays as set the default set is circles.under.
The functions are..
circles.firstInSet(set) Returns the first circle (The visual bottom most) in set or undefined
circles.lastInSet(set) Returns the last circle (The visual top most) in set or undefined
circles.closestInSet(set) Returns the closest circle to the point in set or undefined
For example to get the visual top most circle just under the mouse you would call circles.lastInSet(circles.newUnder) or to get the circle closest to the mouse from all circles under the mouse you would call circles.closestInSet(circles.newUnder) (or as it defaults to set under call circles.closestInSet() )
Circle additional states
Each Circle has some additional properties.
Circle.distSqr is the square of the distance from the point
Circle.rSqr is the square of the radius calculated when constructed.
Circle.underCount This value can be used to apply animations to the circle based on its relative state to the point.
If positive is the number of frames plus 1, the circle is under the point.
If this value is 1 then the circle is just moved from not under to under.
If this value is 0 the it has just moved out from under the point.
If negative this value is the number of frames the circle is not under the point
Running Demo
Use mouse to move over circles.
The circle closest and under the mouse is filled with white with alpha = 0.5
addEventListener('mousemove', event => {
mouse.x = event.clientX;
mouse.y = event.clientY;
});
Math.TAU = Math.PI * 2;
Math.rand = (min, max) => Math.random() * (max - min) + min;
const CIRCLE_RADIUS = 50;
const UNDER_STYLE = "#0A0";
const NEW_UNDER_STYLE = "#FF0";
const OUT_STYLE = "#F00";
const CIRCLE_STYLE = "#000";
const CIRCLE_LINE_WIDTH = 1.5;
const CIRCLE_COUNT = 100;
const CIRCLE_CLOSEST = "#FFF";
const ctx = canvas.getContext('2d');
const mouse = {x: 0, y: 0};
requestAnimationFrame(() => {
sizeCanvas();
var i = CIRCLE_COUNT;
while (i--) {
const r = Math.rand(CIRCLE_RADIUS / 3, CIRCLE_RADIUS);
circles.push(new Circle(
Math.rand(r, canvas.width - r),
Math.rand(r, canvas.height - r),
Math.rand(-1, 1),
Math.rand(-1, 1),
r
));
}
animate()
});
function animate() {
sizeCanvas();
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
circles.updateDraw();
const c = circles.closestInSet(circles.under);
if(c) {
ctx.globalAlpha = 0.5;
ctx.beginPath();
ctx.fillStyle = CIRCLE_CLOSEST;
c.draw();
ctx.fill();
ctx.globalAlpha = 1;
}
requestAnimationFrame(animate);
}
function sizeCanvas() {
if (canvas.width !== innerWidth || canvas.height !== innerHeight) {
canvas.width = innerWidth;
canvas.height = innerHeight;
}
}
function Circle( x, y, dx = 0, dy = 0, radius = CIRCLE_RADIUS) {
this.x = x + radius;
this.y = y + radius;
this.dx = dx;
this.dy = dy;
this.radius = radius;
this.rSqr = radius * radius; // radius squared
this.underCount = 0; // counts frames under point
}
Circle.prototype = {
draw() {
ctx.moveTo(this.x + this.radius, this.y);
ctx.arc(this.x, this.y, this.radius, 0, Math.TAU);
},
update() {
this.x += this.dx;
this.y += this.dy;
if (this.x >= canvas.width - this.radius) {
this.x += (canvas.width - this.radius) - this.x;
this.dx = -Math.abs(this.dx);
} else if (this.x < this.radius) {
this.x += this.radius - this.x;
this.dx = Math.abs(this.dx);
}
if (this.y >= canvas.height - this.radius) {
this.y += (canvas.height - this.radius) - this.y;
this.dy = -Math.abs(this.dx);
} else if (this.y < this.radius) {
this.y += this.radius - this.y;
this.dy = Math.abs(this.dy);
}
},
isUnder(point = mouse) {
this.distSqr = (this.x - point.x) ** 2 + (this.y - point.y) ** 2; // distance squared
return this.distSqr < this.rSqr;
}
};
const circles = Object.assign([], {
under: [],
outFromUnder: [],
newUnder: [],
firstInSet(set = this.under) { return set[0] },
lastInSet(set = this.under) { return set[set.length - 1] },
closestInSet(set = this.under) {
var minDist = Infinity, closest;
if (set.length <= 1) { return set[0] }
for (const circle of set) {
if (circle.distSqr < minDist) {
minDist = (closest = circle).distSqr;
}
}
return closest;
},
updateDraw(point) {
this.under.length = this.newUnder.length = this.outFromUnder.length = 0;
ctx.strokeStyle = CIRCLE_STYLE;
ctx.lineWidth = CIRCLE_LINE_WIDTH;
ctx.beginPath();
for(const circle of this) {
circle.update();
if (circle.isUnder(point)) {
if (circle.underCount <= 0) {
circle.underCount = 1;
this.newUnder.push(circle);
} else { circle.underCount ++ }
this.under.push(circle);
} else if (circle.underCount > 0) {
circle.underCount = 0;
this.outFromUnder.push(circle);
} else {
circle.underCount --;
}
circle.draw();
}
ctx.stroke();
ctx.globalAlpha = 0.75;
ctx.beginPath();
ctx.fillStyle = UNDER_STYLE;
for (const circle of this.under) {
if (circle.underCount > 1) { circle.draw() }
}
ctx.fill();
ctx.beginPath();
ctx.fillStyle = OUT_STYLE;
for (const circle of this.outFromUnder) { circle.draw() }
ctx.fill();
ctx.beginPath();
ctx.fillStyle = NEW_UNDER_STYLE;
for (const circle of this.newUnder) { circle.draw() }
ctx.fill();
ctx.globalAlpha = 1;
}
});
#canvas {
position: absolute;
top: 0px;
left: 0px;
background: #6AF;
}
<canvas id="canvas"></canvas>
Well, the mouse is moving and you can simply create a Set which will contain circle objects that will store the circle(s) you are in:
let circleOfTrust = new Set();
//At the initialization you need to add any circles your point is currently in
and then at the loop:
circles.forEach(function( circle ) {
circleOfTrust[circle.update(circleOfTrust.has(circle)) ? "add" : "delete"](circle);
});
if (circleOfTrust.size() === 0) {
//point is outside the circles
} else {
//point is inside the circles in the set
}
and the update:
update: function (isInside) {
var
max_right = canvas.width + this.radius,
max_left = this.radius * -1;
this.x += this.dx;
if( this.x > max_right ) {
this.x += max_right - this.x;
this.dx *= -1;
}
if( this.x < max_left ) {
this.x += max_left - this.x;
this.dx *= -1;
}
return distance(circles[this.index].x, circles[this.index].y, mouse.x, mouse.y)) < circles[this.index].radius;
},
I would propose the following:
Keep a stack of figures with the order of how they were created (or any other meaningful order). This is needed to detect moves over overlapping figures.
Implement a function/method that iterates the stack and determines if the cursor is inside any of the figures.
Remember the last state, on state transition inside->ouside triggers an event.
function FiguresCollection(canvas, callback)
{
var buffer = [];
var lastHitFigure = null;
var addFigure = function(figure)
{
buffer.push(figure);
}
var onMouseMove = function(e)
{
var currentHit = null;
// iterating from the other end, recently added figures are overlapping previous ones
for (var i= buffer.length-1;i>=0;i--)
{
if (distance(e.offsetX, e.offsetY, buffer[i].x, buffer[i].y) <= buffer[i].radius) {
// the cursor is inside Figure i
// if it come from another figure
if (lastHitFigure !== i)
{
console.log("The cursor had left figure ", lastHitFigure, " and entered ",i);
callback(buffer[i]);
}
lastHitFigure = i;
currentHit = i;
break; // we do not care about figures potentially underneath
}
}
if (lastHitFigure !== null && currentHit == null)
{
console.log("the cursor had left Figure", lastHitFigure, " and is not over any other ");
lastHitFigure = null;
callback(buffer[lastHitFigure]);
}
}
}
canvas.addEventListener("mousemove", onMouseMove);
this.addFigure = addFigure;
}
Now use it:
var col = new FiguresCollection(canvas, c=> console.log("The cursor had left, ", c) );
for(let i in circles)
{
c.addFigure(circles[i]);
}
// I hope I got the code right. I haven't tested it. Please point out any issues or errors.

HTML 5 Canvas elements not animating correctly in a .setInterval method

The code will render the animation with one of the "boxes" , but acts very strangely when two of the "boxes" are drawn within the setInterval method. I suspect this may have something to do with ctx.clearRect.
JS Fiddle
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext('2d');
var raf;
var switchDirection = [true, true];
function alien() {
if (canvas.getContext) {
function Spaceships(x) {
this.x = x;
this.y = 100;
this.color = 'rgb(192, 192, 192)';
this.draw = function() {
ctx.beginPath();
ctx.rect(this.x, this.y, 100, 100);
ctx.closePath();
ctx.fillStyle = this.color;
ctx.fill();
};
};
var alienOne = new Spaceships(100);
var alienTwo = new Spaceships(500);
alienOne.draw();
alienTwo.draw();
setInterval(function redraw() {
if (alienOne.x == 200) {
switchDirection[0] = false;
} else if (alienOne.x == 100) {
switchDirection[0] = true;
}
if (switchDirection[0] == true) {
ctx.clearRect(0, 0, 0, 0);
alienOne.draw();
alienOne.x += 10;
} else if (switchDirection[0] == false) {
ctx.clearRect(0, 0, 0, 0);
alienOne.draw();
alienOne.x -= 10;
}
if (alienTwo.x == 600) {
switchDirection[1] = false;
} else if (alienTwo.x == 500) {
switchDirection[1] = true;
}
if (switchDirection[1] == true) {
ctx.beginPath();
ctx.clearRect(0, 0, 0, 0);
alienTwo.draw();
alienTwo.x += 10;
} else if (switchDirection[1] == false) {
ctx.beginPath();
ctx.clearRect(0, 0, 0, 0);
alienTwo.draw();
alienTwo.x -= 10;
}
}, 250);
} else {
alert('you need a better browser to play this game')
}
};
alien();
I have tried placing the second box in its own .setInterval. The animation renders incorrectly by not properly adjusting its width.
Any help would be greatly appreciated.
I fixed your problem, and refactored some of your code:
The main problem was the clearing of the canvas with the clearRect method. You didn't specify the right parameter values.
Some of the code is very repetitive. For now this isn't really a problem since you're only using 2 aliens, however this could become a problem once you have more than two. Imagine writing almost identical code for 40 aliens. This is solved with arrays and for-loops. Also, you use a boolean variable to determine when to add/substract from the alienX. This too can become quite a hassle. Now, we check when the spaceships exceed/go below the tolerated x-value, and change the sign of your velocity accordingly.
As mentioned by #Kaiido in the comments above, you can use requestAnimationFrame for your animations, which is better for this project than setInterval. To maintain the choppy animation effect, I used a really basic counter, with the % operator to only execute the code every 4x in a second (usually, reqAnimFrame callbacks 60x per second, so 60/15 = 4, equal to 250ms in your example) (see below).
The code:
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var aliens = [];
var counter = 0;
function alien() {
if (canvas.getContext) {
function Spaceships(x) {
this.baseX = x; // added a baseX to remember where the spaceship started off
this.x = x;
this.y = 100;
this.velocityX = 10;
this.color = "rgb(192, 192, 192)";
this.draw = function() {
ctx.beginPath();
ctx.rect(this.x, this.y, 100, 100);
ctx.closePath();
ctx.fillStyle = this.color;
ctx.fill();
}
}
aliens.push(new Spaceships(100), new Spaceships(500));
drawAllAliens();
function redraw() {
requestAnimationFrame(redraw);
var maxXDiff = 100; //choose how far or near your aliens/squares can go
counter ++;
// remainder/modulo operator:
// reduce animation to every 4x per sec for that "choppy" animation
if(counter%15 === 0){
for (var i = 0; i < aliens.length; i ++) {
if ( ( (aliens[i].baseX + maxXDiff) < aliens[i].x ) || (aliens[i].baseX > aliens[i].x) ) {
aliens[i].velocityX = -aliens[i].velocityX; // switches the sign
}
aliens[i].x += aliens[i].velocityX;
}
}
drawAllAliens();
}
redraw();
function drawAllAliens() {
ctx.clearRect(0,0,canvas.width,canvas.height);
for (var i = 0; i < aliens.length; i++) {
aliens[i].draw();
}
}
} else {
alert("you need a better browser to play this game");
}
}
alien();

Smooth character movement in canvas game using keyboard controls

I'm creating a side-scrolling endless space themed game using canvas and JavaScript. I'm controlling a spaceship just by using the up and down arrows and I want to implement some kind of movement easing so that the ship doesn't just stop dead when I let go of the keys. I've looked around and haven't found anything plus my own attempts just aren't working. This is what I've tried.
Jet.prototype.checkDirection = function () {
if (this.isUpKey) {
this.drawY -= this.speed;
if (this.speed < 5) {
this.speed += 0.1;
}
}
if (this.isDownKey) {
this.drawY += this.speed;
if (this.speed < 5) {
this.speed += 0.1;
}
}
if (!this.isUpKey) {
if (!this.isDownKey) {
if (this.speed >= 0) {
this.drawY -= this.speed;
this.speed -= 1;
}
}
}
if (!this.isDownKey) {
if (!this.isUpKey) {
if (this.speed >= 0) {
this.drawY += this.speed;
this.speed -= 1;
}
}
}
You just want to apply some friction. Its pretty easy. You can do something like the following.
this.speed*=0.98;
The lower the value (0.8, 0.5, etc) the faster you will slow down.
I provided a demo where you can move around and will gradually slow down. Go ahead and play with the value and see how it affects it.
Live Demo
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d");
canvas.width = canvas.height = 300;
var x = 150, //initial x
y = 150, // initial y
velY = 0,
velX = 0,
speed = 2, // max speed
friction = 0.98, // friction
keys = [];
function update() {
requestAnimationFrame(update);
// check the keys and do the movement.
if (keys[38]) {
if (velY > -speed) {
velY--;
}
}
if (keys[40]) {
if (velY < speed) {
velY++;
}
}
if (keys[39]) {
if (velX < speed) {
velX++;
}
}
if (keys[37]) {
if (velX > -speed) {
velX--;
}
}
// apply some friction to y velocity.
velY *= friction;
y += velY;
// apply some friction to x velocity.
velX *= friction;
x += velX;
// bounds checking
if (x >= 295) {
x = 295;
} else if (x <= 5) {
x = 5;
}
if (y > 295) {
y = 295;
} else if (y <= 5) {
y = 5;
}
// do the drawing
ctx.clearRect(0, 0, 300, 300);
ctx.beginPath();
ctx.arc(x, y, 5, 0, Math.PI * 2);
ctx.fill();
}
update();
// key events
document.body.addEventListener("keydown", function (e) {
keys[e.keyCode] = true;
});
document.body.addEventListener("keyup", function (e) {
keys[e.keyCode] = false;
});
I think what I would do is on keyup don't stop the ship, just have a function that slows it down a little then call this function in setInterval at whatever interval gives you the desired effect and then once the speed of the ship is zero call clearInterval
So on keyup u basically setup setInterval(slowShip, 500)
You could try reducing the speed continuously on every frame
if(!playerUp && !playerDown && moveSpeed > 0){
moveSpeed--;
}

Categories