ProgressiveTilemap - Only render in tilemap what's actually on screen


(Abel Toy) #1

Here’s my ProgressiveTilemap class.

It’s a copy of the FlashPunk Tilemap class but with some optimisations. Instead of using a buffer the size of the map, it uses one the size of the screen and redraws the map when the camera is scrolled, thus reducing memory consumption by a lot for large maps and not really affecting process time.

##Version 0.5: This is an unfinished version. The graphic’s x and y position aren’t implemented. Scroll X and Y isn’t implemented. Tilemap’s usePositions isn’t implemented. The majority of the Tilemap API isn’t implemented. Still, it works for the basic tilemaps. Not documented.

package com.abeltoy.utils
{
	import flash.display.BitmapData;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.utils.getTimer;
	import net.flashpunk.FP;
	import net.flashpunk.Graphic;
	
	/**
	 * ...
	 * @author Abel Toy
	 */
	public class ProgressiveTilemap extends Graphic
	{
		private var _posX:int = 0;
		private var _posY:int = 0;
		
		private var _lastCamColumn:int = 0;
		private var _lastCamRow:int = 0;
		
		private var _width:uint;
		private var _height:uint;
		private var _columns:uint;
		private var _rows:uint;
		
		private var _map:Vector.<uint>;
		
		private var _redraw:Boolean = false;
		private var _bitmap:BitmapData;
		private var _tile:Rectangle;
		
		private var _set:BitmapData;
		private var _setColumns:uint;
		private var _setRows:uint;
		private var _setCount:uint;
		
		private var _screenTileW:int;
		private var _screenTileH:int;
		
		public function ProgressiveTilemap(tileset:*, width:uint, height:uint, tileWidth:uint, tileHeight:uint)
		{
			super();
			
			_width = width;
			_height = height;
			_columns = Math.ceil(_width / tileWidth);
			_rows = Math.ceil(_height / tileHeight);
			
			_map = new Vector.<uint>(_columns * _rows, true);
			
			_screenTileW = Math.ceil(FP.width / tileWidth);
			_screenTileH = Math.ceil(FP.height / tileHeight);
			
			_bitmap = new BitmapData(_screenTileW * tileWidth + tileWidth * 2, _screenTileH * tileHeight + tileHeight * 2, true, 0);
			_tile = new Rectangle(0, 0, tileWidth, tileHeight);
			_point = new Point();
			
			if (tileset is Class)
				_set = FP.getBitmap(tileset);
			else if (tileset is BitmapData)
				_set = tileset;
			if (!_set)
				throw new Error("Invalid tileset graphic provided.");
			_setColumns = Math.ceil(_set.width / tileWidth);
			_setRows = Math.ceil(_set.height / tileHeight);
			_setCount = _setColumns * _setRows;
		}
		
		public function setTile(column:uint, row:uint, index:uint = 0):void
		{
			column %= _columns;
			row %= _rows;
			index %= _setCount;
			
			_map[(row * _columns) + column] = index;
			
			_redraw = true;
			//TODO: only redraw when the set tile is on camera.
		}
		
		public function getTile(column:uint, row:uint):uint
		{
			return _map[((row % _rows) * _columns) + (column % _columns)];
		}
		
		private function _draw(offsetX:int, offsetY:int):void
		{
			var x:int = offsetX - 1;
			var y:int = offsetY - 1;
			var w:int = _screenTileW + offsetX + 1;
			var h:int = _screenTileH + offsetY + 1;
			
			if (x < 0) x = 0;
			if (y < 0) y = 0;
			if (w > _columns) w = _columns;
			if (h > _rows) h = _rows;
			
			while (y < h)
			{
				while (x < w)
				{
					var index:int = _map[(y * _columns) + x];
					_drawTile(x - offsetX, y - offsetY, index);
					
					x++;
				}
				
				x = offsetX;
				y++;
			}
		}
		
		private function _drawTile(x:uint, y:uint, index:uint):void
		{
			_tile.x = (index % _setColumns) * _tile.width;
			_tile.y = uint(index / _setColumns) * _tile.height;
			_point.x = x * _tile.width;
			_point.y = y * _tile.height;
			
			_bitmap.copyPixels(_set, _tile, _point);
		}
		
		override public function render(target:BitmapData, point:Point, camera:Point):void 
		{
			var camColumn:int = Math.floor(camera.x / _tile.width);
			var camRow:int = Math.floor(camera.y / _tile.height);
			
			if (camColumn != _lastCamColumn || camRow != _lastCamRow) _redraw = true;
			
			if (_redraw)
			{
				_draw(camColumn, camRow);
				
				_redraw = false;
				_lastCamColumn = camColumn;
				_lastCamRow = camRow;
			}
			
			_posX = - camera.x % _tile.width;
			_posY = - camera.y % _tile.height;
			
			_point.x = _posX;
			_point.y = _posY;
			
			//TODO: implement x and y position
			//TODO: implement scrollX and scrollY
			
			target.copyPixels(_bitmap, _bitmap.rect, _point, null, null, true);
		}
		
		//TODO: implement Tilemap additional functions
		//TODO: implement usePositions
		
		public function loadFromString(str:String, columnSep:String = ",", rowSep:String = "\n"):void
		{
			var row:Array = str.split(rowSep),
				rows:int = row.length,
				col:Array, cols:int, x:int, y:int;
			for (y = 0; y < rows; y ++)
			{
				if (row[y] == '') continue;
				col = row[y].split(columnSep),
				cols = col.length;
				for (x = 0; x < cols; x ++)
				{
					if (col[x] == '' || col[x] == "-1") continue;
					setTile(x, y, uint(col[x]));
				}
			}
		}
		
		public function get tileWidth():uint
		{
			return _tile.width;
		}
		
		public function get tileHeight():uint
		{
			return _tile.height;
		}
		
		public function get tileCount():uint
		{
			return _setCount;
		}
		
		public function get columns():uint
		{
			return _columns;
		}
		
		public function get rows():uint
		{
			return _rows;
		}
	}
}

Rendering visible terrain only