dcsimg
A Flash Developer Resource Site

Results 1 to 12 of 12

Thread: Best technique for huge backgrounds?

  1. #1
    Junior Member
    Join Date
    Jul 2009
    Posts
    4

    Best technique for huge backgrounds?

    Hey guys,

    I'm working on a top-down car "open world"-ish game that uses Box2D for the Physics. Long story short, I recently realized that my world is 40,000 by 40,000px in size. I'm working on scaling things down, though I don't expect to make it small enough to be realistic.

    What is sort of the "best" technique when dealing with huge backgrounds?

    I'm guessing a single background image would not be the best way to go, as it's extremely huge -- even with JPG compression.

    A tile engine seems more realistic to use, though still memory intensive. If I were to use 128x128px tiles, a 300x300 array would be required to store the map. This would be about 270kB+ per map (if stored in external XML files, or embedded into the SWF). I did some testing in Flash, and initializing 300x300 array with random values from 1-10 eats up about 6MB of memory. For a flash game, is this a lot? I haven't really developed for Flash that much, so I don't know what it can and cannot handle.

    I started a thread on the Box2D forums before I realized it wasn't a Box2D question, but rather a general Flash question. In the thread, VengantMjolnir suggested I only display tiles that are currently visible and buffer those that could possible be visible when the player moves in either direction. Would something like this be exponentially efficient?

    I also ran across Tiled, a tile map editor, that seems pretty interesting in the way it outputs the maps. Would a base64 encoding to the XML files improve performance? Or would the extra CPU to decode the files negate the efforts in saving file size?

    Are there any other threads, or tutorials, regarding this sort of tile-based background for large areas? I ran across a couple, including this scrolling tile tutorial.

    Any suggestions would be highly appreciated!

    Cheers,
    -robodude666

  2. #2
    Please, Call Me Bob trogdor458's Avatar
    Join Date
    Aug 2006
    Location
    Pensacola, FL
    Posts
    915
    270kB does not seem like much considering a 128x128 tile takes up... 48kB?

    Arrays are definately the way I'd go, but only if you have a repetive map
    Breaking up a humongous picture into tiny slices that are all different wouldn't save file size, just rendering time me thinks

    I made a tile system once to experiment with, what do you want to know?

    And does a 300x300 array really take that much memory? I'm surprised, I don't know much about the underlying mechanisms in Flash, but arrays can store a variety of information, and may be the reason for the large memory size

    90,000 (300x300) values worth 8 bytes each would only take up a fraction of that amount

    -shrugs-

    I'm sure someone knows

  3. #3
    Funkalicious TOdorus's Avatar
    Join Date
    Nov 2006
    Location
    Nijmegen, Netherlands
    Posts
    697
    Quote Originally Posted by robodude666 View Post
    A tile engine seems more realistic to use, though still memory intensive. If I were to use 128x128px tiles, a 300x300 array would be required to store the map. This would be about 270kB+ per map (if stored in external XML files, or embedded into the SWF). I did some testing in Flash, and initializing 300x300 array with random values from 1-10 eats up about 6MB of memory. For a flash game, is this a lot? I haven't really developed for Flash that much, so I don't know what it can and cannot handle.
    I don't think so. There are games out there which use tilesheets for rendering which use tilesheets for rendering and have several big tilebased levels. They should easily surpass 6MB. In a more taxing project I actually stuff the users memory with about 300MB of data, so I think you're quite ok.


    Quote Originally Posted by robodude666 View Post
    I started a thread on the Box2D forums before I realized it wasn't a Box2D question, but rather a general Flash question. In the thread, VengantMjolnir suggested I only display tiles that are currently visible and buffer those that could possible be visible when the player moves in either direction. Would something like this be exponentially efficient?
    That is a movieClip technique. Since you use bitmapData you're better of blitting because it is much, much faster and doesn't require you to keep track of objects offscreen.

    http://www.8bitrocket.com/newsdispla...newspage=17171

  4. #4
    Pumpkin Carving 2008 ImprisonedPride's Avatar
    Join Date
    Apr 2006
    Location
    Grand Rapids MI
    Posts
    2,379
    CopyPixels + 2d array of bitmapdata objects?
    The 'Boose':
    ASUS Sabertooth P67 TUF
    Intel Core i7-2600K Quad-Core Sandy Bridge 3.4GHz Overclocked to 4.2GHz
    8GB G.Skill Ripjaws 1600 DDR3
    ASUS ENGTX550 TI DC/DI/1GD5 GeForce GTX 550 Ti (Fermi) 1GB 1GDDR5 (Overclocked to 1.1GHz)
    New addition: OCZ Vertex 240GB SATA III SSD
    WEI Score: 7.6

  5. #5
    Funkalicious TOdorus's Avatar
    Join Date
    Nov 2006
    Location
    Nijmegen, Netherlands
    Posts
    697
    Quote Originally Posted by ImprisonedPride View Post
    CopyPixels + 2d array of bitmapdata objects?
    I'm more of an integer kind of guy, but that could work the same. I don't know about how much memory a map will take in that case though.

  6. #6
    Junior Member
    Join Date
    Jul 2009
    Posts
    4
    Thanks for the replies!

    Quote Originally Posted by TOdorus View Post
    I don't think so. There are games out there which use tilesheets for rendering which use tilesheets for rendering and have several big tilebased levels. They should easily surpass 6MB. In a more taxing project I actually stuff the users memory with about 300MB of data, so I think you're quite ok.

    That is a movieClip technique. Since you use bitmapData you're better of blitting because it is much, much faster and doesn't require you to keep track of objects offscreen.

    http://www.8bitrocket.com/newsdispla...newspage=17171
    Well, that's good. Isn't 300MB a little over the top? What's a good memory usage for a flash game? Something that gives room for a lot of assets, but doesn't eat the user's computer alive.

    I haven't actually learned much about BitmapData, to be honest. Aside from my week or two with AS3, my only experience with Flash development was back in the 5/MX days.

    If I have a BitmapData to contain the background, all I need to do is fill it up with the tiles and not worry about removing them if they're not on screen? Wouldn't a huge BitmapData rob CPU cycles, or are they very efficient compared to placing a ton of Sprites on screen?

    Would Tonypa's tile techniques work well for my needs, or would 8bitrocket's approach work better?

    Thanks!
    -robodude666

  7. #7
    Please, Call Me Bob trogdor458's Avatar
    Join Date
    Aug 2006
    Location
    Pensacola, FL
    Posts
    915
    The great thing about the bitmap method is it doesn't need to be huge
    What I did was make a bitmap about the size of the flash window, plus a little extra

    After that's done, you just copy your 128x128 bits of tiles onto the bitmap whenever they need to appear, they'll automatically delete themselves if you use one of those bitmap scrolling functions

    That's the whole point of the array, is to keep track of which tiles need to appear, and where

    The first tile system I made used movieclips
    The second tile system I made used the bitmap

    The difference in speed was almost embarrassing

  8. #8
    Junior Member
    Join Date
    Jul 2009
    Posts
    4
    Ahhh! So, basically the Bitmap method draws the tiles onto the screen whenever the character moves? It gets rerendered every time, thus only showing those specific tiles that can be viewed?

    I haven't actually read the 8bitrocket article in great depth. Just looked over the code samples and slimmed it. So, sorry if it was explained in the article!

    I'm going to grab some foodage, and read the article in full.

    Cheers,
    -robodude666

  9. #9
    Funkalicious TOdorus's Avatar
    Join Date
    Nov 2006
    Location
    Nijmegen, Netherlands
    Posts
    697
    I believe 8bitrocket and tonypa both use tile blitting. 8bitrocket does take the optimizing a little further though but it's the same logic. The thing with using the copypixel method for your rendering, is that you can do it a few tenthousand times a second (and that's with game logic running alongside). So if you still update a lot of tiles every frame it doesn't really matter, it's just that fast. So most blitting based engines use copypixels exclusively. What you do is take one bitmapData, and copypixel your layers on there (first your tiles, then your characters then your projectiles for example). You could also use a seperate bitmapData and bitmap for tiles, so you only need to update those when the screen scrolls, but you can get away with not doing it.

  10. #10
    Junior Member
    Join Date
    Jul 2009
    Posts
    4
    That's pretty cool! I sat down and tried to do the 8bitrocket tutorial. Oh man! That is probably the worst written tutorial I have ever seen in my entire life.

    Do you know of a different one that actually includes the source files, or where to get source files for this one?

    I learn best by example, rather than by random snippets of code that won't function without code that's not included. There are a lot of pieces of info missing that won't allow it to work. For example, the buffer/tile points & rectangles... What should their default values be?

    I got through part 2 and ended up with this, but it doesn't seem to work. The car can rotate (very slowly), but the background doesn't update. I also made the tilemap a lot smaller, as I don't feel like writing 100x100 manually at the moment.

    Code:
    var aWorld:Array = [
    					[0,0,0,0,0,0,0,0,0,0],
    					[0,0,0,0,0,0,0,0,0,0],
    					[0,0,2,2,2,2,2,2,0,0],
    					[0,0,2,2,2,2,2,2,0,0],
    					[0,0,2,2,2,2,2,2,0,0],
    					[0,0,2,2,2,2,2,2,0,0],
    					[0,0,2,2,2,2,2,2,0,0],
    					[0,0,2,2,2,2,2,2,0,0],
    					[0,0,0,0,0,0,0,0,0,0],
    					[0,0,0,0,0,0,0,0,0,0],
    					];
    
    var mapTileHeight:int = 16;
    var mapTileWidth:int = 16;
    
    var viewWidth:int = 100;
    var viewHeight:int = 100;
    
    var worldCols:int = 10;
    var worldRows:int = 10;
    
    var worldWidth:int = worldCols * mapTileWidth;
    var worldHeight:int = worldRows * mapTileHeight;
    
    var viewCols:int = viewWidth / mapTileWidth;
    var viewRows:int = viewHeight / mapTileHeight;
    
    var viewXOffset:int = 0;
    var viewYOffset:int = 0;
    
    var sprite16x16:BitmapData;
    var sprite16x16_perRow:int = 10;
    var canvasBD:BitmapData;
    var bufferBD:BitmapData;
    var canvasBitmap:Bitmap;
    
    var carSprite:Sprite = new carGraphic();
    var carRotation:Number = -90;
    var carTurnSpeed:Number = 0.5;
    var carMaxSpeed:Number = 5;
    var carAcceleration:Number = .02;
    var carDeceleration:Number = .03;
    var carSpeed:Number = 0;
    
    
    function init():void
    {
    	
    	canvasBD = new BitmapData(viewWidth, viewHeight, false, 0x000000);
    	bufferBD = new BitmapData(viewWidth + 2 * mapTileWidth, viewHeight + 2 * mapTileHeight, false, 0x000000);
    	canvasBitmap = new Bitmap(canvasBD);
    
    	stage.addChild(canvasBitmap);
    	
    	viewXOffset = 0;
    	viewYOffset = 0;
    	
    	sprite16x16 = new BitmapData(160, 160, false, 0xff00ff);
    	sprite16x16.draw(new spriteGraphic(160, 160));
    	
    	carSprite.x = viewWidth / 2;
    	carSprite.y = viewHeight / 2;
    	
    	stage.addChild(carSprite);
    	stage.addEventListener(KeyboardEvent.KEY_DOWN,keyDownListener);
    	stage.addEventListener(KeyboardEvent.KEY_UP,keyUpListener);
    }
    
    var aKeyPress:Object = new Object();
    
    function keyDownListener(e:KeyboardEvent) {
    	aKeyPress[e.keyCode] = true;
    }
    
    function keyUpListener(e:KeyboardEvent) {
    	aKeyPress[e.keyCode] = false;
    }
    
    function checkKeys():void
    {
    	if (aKeyPress[Keyboard.UP]){
            carSpeed+=carAcceleration;
            if (carSpeed >carMaxSpeed) carSpeed=carMaxSpeed;
        }
    	
        if (aKeyPress[Keyboard.DOWN]){
            carSpeed-=carDeceleration;
            if (carSpeed <-carMaxSpeed) carSpeed=-carMaxSpeed;        
        }
    	
        if (aKeyPress[Keyboard.LEFT]){       
            carRotation += (carSpeed+.1)*carTurnSpeed;        
        }
    	
        if (aKeyPress[Keyboard.RIGHT]){
            carRotation -= (carSpeed+.1)*carTurnSpeed;
        }
    
    }
    
    function updatePlayer():void
    {	
    	carSprite.rotation=carRotation;
    	var carRadians:Number = (carRotation / 360) * (2.0 * Math.PI);
    	var carDX:Number = Math.cos(carRadians) * carSpeed;
    	var carDY:Number = Math.sin(carRadians) * carSpeed;
    	
    	viewXOffset += carDX;
    	viewYOffset += carDY;
    		
    	
    	if (viewXOffset < 0) {
    		viewXOffset = 0;
    	}else if (viewXOffset > (worldWidth - viewWidth) - 1) {
    		viewXOffset = (worldWidth - viewWidth) - 1;
    	}
    	
    	if (viewYOffset < 0) {
    		viewYOffset = 0;
    	}else if (viewYOffset > (worldHeight - viewHeight) - 1) {
    		viewYOffset = (worldHeight - viewHeight) - 1;
    	}
    }
    
    		
    function drawView():void
    {
    	
    	var tilex:int = int(viewXOffset / mapTileWidth);
    	var tiley:int = int(viewYOffset / mapTileHeight);
    		
    	var rowCtr:int;
    	var colCtr:int; 
    	
    	var tileNum:int; 
    	
    	var tilePoint:Point = new Point();
    	var tileRect:Rectangle = new Rectangle(0, 0, 16, 16);
    	var bufferPoint:Point = new Point(viewXOffset, viewYOffset);
    	var bufferRect:Rectangle = new Rectangle(0, 0, viewWidth, viewHeight);
    
    	
    	
    	for (rowCtr = 0; rowCtr <= viewRows; rowCtr++) {
    		for (colCtr = 0; colCtr <= viewCols; colCtr++) {
    			tileNum = aWorld[rowCtr + tiley][colCtr + tilex];
    			tilePoint.x = colCtr * mapTileWidth;
    			tilePoint.y = rowCtr * mapTileHeight;
    			tileRect.x = int((tileNum % sprite16x16_perRow)) * mapTileWidth;
    			tileRect.y = int((tileNum / sprite16x16_perRow)) * mapTileHeight;
    			canvasBD.copyPixels(sprite16x16, tileRect, tilePoint);
    		}
    	}
    	
    	
    	bufferRect.x = viewXOffset % mapTileWidth;
    	bufferRect.y = viewYOffset % mapTileHeight;
    	canvasBD.copyPixels(bufferBD, bufferRect, bufferPoint);
    }
    
    init();
    
    stage.addEventListener(Event.ENTER_FRAME, onLoop);
    
    function onLoop(e:Event):void
    {
    	checkKeys();
    	updatePlayer();
    	drawView();
    }
    I'm working in frame1 until I figure it out, then I'll move into Flex and make a proper class.

    Any ideas on what I did wrong?

    Quote Originally Posted by TOdorus View Post
    You could also use a seperate bitmapData and bitmap for tiles, so you only need to update those when the screen scrolls, but you can get away with not doing it.
    I currently have a camera class that extends Sprite. The camera class has two other sprites inside of it called "ground" and "objects" respectively. I was hoping for using the ground "layer" for the terrain, blood, skid marks, etc. while the objects sprite would be for the hero, baddies, trees, etc. Would I be able to keep that sort of system? Instead of keeping the floor in the "ground" sprite I could move it to a new bitmap, which is fine with me. Or is there a better way of handling this?


    Thanks!
    -robodude666
    Last edited by robodude666; 07-18-2009 at 04:22 PM.

  11. #11
    Funkalicious TOdorus's Avatar
    Join Date
    Nov 2006
    Location
    Nijmegen, Netherlands
    Posts
    697
    This is how I do tiles (there are probably some bugs in this, as it's from the top of my head)

    Code:
    private var tileWidth:int
    private var tileHeight:int
    private var screenWidth:int
    private var screenHeight:int
    private var Screen:Bitmapdata // the canvas
    function drawTiles():void{
       var ULScreenPos:Point = new Point (screenX,screenY) //the coordinate of the upperleft of the screen in the game world. I think you call this xOffset and yOffset.
       var DRScreenPos = new Point(URScreenPos.x + screenWidth, URScreenPos.y + screenHeight)//Downright corner.
       //check in which tiles the upper left and down right corners are.
       var ULtilePos:Point = convertToTilePos(ULSCreenPos)
       var DRtilePos:Point = convertToTilePos(DRSCreenPos)
       //Now you can get the tiles that the screen overlaps
       var dX:int = DR.x - UL.x
       var dY:int = DR.y - UL.y
       //the topleft corner of a tile
       var tileX:int
       var tileY:int
       //the coordinates in actual pixels
       var screenX:int
       var screenY:int
       //variable bitmapdata to load in variable tiles
       var tileBitmapData:Bitmapdata
       for(tileX = ULtilePos.x; tileX <= ULtilePos.x+dX; tileX ++){
           for(tileY = ULtilePos.y; tileY <= ULtilePos.y + dY; tileY++){
               tileBitmapData = getTileBitmap(tileX, tileY)//you should have a 2D array or some other kind of storage where the loop can easily find the tile needed
               screenX = (tileX*tileWidth) - ULScreenPos.x
               screenY = (tileY*tileHeight) - ULScreenPos.y
               Screen.copypixels(tileBitmapdata, tileBitmapData.rectangle, new Point(screenX, screenY))
           }
       }
    } 
     
    function convertToTilePos(SCREENPOS:Point):Point{
       var TilePos:Point = new Point (Math.floor(SCREENPOS.x/tileWidth), Math.floor(SCREENPOS.y/tileHeight))
       return(TilePos)
    }
    And my main render loop would look like this
    Code:
    Screen.lock()
    wipe() //copypixel a totally black or white bitmapdata to Screen, to Wipe the previous frame of Screen.
    updateTiles()
    updateObjects()
    Screen.unlock()
    Everything gets drawn to the Screen bitmapdata by using copypixels and when everything is updated the bitmapdata is unlocked so the bitmap gets updated only once.

    Sorry I had some trouble understanding the total code you posted, as it differs slightly from my approach. From what I gather it should work, but you could try and just use a bitmapdata containing a single tile for debugging.
    Code:
        tilePoint.x = colCtr * mapTileWidth;
        tilePoint.y = rowCtr * mapTileHeight;
        canvasBD.copyPixels(sprite16x16, sprite16x16.tileRect, tilePoint);

  12. #12
    Senior Member
    Join Date
    Nov 2005
    Posts
    192
    6 mb of memory is not a lot. The player alone uses like 15. Honestly, you should just make your game as fast as possible at whatever cost. Most people nowadays have at least 2 gb of memory.

    edit:
    If many of your graphics are rotated or flipped, I can show you the system I used to eliminate having to do this externally such as in the gif.

    edit2:
    I guess I might as well post it.

    The only disadvantage to what I'm doing is that it required a pretty complex editor to efficiently handle rotating the tiles and to build the map
    Code:
    override public function redraw():void {	 	
    			var baseLayer:Vector.<Vector.<Tile> > = currentMap.getBase();
    			var sheets:Vector.<TileSheet> = currentMap.getSheets()
    			var xLower:int = cameraX / tileSize;
    			var yLower:int = cameraY / tileSize;
    			
    			var xUpperN:Number=(cameraX + screenW ) / tileSize;
    			var xUpper:int = xUpperN == int(xUpperN) ? xUpperN: int(xUpperN) + 1;//math ceil
    		
    			var yUpperN:Number = (cameraY + screenH ) / tileSize;
    			var yUpper:int = yUpperN == int(yUpperN) ? yUpperN: int(yUpperN) + 1;// math ceil
    			scrbmd.lock();
    			for (var y:int = yLower; y < yUpper; ++y) {
    				for (var x:int = xLower; x < xUpper; ++x) {
    					var tile:Tile = baseLayer[y][x];
    					var sheet:int = tile.sheet;
    					var tSheet:TileSheet = sheets[sheet];
    					
    					p.x = x * tileSize - cameraX;
    					p.y = y * tileSize - cameraY;
    					if(tile.rotate!=0 ){
    						scrbmd.copyPixels(tSheet.getRTile(tile.tileNumber, tile.rotate), rect2, p)
    
    					}
    					else if(tile.flip!=0){
    						scrbmd.copyPixels(tSheet.getFTile(tile.tileNumber, tile.flip), rect2, p);
    					} 
    					else{
    						rect.x = tSheet.getTileCol(tile.tileNumber) * tileSize; 
    						rect.y = tSheet.getTileRow(tile.tileNumber) * tileSize;
    						scrbmd.copyPixels(tSheet.getBMD(), rect, p);
    					}
    					
    				}
    			}
    			scrbmd.unlock();
    }
    		private var rect:Rectangle = new Rectangle(0, 0, tileSize, tileSize);
    		private var rect2:Rectangle = new Rectangle(0, 0, tileSize, tileSize);
    		private var p:Point = new Point();
    Basically each of my tile objects has a sheet index number, and a flip and rotate value. Based on this number it retrieves the appropriate flipped or rotated tile from a dictionary cache stored in my TileSheet class. The dictionary cache of rotated/flipped bitmapdatas is created when the map starts. My map data has all the information about what tiles need to be cached. Originally I tried flipping every single tile on the spritesheet individually to create a flipped version but this was entirely unnecessary and took much longer than I would have liked when building the map. If flip and rotate are 0, then it just uses the normal sprite sheet and the rect/point to crop it.

    Bitmap draw() is incredibly slow compared to copyPixels so rotating each graphic on the sheet 3 times, flipping it horizontally and vertically, for each sprite sheet... you get the idea. So I created a system and editor that would only add the used rotated/flipped tiles to the cache.
    Last edited by Flyingcow; 07-19-2009 at 06:25 PM.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  




Click Here to Expand Forum to Full Width

HTML5 Development Center