package net.flashpunk.graphics
{
import flash.display.BitmapData;
import flash.geom.Point;
import flash.geom.Rectangle;
import net.flashpunk.Graphic;
import net.flashpunk.FP;
import net.flashpunk.masks.Grid;
/**
* A canvas to which Tiles can be drawn for fast multiple tile rendering.
*/
public class Tilemap extends Canvas
{
/**
* If x/y positions should be used instead of columns/rows.
*/
public var usePositions:Boolean;
/**
* Constructor.
* @param tileset The source tileset image.
* @param width Width of the tilemap, in pixels.
* @param height Height of the tilemap, in pixels.
* @param tileWidth Tile width.
* @param tileHeight Tile height.
*/
public function Tilemap(tileset:*, width:uint, height:uint, tileWidth:uint, tileHeight:uint)
{
_width = width - (width % tileWidth);
_height = height - (height % tileHeight);
_columns = _width / tileWidth;
_rows = _height / tileHeight;
_map = new BitmapData(_columns, _rows, false, 0);
_temp = _map.clone();
_tile = new Rectangle(0, 0, tileWidth, tileHeight);
_maxWidth -= _maxWidth % tileWidth;
_maxHeight -= _maxHeight % tileHeight;
super(_width, _height);
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 = uint(_set.width / tileWidth);
_setRows = uint(_set.height / tileHeight);
_setCount = _setColumns * _setRows;
}
/**
* Sets the index of the tile at the position.
* @param column Tile column.
* @param row Tile row.
* @param index Tile index.
*/
public function setTile(column:uint, row:uint, index:uint = 0):void
{
if (usePositions)
{
column /= _tile.width;
row /= _tile.height;
}
index %= _setCount;
column %= _columns;
row %= _rows;
_tile.x = (index % _setColumns) * _tile.width;
_tile.y = uint(index / _setColumns) * _tile.height;
_map.setPixel(column, row, index);
draw(column * _tile.width, row * _tile.height, _set, _tile);
}
/**
* Clears the tile at the position.
* @param column Tile column.
* @param row Tile row.
*/
public function clearTile(column:uint, row:uint):void
{
if (usePositions)
{
column /= _tile.width;
row /= _tile.height;
}
column %= _columns;
row %= _rows;
_tile.x = column * _tile.width;
_tile.y = row * _tile.height;
fill(_tile, 0, 0);
}
/**
* Gets the tile index at the position.
* @param column Tile column.
* @param row Tile row.
* @return The tile index.
*/
public function getTile(column:uint, row:uint):uint
{
if (usePositions)
{
column /= _tile.width;
row /= _tile.height;
}
return _map.getPixel(column % _columns, row % _rows);
}
/**
* Sets a rectangular region of tiles to the index.
* @param column First tile column.
* @param row First tile row.
* @param width Width in tiles.
* @param height Height in tiles.
* @param index Tile index.
*/
public function setRect(column:uint, row:uint, width:uint = 1, height:uint = 1, index:uint = 0):void
{
if (usePositions)
{
column /= _tile.width;
row /= _tile.height;
width /= _tile.width;
height /= _tile.height;
}
column %= _columns;
row %= _rows;
var c:uint = column,
r:uint = column + width,
b:uint = row + height,
u:Boolean = usePositions;
usePositions = false;
while (row < b)
{
while (column < r)
{
setTile(column, row, index);
column ++;
}
column = c;
row ++;
}
usePositions = u;
}
/**
* Clears the rectangular region of tiles.
* @param column First tile column.
* @param row First tile row.
* @param width Width in tiles.
* @param height Height in tiles.
*/
public function clearRect(column:uint, row:uint, width:uint = 1, height:uint = 1):void
{
if (usePositions)
{
column /= _tile.width;
row /= _tile.height;
width /= _tile.width;
height /= _tile.height;
}
column %= _columns;
row %= _rows;
var c:uint = column,
r:uint = column + width,
b:uint = row + height,
u:Boolean = usePositions;
usePositions = false;
while (row < b)
{
while (column < r)
{
clearTile(column, row);
column ++;
}
column = c;
row ++;
}
usePositions = u;
}
/**
* Loads the Tilemap tile index data from a string.
* @param str The string data, which is a set of tile values separated by the columnSep and rowSep strings.
* @param columnSep The string that separates each tile value on a row, default is ",".
* @param rowSep The string that separates each row of tiles, default is "\n".
*/
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] == '') continue;
setTile(x, y, uint(col[x]));
}
}
}
/**
* Saves the Tilemap tile index data to a string.
* @param columnSep The string that separates each tile value on a row, default is ",".
* @param rowSep The string that separates each row of tiles, default is "\n".
*/
public function saveToString(columnSep:String = ",", rowSep:String = "\n"): String
{
var s:String = '',
x:int, y:int;
for (y = 0; y < _rows; y ++)
{
for (x = 0; x < _columns; x ++)
{
s += String(getTile(x, y));
if (x != _columns - 1) s += columnSep;
}
if (y != _rows - 1) s += rowSep;
}
return s;
}
/**
* Gets the index of a tile, based on its column and row in the tileset.
* @param tilesColumn Tileset column.
* @param tilesRow Tileset row.
* @return Index of the tile.
*/
public function getIndex(tilesColumn:uint, tilesRow:uint):uint
{
return (tilesRow % _setRows) * _setColumns + (tilesColumn % _setColumns);
}
/**
* Shifts all the tiles in the tilemap.
* @param columns Horizontal shift.
* @param rows Vertical shift.
* @param wrap If tiles shifted off the canvas should wrap around to the other side.
*/
public function shiftTiles(columns:int, rows:int, wrap:Boolean = false):void
{
if (usePositions)
{
columns /= _tile.width;
rows /= _tile.height;
}
if (!wrap) _temp.fillRect(_temp.rect, 0);
if (columns != 0)
{
shift(columns * _tile.width, 0);
if (wrap) _temp.copyPixels(_map, _map.rect, FP.zero);
_map.scroll(columns, 0);
_point.y = 0;
_point.x = columns > 0 ? columns - _columns : columns + _columns;
_map.copyPixels(_temp, _temp.rect, _point);
_rect.x = columns > 0 ? 0 : _columns + columns;
_rect.y = 0;
_rect.width = Math.abs(columns);
_rect.height = _rows;
updateRect(_rect, !wrap);
}
if (rows != 0)
{
shift(0, rows * _tile.height);
if (wrap) _temp.copyPixels(_map, _map.rect, FP.zero);
_map.scroll(0, rows);
_point.x = 0;
_point.y = rows > 0 ? rows - _rows : rows + _rows;
_map.copyPixels(_temp, _temp.rect, _point);
_rect.x = 0;
_rect.y = rows > 0 ? 0 : _rows + rows;
_rect.width = _columns;
_rect.height = Math.abs(rows);
updateRect(_rect, !wrap);
}
}
/** @private Used by shiftTiles to update a rectangle of tiles from the tilemap. */
private function updateRect(rect:Rectangle, clear:Boolean):void
{
var x:int = rect.x,
y:int = rect.y,
w:int = x + rect.width,
h:int = y + rect.height,
u:Boolean = usePositions;
usePositions = false;
if (clear)
{
while (y < h)
{
while (x < w) clearTile(x ++, y);
x = rect.x;
y ++;
}
}
else
{
while (y < h)
{
while (x < w) updateTile(x ++, y);
x = rect.x;
y ++;
}
}
usePositions = u;
}
/** @private Used by shiftTiles to update a tile from the tilemap. */
private function updateTile(column:uint, row:uint):void
{
setTile(column, row, _map.getPixel(column % _columns, row % _rows));
}
/**
* The tile width.
*/
public function get tileWidth():uint { return _tile.width; }
/**
* The tile height.
*/
public function get tileHeight():uint { return _tile.height; }
/**
* How many columns the tilemap has.
*/
public function get columns():uint { return _columns; }
/**
* How many rows the tilemap has.
*/
public function get rows():uint { return _rows; }
/** @private */ private var _map:BitmapData;
/** @private */ private var _temp:BitmapData;
/** @private */ private var _columns:uint;
/** @private */ private var _rows:uint;
/** @private */ private var _set:BitmapData;
/** @private */ private var _setColumns:uint;
/** @private */ private var _setRows:uint;
/** @private */ private var _setCount:uint;
/** @private */ private var _tile:Rectangle;
/** @private */ private var _rect:Rectangle = FP.rect;
}
}