I am trying to move the whole car object with its different body elements and their attributes, but all of the elements are moving in different speeds instead of the whole car moving in one speed.
Any help with this would be great.
I am also using a random number value for the speed.
Here is the code:
class Car {
constructor(p) {
//moving everything
this.direction = p.direction || 1;
//wheel constructor
this.x = p.x || 130;
this.y = p.y || 160;
this.wheelSize = p.wheelSize || 15;
this.WHcolour = p.WHcolour || "black";
//body constructor
this.carW = p.carW || 130;
this.carH = p.carH || 50;
this.Ccolour = p.Ccolour || "red";
//window constructor
this.windowW = p.windowW || 25;
this.windowH = p.windowH || 15;
this.Wcolour = p.Wcolour || "blue";
//car number
this.carNum = p.carNum || 1;
}
draw() {
//draw wheel 1
cxt.beginPath();
cxt.arc(this.x, this.y, this.wheelSize, 0, 2 * Math.PI);
cxt.fillStyle = this.WHcolour;
cxt.fill();
//draw wheel 2
cxt.beginPath();
cxt.arc(this.x * 1.8, this.y, this.wheelSize, 0, 2 * Math.PI);
cxt.fillStyle = this.WHcolour;
cxt.fill();
//draw body
cxt.beginPath();
cxt.rect(this.x * 0.9, this.y * 0.65, this.carW, this.carH);
cxt.fillStyle = this.Ccolour
cxt.fill();
//draw window1
cxt.beginPath();
cxt.rect(this.x * 1.615, this.y * 0.69, this.windowW, this.windowH);
cxt.fillStyle = this.Wcolour;
cxt.fill();
//draw window 2
cxt.beginPath();
cxt.rect(this.x * 1.0461, this.y * 0.69, this.windowW * 2.4, this.windowH);
cxt.fillStyle = this.Wcolour;
cxt.fill();
}
move() {
//velocity increasment
//wheels
this.x += this.direction;
//body
}
}
function createCar() {
while (cars.length < 1) {
let c = new Car({
//wheels
WHcolour: "black",
x: 130, y: 160, wheelSize: 15,
//body
carW: 130, carH: 50, Ccolour: "yellow",
//windows
windowW: 25, windowH: 15, Wcolour: "blue",
//car number
carNum: 1,
});
cars.push(c)
}
}
You can not multiply on the same axis of your movement, you can make the part relative to each other by adding an offset, as you build more complex object multiplication can be used for something that you will launch from the car, like a rocket that will accelerate relative to the position of the car.
Here is your code working
class Car {
constructor(p) {
//moving everything
this.direction = p.direction || 1;
//wheel constructor
this.x = p.x || 130;
this.y = p.y || 160;
this.wheelSize = p.wheelSize || 15;
this.WHcolour = p.WHcolour || "black";
//body constructor
this.carW = p.carW || 130;
this.carH = p.carH || 50;
this.Ccolour = p.Ccolour || "red";
//window constructor
this.windowW = p.windowW || 25;
this.windowH = p.windowH || 15;
this.Wcolour = p.Wcolour || "blue";
//car number
this.carNum = p.carNum || 1;
}
draw() {
//draw wheel 1
cxt.beginPath();
cxt.arc(this.x, this.y, this.wheelSize, 0, 2 * Math.PI);
cxt.fillStyle = this.WHcolour;
cxt.fill();
//draw wheel 2
cxt.beginPath();
cxt.arc(this.x + 90, this.y, this.wheelSize, 0, 2 * Math.PI);
cxt.fillStyle = this.WHcolour;
cxt.fill();
//draw body
cxt.beginPath();
cxt.rect(this.x - 20 , this.y * 0.65, this.carW, this.carH);
cxt.fillStyle = this.Ccolour
cxt.fill();
//draw window1
cxt.beginPath();
cxt.rect(this.x + 70, this.y * 0.69, this.windowW, this.windowH);
cxt.fillStyle = this.Wcolour;
cxt.fill();
//draw window 2
cxt.beginPath();
cxt.rect(this.x - 10, this.y * 0.69, this.windowW * 2.4, this.windowH);
cxt.fillStyle = this.Wcolour;
cxt.fill();
}
move() {
//velocity increasment
//wheels
this.x += this.direction;
//body
}
}
function createCar() {
while (cars.length < 1) {
let c = new Car({
//wheels
WHcolour: "black",
x: 130, y: 160, wheelSize: 15,
//body
carW: 130, carH: 50, Ccolour: "yellow",
//windows
windowW: 25, windowH: 15, Wcolour: "blue",
//car number
carNum: 1,
});
cars.push(c)
}
}
function move() {
cxt.clearRect(0,0, 800,800)
for (i = 0; i < cars.length; i++) {
cars[i].draw()
cars[i].move()
}
}
let canvas = document.createElement("canvas");
canvas.width = canvas.height = 800;
let cxt = canvas.getContext('2d');
document.body.appendChild(canvas);
var cars = []
createCar()
setInterval(move, 100);
Related
When the program runs the mouse is clicked creating a projectile from the center of the screen moving with every frame in the direction it was fired (mouse position on click).
When N+1 projectiles have fired all projectiles on-screen move to the new clicked location instead of continuing their path.
I am can not figure out why the current projectiles change direction when the New projectile's velocity should have no effect on prior projectiles.
index.html
<canvas></canvas>
<script src="./guns.js"></script>
<script src="./indexh.js"></script>
<script src="./runh.js"></script>
runh.html
const projectilesArray = [];
let frameCount = 0;
function animate() {
animationID = requestAnimationFrame(animate);
c.fillStyle = "rgba(0, 0, 0, 1)";
c.fillRect(0, 0, canvas.width, canvas.height);
projectilesArray.forEach((Projectile, pIndex) => {
Projectile.update();
console.log(Projectile)
if (
Projectile.x + Projectile.radius < 0 ||
Projectile.x - Projectile.radius > canvas.width ||
Projectile.y + Projectile.radius < 0 ||
Projectile.y - Projectile.radius > canvas.height
) {
setTimeout(() => {
projectilesArray.splice(pIndex, 1);
}, 0);
}
});
frameCount++;
if (frameCount > 150) {
}
}
var fire = 1;
let fireRate = 1;
const mouse = {
x: 0,
y: 0,
click: true,
};
canvas.addEventListener('mousedown', (event) => {
if (fire % fireRate == 0) {
if (mouse.click == true) {
mouse.x = event.x;
mouse.y = event.y;
const angle = Math.atan2(mouse.y - (canvas.height / 2), mouse.x - (canvas.width / 2));
const fireY = Math.sin(angle);
const fireX = Math.cos(angle);
//score -= 0;
//scoreL.innerHTML = score;
var weapon = new Projectile(cannon);
weapon.velocity.x = fireX * 9;
weapon.velocity.y = fireY * 9;
projectilesArray.push(weapon);
//var gun = object.constructor()
}
}
});
animate();
indexh.js
const canvas = document.querySelector("canvas");
const c = canvas.getContext("2d");
canvas.width = innerWidth;
canvas.height = innerHeight;
class Projectile {
constructor(config) {
this.color = config.color || "rgb(60, 179, 113)";
this.radius = config.radius || 1;
this.speed = config.speed || 5;
this.rounds = config.rounds || 2;
this.x = config.x || canvas.width / 2;
this.y = config.y || canvas.height /2;
this.velocity = config.velocity;
}
draw() {
c.beginPath();
c.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
c.fillStyle = this.color;
c.fill();
}
update() {
this.draw();
this.x = this.x + this.velocity.x * this.speed;
this.y = this.y + this.velocity.y * this.speed;
}
}
gums.js
let pistol = {
color : "rgb(255, 0, 0)",
radius : 10,
speed : 1,
rounds : 1,
velocity : {
x: 1,
y: 1
}
}
let cannon = {
color : "rgb(0, 0, 255)",
radius : 30,
speed : .5,
rounds : 1,
velocity : {
x: 1,
y: 1
}
}
Thanks
The issue is that you have this cannon object used as a configuration object:
let cannon = {
color : "rgb(0, 0, 255)",
radius : 30,
speed : .5,
rounds : 1,
velocity : {
x: 1,
y: 1
}
}
And in your Projectile constructor you assign this.velocity = config.velocity; You are assigning this.velocity to be the same object instance for all Projectiles. To fix it, try copying the object, like:
this.velocity = {...config.velocity};
That will make a copy of the object rather than sharing the same object instance.
Also note that you have a bug that you didn't ask about in this line:
projectilesArray.splice(pIndex, 1);
If two timeouts are queued in the same loop, the array will shift when the first timeout fires, and the second timeout will be operating on the wrong index.
The if statement in question is:
if (
this.y + this.radius == lineInfo.loneSy &&
this.x + this.radius > lineInfo.loneSx &&
this.x + this.radius < lineInfo.loneEx
)
{
this.dy = -this.dy; //linebounce
}
I know for sure that the first two conditions work properly because if I omit the third conditional of: this.x + this.radius < lineInfo.loneEx, the ball bounces as expected, however when I add the third conditional in an attempt to constrict its bounce to only within the length of the line; the ball doesn't bounce at all - even though it should.
The entire code for reference is:
let canvas = document.getElementById('myCanvas');
let c = canvas.getContext('2d'); //c = super object (methods / functions) 2d
const sceneObjects = [];
let lineReact = {
jitter: false,
hitOne: false,
hitTwo: false,
hitThree: false,
hitFour: false
}
let lineInfo = {
loneSx: 0,
loneSy: 0,
loneEx: 0,
loneEy: 0
}
window.addEventListener('load',
function(e){
lineReact.jitter == true ? (lineReact.jitter = false) : (lineReact.jitter = true);
if (lineReact.jitter == true){
setTimeout(function(){
lineReact.jitter = false;
console.log("Time lapse " + lineReact.jitter);
}, 300);
}
console.log(lineReact.jitter);
}
)
function Square(x, y, width, height, color){
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.color = color;
this.draw = function(){
c.fillStyle = this.color;
c.fillRect(this.x, this.y, this.width, this.height);
}
this.update = function(){
this.draw();
}
}
function Line(startX, startY, endX, endY, color, lNumber){
this.startX = startX;
this.startY = startY;
this.endX = endX;
this.endY = endY;
this.color = color;
this.lockSx = this.startX;
this.lockSy = this.startY;
this.lockEx = this.endX;
this.lockEy = this.endY;
this.draw = function(){
c.beginPath();
c.strokeStyle = this.color;
c.moveTo(this.startX, this.startY);
c.lineTo(this.endX, this.endY);
c.stroke();
}
this.update = function(){
if (lineReact.jitter == false){
this.color = "white";
this.startX = this.lockSx;
this.startY = this.lockSy;
this.endX = this.lockEx;
this.endY = this.lockEy;
} else {
if(lNumber == 1){
lineInfo.loneSx = this.lockSx;
lineInfo.loneSy = this.lockSy;
lineInfo.loneEx = this.lockEx;
lineInfo.loneEy = this.lockEy;
this.startX -= Math.floor(Math.random() * 2);
this.startY += Math.floor(Math.random() * 2);
this.endX += Math.floor(Math.random() * 2);
this.endY += Math.floor(Math.random() * 2);
this.color = "red";
}
if(lNumber == 2){
this.startX -= Math.floor(Math.random() * 2);
this.startY -= Math.floor(Math.random() * 2);
this.endX -= Math.floor(Math.random() * 2);
this.endY += Math.floor(Math.random() * 2);
this.color = "red";
}
if(lNumber == 3){
this.startX -= Math.floor(Math.random() * 2);
this.startY -= Math.floor(Math.random() * 2);
this.endX += Math.floor(Math.random() * 2);
this.endY -= Math.floor(Math.random() * 2);
this.color = "red";
}
if(lNumber == 4){
this.startX += Math.floor(Math.random() * 2);
this.startY -= Math.floor(Math.random() * 2);
this.endX += Math.floor(Math.random() * 2);
this.endY += Math.floor(Math.random() * 2);
this.color = "red";
}
}
this.draw();
//console.log('test read ' + lineInfo.loneSx);
}
}
function Circle(x, y, dx, dy, radius){
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
this.radius = radius;
this.draw = function(){
c.beginPath();
c.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
c.strokeStyle = 'black';
c.fillStyle = 'black';
c.fill();
c.stroke();
}
this.update = function(){
if (this.x + this.radius > canvas.width || this.x - this.radius < 0){
this.dx = -this.dx; // wallbounce
}
if (this.y + this.radius > canvas.height || this.y - this.radius < 0){
this.dy = -this.dy; // wallbounce
}
if (
this.y + this.radius == lineInfo.loneSy &&
this.x + this.radius > lineInfo.loneSx &&
this.x + this.radius < lineInfo.loneEx
)
{
this.dy = -this.dy; //linebounce
}
this.x += this.dx; //velocity
this.y += this.dy; //velocity
this.draw();
}
}
let square = new Square(280, 280, 40, 40, "pink");
let circle = new Circle(245, 195, 1, 1, 6);
let lineOne = new Line(280, 270, 320, 270, "white", 1);
let lineTwo = new Line(330, 280, 330, 320, "white", 2);
let lineThree = new Line(280, 330, 320, 330, "white", 3);
let lineFour = new Line(270, 280, 270, 320, "white", 4);
function animate() {
requestAnimationFrame(animate);
c.clearRect(0, 0, canvas.width, canvas.height);
square.update();
lineOne.update();
lineTwo.update();
lineThree.update();
lineFour.update();
circle.update();
}
animate();
body {
background-color: black;
margin: 0;
}
#myCanvas {
position: absolute;
top: 50%;
left: 50%;
background-color: #385D72;
margin: -300px 0 0 -300px;
}
<canvas id="myCanvas" width="600" height="600">
You need to use || instead of &&:
if (
this.y + this.radius == lineInfo.loneSy &&
(this.x + this.radius < lineInfo.loneSx || this.x + this.radius > lineInfo.loneEx)
)
{
this.dy = -this.dy; //linebounce
}
With && you're testing if both conditions are true, but it's impossible for both to be true at the same time (the ball can't be both to the left of the line and to the right of the line).
I recently made a JS Pong game. It works well, but the ball rarely gets stuck at the bottom or top. It looks like it is halfway through the wall and constantly bouncing. Video of the issue happening. You can try the game here. I do not know why this issue is happening because the logic seems right and works 90% of the time correctly. Here are the main two functions of my program:
function moveAll() {
if (showingWinScreen) {
return;
}
computerMovement();
ballX += ballSpeedX;
ballY += ballSpeedY;
if (ballY <= 10) {
ballSpeedY = -ballSpeedY;
} else if (ballY >= HEIGHT - 10) {
ballSpeedY = -ballSpeedY;
}
if (ballX >= WIDTH - 10) {
if ((ballY > paddleY) && (ballY < paddleY + 100)) {
ballSpeedX = -ballSpeedX;
var deltaY = ballY - paddleY - 50;
ballSpeedY = deltaY / 5;
} else {
player1Score++;
ballReset();
}
} else if (ballX <= 10) {
if ((ballY > mouseY - 50) && (ballY < mouseY + 50)) {
ballSpeedX = -ballSpeedX;
deltaY = ballY - mouseY;
ballSpeedY = deltaY / 6;
} else {
player2Score++;
ballReset();
}
}
}
function drawAll() {
if (showingWinScreen) {
colorRect(0, 0, WIDTH, HEIGHT, "black");
canvas.fillStyle = "yellow";
canvas.fillText("Click to continue!", 300, 300);
if (player1Score == WINNING_SCORE) {
canvas.fillText("You won!", 360, 500);
} else if (player2Score == WINNING_SCORE) {
canvas.fillText("The computer beat you!", 280, 500);
}
return;
}
colorRect(0, 0, WIDTH, HEIGHT, "black");
drawNet();
makeCircle(ballX, ballY, 10, 0, Math.PI * 2, "red");
colorRect(790, paddleY, 10, 100, "cyan");
colorRect(0, mouseY - 50, 10, 100, "yellow");
canvas.fillStyle = "white";
canvas.fillText(player1Score + " " + player2Score, 360, 100);
}
Thank you for your help!
I think there's only one case in which this could happen: when, in a colliding frame, you decrease the speed.
When the speed remains the same, no matter what, your ball will always bounce back to the previous' frames position:
var cvs = document.querySelector("canvas");
var ctx = cvs.getContext("2d");
var balls = [
Ball(50, 50, 0, 5, 5, "red"),
Ball(100, 50, 0, 5, 10, "blue"),
Ball(150, 50, 0, 5, 15, "green"),
Ball(200, 50, 0, 5, 20, "yellow")
];
var next = () => {
updateFrame(balls);
drawFrame(balls);
}
var loop = () => {
requestAnimationFrame(() => {
next();
loop();
});
}
next();
function Ball(x, y, vx, vy, r, color) {
return {
x: x,
y: y,
vx: vx,
vy: vy,
r: r,
color: color
}
};
function updateBall(b) {
b.x += b.vx;
b.y += b.vy;
if (b.y <= b.r ||
b.y >= cvs.height - b.r) {
b.vy *= -1;
}
};
function drawBall(b) {
ctx.beginPath();
ctx.fillStyle = b.color;
ctx.arc(b.x, b.y, b.r, 0, 2 * Math.PI, false);
ctx.fill();
}
function updateFrame(balls) {
balls.forEach(updateBall);
}
function drawFrame(balls) {
ctx.clearRect(0, 0, cvs.width, cvs.height);
balls.forEach(drawBall);
};
<canvas width="300" height="150" style="background: #454545"></canvas>
<button onclick="next()">next</button>
<button onclick="loop()">run</button>
But when the speed changes, things get stuck:
var cvs = document.querySelector("canvas");
var ctx = cvs.getContext("2d");
var balls = [
Ball(50, 50, 0, 10, 5, "red"),
Ball(100, 50, 0, 10, 10, "blue"),
Ball(150, 50, 0, 10, 15, "green"),
Ball(200, 50, 0, 10, 20, "yellow")
];
var next = () => {
updateFrame(balls);
drawFrame(balls);
}
var loop = () => {
requestAnimationFrame(() => {
next();
loop();
});
}
next();
function Ball(x, y, vx, vy, r, color) {
return {
x: x,
y: y,
vx: vx,
vy: vy,
r: r,
color: color
}
};
function updateBall(b) {
b.x += b.vx;
b.y += b.vy;
if (b.y <= b.r ||
b.y >= cvs.height - b.r) {
b.vy *= -0.5;
}
};
function drawBall(b) {
ctx.beginPath();
ctx.fillStyle = b.color;
ctx.arc(b.x, b.y, b.r, 0, 2 * Math.PI, false);
ctx.fill();
}
function updateFrame(balls) {
balls.forEach(updateBall);
}
function drawFrame(balls) {
ctx.clearRect(0, 0, cvs.width, cvs.height);
balls.forEach(drawBall);
};
<canvas width="300" height="150" style="background: #454545"></canvas>
<button onclick="next()">next</button>
<button onclick="loop()">run</button>
In your case, I'm thinking this can only happen when there's a paddle collision AND a wall collision simultaneously.
A quick-to-implement solution would be to check if the new position is valid before translating the ball position. If you don't want the precise location, you can place the ball at the point of collision. Note that this will produce a slightly off frame.
E.g.:
var newY = ballY + ballSpeedY;
// Top wall
if(newY <= 10) {
ballY = 10;
ballSpeedY = -ballSpeedY;
}
// Bottom wall
else if(newY >= HEIGHT-10){
ballY = HEIGHT - 10;
ballSpeedY = -ballSpeedY;
}
// No collision
else {
ballY = newY;
}
Update: a more detailed description of what can happen
Let's say your ball collides with the top border of your canvas and with your paddle in the same frame.
First, you move the ball to the colliding position: ballY += ballSpeedY; Say your ballY is 4, and your ballSpeedY is -5, you'll position the ball to -1, inside the wall.
If this were to be the only collision, you should be okay. You flip the speed (ballSpeedY = -ballSpeedY), so in the next frame, your ball should be back at -1 + 5 = 4, so ballY will be 4 again, and your ball will move towards 4 + 5 = 9 in the next frame.
Now a problem arises, when in the -1 positioned frame, you collide with the paddle as well! When the paddle hits the ball, you modify the ballspeed: ballSpeedY = deltaY / 5;. If this turns out to be < 1, your ball won't be able to exit the wall in the next frame. Instead of -1 + 5 = 4, your ball will, for example, move to: -1 + 0.5 = -0.5.
Now, your ball won't be able to get back in to play, since the next frame will, again, calculate a collision and flip the speed. This results in the bouncy, trembling effect you see when the ball gets stuck.
A naive but pretty decent solution, is to only update the position of the ball to a valid position. I.e.: never to a colliding coordinate.
var animate = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function (callback) {
window.setTimeout(callback, 1000 / 60)
};
var canvas = document.createElement("canvas");
var width = 400;
var height = 600;
canvas.width = width;
canvas.height = height;
var context = canvas.getContext('2d');
var player = new Player();
var computer = new Computer();
var ball = new Ball(200, 300);
var keysDown = {};
var render = function () {
context.fillStyle = "#FF00FF";
context.fillRect(0, 0, width, height);
player.render();
computer.render();
ball.render();
};
var update = function () {
player.update();
computer.update(ball);
ball.update(player.paddle, computer.paddle);
};
var step = function () {
update();
render();
animate(step);
};
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;
}
Paddle.prototype.render = function () {
context.fillStyle = "#0000FF";
context.fillRect(this.x, this.y, this.width, this.height);
};
Paddle.prototype.move = function (x, y) {
this.x += x;
this.y += y;
this.x_speed = x;
this.y_speed = y;
if (this.x < 0) {
this.x = 0;
this.x_speed = 0;
} else if (this.x + this.width > 400) {
this.x = 400 - this.width;
this.x_speed = 0;
}
};
function Computer() {
this.paddle = new Paddle(175, 10, 50, 10);
}
Computer.prototype.render = function () {
this.paddle.render();
};
Computer.prototype.update = function (ball) {
var x_pos = ball.x;
var diff = -((this.paddle.x + (this.paddle.width / 2)) - x_pos);
if (diff < 0 && diff < -4) {
diff = -5;
} else if (diff > 0 && diff > 4) {
diff = 5;
}
this.paddle.move(diff, 0);
if (this.paddle.x < 0) {
this.paddle.x = 0;
} else if (this.paddle.x + this.paddle.width > 400) {
this.paddle.x = 400 - this.paddle.width;
}
};
function Player() {
this.paddle = new Paddle(175, 580, 50, 10);
}
Player.prototype.render = function () {
this.paddle.render();
};
Player.prototype.update = function () {
for (var key in keysDown) {
var value = Number(key);
if (value == 37) {
this.paddle.move(-4, 0);
} else if (value == 39) {
this.paddle.move(4, 0);
} else {
this.paddle.move(0, 0);
}
}
};
function Ball(x, y) {
this.x = x;
this.y = y;
this.x_speed = 0;
this.y_speed = 3;
}
Ball.prototype.render = function () {
context.beginPath();
context.arc(this.x, this.y, 5, 2 * Math.PI, false);
context.fillStyle = "#000000";
context.fill();
};
Ball.prototype.update = function (paddle1, paddle2) {
this.x += this.x_speed;
this.y += this.y_speed;
var top_x = this.x - 5;
var top_y = this.y - 5;
var bottom_x = this.x + 5;
var bottom_y = this.y + 5;
if (this.x - 5 < 0) {
this.x = 5;
this.x_speed = -this.x_speed;
} else if (this.x + 5 > 400) {
this.x = 395;
this.x_speed = -this.x_speed;
}
if (this.y < 0 || this.y > 600) {
this.x_speed = 0;
this.y_speed = 3;
this.x = 200;
this.y = 300;
}
if (top_y > 300) {
if (top_y < (paddle1.y + paddle1.height) && bottom_y > paddle1.y && top_x < (paddle1.x + paddle1.width) && bottom_x > paddle1.x) {
this.y_speed = -3;
this.x_speed += (paddle1.x_speed / 2);
this.y += this.y_speed;
}
} else {
if (top_y < (paddle2.y + paddle2.height) && bottom_y > paddle2.y && top_x < (paddle2.x + paddle2.width) && bottom_x > paddle2.x) {
this.y_speed = 3;
this.x_speed += (paddle2.x_speed / 2);
this.y += this.y_speed;
}
}
};
document.body.appendChild(canvas);
animate(step);
window.addEventListener("keydown", function (event) {
keysDown[event.keyCode] = true;
});
window.addEventListener("keyup", function (event) {
delete keysDown[event.keyCode];
});
http://jsfiddle.net/kHJr6/2/
I'm a canvas beginner, sorry if this is a trivial question. How can I make the fireworks in my work fade out once they've exploded?
https://jsfiddle.net/ccwhryvv/
var SCREEN_WIDTH = window.innerWidth,
SCREEN_HEIGHT = window.innerHeight,
mousePos = {
x: 400,
y: 300
},
// create canvas
canvas = document.createElement('canvas'),
context = canvas.getContext('2d'),
particles = [],
rockets = [],
MAX_PARTICLES = 400,
colorCode = 0;
// init
$(document).ready(function() {
$('#content')[0].appendChild(canvas);
canvas.width = SCREEN_WIDTH;
canvas.height = SCREEN_HEIGHT;
setInterval(launch, 800);
setInterval(loop, 1000 / 50);
});
// update mouse position
$(document).mousemove(function(e) {
e.preventDefault();
mousePos = {
x: e.clientX,
y: e.clientY
};
});
// launch more rockets!!!
$(document).mousedown(function(e) {
for (var i = 0; i < 5; i++) {
launchFrom(Math.random() * SCREEN_WIDTH * 2 / 3 + SCREEN_WIDTH / 6);
}
});
function launch() {
launchFrom(SCREEN_WIDTH / 2);
}
function launchFrom(x) {
if (rockets.length < 10) {
var rocket = new Rocket(x);
rocket.explosionColor = Math.floor(Math.random() * 360 / 10) * 10;
rocket.vel.y = Math.random() * -3 - 4;
rocket.vel.x = Math.random() * 6 - 3;
rocket.size = 8;
rocket.shrink = 0.999;
rocket.gravity = 0.01;
rockets.push(rocket);
}
}
function loop() {
// update screen size
if (SCREEN_WIDTH != window.innerWidth) {
canvas.width = SCREEN_WIDTH = window.innerWidth;
}
if (SCREEN_HEIGHT != window.innerHeight) {
canvas.height = SCREEN_HEIGHT = window.innerHeight;
}
// clear canvas
context.fillStyle = "rgba(0, 0, 0, 0.0)";
context.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
var existingRockets = [];
for (var i = 0; i < rockets.length; i++) {
// update and render
rockets[i].update();
rockets[i].render(context);
// calculate distance with Pythagoras
var distance = Math.sqrt(Math.pow(mousePos.x - rockets[i].pos.x, 2) + Math.pow(mousePos.y - rockets[i].pos.y, 2));
// random chance of 1% if rockets is above the middle
var randomChance = rockets[i].pos.y < (SCREEN_HEIGHT * 2 / 3) ? (Math.random() * 100 <= 1) : false;
/* Explosion rules
- 80% of screen
- going down
- close to the mouse
- 1% chance of random explosion
*/
if (rockets[i].pos.y < SCREEN_HEIGHT / 5 || rockets[i].vel.y >= 0 || distance < 50 || randomChance) {
rockets[i].explode();
} else {
existingRockets.push(rockets[i]);
}
}
rockets = existingRockets;
var existingParticles = [];
for (var i = 0; i < particles.length; i++) {
particles[i].update();
// render and save particles that can be rendered
if (particles[i].exists()) {
particles[i].render(context);
existingParticles.push(particles[i]);
}
}
// update array with existing particles - old particles should be garbage collected
particles = existingParticles;
while (particles.length > MAX_PARTICLES) {
particles.shift();
}
}
function Particle(pos) {
this.pos = {
x: pos ? pos.x : 0,
y: pos ? pos.y : 0
};
this.vel = {
x: 0,
y: 0
};
this.shrink = .97;
this.size = 2;
this.resistance = 1;
this.gravity = 0;
this.flick = false;
this.alpha = 1;
this.fade = 0;
this.color = 0;
}
Particle.prototype.update = function() {
// apply resistance
this.vel.x *= this.resistance;
this.vel.y *= this.resistance;
// gravity down
this.vel.y += this.gravity;
// update position based on speed
this.pos.x += this.vel.x;
this.pos.y += this.vel.y;
// shrink
this.size *= this.shrink;
// fade out
this.alpha -= this.fade;
};
Particle.prototype.render = function(c) {
if (!this.exists()) {
return;
}
c.save();
c.globalCompositeOperation = 'lighter';
var x = this.pos.x,
y = this.pos.y,
r = this.size / 2;
var gradient = c.createRadialGradient(x, y, 0.1, x, y, r);
gradient.addColorStop(0.1, "rgba(255,255,255," + this.alpha + ")");
gradient.addColorStop(0.8, "hsla(" + this.color + ", 100%, 50%, " + this.alpha + ")");
gradient.addColorStop(1, "hsla(" + this.color + ", 100%, 50%, 0.1)");
c.fillStyle = gradient;
c.beginPath();
c.arc(this.pos.x, this.pos.y, this.flick ? Math.random() * this.size : this.size, 0, Math.PI * 2, true);
c.closePath();
c.fill();
c.restore();
};
Particle.prototype.exists = function() {
return this.alpha >= 0.1 && this.size >= 1;
};
function Rocket(x) {
Particle.apply(this, [{
x: x,
y: SCREEN_HEIGHT}]);
this.explosionColor = 0;
}
Rocket.prototype = new Particle();
Rocket.prototype.constructor = Rocket;
Rocket.prototype.explode = function() {
var count = Math.random() * 10 + 80;
for (var i = 0; i < count; i++) {
var particle = new Particle(this.pos);
var angle = Math.random() * Math.PI * 2;
// emulate 3D effect by using cosine and put more particles in the middle
var speed = Math.cos(Math.random() * Math.PI / 2) * 15;
particle.vel.x = Math.cos(angle) * speed;
particle.vel.y = Math.sin(angle) * speed;
particle.size = 10;
particle.gravity = 0.2;
particle.resistance = 0.92;
particle.shrink = Math.random() * 0.05 + 0.93;
particle.flick = true;
particle.color = this.explosionColor;
particles.push(particle);
}
};
Rocket.prototype.render = function(c) {
if (!this.exists()) {
return;
}
c.save();
c.globalCompositeOperation = 'lighter';
var x = this.pos.x,
y = this.pos.y,
r = this.size / 2;
var gradient = c.createRadialGradient(x, y, 0.1, x, y, r);
gradient.addColorStop(0.1, "rgba(255, 255, 255 ," + this.alpha + ")");
// gradient.addColorStop(1, "rgba(255, 255, 255, " + this.alpha + ")");
gradient.addColorStop(1, "rgba(255, 255, 255, 0)");
c.fillStyle = gradient;
c.beginPath();
c.arc(this.pos.x, this.pos.y, this.flick ? Math.random() * this.size / 2 + this.size / 2 : this.size, 0, Math.PI * 2, true);
c.closePath();
c.fill();
c.restore();
};
Thank you!
Creating gradients is expensive -- especially inside an animation loop.
It's more efficient is to pre-create a spritesheet of gradient exploding sprites before your app starts:
Create an in-memory canvas to act as a spritesheet,
Choose a dozen standard colors for you explosions.
Create gradient sprites in sequential order of exploding.
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var ss=makeSpritesheet(10,15);
ctx.fillStyle='navy';
ctx.fillRect(0,0,cw,ch);
ctx.drawImage(ss,0,0);
function makeSpritesheet(maxRadius,colorCount){
var c=document.createElement('canvas');
var ctx=c.getContext('2d');
var spacing=maxRadius*2.5;
c.width=spacing*maxRadius;
c.height=spacing*(colorCount+1);
for(var colors=0;colors<colorCount;colors++){
var y=(colors)*spacing+spacing/2;
var color = parseInt(colors/colorCount*360);
for(r=2;r<=maxRadius;r++){
var x=(r-1)*spacing;
var gradient = ctx.createRadialGradient(x, y, 0, x, y, r);
gradient.addColorStop(0.2, "white");
gradient.addColorStop(0.7, 'hsla('+color+', 100%, 50%, 1)');
gradient.addColorStop(1.0, "rgba(0,0,0,0)");
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc(x,y,r,0,Math.PI*2);
ctx.closePath();
ctx.fill();
}
}
return(c);
}
body{ background-color:white; }
#canvas{border:1px solid red; }
<canvas id="canvas" width=640 height=512></canvas>
During Animation, draw the sprites from the spritesheet to your canvas.
Fade the opacity of each sprite by setting context.globalAlpha before drawing each sprite.
I'm trying to find a way to put as much hexagons in a circle as possible. So far the best result I have obtained is by generating hexagons from the center outward in a circular shape.
But I think my calculation to get the maximum hexagon circles is wrong, especially the part where I use the Math.ceil() and Math.Floor functions to round down/up some values.
When using Math.ceil(), hexagons are sometimes overlapping the circle.
When using Math.floor() on the other hand , it sometimes leaves too much space between the last circle of hexagons and the circle's border.
var c_el = document.getElementById("myCanvas");
var ctx = c_el.getContext("2d");
var canvas_width = c_el.clientWidth;
var canvas_height = c_el.clientHeight;
var PI=Math.PI;
var PI2=PI*2;
var hexCircle = {
r: 110, /// radius
pos: {
x: (canvas_width / 2),
y: (canvas_height / 2)
}
};
var hexagon = {
r: 20,
pos:{
x: 0,
y: 0
},
space: 1
};
drawHexCircle( hexCircle, hexagon );
function drawHexCircle(hc, hex ) {
drawCircle(hc);
var hcr = Math.ceil( Math.sqrt(3) * (hc.r / 2) );
var hr = Math.ceil( ( Math.sqrt(3) * (hex.r / 2) ) ) + hexagon.space; // hexRadius
var circles = Math.ceil( ( hcr / hr ) / 2 );
drawHex( hc.pos.x , hc.pos.y, hex.r ); //center hex ///
for (var i = 1; i<=circles; i++) {
for (var j = 0; j<6; j++) {
var currentX = hc.pos.x+Math.cos(j*PI2/6)*hr*2*i;
var currentY = hc.pos.y+Math.sin(j*PI2/6)*hr*2*i;
drawHex( currentX,currentY, hex.r );
for (var k = 1; k<i; k++) {
var newX = currentX + Math.cos((j*PI2/6+PI2/3))*hr*2*k;
var newY = currentY + Math.sin((j*PI2/6+PI2/3))*hr*2*k;
drawHex( newX,newY, hex.r );
}
}
}
}
function drawHex(x, y, r){
ctx.beginPath();
ctx.moveTo(x,y-r);
for (var i = 0; i<=6; i++) {
ctx.lineTo(x+Math.cos((i*PI2/6-PI2/4))*r,y+Math.sin((i*PI2/6-PI2/4))*r);
}
ctx.closePath();
ctx.stroke();
}
function drawCircle( circle ){
ctx.beginPath();
ctx.arc(circle.pos.x, circle.pos.y, circle.r, 0, 2 * Math.PI);
ctx.closePath();
ctx.stroke();
}
<canvas id="myCanvas" width="350" height="350" style="border:1px solid #d3d3d3;">
If all the points on the hexagon are within the circle, the hexagon is within the circle. I don't think there's a simpler way than doing the distance calculation.
I'm not sure how to select the optimal fill point, (but here's a js snippet proving that the middle isn't always it). It's possible that when you say "hexagon circle" you mean hexagon made out of hexagons, in which case the snippet proves nothing :)
I made the hexagon sides 2/11ths the radius of the circle and spaced them by 5% the side length.
var hex = {x:0, y:0, r:10};
var circle = {x:100, y:100, r:100};
var spacing = 1.05;
var SQRT_3 = Math.sqrt(3);
var hexagon_offsets = [
{x: 1/2, y: -SQRT_3 / 2},
{x: 1, y: 0},
{x: 1/2, y: SQRT_3 / 2},
{x: -1/2, y: SQRT_3 / 2},
{x: -1, y: 0},
{x: -1/2, y: -SQRT_3 / 2}
];
var bs = document.body.style;
var ds = document.documentElement.style;
bs.height = bs.width = ds.height = ds.width = "100%";
bs.border = bs.margin = bs.padding = 0;
var c = document.createElement("canvas");
c.style.display = "block";
c.addEventListener("mousemove", follow, false);
document.body.appendChild(c);
var ctx = c.getContext("2d");
window.addEventListener("resize", redraw);
redraw();
function follow(e) {
hex.x = e.clientX;
hex.y = e.clientY;
redraw();
}
function drawCircle() {
ctx.strokeStyle = "black";
ctx.beginPath();
ctx.arc(circle.x, circle.y, circle.r, 0, 2 * Math.PI, true);
ctx.closePath();
ctx.stroke();
}
function is_in_circle(p) {
return Math.pow(p.x - circle.x, 2) + Math.pow(p.y - circle.y, 2) < Math.pow(circle.r, 2);
}
function drawLine(a, b) {
var within = is_in_circle(a) && is_in_circle(b);
ctx.strokeStyle = within ? "green": "red";
ctx.beginPath();
ctx.moveTo(a.x, a.y);
ctx.lineTo(b.x, b.y);
ctx.closePath();
ctx.stroke();
return within;
}
function drawShape(shape) {
var within = true;
for (var i = 0; i < shape.length; i++) {
within = drawLine(shape[i % shape.length], shape[(i + 1) % shape.length]) && within;
}
if (!within) return false;
ctx.fillStyle = "green";
ctx.beginPath();
ctx.moveTo(shape[0].x, shape[0].y);
for (var i = 1; i <= shape.length; i++) {
ctx.lineTo(shape[i % shape.length].x, shape[i % shape.length].y);
}
ctx.closePath();
ctx.fill();
return true;
}
function calculate_hexagon(x, y, r) {
return hexagon_offsets.map(function (offset) {
return {x: x + r * offset.x, y: y + r * offset.y};
})
}
function drawHexGrid() {
var hex_count = 0;
var grid_space = calculate_hexagon(0, 0, hex.r * spacing);
var y = hex.y;
var x = hex.x;
while (y > 0) {
y += grid_space[0].y * 3;
x += grid_space[0].x * 3;
}
while (y < c.height) {
x %= grid_space[1].x * 3;
while (x < c.width) {
var hexagon = calculate_hexagon(x, y, hex.r);
if (drawShape(hexagon)) hex_count++;
x += 3 * grid_space[1].x;
}
y += grid_space[3].y;
x += grid_space[3].x;
x += 2 * grid_space[1].x;
}
return hex_count;
}
function redraw() {
c.width = window.innerWidth;
c.height = window.innerHeight;
circle.x = c.width / 2;
circle.y = c.height / 2;
circle.r = Math.min(circle.x, circle.y) * 0.9;
hex.r = circle.r * (20 / 110);
ctx.clearRect(0, 0, c.width, c.height);
var hex_count = drawHexGrid();
drawCircle();
ctx.fillStyle = "rgb(0, 0, 50)";
ctx.font = "40px serif";
ctx.fillText(hex_count + " hexes within circle", 20, 40);
}