Pulse effect using vanilla JavaScript - javascript

Trying to create a pulse effect with a canvas object with plain vanilla javascript. Each function seems to be calling properly and all my values are logging right, but for some reason when the drawBall() function is called in the ballShrink() function nothing happens. I am expecting it to animate to shrink but it doesn't. ballRadius is reducing but no animation occurs. I have my code here and a JSFiddle here https://jsfiddle.net/96hthyw0/
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawBall();
}
function ballGrow() {
ballRadius += 1;
drawBall();
if (ballRadius === 20) {
clearInterval(ballGrowInterval);
ballShrinkInterval = setInterval(ballShrink, 100);
}
}
function ballShrink() {
ballRadius -= 1;
drawBall();
if (ballRadius === 0) {
clearInterval(ballShrinkInterval);
}
}
function testInterval() {
ballGrowInterval = setInterval(ballGrow, 100);
}
testInterval();
draw();

The problem is you're not clearing your last drawing but just adding new drawings on top of the existing one. Here is an updated fiddle: https://jsfiddle.net/96hthyw0/1/ but in short all I did was change your ballShrink function to this:
function ballShrink() {
ballRadius -= 1;
draw(); // clear canvas - note you could add this to ball grow too
drawBall();
if (ballRadius === 0) {
clearInterval(ballShrinkInterval);
}
}

Related

How to stop snake from moving diagonally?

I'm working on a Snake game in JavaScript and I want the snake to move only vertically or horizontally but it keeps moving diagonally. For example, if I press up, it moves up, but then if I press right, it'll move diagonally rather than only to the right.
const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')
const length_width = 15;
let snakeCoord = [
{x:300,y:150},
{x:315,y:150},
{x:330,y:150},
{x:345,y:150},
{x:360,y:150},
{x:375,y:150}
];
function drawSnakePart(snakePart) {
ctx.beginPath();
ctx.fillRect(snakePart.x, snakePart.y, length_width, length_width);
ctx.strokeRect(snakePart.x, snakePart.y, length_width, length_width);
ctx.closePath();
}
function drawSnake() {
snakeCoord.forEach(drawSnakePart);
}
function moveSnake(dx, dy) {
const head = {
x: snakeCoord[0].x + dx,
y: snakeCoord[0].y + dy
};
snakeCoord.unshift(head);
snakeCoord.pop();
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawSnake();
setTimeout(function() {
moveSnake(dx, dy)
}, 100);
}
function keyPress(e) {
let key = e.key;
if (key == "ArrowUp") {
if (snakeCoord[0].y - length_width !== snakeCoord[1].y) {
moveSnake(0, -length_width);
}
} else if (key == "ArrowDown") {
if (snakeCoord[0].y + length_width !== snakeCoord[1].y) {
moveSnake(0, length_width);
}
} else if (key == "ArrowLeft") {
if (snakeCoord[0].x - length_width !== snakeCoord[1].x) {
moveSnake(-length_width, 0);
}
} else if (key == "ArrowRight") {
if (snakeCoord[0].x + length_width !== snakeCoord[1].x) {
moveSnake(length_width, 0);
}
}
}
drawSnake();
document.addEventListener("keyup", keyPress);
<canvas width="500" height="500"></canvas>
On every keypress and then recursively you are setting new timeout setTimeout(function(){ moveSnake(dx,dy) }, 100);. You end up with growing number of controdicting moveSnake calls.
You should save timeout to a variable and clear it with clearTimeout() on keypress before calling moveSnake().
Rather than have the keyboard handler call a move method that starts its own timer loop, you should have a single update routine that updates everything for one frame of animation. You should also drive rendering as fast as possible using requestAnimationFrame and have each render request the next animation frame. (See example at the link provided.) If you want slower animation then you can ratchet a step-by-step update of the scene with a separate timer. (Trust me, some day you'll want high frame-rate animation, even in your step-by-step game.)
I was bored so I decided to implement some changes to your code.
const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')
const length_width = 15;
let snakeCoord = [
{x:300,y:150},
{x:315,y:150},
{x:330,y:150},
{x:345,y:150},
{x:360,y:150},
{x:375,y:150}
];
let snake = {
dir: {dx: -1, dy: 0},
nextDir: [], // buffered direction changes
speed: 5, // steps per second
ratchet: 0
};
function drawSnakePart(snakePart) {
ctx.beginPath();
ctx.fillRect(snakePart.x, snakePart.y, length_width, length_width);
ctx.strokeRect(snakePart.x, snakePart.y, length_width, length_width);
ctx.closePath();
}
function drawSnake() {
snakeCoord.forEach(drawSnakePart);
}
function moveSnake() {
if (snake.nextDir[0]) {
// only change directions if it doesn't result in doubling back on yourself
if (snakeCoord[0].x + snake.nextDir[0].dx * length_width !== snakeCoord[1].x
&& snakeCoord[0].y + snake.nextDir[0].dy * length_width !== snakeCoord[1].y) {
snake.dir = snake.nextDir[0];
}
snake.nextDir.shift(1);
}
const head = {
x: snakeCoord[0].x + snake.dir.dx * length_width,
y: snakeCoord[0].y + snake.dir.dy * length_width
};
snakeCoord.unshift(head);
snakeCoord.pop();
}
function keyPress(e) {
let key = e.key;
if (key == "ArrowUp") {
setDirection(0,-1);
} else if (key == "ArrowDown") {
setDirection(0, 1);
} else if (key == "ArrowLeft") {
setDirection(-1, 0);
} else if (key == "ArrowRight") {
setDirection(1, 0);
}
e.preventDefault();
}
drawSnake();
let lastTime = new Date();
window.requestAnimationFrame(render);
function setDirection(dx, dy) {
snake.nextDir.push({dx, dy}); // overwrite any pending direction changes.
}
function update() {
let now = Date.now();
let elapsed = (now - lastTime) / 1000;
snake.ratchet += elapsed * snake.speed;
while (snake.ratchet >= 1) {
moveSnake();
snake.ratchet -= 1;
}
lastTime = now;
}
function render() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
update();
drawSnake();
window.requestAnimationFrame(render);
}
document.addEventListener("keydown", keyPress);
* {
overflow: hidden
}
<canvas width="500" height="500"></canvas>
Made this a high frame rate render loop. Employed a ratchet mechanism to move the snake discretely every so often at some rate (see snake.speed). Added a property of the snake which is its direction. (see snake.dir). Buffered keystrokes of requested direction changes (see snake.nextDir) Simplified the logic of preventing the snake from doubling back on itself. Eat up one direction change per move step.
You still need to do the snake self-collision. (Assuming this is what you're up to, with a traditional snake game.)
Anyway, I hope this helps you, or someone else.

I want to start a new animation every time I click with requestFrameAnimation

I'm having multiple issues.
Everytime I click the animation goes faster. SOLVED #Jorge Fuentes González
Everytime I click the
last animation stops moving SOLVED #Kaiido
I have changed about everything I could think of around and still the same issue. Any help would be appreciated. Thanks!
function drawFrame(frameX, frameY, canvasX, canvasY) {
ctx.drawImage(img,
frameX * width, frameY * height,
width, height,
x_click, y_click,
scaledWidth, scaledHeight);
}
// Number of frames in animation
var cycleLoop = [3, 2, 1, 0, 7, 6, 5];
// Position of sprite in sheet
var currentLoopIndex = 0;
var frameCount = 0;
function step() {
frameCount++;
if (frameCount < 30) {
window.requestAnimationFrame(step);
return;
}
frameCount = 0;
// ctx.clearRect(0, 0, canvas.width, canvas.height);
drawFrame(cycleLoop[currentLoopIndex++], 0, 0, 0);
// Starts animation over
if (currentLoopIndex >= cycleLoop.length) {
// If you want to loop back in oposite direction after full animation
cycleLoop.reverse();
// Reseting position of which sprite to use
currentLoopIndex = 0;
}
window.requestAnimationFrame(step);
}
canvas.addEventListener("mousedown", getPosition, false);
function getPosition(event) {
x_click = event.x;
y_click = event.y;
x_click -= canvas.offsetLeft * 10;
y_click -= canvas.offsetTop * 10;
step();
}
==============================
JS Fiddle:
https://jsfiddle.net/HYUTS/q4fazt6L/9/
=======================================
Each time you click, you call step();, which will call window.requestAnimationFrame(step);, which will call step() the next animation frame. I don't see any stop point so the loop will be called forever.
So, when you call step() the first time, step() will be called continuously for ever, and if you click again, another step() "line" will be called a second time which will call window.requestAnimationFrame(step); for ever again, so now you will have two "lines" calling step(). That's why the animation goes faster, because on each animation frame step() will be called twice, doubling the calculations.
What you have to do is to check if the animation is already running (with a flag) and do not run it again, or to window.cancelAnimationFrame(ID) before starting the step() loop again. Note that on each click you must restart the variables that control the animation, like frameCount and currentLoopIndex
function drawFrame(frameX, frameY, canvasX, canvasY) {
ctx.drawImage(img,
frameX * width, frameY * height,
width, height,
x_click, y_click,
scaledWidth, scaledHeight);
}
// Number of frames in animation
var cycleLoop = [3, 2, 1, 0, 7, 6, 5];
// Position of sprite in sheet
var currentLoopIndex = 0;
var frameCount = 0;
var animationid = null;
function step() {
frameCount++;
if (frameCount < 30) {
animationid = window.requestAnimationFrame(step);
return;
}
frameCount = 0;
// ctx.clearRect(0, 0, canvas.width, canvas.height);
drawFrame(cycleLoop[currentLoopIndex++], 0, 0, 0);
// Starts animation over
if (currentLoopIndex >= cycleLoop.length) {
// If you want to loop back in oposite direction after full animation
cycleLoop.reverse();
// Reseting position of which sprite to use
currentLoopIndex = 0;
}
animationid = window.requestAnimationFrame(step);
}
canvas.addEventListener("mousedown", getPosition, false);
function getPosition(event) {
x_click = event.x;
y_click = event.y;
x_click -= canvas.offsetLeft * 10;
y_click -= canvas.offsetTop * 10;
frameCount = currentLoopIndex = 0;
window.cancelAnimationFrame(animationid);
step();
}
First step in your situation, is to create different objects for every animatables, so they can be drawn and updated independently.
After, you will have to split your logic in several parts.
A basic setup is to have one main loop that runs constantly in the background, and which will call all higher level objects update function, then all the drawing functions.
It's in these higher level methods that you will do the checks as to whether they should actually be discarded or not. The main loop doesn't have to take care of it.
In the example below, I created a class for your animatable objects. These objects will now have their own status, and will be able to update as they wish independently of others.
With this setup, adding a new Object in the scene is just a matter of pushing it in an Array.
// Our Animatable class (ES5 style...)
// Each object as its own frameCount and its own loopIndex
function Animatable(x, y) {
this.x = x;
this.y = y;
this.frameCount = 0;
this.loopIndex = 0;
this.cycleLoop = [3, 2, 1, 0, 7, 6, 5];
}
Animatable.prototype = {
update: function() {
this.frameCount++;
if (this.frameCount < 30) {
return;
}
this.frameCount = 0;
this.loopIndex++
if (this.loopIndex >= this.cycleLoop.length) {
// If you want to loop back in oposite direction after full animation
this.cycleLoop.reverse();
// Reseting position of which sprite to use
this.loopIndex = 0;
}
},
draw: function() {
// check the image is loaded
if (!img.naturalWidth) return;
var frameX = this.cycleLoop[this.loopIndex];
ctx.drawImage(img,
frameX * width, 0,
width, height,
this.x - scaledWidth/2, this.y - scaledHeight/2,
scaledWidth, scaledHeight);
}
};
// the main anim loop, independent
function startAnimLoop() {
animloop();
function animloop() {
requestAnimationFrame(animloop);
// updates
animatables.forEach(update);
// drawings
ctx.clearRect(0,0,canvas.width, canvas.height);
animatables.forEach(draw);
}
function update(animatable) {
animatable.update();
}
function draw(animatable) {
animatable.draw();
}
}
// one image for all
var img = new Image();
img.src = 'https://imgur.com/u2hjhwq.png';
img.onload = startAnimLoop;
// here we will hold all our objects
var animatables = [new Animatable(50, 50)]; // start with a single one
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');
// some constant from OP's fiddle
var scale = 1.5;
var width = 100; // Bigger numbers push left <-, smaller right ->
var height = 100;
var scaledWidth = scale * width;
var scaledHeight = scale * height;
canvas.onclick = function(evt) {
var rect = canvas.getBoundingClientRect();
var x = evt.clientX - rect.left;
var y = evt.clientY - rect.top;
// we simply create a new object ;-)
animatables.push(new Animatable(x, y));
};
canvas{border:1px solid}
<canvas id="canvas" width="500" height="500"></canvas>
window.requestAnimationFrame is still running when you click again, and when you click you add another tick per frame to your animation, doubling your speed, as step() is called two times each frame now. You should cancel the previous animation frame when clicking again, using window.cancelAnimationFrame()
https://developer.mozilla.org/en-US/docs/Web/API/Window/cancelAnimationFrame
Like this:
...
var animationID;
//in step() save the id in every call
function step() {
...
animationID = window.requestAnimationFrame(step);
...
}
//In getPosition cancel the current animation
function.getPosition(event) {
...
window.cancelAnimationFrame(animationId);
...
}
And if you want multiple animations running, create an object for each and make the function step() their property, then run window.requestAnimationFrame(this.step) inside of step(). You'd also have to save every variable needed for the animation like currentLoopIndex as part of the object.

p5.js : Trouble with clicking buttons and objects

I am really new to p5.js, and I'm trying to find a solution to this for a few days with no luck.
I have a button and an ellipse. When the button is clicked a rectangle appears on the canvas and when the ellipse is clicked its color changes.The problem is that I can't have both at the same time and the reason is this part of the code:
stroke(rgb);
strokeWeight(2);
If I have it on my code, when I click the button the rectangle appears, but when I click the ellipse nothing happens. If I leave it out of my code, I can change the ellipse's color, but when I click the button nothing happens.
I have no idea why these two lines of code make it impossible to interact with both the button and the shape, and I'd really like to find out. Am I missing something important?
My code is the following:
function setup(){
createCanvas(800, 600);
bubble = new new_ellipse(500,300);
}
function draw() {
background(51);
button = createButton("clickme");
button.position(100, 65);
button.mousePressed(show_rect);
bubble.display();
stroke(rgb);
strokeWeight(2);
}
function mousePressed(){
bubble.clicked();
}
function new_ellipse(x,y){
this.x = x;
this.y = y;
this.col = color(255,100);
this.display = function(){
stroke(255);
fill(this.col);
ellipse(this.x, this.y, 100, 100);
}
this.clicked = function(){
var d = dist(mouseX, mouseY, this.x, this.y);
if(d < 50){
this.col = color(233,11,75);
}
}
}
function show_rect(){
fill(255,24,23);
rect(200,200,100,100);
}
Two related issues:
Don't call createButton inside draw. draw is called about 60 times a second, so this spams buttons. draw is your animation loop. Use setup to set things up.
Given that draw() runs 60 times a second, any calls to show_rect triggered by a button press will be almost instantly wiped clean by background(51) a split second later.
One option is to introduce a boolean to keep track of whether the button has been pressed yet, then re-draw it every frame if it's supposed to be visible.
Another approach is to drop the draw() function and re-paint only when state changes. This prevents background(51) from blasting everything and is efficient, but also isn't suitable if you have animations or a good deal of dynamism in your app.
Here's a sample of the first approach, using a boolean:
var bubble, button;
var shouldShowRect = false;
function setup() {
createCanvas(800, 600);
bubble = new Ellipse(500, 300);
button = createButton("clickme");
button.mousePressed(function () {
// or set to true if you don't want to toggle
shouldShowRect = !shouldShowRect;
});
button.position(100, 65);
}
function draw() {
background(51);
bubble.display();
if (shouldShowRect) {
showRect();
}
strokeWeight(2);
}
function mousePressed() {
bubble.clicked();
}
function Ellipse(x, y) {
this.x = x;
this.y = y;
this.col = color(255, 100);
this.display = function() {
stroke(255);
fill(this.col);
ellipse(this.x, this.y, 100, 100);
}
this.clicked = function() {
var d = dist(mouseX, mouseY, this.x, this.y);
if (d < 50) {
this.col = color(233, 11, 75);
}
}
}
function showRect() {
fill(255, 24, 23);
rect(200, 200, 100, 100);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.5.0/p5.js"></script>

p5.js: Why is my ellipse flashing?

I have an ellipse that scales through draw(), but for some reason, it flashes uncontrollably. I can't seem to figure out why. I suspect it has to do with setTimeout. I need it because I need to wait 10 seconds before drawing the ellipse Here's the code:
//diameter of ellipse that increments
var dia1 = 0;
var dia2 = 0;
function setup() {
createCanvas(400,400);
stroke(255);
noFill();
frameRate(40);
}
//draw and increment ellipse
function circle1() {
ellipse(width/2,height/2, dia1,dia1);
dia1 = dia1+1;
if (dia1 >= width) {
dia1 = 0;
}
}
function circle2() {
ellipse(width/2,height/2, dia2,dia2);
dia2 = dia2+1;
if (dia2 >= width) {
dia2 = 0;
}
}
function draw() {
background(40,40,40);
//wait 10 seconds before drawing ellipse
setTimeout(function() { circle1(); }, 10000);
circle2();
console.log(dia1);
}
You should not use setTimeout() to call drawing functions.
If you want to do timing, use the millis() function. More info is available in the reference, but a basic program would look like this:
function draw(){
background(0);
if(millis() > 10000){
ellipse(width/2, height/2, 25, 25);
}
}

Why does my object (rectangle) flickers when I move my image?

When I move my object from the left to the right my rectangle (where I am supposed to put the high score and lives) flickers? How can I fix this? I have the same problem when I use a fillText, the text flickers each time I go to the left or right with my paddle.
var canvas = document.getElementById("mijnCanvas");
var mijnObject = canvas.getContext("2d");
var afbeelding = new Image();
var balkX = (canvas.width/2)-50;
var balkY = canvas.height-100;
function makenBalkKort() {
mijnObject.drawImage(afbeelding, balkX, balkY, afbeelding.width, afbeelding.height);
}
afbeelding.src = "Afbeeldingen/BrickSmasher_Balk_Kort.png";
function tekenenObjecten() {
mijnObject.clearRect(0, 0, canvas.width, canvas.height);
makenBalkKort();
drawFrame();
}
setInterval(tekenenObjecten, 20);
window.addEventListener("keydown", function LinksOfRechts() {
mijnObject.clearRect(balkX, balkY, canvas.width, canvas.height);
var balkNaarX = 15;
var code = event.which || event.keyCode;
if(code == 37) {
if(balkX > 0) {
balkX -= balkNaarX;
}
}
else if(code == 39) {
if(balkX < canvas.width-afbeelding.width) {
balkX += balkNaarX;
}
}
mijnObject.drawImage(afbeelding, balkX, balkY, afbeelding.width, afbeelding.height);
});
function drawFrame() {
mijnObject.beginPath();
mijnObject.fillStyle = "#000000";
mijnObject.strokeStyle = "#000000";
mijnObject.rect(0, 750, canvas.width, 50);
mijnObject.fill();
mijnObject.stroke();
mijnObject.closePath();
}
Here is my image:
Your image is flickering because you're clearing a large rectangle in your keydown event handler, and part of this covers the bottom area. There is then a delay of up to 20 milliseconds before the function that redraws the whole board is queued.
A simple, but dirty fix would be to adjust the area being cleared in the keydown handler to only cover the image area:
mijnObject.clearRect(balkX, balkY, afbeelding.width, afbeelding.height);
However, a better solution would be to avoid making any changes to the canvas in any event handlers; that is, to remove the clearRect and drawImage calls from the event handler. To ensure that the canvas refreshes as soon as possible with the updated state, you can then use requestAnimationFrame instead of setInterval to call the tekenenObjecten function:
function tekenenObjecten() {
mijnObject.clearRect(0, 0, canvas.width, canvas.height);
makenBalkKort();
drawFrame();
requestAnimationFrame(tekenenObjecten);
}
requestAnimationFrame(tekenenObjecten);

Categories

Resources