Terrain Modification in Grid Based Games – Appendix A: Dynamic Tiles

Terrain Modification Tutorial Thumbnail

This appendix is part of the terrain modification tutorial series and holds information about the dynamic creation and drawing of tiles.
The different tile styles presented are some sort of tribute to great old games like Populous and SimCity and their differences in tile design.

Part A.0: The Tile Showroom
To really have a good overview of the different tiles the following code will not produce a complete map. Single tiles are presented with offset.

var nodeArray:Array = new Array();
var posArray:Array = new Array();

var nodeRows:int = 8;
var nodeCols:int = 8;

var tileSizeH:int = 32; //horizontal
var tileSizeV:int = 16; //vertical
var tileSizeM:int = 12; //mountain

var heightArray:Array = new Array(
				  0,0,0,0,
				  0,0,0,1,
				  0,0,1,0,
				  0,0,1,1,
				  0,1,0,0,
				  0,1,0,1,
				  0,1,1,0,
				  0,1,1,1,
				  1,0,0,0,
				  1,0,0,1,
				  1,0,1,0,
				  1,0,1,1,
				  1,1,0,0,
				  1,1,0,1,
				  1,1,1,0,
				  1,1,1,1);

function makeGrid()
{
	for ( var u = 0; u < nodeCols; u++ )
	{
		for ( var v = 0; v < nodeRows; v++ )
		{
			var node:Object = new Object();
			node.u = u;
			node.v = v;
			node.nodePos = u + "." + v;
			
			nodeArray.push(node);
			posArray.push(node.nodePos);
		}
	}
}

function placeNodes()
{
	for each ( var node in nodeArray )
	{
		node.xPos = ( node.u - node.v ) * tileSizeH;
		node.yPos = ( node.v + node.u ) * tileSizeV;
		node.zPos = node.w * tileSizeM;
	}
}

function drawNodes()
{
	var nodeMap:Sprite = new Sprite();
	var nodeMapSizeH = ( nodeCols - 1 + nodeRows - 1 ) * tileSizeH;
	var nodeMapSizeV = ( nodeRows - 1 + nodeCols - 1 ) * tileSizeV;
	nodeMap.x = stage.stageWidth * 0.5;
	nodeMap.y = stage.stageHeight * 0.5 - nodeMapSizeV * 0.5;
	nodeMap.graphics.lineStyle(1, 0xaaaaaa);
	for each ( var node in nodeArray )
	{
		nodeMap.graphics.drawCircle(node.xPos, node.yPos, 3);
	}
	addChild(nodeMap);
}

function getNodeByCoords( u, v )
{
	var posString:String = u + "." + v;
	var nodePos:int = posArray.indexOf(posString);
	if ( nodePos >= 0 )
	{
		return nodeArray[nodePos];
	}
	else
	{
		return null;
	}
}

var tileArray:Array = new Array();

function makeTiles()
{
	var validT:int = 0;
	for ( var u = 0; u < nodeCols - 1; u++ )
	{
		for ( var v = 0; v < nodeRows - 1; v++ )
		{
			var tile:Object = new Object();
			tile.n = getNodeByCoords(u, v);
			tile.e = getNodeByCoords(u+1, v);
			tile.s = getNodeByCoords(u+1, v+1);
			tile.w = getNodeByCoords(u, v+1);
			tileArray.push(tile);
			
			if ( u%2 == 0 && v%2 == 0 ) //test if row and column are even
			{
				tile.valid = true;
				tile.n.w = heightArray[validT];
				tile.e.w = heightArray[validT+1];
				tile.s.w = heightArray[validT+2];
				tile.w.w = heightArray[validT+3];
				validT+=4;
			}			
		}
	}
}

function drawTiles()
{
	var tileMap:Sprite = new Sprite();
	var tileMapSizeH = ( nodeCols - 1 + nodeRows - 1 ) * tileSizeH;
	var tileMapSizeV = ( nodeRows - 1 + nodeCols - 1 ) * tileSizeV;
	tileMap.x = stage.stageWidth * 0.5;
	tileMap.y = stage.stageHeight * 0.5 - tileMapSizeV * 0.5;
	
	for each ( var tile in tileArray )
	{
		if ( tile.valid )
		{
			tile.n.zPos == tile.s.zPos ? tile.ver = true : tile.ver = false;
			tile.e.zPos == tile.w.zPos ? tile.hor = true : tile.hor = false;
		
			with(tileMap.graphics)
			{
				lineStyle(1, 0xaaaaaa);
			
				//bottom
				moveTo(tile.n.xPos, tile.n.yPos);
				lineTo(tile.e.xPos, tile.e.yPos);
				lineTo(tile.s.xPos, tile.s.yPos);
				lineTo(tile.w.xPos, tile.w.yPos);
				lineTo(tile.n.xPos, tile.n.yPos);
			}
		}
	}
	addChild(tileMap);
}

makeGrid();
makeTiles();
placeNodes();
drawNodes();
drawTiles();

So this is the basic presentation mode. Tiles are valid if their row and column number is even. If so, the nodes are assigned a height value. These and only these valid tiles are drawn.
As you can see in the heightArray there are 16 possible combinations (4 nodes, two possible states, 2^4 = 16).

Part A.1: The Basic Tiles
The following chapters present the relevant drawTiles() function and their differences.

function drawTiles()
{
	var tileMap:Sprite = new Sprite();
	var tileMapSizeH = ( nodeCols - 1 + nodeRows - 1 ) * tileSizeH;
	var tileMapSizeV = ( nodeRows - 1 + nodeCols - 1 ) * tileSizeV;
	tileMap.x = stage.stageWidth * 0.5;
	tileMap.y = stage.stageHeight * 0.5 - tileMapSizeV * 0.5;
	tileMap.graphics.lineStyle(1, 0x000000);
	
	for each ( var tile in tileArray )
	{
		if ( tile.valid )
		{
			tile.n.zPos == tile.s.zPos ? tile.ver = true : tile.ver = false;
			tile.e.zPos == tile.w.zPos ? tile.hor = true : tile.hor = false;
			
			with(tileMap.graphics)
			{
				lineStyle(1, 0xaaaaaa);
			
				//bottom
				moveTo(tile.n.xPos, tile.n.yPos);
				lineTo(tile.e.xPos, tile.e.yPos);
				lineTo(tile.s.xPos, tile.s.yPos);
				lineTo(tile.w.xPos, tile.w.yPos);
				lineTo(tile.n.xPos, tile.n.yPos);
				
				//sides
				moveTo(tile.n.xPos, tile.n.yPos - tile.n.zPos);
				lineTo(tile.n.xPos, tile.n.yPos);
				moveTo(tile.e.xPos, tile.e.yPos - tile.e.zPos);
				lineTo(tile.e.xPos, tile.e.yPos);
				moveTo(tile.s.xPos, tile.s.yPos - tile.s.zPos);
				lineTo(tile.s.xPos, tile.s.yPos);
				moveTo(tile.w.xPos, tile.w.yPos - tile.w.zPos);
				lineTo(tile.w.xPos, tile.w.yPos);
			
				lineStyle(1, 0x000000);
			
				if ( tile.ver && tile.hor )
				{
					if ( tile.n.zPos > tile.e.zPos )
					{
						moveTo(tile.n.xPos, tile.n.yPos - tile.n.zPos);
						lineTo(tile.s.xPos, tile.s.yPos - tile.s.zPos);
					}
					else if ( tile.n.zPos < tile.e.zPos )
					{
						moveTo(tile.e.xPos, tile.e.yPos - tile.e.zPos);
						lineTo(tile.w.xPos, tile.w.yPos - tile.w.zPos);
					}
					//if both are the same, there is no line
				}
				else if ( tile.ver && !tile.hor )
				{
					moveTo(tile.e.xPos, tile.e.yPos - tile.e.zPos);
					lineTo(tile.w.xPos, tile.w.yPos - tile.w.zPos);
				}
				else if ( !tile.ver && tile.hor )
				{
					moveTo(tile.n.xPos, tile.n.yPos - tile.n.zPos);
					lineTo(tile.s.xPos, tile.s.yPos - tile.s.zPos);
				}
			
				moveTo(tile.n.xPos, tile.n.yPos - tile.n.zPos);
				lineTo(tile.e.xPos, tile.e.yPos - tile.e.zPos);
				lineTo(tile.s.xPos, tile.s.yPos - tile.s.zPos);
				lineTo(tile.w.xPos, tile.w.yPos - tile.w.zPos);
				lineTo(tile.n.xPos, tile.n.yPos - tile.n.zPos);	
			}
		}
	}
	addChild(tileMap);
}

The basic tiles are pretty simple but yet effective. The mountain height is lower than the middle of a tile so backface culling is not neccessary.

Part A.2: The Populous Tiles
Yay, Populous. Great game. What is different here regarding the basic tiles is the mountain height, var tileSizeM:int = 16; //mountain. Now there is one tile that has a hidden (being perpendicular to the viewing plane) face.

function drawTiles()
{
	var tileMap:Sprite = new Sprite();
	var tileMapSizeH = ( nodeCols - 1 + nodeRows - 1 ) * tileSizeH;
	var tileMapSizeV = ( nodeRows - 1 + nodeCols - 1 ) * tileSizeV;
	tileMap.x = stage.stageWidth * 0.5;
	tileMap.y = stage.stageHeight * 0.5 - tileMapSizeV * 0.5;
	tileMap.graphics.lineStyle(1, 0x000000);
	
	for each ( var tile in tileArray )
	{
		if ( tile.valid )
		{
			tile.n.zPos == tile.s.zPos ? tile.ver = true : tile.ver = false;
			tile.e.zPos == tile.w.zPos ? tile.hor = true : tile.hor = false;
			
			with(tileMap.graphics)
			{
				lineStyle(1, 0xaaaaaa);
			
				//bottom
				moveTo(tile.n.xPos, tile.n.yPos);
				lineTo(tile.e.xPos, tile.e.yPos);
				lineTo(tile.s.xPos, tile.s.yPos);
				lineTo(tile.w.xPos, tile.w.yPos);
				lineTo(tile.n.xPos, tile.n.yPos);
			
				//sides	
				moveTo(tile.n.xPos, tile.n.yPos - tile.n.zPos);
				lineTo(tile.n.xPos, tile.n.yPos);
				moveTo(tile.e.xPos, tile.e.yPos - tile.e.zPos);
				lineTo(tile.e.xPos, tile.e.yPos);
				moveTo(tile.s.xPos, tile.s.yPos - tile.s.zPos);
				lineTo(tile.s.xPos, tile.s.yPos);
				moveTo(tile.w.xPos, tile.w.yPos - tile.w.zPos);
				lineTo(tile.w.xPos, tile.w.yPos);
				
				lineStyle(1, 0x000000);
			
				if ( tile.ver && tile.hor )
				{
					if ( tile.n.zPos > tile.e.zPos )
					{
						moveTo(tile.n.xPos, tile.n.yPos - tile.n.zPos);
						lineTo(tile.n.xPos, tile.e.yPos - tile.n.zPos * 0.5);
						lineTo(tile.s.xPos, tile.s.yPos - tile.s.zPos);
						
						moveTo(tile.e.xPos, tile.e.yPos - tile.e.zPos);
						lineTo(tile.n.xPos, tile.e.yPos - tile.n.zPos * 0.5);
						lineTo(tile.w.xPos, tile.w.yPos - tile.w.zPos);
					}	
					else if ( tile.n.zPos < tile.e.zPos )
					{
						moveTo(tile.e.xPos, tile.e.yPos - tile.e.zPos);
						lineTo(tile.n.xPos, tile.e.yPos - tile.e.zPos * 0.5);
						lineTo(tile.w.xPos, tile.w.yPos - tile.w.zPos);
						
						moveTo(tile.n.xPos, tile.n.yPos - tile.n.zPos);
						lineTo(tile.n.xPos, tile.e.yPos - tile.e.zPos * 0.5);
						lineTo(tile.s.sPos, tile.s.yPos - tile.s.zPos);
					}
					//if both are the same, there is no line
				}
				else if ( tile.ver && !tile.hor )
				{
					moveTo(tile.e.xPos, tile.e.yPos - tile.e.zPos);
					lineTo(tile.w.xPos, tile.w.yPos - tile.w.zPos);
				}
				else if ( !tile.ver && tile.hor )
				{
					moveTo(tile.n.xPos, tile.n.yPos - tile.n.zPos);
					lineTo(tile.s.xPos, tile.s.yPos - tile.s.zPos);
				}
			
				moveTo(tile.n.xPos, tile.n.yPos - tile.n.zPos);
				lineTo(tile.e.xPos, tile.e.yPos - tile.e.zPos);
				lineTo(tile.s.xPos, tile.s.yPos - tile.s.zPos);
				lineTo(tile.w.xPos, tile.w.yPos - tile.w.zPos);
				lineTo(tile.n.xPos, tile.n.yPos - tile.n.zPos);	
			}
		}
	}
	addChild(tileMap);
}

Two tiles are a bit more complicated than in the basic version. And the code gives:

Part A.3: The SimCity Tiles
And those where the highest tiles, var tileSizeM:int = 24; //mountain. Thus, when shading the faces, some of them will be hidden, so backface culling might be neccessary.

function drawTiles()
{
	var tileMap:Sprite = new Sprite();
	var tileMapSizeH = ( nodeCols - 1 + nodeRows - 1 ) * tileSizeH;
	var tileMapSizeV = ( nodeRows - 1 + nodeCols - 1 ) * tileSizeV;
	tileMap.x = stage.stageWidth * 0.5;
	tileMap.y = stage.stageHeight * 0.5 - tileMapSizeV * 0.5;
	tileMap.graphics.lineStyle(1, 0x000000);
	
	for each ( var tile in tileArray )
	{
		if ( tile.valid ) {
		
			tile.n.zPos == tile.s.zPos ? tile.ver = true : tile.ver = false;
			tile.e.zPos == tile.w.zPos ? tile.hor = true : tile.hor = false;
			
			with(tileMap.graphics)
			{
				lineStyle(1, 0xaaaaaa);
			
				//bottom
				moveTo(tile.n.xPos, tile.n.yPos);
				lineTo(tile.e.xPos, tile.e.yPos);
				lineTo(tile.s.xPos, tile.s.yPos);
				lineTo(tile.w.xPos, tile.w.yPos);
				lineTo(tile.n.xPos, tile.n.yPos);
			
				//sides
				moveTo(tile.n.xPos, tile.n.yPos - tile.n.zPos);
				lineTo(tile.n.xPos, tile.n.yPos);
				moveTo(tile.e.xPos, tile.e.yPos - tile.e.zPos);
				lineTo(tile.e.xPos, tile.e.yPos);
				moveTo(tile.s.xPos, tile.s.yPos - tile.s.zPos);
				lineTo(tile.s.xPos, tile.s.yPos);
				moveTo(tile.w.xPos, tile.w.yPos - tile.w.zPos);
				lineTo(tile.w.xPos, tile.w.yPos);
			
				lineStyle(1, 0x000000);
			
				if ( tile.ver && tile.hor )
				{
					if ( tile.n.zPos > tile.e.zPos )
					{
						moveTo(tile.e.xPos, tile.e.yPos - tile.e.zPos);
						lineTo(tile.w.xPos, tile.w.yPos - tile.w.zPos);
					}
					else if ( tile.n.zPos < tile.e.zPos )
					{
						moveTo(tile.n.xPos, tile.n.yPos - tile.n.zPos);
						lineTo(tile.s.xPos, tile.s.yPos - tile.s.zPos);
					}
					//if both are the same, there is no line
				}
				else if ( tile.ver && !tile.hor )
				{
					moveTo(tile.n.xPos, tile.n.yPos - tile.n.zPos);
					lineTo(tile.s.xPos, tile.s.yPos - tile.s.zPos);
				}
				else if ( !tile.ver && tile.hor )
				{
					moveTo(tile.e.xPos, tile.e.yPos - tile.e.zPos);
					lineTo(tile.w.xPos, tile.w.yPos - tile.w.zPos);
				}

				moveTo(tile.n.xPos, tile.n.yPos - tile.n.zPos);
				lineTo(tile.e.xPos, tile.e.yPos - tile.e.zPos);
				lineTo(tile.s.xPos, tile.s.yPos - tile.s.zPos);
				lineTo(tile.w.xPos, tile.w.yPos - tile.w.zPos);
				lineTo(tile.n.xPos, tile.n.yPos - tile.n.zPos);
			}
		}
	}
	addChild(tileMap);
}

Next thing will be shading.

Yoho!

This entry was posted in as3, flash, grids, mochiads, Terrain Modification, Tutorial and tagged , , , , . Bookmark the permalink.

Comments are closed.