So I am trying to implement a concept of shooting star over an already drawn canvas of slowly moving stars. But I haven't found a way to do so. I tried implementing an array to make it look so but the trail isn't as efficient.
This code is as follows:
var canvas = document.querySelector('canvas');
var c = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var mouse = {
x : innerWidth/2,
y : innerHeight/2
};
var colors = [
'#3399CC',
'#67B8DE',
'#91C9E8',
'#B4DCED',
'#E8F8FF'
];
addEventListener('resize', function () {
canvas.width = innerWidth;
canvas.height = innerHeight;
init();
});
var isClicked = false;
addEventListener('click', function () {
mouse.x = event.clientX;
mouse.y = event.clientY;
isClicked = true;
});
function randomIntFromRange (min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
function randomColor (colors) {
return colors[Math.floor(Math.random() * colors.length)];
}
function Stars (x, y, radius, dy, color) {
this.x = x;
this.y = y;
this.radius = radius;
this.dy = dy;
this.color = color;
this.draw = function () {
c.beginPath();
c.arc(this.x, this.y, this.radius, 0, 2 * Math.PI, false);
c.shadowColor = this.color;
c.shadowBlur = 15;
c.shadowOffsetX = 0;
c.shadowOffsetY = 0;
c.fillStyle = this.color;
c.fill();
c.closePath();
}
this.update = function () {
if (this.y < -10) {
this.y = canvas.height + 10;
this.x = randomIntFromRange(this.radius, canvas.width);
}
this.y -= this.dy;
this.draw();
}
}
function ShootingStar (x, y, radius) {
this.x = x;
this.y = y;
this.radius = radius;
this.draw = function () {
c.beginPath();
c.arc(this.x, this.y, this.radius, 0, 2 * Math.PI, false);
c.shadowColor = "red";
c.shadowBlur = 15;
c.shadowOffsetX = 0;
c.shadowOffsetY = 0;
c.fillStyle = "red";
c.fill();
c.closePath();
}
this.update = function () {
this.x += 10;
this.y += 10;
this.draw();
}
}
let stars = [];
let shooting_star = [];
function init () {
stars = [];
for (var i = 0; i < 300; i++) {
var stars_radius = randomIntFromRange(2, 3);
var stars_x = randomIntFromRange(stars_radius, canvas.width);
var stars_y = randomIntFromRange(stars_radius, canvas.height);
var stars_dy = Math.random() / 6;
var color = randomColor(colors);
stars.push(new Stars(stars_x, stars_y, stars_radius, stars_dy, color));
}
}
function Explode () {
shooting_star = [];
var shooting_star_radius = 3;
var shooting_star_x = mouse.x;
var shooting_star_y = mouse.y;
for (var i = 0; i < 50; i++) {
shooting_star.push(new ShootingStar(shooting_star_x, shooting_star_y, shooting_star_radius));
if (shooting_star_radius > 0.2) {
shooting_star_radius -= .2;
}
var initiator = randomIntFromRange(-1, 1);
console.log(initiator);
shooting_star_x -= 3;
shooting_star_y -= 3;
}
}
function animate () {
requestAnimationFrame(animate);
c.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < stars.length; i++)
stars[i].update();
for (var i = 0; i < shooting_star.length; i++)
shooting_star[i].update();
if (isClicked == true) {
Explode();
isClicked = false;
}
}
init();
animate();
Here is the jsfiddle to it
https://jsfiddle.net/qjug4qdz/
I basically want the shooting star to come from a random location to the point where my mouse is clicked, but the trail is difficult to work with using an array.
Basic particles
For the particular effect you are looking for you can use a basic particle system.
As the shooting star moves you will drop a particle that starts with the star velocity and then slows and fades out.
The particle
You first start with the particle. I like to use Object.assign when creating objects but you can use any method you like, class, new, factory...
// This defines a particle and is copied to create new particles
const starDust = {
x : 0, // the current position
y : 0,
dx : 0, // delta x,y (velocity)
dy : 0,
drag : 0, // the rate that the particle slows down.
life : 0, // count down till particle is removed
age : 0, // the starting value of life
draw(){ // function to update and draw the particle
this.x += this.dx; // move it
this.y += this.dy;
this.dx *= this.drag; // slow it down
this.dy *= this.drag;
const unitLife = (this.life / this.age); // get the life left as a value
// from 0 to 1 where 0 is end
ctx.globalAlpha = unitLife; // set the alpha
ctx.beginPath();
ctx.arc(this.x,this.y,4,0,Math.PI); // draw the particle
this.life -= 1; // count down
return this.life > 0; // return true if still alive
}
Be memory aware.
A common mistake when creating particle systems is that people forget that creating and destroying objects will add a lot of work to javascripts memory management. The worst of which is GC (Garbage Collection). GC is a major source of lag and if you are wasteful with memory it will impact the quality of the animation. For simple particles it may not be noticeable, but you may want hundreds of complex particles spawning each frame. This is when GC realy hurts the animation.
Most Game engines reduce the GC impact by reusing objects rather than dereferencing and recreating. A common method is an object pool, where a second array holds objects that are no longer used. When a new object is needed then the pool is first checked, if there is an unused object, it is used, else a new object is created.
This way you never delete any particles, greatly reducing the GC workload, and preventing your animation from dropping frames (if you use a lot of particles)
Particle needs initializer
But you need to provide a way to re-initialize the object. Thus add the function init to the particle that will set it up to be used again
init(x,y,vx,vy){ // where x,y and velocity vx,vy of shooting star
this.x = x;
this.y = y;
this.dx = vx;
this.dy = vy;
// give a random age
this.age = (Math.random() * 100 + 60) | 0; // in frames and | 0 floors the value
this.life = this.age; // and set the life countdown
this.drag = Math.random() * 0.01 + 0.99; // the drag that slows the particle down
}
} // end of starDust object.
The arrays
To manage all the particles we create object that has arrays and methods for adding, creating and rendering the particles. In this case I will call it dust
const dust = {
particles : [], // array of active particles
pool : [], // array of unused particels
createParticle(particleDesc){ // creates a new particle from particleDesc
return Object.assign({},particleDesc);
},
add(x,y,vx,vy){ // where x,y and velocity vx,vy
var dust;
if(this.pool.length){ // are there any particles in the pool
dust = this.pool.pop(); // get one
}else{ // else there are no spare particles so create a new one
dust = this.createParticle(starDust);
}
dust.init(x,y,vx,vy); // init the particle
this.items.push(dust); // put it in the active particle array
return dust; // return it (sometimes you want to do something with it)
},
draw(){ // updates and draws all active particles
var i = 0;
while(i < this.items.length){ // iterate each particle in items
if(this.items[i].draw() === false){ // is it dead??
this.pool.push(this.items.splice(i,1)[0]); // if dead put in the pool for later
}else{ i ++ } // if not dead get index of next particle
}
}
}//end of dust object
Using the particle system
The simplest way to create a particle is to use a random number and set the chance of a particle being created every frame.
In your main loop
// assuming that the falling star is called star and has an x,y and dx,dy (delta)
if(star) { // only if there is a start to spawn from
// add a particle once every 10 frame (on average
if(Math.random() < 0.1) {
dust.add(star.x, star.y, star.dx, star.dy); // add some dust at the shooting starts position and speed
}
}
dust.draw(); // draw all particles
And that is it.
Related
Very first post so I apologize if I format my question wrong.
I have had some experience animating multiple circles with canvas prior to this project but currently am trying to animate a custom made letter Z.
Currently in the process of trying to make 100 Z's do the same thing, for some reason my constructor object is only drawing one.
So far I have:
console logged in my draw function to see if its getting that far and it is
console logged the array of Z objects, and all 100 are there.
in that array of objects I've checked all their x-coordinates and they are different, so I don't think all 100 are just sitting on top of each other.
My suspicions are that update function is some how wrong with my use of "this"
I guess ill just dump my whole javascript file incase its something else. Sorry i dont know typical protocol.
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;
canvasHeight = canvas.height;
canvasWidth = canvas.width
var dx = 0
var dy = 0;
var direction = true
function Z(xCoord, yCoord, dx, dy, direction) {
this.x = xCoord
this.y = yCoord
this.dy = dy
this.dx = dx
this.direction = direction
this.update = function(){
//making the Z wiggle
if (this.dx < 20 && this.direction) {
this.dx++
}
else if (this.dx === 20 && this.direction) {
this.direction = false;
}
if (this.dx > -20 && !this.direction) {
this.dx--
}
else if (this.dx === -20 && !this.direction) {
this.direction = true;
}
//if Z gets to top of page it resets down at the bottom
if (this.dy === -canvasHeight - this.y) {
this.dy = 0
}
this.dy--;
this.draw()
}
this.draw = function() {
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
context.translate(this.dx, this.dy); /// translate (move)
//The Letter Z
context.beginPath();
context.moveTo(this.x, this.y + canvasHeight); // x+0
context.lineTo(this.x+10, this.y + canvasHeight); // x+10
context.lineTo(this.x+2, this.y + canvasHeight-9); // x+2
context.lineTo(this.x+5, this.y + canvasHeight-9); // x+5
context.lineTo(this.x+10, this.y + canvasHeight-9); // x+10
context.lineTo(this.x+10, this.y + canvasHeight-10); // x+10
context.lineTo(this.x, this.y + canvasHeight-10); // x+0
context.lineTo(this.x+8, this.y + canvasHeight-1); // x+8
context.lineTo(this.x, this.y + canvasHeight-1); // x+0
context.lineTo(this.x, this.y + canvasHeight); // x+0
// complete custom shape
context.closePath();
context.lineWidth = 4;
context.fillStyle = 'white';
context.fill();
context.strokeStyle = 'black';
context.stroke();
context.translate(-this.dx, -this.dy); /// translate (move)
}
}
var ZArr = []
//for loop to make all the Z's
for (var index = 0; index < 100; index++) {
var randomX = Math.random() * canvasWidth;
var randomY = Math.floor(Math.random() * 500) + 1
var newZ = new Z(randomX, randomY, dx, dy, direction);
ZArr.push(newZ)
}
// console.log(ZArr)
function executeFrame() {
for (let index = 0; index < ZArr.length; index++) {
ZArr[index].update();
}
requestAnimationFrame(executeFrame);
}
//start animation
executeFrame();
Move context.clearRect(0, 0, context.canvas.width, context.canvas.height); to your render loop. Your erasing each drawing every loop of your array.
function executeFrame() {
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
for (let index = 0; index < ZArr.length; index++) {
ZArr[index].update();
}
requestAnimationFrame(executeFrame);
}
This is because your clear canvas line is inside of your Z object's draw function, so the field will be cleared everytime you try to draw a new Z. This means only the last one in the array will be visible. Just move this line.
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
To your executeFrame function just above the for loop.
Good luck!
Good day,
I am generating some circles with colors, sizes and positions. All of this things randomly.
But, my problem is that I do not want them to collide, so that no circle is inside another, not even a little bit.
The logic explained in detail within the code, I would like to know why the failure and why the infinite loop.
The important functions are:
checkSeparation and setPositions
window.addEventListener("load", draw);
function draw() {
var canvas = document.getElementById("balls"), // Get canvas
ctx = canvas.getContext("2d"); // Context
canvas.width = document.body.clientWidth; // Set canvas width
canvas.height = document.documentElement.scrollHeight; // Height
var cW = canvas.width, cH = canvas.height; // Save in vars
ctx.fillStyle = "#fff022"; // Paint background
ctx.fillRect(0, 0, cW, cH); // Coordinates to paint
var arrayOfBalls = createBalls(); // create all balls
setPositions(arrayOfBalls, cW, cH);
arrayOfBalls.forEach(ball => { // iterate balls to draw
ctx.beginPath(); // start the paint
ctx.fillStyle = ball.color;
ctx.arc(ball.x, ball.y, ball.radius, 0, (Math.PI/180) * 360, false); // draw the circle
ctx.fill(); // fill
ctx.closePath(); // end the paint
});
}
function Ball() {
this.x = 0; // x position of Ball
this.y = 0; // y position of Ball
this.radius = Math.floor(Math.random() * ( 30 - 10 + 1) + 10);
this.color = "";
}
Ball.prototype.setColor = function(){
for(var j = 0, hex = "0123456789ABCDEF", max = hex.length,
random, str = ""; j <= 6; j++, random = Math.floor(Math.random() * max), str += hex[random])
this.color = "#" + str;
};
function random(val, min) {
return Math.floor(Math.random() * val + min); // Random number
}
function checkSeparation(value, radius, toCompare) {
var min = value - radius, // Min border of circle
max = value + radius; // Max border of circle
// Why ? e.g => x position of circle + this radius it will be its right edge
for(; min <= max; min++) {
if(toCompare.includes(min)) return false;
/*
Since all the positions previously obtained, I add them to the array, in order to have a reference when verifying the other positions and that they do NOT collide.
Here I check if they collide.
In the range of:
[pos x - its radius, pos x + its radius]
*/
}
return true; // If they never collided, it returns true
}
function createBalls() {
var maxBalls = 50, // number of balls
balls = []; // array of balls
for(var j = 0; j < maxBalls; j++) { // create 50 balls
var newBall = new Ball(); // create ball
newBall.setColor(); // set the ball color
balls.push(newBall); //push the ball to the array of balls
}
return balls; // return all balls to draw later
}
function setPositions(balls, canvasW, canvasH) {
var savedPosX = [], // to save x pos of balls
savedPosY = []; // to save y pos of balls
for(var start = 0, max = balls.length; start < max; start++) {
var current = balls[start], // current ball
randomX = random(canvasW, current.radius), // get random value for x pos
randomY = random(canvasH, current.radius); // get random value for y pos
if(checkSeparation(randomX, current.radius, savedPosX)) {
current.x = randomX; // If it position, along with your radio does not touch another circle, I add the position
} else {
// start--; continue;
console.log("X: The above code causes an infinite loop");
}
if(checkSeparation(randomY, current.radius, savedPosY)) {
current.y = randomY;
} else {
// start--; continue;
console.log("Y: The above code causes an infinite loop");
}
}
}
body,html {
margin: 0; border: 0; padding: 0; overflow: hidden;
}
<canvas id="balls"></canvas>
In your code, you test possible collisions by means of arrays of already used x and y positions, but you never add new positions to these arrays. You also check the x and y coordinates separately, which means you are really testing a collision of a bounding box.
Two circles collide when the distance between their centres is smaller than the sum of their radii, so you could use:
function collides(balls, n, x, y, r) {
for (let i = 0; i < n; i++) {
let ball = balls[i];
let dx = ball.x - x;
let dy = ball.y - y;
let dd = dx*dx + dy*dy;
let rr = r + ball.radius;
if (dd < rr * rr) return true;
}
return false;
}
function setPositions(balls, canvasW, canvasH) {
for (let i = 0, max = balls.length; i < max; i++) {
let ball = balls[i],
r = ball.radius,
maxTries = 20;
ball.x = -canvasW;
ball.y = -canvasH;
for (let tries = 0; tries = maxTries; tries++) {
let x = random(canvasW - 2*r, r),
y = random(canvasH - 2*r, r);
if (!collides(balls, i, x, y, r)) {
ball.x = x;
ball.y = y;
break;
}
}
}
}
This is reasonably fast for 50 balls, but will be slow if you have more balls. In that case, some spatial data structures can speed up the collision search.
You must also guard against the case that no good place can be found. The code above gives up after 20 tries and moves the ball outside the visible canvas. You can improve the chances of placing balls by sorting the balls by radius and plaing the large balls first.
Finally, you add one hex digit too many to your random colour. (That for loop, where everything happens in the loop control is horrible, by the way.)
I am trying to create a bouncing ball (on a canvas). I would love if the ball could accelerate when going up and down. No idea how to do this with setInterval. Here is my code:
setInterval(function animate() {
ctx.clearRect( 0, 0, canvas.width, canvas.height);
if (movement1 === true) {
dotHeight += 1;
if (dotHeight >= 100) movement1 = false;
} else {
dotHeight -= 1;
if (dotHeight <= 0) movement1 = true;
}
ctx.beginPath();
ctx.arc(canvas.width / 2, (canvas.height / 2) + dotHeight, dotSize, 0, 2 * Math.PI);
ctx.fillStyle = "white";
ctx.fill();
}, 4);
This results in a linear movement. I would love to have a natural movement. Basically starting fast and getting slower when reaching the top and vice versa.
You should have both a speed and a gravity (or acceleration) variable:
speed tells you how many units (pixels here) is going to travel your object in the current update.
gravity tells you by how many units is speed increased on each update.
You want a constant gravity so that speed is increasing the same amount of pixels on each update. That will give you a variable speed, so that your object (dot here) is travelling longer or shorter distances on each update, depending on the direction it is travelling.
To make the dot bounce just change the direction of its speed once it reaches the floor. You just need to multiply it by -1 or, instead of that, you could multiply it by a bouncingFactor (-1 < bouncingFactor < 0) so that it loses energy on each bounce:
Here you can see a working example:
var canvas = document.getElementById("canvas");
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
var ctx = canvas.getContext("2d");
var frames = 0; // Frame counter just to make sure you don't crash your browser while editing code!
// DOT STUFF:
var dotSize = 20;
var dotMinY = 0 + dotSize; // Start position
var dotMaxY = canvas.height - dotSize; // Floor
var dotY = dotMinY;
var dotSpeed = 0;
var dotLastBounceSpeed = 0; // You can use this to determine whether the ball is still bouncing enough to be visible by the user.
var center = canvas.width / 2; // Try to take every operation you can out of the animate function.
var pi2 = 2 * Math.PI;
// WORLD STUFF:
var gravity = .5;
var bounceFactor = .8; // If < 1, bouncing absorbs energy so ball won't go as high as it was before.
// MAIN ANIMATION LOOP:
function animate() {
ctx.clearRect( 0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(center, dotY, dotSize, 0, pi2);
ctx.fillStyle = "red";
ctx.fill();
// First, dotSpeed += gravity is calculated and that returns the new value for dotSpeed
// then, that new value is added to dotY.
dotY += dotSpeed += gravity;
if(dotY >= dotMaxY ) {
dotY = dotMaxY;
dotSpeed *= -bounceFactor;
}
var dotCurrentBounceSpeed = Math.round(dotSpeed * 100); // Takes two decimal digits.
if(frames++ < 5000 && dotLastBounceSpeed != dotCurrentBounceSpeed) {
dotLastBounceSpeed = dotCurrentBounceSpeed;
setTimeout(animate, 16); // 1000/60 = 16.6666...
}
else alert("Animation end. Took " + frames + " frames.");
}
animate();
html, body, #canvas {
position:relative;
width: 100%;
height: 100%;
margin: 0;
overflow:hidden;
}
<canvas id="canvas"></canvas>
You should also consider using requestAnimationFrame insted of setTimeout. From the MDN doc:
The Window.requestAnimationFrame() method tells the browser that you
wish to perform an animation and requests that the browser call a
specified function to update an animation before the next repaint. The
method takes as an argument a callback to be invoked before the
repaint.
The same example with requestAnimationFrame:
var canvas = document.getElementById("canvas");
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
var ctx = canvas.getContext("2d");
var frames = 0; // Frame counter just to make sure you don't crash your browser while editing code!
// DOT STUFF:
var dotSize = 20;
var dotMinY = 0 + dotSize; // Start position
var dotMaxY = canvas.height - dotSize; // Floor
var dotY = dotMinY;
var dotSpeed = 0;
var dotLastBounceSpeed = 0; // You can use this to determine whether the ball is still bouncing enough to be visible by the user.
var center = canvas.width / 2; // Try to take every operation you can out of the animate function.
var pi2 = 2 * Math.PI;
// WORLD STUFF:
var gravity = .5;
var bounceFactor = .8; // If < 1, bouncing absorbs energy so ball won't go as high as it was before.
// MAIN ANIMATION LOOP:
function animate() {
ctx.clearRect( 0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(center, dotY, dotSize, 0, pi2);
ctx.fillStyle = "red";
ctx.fill();
// First, dotSpeed += gravity is calculated and that returns the new value for dotSpeed
// then, that new value is added to dotY.
dotY += dotSpeed += gravity;
if(dotY >= dotMaxY ) {
dotY = dotMaxY;
dotSpeed *= -bounceFactor;
}
var dotCurrentBounceSpeed = Math.round(dotSpeed * 100); // Takes two decimal digits.
if(frames++ < 5000 && dotLastBounceSpeed != dotCurrentBounceSpeed) {
dotLastBounceSpeed = dotCurrentBounceSpeed;
//setTimeout(animate, 10);
window.requestAnimationFrame(animate); // Better!!
}
else alert("Animation end. Took " + frames + " frames.");
}
animate();
html, body, #canvas {
position:relative;
width: 100%;
height: 100%;
margin: 0;
overflow:hidden;
}
<canvas id="canvas"></canvas>
As you can see, you only need to change one line of code! However, you may need a polyfill so that you fall back to setTimeout if the browser does not support requestAnimationFrame.
You can learn more about requestAnimationFrame in this post. It explains the basics and also how to set a custom frame rate.
The basic principle is to use a velocity variable as opposed to a constant height increment. So instead of dotHeight += 1 or dotHeight -= 1 you would do dotHeight += dotVelocity, where you define dotVelocity, and subtract it by a constant value (gravity) whenever the ball is in the air.
var dotHeight = 0;
var dotVelocity = 3; // start out moving up, like in your example
var gravity = .1; // you can adjust this constant for stronger/weaker gravity
setInterval(function animate() {
ctx.clearRect( 0, 0, canvas.width, canvas.height);
if (dotHeight > 0) { // if it hit the ground, stop movement
dotVelocity -= gravity;
dotHeight += dotVelocity;
}
ctx.beginPath();
ctx.arc(canvas.width / 2, (canvas.height / 2) + dotHeight, dotSize, 0, 2 * Math.PI);
ctx.fillStyle = "white";
ctx.fill();
}, 4);
You could use a speed variable instead of the constant 1 to determine how "far" to move the ball, like this:
var speed = 1;
setInterval(function animate() {
ctx.clearRect( 0, 0, canvas.width, canvas.height);
if (movement1 === true) {
dotHeight += speed;
if (dotHeight >= 100) movement1 = false;
// make it faster
speed += 1;
} else {
dotHeight -= speed;
if (dotHeight <= 0) movement1 = true;
// slow down
speed -= 1;
}
ctx.beginPath();
ctx.arc(canvas.width / 2, (canvas.height / 2) + dotHeight, dotSize, 0, 2 * Math.PI);
ctx.fillStyle = "white";
ctx.fill();
}, 4);
I'm trying to create a game in canvas with javascript where you control a spaceship and have it so that the canvas will translate and rotate to make it appear like the spaceship is staying stationary and not rotating.
Any help would be greatly appreciated.
window.addEventListener("load",eventWindowLoaded, false);
function eventWindowLoaded() {
canvasApp();
}
function canvasSupport() {
return Modernizr.canvas;
}
function canvasApp() {
if (!canvasSupport()) {
return;
}
var theCanvas = document.getElementById("myCanvas");
var height = theCanvas.height; //get the heigth of the canvas
var width = theCanvas.width; //get the width of the canvas
var context = theCanvas.getContext("2d"); //get the context
var then = Date.now();
var bgImage = new Image();
var stars = new Array;
bgImage.onload = function() {
context.translate(width/2,height/2);
main();
}
var rocket = {
xLoc: 0,
yLoc: 0,
score : 0,
damage : 0,
speed : 20,
angle : 0,
rotSpeed : 1,
rotChange: 0,
pointX: 0,
pointY: 0,
setScore : function(newScore){
this.score = newScore;
}
}
function Star(){
var dLoc = 100;
this.xLoc = rocket.pointX+ dLoc - Math.random()*2*dLoc;
this.yLoc = rocket.pointY + dLoc - Math.random()*2*dLoc;
//console.log(rocket.xLoc+" "+rocket.yLoc);
this.draw = function(){
drawStar(this.xLoc,this.yLoc,20,5,.5);
}
}
//var stars = new Array;
var drawStars = function(){
context.fillStyle = "yellow";
if (typeof stars !== 'undefined'){
//console.log("working");
for(var i=0;i< stars.length ;i++){
stars[i].draw();
}
}
}
var getDistance = function(x1,y1,x2,y2){
var distance = Math.sqrt(Math.pow((x2-x1),2)+Math.pow((y2-y1),2));
return distance;
}
var updateStars = function(){
var numStars = 10;
while(stars.length<numStars){
stars[stars.length] = new Star();
}
for(var i=0; i<stars.length; i++){
var tempDist = getDistance(rocket.pointX,rocket.pointY,stars[i].xLoc,stars[i].yLoc);
if(i == 0){
//console.log(tempDist);
}
if(tempDist > 100){
stars[i] = new Star();
}
}
}
function drawRocket(xLoc,yLoc, rWidth, rHeight){
var angle = rocket.angle;
var xVals = [xLoc,xLoc+(rWidth/2),xLoc+(rWidth/2),xLoc-(rWidth/2),xLoc-(rWidth/2),xLoc];
var yVals = [yLoc,yLoc+(rHeight/3),yLoc+rHeight,yLoc+rHeight,yLoc+(rHeight/3),yLoc];
for(var i = 0; i < xVals.length; i++){
xVals[i] -= xLoc;
yVals[i] -= yLoc+rHeight;
if(i == 0){
console.log(yVals[i]);
}
var tempXVal = xVals[i]*Math.cos(angle) - yVals[i]*Math.sin(angle);
var tempYVal = xVals[i]*Math.sin(angle) + yVals[i]*Math.cos(angle);
xVals[i] = tempXVal + xLoc;
yVals[i] = tempYVal+(yLoc+rHeight);
}
rocket.pointX = xVals[0];
rocket.pointY = yVals[0];
//rocket.yLoc = yVals[0];
//next rotate
context.beginPath();
context.moveTo(xVals[0],yVals[0])
for(var i = 1; i < xVals.length; i++){
context.lineTo(xVals[i],yVals[i]);
}
context.closePath();
context.lineWidth = 5;
context.strokeStyle = 'blue';
context.stroke();
}
var world = {
//pixels per second
startTime: Date.now(),
speed: 50,
startX:width/2,
startY:height/2,
originX: 0,
originY: 0,
xDist: 0,
yDist: 0,
rotationSpeed: 20,
angle: 0,
distance: 0,
calcOrigins : function(){
world.originX = -world.distance*Math.sin(world.angle*Math.PI/180);
world.originY = -world.distance*Math.cos(world.angle*Math.PI/180);
}
};
var keysDown = {};
addEventListener("keydown", function (e) {
keysDown[e.keyCode] = true;
}, false);
addEventListener("keyup", function (e) {
delete keysDown[e.keyCode];
}, false);
var update = function(modifier) {
if (37 in keysDown) { // Player holding left
rocket.angle -= rocket.rotSpeed* modifier;
rocket.rotChange = - rocket.rotSpeed* modifier;
//console.log("left");
}
if (39 in keysDown) { // Player holding right
rocket.angle += rocket.rotSpeed* modifier;
rocket.rotChange = rocket.rotSpeed* modifier;
//console.log("right");
}
};
var render = function (modifier) {
context.clearRect(-width*10,-height*10,width*20,height*20);
var dX = (rocket.speed*modifier)*Math.sin(rocket.angle);
var dY = (rocket.speed*modifier)*Math.cos(rocket.angle);
rocket.xLoc += dX;
rocket.yLoc -= dY;
updateStars();
drawStars();
context.translate(-dX,dY);
context.save();
context.translate(-rocket.pointX,-rocket.pointY);
context.translate(rocket.pointX,rocket.pointY);
drawRocket(rocket.xLoc,rocket.yLoc,50,200);
context.fillStyle = "red";
context.fillRect(rocket.pointX,rocket.pointY,15,5);
//context.restore(); // restores the coordinate system back to (0,0)
context.fillStyle = "green";
context.fillRect(0,0,10,10);
context.rotate(rocket.angle);
context.restore();
};
function drawStar(x, y, r, p, m)
{
context.save();
context.beginPath();
context.translate(x, y);
context.moveTo(0,0-r);
for (var i = 0; i < p; i++)
{
context.rotate(Math.PI / p);
context.lineTo(0, 0 - (r*m));
context.rotate(Math.PI / p);
context.lineTo(0, 0 - r);
}
context.fill();
context.restore();
}
// the game loop
function main(){
requestAnimationFrame(main);
var now = Date.now();
var delta = now - then;
update(delta / 1000);
//now = Date.now();
//delta = now - then;
render(delta / 1000);
then = now;
// Request to do this again ASAP
}
var w = window;
var requestAnimationFrame = w.requestAnimationFrame || w.webkitRequestAnimationFrame || w.msRequestAnimationFrame || w.mozRequestAnimationFrame;
//start the game loop
//gameLoop();
//event listenters
bgImage.src = "images/background.jpg";
} //canvasApp()
Origin
When you need to rotate something in canvas it will always rotate around origin, or center for the grid if you like where the x and y axis crosses.
You may find my answer here useful as well
By default the origin is in the top left corner at (0, 0) in the bitmap.
So in order to rotate content around a (x,y) point the origin must first be translated to that point, then rotated and finally (and usually) translated back. Now things can be drawn in the normal order and they will all be drawn rotated relative to that rotation point:
ctx.translate(rotateCenterX, rotateCenterY);
ctx.rotate(angleInRadians);
ctx.translate(-rotateCenterX, -rotateCenterY);
Absolute angles and positions
Sometimes it's easier to keep track if an absolute angle is used rather than using an angle that you accumulate over time.
translate(), transform(), rotate() etc. are accumulative methods; they add to the previous transform. We can set absolute transforms using setTransform() (the last two arguments are for translation):
ctx.setTransform(1, 0, 0, 1, rotateCenterX, rotateCenterY); // absolute
ctx.rotate(absoluteAngleInRadians);
ctx.translate(-rotateCenterX, -rotateCenterY);
The rotateCenterX/Y will represent the position of the ship which is drawn untransformed. Also here absolute transforms can be a better choice as you can do the rotation using absolute angles, draw background, reset transformations and then draw in the ship at rotateCenterX/Y:
ctx.setTransform(1, 0, 0, 1, rotateCenterX, rotateCenterY);
ctx.rotate(absoluteAngleInRadians);
ctx.translate(-rotateCenterX, -rotateCenterY);
// update scene/background etc.
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transforms
ctx.drawImage(ship, rotateCenterX, rotateCenterY);
(Depending on orders of things you could replace the first line here with just translate() as the transforms are reset later, see demo for example).
This allows you to move the ship around without worrying about current transforms, when a rotation is needed use the ship's current position as center for translation and rotation.
And a final note: the angle you would use for rotation would of course be the counter-angle that should be represented (ie. ctx.rotate(-angle);).
Space demo ("random" movements and rotations)
The red "meteors" are dropping in one direction (from top), but as the ship "navigates" around they will change direction relative to our top view angle. Camera will be fixed on the ship's position.
(ignore the messy part - it's just for the demo setup, and I hate scrollbars... focus on the center part :) )
var img = new Image();
img.onload = function() {
var ctx = document.querySelector("canvas").getContext("2d"),
w = 600, h = 400, meteors = [], count = 35, i = 0, x = w * 0.5, y, a = 0, a2 = 0;
ctx.canvas.width = w; ctx.canvas.height = h; ctx.fillStyle = "#555";
while(i++ < count) meteors.push(new Meteor());
(function loop() {
ctx.clearRect(0, 0, w, h);
y = h * 0.5 + 30 + Math.sin((a+=0.01) % Math.PI*2) * 60; // ship's y and origin's y
// translate to center of ship, rotate, translate back, render bg, reset, draw ship
ctx.translate(x, y); // translate to origin
ctx.rotate(Math.sin((a2+=0.005) % Math.PI) - Math.PI*0.25); // rotate some angle
ctx.translate(-x, -y); // translate back
ctx.beginPath(); // render some moving meteors for the demo
for(var i = 0; i < count; i++) meteors[i].update(ctx); ctx.fill();
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transforms
ctx.drawImage(img, x - 32, y); // draw ship as normal
requestAnimationFrame(loop); // loop animation
})();
};
function Meteor() { // just some moving object..
var size = 5 + 35 * Math.random(), x = Math.random() * 600, y = -200;
this.update = function(ctx) {
ctx.moveTo(x + size, y); ctx.arc(x, y, size, 0, 6.28);
y += size * 0.5; if (y > 600) y = -200;
};
}
img.src = "http://i.imgur.com/67KQykW.png?1";
body {background:#333} canvas {background:#000}
<canvas></canvas>
So right now I have a simple canvas element with functions that create random colors, sizes, and positions of arcs (circles).
The 'for' loop that generates the random positions of these random circles executes 1 circle every 100 milliseconds (This is done onclick).
I want to know how I can make each circle slowly come near the cursor, and then follow the cursor around wherever it moves.
http://jsfiddle.net/JXXgx/
You may try something like this:
var MAXIMUM_AMOUNT = 1000,
FPS = 30,
targetToGo, //
shapes = []; //storage of circles
//Helper class
function CircleModel(x,y,r,color){
this.x = x;
this.y = y;
this.r = r;
this.color = color;
}
function initScene(){
//Listening for mouse position changes
$('canvas').mousemove(function(e){
targetToGo.x = e.pageX;
targetToGo.y = e.pageY;
});
//Circle generation timer
var intervalID = setInterval(function(){
if( shapes.length < MAXIMUM_AMOUNT ){
for(var i = 0; i < 1; i++){
//Generating random parameters for circle
var randX = targetToGo.x - 500 + Math.floor(Math.random() * 1000); //position x
var randY = targetToGo.y - 300 + Math.floor(Math.random() * 600); //position y
var randRadius = Math.floor(Math.random() * 12); //radius
var randColor = "#"+("000000"+(0xFFFFFF*Math.random()).toString(16)).substr(-6); //color
//Adding circle to scene
shapes.push( new CircleModel(randX,randY,randRadius,randColor) );
}
}else{
clearInterval(intervalID);
}
}, 100);
//Starts rendering timer -
// '1000' represents 1 second,as FPS represents seconds,not miliseconds
setInterval(render,1000/FPS);
}
function render(){
var ctx = $('canvas')[0].getContext("2d");
var circle;
//Clearing the scene
ctx.clearRect(0,0,$('canvas').width(),$('canvas').height());
//Drawing circles
for(var i=0; i < shapes.length;++i){
circle = shapes[i];
//(animation part)
//repositioning circle --
// (1/circle.r) is a degree of inertion,and the bigger radius,the slower it moves
circle.x += (targetToGo.x - circle.x)*1/circle.r;
circle.y += (targetToGo.y - circle.y)*1/circle.r;
////////////////////////////////////////////
ctx.fillStyle = circle.color;
ctx.beginPath();
ctx.arc(circle.x, circle.y, circle.r, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fill();
}
}
$("canvas").click(function(e){
targetToGo = {x: e.pageX, y:e.pageY};
initScene();
});
Put this code inside of $(document).ready handler.
Demo