This question already has an answer here:
CanvasContext2D drawImage() issue [onload and CORS]
(1 answer)
Closed 4 years ago.
I am trying to make a infinite scrolling game in javascript.
The following code works as expected (it doesn't work here because the images cannot be uploaded):
class Vec {
constructor(x = 0, y = 0) {
this.x = x;
this.y = y;
}
}
class Rect {
constructor(src) {
this.pos = new Vec;
this.vel = new Vec;
this.image = new Image();
this.image.src = src;
}
get left() {
return this.pos.x;
}
get right() {
return this.pos.x + this.image.width;
}
get top() {
return this.pos.y;
}
get bottom() {
return this.pos.y + this.image.height;
}
}
class Player extends Rect {
constructor() {
super("images/frog-still.png");
this.pos.x = 100;
this.pos.y = HEIGHT - GROUND_Y - this.image.height;
}
}
class Enemy extends Rect {
constructor() {
super("images/spike.png");
this.image.width = this.image.width / 4;
this.image.height = this.image.height / 4;
this.pos.x = WIDTH;
this.pos.y = HEIGHT - GROUND_Y - this.image.height;
}
}
// setup
const ctx = document.getElementById("canvas").getContext("2d");
let isOver = false;
const WIDTH = 600;
const HEIGHT = 400;
const GROUND_Y = 50;
const player = new Player;
let enemies = [];
const MAX_ENEMY = 3;
let isJumping = false;
const JUMP_STRENGTH = -450;
const GRAVITY = 25;
document.addEventListener("keydown", (e) => {
if (e.keyCode === 38 && isJumping === false) {
isJumping = true;
player.vel.y = JUMP_STRENGTH;
}
}, false);
let lastUpdate = 0;
let random = Math.floor((Math.random() * 2501) + 1000);
function update(dt, now) {
// update player
player.vel.y += GRAVITY;
player.pos.y += player.vel.y * dt;
if (player.bottom >= HEIGHT - GROUND_Y) {
isJumping = false;
player.pos.y = HEIGHT - GROUND_Y - player.image.height;
player.vel.y = 0;
}
// create enemy
if (now - lastUpdate > random) {
lastUpdate = now;
random = Math.floor((Math.random() * 2501) + 1000);
enemies.push(new Enemy);
}
for (let i = 0; i < enemies.length; i++) {
// update enemy
enemies[i].vel.x = -300 - (now / 500);
enemies[i].pos.x += enemies[i].vel.x * dt;
if (player.right > enemies[i].left && player.left < enemies[i].right && player.bottom > enemies[i].top)
isOver = true;
if (enemies[i].right < 0)
enemies.splice(i, 1);
}
}
function draw() {
// draw background
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, WIDTH, HEIGHT);
// draw ground
ctx.fillStyle = "#0f0";
ctx.fillRect(0, HEIGHT - GROUND_Y, WIDTH, GROUND_Y);
// draw player
ctx.drawImage(player.image, player.pos.x, player.pos.y, player.image.width, player.image.height);
// draw enemy
ctx.fillStyle = "#f00";
for (let i = 0; i < enemies.length; i++) {
ctx.drawImage(enemies[i].image, enemies[i].pos.x, enemies[i].pos.y, enemies[i].image.width, enemies[i].image.height);
}
}
// game loop
const TIMESTEP = 1 / 60;
let accumulator = 0;
let lastRender = 0;
function loop(timestamp) {
accumulator += (timestamp - lastRender) / 1000;
lastRender = timestamp;
while (accumulator >= TIMESTEP) {
update(TIMESTEP, timestamp);
accumulator -= TIMESTEP;
}
draw();
if (!isOver)
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
<!DOCTYPE html>
<html>
<head>
<title>Jump_Over_It</title>
<link href="css/default.css" rel="stylesheet" />
</head>
<body>
<canvas id="canvas" width="600" height="400"></canvas>
<script src="js/main.js"></script>
</body>
</html>
However, when add
this.image.width = this.image.width / 2;
this.image.height = this.image.height / 2;
to the Player class (like I did with the Enemy class), the player disappears. If I do console.log(player.image), it says that the image width and the image height is equal to 0. Why is this happening?
How can I fix this problem?
I don't know why, but while the image is not rendered the width and height are 0. So, I created the method beforeRender() that is implemented by Player and Enemy. This method is invoked before the ctx.drawImage and update the graphics settings.
I know it is not the answer you are looking for, but it is my best.
class Vec {
constructor(x = 0, y = 0) {
this.x = x;
this.y = y;
}
}
class Rect {
constructor(src) {
this.pos = new Vec;
this.vel = new Vec;
this.image = new Image;
this.image.src = src;
this.rendered = false;
}
get left() {
return this.pos.x;
}
get right() {
return this.pos.x + this.image.width;
}
get top() {
return this.pos.y;
}
get bottom() {
return this.pos.y + this.image.height;
}
get render() {
if (!this.rendered) {
if (this.beforeRender()) {
this.rendered = true;
}
}
return this.image;
}
beforeRender() {}
}
class Player extends Rect {
constructor() {
super("https://cdn4.iconfinder.com/data/icons/spring-flat-colorful/2048/5683_-_Frog-256.png");
this.pos.x = 100;
this.pos.y = HEIGHT - GROUND_Y - this.image.height;
}
beforeRender() {
if (this.image.width && this.image.height) {
this.image.width /= 2;
this.image.height /= 2;
return true;
}
return false;
}
}
class Enemy extends Rect {
constructor() {
super("https://vignette.wikia.nocookie.net/scribblenauts/images/8/8d/Steel_Spike.png/revision/latest?cb=20130105173440");
this.image.width /= 4;
this.image.height /= 4;
this.pos.x = WIDTH;
this.pos.y = HEIGHT - GROUND_Y - this.image.height;
}
beforeRender() {
if (this.image.width && this.image.height) {
this.image.width /= 4;
this.image.height /= 4;
return true;
}
return false;
}
}
// setup
const ctx = document.getElementById("canvas").getContext("2d");
let isOver = false;
const WIDTH = 600;
const HEIGHT = 400;
const GROUND_Y = 50;
const player = new Player;
let enemies = [];
const MAX_ENEMY = 3;
let isJumping = false;
const JUMP_STRENGTH = -450;
const GRAVITY = 25;
document.addEventListener("keydown", (e) => {
if (e.keyCode === 38 && isJumping === false) {
isJumping = true;
player.vel.y = JUMP_STRENGTH;
}
}, false);
let lastUpdate = 0;
let random = Math.floor((Math.random() * 2501) + 1000);
function update(dt, now) {
// update player
player.vel.y += GRAVITY;
player.pos.y += player.vel.y * dt;
if (player.bottom >= HEIGHT - GROUND_Y) {
isJumping = false;
player.pos.y = HEIGHT - GROUND_Y - player.image.height;
player.vel.y = 0;
}
// create enemy
if (now - lastUpdate > random) {
lastUpdate = now;
random = Math.floor((Math.random() * 2501) + 1000);
enemies.push(new Enemy);
}
for (let i = 0; i < enemies.length; i++) {
// update enemy
enemies[i].vel.x = -300 - (now / 500);
enemies[i].pos.x += enemies[i].vel.x * dt;
if (player.right > enemies[i].left && player.left < enemies[i].right && player.bottom > enemies[i].top)
isOver = true;
if (enemies[i].right < 0)
enemies.splice(i, 1);
}
}
function draw() {
// draw background
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, WIDTH, HEIGHT);
// draw ground
ctx.fillStyle = "#0f0";
ctx.fillRect(0, HEIGHT - GROUND_Y, WIDTH, GROUND_Y);
// draw player
ctx.drawImage(player.render, player.pos.x, player.pos.y, player.image.width, player.image.height);
// draw enemy
ctx.fillStyle = "#f00";
for (let i = 0; i < enemies.length; i++) {
ctx.drawImage(enemies[i].image, enemies[i].pos.x, enemies[i].pos.y, enemies[i].image.width, enemies[i].image.height);
}
}
// game loop
const TIMESTEP = 1 / 60;
let accumulator = 0;
let lastRender = 0;
requestAnimationFrame(loop);
function loop(timestamp) {
accumulator += (timestamp - lastRender) / 1000;
lastRender = timestamp;
while (accumulator >= TIMESTEP) {
update(TIMESTEP, timestamp);
accumulator -= TIMESTEP;
}
draw();
if (!isOver)
requestAnimationFrame(loop);
}
<!DOCTYPE html>
<html>
<head>
<title>Jump_Over_It</title>
<link href="css/default.css" rel="stylesheet" />
</head>
<body>
<canvas id="canvas" width="600" height="400"></canvas>
<script src="js/main.js"></script>
</body>
</html>
Related
I'm trying to make this particle text be on a transparent background to add onto a slideshow. Can someone assist? I cannot seem to find where it is stated within the code.
Here is the JS code. Sorry I can't seem to get it to fit properly within the code section of the post.
class Particles { constructor(x, y, texture, size) {
this.x = x;
this.y = y;
this.sprite = new PIXI.Sprite(new PIXI.Texture.from(texture));
this.sprite.texture.frame = new PIXI.Rectangle(x, y, size, size);
this.sprite.x = x;
this.sprite.y = y;
this.speedX = 0;
this.speedY = 0;
this.radius = 100;
this.gravity = 0.1;
this.maxGravity = 0.01 + Math.random() * 0.03;
this.friction = 0.9;
this.dirX = Math.random() - 0.5;
this.dirY = Math.random() - 0.5; }
update(mouse) {
const distanceX = mouse.x - this.sprite.x;
const distanceY = mouse.y - this.sprite.y;
const distanceSqrd = distanceX * distanceX + distanceY * distanceY;
if (distanceSqrd < this.radius * this.radius && distanceSqrd > 0) {
const distance = Math.sqrt(distanceSqrd);
let normalX = distanceX / distance;
let normalY = distanceY / distance;
this.speedX -= normalX;
this.speedY -= normalY;
this.gravity *= this.friction;
} else {
this.gravity += 0.1 * (this.maxGravity - this.gravity);
}
let oDistX = this.x - this.sprite.x;
let oDistY = this.y - this.sprite.y;
this.speedX += oDistX * this.gravity;
this.speedY += oDistY * this.gravity;
this.speedX *= this.friction;
this.speedY *= this.friction;
this.sprite.x += this.speedX;
this.sprite.y += this.speedY; } }
class ParticlesText { constructor({ text, size }) {
this.app = new PIXI.Application({ width: innerWidth, height: innerHeight });
document.querySelector("#content-canvas").append(this.app.view);
this.text = text;
this.size = size;
this.cols = 500;
this.rows = 200;
this.pSize = 2;
this.particles = [];
this.mouse = {x: 0, y: 0}
this.container = new PIXI.particles.ParticleContainer(50000);
this.app.stage.addChild(this.container);
this.onResize = this.onResize.bind(this); }
init() {
const that = this;
opentype.load(
"https://raw.githack.com/AlainBarrios/Fonts/master/LeagueSpartan-Bold.otf",
function(err, font) {
if (err) {
alert("Font could not be loaded: " + err);
} else {
const canvas = document.createElement("canvas");
// Now let's display it on a canvas with id "canvas"
const ctx = canvas.getContext("2d");
// Construct a Path object containing the letter shapes of the given text.
// The other parameters are x, y and fontSize.
// Note that y is the position of the baseline.
const path = font.getPath(that.text, 0, that.size, that.size);
const width = font.getAdvanceWidth(that.text, that.size);
that.cols = width;
that.rows = that.size;
canvas.width = width;
canvas.height = that.size;
path.fill = "rgba(255,255,255,1)";
// If you just want to draw the text you can also use font.draw(ctx, text, x, y, fontSize).
path.draw(ctx);
that.addObjects(canvas, ctx);
}
}
); }
isEmpty(x, y, ctx) {
const image = ctx.getImageData(x, y, this.pSize, this.pSize);
let emptyCounter = 0;
for (let i = 0; (length = image.data.length), i < length; i += 4) {
if (image.data[i + 3] !== 0) {
continue;
}
emptyCounter++;
}
return emptyCounter === this.pSize * this.pSize; }
addObjects(canvas, ctx) {
this.container.x = this.app.renderer.width / 2 - this.cols / 2;
this.container.y = this.app.renderer.height / 2 - this.rows / 2;
for (let i = 0; i < this.cols; i += 1) {
for (let j = 0; j < this.rows; j += 1) {
const x = i * this.pSize;
const y = j * this.pSize;
if (this.isEmpty(x, y, ctx)) continue;
const p = new Particles(x, y, canvas, this.pSize);
this.particles.push(p);
this.container.addChild(p.sprite);
}
}
this.container.interactive = true;
this.onResize();
window.addEventListener("resize", this.onResize);
this.container.on("mousemove", e => {
this.mouse = e.data.getLocalPosition(this.container);
});
this.animate(); }
onResize() {
const { innerWidth, innerHeight } = window;
const size = [innerWidth, innerHeight];
const ratio = size[0] / size[1];
if (innerWidth / innerHeight >= ratio) {
var w = innerHeight * ratio;
var h = innerHeight;
} else {
var w = innerWidth;
var h = innerWidth / ratio;
}
//this.app.renderer.view.style.width = w + "px";
//this.app.renderer.view.style.height = h + "px"; }
animate() {
this.app.ticker.add(() => {
stats.begin();
this.particles.forEach(p => {
p.update(this.mouse);
});
stats.end();
}); } }
const particles = new ParticlesText({ text: "KILL THE ROBOTS", size:
150 }); particles.init();
I'm trying to create a 2D platformer in JavaScript, I'm getting no errors, but it's not working properly. Pressing the left arrow or the up arrow key does nothing, pressing the right arrow key causes the X-Pos to start incrementing by 1 pixel per tick. I realize this code is long and my 'physics' logic is completely twisted and backwards, but I can't understand anyone's guides on how to add phsyics and velocity and acceleration and gravity and I'm just super confused. I started using JavaScript a week ago and I really have no idea what I'm doing, so any recommendations about any of my code, even things that aren't technically 'problems' would be most appreciated. Thanks guys, you're dope <3
<!DOCTYPE html>
<html>
<meta charset="utf-8"/>
<head>
<title>For The Intended</title>
</head>
<body>
<canvas id="canvas" width="1200" height="800"></canvas>
<!-- <script src="platform.js"></script> -->
<script>
//
//notes
//
//Block collision should come when velocity tries to move, and the x-y position should be player.x + player.velocity to check exactly where the collision will be
//
//
//
//
//
//
//Canvas Settings
var cvs = document.getElementById("canvas");
var ctx = cvs.getContext('2d');
cvs.style = "position:absolute; left: 60%; width: 1200; margin-left: -800px";
//Variables
var platforms = [];
var imgCount = 0
var totalImgCount = 3
var lastUpdate = Date.now();
var keys = {};
//Controls
//Images
var platformImage = new Image();
var playerImage = new Image();
var bgImage = new Image();
platformImage.src = "images/platform.png";
playerImage.src = "images/forward1.png";
playerImage.src = "images/backward1.png";
bgImage.src = "images/bg.png";
platformImage.onload = function() {
imgCount += 1
}
playerImage.onload = function() {
imgCount += 1
}
bgImage.onload = function() {
imgCount += 1
}
//Objects
//Platforms
function Platform(x, y, length, height) {
this.x = x;
this.y = y;
this.length = length;
this.height = height;
platforms.push(this)
}
platform = new Platform(30,30,30,30)
platform = new Platform(40,40,40,40)
//Player
function Player(x, y, gravity, velocityX, velocityY, acceleration, isJumping, direction){
this.x = x
this.y = y
this.gravity = gravity
this.velocityX = velocityX
this.velocityY = velocityY
this.acceleration = acceleration
this.isJumping = isJumping
this.direction = direction
}
player = new Player(200, 600, 0.7, 1, 1, false, "forward")
function jump() {
if (player.isJumping == false) {
player.velocityY = -15;
player.isJumping = true;
}
}
function jumpingHandler() {
if (player.isJumping) {
player.velocityY += player.gravity;
player.y += player.velocityY;
draw();
if (player.y > 600) {
player.y = 600;
player.velocityY = 0;
player.isJumping = false;
}
}
}
*function move(e) {
if(keys[e.keyCode]) {
if(keys[38]) {
jump();
}
if(keys[37]) {
if (player.x > 150){
if (player.acceleration > -5){
player.acceleration = Math.floor(player.acceleration);
player.acceleratoion -= 1
player.acceleration = Math.floor(player.acceleration);
}
player.direction = "backward"
playerImage.src = "images/backward1.png";
}
}
if(keys[39]) {
if (player.x < 1050){
console.log("hey")
if (player.acceleration < 5){
console.log("hey2")
player.acceleration = Math.floor(player.acceleration);
player.acceleration += 1
player.acceleration = Math.floor(player.acceleration);
}
player.direction = "forward"
playerImage.src = "images/forward1.png";
}
}
}
}*
//Game Requirements
function draw() {
if (imgCount == 3) {
for (var i = 0; i < platforms.length; i++) {
ctx.drawImage(platformImage, platforms[i].x, platforms[i].y)
//Do something
}
ctx.drawImage(playerImage, player.x, player.y)
}
}
setInterval(update, 33);
function updateKeys() {
window.onkeyup = function(e) { keys[e.keyCode] = false; move(e)}
window.onkeydown = function(e) { keys[e.keyCode] = true; move(e)}
}
function update() {
if (player.acceleration > 1) {player.acceleration -= 0.2}
if (player.acceleration < 1) {player.acceleration += 0.2}
player.acceleration = Math.floor(player.acceleration);
player.velocityX = player.acceleration
player.x += (player.velocityX)
console.log("Velocity" + player.velocityX)
console.log("Acceleration" + player.acceleration)
console.log("X-Pos" + player.x)
jumpingHandler()
updateKeys()
draw()
}
</script>
</body>
</html>
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>
The Problem
I am creating a game that involves dodging projectiles. The player is in control of an image of a ship and I dont want the ship to move exactly together as this looks very unrealistic.
The Question
Is there a way to control how fast the image moves, how can i slow the movemnt of the image down?
The Code
var game = create_game();
game.init();
//music
var snd = new Audio("Menu.mp3");
snd.loop = true;
snd.play();
document.getElementById('mute').addEventListener('click', function (evt) {
if ( snd.muted ) {
snd.muted = false
evt.target.innerHTML = 'mute'
}
else {
snd.muted = true
evt.target.innerHTML = 'unmute'
}
})
function create_game() {
debugger;
var level = 1;
var projectiles_per_level = 1;
var min_speed_per_level = 1;
var max_speed_per_level = 2;
var last_projectile_time = 0;
var next_projectile_time = 0;
var width = 600;
var height = 500;
var delay = 1000;
var item_width = 30;
var item_height = 30;
var total_projectiles = 0;
var projectile_img = new Image();
var projectile_w = 30;
var projectile_h = 30;
var player_img = new Image();
var c, ctx;
var projectiles = [];
var player = {
x: 200,
y: 400,
score: 0
};
function init() {
projectile_img.src = "projectile.png";
player_img.src = "player.png";
level = 1;
total_projectiles = 0;
projectiles = [];
c = document.getElementById("c");
ctx = c.getContext("2d");
ctx.fillStyle = "#ff6600";
ctx.fillRect(0, 0, 500, 600);
c.addEventListener("mousemove", function (e) {
//moving over the canvas.
var bounding_box = c.getBoundingClientRect();
player.x = (e.clientX - bounding_box.left) * (c.width / bounding_box.width) - player_img.width / 2;
}, false);
setupProjectiles();
requestAnimationFrame(tick);
}
function setupProjectiles() {
var max_projectiles = level * projectiles_per_level;
while (projectiles.length < max_projectiles) {
initProjectile(projectiles.length);
}
}
function initProjectile(index) {
var max_speed = max_speed_per_level * level;
var min_speed = min_speed_per_level * level;
projectiles[index] = {
x: Math.round(Math.random() * (width - 2 * projectile_w)) + projectile_w,
y: -projectile_h,
v: Math.round(Math.random() * (max_speed - min_speed)) + min_speed,
delay: Date.now() + Math.random() * delay
}
total_projectiles++;
}
function collision(projectile) {
if (projectile.y + projectile_img.height < player.y + 74) {
return false;
}
if (projectile.y > player.y + 74) {
return false;
}
if (projectile.x + projectile_img.width < player.x + 177) {
return false;
}
if (projectile.x > player.x + 177) {
return false;
}
return true;
}
function maybeIncreaseDifficulty() {
level = Math.max(1, Math.ceil(player.score / 10));
setupProjectiles();
}
function tick() {
var i;
var projectile;
var dateNow = Date.now();
c.width = c.width;
for (i = 0; i < projectiles.length; i++) {
projectile = projectiles[i];
if (dateNow > projectile.delay) {
projectile.y += projectile.v;
if (collision(projectile)) {
initProjectile(i);
player.score++;
} else if (projectile.y > height) {
initProjectile(i);
} else {
ctx.drawImage(projectile_img, projectile.x, projectile.y);
}
}
}
ctx.font = "bold 24px sans-serif";
ctx.fillStyle = "#ff6600";
ctx.fillText(player.score, c.width - 50, 50);
ctx.fillText("Level: " + level, 20, 50);
ctx.drawImage(player_img, player.x, player.y);
maybeIncreaseDifficulty();
requestAnimationFrame(tick);
}
return {
init: init
};
}
https://jsfiddle.net/a6nmy804/4/ (Broken)
Throttle the player's movement using a "timeout" countdown
Create a global var playerFreezeCountdown=0.
In mousemove change player.x only if playerFreezeCountdown<=0.
If playerFreezeCountdown>0 you don't change player.x.
If playerFreezeCountdown<=0 you both change player.x and also set playerFreezeCountdown to a desired "tick timeout" value: playerFreezeCountdown=5. This timeout will cause prevent the player from moving their ship until 5 ticks have passed.
In tick, always decrement playerFreezeCountdown--. This will indirectly allow a change to player.x after when playerFreezeCountdown is decremented to zero or below zero.
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>