views:

775

answers:

6

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;
     }
    }
}
+2  A: 

You may want to see if you can blit all of the pieces together into a flattend Bitmaps as you go. Draw all of the layers and then use BitmapData's draw method to combine them into a single Bitmap.

Even with cacheAsBitmap on for all of the pieces Flash is still having to combine all of those pieces every frame.

Branden Hall
The end result is already one image, the container. I can not make a static image with an external program as the objects are positioned, scaled, alpha(-ed?) and rotated randomly.
Tom
Well, as best I can tell from the code you posted, the result is a Sprite that contains multiple Bitmaps.I'm not suggesting you use any external program, instead I'm suggesting that after you create a square you flatten all of the bitmaps into a single bitmap just by using BitmapData.draw to capture the composed bitmaps. Then you throw away the pieces. Now flash only has to blit one bitmap, not four per square.
Branden Hall
This is definitely the first thing you should try.
fenomas
If I throw away the containers, how can I remove them when the player is moving too far away from them while still retaining the nearby containers?
Tom
Well, that's the thing - you technically don't throw them away - you just don't display them. You never add the sprite that contains all of your assets (stars, clouds, etc) to the display list. Instead you just keep capturing out sections of it using the draw method of BitmapData. You just shuffle around those pieces like any tile-based game would.
Branden Hall
I've no idea what you mean other than that you want me to use the BitmapData.draw method. Could you maybe give an example or something?
Tom
Right now your object sends back a sprite. Make it send back a Bitmap instead. Create that bitmap by defining a BitmapData object set to the size of your square. Then populate it by calling myBitmapData.draw(container). Push that into a bitmap object - myBimtap = new Bitmap(myBitmapData) and just return that object. The result is that you've flattened each square of your world into a single bitmap rather than dozens.
Branden Hall
Oh - and you'll do this at the end of your method once you've created everything. Also, you may want to look into making smaller squares (tiles). That way you could generate a few dozen random tiles and just reuse them.
Branden Hall
Alright, well the problem I have is that when I change the container type in the function to a bitmap I can not add childs to it (the starts and clouds etc.). How should I add them to the bitmap container then?
Tom
starts = stars*
Tom
The container will still be a sprite. You're just capturing the sprite into a bitmap. The function is just changing it's return type.
Branden Hall
Thanks, I think it's better now. However it's still taking too much performance on the actual loading of the container. Especially the 500 simple stars seem to hit performace a lot. Do you have any other suggestions?
Tom
I figured I might be able to let flash draw those stars by itself, as they are nothing else but 1x1 white pixels. Not sure what would be the best way though and if it even is a good idea.
Tom
Note that with this bitmap method memory usage is peaking at 75MB instead of 4MB. But I guess it's worth it.
Tom
I updated the thread with my complete class.
Tom
+3  A: 

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 ... ;)


back2dos
Great to see you popping up so often with answers just as great. :) I have a few questions though. With "leaves" do you mean an array containing all objects (stars etc.)? Are nodes like containers (another array) containing the arrays with leaves? If yes, I think it is comperable with my old setup. Second question: should I poll to see if an object is out of view range, and if so set .visible = false? Question 3: if I add all containers to one master container (suggested at the bottom for movement), how should I move only visible containers separately? And why should I do that, since...
Tom
...since it will only change the master container's X and Y, not actually causing the non-visible parts to be rendered, right? It's just a variable. Question 4: the link of what you made looks nice. Is the source public? Or can I see how you've done it? Would maybe answer some of my questions. Question 5: what do you mean with an object pool, just an array with objects? Question 6: haXe looks great, first time I see it. Do you recommend me to learn that language and rewrite the whole client again with that language? It's meant for a huge project, a 2d game as you probably know by now. Thanks!
Tom
Looks like the bounty will end soon even though I have so many questions left unanswered. I can't really accept your answer without them though.
Tom
A: 

Try stretching the window of the player, both bigger and smaller. If that has a significant effect on frame rate, your fastest and easiest way to improve performance is to shrink the size of the stage. This tends to be an unpopular answer when presented to people - especially artists - but if your bottleneck is in the size of your stage, there is not much you can do in code to fix that.

Sean
The averga framerate is actually no problem at all. It's the instant loading of a new container that is causing a problem for a few milliseconds.
Tom
+1  A: 

What if instead of destroying background squares you just put them in a pile of "ready to go" squares that you can draw on, capping it at like 4? then you don't have to create one when you need one you just move it into the right spot and maybe shuffle the stars or something.

[would add example but i don't write AS3 code :(]

RCIX
Since all objects inside 1 square are being pushed into 1 layer, I can't change any of the invidiual object priorities like X and Y of each star. Not pushing them into one layer doesn't seem to be an option either as it'd cost too much performance.
Tom
Good point. Can you live with a repeating background then? :)
RCIX
Afraid not, mate.
Tom
Hmm.... What if you cache an object that is built but not flattened then you just go to that and shuffle it and obtain a flattened copy of that image when you need a new one?
RCIX
Interesting idea, I will play around with it. Thanks.
Tom
+1  A: 
  • You should be able to simplify some of your math by using stored variables instead of stageCenterX + stageWidth * 0.75, and similar since they don't change.
  • Have you considered using HitTestPoint instead of doing the math to check positions of containers? It's a native function, so it might be faster.
  • You should use a Shape instead of a Sprite if you don't need to add children to the object. e.g., your star. This might help quite a bit.
  • What if you created a set of star backgrounds at the start of the program. Then converted them to bitmaps, and saved them for later reuse. e.g., create a star background, convert it to bitmap data, and save this in an array. Do this, say, 10 times, and then when you need a background to just randomly select one, and apply your other shapes to it. The benefit of doing this is that you don't have to render 100-250 Sprites or Shapes each time you create a new background--that takes time.

EDIT: New idea:

  • Maybe you can play with the idea of only drawing the stars on the backgrounds rather than adding individual objects. The number of objects added to the screen are a big part of the problem. So I'm suggesting you draw the stars on the container directly, but with different sizes and alphas. Then scale the container down so that you get the effect you're looking for. You could reduce the display footprint from 500-1000 stars down to 0. That would be a huge improvement if you can get the effect you need from it.
Glenn
I'm aware of little things within calculations that make the application potentially slower than it could be. However, these are definitely not what's causing my problem as the total delay is an unnoticeable amount of milli or even micro seconds. For now, these calculations are easier to understand which allows me to improve the general algoritm better, resulting in faster code. When I know what background system I am going to use, I could do optimizations like constant calculation variables. The shape is a good call, thanks. I will play around with the last dot, seems like a valid point.
Tom
If you try my last suggestion, do it both with the current bitmap method and with the objects in their original shapes. I'm not entirely convinced that making the entire thing bitmaps is the way to go. You might be able to keep your memory low if you store everything in their native format.
Glenn
I mean 2nd to last. Just added another idea.
Glenn
+1  A: 

Try using a single large BitmapData with it's own Bitmap, larger than the stage (although you might hit the limits of BitmapDatas if you have a really big stage and/or are using flash 9), and drawing new background images to it using the copyPixels method (a really fast way of copying pixels, faster than draw(), at least as far as I've seen).

Pan the large Bitmap around when you want and when you reach an edge, pan the bitmap to the other side, copyPixels the whole thing back to where it was previously (so the image should stay in the same place relative to the stage, but the Bitmap should move) and copyPixels new images where they are missing.

Since you are using alpha for the images as well, so you might want to check all the parameters of copyPixels if it doesn't copy alpha as you wanted it to (probably mergeAlpha?).

So, to recap, use a single large Bitmap that extends well over the boundaries of the stage, have the images ready as BitmapDatas, do the wrap trick and fill in the blanks with copyPixels from images.

I don't know if this way would perform better (the copyPixels over the whole bitmap worries me a little), but it's definitely something to try. Good luck :)

SmilyOrg