R – Optimizing the dynamic background engine for a 2d flash game in actionscript-3

actionscript-3backgrounddynamicoptimizationperformance

Edit 2: judging on the lack of replies I start wondering if my issue is clear enough. Please tell me if I need to elaborate more.

Notice: see bottom for a code update!

Short introduction: I'm writing a 2 dimensional flash space game in actionscript. The universe is infinitely big, because of this feature, the background has to be rendered dynamically and background objects (gas clouds, stars, etc.) have to be positioned randomly.

I created a class called BackgroundEngine and it's working very well, the problem is however the rendering performance. This is how it works:

At startup, 4 background containers (each the size of the stage) are created around the player. Top left, top right, bottom left and bottom right. All background squares are added to a master container, for easy movement of the background. Now, there are 2 polling functions:

1) "garbage poller": looks for background containers that are 2 times the stage width or height away from the player's X or Y coord, respectively. If so, it will remove that background square and allow it for garbage collection.

2) "rendering poller": looks whether there is currently a background at all sides of the player (x – stageWidth, x + stageWidth, y – stageHeight, y + stageHeight). If not, it will draw a new background square at the corresponding location.

All background squares are created with the following function (the ones that are created dynamically and the four on startup):

<<< removed old code, see bottom for updated full source >>>

All the randoms you see there are making sure that the environment looks very unique on every square. This actually works great, the universe looks quite awesome.

The following assets are being used as background objects:

1) Simple stars : http://www.feedpostal.com/client/assets/background/1.png (you probably won't be able to see that one in a browser with a white background).

2) Bright stars : http://www.feedpostal.com/client/assets/background/2.png

3) White gas clouds : http://www.feedpostal.com/client/assets/background/3.png

4) Red gas clouds: http://www.feedpostal.com/client/assets/background/4.png

Important notes:

1) All assets are cached, so they don't have to be re-downloaded all the time. They are only downloaded once.

2) The images are not rotating or being scaled after they are created, so I enabled cacheAsBitmap for all objects, containers and the masterContainer.

3) I had to use PNG formats in Photoshop because GIFs did not seem to be rendered very well in flash when used with transparency.

So, the problem is that when I fly around the rendering of the background takes too much performance: the client starts "lagging" (FPS wise). Because of this, I need to optimize the background engine so that it will render much quicker. Can you folks help me out here?

Update 1:
This is what I have so far after the one response I got.

BackgroundEngine.as

package com.tommedema.background
{
    import br.com.stimuli.loading.BulkLoader;

    import com.tommedema.utils.Settings;
    import com.tommedema.utils.UtilLib;

    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.Sprite;
    import flash.events.Event;

    public final class BackgroundEngine extends Sprite
    {
        private static var isLoaded:Boolean = false;
        private static var bulkLoader:BulkLoader = BulkLoader.getLoader("main");
        private static var masterContainer:Sprite;
        private static var containers:Array = [];
        private static var stageWidth:uint;
        private static var stageHeight:uint;
        private static var assets:Array;

        //moves the background's X coord
        public static function moveX(amount:Number):void
        {
            if (masterContainer)
            {
                masterContainer.x += amount;
                collectGarbage();
                drawNextContainer();
            }
        }

        //moves the background's Y coord
        public static function moveY(amount:Number):void
        {
            if (masterContainer)
            {
                masterContainer.y += amount;
                collectGarbage();
                drawNextContainer();
            }
        }

        //returns whether the background engine has been loaded already
        public static function loaded():Boolean
        {
            return isLoaded;
        }

        //loads the background engine
        public final function load():void
        {
            //set stage width and height
            stageWidth = stage.stageWidth;
            stageHeight = stage.stageHeight;

            //retreive all background assets
            bulkLoader.add(Settings.ASSETS_PRE_URL + "background/1.png", {id: "background/1.png"});
            bulkLoader.add(Settings.ASSETS_PRE_URL + "background/2.png", {id: "background/2.png"});
            bulkLoader.add(Settings.ASSETS_PRE_URL + "background/3.png", {id: "background/3.png"});
            bulkLoader.add(Settings.ASSETS_PRE_URL + "background/4.png", {id: "background/4.png"});
            bulkLoader.addEventListener(BulkLoader.COMPLETE, assetsComplete);
            bulkLoader.start();

            //set isLoaded to true
            isLoaded = true;
        }

        //poller function for drawing next background squares
        private static function drawNextContainer():void
        {
            var stageCenterX:Number = stageWidth / 2;
            var stageCenterY:Number = stageHeight / 2;
            var curContainer:Bitmap = hasBackground(stageCenterX, stageCenterY);
            if (curContainer)
            {
                //top left
                if (!hasBackground(stageCenterX - stageWidth, stageCenterY - stageHeight)) drawNewSquare(curContainer.x - stageWidth, curContainer.y - stageHeight);
                //top
                if (!hasBackground(stageCenterX, stageCenterY - stageHeight)) drawNewSquare(curContainer.x, curContainer.y - stageHeight);
                //top right
                if (!hasBackground(stageCenterX + stageWidth, stageCenterY - stageHeight)) drawNewSquare(curContainer.x + stageWidth, curContainer.y - stageHeight);
                //center left
                if (!hasBackground(stageCenterX - stageWidth, stageCenterY)) drawNewSquare(curContainer.x - stageWidth, curContainer.y);
                //center right
                if (!hasBackground(stageCenterX + stageWidth, stageCenterY)) drawNewSquare(curContainer.x + stageWidth, curContainer.y);
                //bottom left
                if (!hasBackground(stageCenterX - stageWidth, stageCenterY + stageHeight)) drawNewSquare(curContainer.x - stageWidth, curContainer.y + stageHeight);
                //bottom
                if (!hasBackground(stageCenterX, stageCenterY + stageHeight)) drawNewSquare(curContainer.x, curContainer.y + stageHeight);
                //bottom right
                if (!hasBackground(stageCenterX + stageWidth, stageCenterY + stageHeight)) drawNewSquare(curContainer.x + stageWidth, curContainer.y + stageHeight);
            }
        }

        //draws the next square and adds it to the master container
        private static function drawNewSquare(x:Number, y:Number):void
        {
            containers.push(genSquareBg());
            var cIndex:uint = containers.length - 1;
            containers[cIndex].x = x;
            containers[cIndex].y = y;
            masterContainer.addChild(containers[cIndex]);
        }

        //returns whether the given location has a background and if so returns the corresponding square
        private static function hasBackground(x:Number, y:Number):Bitmap
        {
            var stageX:Number;
            var stageY:Number;
            for(var i:uint = 0; i < containers.length; i++)
            {
                stageX = masterContainer.x + containers[i].x;
                stageY = masterContainer.y + containers[i].y;
                if ((containers[i]) && (stageX < x) && (stageX + stageWidth > x) && (stageY < y) && (stageY + stageHeight > y)) return containers[i];
            }
            return null;
        }

        //polling function for old background squares garbage collection
        private static function collectGarbage():void
        {
            var stageX:Number;
            var stageY:Number;

            for(var i:uint = 0; i < containers.length; i++)
            {
                if (containers[i])
                {
                    stageX = masterContainer.x + containers[i].x;
                    stageY = masterContainer.y + containers[i].y;
                    if ((stageX < -stageWidth * 1.5) || (stageX > stageWidth * 2.5) || (stageY < -stageHeight * 1.5) || (stageY > stageHeight * 2.5))
                    {
                        containers[i].parent.removeChild(containers[i]);
                        containers.splice(i, 1);
                    }
                }
            }
        }

        //dispatched when all assets have finished downloading
        private final function assetsComplete(event:Event):void
        {
            assets = [];
            assets.push(bulkLoader.getBitmap("background/1.png")); //star simple
            assets.push(bulkLoader.getBitmap("background/2.png")); //star bright
            assets.push(bulkLoader.getBitmap("background/3.png")); //cloud white
            assets.push(bulkLoader.getBitmap("background/4.png")); //cloud red
            init();
        }

        //initializes startup background containers
        private final function init():void
        {
            masterContainer = new Sprite(); //create master container

            //generate default background containers
            containers.push(genSquareBg()); //top left
            containers[0].x = 0;
            containers[0].y = 0;
            containers.push(genSquareBg()); //top
            containers[1].x = stageWidth;
            containers[1].y = 0;
            containers.push(genSquareBg()); //top right
            containers[2].x = stageWidth * 2;
            containers[2].y = 0;
            containers.push(genSquareBg()); //center left
            containers[3].x = 0;
            containers[3].y = stageHeight;
            containers.push(genSquareBg()); //center
            containers[4].x = stageWidth;
            containers[4].y = stageHeight;
            containers.push(genSquareBg()); //center right
            containers[5].x = stageWidth * 2;
            containers[5].y = stageHeight;
            containers.push(genSquareBg()); //bottom left
            containers[6].x = 0;
            containers[6].y = stageHeight * 2;
            containers.push(genSquareBg()); //bottom
            containers[7].x = stageWidth;
            containers[7].y = stageHeight * 2;
            containers.push(genSquareBg()); //bottom right
            containers[8].x = stageWidth * 2;
            containers[8].y = stageHeight * 2;

            //add the new containers to the master container
            for (var i:uint = 0; i <= containers.length - 1; i++)
            {
                masterContainer.addChild(containers[i]);    
            }

            //display the master container
            masterContainer.x = 0 - stageWidth;
            masterContainer.y = 0 - stageHeight;
            masterContainer.cacheAsBitmap = true;
            addChild(masterContainer);
        }

        //duplicates a bitmap display object
        private static function dupeBitmap(source:Bitmap):Bitmap {
            var data:BitmapData = source.bitmapData;
            var bitmap:Bitmap = new Bitmap(data);
            return bitmap;
        }

        //draws a simple star
    private static function drawStar(x:Number, y:Number, width:uint, height:uint):Sprite
    {
        var creation:Sprite = new Sprite();
        creation.graphics.lineStyle(1, 0xFFFFFF);
        creation.graphics.beginFill(0xFFFFFF);
        creation.graphics.drawRect(x, y, width, height);
        return creation;
    }

    //generates a background square
    private static function genSquareBg():Bitmap
    {
        //set 1% margin
        var width:Number = stageWidth * 0.99;
        var height:Number = stageHeight * 0.99;
        var startX:Number = 0 + stageWidth / 100;
        var startY:Number = 0 + stageHeight / 100;

        var scale:Number;
        var drawAmount:uint;
        var tmpBitmap:Bitmap;
        var tmpSprite:Sprite;
        var i:uint;

        //create container
        var container:Sprite = new Sprite();

        //draw simple stars
        drawAmount = UtilLib.getRandomInt(100, 250);
        for(i = 1; i <= drawAmount; i++)
        {
            tmpSprite = drawStar(0, 0, 1, 1);
            tmpSprite.x = UtilLib.getRandomInt(0, stageWidth);
            tmpSprite.y = UtilLib.getRandomInt(0, stageHeight);
            tmpSprite.alpha = UtilLib.getRandomInt(3, 10) / 10;
            scale = UtilLib.getRandomInt(2, 10) / 10;
            tmpSprite.scaleX = tmpSprite.scaleY = scale;
            container.addChild(tmpSprite);
        }

        //draw bright stars
        if (Math.random() >= 0.8) drawAmount = UtilLib.getRandomInt(1, 2);
        else drawAmount = 0;
        for(i = 1; i <= drawAmount; i++)
        {
            tmpBitmap = dupeBitmap(assets[1]);
            tmpBitmap.alpha = UtilLib.getRandomInt(3, 7) / 10;
            tmpBitmap.rotation = UtilLib.getRandomInt(0, 360);
            scale = UtilLib.getRandomInt(3, 10) / 10;
            tmpBitmap.scaleX = scale; tmpBitmap.scaleY = scale;
            tmpBitmap.x = UtilLib.getRandomInt(startX + tmpBitmap.width, width - tmpBitmap.width);
            tmpBitmap.y = UtilLib.getRandomInt(startY + tmpBitmap.height, height - tmpBitmap.height);
            container.addChild(tmpBitmap);
        }

        //draw white clouds
        drawAmount = UtilLib.getRandomInt(1, 4);
        for(i = 1; i <= drawAmount; i++)
        {
            tmpBitmap = dupeBitmap(assets[2]);
            tmpBitmap.alpha = UtilLib.getRandomInt(1, 10) / 10;
            scale = UtilLib.getRandomInt(15, 30);
            tmpBitmap.scaleX = scale / 10;
            tmpBitmap.scaleY = UtilLib.getRandomInt(scale / 2, scale) / 10;
            tmpBitmap.x = UtilLib.getRandomInt(startX, width - tmpBitmap.width);
            tmpBitmap.y = UtilLib.getRandomInt(startY, height - tmpBitmap.height);
            container.addChild(tmpBitmap);
        }

        //draw red clouds
        drawAmount = UtilLib.getRandomInt(0, 1);
        for(i = 1; i <= drawAmount; i++)
        {
            tmpBitmap = dupeBitmap(assets[3]);
            tmpBitmap.alpha = UtilLib.getRandomInt(2, 6) / 10;
            scale = UtilLib.getRandomInt(5, 30) / 10;
            tmpBitmap.scaleX = scale; tmpBitmap.scaleY = scale;
            tmpBitmap.x = UtilLib.getRandomInt(startX, width - tmpBitmap.width);
            tmpBitmap.y = UtilLib.getRandomInt(startY, height - tmpBitmap.height);
            container.addChild(tmpBitmap);
        }

        //convert all layers to a single bitmap layer and return
        var bitmapData:BitmapData = new BitmapData(stageWidth, stageHeight, true, 0x000000);
        bitmapData.draw(container);
        container = null;
        var bitmapContainer:Bitmap = new Bitmap(bitmapData);
        bitmapContainer.cacheAsBitmap = true;
        return bitmapContainer;
    }
    }
}

When the player is moving, the background moveX and moveY methods are called with the inverse direction of the player. This will also cause the collectGarbage and drawNextContainer methods to be called.

The problem with this setup is that there are a minimum of 9 containers active at all times. Top left, top, top right, center left, center, center right, bottom left, bottom and bottom right. This takes a lot of performance.

Edit: I also wonder, should I use cacheAsBitmap? If so, on which images? On the containers and the master container or on only one of them? When I enable it for all images (even the temporary sprite objects) it's actually lagging more.

Update 2:

This version is using squares that are twice as big as the stage. Only one or two squares should be loaded at a time. It is better, but I still notice a performance hit while moving. It makes the client freeze for a very brief moment. Any idea how to optimize it?

BackgroundEngine2.as

package com.tommedema.background
{
    import br.com.stimuli.loading.BulkLoader;

    import com.tommedema.utils.Settings;
    import com.tommedema.utils.UtilLib;

    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.Sprite;
    import flash.events.Event;

    public final class BackgroundEngine2 extends Sprite
    {
        //general
        private static var isLoaded:Boolean = false;        
        private static var bulkLoader:BulkLoader = BulkLoader.getLoader("main");
        private static var assets:Array;

        //objects
        private static var masterContainer:Sprite;
        private static var containers:Array = [];

        //stage
        private static var stageWidth:uint;
        private static var stageHeight:uint;
        private static var stageCenterX:Number;
        private static var stageCenterY:Number;

        //moves the background's X coord
        public static function moveX(amount:Number):void
        {
            if (!masterContainer) return;
            masterContainer.x += amount;
            collectGarbage();
            drawNextContainer();
        }

        //moves the background's Y coord
        public static function moveY(amount:Number):void
        {
            if (!masterContainer) return;
            masterContainer.y += amount;
            collectGarbage();
            drawNextContainer();
        }

        //returns whether the background engine has been loaded already
        public static function loaded():Boolean
        {
            return isLoaded;
        }

        //loads the background engine
        public final function load():void
        {
            //set stage width, height and center
            stageWidth = stage.stageWidth;
            stageHeight = stage.stageHeight;
            stageCenterX = stageWidth / 2;
            stageCenterY = stageHeight / 2;

            //retreive background assets
            bulkLoader.add(Settings.ASSETS_PRE_URL + "background/1.png", {id: "background/1.png"});
            bulkLoader.add(Settings.ASSETS_PRE_URL + "background/2.png", {id: "background/2.png"});
            bulkLoader.add(Settings.ASSETS_PRE_URL + "background/3.png", {id: "background/3.png"});
            bulkLoader.add(Settings.ASSETS_PRE_URL + "background/4.png", {id: "background/4.png"});
            bulkLoader.addEventListener(BulkLoader.COMPLETE, assetsComplete);
            bulkLoader.start();

            //set isLoaded to true
            isLoaded = true;
        }

        //poller function for drawing next background squares
        private static function drawNextContainer():void
        {
            var curContainer:Bitmap = hasBackground(stageCenterX, stageCenterY);
            if (curContainer)
            {
                if (!hasBackground(stageCenterX - stageWidth * 0.75, stageCenterY - stageHeight * 0.75)) //top left
                    drawNewSquare(curContainer.x - curContainer.width, curContainer.y - curContainer.height);
                if (!hasBackground(stageCenterX, stageCenterY - stageHeight * 0.75)) //top
                    drawNewSquare(curContainer.x, curContainer.y - curContainer.height);
                if (!hasBackground(stageCenterX + stageWidth * 0.75, stageCenterY - stageHeight * 0.75)) //top right
                    drawNewSquare(curContainer.x + curContainer.width, curContainer.y - curContainer.height);
                if (!hasBackground(stageCenterX - stageWidth * 0.75, stageCenterY)) //center left
                    drawNewSquare(curContainer.x - curContainer.width, curContainer.y);
                if (!hasBackground(stageCenterX + stageWidth * 0.75, stageCenterY)) //center right
                    drawNewSquare(curContainer.x + curContainer.width, curContainer.y);
                if (!hasBackground(stageCenterX - stageWidth * 0.75, stageCenterY + stageHeight * 0.75)) //bottom left
                    drawNewSquare(curContainer.x - curContainer.width, curContainer.y + curContainer.height);
                if (!hasBackground(stageCenterX, stageCenterY + stageHeight * 0.75)) //bottom center
                    drawNewSquare(curContainer.x, curContainer.y + curContainer.height);
                if (!hasBackground(stageCenterX + stageWidth * 0.75, stageCenterY + stageHeight * 0.75)) //bottom right
                    drawNewSquare(curContainer.x + curContainer.width, curContainer.y + curContainer.height);
            }
        }

        //draws the next square and adds it to the master container
        private static function drawNewSquare(x:Number, y:Number):void
        {
            containers.push(genSquareBg());
            var cIndex:uint = containers.length - 1;
            containers[cIndex].x = x;
            containers[cIndex].y = y;
            masterContainer.addChild(containers[cIndex]);
        }

        //returns whether the given location has a background and if so returns the corresponding square
        private static function hasBackground(x:Number, y:Number):Bitmap
        {
            var stageX:Number;
            var stageY:Number;
            for(var i:uint = 0; i < containers.length; i++)
            {
                stageX = masterContainer.x + containers[i].x;
                stageY = masterContainer.y + containers[i].y;
                if ((containers[i]) && (stageX < x) && (stageX + containers[i].width > x) && (stageY < y) && (stageY + containers[i].height > y)) return containers[i];
            }
            return null;
        }

        //polling function for old background squares garbage collection
        private static function collectGarbage():void
        {
            var stageX:Number;
            var stageY:Number;
            for(var i:uint = 0; i < containers.length; i++)
            {
                if ((containers[i]) && (!isRequiredContainer(containers[i])))
                {
                    masterContainer.removeChild(containers[i]);
                    containers.splice(i, 1);
                }
            }
        }

        //returns whether the given container is required for display
        private static function isRequiredContainer(container:Bitmap):Boolean
        {
            if (hasBackground(stageCenterX, stageCenterY) == container) //center
                return true;
            if (hasBackground(stageCenterX - stageWidth * 0.75, stageCenterY - stageHeight * 0.75) == container) //top left
                return true;
            if (hasBackground(stageCenterX, stageCenterY - stageHeight * 0.75) == container) //top
                return true;
            if (hasBackground(stageCenterX + stageWidth * 0.75, stageCenterY - stageHeight * 0.75) == container) //top right
                return true;
            if (hasBackground(stageCenterX - stageWidth * 0.75, stageCenterY) == container) //center left
                return true;
            if (hasBackground(stageCenterX + stageWidth * 0.75, stageCenterY) == container) //center right
                return true;
            if (hasBackground(stageCenterX - stageWidth * 0.75, stageCenterY + stageHeight * 0.75) == container) //bottom left
                return true;
            if (hasBackground(stageCenterX, stageCenterY + stageHeight * 0.75) == container) //bottom center
                return true;
            if (hasBackground(stageCenterX + stageWidth * 0.75, stageCenterY + stageHeight * 0.75) == container) //bottom right
                return true;
            return false;
        }

        //dispatched when all assets have finished downloading
        private final function assetsComplete(event:Event):void
        {
            assets = [];
            assets.push(bulkLoader.getBitmap("background/1.png")); //star simple
            assets.push(bulkLoader.getBitmap("background/2.png")); //star bright
            assets.push(bulkLoader.getBitmap("background/3.png")); //cloud white
            assets.push(bulkLoader.getBitmap("background/4.png")); //cloud red
            init();
        }

        //initializes startup background containers
        private final function init():void
        {
            masterContainer = new Sprite(); //create master container

            //generate default background container
            containers.push(genSquareBg()); //top left
            containers[0].x = 0;
            containers[0].y = 0;
            masterContainer.addChild(containers[0]);

            //display the master container
            masterContainer.x = -(stageWidth / 2);
            masterContainer.y = -(stageHeight / 2);
            masterContainer.cacheAsBitmap = true;
            addChild(masterContainer);
        }

        //duplicates a bitmap display object
        private static function dupeBitmap(source:Bitmap):Bitmap {
            var data:BitmapData = source.bitmapData;
            var bitmap:Bitmap = new Bitmap(data);
            return bitmap;
        }

        //draws a simple star
        private static function drawStar(x:Number, y:Number, width:uint, height:uint):Sprite
        {
            var creation:Sprite = new Sprite();
            creation.graphics.lineStyle(1, 0xFFFFFF);
            creation.graphics.beginFill(0xFFFFFF);
            creation.graphics.drawRect(x, y, width, height);
            return creation;
        }

        //generates a background square
        private static function genSquareBg():Bitmap
        {
            var width:Number = stageWidth * 2;
            var height:Number = stageHeight * 2;
            var startX:Number = 0;
            var startY:Number = 0;

            var scale:Number;
            var drawAmount:uint;
            var tmpBitmap:Bitmap;
            var tmpSprite:Sprite;
            var i:uint;

            //create container
            var container:Sprite = new Sprite();

            //draw simple stars
            drawAmount = UtilLib.getRandomInt(100, 250);
            for(i = 1; i <= drawAmount; i++)
            {
                tmpSprite = drawStar(0, 0, 1, 1);
                tmpSprite.x = UtilLib.getRandomInt(startX, width);
                tmpSprite.y = UtilLib.getRandomInt(startY, height);
                tmpSprite.alpha = UtilLib.getRandomInt(3, 10) / 10;
                scale = UtilLib.getRandomInt(5, 15) / 10;
                tmpSprite.scaleX = tmpSprite.scaleY = scale;
                container.addChild(tmpSprite);
            }

            //draw bright stars
            drawAmount = UtilLib.getRandomInt(1, 2);
            for(i = 1; i <= drawAmount; i++)
            {
                tmpBitmap = dupeBitmap(assets[1]);
                tmpBitmap.alpha = UtilLib.getRandomInt(3, 7) / 10;
                tmpBitmap.rotation = UtilLib.getRandomInt(0, 360);
                scale = UtilLib.getRandomInt(3, 10) / 10;
                tmpBitmap.scaleX = scale; tmpBitmap.scaleY = scale;
                tmpBitmap.x = UtilLib.getRandomInt(startX + tmpBitmap.width, width - tmpBitmap.width);
                tmpBitmap.y = UtilLib.getRandomInt(startY + tmpBitmap.height, height - tmpBitmap.height);
                container.addChild(tmpBitmap);
            }

            //draw white clouds
            drawAmount = UtilLib.getRandomInt(2, 4);
            for(i = 1; i <= drawAmount; i++)
            {
                tmpBitmap = dupeBitmap(assets[2]);
                tmpBitmap.alpha = UtilLib.getRandomInt(1, 10) / 10;
                scale = UtilLib.getRandomInt(15, 40);
                tmpBitmap.scaleX = scale / 10;
                tmpBitmap.scaleY = UtilLib.getRandomInt(scale / 2, scale * 2) / 10;
                tmpBitmap.x = UtilLib.getRandomInt(startX, width - tmpBitmap.width);
                tmpBitmap.y = UtilLib.getRandomInt(startY, height - tmpBitmap.height);
                container.addChild(tmpBitmap);
            }

            //draw red clouds
            drawAmount = UtilLib.getRandomInt(0, 2);
            for(i = 1; i <= drawAmount; i++)
            {
                tmpBitmap = dupeBitmap(assets[3]);
                tmpBitmap.alpha = UtilLib.getRandomInt(2, 6) / 10;
                scale = UtilLib.getRandomInt(5, 40) / 10;
                tmpBitmap.scaleX = scale; tmpBitmap.scaleY = scale;
                tmpBitmap.x = UtilLib.getRandomInt(startX, width - tmpBitmap.width);
                tmpBitmap.y = UtilLib.getRandomInt(startY, height - tmpBitmap.height);
                container.addChild(tmpBitmap);
            }

            //convert all layers to a single bitmap layer and return
            var bitmapData:BitmapData = new BitmapData(width, height, true, 0x000000);
            bitmapData.draw(container);
            container = null;
            var bitmapContainer:Bitmap = new Bitmap(bitmapData);
            //bitmapContainer.cacheAsBitmap = true;
            return bitmapContainer;
        }
    }
}

Best Answer

ok, this should show you can really get another category of numbers with other aproaches ...

the limit here is not the number of stars, the limit is density, i.e. the number of stars visible at the same time ... with text disabled, i can get up to 700 @ 30fps, on a Core2Duo, with quite a recent version of the debug player ...

i realized, flash player is not very good at clipping ... and that actually, using the most simple way, you spend a whole lot of time moving around objects, that are far from being visible ...

to really be able to optimize things, i chose to use MVC here ... not in the classic bloated way ... the idea is to handle the model, and if any elements are visible, create views for them ...

now the best aproach is to build up a spatial tree ...

  1. you have leaves, containing objects, and nodes containing leaves or nodes
  2. if you add an object to a leaf and it surpases a certain size, you turn it into a node with nxn leaves, redestributing its children between
  3. any object added to the background will be added to a grid, determined by the object's coordinates ... grids are created just-in-time, an start off as leaves

the big advantage of this is, that you can quickly isolate the visible nodes/leaves. in each iteration, only the nodes/leaves which either turn visible, or are already visible (and may become invisible), are interesting. you need not do any updates in the rest of the tree. after finding all the visible objects, you create views for objects that turn visible, update the position of those that simply stay visible, and destroy views for objects that become invisible ...

this saves an awful lot of everything ... memory and computation power ... if you try with a huge world size (100000), you will see, that you run out of RAM quickly, long before CPU does anything ... instantiating 500000 stars uses 700MB ram, with about 50 stars visible, running at 70 fps without any tremendous CPU usage ...

the engine is just a proof of concept ... and code looks awful ... the only feature i am currently proud about is, that it supports object to occupate a certain area, which is why an object can be part of several leafs ... i think, this is something i will remove though, because it should give me a great speed up ... you also see, it stalls a little, while adding stars, which happens, when leafs flip to nodes ...

i am quite sure, this is not the best way for a spatial subdivision tree, it's just that everything i found seemed kind of useless to me ... probably someone who studied (and understood) CS, can refine my approach ... :)

other than that, i used an object pool for the views, and Haxe, since it performs better ... a thing that probably is not so clever, is to move all the views individually, instead of putting them on one layer and moving that around ...

some people also render things into BitmapDatas manually, to gain performance, which seems to work quite well (just can't find the question right now) ... you should however consider using copyPixels instead of draw ...

hope this helps ... ;)


edit: i decided to turn my detailed answer into a blog post ... have fun reading ... ;)


Related Topic