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;
};
Related
I'm trying to optimize the layer order of paths in Illustrator so that when sent to a laser cutter, the end of one path is close to the start of the next path reducing the travel time of the laser between each cut.
I've come up with the following code, which works, but could be further optimized considering length of lines, or through an annealing process. I'm posting it here in case anyone else is Googling 'Laser cutting optimization' and doesn't want to write their own code. Also if anyone can suggest improvements to the below code, I'd love to hear them.
// For this script to work, all paths to be optimised need to be on layer 0.
// Create a new empty layer in position 1 in the layer heirarchy.
// Run the script, all paths will move from layer 0 to layer 1 in an optimized order.
// Further optimisation possible with 'Annealing', but this will be a good first run optimization.
// Load into Visual Studio Code, follow steps on this website
// https://medium.com/#jtnimoy/illustrator-scripting-in-visual-studio-code-cdcf4b97365d
// to get setup, then run code when linked to Illustrator.
function test() {
if (!app.documents.length) {
alert("You must have a document open.");
return;
}
var docRef = app.activeDocument;
function endToStartDistance(endPath, startPath) {
var endPoint = endPath.pathPoints[endPath.pathPoints.length - 1].anchor;
var startPoint = startPath.pathPoints[0].anchor;
var dx = (endPoint[0] - startPoint[0]);
var dy = (endPoint[1] - startPoint[1]);
var dist = Math.pow((Math.pow(dx, 2) + Math.pow(dy, 2)), 0.5);
return dist;
}
function Optimize(items) {
var lastPath, closest, minDist, delIndex, curItem, tempItems = [];
var topLayer = app.activeDocument.layers[0];
var newLayer = app.activeDocument.layers[1];
for (var x = 1, len = items.length; x < len; x++) {
tempItems.push(items[x]);
}
lastPath = items[0];
lastPath.move(newLayer, ElementPlacement.PLACEATBEGINNING);
while (tempItems.length) {
closest = tempItems[0];
minDist = endToStartDistance(lastPath, closest);
delIndex = 0;
for (var y = 1, len = tempItems.length; y < len; y++) {
curItem = tempItems[y];
if (endToStartDistance(lastPath, curItem) < minDist) {
closest = curItem;
minDist = endToStartDistance(lastPath, closest);
delIndex = y;
}
}
$.writeln(minDist);
//closest.zOrder(ZOrderMethod.BRINGTOFRONT);
closest.move(newLayer, ElementPlacement.PLACEATBEGINNING);
lastPath = closest;
tempItems.splice(delIndex, 1);
}
}
var allPaths = [];
for (var i = 0; i < documents[0].pathItems.length; i++) {
allPaths.push(documents[0].pathItems[i]);
//$.writeln(documents[0].pathItems[i].pathPoints[0].anchor[0])
}
Optimize(allPaths);
}
test();
Version 2 of the above code, some changes include the ability to reverse paths if this results in a reduced distance for the cutting head to move between paths, and added comments to make the code easier to read.
// Create a new empty layer in position 1 in the layer heirarchy.
// Run the script, all paths will move from their current layer to layer 1 in an optimized order.
// Further optimisation possible with 'Annealing', but this will be a good first run optimization.
// Load into Visual Studio Code, follow steps on this website
// https://medium.com/#jtnimoy/illustrator-scripting-in-visual-studio-code-cdcf4b97365d
// to get setup, then run code when linked to Illustrator.aa
function main() {
if (!app.documents.length) {
alert("You must have a document open.");
return;
}
var docRef = app.activeDocument;
// The below function gets the distance between the end of the endPath vector object
// and the start of the startPath vector object.
function endToStartDistance(endPath, startPath) {
var endPoint = endPath.pathPoints[endPath.pathPoints.length - 1].anchor;
var startPoint = startPath.pathPoints[0].anchor;
var dx = (endPoint[0] - startPoint[0]);
var dy = (endPoint[1] - startPoint[1]);
var dist = Math.pow((Math.pow(dx, 2) + Math.pow(dy, 2)), 0.5);
return dist;
}
// The below function gets the distance between the end of the endPath vector object
// and the end of the startPath vector object.
function endToEndDistance(endPath, startPath) {
var endPoint = endPath.pathPoints[endPath.pathPoints.length - 1].anchor;
var startPoint = startPath.pathPoints[startPath.pathPoints.length - 1].anchor;
var dx = (endPoint[0] - startPoint[0]);
var dy = (endPoint[1] - startPoint[1]);
var dist = Math.pow((Math.pow(dx, 2) + Math.pow(dy, 2)), 0.5);
return dist;
}
// The below function iterates over the supplied list of tempItems (path objects) and checks the distance between
// the end of path objects and the start/end of all other path objects, ordering the objects in the layer heirarchy
// so that there is the shortest distance between the end of one path and the start of the next.
// The function can reverse the direciton of a path if this results in a smaller distance to the next object.
function Optimize(tempItems) {
var lastPath, closest, minDist, delIndex, curItem;
var newLayer = app.activeDocument.layers[1]; // There needs to be an empty layer in position 2 in the layer heirarchy
// This is where the path objects are moved as they are sorted.
lastPath = tempItems[0]; // Arbitrarily take the first item in the list of supplied items
tempItems.splice(0, 1); // Remove the first item from the list of items to be iterated over
lastPath.move(newLayer, ElementPlacement.PLACEATBEGINNING); // Move the first item to the first position in the new layer
while (tempItems.length) { // Loop over all supplied items while the length of this array is not 0.
// Items are removed from the list once sorted.
closest = tempItems[0]; // Start by checking the distance to the first item in the list
minDist = Math.min(endToStartDistance(lastPath, closest), endToEndDistance(lastPath, closest));
// Find the smallest of the distances between the end of the previous path item
// and the start / end of this next item.
delIndex = 0; // The delIndex is the index to be removed from the tempItems list after iterating through
// the entire list.
for (var y = 1, len = tempItems.length; y < len; y++) {
// Iterate over all items in the list, starting at item 1 (item 0 already being used above)
curItem = tempItems[y];
if (endToStartDistance(lastPath, curItem) < minDist || endToEndDistance(lastPath, curItem) < minDist) {
// If either the end / start distance to the current item is smaller than the previously
// measured minDistance, then the current path item becomes the new smallest entry
closest = curItem;
minDist = Math.min(endToStartDistance(lastPath, closest), endToEndDistance(lastPath, closest));
// The new minDistace is set
delIndex = y; // And the item is marked for removal from the list at the end of the loop.
}
}
if (endToEndDistance(lastPath, closest) < endToStartDistance(lastPath, closest)) {
reversePaths(closest); // If the smallest distance is yielded from the end of the previous path
// To the end of the next path, reverse the next path so that the
// end-to-start distance between paths is minimised.
}
closest.move(newLayer, ElementPlacement.PLACEATBEGINNING); // Move the closest path item to the beginning of the new layer
lastPath = closest; // The moved path item becomes the next item in the chain, and is stored as the previous item
// (lastPath) for when the loop iterates again.
tempItems.splice(delIndex, 1); // Remove the item identified as closest in the previous loop from the list of
// items to iterate over. When there are no items left in the list
// The loop ends.
}
}
function reversePaths(theItems) { // This code taken / adapted from https://gist.github.com/Grsmto/bfe1541957a0bb17972d
if (theItems.typename == "PathItem" && !theItems.locked && !theItems.parent.locked && !theItems.layer.locked) {
pathLen = theItems.pathPoints.length;
for (k = 0; k < pathLen / 2; k++) {
h = pathLen - k - 1;
HintenAnchor = theItems.pathPoints[h].anchor;
HintenLeft = theItems.pathPoints[h].leftDirection;
HintenType = theItems.pathPoints[h].pointType;
HintenRight = theItems.pathPoints[h].rightDirection;
theItems.pathPoints[h].anchor = theItems.pathPoints[k].anchor;
theItems.pathPoints[h].leftDirection = theItems.pathPoints[k].rightDirection;
theItems.pathPoints[h].pointType = theItems.pathPoints[k].pointType;
theItems.pathPoints[h].rightDirection = theItems.pathPoints[k].leftDirection;
theItems.pathPoints[k].anchor = HintenAnchor;
theItems.pathPoints[k].leftDirection = HintenRight;
theItems.pathPoints[k].pointType = HintenType;
theItems.pathPoints[k].rightDirection = HintenLeft;
}
}
}
var allPaths = []; // Grab every line in the document
for (var i = 0; i < documents[0].pathItems.length; i++) {
allPaths.push(documents[0].pathItems[i]);
// This could be better changed to the selected objects, or to filter only objects below a certain
// stroke weight so that raster paths are not affected, but cut paths are.
}
Optimize(allPaths); // Feed all paths in the document into the optimize function.
}
main(); // Call the main function, executing the above code.
I am working on an io game similar to agar.io and slither.io (using node.js and socket.io) where there are up to 50 players and around 300 foods in 2d space on the map at a time. Players and food are both circular. Every frame, the server needs to check whether a player has collided with food and act accordingly. Players and foods are both arrays of JSON objects with varying coordinates and sizes. The brute-force method would be looping through all the foods, and for each food, looping through all players to see if they are in collision. Of course, that makes 300*50 iterations, 60 times per second (at 60fps), which is of course way too heavy for the server.
I did come across the quadtree method which is a new concept to me. Also my scarce knowledge on javascript is making me wonder how exactly I might implement it. The problems that I cannot solve are the following:
1. Since players can theoretically be of any size (even as big as the map), then how big would the sections that I divide the map in have to be?
2. Even if I do divide the map into sections, then the only way I can see it working is that for every player, I need to get the foods that share the same sections as the player. This is the big question - now matter how much I think of it, I would still need to loop through every food and check if it's in the required sections. How would I do that without looping? Because that still makes 50*300 iterations, 60 times per second, which does not sound in any way faster to me.
tldr: I need to find a way to detect collisions between a set of 50 objects and a set of 300 objects, 60 times per second. How do I do that without looping through 50*300 iterations at 60 fps?
I could not find any information online that answers my questions. I apologize in advance if I have missed something somewhere that could yield the answers I seek.
This is a small example that only checks a single layer, but I think it demonstrates how you can check for collisions without iterating over all objects.
// 2d array of list of things in said square
// NOT A QUADTREE JUST DEMONSTRATING SOMETHING
let quadlayer = [];
for (let i=0;i<4;++i) {
quadlayer[i] = [];
for (let j=0;j<4;++j) {
quadlayer[i][j] = [];
}
}
function insertObject(ur_object) {
quadlayer[ur_object.x][ur_object.y].push(ur_object);
}
function checkCollision(ur_object) {
let other_objects = quadlayer[ur_object.x][ur_object.y];
console.log('comparing against '+other_objects.length+' instead of '+100);
}
for (let i=0;i<10;++i) {
for (let j=0;j<10;++j) {
insertObject({
x:i%4,
y:j%4
})
}
}
checkCollision({x:1,y:2});
An interesting problem... Here's another take, which essentially uses the sweep line algorithm. (For a good explanation of the sweep line algorithm, see https://www.geeksforgeeks.org/given-a-set-of-line-segments-find-if-any-two-segments-intersect/ ).
The typical performance of a sweep line algorithm is O(n log n) compared to brute force of O(n^2).
In this particular implementation of the sweep line algorithm, all objects (food and people) are kept in a queue, with each object having two entries in the queue. The first entry is x - radius and the second entry is x + radius. That is, the queue tracks the lower/left and upper/right x bounds of all the objects. Furthermore, the queue is sorted by the x bounds using function updatePositionInQueue, which is essentially an insertion sort.
This allows the findCollisions routine to simply walk the queue, maintaining an active set of objects that need to be checked against each other. That is, the objects that overlap in the x dimension will be dynamically added and removed from the active set. Ie, when the queue entry represents a left x bound of an object, the object is added to the active set, and when the queue entry represents an right x bound of an object, the object is removed from the active set. So as the queue of objects is walked, each object that is about to be added to the active set only has to be checked for collisions against the small active set of objects with overlapping x bounds.
Note that as the algorithm stands, it checks for all collisions between people-and-people, people-and-food, and food-and-food...
As a pleasant bonus, the updatePositionInQueue routine permits adjustment of the sorted queue whenever a object moves. That is, if a person moves, the coordinates of their x,y position can be updated on the object, and then updatePositionInQueue( this.qRight ) and updatePositionInQueue( this.qLeft ) can be called, which will look to the previous and next objects in the sorted queue to move the updated object until its x bound is properly sorted. Given that the objects position should not be changing that much between frames, the movement of the left and right x bound entries in the queue should be minimal from frame-to-frame.
The code is as follows, which towards the bottom randomly generates object data, populates the queue, and then runs both the sweep line collision check along with a brute force check to verify the results, in addition to reporting on the performance as measured in object-to-object collision checks.
var queueHead = null;
function QueueEntry(paf, lor, x) {
this.paf = paf;
this.leftOrRight = lor;
this.x = x;
this.prev = null;
this.next = null;
}
function updatePositionInQueue( qEntry ) {
function moveEntry() {
// Remove qEntry from current position in queue.
if ( qEntry.prev === null ) queueHead = qEntry.next;
if ( qEntry.prev ) qEntry.prev.next = qEntry.next;
if ( qEntry.next ) qEntry.next.prev = qEntry.prev;
// Add qEntry to new position in queue.
if ( newLocation === null ) {
qEntry.prev = null;
qEntry.next = queueHead;
queueHead = qEntry;
} else {
qEntry.prev = newLocation;
qEntry.next = newLocation.next;
if ( newLocation.next ) newLocation.next.prev = qEntry;
newLocation.next = qEntry;
}
}
// Walk the queue, moving qEntry into the
// proper spot of the queue based on the x
// value. First check against the 'prev' queue
// entry...
let newLocation = qEntry.prev;
while (newLocation && qEntry.x < newLocation.x ) {
newLocation = newLocation.prev;
}
if (newLocation !== qEntry.prev) {
moveEntry();
}
// ...then against the 'next' queue entry.
newLocation = qEntry;
while (newLocation.next && newLocation.next.x < qEntry.x ) {
newLocation = newLocation.next;
}
if (newLocation !== qEntry) {
moveEntry();
}
}
function findCollisions() {
console.log( `\nfindCollisions():\n\n` );
var performanceCount = 0;
var consoleResult = [];
activeObjects = new Set();
var i = queueHead;
while ( i ) {
if ( i.leftOrRight === true ) {
activeObjects.delete( i.paf );
}
if ( i.leftOrRight === false ) {
let iPaf = i.paf;
for ( let o of activeObjects ) {
if ( (o.x - iPaf.x) ** 2 + (o.y - iPaf.y) ** 2 <= (o.radius + iPaf.radius) ** 2 ) {
if ( iPaf.id < o.id ) {
consoleResult.push( `Collision: ${iPaf.id} with ${o.id}` );
} else {
consoleResult.push( `Collision: ${o.id} with ${iPaf.id}` );
}
}
performanceCount++;
}
activeObjects.add( iPaf );
}
i = i.next;
}
console.log( consoleResult.sort().join( '\n' ) );
console.log( `\nfindCollisions collision check count: ${performanceCount}\n` );
}
function bruteForceCollisionCheck() {
console.log( `\nbruteForceCollisionCheck():\n\n` );
var performanceCount = 0;
var consoleResult = [];
for ( i in paf ) {
for ( j in paf ) {
if ( i < j ) {
let o1 = paf[i];
let o2 = paf[j];
if ( (o1.x - o2.x) ** 2 + (o1.y - o2.y) ** 2 <= (o1.radius + o2.radius) ** 2 ) {
if ( o1.id < o2.id ) {
consoleResult.push( `Collision: ${o1.id} with ${o2.id}` );
} else {
consoleResult.push( `Collision: ${o2.id} with ${o1.id}` );
}
}
performanceCount++;
}
}
}
console.log( consoleResult.sort().join( '\n' ) );
console.log( `\nbruteForceCollisionCheck collision check count: ${performanceCount}\n` );
}
function queuePrint() {
var i = queueHead;
while (i) {
console.log(`${i.paf.id}: x(${i.x}) ${i.paf.type} ${i.leftOrRight ? 'right' : 'left'} (x: ${i.paf.x} y: ${i.paf.y} r:${i.paf.radius})\n`);
i = i.next;
}
}
function PeopleAndFood( id, type, x, y, radius ) {
this.id = id;
this.type = type;
this.x = x;
this.y = y;
this.radius = radius;
this.qLeft = new QueueEntry( this, false, x - radius );
this.qRight = new QueueEntry( this, true, x + radius );
// Simply add the queue entries to the
// head of the queue, and then adjust
// their location in the queue.
if ( queueHead ) queueHead.prev = this.qRight;
this.qRight.next = queueHead;
queueHead = this.qRight;
updatePositionInQueue( this.qRight );
if ( queueHead ) queueHead.prev = this.qLeft;
this.qLeft.next = queueHead;
queueHead = this.qLeft;
updatePositionInQueue( this.qLeft );
}
//
// Test algorithm...
//
var paf = [];
const width = 10000;
const height = 10000;
const foodCount = 300;
const foodSizeMin = 10;
const foodSizeMax = 20;
const peopleCount = 50;
const peopleSizeMin = 50;
const peopleSizeMax = 100;
for (i = 0; i < foodCount; i++) {
paf.push( new PeopleAndFood(
i,
'food',
Math.round( width * Math.random() ),
Math.round( height * Math.random() ),
foodSizeMin + Math.round(( foodSizeMax - foodSizeMin ) * Math.random())
));
}
for (i = 0; i < peopleCount; i++) {
paf.push( new PeopleAndFood(
foodCount + i,
'people',
Math.round( width * Math.random() ),
Math.round( height * Math.random() ),
peopleSizeMin + Math.round(( peopleSizeMax - peopleSizeMin ) * Math.random())
));
}
queuePrint();
findCollisions();
bruteForceCollisionCheck();
(Note that the program prints the queue, followed by the results of findCollisions and bruteForceCollisionCheck. Only the tail end of the console appears to show when running the code snippet.)
Am sure that the algorithm can be squeezed a bit more for performance, but for the parameters in the code above, the test runs are showing a brute force check of 61075 collisions vs ~600 for the sweep line algorithm. Obviously the size of the objects will impact this ratio, as the larger the objects, the larger the set of objects with overlapping x bounds that will need to be cross checked...
An enjoyable problem to solve. Hope this helps.
I'm creating a small game in javascript and I'm using svg for the graphics. Right now I'm having a problem with updating the game in the middle of a game tick. If I exit my loop directly after I update the fill attribute with "setAttributeNS", it's redrawn, but if I don't do that, it isn't updated until after "game_tick" is over. Even worse, if I call "game_tick" multiple times in a row, the svg objects aren't updated until after I've run all of the "game_tick"s instead of being updated after each one.
function game_tick(){
num_grid_copy = num_grid.slice();
for (var x = 0; x < num_squares_x; x += 1) {
for (var y = 0; y < num_squares_x; y += 1) {
var n = get_neighbors(x,y);
var isAliveInNextGen = next_gen(n, num_grid[x*num_squares_x+y]);
num_grid_copy[x*num_squares_x+y] = isAliveInNextGen;
if (isAliveInNextGen == 1){
rect_grid[x*num_squares_x+y].setAttributeNS(null, 'fill', '#0099ff');
}
else {
rect_grid[x*num_squares_x+y].setAttributeNS(null, 'fill', '#fff');
}
}
}
num_grid = num_grid_copy;
}
Thanks to valuable input from Robert I realized that javascript execution and page rendering are done in the same thread. I changed the function to the following:
function start() {
var inc = 0,
max = 25;
delay = 100; // 100 milliseconds
var repeat = setInterval(function() {
game_tick();
if (++inc >= max)
clearInterval(repeat);
},
delay);
}
This works fine. I can set the delay and the number of times it repeats.
I am working on a randomising function for a game in javascript and jquery. I have written this to randomise the position of a square in a 8x8x64px grid, and give the player a point when their character moves over it. The function works once, but wont repeat.
The relevant script
Every time the player moves it searches for
if (xNum==ptX && yNum==ptY) {
getPoint();
pointRand();
}
yNum and xNum is the player's coordinates and ptY and ptX is the point's. Movement occurs on keyUp for the arrow keys, and the if is also executed then.
Corresponding functions
//Score
var ptAllow = true;
var pt = 0;
var ptX = 7;
var ptY = 7;
var yPt = ptX*64+"px";
var xPt = ptY*64+"px";
function getPoint() {
if (ptAllow == true) {
pt++;
document.getElementById("scoreCounter").innerHTML = "score: "+pt;
document.getElementById("scoreFin").innerHTML = "score: "+pt;
}
}
function pointRand() {
ptX = Math.floor(Math.random() * 8) + 1;
ptY = Math.floor(Math.random() * 8) + 1;
ptX--;
ptY--;
yPt = ptX*64+"px";
xPt = ptY*64+"px";
$("#point").animate({"left": xPt, "top": yPt}, 100);
}
Like I said, It works perfectly the first time you move over a point, but after that nothing happens. . ptAllow is set to false once the player hits an obstacle, and the game is over.
Edit: There are no errors in the browser console, and the reason for adding and subtracting in pointRand() is that math.floor does not seem to work with a lowest value of zero.
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 :-) )