2

I need to check whether or not an object is in distance to show it to the screen ( its a side scroller video game )

So far i have this:

for ( var i=0; i < Coins.length; i++ )
{
    var obj = Coins[i];


    if ( worldObj.distance > obj.distance && worldObj.distance < obj.distance + canvas.height )
    {

        DrawCoins(obj);
    }
}

Where worldObj.distance is the distance the player has traveled and the obj.distance is the distance of the object.

The problem:

This for loop causes a great performance drop on mobile devices due to the amount of coins in the level ( more than 10,000) and this gets executed 60 times per second ( 60 fps )

How can i fix this?

Thank you! :)

EDIT: Tried caching canvas.height into a variable, ( eg: var height = canvas.height; ) before the loop. No performance differences (44 ms vs 44 ms on a I5 2500K, imagine on mobile!!).

EDIT: Tried caching Coins.length before the loop, ( eg: var len = Coins.length; ). No performance differences (44 ms vs 44 ms).

EDIT: This is how i create coins:

for(var i=0; i<10000; i++)
{

    /* Coins */
    for ( var z=0; z < 6; z++ )
    {
        if ( RNG(0,100) < Upgrades.coinChance ) // random number generator, Upgrades.coinChance = 5; -> 5% chance
        {
            Coins.push({ active: 1, type: "Gold", cash: 5, x: 60*(z+1), distance: i*RNG(20,100) });
        }
    }
}
  • How are you drawing the coins? If you're using a HTML5 Canvas, you should [let it handle culling for you](http://stackoverflow.com/questions/16893626/should-i-cull-elements-before-rendering-to-html5-canvas-or-let-the-canvas-cull). – K893824 Apr 19 '14 at 17:02
  • 2
    Seems like the most obvious would be to either a) Only draw coins that are visible or b) Only refresh coins when he gets close to them. – Jeremy J Starcher Apr 19 '14 at 17:02
  • simply caching canvas.height into a var before the loop would help considerably. caching worldObj.distance would be good also. – dandavis Apr 19 '14 at 17:05
  • Hi, it's not the DrawCoins function that is slow, but the for loop itself, at least on a single core 1ghz mobile device (10.000 object x 60 fps = 600,000 per second ) – user3552137 Apr 19 '14 at 17:05
  • As Jeremy said, it's good to check only the visible items. If that's not the point, a good idea is to segment those coins into let's call them `regions`. For example a 500x500 square. You set this property when initializing them. Later, when you know where the `world` is, you can loop only through the coins that are INSIDE your current `region`. – Andrey Popov Apr 19 '14 at 17:17
  • Can you control the creation or structure of `Coins` array? – Kushal Apr 19 '14 at 17:24
  • 3
    You could put the coins into a binary search tree, based on the X coordinate of the coins. This way you could grab an entire branch of the tree and simply refresh those particular coins which are within the X coordinate range of the screen. – Robert Apr 19 '14 at 17:28
  • 1
    Not necessarily for this purpose, but you can give [Crossfilter](http://square.github.io/crossfilter/) a try. – Kushal Apr 19 '14 at 17:36

1 Answers1

0

Alright. I've done some number crunching and come up with two algorithms, with tradeoffs between them.

As a testbed, I'm running the following initialization code:

var n,m;

var Coin=function(x)
{
    return {x:x,r:1};
}
var coins=[];
var map={};

var viewSize=50;
var worldSize=1000;

n=100000;
while(n--)
{
    coins[n]=Coin(worldSize*Math.random());
}

As you can see, there are 100000 coins here. More than you stipulated in your answer, I know, but I wanted to stress-test things.

The first algorithm is a somewhat optimized version of the one you posted in your question. It just loops through and tests if the nearest edge of each coin is less than viewSize/2 distance from some point x. If so, it adds it to an array, which it ultimately returns.

var checkCoins1=function(list,x,w)
{
    var selected,k,n,no;
    selected=[];
    n=list.length;
    while(n--)
    {
        no=list[n];
        if(Math.abs(no.x-x)<w/2+no.r)
        {
            selected.push(no);
        }
    }
    return selected;
};

This method takes no extra time to setup and 7ms for each execution with our 100000 coins. Definitely a cost in an animation loop, but a manageable one.

The second algorithm makes use of a map. It starts out by sorting the list of coins, then selects a set of points across worldSize, each one viewSize apart form the last one. It creates an object with these points as keys. It then loops through all of the coins and saves the index of the one nearest to each point in the object. This takes some time, but it only needs to be done once. When the actual loop runs, it just finds the point in the map immediately before the left (or lower, as the case may be) edge of the viewing window. It then loops forward through all of the coins in the list, saving them to an array as it goes, until it gets to a coin that is farther along than the right (or upper) edge of the viewing window. Then it stops and returns the array. This way, it doesn't have to check every coin in the list, but instead just a few of them.

The setup code looks like this:

coins.sort(
    function(a,b)
    {
        return -(a.x<b.x)+(a.x>b.x);
    }
);
n=Math.floor(worldSize/viewSize);
while(n--)
{
    map[n*viewSize]=undefined;
}
n=coins.length;
while(n--)
{
    for(m in map)
    {
        if(map[m]===undefined || Math.abs(coins[n].x-m)<Math.abs(coins[map[m]].x-m))
        {
            map[m]=n;
        }
    }
}

And the main loop looks like this:

var checkCoins2=function(list,map,x,w)
{
    var selected,y,k,n,no;
    selected=[];
    y=x-w/2;
    n=map[Math.floor(y/w)*w];
    while(1)
    {
        no=list[n++];
        if(Math.abs(no.x-x)<w/2+no.r)
        {
            selected.push(no);
        }
        else if(no.x>x)
        {
            break;
        }
    }
    return selected;
};

The initialization takes a whopping 900ms, but the loop takes only 1ms each time it runs. As the ratio between worldSize and viewSize increases, the loop will get faster and faster.

Overall, I would say that the first algorithm is simpler, but if you find yourself pressed for time in your animation loop and can take a second to initialize when the game (or level) loads, then you should use the second algorithm.

Perhaps, if you know the placement of the coins to begin with, you could even pre-sort the list and pre-build the map when you write the code. Then you wouldn't need the client to do the initialization at all.

If you have any questions, please ask!

mindoftea
  • 816
  • 6
  • 16