For a university project I have been tasked with creating a Flappy Bird clone. It's being done using the HTML5 canvas.
The issue doesn't happen very often, but it seems that every 6 or so seconds, the grass will flicker. I'm not sure what's causing this, it could be a performance issue.
Here is a link so you may see the issue: http://canvas.pixcelstudios.uk
Here is the function I'm using to the draw the grass:
var drawGrass = function(cWidth, ctx, minusX)
{
var x = bg_grass.x;
var y = bg_grass.y;
var w = bg_grass.w;
var h = bg_grass.h;
var img = bg_grass.img;
if (minusX[0] >= cWidth)
{
bg_grass.x = 0;
minusX[0] = 0;
}
ctx.drawImage(img, x, y, w, h);
if (minusX[0] > 0)
{
ctx.drawImage(img, w-minusX[0], y, w, h);
}
};
Basically, I'm drawing two grass sprites, each taking up a canvas width. One starts with an X of 0 and the other starts at the end of the canvas. Both are decremented each frame, then one is completely off the screen, it's completely reset to keep it looping.
I don't think it's anything to do with my update loop which is as follows:
this.update = function()
{
clearScreen();
updateBackground();
updatePositions();
checkCollisions();
render();
requestAnimFrame(gameSpace.update);
};
I've done a little bit of reading and I've read about having a second canvas to act as a buffer. Apparently this can stop flickering and improve performance? But all of the examples I've seen show the parts being drawn into the canvas out of a loop and I can't really see how doing it within a game loop (moving parts and all) would increase performance rather than decrease it. Surely the same operations are being performed, except now you also have to draw the second canvas onto the first?
Please let me know if you need any more information (although you should be able to see the whole source from the web link).
Thanks!
Okay I found the issue! Was just a simple mistake in my drawGrass function.
Due to the ordering, there'd be just a single frame where I'd set my shorthand X variable to bg_grass.x and THEN set bg_grass.x to something else, therefore drawing the wrong value.
I've now set my shorthand variables after the first if-statement.
However, if anyone could provide any insight into the second part of the question regarding a buffer canvas, I'd still much appreciate that.
Related
I'm looking to have an algorithm that can randomly draw a "squiggly wiggly" pattern as per the picture.
It would be nice if it were progressively drawn as you would draw it with a pen and if it were based on speed, acceleration and forces like a double pendulum animation might be.
This would be for javascript in the p5 library.
Is there some way of producing this that a) looks hand drawn and b) fills a page, somewhat like a Hilbert curve?
Very interested to hear ideas of how this could be produced, regardless of whether there is some kind of formal algorithm, although a formal algorithm would be best.
Cheers
I can think of two solutions, but there could be more as I'm not very good at coding in general yet.
First of all, you can use perlin noise. With the code
var noiseSeeds = [];
//This changes the noise value over time
var noiseTime = 0;
var x = 0;
var y = 0;
function setup() {
createCanvas(400, 400);
//This will help for making two separate noise values later
noiseSeeds = [random(100), random(100)];
}
function draw() {
//Finding the x value
noiseSeed(noiseSeeds[0]);
x = noise(noiseTime)*400;
//Finding the y value
noiseSeed(noiseSeeds[1]);
y = noise(noiseTime)*400;
//Increasing the noise Time so the next value is slightly different
noiseTime += 0.01;
//Draw the point
stroke(0);
strokeWeight(10);
point(x, y);
}
You can create a scribble on screen. You would have to use createGraphics()in some way to make this more efficient. This method isn't the best because the values are generally closer to the center.
The second solution is to make a point that has two states - far away from an edge and close to an edge. While it is far away, the point would keep going in relatively the same direction with small velocity changes. However, the closer the point gets to the edges, the (exponentially) bigger the velocity changes so that the point curves away from the edge. I don't know exactly how you could implement this, but it could work.
I've been experimenting with a basic game loop with HTML's Canvas element. Numerous tutorials online don't go into enough detail with the concepts of rendering and canvas.ctx (context).
What I'm trying to do is something very simple: Render an image on a canvas element and, on keydown, update its position and render it at the new location, making it move across the screen. Basically, what every video game does with its sprites.
I've been told through these tutorials that ctx.drawImage(image, x, y, ...) will work for this. However, what ends up happening in my version is essentially what happens when you win a game of solitaire on windows. It repeats the sprite's image as if it's creating a brand new sprite each time the game loops. The sprite itself doesn't move, a new sprite seems to be generated to the left/right/etc of the original one. I understand that I'm calling ctx.drawImage(...) every time I'm iterating through the game loop. However, this didn't happen when I used ctx.clearRect(...). It worked precisely how I expected it to. I'm not exactly sure why creating a rectangle with ctx works while creating an image doesn't.
My question is: Is there a way to simply update the position of the sprite without creating a brand new version of it every single loop?
Here's my relevant code:
let lastRender = 0; // For the general loop
let image = new Image();
image.src = "/img/image.png";
let state = {
pressedKeys: {
// left, right, up, down: false
},
position: {
x: canvas.width / 2,
y: canvas.width / 2
},
speed: 20
}
let pepsi = new Sprite({
img: image,
width: 100,
height: 100
)};
function Sprite (options) {
this.img = options.img;
this.width = options.width;
this.height = options.height;
this.render = function(){
ctx.drawImage(
this.img,
state.position.x,
state.position.y
)
}
}
function updatePosition(progress) {
//pressedKeys is just an object that relates WASD to the key codes
// and their respective directions, it's ignorable
if (state.pressedKeys.left) {
state.position.x -= state.speed;
}
if (state.pressedKeys.right) {
state.position.x += state.speed;
}
if (state.pressedKeys.up) {
state.position.y -= state.speed;
}
if (state.pressedKeys.down) {
state.position.y += state.speed;
}
}
function draw() {
pepsi.render();
}
function loop(timestamp) {
let progress = timestamp - lastRender;
update(progress) // <-- Updates position, doesn't touch draw()
draw(); // <-- Runs pepsi.render(); each loop
lastRender = timestamp;
window.requestAnimationFrame(loop);
}
window.requestAnimationFrame(loop); // for the general loop
If you have any qualms with the way this project is set up (for example, using the state.position for each Sprite), then I'd be glad to hear them in addition to the solution to my problem. Not in isolation. I got most of this code from contextless, non-specific online tutorials, but I understand most of it, save for the rendering.
Also, if you've seen this kind of question before and are on the fence about saying "Possible duplicate of {Borderline Tangentially-Related Post from Four Years Ago}", then here's some advice: Just answer the question again. It literally does nothing negative to you.
The solitaire smearing effect that you are getting, comes from the fact each frame is being drawn over the top of the last one. The canvas doesn't get cleared automatically between frames.
You mentioned that you have used clearRect, the use of clearRect is to clear all the pixels in the specified rectangle.
So if you put ctx.clearRect(0, 0, canvas.width, canvas.height) in the draw function before pepsi.render(), that should clear the canvas before drawing the next frame.
I'm creating a side-scrolling space-shooter in javascript. So far everything seems to be working well. However, there is an odd bug in the canvas rendering that I can't quite figure out (and is difficult to describe, so bear with me!)
I have a player that can shoot projectiles by clicking the left mouse button. When the projectile first leaves the player, there appears to be two of them for a brief second, until they eventually merge in to the one projectile. I'm not creating two, so this seems like an optical illusion (this is most evident if you fire a few projectiles in quick succession).
The really odd thing is, when I try and capture a screenshot of this happening, all looks fine. Can anyone figure out what's going on?
Player code including projectiles (full code in fiddle);
var Player = (function () {
// ------------------------------------------------------------------------------------------------
// PLAYER VARIABLES
// ------------------------------------------------------------------------------------------------
var w = 50;
var h = 50;
var x = 0;
var y = 0;
var projectiles = [];
// ------------------------------------------------------------------------------------------------
// BIND EVENTS TO THE GLOBAL CANVAS
// ------------------------------------------------------------------------------------------------
Canvas.bindEvent('mousemove', function (e) {
y = (e.pageY - Canvas.element.getBoundingClientRect().top) - (h / 2);
});
Canvas.bindEvent('click', function () {
createProjectile(50, (y + (h / 2)) - 10);
});
// ------------------------------------------------------------------------------------------------
// FUNCTIONS
// ------------------------------------------------------------------------------------------------
var createProjectile = function (x, y) {
projectiles.push({
x: x,
y: y
})
};
var update = function () {
for (var p = projectiles.length - 1; p >= 0; p--) {
projectiles[p].x += 10;
if (projectiles[p].x > Canvas.element.width)projectiles.splice(p, 1);
}
};
var render = function () {
Canvas.context.fillStyle = 'white';
Canvas.context.fillRect(x, y, w, h);
for (var p = 0; p < projectiles.length; p++) {
Canvas.context.fillStyle = 'red';
Canvas.context.fillRect(projectiles[p].x, projectiles[p].y, 5, 5);
}
};
// ------------------------------------------------------------------------------------------------
// Exposed Variables and Functions
// ------------------------------------------------------------------------------------------------
return {
update: update,
render: render
}
})();
Js Fiddle Demo HERE: https://jsfiddle.net/oqz204bj/
EDIT
Based on #Pimskie's answer, It does indeed seem like an optical illusion - so my question now becomes, how could I reduce this effect? I plan on implementing a feature in the future that allows the player to switch weapons (where some of them would **actually* fire multiple projectiles) but I don't want this effect to remain for fear of confusion.
yes it is an optical illusion. The reason it looks like there multiple squares when first fired is because your eyes are focused on the big static ship square. Once your eye starts to follow the movement path, then it looks more like a fluid square moving instead of a square being redrawn 60 or 30 times per second. hold a piece of paper or your hand up to your screen covering the left half of it. Focus on the piece of paper and fire a few shots. You'll notice that the shots seem to appear multiple, the same as when just fired. It's a matter of your mind seeing 3 different frames as the same one.
requestAnimationFrame depends on the frame rate of your browser and computer. In most cases that's 60fps. 60 to 70fps is the limit of most monitors, and so it doesn't make sense to try and go above that. HOWEVER you can create the illusion of a more fluid movement by having a trailing tracer effect on your projectiles. That would involve having 2 or 3 extra squares created behind each projectile that have less and less opacity.
My best guess it's an optical illusion indeed.
Check this updated fiddle: https://jsfiddle.net/oqz204bj/1/
I removed one requestAnimationFrame and replaced a other with a very slow setInterval, just for demonstration. You can see only one bullet is created.
I am trying to align bars which are synced to music (therefore moving) to a circle on a canvas. I already have the sync to music and make a round circle with it ready.
Right now I am trying to rotate them so it looks good, however since this is my first attempt with canvas I am failing miserably..
Here is the code Gist.
If i run it with the c.rotate(bar[i].rot); it gets all scrambled...
Please can you help me out with this.
Thank you very much.
First thing :
- Clear the whole canvas on each frame. It's too complicated, and performance-wise not worth the trouble to erase only what's required.
Second :
The Canvas's RenderingContext2D, is a context, meaning it's a state machine that will modify its state each time you perform a change on it.
This change might be a transform : translate, scale, rotate.
Or affect the rendering : globalAlpha, globalCompositeOperation, shadows.
Or be a strokeStyle/fillStyle/font change.
Any time you change the context's state, it is a very good practice to save it before, and restore it after :
context.save();
context.translate(.., ..);
context.beginPath();
context.move();
context.restore();
this way you end up with a context just as 'clean' after the call than before, no question asked.
For your code, you were quite close, for instance this code is quite ok (using random values) :
function animate() {
requestAnimationFrame(animate);
// clear whole screen
context.clearRect(0,0,600,600);
context.fillStyle = '#000';
// rotating step
var angle = 2*Math.PI/cnt ;
// save context
context.save();
context.translate(300,300);
for (var i=0; i<cnt; i++) {
context.rotate(angle);
var val = values[i];
context.fillRect(-10, 100, 20, 80*val );
values[i]+=(Math.random()-0.5)/20;
}
// restore now we're done.
context.restore();
}
you can try it here :
http://jsbin.com/haxeqaza/1/edit?js,output
do not hesitate to comment // animate() and to launch animate2() instead, with 2 nice little trick.
:-)
All transformations add up, unless you use the contexts .save() and .restore() methods. They save the current transformation and restore it afterwards, so transformations in beteeen will not affect the next draw.
But since you want to rotate each bar by a fixed amount, I suggest you do exactly that and set the rotation for each bar so a fixed amount instead of increasing it and let the rotations add up.
I'm making an HTML5 game engine, and I want my Camera object to have a zoom property. In the renderer, I thought that I could easily implement it, like this:
context.save();
context.scale(camera.zoom, camera.zoom);
draw();
context.restore();
There is a problem, though. When I first tested this, the camera seemed to zoom forever! I figured that context.save() and context.restore() probably aren't working as expected, and that the context's internal scaling factor is getting multiplied by camera's zoom ad infinitum.
This fixed the situation:
context.save();
context.scale(camera.zoom, camera.zoom);
draw();
context.scale(1/camera.zoom, camera.zoom);
context.restore();
This works now, but I'm afraid that this isn't the most elegant/fast solution. Also, I think it is possible that, because of the floating point calculation imprecision, the scaling factor always changes slightly. That is, 1/camera.zoom might not always produce the same results.
So, two questions:
Why wont the context.restore() set the scale of the context back to (1, 1)?
How can I manually manipulate the scaling of the context?
Edit:
It was pointed out that the number of context.save()'s and context.restore()'s might be different, but that is not the case.
Here is how I measured it:
renderer.context.save = (function()
{
var original = renderer.context.save;
return function()
{
renderer.saved ++;
original.call(renderer.context);
}
})();
renderer.context.restore = (function()
{
var original = renderer.context.restore;
return function()
{
renderer.saved --;
original.call(renderer.context);
}
})();
The renderer.saved value is 1 right before the context is restored one last time (after draw), and 0 after each rendering.
It seems like I have accidentally solved the problem. It works now.
The main suspect is this part of the code:
renderer.context.save();
//Erase everything.
renderer.context.globalAlpha = 1;
renderer.context.fillStyle = renderer.settings.fillStyle;
renderer.context.fillRect(0, 0, renderer.width, renderer.height);
//Zoom.
renderer.context.scale(camera.zoom, camera.zoom);
I believe that I used to zoom before actually saving the context, resulting in restoring having no effect on the zoom.