Changing color of Javascript class object dynamically? - javascript

I want to change the color of the balls to red when they collide. I tried using my function check() to change the color of the balls when they collide using balls[i].color but how do I know the positions of the balls to compare when they collide?
function randomXToY(minVal,maxVal,floatVal)
{
var randVal = minVal+(Math.random()*(maxVal-minVal));
return typeof floatVal=='undefined'?Math.round(randVal):randVal.toFixed(floatVal);
}
// The Ball class
Ball = (function() {
// constructor
function Ball(x,y,radius,color){
this.center = {x:x, y:y};
this.radius = radius;
this.color = color;
this.dx = 2;
this.dy = 2;
this.boundaryHeight = $('#ground').height();
this.boundaryWidth = $('#ground').width();
this.dom = $('<p class="circle"></p>').appendTo('#ground');
// the rectange div a circle
this.dom.width(radius*2);
this.dom.height(radius*2);
this.dom.css({'border-radius':radius,background:color});
this.placeAtCenter(x,y);
}
// Place the ball at center x, y
Ball.prototype.placeAtCenter = function(x,y){
this.dom.css({top: Math.round(y- this.radius), left: Math.round(x - this.radius)});
this.center.x = Math.round(x);
this.center.y = Math.round(y);
};
Ball.prototype.setColor = function(color) {
if(color) {
this.dom.css('background',color);
} else {
this.dom.css('background',this.color);
}
};
// move and bounce the ball
Ball.prototype.move = function(){
var diameter = this.radius * 2;
var radius = this.radius;
if (this.center.x - radius < 0 || this.center.x + radius > this.boundaryWidth ) {
this.dx = -this.dx;
}
if (this.center.y - radius < 0 || this.center.y + radius > this.boundaryHeight ) {
this.dy = -this.dy;
}
this.placeAtCenter(this.center.x + this.dx ,this.center.y +this.dy);
};
return Ball;
})();
var number_of_balls = 5;
var balls = [];
var x;
var y;
$('document').ready(function(){
for (i = 0; i < number_of_balls; i++) {
var boundaryHeight = $('#ground').height();
var boundaryWidth = $('#ground').width();
y = randomXToY(30,boundaryHeight - 50);
x = randomXToY(30,boundaryWidth - 50);
var radius = randomXToY(15,30);
balls.push(new Ball(x,y,radius, '#'+Math.floor(Math.random()*16777215).toString(16)));
}
loop();
check();
});
check = function(){
for (var i = 0; i < balls.length; i++){
for(var j=0;j<balls.length;j++){
if(x==y){
balls[i].color='#ff0000';
alert("y");
}
else{
}
}}
setTimeout(check,8);
};
loop = function(){
for (var i = 0; i < balls.length; i++){
balls[i].move();
}
setTimeout(loop, 8);
};
http://jsbin.com/imofat/743/edit

Calculate the Eucledian distance between the centers of each ball. Then, when this distance is smaller or equal to the sum of their radiuses, there's a collision:
check = function() {
for (var i = 0; i < balls.length; i++) {
for(var j = 0; j < balls.length; j++) {
if (i != j) { // ignore self-collision
if (Math.pow(balls[j].center.x - balls[i].center.x, 2) + Math.pow(balls[j].center.y - balls[i].center.y, 2) <= Math.pow(balls[i].radius + balls[j].radius, 2)) {
balls[j].setColor('red');
} else {
balls[j].setColor(balls[j].color);
}
}
}}
Here's a DEMO.

Edit:
for colliding with other balls, it will require more work.. check this post: Ball to Ball Collision
just setColor when the direction changes, assuming "collide" means "hit the wall":
// move and bounce the ball
Ball.prototype.move = function(){
var diameter = this.radius * 2;
var radius = this.radius;
if (this.center.x - radius < 0 || this.center.x + radius > this.boundaryWidth ) {
this.dx = -this.dx;
this.setColor( 'red' );
}
if (this.center.y - radius < 0 || this.center.y + radius > this.boundaryHeight ) {
this.dy = -this.dy;
this.setColor( 'red' );
}
this.placeAtCenter(this.center.x + this.dx ,this.center.y +this.dy);
};

I added an actual ball collision check inside the check method:
check = function(){
var dx, dy;
var x1, x2, y1, y2, r1, r2;
for (var i = 0; i < balls.length; i++){
x1 = balls[i].center.x;y1=balls[i].center.y;r1=balls[i].radius;
for(var j=0;j<balls.length;j++){
if (i===j) {continue;} // collision with self
x2 = balls[j].center.x;y2=balls[j].center.y;r2=balls[j].radius;
if( Math.sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2)) <=(r1+r2) ){ // check collision
balls[i].setColor('#ff0000');
}
else{
}
}
}
setTimeout(check,8);
};

Related

how can I detect collision in a 2D tile game map

I made this basic game where I drew a map and a player, the player can move anywhere but how can I make so that it wont move when its on the tile[1] in the map?
also when I try to check if the player.x is greater than 50 it could go left it works but than if I click 2 keys at once it goes through
const context = document.querySelector("canvas").getContext("2d");
var rgb = 'rgb(' + Math.random()*256 + ',' + Math.random()*256 + ',' + Math.random()*256 + ','+Math.random() + ')';
document.onload = Loop();
var width = 1500;
var height = 800;
function Loop(){
var width = 1500;
var height = 800;
context.canvas.height = height;
context.canvas.width = width;
this.interval = setInterval(Update, 1000/100);
}
const Player = function(x, y, w, h, color) {
this.x = x; this.y = y; this.w = w; this.h = h;
this.speedY = 0; this.speedX = 0;
this.Draw = function(){
context.fillStyle = this.color;
context.fillRect(this.x, this.y, this.w, this.h);
};
this.Move = function(){
this.x += this.speedX;
this.y += this.speedY;
};
};<code>
var player = new Player(100,100,50, 50, rgb);
var Key = {};
function Update(){
context.clearRect(0, 0, width, height);
Map();
player.Draw();
player.Move();
onkeydown = onkeyup = function(e){
player.speedX = 0;
player.speedY = 0;
e = e || event;
Key[e.keyCode] = e.type == 'keydown';
if(Key[37] || Key[65]) {player.speedX -= 2}
if(Key[38] || Key[87]) {player.speedY -= 2}
if(Key[39] || Key[68]) {player.speedX += 2}
if(Key[40] || Key[83]) {player.speedY += 2}
if(Key[32]) {player.color = 'rgb(' + Math.random()*256 + ',' + Math.random()*256 + ',' + Math.random()*256 + ','+Math.random()*1 + ')';}
};
}
var map = [
1, 1, 1, 1, 1,
1, 0, 0, 0, 1,
1, 0, 0, 0, 1,
1, 0, 0, 0, 1,
1, 1, 1, 1, 1
];
var row = 5;
var column = 5;
function Map(){
for(let y = -1; y < column; y++){
for(let x = -1; x < row; x++){
switch(map[((y*row) + x)]) {
case 0: context.fillStyle = player.color;
break;
case 1: context.fillStyle = "#ffffff";
break;
default: context.fillStyle = "#000000";
}
context.fillRect(x*50, y*50, 50, 50);
}
}
}
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>
body {
background-color: black;
}
canvas {
display: block;
margin: auto;
border: solid 1px white;
border-radius: 10px;
}
script {
display: none;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script type="application/javascript">
void function() {
"use strict";
// Classes
function Camera(x,y) {
this.x = x || 0.0;
this.y = y || 0.0;
}
Camera.prototype = {
set: function(x,y) {
this.x = x || 0.0;
this.y = y || 0.0;
},
pan: function(x,y) {
this.x += x || 0.0;
this.y += y || 0.0;
}
};
var nextID = 0;
function Tile(colour) {
this.id = nextID++;
this.colour = colour || "black";
}
function Map(width,height) {
this.width = width || 1;
this.height = height || 1;
this.map = [];
this.map.length = this.height;
for (var y = 0; y < this.height; ++y) {
this.map[y] = [];
this.map[y].length = width;
for (var x = 0; x < this.width; ++x) {
this.map[y][x] = Math.random() < 0.2 ?
this.TILE_WALL:
this.TILE_GRASS;
}
this.map[y][0] = this.TILE_WALL;
this.map[y][this.width - 1] = this.TILE_WALL;
}
for (var x = 0; x < this.width; ++x) {
this.map[0][x] = this.TILE_WALL;
this.map[this.height - 1][x] = this.TILE_WALL;
}
}
Map.prototype = {
TILE_WIDTH: 32.0,
TILE_HEIGHT: 32.0,
INV_TILE_WIDTH: 0.0,
INV_TILE_HEIGHT: 0.0,
TILE_AIR: new Tile("#00000000"),
TILE_GRASS: new Tile("#00AA00FF"),
TILE_WALL: new Tile("#555555FF"),
set: function(x,y,tile) {
this.map[y][x] = tile;
},
scaleX: function(x) {
return (x * this.INV_TILE_WIDTH) | 0;
},
scaleY: function(y) {
return (y * this.INV_TILE_HEIGHT) | 0;
},
isColliding: function(x,y) {
return x > -1 && x < this.width
&& y > -1 && y < this.height
&& this.map[y][x].id > 1;
},
render: function(ctx,camera) {
for (var y = 0; y < this.height; ++y) {
for (var x = 0; x < this.width; ++x) {
var tile = this.map[y][x];
var _x = x * this.TILE_WIDTH - camera.x;
var _y = y * this.TILE_HEIGHT - camera.y;
ctx.fillStyle = tile.colour;
ctx.fillRect(_x,_y,this.TILE_WIDTH - 1,this.TILE_HEIGHT - 1);
}
}
}
};
Map.prototype.INV_TILE_WIDTH = 1.0 / Map.prototype.TILE_WIDTH;
Map.prototype.INV_TILE_HEIGHT = 1.0 / Map.prototype.TILE_HEIGHT;
function Player(x,y) {
this.x = x || 0.0;
this.y = y || 0.0;
this.dx = 0.0;
this.dy = 0.0;
this.isUp = false;
this.isDown = false;
this.isLeft = false;
this.isRight = false;
}
Player.prototype = {
WIDTH: 20.0,
HEIGHT: 20.0,
ACCELERATION: 1.0,
DEACCELERATION: 0.5,
MAX_SPEED: 3.0,
tick: function(map) {
// Movement
if (this.isUp) {
this.dy -= this.ACCELERATION;
if (this.dy < -this.MAX_SPEED) {
this.dy = -this.MAX_SPEED;
}
} else if (this.dy < 0.0) {
this.dy += this.DEACCELERATION;
if (this.dy > 0.0) {
this.dy = 0.0;
}
}
if (this.isDown) {
this.dy += this.ACCELERATION;
if (this.dy > this.MAX_SPEED) {
this.dy = this.MAX_SPEED;
}
} else if (this.dy > 0.0) {
this.dy -= this.DEACCELERATION;
if (this.dy < 0.0) {
this.dy = 0.0;
}
}
if (this.isLeft) {
this.dx -= this.ACCELERATION;
if (this.dx < -this.MAX_SPEED) {
this.dx = -this.MAX_SPEED;
}
} else if (this.dx < 0.0) {
this.dx += this.DEACCELERATION;
if (this.dx > 0.0) {
this.dx = 0.0;
}
}
if (this.isRight) {
this.dx += this.ACCELERATION;
if (this.dx > this.MAX_SPEED) {
this.dx = this.MAX_SPEED;
}
} else if (this.dx > 0.0) {
this.dx -= this.DEACCELERATION;
if (this.dx < 0.0) {
this.dx = 0.0;
}
}
// Collision
if (this.dx !== 0.0) {
var minY = map.scaleY(this.y);
var maxY = map.scaleY(this.y + this.HEIGHT);
var minX = 0;
var maxX = 0;
if (this.dx < 0.0) {
minX = map.scaleX(this.x + this.dx);
maxX = map.scaleX(this.x);
} else {
minX = map.scaleX(this.x + this.WIDTH);
maxX = map.scaleX(this.x + this.WIDTH + this.dx);
}
loop:
for (var y = minY; y <= maxY; ++y) {
for (var x = minX; x <= maxX; ++x) {
if (map.isColliding(x,y)) {
this.x = this.dx < 0.0 ?
(x + 1) * map.TILE_WIDTH:
x * map.TILE_WIDTH - this.WIDTH - 1;
this.dx = 0.0;
break loop;
}
}
}
}
if (this.dy !== 0.0) {
var minX = map.scaleX(this.x);
var maxX = map.scaleX(this.x + this.WIDTH);
var minY = 0;
var maxY = 0;
if (this.dy < 0.0) {
minY = map.scaleY(this.y + this.dy);
maxY = map.scaleY(this.y);
} else {
minY = map.scaleY(this.y + this.HEIGHT);
maxY = map.scaleY(this.y + this.HEIGHT + this.dy);
}
loop:
for (var y = minY; y <= maxY; ++y) {
for (var x = minX; x <= maxX; ++x) {
if (map.isColliding(x,y)) {
this.y = this.dy < 0.0 ?
(y + 1) * map.TILE_HEIGHT:
y * map.TILE_HEIGHT - this.HEIGHT - 1;
this.dy = 0.0;
break loop;
}
}
}
}
this.x += this.dx;
this.y += this.dy;
},
render: function(ctx,camera) {
camera.set(this.x,this.y);
ctx.lineWidth = 1;
ctx.strokeStyle = "black";
ctx.fillStyle = "darkred";
ctx.beginPath();
ctx.rect(this.x - camera.x,this.y - camera.y,this.WIDTH,this.HEIGHT);
ctx.fill();
ctx.stroke();
}
};
// Variables
var canvasWidth = 180;
var canvasHeight = 160;
var canvas = null;
var ctx = null;
var camera = null;
var map = null;
var player = null;
// Functions
function onKeyDown(e) {
switch(e.key.toUpperCase()) {
case "W": player.isUp = true; break;
case "S": player.isDown = true; break;
case "A": player.isLeft = true; break;
case "D": player.isRight = true; break;
}
}
function onKeyUp(e) {
switch(e.key.toUpperCase()) {
case "W": player.isUp = false; break;
case "S": player.isDown = false; break;
case "A": player.isLeft = false; break;
case "D": player.isRight = false; break;
}
}
function loop() {
// Tick
player.tick(map);
// Render
ctx.fillStyle = "gray";
ctx.fillRect(-canvasWidth >> 1,-canvasHeight >> 1,canvasWidth,canvasHeight);
map.render(ctx,camera);
player.render(ctx,camera);
//
requestAnimationFrame(loop);
}
// Entry point (first to execute)
onload = function() {
canvas = document.getElementById("canvas");
canvas.width = canvasWidth;
canvas.height = canvasHeight;
ctx = canvas.getContext("2d");
ctx.translate(canvasWidth >> 1,canvasHeight >> 1);
camera = new Camera(0.0,0.0);
map = new Map(10,10);
player = new Player(40.0,40.0);
map.set(1,1,map.TILE_GRASS);
addEventListener("keydown",onKeyDown);
addEventListener("keyup",onKeyUp);
loop();
}
}();
</script>
</body>
</html>
Firstly, looking at your code, there are some things that are missing which is required to implement basic collision detection and those are:
The player's current direction that he/she is moving in. This is important because it allows the function determining the collision detection to distinguish which side it is checking for the collision (Up, down, left, or right) since a player can only collide with one side at a time.
The tile's position and size. This is also very important because like the first point, there is only one side of the tile that the player can collide with and knowing the size and position can determine if it is a collision or not based on the players size and position.
Also, since you mentioned it is a basic game, the implementation below is a basic collision detection. If you were to make a more complex and bigger game, you should try looking into quad trees for more efficient collision detection:
https://gamedevelopment.tutsplus.com/tutorials/quick-tip-use-quadtrees-to-detect-likely-collisions-in-2d-space--gamedev-374
Now this is the function for detecting collision, for the sake of readability and shortness, p will represent the player object and t would represent the tile object. This function returns whether or not the player is colliding with a tile based on their direction of movement.
function isColliding(p, t){
if (p.direction == 'up') {
return p.y +(p.height/2)-p.speedY< t.y + t.height && p.y > t.y
&& p.x + p.width > t.x && p.x < t.x + t.width;
}
if (p.direction == 'down') {
return p.y + (p.height/2)+p.speedY > t.y && p.y < t.y
&& p.x + p.width > t.x && p.x < t.x + t.width;
}
if (p.direction == 'right') {
return p.x + p.width+p.speedX > t.x && p.x < t.x
&& p.y +(p.height/2)> t.y && p.y + p.height < t.y +t.height+ (p.height / 2);
}
if (p.direction == 'left') {
return p.x -p.speedX< t.x + t.width && p.x > t.x
&& p.y +(p.height/2)> t.y && p.y + p.height < t.y +t.height+ (p.height / 2);
}
return false;
}
You would probably want to put this in the player move function to constantly detect for tiles as it is moving. To do that, you'd want to modify your keydown detection so that with each different keydown, it would update the player's direction, here's a simple example:
document.onkeydown = function(event){
if (event.keyCode == 87)
player.up = true;
else if (event.keyCode == 65)
player.left = true;
else if (event.keyCode == 83)
player.down = true;
else if (event.keyCode == 68)
player.right = true;
}
and another simple example for every time the player moves (user presses a keydown):
const Player= function(/*Param stuff*/){
/*Property stuff*/
//tileArray is the array (or object, your choice) of all the current tiles in the map
this.move=function(tileArray){
//Go through all tiles to see if player is colliding with any of them
for(var t in tileArray){
if(this.up){
if(isColliding(this, tileArray[t]){
//functionality for when player collides
}else{
//functionality for when player doesn't collide
}
}
//check if player is going down, left, etc
}
}
}
These are just examples of how to implement the detection. You should use it as a reference to implement it relatively to how your code function because I didn't write it based on what you posted.
PS.
Make sure to also convert the directions to false after the user stops pressing the key.

HTML5 canvas particle explosion

I'm trying to get this particle explosion working. It's working but it looks like some frames does not get rendered. If I click many times to call several explosions it starts to uhm.. "lag/stutter". Is there something I have forgotten to do? It may look like the browser hangs when I click many times. Is it too much to have 2 for loops inside each other?
Attached my code so you can see.
Just try to click many times, and you will see the problem visually.
// Request animation frame
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
// Canvas
var c = document.getElementById('canvas');
var ctx = c.getContext('2d');
// Set full-screen
c.width = window.innerWidth;
c.height = window.innerHeight;
// Options
var background = '#333'; // Background color
var particlesPerExplosion = 20;
var particlesMinSpeed = 3;
var particlesMaxSpeed = 6;
var particlesMinSize = 1;
var particlesMaxSize = 3;
var explosions = [];
var fps = 60;
var now, delta;
var then = Date.now();
var interval = 1000 / fps;
// Optimization for mobile devices
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
fps = 29;
}
// Draw
function draw() {
// Loop
requestAnimationFrame(draw);
// Set NOW and DELTA
now = Date.now();
delta = now - then;
// New frame
if (delta > interval) {
// Update THEN
then = now - (delta % interval);
// Our animation
drawBackground();
drawExplosion();
}
}
// Draw explosion(s)
function drawExplosion() {
if (explosions.length == 0) {
return;
}
for (var i = 0; i < explosions.length; i++) {
var explosion = explosions[i];
var particles = explosion.particles;
if (particles.length == 0) {
explosions.splice(i, 1);
return;
}
for (var ii = 0; ii < particles.length; ii++) {
var particle = particles[ii];
// Check particle size
// If 0, remove
if (particle.size < 0) {
particles.splice(ii, 1);
return;
}
ctx.beginPath();
ctx.arc(particle.x, particle.y, particle.size, Math.PI * 2, 0, false);
ctx.closePath();
ctx.fillStyle = 'rgb(' + particle.r + ',' + particle.g + ',' + particle.b + ')';
ctx.fill();
// Update
particle.x += particle.xv;
particle.y += particle.yv;
particle.size -= .1;
}
}
}
// Draw the background
function drawBackground() {
ctx.fillStyle = background;
ctx.fillRect(0, 0, c.width, c.height);
}
// Clicked
function clicked(e) {
var xPos, yPos;
if (e.offsetX) {
xPos = e.offsetX;
yPos = e.offsetY;
} else if (e.layerX) {
xPos = e.layerX;
yPos = e.layerY;
}
explosions.push(new explosion(xPos, yPos));
}
// Explosion
function explosion(x, y) {
this.particles = [];
for (var i = 0; i < particlesPerExplosion; i++) {
this.particles.push(new particle(x, y));
}
}
// Particle
function particle(x, y) {
this.x = x;
this.y = y;
this.xv = randInt(particlesMinSpeed, particlesMaxSpeed, false);
this.yv = randInt(particlesMinSpeed, particlesMaxSpeed, false);
this.size = randInt(particlesMinSize, particlesMaxSize, true);
this.r = randInt(113, 222);
this.g = '00';
this.b = randInt(105, 255);
}
// Returns an random integer, positive or negative
// between the given value
function randInt(min, max, positive) {
if (positive == false) {
var num = Math.floor(Math.random() * max) - min;
num *= Math.floor(Math.random() * 2) == 1 ? 1 : -1;
} else {
var num = Math.floor(Math.random() * max) + min;
}
return num;
}
// On-click
$('canvas').on('click', function(e) {
clicked(e);
});
draw();
<!DOCTYPE html>
<html>
<head>
<style>
* {
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
</body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
</html>
You are returning from iterating over the particles if one is too small. This causes the other particles of that explosion to render only in the next frame.
I have a working version:
// Request animation frame
const requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
// Canvas
const c = document.getElementById('canvas');
const ctx = c.getContext('2d');
// Set full-screen
c.width = window.innerWidth;
c.height = window.innerHeight;
// Options
const background = '#333'; // Background color
const particlesPerExplosion = 20;
const particlesMinSpeed = 3;
const particlesMaxSpeed = 6;
const particlesMinSize = 1;
const particlesMaxSize = 3;
const explosions = [];
let fps = 60;
const interval = 1000 / fps;
let now, delta;
let then = Date.now();
// Optimization for mobile devices
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
fps = 29;
}
// Draw
function draw() {
// Loop
requestAnimationFrame(draw);
// Set NOW and DELTA
now = Date.now();
delta = now - then;
// New frame
if (delta > interval) {
// Update THEN
then = now - (delta % interval);
// Our animation
drawBackground();
drawExplosion();
}
}
// Draw explosion(s)
function drawExplosion() {
if (explosions.length === 0) {
return;
}
for (let i = 0; i < explosions.length; i++) {
const explosion = explosions[i];
const particles = explosion.particles;
if (particles.length === 0) {
explosions.splice(i, 1);
return;
}
const particlesAfterRemoval = particles.slice();
for (let ii = 0; ii < particles.length; ii++) {
const particle = particles[ii];
// Check particle size
// If 0, remove
if (particle.size <= 0) {
particlesAfterRemoval.splice(ii, 1);
continue;
}
ctx.beginPath();
ctx.arc(particle.x, particle.y, particle.size, Math.PI * 2, 0, false);
ctx.closePath();
ctx.fillStyle = 'rgb(' + particle.r + ',' + particle.g + ',' + particle.b + ')';
ctx.fill();
// Update
particle.x += particle.xv;
particle.y += particle.yv;
particle.size -= .1;
}
explosion.particles = particlesAfterRemoval;
}
}
// Draw the background
function drawBackground() {
ctx.fillStyle = background;
ctx.fillRect(0, 0, c.width, c.height);
}
// Clicked
function clicked(e) {
let xPos, yPos;
if (e.offsetX) {
xPos = e.offsetX;
yPos = e.offsetY;
} else if (e.layerX) {
xPos = e.layerX;
yPos = e.layerY;
}
explosions.push(
new explosion(xPos, yPos)
);
}
// Explosion
function explosion(x, y) {
this.particles = [];
for (let i = 0; i < particlesPerExplosion; i++) {
this.particles.push(
new particle(x, y)
);
}
}
// Particle
function particle(x, y) {
this.x = x;
this.y = y;
this.xv = randInt(particlesMinSpeed, particlesMaxSpeed, false);
this.yv = randInt(particlesMinSpeed, particlesMaxSpeed, false);
this.size = randInt(particlesMinSize, particlesMaxSize, true);
this.r = randInt(113, 222);
this.g = '00';
this.b = randInt(105, 255);
}
// Returns an random integer, positive or negative
// between the given value
function randInt(min, max, positive) {
let num;
if (positive === false) {
num = Math.floor(Math.random() * max) - min;
num *= Math.floor(Math.random() * 2) === 1 ? 1 : -1;
} else {
num = Math.floor(Math.random() * max) + min;
}
return num;
}
// On-click
$('canvas').on('click', function (e) {
clicked(e);
});
draw();
<!DOCTYPE html>
<html>
<head>
<style>* {margin:0;padding:0;overflow:hidden;}</style>
</head>
<body>
<canvas id="canvas"></canvas>
</body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
</html>
Loops, break and continue.
The problem was caused when you checked for empty particle arrays and when you found a particle to remove.
The bugs
The following two statements and blocks caused the problem
if (particles.length == 0) {
explosions.splice(i, 1);
return;
}
and
if (particles.size < 0) {
explosions.splice(ii, 1);
return;
}
The returns stopped the rendering of particles, so you would sometimes return before drawing a single particle was rendered just because the first explosion was empty or first particle was too small.
Continue and break
You can use the continue token in javascript to skip the rest of a for, while, do loop
for(i = 0; i < 100; i++){
if(test(i)){
// need to skip this iteration
continue;
}
// more code
// more code
// continue skips all the code upto the closing }
} << continues to here and if i < 100 the loop continues on.
Or you can completely break out of the loop with break
for(i = 0; i < 100; i++){
if(test(i)){
// need to exit the for loop
break;
}
// more code
// more code
// break skips all the code to the first line after the closing }
}
<< breaks to here and if i remains the value it was when break was encountered
The fix
if (particles.length == 0) {
explosions.splice(i, 1);
continue;
}
and
if (particles.size < 0) {
explosions.splice(ii, 1);
continue;
}
Your example with the fix
Your code with the fix. Befor I found it I started changing stuff.
Minor stuff.
requestAnimationFrame passes a time in milliseconds so to an accuracy of micro seconds.
You were setting then incorrectly and would have been losing frames. I changed the timing to use the argument time and then is just set to the time when a frame is drawn.
There are some other issues, nothing major and more of a coding style thing. You should capitalise objects created with new
function Particle(...
not
function particle(...
and your random is a overly complex
function randInt(min, max = min - (min = 0)) {
return Math.floor(Math.random() * (max - min) + min);
}
or
function randInt(min,max){
max = max === undefined ? min - (min = 0) : max;
return Math.floor(Math.random() * (max - min) + min);
}
randInt(100); // int 0 - 100
randInt(10,20); // int 10-20
randInt(-100); // int -100 to 0
randInt(-10,20); // int -10 to 20
this.xv = randInt(-particlesMinSpeed, particlesMaxSpeed);
this.yv = randInt(-particlesMinSpeed, particlesMaxSpeed);
this.size = randInt(particlesMinSize, particlesMaxSize);
And if you are using the same name in variables a good sign to create an object
var particlesPerExplosion = 20;
var particlesMinSpeed = 3;
var particlesMaxSpeed = 6;
var particlesMinSize = 1;
var particlesMaxSize = 3;
Could be
const settings = {
particles : {
speed : {min : 3, max : 6 },
size : {min : 1 : max : 3 },
explosionCount : 20,
},
background : "#000",
}
Anyways your code.
var c = canvas;
var ctx = c.getContext('2d');
// Set full-screen
c.width = innerWidth;
c.height = innerHeight;
// Options
var background = '#333'; // Background color
var particlesPerExplosion = 20;
var particlesMinSpeed = 3;
var particlesMaxSpeed = 6;
var particlesMinSize = 1;
var particlesMaxSize = 3;
var explosions = [];
var fps = 60;
var now, delta;
var then = 0; // Zero start time
var interval = 1000 / fps;
// Optimization for mobile devices
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
fps = 29;
}
// Draw
// as time is passed you need to start with requestAnimationFrame
requestAnimationFrame(draw);
function draw(time) { //requestAnimationFrame frame passes the time
requestAnimationFrame(draw);
delta = time - then;
if (delta > interval) {
then = time
drawBackground();
drawExplosion();
}
}
// Draw explosion(s)
function drawExplosion() {
if (explosions.length == 0) {
return;
}
for (var i = 0; i < explosions.length; i++) {
var explosion = explosions[i];
var particles = explosion.particles;
if (particles.length == 0) {
explosions.splice(i, 1);
//return;
continue;
}
for (var ii = 0; ii < particles.length; ii++) {
var particle = particles[ii];
// Check particle size
// If 0, remove
if (particle.size < 0) {
particles.splice(ii, 1);
// return;
continue;
}
ctx.beginPath();
ctx.arc(particle.x, particle.y, particle.size, Math.PI * 2, 0, false);
ctx.closePath();
ctx.fillStyle = 'rgb(' + particle.r + ',' + particle.g + ',' + particle.b + ')';
ctx.fill();
// Update
particle.x += particle.xv;
particle.y += particle.yv;
particle.size -= .1;
}
}
}
// Draw the background
function drawBackground() {
ctx.fillStyle = background;
ctx.fillRect(0, 0, c.width, c.height);
}
// Clicked
function clicked(e) {
var xPos, yPos;
if (e.offsetX) {
xPos = e.offsetX;
yPos = e.offsetY;
} else if (e.layerX) {
xPos = e.layerX;
yPos = e.layerY;
}
explosions.push(new explosion(xPos, yPos));
}
// Explosion
function explosion(x, y) {
this.particles = [];
for (var i = 0; i < particlesPerExplosion; i++) {
this.particles.push(new particle(x, y));
}
}
// Particle
function particle(x, y) {
this.x = x;
this.y = y;
this.xv = randInt(particlesMinSpeed, particlesMaxSpeed, false);
this.yv = randInt(particlesMinSpeed, particlesMaxSpeed, false);
this.size = randInt(particlesMinSize, particlesMaxSize, true);
this.r = randInt(113, 222);
this.g = '00';
this.b = randInt(105, 255);
}
// Returns an random integer, positive or negative
// between the given value
function randInt(min, max, positive) {
if (positive == false) {
var num = Math.floor(Math.random() * max) - min;
num *= Math.floor(Math.random() * 2) == 1 ? 1 : -1;
} else {
var num = Math.floor(Math.random() * max) + min;
}
return num;
}
// On-click
$('canvas').on('click', function(e) {
clicked(e);
});
<!DOCTYPE html>
<html>
<head>
<style>
* {
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
</body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
</html>

Draw random coloured circles on canvas

I am trying to create a animation where circles run from right to left. The circles' colours are selected randomly by a function. I have created a fiddle where one circle runs from right to left. Now my function creates a random colour. This function is executed every second and the circle changes its colour every second, instead of a new circle with the random picked colour become created. How can I change it so that it draws a new circle every second on the canvas and doesn't only change the colour of the circle?
This is my function:
function getRandomElement(array) {
if (array.length == 0) {
return undefined;
}
return array[Math.floor(Math.random() * array.length)];
}
var circles = [
'#FFFF00',
'#FF0000',
'#0000FF'
];
function drawcircles() {
ctx.beginPath();
ctx.arc(ballx * 108, canvasHeight / 2, x*5, 0, 2*Math.PI, false);
ctx.fillStyle = getRandomElement(circles);
ctx.fill();
ctx.closePath;
}
Comments on your question:
You are changing the color of the fillStyle to a random color at each frame. This is the reason why it keeps "changing" color. Set it to the color of the circle:
context.fillStyle = circle.color;
make circles with x, y, diameter, bounciness, speed, and color using an array
draw and update them with requestAnimationFrame (mine is a custom function)
My answer:
I made this just last night, where some circles follow the cursor and "bounce" off the edges of the screen. I tested the code and it works.
I might post a link later, but here is all of the code right now...
<!DOCTYPE html>
<html>
<body>
<canvas id="canvas"></canvas>
<script>
var lastTime = 0;
function requestMyAnimationFrame(callback, time)
{
var t = time || 16;
var currTime = new Date().getTime();
var timeToCall = Math.max(0, t - (currTime - lastTime));
var id = window.setTimeout(function(){ callback(currTime + timeToCall); }, timeToCall);
lastTime = currTime + timeToCall;
return id;
}
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
canvas.width = window.innerWidth - 20;
canvas.height = window.innerHeight - 20;
canvas.style.width = canvas.width + "px";
canvas.style.height = canvas.height + "px";
var circles = [];
var mouse =
{
x: 0,
y: 0
}
function getCoordinates(x, y)
{
return "(" + x + ", " + y + ")";
}
function getRatio(n, d)
{
// prevent division by 0
if (d === 0 || n === 0)
{
return 0;
}
else
{
return n/d;
}
}
function Circle(x,y,d,b,s,c)
{
this.x = x;
this.y = y;
this.diameter = Math.round(d);
this.radius = Math.round(d/2);
this.bounciness = b;
this.speed = s;
this.color = c;
this.deltaX = 0;
this.deltaY = 0;
this.drawnPosition = "";
this.fill = function()
{
context.beginPath();
context.arc(this.x+this.radius,this.y+this.radius,this.radius,0,Math.PI*2,false);
context.closePath();
context.fill();
}
this.clear = function()
{
context.fillStyle = "#ffffff";
this.fill();
}
this.draw = function()
{
if (this.drawnPosition !== getCoordinates(this.x, this.y))
{
context.fillStyle = this.color;
// if commented, the circle will be drawn if it is in the same position
//this.drawnPosition = getCoordinates(this.x, this.y);
this.fill();
}
}
this.keepInBounds = function()
{
if (this.x < 0)
{
this.x = 0;
this.deltaX *= -1 * this.bounciness;
}
else if (this.x + this.diameter > canvas.width)
{
this.x = canvas.width - this.diameter;
this.deltaX *= -1 * this.bounciness;
}
if (this.y < 0)
{
this.y = 0;
this.deltaY *= -1 * this.bounciness;
}
else if (this.y+this.diameter > canvas.height)
{
this.y = canvas.height - this.diameter;
this.deltaY *= -1 * this.bounciness;
}
}
this.followMouse = function()
{
// deltaX/deltaY will currently cause the circles to "orbit" around the cursor forever unless it hits a wall
var centerX = Math.round(this.x + this.radius);
var centerY = Math.round(this.y + this.radius);
if (centerX < mouse.x)
{
// circle is to the left of the mouse, so move the circle to the right
this.deltaX += this.speed;
}
else if (centerX > mouse.x)
{
// circle is to the right of the mouse, so move the circle to the left
this.deltaX -= this.speed;
}
else
{
//this.deltaX = 0;
}
if (centerY < mouse.y)
{
// circle is above the mouse, so move the circle downwards
this.deltaY += this.speed;
}
else if (centerY > mouse.y)
{
// circle is under the mouse, so move the circle upwards
this.deltaY -= this.speed;
}
else
{
//this.deltaY = 0;
}
this.x += this.deltaX;
this.y += this.deltaY;
this.x = Math.round(this.x);
this.y = Math.round(this.y);
}
}
function getRandomDecimal(min, max)
{
return Math.random() * (max-min) + min;
}
function getRoundedNum(min, max)
{
return Math.round(getRandomDecimal(min, max));
}
function getRandomColor()
{
// array of three colors
var colors = [];
// go through loop and add three integers between 0 and 255 (min and max color values)
for (var i = 0; i < 3; i++)
{
colors[i] = getRoundedNum(0, 255);
}
// return rgb value (RED, GREEN, BLUE)
return "rgb(" + colors[0] + "," + colors[1] + ", " + colors[2] + ")";
}
function createCircle(i)
{
// diameter of circle
var minDiameter = 25;
var maxDiameter = 50;
// bounciness of circle (changes speed if it hits a wall)
var minBounciness = 0.2;
var maxBounciness = 0.65;
// speed of circle (how fast it moves)
var minSpeed = 0.3;
var maxSpeed = 0.45;
// getRoundedNum returns a random integer and getRandomDecimal returns a random decimal
var x = getRoundedNum(0, canvas.width);
var y = getRoundedNum(0, canvas.height);
var d = getRoundedNum(minDiameter, maxDiameter);
var c = getRandomColor();
var b = getRandomDecimal(minBounciness, maxBounciness);
var s = getRandomDecimal(minSpeed, maxSpeed);
// create the circle with x, y, diameter, bounciness, speed, and color
circles[i] = new Circle(x,y,d,b,s,c);
}
function makeCircles()
{
var maxCircles = getRoundedNum(2, 5);
for (var i = 0; i < maxCircles; i++)
{
createCircle(i);
}
}
function drawCircles()
{
var ii = 0;
for (var i = 0; ii < circles.length; i++)
{
if (circles[i])
{
circles[i].draw();
ii++;
}
}
}
function clearCircles()
{
var ii = 0;
for (var i = 0; ii < circles.length; i++)
{
if (circles[i])
{
circles[i].clear();
ii++;
}
}
}
function updateCircles()
{
var ii = 0;
for (var i = 0; ii < circles.length; i++)
{
if (circles[i])
{
circles[i].keepInBounds();
circles[i].followMouse();
ii++;
}
}
}
function update()
{
requestMyAnimationFrame(update,10);
updateCircles();
}
function draw()
{
requestMyAnimationFrame(draw,1000/60);
context.clearRect(0,0,canvas.width,canvas.height);
drawCircles();
}
function handleError(e)
{
//e.preventDefault();
//console.error(" ERROR ------ " + e.message + " ------ ERROR ");
}
window.addEventListener("load", function()
{
window.addEventListener("error", function(e)
{
handleError(e);
});
window.addEventListener("resize", function()
{
canvas.width = window.innerWidth - 20;
canvas.height = window.innerHeight - 20;
});
window.addEventListener("mousemove", function(e)
{
mouse.x = e.layerX || e.offsetX;
mouse.y = e.layerY || e.offsetY;
});
makeCircles();
update();
draw();
});
</script>
</body>
</html>

New to JS, is there a way to turn these circles to diamonds?

As the titles suggests, I'm trying to get this blinking field of circles to rectangles or diamonds, whichever is easer really.
Link: http://jsfiddle.net/Jksb5/1/
HTML
<canvas id="pixie"></canvas>
JS
var WIDTH;
var HEIGHT;
var canvas;
var con;
var g;
var pxs = new Array();
var rint = 60;
$(document).ready(function(){
WIDTH = 960;
HEIGHT = 300;
canvas = document.getElementById('pixie');
$(canvas).attr('width', WIDTH).attr('height',HEIGHT);
con = canvas.getContext('2d');
for(var i = 0; i < 100; i++) {
pxs[i] = new Circle();
pxs[i].reset();
}
setInterval(draw,rint);
});
function draw() {
con.clearRect(0,0,WIDTH,HEIGHT);
for(var i = 0; i < pxs.length; i++) {
pxs[i].fade();
pxs[i].move();
pxs[i].draw();
}
}
function Circle() {
this.s = {ttl:8000, xmax:5, ymax:2, rmax:25, rt:1, xdef:90, ydef:90, xdrift:4, ydrift: 4, random:true, blink:true};
this.reset = function() {
this.x = (this.s.random ? WIDTH*Math.random() : this.s.xdef);
this.y = (this.s.random ? HEIGHT*Math.random() : this.s.ydef);
this.r = ((this.s.rmax-1)*Math.random()) + 1;
this.dx = (Math.random()*this.s.xmax) * (Math.random() < .5 ? -1 : 1);
this.dy = (Math.random()*this.s.ymax) * (Math.random() < .5 ? -1 : 1);
this.hl = (this.s.ttl/rint)*(this.r/this.s.rmax);
this.rt = Math.random()*this.hl;
this.s.rt = Math.random()+1;
this.stop = Math.random()*.2+.4;
this.s.xdrift *= Math.random() * (Math.random() < .5 ? -1 : 1);
this.s.ydrift *= Math.random() * (Math.random() < .5 ? -1 : 1);
}
this.fade = function() {
this.rt += this.s.rt;
}
this.draw = function() {
if(this.s.blink && (this.rt <= 0 || this.rt >= this.hl)) this.s.rt = this.s.rt*-1;
else if(this.rt >= this.hl) this.reset();
var newo = 1-(this.rt/this.hl);
con.beginPath();
con.arc(this.x,this.y,this.r,0,Math.PI*2,true);
// con.rect(188, 50, 100, 100);
con.closePath();
var cr = this.r*newo;
g = con.createRadialGradient(this.x,this.y,0,this.x,this.y,(cr <= 0 ? 1 : cr));
g.addColorStop(0.0, 'rgba(237,23,31,'+newo+')');
// g.addColorStop(this.stop, 'rgba(237,146,157,'+(newo*.6)+')');
g.addColorStop(1.0, 'rgba(126,22,40,0)');
con.fillStyle = g;
con.fill();
}
this.move = function() {
this.x += (this.rt/this.hl)*this.dx;
this.y += (this.rt/this.hl)*this.dy;
if(this.x > WIDTH || this.x < 0) this.dx *= -1;
if(this.y > HEIGHT || this.y < 0) this.dy *= -1;
}
this.getX = function() { return this.x; }
this.getY = function() { return this.y; }
}
One thing I've tried was to change the con.arc var to be a rectangle thinking that might be what's generating the circle, but when I comment that particular line it makes the field into a small rectangle, not the objects.
So I'm guessing what needs to be modified is the this function, which I really can't figure out/understand. Any help would be appreciated, thanks!
You can replace the arc call with
con.moveTo(this.x, this.y-this.r/2);
con.lineTo(this.x+this.r/2, this.y);
con.lineTo(this.x, this.y+this.r/2);
con.lineTo(this.x-this.r/2, this.y);
The gradient filling will of course remain round
con is a CanvasRenderingContext2D, which is what you use to draw on the canvas.
First, a path is created with
con.beginPath();
con.arc(this.x, this.y, this.r, 0, Math.PI * 2, true);
con.closePath();
This doesn't actually draw anything on the canvas, though. Nothing is drawn until the path is filled: con.fill().
What the lines before con.fill() do is create a gradient object and set it as the fillStyle, so it's drawn inside the circle when con.fill is called.
g = con.createRadialGradient(this.x, this.y, 0, this.x, this.y, (cr <= 0 ? 1 : cr));
g.addColorStop(0.0, 'rgba(237,23,31,' + newo + ')');
g.addColorStop(1.0, 'rgba(126,22,40,0)');
con.fillStyle = g;
If you remove those lines, con.rect will work. You can replace the circles with squares like this:
con.beginPath();
con.rect(this.x, this.y, this.r, this.r);
con.closePath();
con.fill();
It isn't as pretty, but it's a start.

javascript draw particles

How can I draw around 50000 particles in a browser and then just stop, I know how to create unending animations of particles, but how can I create one that once its done drawing the particles it just stops.
Edit
So essintially I want to time the drawing of the particles, however when i attack a timer, it doesnt get the change because the animation doesnt stop.
var scene = new Scene(),
particles = [],
len = 40000,
height = document.body.offsetHeight,
width = document.body.offsetWidth;
function Particle() {
this.x = 0;
this.y = 0;
this.size = 0;
this.depth = 0;
this.vy = 0;
}
Particle.prototype = {
constructor: Particle,
update: function (width, height) {
if (this.y > height) {
this.y = 1 - this.size;
}
this.y += this.vy;
}
};
for (var i = 0; i < len; i++) {
var particle = new Particle();
particle.x = Math.random() * width;
particle.y = Math.random() * height;
particle.depth = Math.random() * 10 | 0;
particle.size = (particle.depth + 1) / 8;
particle.vy = (particle.depth * .25) + 1 / Math.random();
particles.push(particle);
}
function falling_particles(scene) {
for (var i = 0, l = particles.length; i < l; i++) {
var particle = particles[i];
for (var w = 0; w < particle.size; w++) {
for (var h = 0; h < particle.size; h++) {
var pData = (~~(particle.x + w) + (~~(particle.y + h) * scene.width)) * 4;
scene.idata.data[pData] = 255;
scene.idata.data[pData + 1] = 255;
scene.idata.data[pData + 2] = 255;
scene.idata.data[pData + 3] = 255;
}
}
particle.update(scene.width, scene.height);
}
return scene.idata;
}
scene.setup(document.getElementById('canvas'), falling_particles, width, height, !0);
scene.animate();
window.onresize = function () {
height = scene.height = scene.canvas.height = document.body.offsetHeight;
width = scene.width = scene.canvas.width = document.body.offsetWidth;
};
link here: http://jsfiddle.net/MdSP4/
I'm not sure, what exactly you want to do, but if you add
setTimeout(function(){
scene.paused = true;
},1000);
Then all the drawing will stop after a second.

Categories

Resources