I am trying to create a 2d platformer, and the code is the base of it.
For some unknown reason, my final function draw mixes the other functions' properties (especially colour, and line width etc.).
If there is a type reason ( "this." is functioning inappropriate etc.)
I want to know for further projects.
Any good answer will be fully appreciated!.
/* main.js */
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d")
function Shooter() {
this.x = 100;
this.y = 500;
this.size = 50;
this.color = "blue";
this.borderColor = "black";
this.borderWidth = 5;
this.draw = function() {
ctx.fillRect(this.x, this.y, this.size, this.size);
ctx.strokeRect(this.x, this.y, this.size, this.size);
ctx.fillStyle = this.color;
ctx.strokeStyle = this.borderColor;
ctx.lineWidth = this.borderWidth;
}
}
function Gun() {
this.x = sh.x + sh.size / 2 + 10;
this.y = sh.y + sh.size / 2;
this.color = "grey";
this.borderColor = "brown";
this.borderWidth = 1;
this.width = 20;
this.height = 10;
this.draw = function() {
ctx.fillRect(this.x,this.y,this.width,this.height);
ctx.strokeRect(this.x,this.y,this.width,this.height);
ctx.fillStyle = this.color;
ctx.strokeStyle = this.borderColor;
ctx.lineWidth = this.borderWidth;
}
}
function Bullet() {
this.x = sh.x + sh.size * 2;
this.y = sh.y + sh.size / 2;
this.color = "orange";
this.radius = 5;
this.vx = 20;
this.borderColor = "green";
this.borderWidth = 2;
this.draw = function() {
ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
ctx.fillStyle = this.color;
ctx.strokeStyle = this.borderColor;
ctx.lineWidth = this.borderWidth;
ctx.stroke();
}
}
var sh = new Shooter();
var g = new Gun();
var b = new Bullet();
function draw() {
sh.draw();
g.draw();
b.draw();
requestAnimationFrame(draw);
}
draw();
/* main.css */
html, body {
overflow: hidden;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Page Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="main.css" />
</head>
<body>
<canvas id="canvas" width="1536px" height="754px"></canvas>
<!-- device innerWidth and innerHeight -->
<!-- make fullScreen to see the issue -->
<script src="main.js"></script>
</body>
</html>
The problem is that you first draw the shape and after that you set the fill and the stroke. Doing so you set the fill and stroke for the next shape.
In my code I'm using ctx.translate(0,-400) because otherwise the canvas would have been too large. Remove this line when setting your canvas size.
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
//setting the canvas size
canvas.width = 400;
canvas.height = 200;
ctx.translate(0,-400);
function Shooter() {
this.x = 100;
this.y = 500;
this.size = 50;
this.color = "blue";
this.borderColor = "black";
this.borderWidth = 5;
this.draw = function() {
// first set the colors for this shape
ctx.fillStyle = this.color;
ctx.strokeStyle = this.borderColor;
ctx.lineWidth = this.borderWidth;
// then fill and stroke the shape
ctx.fillRect(this.x, this.y, this.size, this.size);
ctx.strokeRect(this.x, this.y, this.size, this.size);
}
}
function Gun() {
this.x = sh.x + sh.size / 2 + 10;
this.y = sh.y + sh.size / 2;
this.color = "grey";
this.borderColor = "brown";
this.borderWidth = 1;
this.width = 20;
this.height = 10;
this.draw = function() {
ctx.fillStyle = this.color;
ctx.strokeStyle = this.borderColor;
ctx.lineWidth = this.borderWidth;
ctx.fillRect(this.x,this.y,this.width,this.height); ctx.strokeRect(this.x,this.y,this.width,this.height);
}
}
function Bullet() {
this.x = sh.x + sh.size * 2;
this.y = sh.y + sh.size / 2;
this.color = "orange";
this.radius = 5;
this.vx = 20;
this.borderColor = "green";
this.borderWidth = 2;
this.draw = function() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
ctx.fillStyle = this.color;
ctx.strokeStyle = this.borderColor;
ctx.lineWidth = this.borderWidth;
ctx.fill();
ctx.stroke();
}
}
var sh = new Shooter();
var g = new Gun();
var b = new Bullet();
function draw() {
sh.draw();
g.draw();
b.draw();
requestAnimationFrame(draw);
}
draw();
canvas{border:1px solid}
<canvas id="canvas"></canvas>
Some extra points.
The existing answer will solve your problem, however I have some time and noticed some points about your code that can be improved.
Performance is king
When writing games (Or for that matter animating content) you will get to a point where the complexity of the animation (number of animated and drawn items) gets to a stage where the device can no longer do it at full frame rate. This become more of a problems as you try to cover a larger range of devices.
To get the most speed per line of code in Javascript you need to be aware of some simple rules in regard to objects and how they are created and destroyed (free memory for other objects).
JavaScript is managed
This means that as a programmer you don't need to worry about memory. You create an object and the memory for it is found for you. When you no longer need the object javascript will cleanup the memory so it is free for other objects.
This make programing in Javascript easier. However in animations this can become a problem as the code that manages the memory allocation and clean up (AKA delete allocated memory or GC Garbage Collection) takes time. If your animation is taking a long time to compute and render each frame then GC is forced to block your script and cleanup.
This management of memory is a the biggest source of Jank in Javascript animations (games).
It also introduces extra processing to create objects, as free memory has to be located and allocated when you create a new object. It gets worse on low end devices with low amounts of RAM. Creating new objects will more often force GC to free up memory for the new object (stealing precious CPU cycles). This make performance on low end devices not a linear degradation but rather a logarithmic degradation.
Types of Objects.
Objects (player, pickups, bullets, FX) can be classified by how long they live and how many can exist at one time. The lifetime of the object means you can take advantages of how JS manages memory to optimise objects and memory use.
Single instance object.
These are objects that exist only once in the animation. That is have a lifetime from start to end (in a game that may be from level start to level end).
Eg the player, the score display.
Example
The best ways to create these objects is as a singleton or object factory. The bullets example below this uses a singleton
EG Object factory to create player
function Shooter() {
// Use closure to define the properties of the object
var x = 100;
var y = 500;
const size = 50;
const style = {
fillStyle : "blue",
strokeStyle : "black",
lineWidth : 5,
}
// the interface object defines functions and properties that
// need to be accessed from outside this function
const API = {
draw() {
ctx.fillStyle = style.fillStyle;
ctx.strokeStyle = style.strokeStyle;
ctx.lineWidth = style.lineWidth;
ctx.fillRect(x, y, size, size);
ctx.strokeRect(x, y, size, size);
// it is quicker to do the above two lines as
/*
ctx.beginPath(); // this function is done automatically
// for fillRect and strokeRect. It
ctx.rect(x, y, size, size);
ctx.fill();
ctx.stroke();
*/
}
}
return API;
}
You create an use it just like any other object
const player = new Shooter();
// or
const player = Shooter(); / You dont need the new for this type of object
// to draw
player.draw();
Many instance object.
These are objects that have very short lives, maybe a few frames. They can also exist in the hundreds (think of sparks FX in an explosion, or rapid fire bullets)
In your code you have only one bullet, but I can imagine that you could have many, or rather than a bullets, it could be gribble or spark FXs.
Instantiation
Creating objects requires CPU cycles. Modern JS has many optimisations and thus there is not much difference in how you create objects. But there is still a difference and using the best method pays off, especially if you do it 1000's of times a second. (the last JS game I wrote handles upto 80,000 FX objects a second, most live no more than 3-6 frames)
For many short lived objects define a prototype or use the class syntax (this reduces creating time by around 50%). Store items in a pool when not used to stop GC hits and reduce instantiation overheads. Be render smart, and don't needlessly waste time waiting for GPU to do pointless state changes.
Memory
These short lived objects are the biggest source of slowdown and JANK due to memory management overhead creating and deleting them creates.
To combat the memory management overhead you can do that management yourself. The best way is complicated (use a pre allocated (at level start) bubble array and sets a max number for each object during the level).
Object Pool
The simplest solutions that will get you 90% of the best and allows for unlimited (depends on total RAM) is to use object pools.
A pool is an array of unused objects, that you would normally have let GC delete. Active objects are stored in an array. They do their thing and when done they are moves from the object array into the pool.
When you need a new object rather than create it with new Bullet() you first check if the pool has any. If it does you take the old object from the pool reset its properties and put it on the active array. If the pool is empty you create a new object.
This means that you never delete an object (over the lifetime of the level / animation). Because you check the pool each time you create the max memory the bullets will use will actually be less than creating new objects each time (GC does not delete immediately)
Rendering
The 2D context is a great API for rendering, but use it badly and you can kill the frame rate without changing the rendered appearance.
If you have many objects that use the same style. Don't render them as separate path. Defined one path, add the objects then fill and stroke.
Example
Example of a rapid fire buller pool. All bullets have the same style. This interface hides the bullets from the main code. You can only access the bullets API
const bullets = (() => { // a singleton
function Bullet() { }
const radius = 5;
const startLife = 100;
this.radius = 5;
const style = {
fillStyle : "orange",
strokeStyle : "green",
lineWidth : 2,
}
// to hold the interface
Bullets.prototype = {
init(x,y,dx,dy) { // dx,dy are delta
this.life = startLife;
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
},
draw() {
ctx.arc(this.x, this.y, radius, 0 , Math.PI * 2);
},
move() {
this.x += this.dx;
this.y += this.dy;
this.life --;
}
};
const pool = []; // holds unused bullets
const bullets = []; // holds active bullets
// The API that manages the bullets
const API = {
fire(x,y,dx,dy) {
var b;
if(pool.length) {
b = bullets.pop();
} else {
b = new Bullet();
}
b.init(x,y,dx,dy);
bullets.push(bullets); // put on active array
},
update() {
var i;
for(i = 0; i < bullets.length; i ++) {
const b = bullets[i];
b.move();
if(b.life <= 0) { // is the bullet is no longer needed move to the pool
pool.push(bullets.splice(i--, 1)[0]);
}
}
},
draw() {
ctx.lineWidth = style.lineWidth;
ctx.fillStyle = style.fillStyle;
ctx.strokeStyle = style.strokeStyle;
ctx.beginPath();
for(const b of bullets) { b.draw() }
ctx.fill();
ctx.stroke();
},
get count() { return bullets.length }, // get the number of active
clear() { // remove all
pool.push(...bullets); // move all active to the pool;
bullets.length = 0; // empty the array;
},
reset() { // cleans up all memory
pool.length = 0;
bullets.length = 0;
}
};
return API;
})();
To use
...in the fire function
// simple example
bullets.fire(gun.x, gun.y, gun.dirX, gun.dirY);
...in the main render loop
bullets.update(); // update all bullets
if(bullets.count) { // if there are bullets to draw
bullets.draw();
}
...if restarting level
bullets.clear(); // remove bullets from previouse play
...if at end of level free the memory
bullets.clear();
The somewhere in between object.
These are objects that lay somewhere in between the above two types,
Eg a power ups, background items, enemy AI agents.
If object does not get created in high numbers, and have lives that may last more than a second to less than a complete level you need to ensure that they can be instantiated using the optimal method. (Personally I use pools (bubble array) for all but objects that live for the life of the level, but that can result in a lot of code)
Define the prototype
There are two way to effectively create these objects. Using the class syntax (personally hate this addition to JS) or define the prototype outside the instantiating function.
Example
function Gun(player, bullets) {
this.owner = player;
this.bullets = bullets; // the bullet pool to use.
this.x = player.x + player.size / 2 + 10;
this.y = player.y + player.size / 2;
this.width = 20;
this.height = 10;
const style = {
fillStyle : "grey",
strokeStyle : "brown",
lineWidth : 1,
};
}
// Moving the API to the prototype improves memory use and makes creation a little quicker
Gun.prototype = {
update() {
this.x = this.owner.x + this.owner.size / 2 + 10;
this.y = this.owner.y + this.owner.size / 2;
},
draw() {
ctx.lineWidth = this.style.lineWidth;
ctx.fillStyle = this.style.fillStyle;
ctx.strokeStyle = this.style.strokeStyle;
ctx.beginPath();
ctx.rect(this.x,this.y,this.width,this.height);
ctx.fill();
ctx.stroke();
},
shoot() {
this.bullets.fire(this.x, this.y, 10, 0);
},
}
Hope this helps. :)
Related
I'm trying to create a JavaScript object that has a method which allows a rectangle to rotate around its own origin during a rAF callback.
Things I have done:
Calculating the origin of an object within the canvas space.
Using ctx.save() and ctx.restore() - this is where my issues arise.
When I use the save() and restore() methods to push and pop the saved states within method calls for different objects it either doesn't change anything, or stops the entire animation.
The rotation in my example appears to be applied globally to the canvas (which is how the functionality is specified on MDN). I'm trying to translate around origin around multiple instances. I've spent hours on this.
Is there something going on with the inheritance mechanism in JavaScript that's not resetting my transforms for different instances of the rectangle objects in the code example?
// author: Nicholas Fazzolari
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var xCenterCanvas = innerWidth/2;
var yCenterCanvas = innerHeight/2;
// custom rectangle object
function RectangleCustom(x, y, w, h, color) {
this.w = w;
this.h = h;
this.x = x;
this.y = y;
this.color = color;
this.radians = (Math.PI/180) * 2; // change the last value to change speed
// draws a rectangle at given coordinates
this.draw = function() {
ctx.save();
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, this.w, this.h);
ctx.restore();
}
// rotates the rectangle around it's center relative to a given xy position
this.rotateRect = function() {
ctx.save();
ctx.translate(this.x + this.w * 0.5, this.y + this.h * 0.5);
ctx.rotate(this.radians);
ctx.translate(-this.x -this.w * 0.5, -this.y - this.h * 0.5);
//ctx.restore()
}
}
// singleton rectangles
var bkgRectangle = new RectangleCustom(0, 0, innerWidth, innerHeight, "#212121");
var redRectangle = new RectangleCustom(xCenterCanvas - 64, yCenterCanvas - 64, 128, 128, "#F44336");
// main animation loop
function mainAnimationLoop() {
// runs animation and clears the canvas each call
requestAnimationFrame(mainAnimationLoop);
ctx.clearRect(0, 0, innerWidth, innerHeight);
bkgRectangle.draw();
redRectangle.draw();
redRectangle.rotateRect();
}
mainAnimationLoop();
I have tried rotating multiple rectangles around their own origin at different positions without animation using save() and restore() - which worked.
Additionally, I have tried moving the rotate method inside of the draw method and the results were the same. My rationale was that the rotation would be applied as a function call within draw() - the rationale was clearly wrong.
Any insight towards a solution would be greatly helpful. I have included a link to the pen on codepen to see the concept in motion.
Instead of drawing the rects at (this.x, this.y) you may draw them at 0,0 and translate them to (this.x, this.y);
// author: Nicholas Fazzolari
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var xCenterCanvas = innerWidth/2;
var yCenterCanvas = innerHeight/2;
// custom rectangle object
function RectangleCustom(x, y, w, h, color) {
this.w = w;
this.h = h;
this.x = x;
this.y = y;
this.color = color;
this.radians = (Math.PI/180) * 2; // change the last value to change speed
this.rotation = 0;
// draws a rectangle at given coordinates
this.draw = function() {
this.rotation += this.radians;
ctx.save();
ctx.fillStyle = this.color;
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
ctx.fillRect(0,0, this.w, this.h);
ctx.restore();
}
this.update = function() {
// animation updates
}
}
// singleton rectangles
var bkgRectangle = new RectangleCustom(0, 0, innerWidth, innerHeight, "#212121");
var redRectangle = new RectangleCustom(xCenterCanvas - 64, yCenterCanvas - 64, 128, 128, "#F44336");
// main animation loop
function mainAnimationLoop() {
// runs animation and clears the canvas each call
requestAnimationFrame(mainAnimationLoop);
ctx.clearRect(0, 0, innerWidth, innerHeight);
bkgRectangle.draw();
redRectangle.draw();
}
mainAnimationLoop();
<canvas></canvas>
I've made an attempt at creating a simple audio visualizer for an mp3 track using canvas to draw/ animate circles in sync with the audio track using the web audio API.
What i've done so far:
What I want to do basically now is change the colours of the circles at a certain amount of time (eg. at a different part of the track - the drop etc). How would I go about doing this? setTimeout? I had a search but could not find anything (and I'm still quite new to JavaScript).
Here is the full code.
// Estabilish all variables tht analyser will use
var analyser, canvas, ctx, random = Math.random, circles = [];
// begins once all the page resources are loaded.
window.onload = function() {
canvas = document.createElement('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
document.body.appendChild(canvas);
ctx = canvas.getContext('2d', {alpha: false});
setupWebAudio(); //loads audio track
for (var i = 0; i < 20; i++) { // loop runs 50 times - creating 49 circles
circles[i] = new Circle();
circles[i].draw();
}
draw();
};
function setupWebAudio() {
var audio = document.createElement('audio');
audio.src = 'rustie.mp3';
audio.controls = true;
document.body.appendChild(audio);
var audioContext = new AudioContext(); // AudioContext object instance (WEB AUDIO API)
//contains an audio signal graph representing connections betweens AudioNodes
analyser = audioContext.createAnalyser(); // Analyser method
var source = audioContext.createMediaElementSource(audio);
// Re-route audio playback into the processing graph of the AudioContext
source.connect(analyser);
analyser.connect(audioContext.destination);
audio.play();
}
function draw() {
requestAnimationFrame(draw);
var freqByteData = new Uint8Array(analyser.frequencyBinCount); //Array of the frequency data from the audio track (representation of the sound frequency)
analyser.getByteFrequencyData(freqByteData); //take the analyser variable run it through the getByteFrequencyData method - passing through the array
ctx.fillStyle = "#ff00ed";
ctx.fillRect(0, 0, canvas.width, canvas.height); //fill the canvas with colour
for (var i = 1; i < circles.length; i++) {
circles[i].radius = freqByteData[i] * 1;
circles[i].y = circles[i].y > canvas.height ? 0 : circles[i].y + 1;
circles[i].draw();
}
}
function Circle() {
this.x = random() * canvas.width; // creates random placement of circle on canvas
this.y = random() * canvas.height;
this.radius = random() * 20 + 20; //creates random radius of circle
this.color = 'rgb(6,237,235)'; //colour of circles
}
Circle.prototype.draw = function() { //Drawing path
var that = this;
ctx.save();
ctx.beginPath();
ctx.globalAlpha = 0.75; //Transparency level
ctx.arc(that.x, that.y, that.radius, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
ctx.restore();
}
Another thing to add also - where in the code is it setting the movement/path of the circles? As they are going from the top to the bottom of the canvas. Wanted to know if I could change this.
Question 1
Instead of hard coding the circle color to this.color = 'rgb(6,237,235)';, you can use a global variable to hold the hue var hue = 0; and then use that in your Circle.draw() like this: ctx.fillStyle = 'hsla(' + hue + ', 50%, 50%, 0.75)';
Note 1: by defining the alpha in the color, you no longer need to set ctx.globalAlpha.
Note 2: you don't need this.color any more.
As you say, you can use setTimeout to change the hue variable at a certain point in time. Or you can use the data in freqByteData to change the hue variable continuously.
More info about hsl color: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#hsl()_and_hsla()
Question 2
You are updating the y coordinate of each circle with this line:
circles[i].y = circles[i].y > canvas.height ? 0 : circles[i].y + 1;
Which means: if current y position is greater than the canvas height: set the new value to zero, otherwise increment by one.
I am figuring this one out an unhealthy amount of time for now and I did not found any note for this bug. I started to build a simple HTML Canvas animation in JavaScript. For now I expect the small squares to move. Here is the code (I am also using babeljs):
class Pod {
constructor(x,y) {
this.posX = x;
this.posY = y;
this.velX = getRandomNumber(-5,5);
this.velY = getRandomNumber(-5,5);
}
getPos() {
return ([this.posX,this.posY]);
}
move() {
this.posX += this.velX;
this.posY += this.velY;
}
render() {
ctx.save();
ctx.rect(this.posX,this.posY,3,3);
ctx.fillStyle = "#ffffff";
ctx.fill();
ctx.restore();
}
}
/*the classes end here*/
var canvas = document.getElementById('canvas')
var ctx = canvas.getContext('2d');
var elementsNum = 10;
const stack = new Array(elementsNum);
for(var i = 0; i < elementsNum; i++) {
stack[i] = new Pod(getRandomNumber(0,500),getRandomNumber(0,500));
}
function run() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#000000";
ctx.fillRect(0,0,canvas.width,canvas.height);
ctx = canvas.getContext('2d');
for(var i = 0; i < elementsNum; i++) {
stack[i].move();
stack[i].render();
}
//window.requestAnimationFrame(run);
}
/*helper functions*/
function getRandomNumber(min, max) {
return Math.random() * (max - min) + min;
}
After running a cycle of the function run(), the small squares (I called them Pods) are rendered. Next cycle starts with clearing the canvas with ctx.clearRect... I am resetting the context and start moving and then drawing the Pods from the stack. When I draw the first Pod, it will draw all of them and also the previous frame.
Here is the codepen for this particular script: http://codepen.io/erikputz/pen/YNNXqX
(I knowingly commented the requestAnimationFrame, so you need to use the console to call the run() function)
Thank you forward for your help.
http://codepen.io/zfrisch/pen/bgazyO
This should solve your issue:
render() {
ctx.beginPath();
ctx.rect(this.posX,this.posY,3,3);
ctx.fillStyle = "#ffffff";
ctx.fill();
ctx.closePath();
}
With canvas you need to identify individual shapes through code by using the beginPath and closePath methods. In certain methods this is innate, like in fillRect. Hence the above code could be simplified even more to:
render() {
ctx.fillStyle = "#ffffff";
ctx.fillRect(this.posX,this.posY,3,3);
}
When you're just declaring a shape (rect) you need to specify when the path begins and when it is closed, otherwise it will most likely cause issues like the shape-bleeding you had in your original code.
Also, as a tip, you don't need to save state.save() / .restore() unless you're translating/scaling/rotating/or moving on the canvas element. Filling shapes doesn't apply.
I've read many posts and gone through several tutorials on HTML5 and the canvas specifically, but so far I have been unable to replicate my exact problem. If there is an answer for it already out there, please point me in the right direction.
My ultimate goal is to make a simple Pong game. I've drawn the basic objects using JavaScript and now I am trying to get the player (left) paddle to move. The problem I am running into with my current code is that instead of moving the paddle, it fills in the area it travels to. Through various trials and error of adapting and trying different methods I don't think the paddle is being elongated (adding pixels to the height), but it seems like a new paddle object is being created rather than the one being moved.
I've looked it over and over again (you guys aren't a first-ditch effort), but can't seem to figure out what's happening. Any help would be much appreciated.
// Requests a callback 60 times per second from browser
var animate = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback) { window.setTimeout(callback, 1000/60) };
// Get canvas and set context
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
context.fillStyle = "white";
// Settle variables for canvas width and height
var canvas_width = 500;
var canvas_height = 400;
// Set varaibles for paddle width and height
var paddle_width = 15;
var paddle_height = 75;
// Initializes variables
var playerScore = 0;
var computerScore = 0;
var player = new Player();
var computer = new Computer();
var ball = new Ball((canvas_width/2),(canvas_height/2));
// Renders the pong table
var render = function() {
player.render();
computer.render();
ball.render();
};
var update = function() {
player.update();
};
// Callback for animate function
var step = function() {
update();
render();
animate(step);
};
// Creates paddle object to build player and computer objects
function Paddle(x, y, width, height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.x_speed = 0;
this.y_speed = 0;
};
function Player() {
this.paddle = new Paddle(1, ((canvas_height/2) - (paddle_height/2)), paddle_width, paddle_height);
};
function Computer() {
this.paddle = new Paddle((canvas_width - paddle_width - 1), ((canvas_height/2) - (paddle_height/2)), paddle_width, paddle_height);
};
// Creates ball object
function Ball(x, y) {
this.x = x;
this.y = y;
this.radius = 10;
};
// Adds render functions to objects allowing them to be drawn on canvas
Ball.prototype.render = function() {
context.beginPath();
context.arc(this.x, this.y, this.radius, Math.PI * 2, false);
context.fillStyle = "white";
context.fill();
context.closePath();
};
Paddle.prototype.render = function() {
context.fillStyle = "white";
context.fillRect(this.x, this.y, this.width, this.height);
};
Player.prototype.render = function() {
this.paddle.render();
};
// Appends a move method to Paddle prototype
Paddle.prototype.move = function(x, y) {
this.y += y;
this.y_speed = y;
};
// Updates the location of the player paddle
Player.prototype.update = function() {
for(var key in keysDown) {
var value = Number(key);
if(value == 38) {
this.paddle.move(0, -4);
} else if (value == 40) {
this.paddle.move(0, 4);
} else {
this.paddle.move(0, 0);
}
}
};
Computer.prototype.render = function() {
this.paddle.render();
};
// Draws center diving line
context.strokeStyle = "white";
context.setLineDash([5, 3]);
context.beginPath();
context.moveTo((canvas_width/2), 0);
context.lineTo((canvas_width/2), canvas_height);
context.stroke();
context.closePath();
// Draws score on canvas
context.font = "40px Arial";
context.fillText('0', (canvas_width * .23), 50);
context.fillText('0', (canvas_width * .73), 50);
window.onload = function() {
animate(step);
};
var keysDown = {};
window.addEventListener("keydown", function(event) {
keysDown[event.keyCode] = true;
});
window.addEventListener("keyup", function(event) {
delete keysDown[event.keyCode];
});
My apologies: I cut the html/css code and meant to paste it, but forgot.
pong.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Pong</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<canvas id="canvas" width="500" height="400"></canvas>
<script src="main.js"></script>
</body>
</html>
style.css:
#canvas {
background-color: black;
}
The canvas itself has no "objects", it's just a bitmap, and anything you draw on it just changes the colours of certain pixels, making it look like it's drawing "on top" of what's already there but it doesn't even do that. It just flips pixel colours.
I don't see any code that "resets" the canvas for your next frames, so you're literally just drawing the same paddle at a different height value by colouring different pixels with the paddle's colours without recolouring the original pixels using the background colour.
The easiest solution here is to add a context.clearRect(0, 0, canvas.width, canvas.height); at the start of render():
var render = function() {
// clear the canvas so we can draw a new frame
context.clearRect(0,0,canvas.width,canvas.height);
// draw the frame content to the bitmap
player.render();
computer.render();
ball.render();
};
Note that this reveals you also need to draw your scores and center line every frame. Either that, or you need to make sure your paddles (and your ball) first restore the background colour on their old position, before drawing themselves on their new position, which would require considerably more code.
i am trying to animate 3 different shapes with setTimeout , my question is how can i use multiple setTimeout to animate 3 different shapes also is there a better way to do this maybe using setInterval
window.onload = draw;
var x = 5;
var y = 5;
radius = 50;
var x2 = 50;
var y2 = 120;
var x3 = 53;
var y3 = 230;
var context;
var loopTimer;
function draw(){
var canvas = document.getElementById('canvas');
context = canvas.getContext('2d');
context.save();
context.clearRect(0,0,720,550);
rectangle(x,y);
circle(x2,y2);
circle2(x3,y3);
}
function rectangle(x,y){
//drawing a rectangle
context.fillStyle = "rgba(93,119,188,237)";
context.clearRect(0,0,720,550);
context.rect(x, y, 50, 50);
context.fill();
context.lineWidth = 7;
context.strokeStyle = 'yellow';
context.stroke();
x += 1;
loopTimer = setTimeout('rectangle('+x+','+y+')',50);
}
function circle(x2,y2){
//darwong a circle
context.beginPath();
context.clearRect(0,0,720,550);
context.fillStyle = "#0000ff";
//Draw a circle of radius 20 at the current coordinates on the canvas.
context.arc(x2, y2, radius, 0, Math.PI*2, true);
context.closePath();
context.fill();
x2 += 1;
loopTimer = setTimeout('circle('+x2+','+y2+')',20);
}
function circle2(x3,y3){
//drawing a second circle
context.beginPath();
context.clearRect(0,0,720,550);
context.fillStyle = 'green';
context.arc(x3, y3, radius, 0, Math.PI*2, true);
context.closePath();
context.fill();
context.lineWidth = 5;//border around the circle
context.strokeStyle = 'red';
context.stroke();
x3 += 1;
loopTimer = setTimeout('circle2('+x3+','+y3+')',20);
}
Animating objects
When doing digital animation there is never need for more than one single timer.
The key is to bind properties to the objects being animation such as its position, speed (or steps), color, shape and so forth.
The logic step therefor is to create custom objects that we can collect this information and use an update function to do all the updates for us in a single step within the loop.
Example
ONLINE DEMO HERE
Lets create an object collection where we store all our objects:
var animObjects = [];
Nothing fancy about that - just an array.
A single loop
To show how simple this can get I will show the loop itself first, then dissect the object. The loop simply iterates through our collection and calls the update method on each object:
function loop() {
/// clear canvas before redrawing all objects
ctx.clearRect(0, 0, demo.width, demo.height);
/// loop through the object collection and update each object
for(var i = 0, animObject; animObject = animObjects[i]; i++) {
animObject.update();
}
/// use this instead of setTimeout/setInterval (!)
requestAnimationFrame(loop);
}
Now, you noticed probably that we used requestAnimationFrame (rAF) here instead of setTimeout or setInterval. rAF allows us to synchronize to the monitor's update frequency whereas setTimout/setInterval cannot. In addition rAF works more efficient than the other two which will benefit us if we need to animate a lot of stuff.
The animation object
Now lets take a look at the object, how come we only need to call update and things animate?
As we saw earlier we create a animated circle object simply by calling:
var animObject = new animCircle(context, x, y, radius, color, stepX, stepY);
This allows us to set which context we want to use (in case we use several layers of canvas), the start position, color and number of steps per frame. Note that these properties can be changed during the animation (e.g. change radius and color).
The object itself looks like this -
function animCircle(ctx, x, y, r, color, stepX, stepY) {
/// keep a reference to 'this' context for external calls
var me = this;
/// make the parameters public so we can alter them
this.x = x;
this.y = y;
this.radius = r;
this.color = color;
this.stepX = stepX;
this.stepY = stepY;
/// this will update the object by incrementing x and y
this.update = function() {
me.x += me.stepX;
me.y += me.stepY;
/// additional updates can be inserted here
render();
}
/// and this takes care of drawing the object with current settings
function render() {
ctx.beginPath();
ctx.arc(me.x, me.y, me.radius, 0, 2 * Math.PI);
ctx.closePath();
ctx.fillStyle = me.color;
ctx.fill();
}
return this;
}
That's all there is to it!
The objects update() method will do all the calculations for us as well as call the internal render() method.
You can create many of these objects at various positions and speeds and animate all of them from a single loop.
I only created an object for the circle to keep things simple. You should be able to create an object for rectangle and what have you by using this as a base. You can of course extent the object to keep track of other things as well such as stroke color, angles and so forth.
I also made the objects bounce off the canvas' edges for the sake of demo. Please adjust as needed.
If i get the question right... why you just dont create three different functions like drawCircle? drawWhatever and create three setTimeouts? or something like that... ?
why you use so manny ' ? there is no reason to do that in:
var loopTimer = setTimeout('draw('+x+','+y+')',20);