Html5 canvas how to generate multiple images - javascript

Im making a simple zombie game in html5 canvas and wanted to know how to create a zombie every x seconds in a random place? so far i have
var zombies = new Array();
function SummonZombies (){
TotalZombies++;
zombies[TotalZombies] = new Image();
zombies[TotalZombies].src = 'images/monster.png';
ctx.drawImage(zombies[TotalZombies], zombie_x, zombie_y);
}
Only one zombie is being created with this? how would i get it to generate more.

First of all, where are you declaring the variable TotalZombies?
Try something like this :
var zombies = new Array();
for (var i = 0; i < 100; i++) {
var zombie = new Image();
zombie.src = 'images/monster.png';
ctx.drawImage(zombie, Math.floor((Math.random()*100)+1), Math.floor((Math.random()*100)+1));
zombies.push(zombie);
}
This will create 100 zombies, with random x and y positions between 1 and 100. It will add each zombie to the zombies array after they have been instantiated.

You should iterate through zombies array, and invoke drawImage() on everyone.
Extra tip: remember to change x and y after all iteration.

You must separate a Zombi from your zombies :
create a class that will describe what a Zombi is, and only after you will define a collection of such lovely guys and girls :
// This Class defines what a Zombi is.
function Zombi(x,y) {
this.x = x;
this.y = y;
}
var ZombiImage = new Image();
ZombiImage.src = "images/monster.png";
// image of a zombi is shared amongst all zombies, so it is
// defined on the prototype
Zombi.prototype.image = ZombiImage;
// draw the zombi on provided context
Zombi.prototype.draw = function(ctx) {
ctx.drawImage(this.image, this.x, this.y);
}
Now for the collection :
// This class defines a collection of Zombies.
function Zombies() {
this.zombies = [];
}
// summons a zombi at a random place. returns the summoned zombi.
myZombies.prototype.summon() {
var randX = Math.random()*100;
var randY = Math.random()*100;
return this.summonAt(randX, randY);
}
// summons a zombi at x,y. returns the summoned zombi.
myZombies.prototype.summonAt = function (x,y) {
var newZombi = new Zombi(x,y);
this.zombies.push();
return newZombi;
}
// draws all zombies on provided context.
myZombies.prototype.drawAll = function (ctx) {
var i=0;
var __zombies = this.zombies;
for (;i<__zombies.length; i++) {
__zombies[i].draw(ctx);
}
}
// collection of all zombies for your game.
var zombies = new Zombies();
// here you can call zombies.summon(); or zombies.drawAll();
// and even zombies.summonAt(x,y);
In fact the code above is simplified : you must handle the onload event of the image to start the game only after the image was loaded.
But you should get the idea : separate the issues (handle ONE zombi vs a collection of zombies) will get you faster to your goal.
With this -simple- design, you'll be able to easily add-up behaviour to your zombies.
Just one more example in which i will add the seekBrain and walk behaviour :
// This Class defines what a Zombi is.
function Zombi(x,y) {
this.x = x;
this.y = y;
this.dirX = 0 ; // direction X
this.dirY = 0; // direction Y
this.speed = 0.1; // common speed for all zombies
}
// have the zombi seek the brain located at (x,y)
Zombi.prototype.seekBrain = function (x,y) {
this.dirX = (x - this.x );
this.dirY = (y - this.y );
// normalize direction
var norm = Math.sqrt( this.dirX*this.dirX + this.dirY*this.dirY );
this.dirX/=norm;
this.dirY/=norm;
}
// Have the zombi walk in its current direction
Zombi.prototype.walk = function() {
this.x += this.dirX * this.speed;
this.y += this.dirY * this.speed;
}
// image and draw remains the same
And now you might want for your collection :
// makes all zombies walk.
Zombies.walkAll = function() {
var i=0;
var __zombies = this.zombies;
for (;i<__zombies.length; i++) {
__zombies[i].walk();
}
}
// constructor, summon, summonAt, and drawAll remains the same.
So to summon a zombi at random place every xxx ms, do something like :
// summons a zombi at a random place every 2 seconds (==2000 ms)
setTimeInterval(2000, function() { zombies.summon(); } );
now, if hero.x and hero.y are what we guess, you can do :
// Have a random zombi hunt for hero's brain every 2 seconds
setTimeInterval(2000, function() {
var which = Math.floor(zombies.zombies.length * Math.random());
zombies.zombies[which].seekBrain(hero.x, hero.y);
} );
provided you call to zombies.walkAll(); and zombies.drawAll(); on a regular basis, you've got the start of a game ! (i love so much zombies :-) )

Related

Multiple canvases in p5 [duplicate]

This question already has answers here:
Can I create multiple canvas elements on same page using p5js
(2 answers)
Closed last month.
I'm writing code for a neuro evolution project that should learn to play the game snake. I'm using js and p5.js. I want to have 10 game canvases parallel and each game should individually play the game.
What I'm struggling with is the multiple canvases part. When I use this:
let screens = 10;
for(let k = 0; k < screens ;k++){
var t = function( p ) {
p.setup = function() {
p.createCanvas(200, 200);
let x = Math.random()*100;
console.log(x);
snake = new Snake(x,50);
food = new Food();
};
p.draw = function() {
p.background(0);
snake.placeSnake(p);
snake.think(p);
if(counter % 50 == 0){
snake.moveSnake();
}
if(snake.offScreen(p)){
//kill snake
}
}
food.placeFood(p);
if(food.hitsFood(snakes)){
food.generateFood(p);
}
counter++;
};
};
var myp5 = new p5(t);
}
All the screens are identical to the last one spawned. How would I do different so that each canvas is unique?
thanks in advance.
This might be a variable scope issue (untested, as I don't have all your code).
Try adding let snake, food, counter = 0; inside the var t= function(p){} defintion, like this:
let screens = 10;
for(let k = 0; k < screens ;k++){
var t = function( p ) {
let snake, food, counter = 0; // declare here as shared between setup and draw
p.setup = function() {
p.createCanvas(200, 200);
let x = Math.random()*100;
console.log(x);
snake = new Snake(x,50);
food = new Food();
};
p.draw = function() {
p.background(0);
snake.placeSnake(p);
snake.think(p);
if(counter % 50 == 0){
snake.moveSnake();
}
if(snake.offScreen(p)){
//kill snake
}
//}
food.placeFood(p);
if(food.hitsFood(snakes)){
food.generateFood(p);
}
counter++;
};
};
var myp5 = new p5(t);
}
The explanation for this would be that, if you don't declare the variables, they are implicitly declared in the global scope, and so every snake points to the same snake (and likewise with food). The canvases are unique, but the snake and food variables for each one only point to the last snake or food created.
(Depending on the context, it might be worth preventing this kind of problem from occurring by using strict mode, which does not allow undeclared variables).

How to realistically simulate car steering physics

I'm making a car simulation in Javascript and I am using this website to help me with the physics: http://www.asawicki.info/Mirror/Car%20Physics%20for%20Games/Car%20Physics%20for%20Games.html
The current progress of the simulation can be seen on my website:
https://cloudynet.tk/projects/car-sim/code.html
The problem I have is with the car steering physics. I have managed to get the low-speed steering to work correctly but the high-speed steering (where lateral forces are introduced) is very hard to get right. I understand how slip angles influence the lateral force but in my simulation, it is not working very well. I'm wondering if the implementation of the lateral force is correct or is there something wrong with the code? Also, I don't quite understand if the longitudinal force and the lateral force affects a single velocity vector or is separated into two "directional" vectors.
Here's my current physics function (whole code can be seen on the website):
applyPhysics() {
// Get car direction vector
let direction = new Vector(1, 0);
direction = Vector.rotate(direction, this.carAngle);
// LONGITUDINAL FORCES
// Traction forces
let tractionForce = direction.copy(); // Traction force (engine power)
if (this.engineForce) {
tractionForce.mult(this.engineForce);
}
else if (this.brakingForce) {
tractionForce.mult(-this.brakingForce);
}
// Frictional forces
let dragForce = this.velocity.copy(); // Air resistance force
dragForce.mult(this.velocity.getMag())
dragForce.mult(-this.drag);
let rollingResistanceForce = this.velocity.copy(); // Rolling resistance force (friction with ground)
rollingResistanceForce.mult(-this.rrDrag);
let netLongitudinalForce = tractionForce.copy(); // Total longitudinal force
netLongitudinalForce.add(dragForce)
netLongitudinalForce.add(rollingResistanceForce);
// Calculate acceleration
this.acceleration = netLongitudinalForce.copy();
this.acceleration.div(this.mass);
if (this.acceleration.getMag() < 0.001)
this.acceleration = new Vector();
// Calculate velocity
let accelerationDelta = this.acceleration.copy();
accelerationDelta.mult(dt);
this.velocity.add(accelerationDelta);
this.velDir = this.velocity.getDir();
this.sideslipAngle = this.carAngle - this.velDir; // Calculate side slip angle
if (this.speed > 20) { // High speed-turning
// LATERAL FORCES
let peakSlipAngle = 5;
// Calculate slip angle for back wheel
var c = this.wheels.baseline/2;
var omegaC = this.angularVelocity*c;
var longV = Math.cos(this.carAngle) * this.velocity.getMag();
var latV = Math.sin(this.carAngle) * this.velocity.getMag();
this.wheels.back.slipAngle = Math.atan(((latV - omegaC)/Math.abs(longV)) || 0);
var backSlipDeg = deg(this.wheels.back.slipAngle)
this.wheels.back.lateralForce = 5000*Math.sign(this.wheels.back.slipAngle);
if (backSlipDeg < peakSlipAngle && backSlipDeg > -peakSlipAngle) {
this.wheels.back.lateralForce = 5000*backSlipDeg/peakSlipAngle;
} else {
this.wheels.back.lateralForce = 5000*(1-((Math.abs(backSlipDeg)-peakSlipAngle)/500))*Math.sign(this.wheels.back.slipAngle);
}
// Calculate slip angle for front wheel
var b = this.wheels.baseline/2;
var omegaB = this.angularVelocity*b;
var longV = Math.cos(this.wheels.front.slipAngle) * this.velocity.getMag();
var latV = Math.sin(this.wheels.front.slipAngle) * this.velocity.getMag();
this.wheels.front.slipAngle = Math.atan((((latV - omegaB)/Math.abs(longV)) || 0)-this.steeringAngle*Math.sign(longV));
var frontSlipDeg = deg(this.wheels.front.slipAngle);
this.wheels.front.lateralForce = 5000*Math.sign(this.wheels.front.slipAngle);
if (frontSlipDeg < peakSlipAngle && frontSlipDeg > -peakSlipAngle) {
this.wheels.front.lateralForce = 5000*frontSlipDeg/peakSlipAngle;
} else {
this.wheels.front.lateralForce = 5000*(1-((Math.abs(frontSlipDeg)-peakSlipAngle)/500))*Math.sign(this.wheels.front.slipAngle);
}
// Calculate cornering force
this.corneringForce = this.wheels.back.lateralForce + Math.cos(this.steeringAngle) * this.wheels.front.lateralForce;
// Calculate centripetal force
this.centripetalForce = this.mass * (this.velocity.getMag() ** 2) / this.wheels.baseline/Math.sin(this.steeringAngle);
var lateralDirection = new Vector(0, -1);
lateralDirection = Vector.rotate(lateralDirection, this.carAngle);
let lateralForce = lateralDirection.copy();
lateralForce.mult(this.corneringForce);
this.latAcceleration = lateralForce.copy();
this.latAcceleration.div(this.mass);
if (this.latAcceleration.getMag() < 0.001)
this.latAcceleration = new Vector();
let latAccelerationDelta = this.latAcceleration.copy();
latAccelerationDelta.mult(dt);
this.latVelocity.add(latAccelerationDelta);
// Calculate position
let latVelocityDelta = this.latVelocity.copy();
latVelocityDelta.mult(dt);
this.pos.add(latVelocityDelta);
} else {
this.velocity = Vector.rotate(this.velocity, this.carAngle - this.velDir); // Correct velocity based on car orientation
}
// Calculate position
let velocityDelta = this.velocity.copy();
velocityDelta.mult(dt);
this.pos.add(velocityDelta);
// Calculate speed
this.speed = this.velocity.getMag();
}
I believe the problem is with the lines regarding the slip angles. I would look over the script carefully or ctrl f to find where you typed Math.sign rather than Math.sin(). I'm not sure if this is the problem but it's something I noticed looking over your code. I'm working on a basic drifting game with javascript and it requires a lot of the same physics as your project.
Hopefully, I was able to help

Javascript Garbage Collector not clearing objects

I've spent a few days searching through SO, googling, and reading articles, and I cannot for the life of me figure out how to avoid memory leaking. I wrote a quick demo to see what was going on here: https://codepen.io/dotjersh/pen/WMVwWx.
var SelectMap = function(canvas,onComplete){
var size = [3,3];
var s = 39;//sidelength
var p = 1; //padding
var color = ['#3D5AFE','#F57F17']
var ctx = canvas.getContext("2d");
var cursor = null;
canvas.width = size[0] * (s + p);
canvas.height = size[1] * (s + p);
canvas.addEventListener('mousemove',hover);
canvas.addEventListener('click',click);
render();
function click(e){
onComplete(Math.floor(cursor.x/(s + p)),Math.floor(cursor.y/(s + p)));
destroy();
}
function hover(e){
cursor = {x:Math.abs(e.clientX - canvas.offsetLeft),y:Math.abs(e.clientY - canvas.offsetTop)}
render();
}
function render(){
ctx.clearRect(0,0,canvas.width,canvas.height)
for(var x = 0; x < size[0]; x++){
for(var y = 0; y < size[1]; y++){
ctx.fillStyle = color[0];
if(cursor){
var xPoint = ((x*s) + (x*p));
var yPoint = ((y*s) + (y*p));
if(Math.floor(cursor.x/(s + p)) == x && Math.floor(cursor.y/(s + p)) == y){
ctx.fillStyle = color[1];
}
}
ctx.fillRect((x*s) + (x*p),(y*s) + (y*p),s,s);
}
}
}
function destroy(){
canvas.removeEventListener('mousemove',hover);
canvas.removeEventListener('click',click);
ctx.clearRect(0,0,canvas.width,canvas.height);
}
return{
destroy: destroy,
}
}
function go(){
var bob = new SelectMap(document.getElementById('canvas'),function(x,y){
alert(x + "," + y);
bob = null;
});
}
<canvas id="canvas"></canvas>
The intended result is that once you open the page, the base amount of Memory is stored. You can run go(), and see the memory increase. Once you click something, the object should remove itself from the global scope. On chrome I run the garbage collector, but afterward there is no change in the amount of memory used. It should return to the original memory should it not?
Some of the things I've done:
- Made sure all the events are removed
- set object to null
- cleared out the canvas
I've been trying to understand this for days, any help would be appreciated.
Credit to #JonasW.
He mentioned that garbage collectors only will collect data if there is data to collect, and they won't get kilobytes of data. I modified my codepen to created 25MB of useless data and it ended up working. The codepen saved ended up created kilobytes of data each type go() was run and then removed. Which was the intention, to get rid of that 25MB every time it ran.
Thanks!

How do I reuse objects in an array for a particle system in JavaScript/jQuery?

I'm in the process of building an entity system for a canvas game. This started from a simple particle emitter/updater which I am altering to accommodate a multi-particle/entity generator. Whilst I am usually ok with JavaScript/jQuery I am running into the limits of my experience as it concerns arrays and would gratefully accept any help on the following:
When I need a new particle/entity my current system calls a function to push an object into an array which contains variables for the entity updates.
Then the update function runs a for loop over the array, checking on the type variable to update the particle (position/colour/etc...). Previously I would then [array.splice] the particle, based on some condition. When I needed further particles/entities I would then push new particles.
What I would like to achieve here is:
In the makeParticle function, check over the particle array for any "dead" particles and if any are available reuse them, or push a new particle if not I have created a particleAlive var as a flag for this purpose.
var particles = [];
var playing = false;
function mousePressed(event) {
playing = !playing;
}
if(playing) {
makeParticle(1, 200, 200, 10, "blueFlame");
makeParticle(1, 300, 200, 10, "redFlame");
}
function makeParticle(numParticles, xPos, yPos, pRadius, pType) {
var i;
for (i = 0; i < numParticles; i++) {
var p = {
type : pType,
x : xPos,
y : yPos,
xVel : random(-0.5, 0.5),
yVel : random(-1, -3),
particleAlive : true,
particleRender : true,
size : pRadius
}; // close var P
particles.push(p);
// instead of pushing fresh particles all the time I would like the function, here, to check for free objects in the array
} // close for loop
} // close function makeParticle
function runtime() {
for(var i=0; i<particles.length; i++) {
var p = particles[i];
var thisType = p.type;
switch (thisType) {
case "blueFlame":
c.fillStyle = rgb(100,100,255);
c.fillCircle(p.x,p.y,p.size);
p.x += p.xVel;
p.y += p.yVel;
p.size*=0.9;
if (particles.size < 0.5) {
particleAlive = false;
particleRender = false;
} // close if
break;
case "redFlame":
c.fillStyle = rgb(255,100,100);
c.fillCircle(p.x,p.y,p.size);
p.x -= p.xVel;
p.y -= p.yVel;
p.size*=0.95;
if (particles.size < 0.5) {
particleAlive = false;
particleRender = false;
} // close if
break;
} // close switch
} // close function runtime
I've found previous answers to relate questions, but I've been unable to get it working within the makeParticle function, like how to assign the attributes of p to particle[j]:
var particleUseOldOrNew = function() {
for (var j = 0, len = particles.length; j < len; j++) {
if (particles[j].particleAlive === false)
// particles[j] = p;
return particle[j];
}
return null; // No dead particles found, create new "particles.push(p);" perhaps?
}
My personal opinion on the matter is that if you are making a new particle, it should be a new object, not a "re-using" of an old one with properties changed. Each new object should have a unique identifier, so if you need to track them (for development purposes, debugging, or later re-use), it is easy to do. Or at least keep a counter of the number of times you've re-used a particle object to represent a "new" particle! Though I guess if you've found that "re-using" improves performance (have you?), that's the way to go.
Anyway, enough pontificating, here is how I would do what you're asking (I assume speed is your main concern, so I did this with only native JS):
var particles = [];
//Function to create brand spanking new particle
function makeNewParticle(xPos, yPos, pRadius, pType){
return {
type : pType,
x : xPos,
y : yPos,
xVel : random(-0.5, 0.5),
yVel : random(-1, -3),
particleAlive : true,
particleRender : true,
size : pRadius
};
};
//Function to change the properties of an old particle to make a psuedo-new particle (seriously, why do you want to do this?)
function changeExistingParticle(existing, xPos, yPos, pRadius, pType){
existing.x = xPos;
existing.y = yPos;
existing.size = pRadius;
existing.type = pType;
return existing;
};
//Figure out the keys of dead particles in the particles[] array
function getDeadParticleKeys() {
var keys = [];
for(var p = 0; P < particles.length; p++) {
if (!particles[p].particleAlive) {
keys.push(p);
}
}
};
function makeParticle(numParticles, xPos, yPos, pRadius, pType) {
var d, i, deadParticles;
//Grab the "dead" particle keys
deadParticleKeys = getDeadParticleKeys();
numParticles -= deadParticleKeys.length;
//Replace each dead particle with a "live" one at a specified key
for (d = 0; d < deadParticleKeys.length; d++) {
particles[ deadParticleKeys[d] ] = changeExistingParticle(particles[ deadParticleKeys[d] ], xPos, yPos, pRadius, pType)
}
//If we had more particles than there were dead spaces available, add to the array
for (i = 0; i < numParticles; i++) {
particles.push( makeNewParticle(xPos, yPos, pRadius, pType) );
}
};
Now, here's how I recommend doing it: abandon the idea or "re-using" particles, make a separate constructor for each particle (will help immensely if you add methods to your particles in the future), and just scrap dead particles every time one is added:
//Make a constructor for a particle
var Particle = function(props){
if (typeof props === 'function') {
props = props();
}
this.type = props.type;
this.x = props.x;
this.y = props.y;
this.size = props.size;
};
Paticle.prototype.particleAlive = true;
Paticle.prototype.particleRender = true;
//Global particles list
var particles = [];
//Remove all dead element from a ParticleList
particles.clean = function(){
var p, keys;
for (p = this.length; p >= 0; p--) {
if (!p.particleAlive) {
this.splice(p, 1);
}
}
};
//Method for adding x amount of new particles - if num parameter isn't provided, just assume it to be 1
particles.add = function(props, num){
//First, clean out all the garbage!
this.clean();
//Now, append new particles to the end
var n, limit = (num && typeof num === 'number') ? num : 1;
for (n = 0; n < limit; n++){
particles.push( new Particle(props) );
}
};
//A couple examples
particles.add({ //Add a single blueFlame
type: "blueFlame",
size: 10,
x: 200,
y: 200
});
particles.add({ //Add 4 redFlames
type: "redFlame",
size: 10,
x: 300,
y: 200
}, 4);
particles.add(function(){//Add 4 greenFlames, with randomized XY cooridinates
this.x = Math.round(Math.random() * 1000);
this.y = Math.round(Math.random() * 1000);
this.size = 20;
this.type = "greenFlame";
}, 4);
Way less code to manage. I'm not sure which way is faster, but I'd bet the speed difference is negligible. Of course, you could check for yourself by making a quick jsPerf.

Garbage collection pauses; Javascript

I've been working on creating a basic 2D tiled game and have been unable to pinpoint the source of noticeable pauses lasting ~100-200ms every second or two, but it seems like GC pauses as when I profiled my app, each game loop is taking around 4ms with a target of 60fps, which means it is running well within the required limit (16ms).
As far as I am aware, I have moved my object variables outside the functions that use them so they never go out of scope and therefore should not be collected, but I am still getting pauses.
Each game loop, the tiles are simply moved 1px to the left (to show smoothness of game frames), and apart from that, all that is called is this draw map function: (NOTE, these functions are defined as part of my engine object at startup so is this true that these functions are not created then collected each time they are called?).
engine.map.draw = function () {
engine.mapDrawMapX = 0;
engine.mapDrawMapY = 0;
// Just draw tiles within screen (and 1 extra on both x and y boundaries)
for (engine.mapDrawJ = -1; engine.mapDrawJ <= engine.screen.tilesY; engine.mapDrawJ++) {
for (engine.mapDrawI = -1; engine.mapDrawI <= engine.screen.tilesX; engine.mapDrawI++) {
//calculate map location (viewport)
engine.mapDrawMapX = engine.mapDrawI + engine.viewport.x;
engine.mapDrawMapY = engine.mapDrawJ + engine.viewport.y;
engine.mapDrawTile = (engine.currentMap[engine.mapDrawMapY] && engine.currentMap[engine.mapDrawMapY][engine.mapDrawMapX]) ? engine.currentMap[engine.mapDrawMapY][engine.mapDrawMapX] : '';
engine.tile.draw(engine.mapDrawI, engine.mapDrawJ, engine.mapDrawTile);
}
}
};
And the method called to draw each tile is:
engine.tile.drawTile = new Image(0,0);
engine.tile.draw = function (x, y, tile) {
if ('' != tile) {
engine.tile.drawTile = engine.tile.retrieve(tile); //this returns an Image() object
engine.context.drawImage(engine.tile.drawTile,
x * TILE_WIDTH + engine.viewport.offsetX,
y * TILE_HEIGHT + engine.viewport.offsetY,
TILE_WIDTH, TILE_HEIGHT);
} else {
engine.context.clearRect(x * TILE_WIDTH, y * TILE_HEIGHT, TILE_WIDTH, TILE_HEIGHT);
}
};
As per request, here are the store and retrieve functions:
engine.tile.store = function (id, img) {
var newID = engine.tile.images.length;
var tile = [id, new Image()];
tile[1] = img;
engine.tile.images[newID] = tile; // store
};
engine.tile.retrieveI;
engine.tile.retrieve = function (id) {
//var len = engine.tile.images.length;
for (engine.tile.retrieveI = 0; engine.tile.retrieveI < engine.tile.images.length; engine.tile.retrieveI++) {
if (engine.tile.images[engine.tile.retrieveI][0] == id) {
return engine.tile.images[engine.tile.retrieveI][1]; // return image
}
}
//return null;
};

Categories

Resources