Game Development Community

Tile Highlighting Based On Mouse Position

by Jesse Tucker · in Torque Game Builder · 09/08/2009 (9:25 pm) · 3 replies

Hi Everybody!

This is my first official question about TGB, and I'm hoping someone with a bit more experience can guide me in the right direction. I'm pretty new to this, and have a solution that may be difficult but will probably work. I'm just curious to see if someone else knows a faster/easier approach.

I'm trying to script up a system that will draw an alpha highlighter on a tile if the mouse pointer happens to be over it.

Here's my current idea for implementation:

1. Use onMouseMove and onMouseDragged Events and get the mouse worldLocation, then pass that worldLocation into an updateHighlighter() function.

2. In the updateHighlighter() function, I somehow use those world coordinates to determine if the mouse pointer is over a tile. (is there a quick way to do this?)

3. If the mouse is over a tile, draw a highlighter on that tile (won't conflict with the grid graphics, it's currently only being used for pathfinding.) If the mouse is over a new tile or no tile, then clear the highlighter on the previous tile.


Are there better ways to do this? I'm wondering if there's a way to use onMouseEnter/Leave or collision methods for individual tiles, or if I'd be better off dropping sprites at every tile piece and modifying the layer based on onMouseEnter/Leave.

Any help would be greatly appreciated!

-Jesse

About the author

Recent Threads


#1
09/11/2009 (2:52 pm)
No replies? Bummer. i'll guess until someone better comes along

i don't know if there's an onEnter/mouseOver event for individual tiles. What i do is basically what you're suggesting. i track the mouse position globally and then call a function i wrote that determines what tile i'm over. Then i create a sprite a little larger than the tile that is bright yellow and partially transparent

Here's the thing i think you most want - how do i get a tile from a world coordinate? Look in the help file for the method pickTilet2dTileLayer::pickTile(int tileX, int tileY)(int tileX, int tileY)

As for the rest, looking over some of my old code, it seems i took some ideas from http://tdn.garagegames.com/wiki/T2DScript/TileMovement

Here's one of the functions i wrote to get the world coordinates of a specific tile

function t2dTileLayer::tilePosition(%this, %tile)
{
	%xOffset = %this.getPositionX() - (%this.getWidth() /2);
	%yOffset = %this.getPositionY() - (%this.getHeight()/2);
	%worldX = %xOffset +
			 (%this.getTileSizeX() * getWord(%tile, 0)) +
			 (%this.getTileSizeX() * 0.5);
	%worldY = %yOffset +
			 (%this.getTileSizeY() * getWord(%tile, 1)) +
			 (%this.getTileSizeY() * 0.5);

	return %worldX SPC %worldY;
}

In my games, i always had a non-visual map that i did all the work on and then just used the TGB map to render it (and to be honest, a lot of times i don't even do that, i just create at runtime arrays of sprites)

#2
09/11/2009 (2:53 pm)
Here's one of the files i had for highlighting tiles (using, as mentioned above, a non-visual variable to track data):

function map::clear(%this)
{
	echo("clearing map");
	%mapWidth  = %this.getTileCountX();
	%mapHeight = %this.getTileCountY();

	for (%x = 0; %x < %mapWidth; %x++)
	{
		for (%y = 0; %y < %mapHeight; %y++)
			$map[%x, %y] = "";
	}
}

function map::initializeMap(%this)
{
	%this.clear();
}

function map::onLevelLoaded(%this, %scenegraph)
{
	%this.initializeMap();
}

function map::print(%this)
{
	%mapWidth  = %this.getTileCountX();
	%mapHeight = %this.getTileCountY();

	for (%y = 0; %y < %mapHeight; %y++)
	{
		%line = "";
		for (%x = 0; %x < %mapWidth; %x++)
			if (isObject($map[%x, %y]))
			{
				%line = %line @ " Y ";
				echo("found " @ $map[%x, %y].getName() @ " at " @ %x SPC %y);
			}
			else
				%line = %line @ " N ";
		echo(%line);
	}
}

function map::setTile(%this, %object, %tile)
{
	if (!("" $= %object.tileX))
	{
		$map[%object.tileX, %object.tileY] = "";
	}

	%object.tileX = getWord(%tile, 0);
	%object.tileY = getWord(%tile, 1);
	$map[%object.tileX, %object.tileY] = %object;
}

function map::hasValue(%this, %tile)
{
	%x = getWord(%tile, 0);
	%y = getWord(%tile, 1);
	return isObject($map[%x, %y]);
}

function map::highlightTile(%this, %tile, %red, %green, %blue, %alpha)
{
	//--- If they didn't pass in a color, use a default one
	if (%red==0 && %green==0 && %blue==0)
	{
		%red = 0; %green = 1; %blue = 1; %alpha = 0.5;
	}

	%highlight = new t2dStaticSprite() {
		Scenegraph = %this.getScenegraph();
		BlendColor = %red SPC %green SPC %blue SPC %alpha;
		ImageMap   = "squareImageMap";
		Layer      = "2";
		Position   = %this.tilePosition(%tile);
		Size       = %this.getTileSizeX() SPC %this.getTileSizeY();
	};

	//--- Save this so we can turn it off later
	$highlightMap[getWord(%tile, 0), getWord(%tile, 1)] = %highlight;
}

function map::clearHighlights(%this)
{
	%mapWidth  = map.getTileCountX();
	%mapHeight = map.getTileCountY();

	for (%x = 0; %x < %mapWidth; %x++)
	{
		for (%y = 0; %y < %mapHeight; %y++)
			%this.dehighlightTile(%x SPC %y);
	}
}

function map::dehighlightTile(%this, %tile)
{
	%object = $highlightMap[getWord(%tile, 0), getWord(%tile, 1)];
	if (isObject(%object))
	{
		%object.safeDelete();
		$highlightMap[getWord(%tile, 0), getWord(%tile, 1)] = "";
	}
}

function map::hasLineOfSight(%this, %start, %end)
{
	%result = Bresenham(%start, %end);

	%isBlocked = false;

	//--- Should probably ignore the first and last points.
	//--- If we're looking from us to someone else, both of those
	//---  points will be occupied
	//--- It's easier here to chop the points than at the caller's
	//---  end since he won't know what the first point to use would be
	%numPoints = %result.getCount() - 1;
	for (%i=1; %i < %numPoints; %i++)
	{
		%object = %result.getObject(%i);
		%point = %object.Point;
//		echo("Line crosses tile (" @ %point @ ")");

		if (%this.hasValue(%point))
		{
			%isBlocked = true;
			map.highlightTile(%Point, 1, 0, 0, 0.5);
		}
		else
			map.highlightTile(%Point, 0, 1, 0, 0.5);
	}

	echo("hasLineOfSight = " @ !%isBlocked);
	return !%isBlocked;
}

function Bresenham(%start, %end)
{
	%startX = getWord(%start, 0);
	%startY = getWord(%start, 1);
	%endX   = getWord(%end,   0);
	%endY   = getWord(%end,   1);

	//--- The algorithm only works in one direction
	//---  so if that's not, change our problem to match the solution :(
	//--- We're going to count along the long end so if the long end
	//---  isn't X we're gonna swap X and Y
	%isSteep = mAbs(%endY - %startY) > mAbs(%endX - %startX);
	if (%isSteep)
	{
		%t = %startX;
		%startX = %startY;
		%startY = %t;

		%t = %endX;
		%endX = %endY;
		%endY = %t;
	}

	//--- Force our line to go left to right
	if (%startX > %endX)
	{
		%t = %startX;
		%startX = %endX;
		%endX   = %t;
		%t = %startY;
		%startY = %endY;
		%endY   = %t;
	}

	%changeInX = %endX - %startX;
	%changeInY = mAbs(%endY - %startY);
	%error = 0;
	if (%startY < %endY)
		%yStep = 1;
	else
		%yStep = -1;

	%result = new SimSet();
	%y = %startY;
	for (%x = %startX; %x <= %endX; %x++)
	{
		%point = %x SPC %y;
		if (%isSteep)
		{
			%point = %y SPC %x;
		}
		//--- Using silly syntax because Codeweaver whines
		//---  when you define datablocks inside function calls
		%t = new SimObject() { Point = %point; };
		%result.add(%t);

		%error += %changeInY;
		if ((2*%error) >= %changeInX)
		{
			%y += %yStep;
			%error -= %changeinX;
		}
	}

	return %result;
}
#3
09/11/2009 (8:52 pm)
Thanks a bunch for the reply! I'll digest your material and see if I can make some use out of it. :)
#4
09/18/2009 (8:21 pm)
Thanks for your help. I figured out a quick way to drop little highlighter sprites at the grid locations:

function t2dTileMap::onTileScript (%this, %tilelayer, %tilepos, %tilescript)  
{  
    if ( %tilescript $= "OpenSpace" )
	{
		%newHighlighterOpen = HighlighterOpenTemplate.cloneWithBehaviors();
		%xTile = getWord( %tilepos, 0);
		%yTile = getWord( %tilepos, 1);
		%tileWorldPos = %tilelayer.getTileWorldPosition(%xTile, %yTile);
		%newHighlighterOpen.setPosition(%tileWorldPos);
	}
}  

function HighlighterOpen::OnMouseEnter(%this)
{
	%this.setLayer( 20 );
}

function HighlighterOpen::OnMouseLeave(%this)
{
	%this.setLayer( 31 );
}