Terrain Modification in Grid Based Games – Part 2: Moving Nodes

Terrain Modification Tutorial Thumbnail

In this part of the terrain modification tutorial I will show you how to move single nodes and their neighbors if necessary. To begin at a smaller scale I will not use a multi-row grid but a line of nodes instead.
Every node in a line will have two neighbors (except for the outer ones of course).

First task is building the (one row) grid. Since the first part dealt with a lot of grid building I’ll just give you the code here.

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

var nodeRows:int = 1;
var nodeCols:int = 9;

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

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

The only thing new is that the nodes get assigned a w-value, which is the integer step for the height that is calculated in the next function. And the initial value is set for a better visualization.

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

var nodeMap:Sprite = new Sprite();
var nodeMapSizeH = ( nodeCols - 1 ) * tileSizeH;
var nodeMapSizeV = ( nodeRows - 1 ) * tileSizeV;
nodeMap.x = stage.stageWidth * 0.5 - nodeMapSizeH * 0.5;
nodeMap.y = stage.stageHeight * 0.5 - nodeMapSizeV * 0.5;
nodeMap.graphics.lineStyle(1, 0xaaaaaa);
addChild(nodeMap);

function drawNodes()
{	
	for each ( var node in nodeArray )
	{
		nodeMap.graphics.drawCircle(node.xPos, node.yPos, 3);
	}
}

makeGrid();
placeNodes();
drawNodes();

Nothing new here. Right now you've got a line of grey circles. Oh, and each node holds an array of its neighboring nodes. We'll see how that works when coming to the function.

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

This function was also explained in Part 1.

function validateNode ( u, v )
{
	if ( u >= 0 && u <= nodeCols - 1 && v >= 0 && v <= nodeRows - 1 )
	{
		return true;
	}
	else
	{
		return false;
	}
}

validateNode does check if the there even can be a node at this position. Also nothing exciting.

function getNeighbors( cNode )
{
	var nArray:Array = new Array();
	validateNode ( cNode.u, cNode.v - 1 ) ? nArray.push( getNodeByCoords ( cNode.u, cNode.v - 1 ) ) : void;
	validateNode ( cNode.u + 1, cNode.v ) ? nArray.push( getNodeByCoords ( cNode.u + 1, cNode.v ) ) : void;
	validateNode ( cNode.u, cNode.v + 1 ) ? nArray.push( getNodeByCoords ( cNode.u, cNode.v + 1 ) ) : void;
	validateNode ( cNode.u - 1, cNode.v ) ? nArray.push( getNodeByCoords ( cNode.u - 1, cNode.v ) ) : void;
	return ( nArray );
}

Alright, although we're on a one row grid this function checks if the center node (cNode) has a neighbor above, right, below and left and any existing neighbor is pushed into an array which finally is returned. So, each node now knows about its adjacent neighbors.

var lineMap:Sprite = new Sprite();
lineMap.x = nodeMap.x;
lineMap.y = nodeMap.y;
addChild(lineMap);

function drawMap ()
{
	lineMap.graphics.clear();
	//drawing a line from the node circle to the actual position including height
	lineMap.graphics.lineStyle(1, 0xaaaaaa);
	for ( var i:int = 0; i < nodeArray.length; i++ )
	{
		lineMap.graphics.moveTo ( nodeArray[i].xPos, nodeArray[i].yPos - 3 );
		lineMap.graphics.lineTo ( nodeArray[i].xPos, nodeArray[i].yPos - nodeArray[i].zPos );
	}
	//drawing a line between a node and the next, caveat: the loop ends second to the last node
	lineMap.graphics.lineStyle(1, 0x000000);
	for ( i = 0; i < nodeArray.length - 1; i++ )
	{
		lineMap.graphics.moveTo ( nodeArray[i].xPos, nodeArray[i].yPos - nodeArray[i].zPos );
		lineMap.graphics.lineTo ( nodeArray[i+1].xPos, nodeArray[i+1].yPos - nodeArray[i+1].zPos );
	}
	//finally some nice circles at the height position
	for ( i = 0; i < nodeArray.length; i++ )
	{
		lineMap.graphics.moveTo( nodeArray[i].xPos, nodeArray[i].yPos - nodeArray[i].zPos );
		lineMap.graphics.beginFill(0xffffff);
		lineMap.graphics.drawCircle( nodeArray[i].xPos, nodeArray[i].yPos - nodeArray[i].zPos, 3 );
		lineMap.graphics.endFill();
	}
}

drawMap();

To draw the filled circles, the moveTo was absolutely necessary. Leaving that out corrupted the fill and caused serious errors. So: be careful with fills, they tend to have weird behaviour.

var tFormCursor:MovieClip = new MovieClip();
with( tFormCursor.graphics )
{
	lineStyle(1, 0x990000);
	beginFill(0x990000, 0.5);
	drawCircle(0, 0, 6);
	endFill();
}
tFormCursor.visible = false;
addChild(tFormCursor);

var nFormCursor:MovieClip = new MovieClip();
with( nFormCursor.graphics )
{
	lineStyle(1, 0x009900);
	beginFill(0x009900, 0.5);
	drawCircle(0, 0, 6);
	endFill();
}
nFormCursor.visible = false;
addChild(nFormCursor);

Hmm, one circle to show the actual hovered node (green) and one on the actual height position (red). Both invisible until needed.

stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);

var actualNode:Object = new Object();

function mouseMoveHandler(event:MouseEvent)
{	
	updatePosition();
}
function updatePosition()
{
	var nodeU = Math.round ( ( mouseX - nodeMap.x ) / tileSizeH );
	var nodeV = Math.round ( ( mouseY - nodeMap.y ) / tileSizeV );

	if ( validateNode(nodeU, nodeV) )
	{
		actualNode = getNodeByCoords ( nodeU, nodeV );
		
		tFormCursor.visible = true;
		tFormCursor.x = actualNode.xPos + nodeMap.x;
		tFormCursor.y = actualNode.yPos - actualNode.zPos + nodeMap.y;
		
		nFormCursor.visible = true;
		nFormCursor.x = actualNode.xPos + nodeMap.x;
		nFormCursor.y = actualNode.yPos + nodeMap.y;
	}
	else
	{
		actualNode = null;
		
		tFormCursor.visible = false;
		nFormCursor.visible = false;
	}
}

An eventHandler firing at every mouse move, calling a function that calculates the actual hovered node and places the cursors appropriately. If there is no node near nothing is shown.

stage.addEventListener(MouseEvent.CLICK, mouseClickHandler);

function mouseClickHandler(event:MouseEvent)
{
	if ( actualNode )
	{
		var openList:Array = new Array();
		openList.push(actualNode);
		while ( openList.length > 0 )
		{
			var oNode = openList.shift();
			oNode.w++;
			for each ( var nNode in oNode.nArray )
			{
				if ( oNode.w - nNode.w > 1 )
				{
					openList.push(nNode);
				}
			}
		}
		placeNodes();
		drawMap();
		updatePosition();
	}
}

Well, that is the main function in this tutorial. When a click is received over a node (the if checks it actualNode is not null) an array is created and the actual node pushed into it.
While there is something in this array the first element is removed from the array and returned. This nodes height step is now increased by one. And by checking its neighbors those of them are identified that have a height difference to the actual center node of more than one step. If so, they are pushed to the open list. Once all relevant nodes are updated their pixel height is recalculated, the map is drawn and the cursors are set to the new position (at least the red one).
Okay, here is the swf. Play around. The next turorial will cover height modification in a grid. And maybe for up and down.

Yoho!

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

Comments are closed.