3
\$\begingroup\$

I'm working on a game using the now abandoned Famo.us Javascript framework.

I have the game in a running prototype and I'm building with Cordova and running it on an iPhone 6 using PhoneGap.

There seems to be some performance issues with it and I can't help but notice that a bit of lag happens right as a new enemy is generated onto the field.

There's a few random factors when generating a new enemy:

  • position on the screen
  • type of enemy
  • direction of travel

I think the random factors are slowing the creation down because I'm not very good at high performing code, that said the following works:

//speed = velocity (randomly determined before hand - e.g. 300)
//timing = delay until next spawn (also random - e.g. 500)
function addEnemy(speed, timing){
// Famo.us (add node to scenegraph)
var newEnemy = gameEnemies.addChild();
newEnemy.name = "";
newEnemy.num = gameEnemies.iterator++;
// Possible sizes of enemy
var sizes = [30,40,50,60];
// choose a random size
var size = sizes[Math.floor(Math.random()*sizes.length)];
newEnemy.setSizeMode('absolute', 'absolute')
    .setAbsoluteSize(size, size);
// possible sides of the screen
var sidesOps = [1,2,3,4];
// choose random side
var sideOp = sidesOps[Math.floor(Math.random()*sidesOps.length)];
// switch through possible sides and place in a random position along that axis and give enemy the name of the side it was placed on
switch (sideOp) {
    case 1:
        newEnemy.setPosition(gameSize[0],Math.round(Math.random() * gameSize[1]),2);
        newEnemy.name = "right";
    break;
    case 2:
        newEnemy.setPosition(-size,Math.round(Math.random() * gameSize[1]),2);
        newEnemy.name = "left";
    break;
    case 3:
        newEnemy.setPosition(Math.round(Math.random() * gameSize[0]),gameSize[1],2);
        newEnemy.name = "bottom";
    break;
    case 4:
        newEnemy.setPosition(Math.round(Math.random() * gameSize[0]),-size,2);
        newEnemy.name = "top";
    break;
}
// Famo.us adds DOMElement to the scenegraph, node and DOM tree
newEnemy.DOMElement = new DOMElement(newEnemy);
// 'types' of enemies
var colors = []
// score restraint (only add in new types past a score threshold)
if(game.score < 699){
    colors = ['red','black','red','black','red','black','red','black'];
}else{
    colors = ['red','black','blue','grey','green','yellow','orange','purple'];
}
// choose a random type but restrict it by chance (i.e. 1:2 or 1:100)
var color = colors[0];
var ran = Math.random();
var x = Math.floor(ran*100);
if (x > 47 && x < 94) {
    color=colors[1];
}else if (x == 95){
    color=colors[2];
}else if (x == 96) {
    color=colors[3];
}else if (x == 97){
    color=colors[4];
}else if (x == 98){
    color=colors[5];
}else if (x == 99){
    color=colors[6];
}else if (x == 100){
    color=colors[7];
}
newEnemy.DOMElement.setProperty('background',color)
    .setProperty('border-radius', "100%");
// Component used by Famo.us updater loop and engine
newEnemy.newEnemyComponent = {
    id: null,
    node: null,
    // deletion function called when node should be terminated
    done: function(node){
        if(node in node._updater._updateQueue)FamousEngine._updateQueue.splice(node._updater._updateQueue.indexOf(node), 1);
        if(node._updateQueue && node._updateQueue.length)
            node._updateQueue = [];
        if(node._nextUpdateQueue && node._nextUpdateQueue.length)
            node._nextUpdateQueue = [];
        game.world.remove(node.collision);
        game.world.remove(node.sphere);
        node.dismount();
    },
    // mounting function called when node is added to scenegraph
    onMount: function (node){
        this.id = node.addComponent(this);
        this.node = node;
    },
   // updating function called when engine calls update callbacks
    onUpdate: function(time){
        // updating location of enemy
        var spherePosition = this.node.sphere.getPosition();
        // if enemy is beyond bounding walls of game, call it's done() function
        if((spherePosition.x-65) > gameSize[0] || (spherePosition.x+65) < 0
            || (spherePosition.y-65) > gameSize[1] || (spherePosition.y+65) < 0){
            if(this.node._id != null){
                this.done(this.node);
            }
        // else update the position based on the location of the physics body and call the next update
        }else{
            this.node.setPosition(spherePosition.x,spherePosition.y);
            this.node.requestUpdate(this.id);
        }
    }
};
newEnemy.addComponent(newEnemy.newEnemyComponent);
// add a physics body to the world and enemy node, position it accordingly
var newEnemyPosition = newEnemy.getPosition();
newEnemy.sphere = new Sphere({
    mass: 1,
    radius: size/2,
    position:new Vec3(newEnemyPosition[0],newEnemyPosition[1])
});
newEnemy.sphere.node = newEnemy;
world.addBody(newEnemy.sphere);
// add collision between player and enemy to the physics engine
newEnemy.collision = world.addConstraint(
    new Collision([game.boxNode.box,newEnemy.sphere],{restitution:0})
);
gameEnemies.constraintIterator++;

// randomly determine path of enemy (either straight forward or on a diagonal)
var diag = Math.random() < 0.5 ? true : false;
switch (newEnemy.name) {
    case "left":
        if(diag == true){
            if(newEnemyPosition[1]> (gameSize[1]/2)){
                newEnemy.sphere.setVelocity(speed,-speed);
            }
            else{
                newEnemy.sphere.setVelocity(speed,speed);
            }
        }
        else{
            newEnemy.sphere.setVelocity(speed,0);
        }
    break;
    case "right":
        if(diag == true){
            if(newEnemyPosition[1]> (gameSize[1]/2)){
                newEnemy.sphere.setVelocity(-speed,-speed);
            }
            else{
                newEnemy.sphere.setVelocity(-speed,speed);
            }
        }
        else{
            newEnemy.sphere.setVelocity(-speed,0);
        }
    break;
    case "top":
        if(diag == true){
            if(newEnemyPosition[0]> (gameSize[0]/2)){
                newEnemy.sphere.setVelocity(-speed,speed);
            }
            else{
                newEnemy.sphere.setVelocity(speed,speed)
            }
        }
        else{
            newEnemy.sphere.setVelocity(0,speed);
        }
    break;
    case "bottom":
        if(diag == true){
            if(newEnemyPosition[0]> (gameSize[0]/2)){
                newEnemy.sphere.setVelocity(-speed,-speed);
            }
            else {
                newEnemy.sphere.setVelocity(speed,-speed);
            }
        }
        else{
            newEnemy.sphere.setVelocity(0,-speed);
        }
    break;
}
// call the first update reqeust
newEnemy.requestUpdate(newEnemy.newEnemyComponent.id)
// set a timeout for the next enemy to be added (addEnemyUtil determines speed and timing factors and calls addEnemy)
FamousEngine.getClock().setTimeout(function(){
    addEnemyUtil();
},timing);
}
\$\endgroup\$
2
\$\begingroup\$

I don't think that it is the Math.random() that is causing problems. This jsfiddle calls Math.random() 1 million times in about 60-80 ms (chrome on iPhone 5s). I believe the issue is that you are doing a lot of calculations for each new enemy. There are a couple of ways I can think to avoid this heavy calculation.

You could compute the position (and all the other properties) of your enemies at the beginning of each level, then add the pre-computed enemies when necessary, if you have levels in your game. Another solution could be to introduce "waves" into your game. At the beginning of each wave you could calculate where enemies will spawn.

\$\endgroup\$
  • \$\begingroup\$ It's not that style of game, new enemies are added to the game field as time prolongs, kind of like Asteroids. \$\endgroup\$ – RenaissanceProgrammer Feb 17 '16 at 2:17
  • \$\begingroup\$ Actually asteroids might be a bad example, probably more like geometry wars. \$\endgroup\$ – RenaissanceProgrammer Feb 17 '16 at 2:32
  • \$\begingroup\$ I found another bug that was causing the stuttering, this calculation of random properties isn't actually so bad on my performance there was another looping call that was causing issues, anyway, do you think these type of if-else and switch algorithms are ok? \$\endgroup\$ – RenaissanceProgrammer Feb 17 '16 at 2:40
  • 2
    \$\begingroup\$ Yeah, if-else if statements shouldn't be too bad. You are doing a lot of computations though (about 160 lines of it). Because of that the only way to reduce time is to put computation at loading phase. Maybe you could precompute 100 enemies and randomly choose from those, after a certain amount of time you would get repeats but that might be the best way. If you really want to pin down what is taking the longest you could add timing statement throughout your code to find what bits are taking the longest. My guess is that it will be fairly uniform though. \$\endgroup\$ – McAngus Feb 17 '16 at 18:00
0
\$\begingroup\$

If you can't find a way to get rid of the lag, you might try JavaScript workers. With the worker you can generate the new enemy in the background without interfering with the main UI thread. Once the enemy is generated, pass it to the main thread for adding to the game.

\$\endgroup\$
  • \$\begingroup\$ I'm not sure this will work because web workers cannot manipulate the DOM and part of this function is to have the Famo.us framework go into the DOM and create an element and style it, but I'll see what I can do with a worker, thanks! \$\endgroup\$ – RenaissanceProgrammer Feb 18 '16 at 20:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.