- Home /
Grid Snapping for Tower Defense Units
I'm having trouble finding a good way to snap units to a grid accommodating areas in the grid that are already taken.
The gray blocks are the the grid where objects can be placed, the black taken spots on the grid. The blue group of three is an example of a multi block grid unit, and the single a single block unit.
In this example I'm dragging a multi block unit on the top left, and the middle. In both cases, I can't figure out how to snap to the grid, and ensure that A) it's not partly outside the grid and B) not overlapping a previously used space.
I should add that the units can be rotated 90 degrees by the player, so the rotational alignment isn't fixed.
Any help, or perhaps a direction to some reading in this area would be greatly appreciated.
$$anonymous$$aybe think about abstracting the pieces from the "grid" (5x5 on/off). When dragging your block around, you could convert the object-positions to a grid x&y and have a simple function that deter$$anonymous$$es if that grid space is occupied (or off-grid). Then you can expand that further for shapes (a 2D array of on/off points too).
Do you already have the snapping effect in place?
Yeah, snapping was the easy part. I guess to use that direction, I could add in empty game objects in the middle of each movable block, and then check each block the empty objects are over.
Answer by NoseKills · Apr 16, 2014 at 09:41 PM
Call me crazy and old school, but i like to do an abstract 2D grid/array representation of it whenever possible :D It's quite flexible performant and simple once you get used to the idea.
The grid is an array, the building is an array. Both have data to determine which cells in their local coordinates are reserved and which not. You only need a few simple methods to convert building coordinates to grid coordinates and check for overlapping reserved tiles.
Here's a small sample that should work if you just add the script to a GameObject. Note that data of the "building" is hardcoded for each rotation in this example. You could use an actual matrix to calculate cell positions after the rotations so you'd only have to provide the data for 0-rotation.
using UnityEngine;
using System.Collections;
public class GridExmpl : MonoBehaviour
{
static readonly Vector2 INVALID_COORDS = new Vector2(-1, -1);
const int INVALID_CELL_IDX = -1;
const float CUBE_SCALE = 0.9f;
public int _GridWidth;
public int _GridHeight;
private GridCell[] _Grid; // mark occupied cells here from bottom left to top right
private Building _DraggedBuilding;
private int _LastMouseIdx;
void Start()
{
// center the grid to screen. fugly implementation
transform.position = new Vector3(-(_GridWidth/2f) + 0.5f, - (_GridHeight/2f) + 0.5f, 0);
// create a grid of "GridCell" cubes
_Grid = new GridCell[_GridWidth * _GridHeight];
for (int i = 0; i < _Grid.Length; i++)
{
GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
cube.transform.parent = transform;
cube.transform.localPosition = indexToCoord(i);
cube.transform.localScale = Vector3.one * CUBE_SCALE;
_Grid[i] = new GridCell(cube);
}
// mark some cells reserved
_Grid[coordToIndex(_GridWidth / 2, _GridHeight / 2)]._Reserved = true;
_Grid[coordToIndex(_GridWidth / 2, (_GridHeight / 2) - 1)]._Reserved = true;
// set colors for first time drawing
resetHighlighting();
// make a default dragged building
_DraggedBuilding = new Building();
}
// calculate _Grid index based on given x,y -coordinates
private int coordToIndex(int x, int y)
{
if (x >= 0 && y >= 0 && x < _GridWidth && y < _GridHeight)
return x + (y * _GridWidth);
else
return INVALID_CELL_IDX;
}
// calculate _Grid coords based on given index
private Vector2 indexToCoord(int i)
{
if (i >= 0 && i < _Grid.Length)
return new Vector2(i % _GridWidth, i / _GridWidth);
else
return INVALID_COORDS;
}
void Update()
{
// tell building to rotate
if (Input.GetMouseButtonDown(0))
{
_DraggedBuilding.rotateLeft();
_LastMouseIdx = INVALID_CELL_IDX; // reset to make sure we checkFit() after rotation even if not changing cell
}
else if (Input.GetMouseButtonDown(1))
{
_DraggedBuilding.rotateRight();
_LastMouseIdx = INVALID_CELL_IDX;
}
// check which world coordinate the mouse is over. Since gridCells are 1x1 units,
// it's translateable to grid coordinates straight away. Should work well enough
Vector3 worldPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
worldPos -= transform.position - new Vector3(0.5f, 1f, 0f);
int hoverIndex = coordToIndex((int)worldPos.x, (int)worldPos.y);
// only check if hovered cell is valid and changed
if (hoverIndex != INVALID_CELL_IDX && hoverIndex != _LastMouseIdx)
{
resetHighlighting();
checkFit(_DraggedBuilding, hoverIndex);
}
}
//check if current dragged building fits in this square
void checkFit(Building building, int idx)
{
// bottom left coords of building in grid
Vector2 startCoords = indexToCoord(idx);
bool[] buildingCells = building.rotatedCells;
// loop through building cells and see which cells are reserved in both building and grid
for (int y = 0; y < Building.HEIGHT; y++)
{
for (int x = 0; x < Building.WIDTH; x++)
{
// this building cell matches this index in grid
int indexInGrid = coordToIndex((int)startCoords.x + x, (int)startCoords.y + y);
if (indexInGrid == INVALID_CELL_IDX)
continue;
int indexInBuilding = building.coordToIndex(x, y);
// if this index inside the building is reserved, check if the underlying grid cell is reserved
if (buildingCells[indexInBuilding])
{
_Grid[indexInGrid]._GO.renderer.material.color = _Grid[indexInGrid]._Reserved ? Color.red : Color.magenta;
}
}
}
}
void resetHighlighting()
{
for (int i = 0; i < _Grid.Length; i++)
{
// default coloring for reserved and free grid tiles
_Grid[i]._GO.renderer.material.color = _Grid[i]._Reserved ? Color.black : Color.white;
}
}
// simple class to represent a cell in the grid.
// _Reserved status and a _GameObject to show status visually
public class GridCell
{
public bool _Reserved;
public GameObject _GO;
public GridCell (GameObject cube)
{
_GO = cube;
}
}
public class Building
{
private const int ROT_0 = 0;
private const int ROT_90 = 1;
private const int ROT_190 = 2;
private const int ROT_270 = 3;
private const int ROT_COUNT = 4;
public const int WIDTH = 2;
public const int HEIGHT = 2;
private int _Rotation;
// reserved status of building's cells in it's local space from bottom left to top right
private bool[][] _MyCells = new bool[][]{
// bl br tl tr
new bool[]{true,true,true,false},
new bool[]{true,false,true,true},
new bool[]{false,true,true,true},
new bool[]{true,true,false,true}
};
public bool[] rotatedCells
{
get {
return _MyCells[_Rotation];
}
}
public Building()
{
}
public void rotateRight ()
{
_Rotation = ++_Rotation % ROT_COUNT;
}
public void rotateLeft ()
{
if (--_Rotation < 0)
_Rotation += ROT_COUNT;
}
// calculate index based on given x,y -coordinates
public int coordToIndex(int x, int y)
{
if (x >= 0 && y >= 0 && x < WIDTH && y < HEIGHT)
return x + (y * WIDTH);
else
return INVALID_CELL_IDX;
}
}
}
This is great, looking at what you've got here opened my eyes to exactly what I needed to do. Thanks!
Ok, but how building road or wall with dragging like a simcity?
Answer by oldschoolj · Apr 16, 2014 at 10:53 PM
Just wanted to let you know that there is a pretty good TD Grid tutorial out there from CG Cookie on this. I actually used a bit of it.
Your answer
Follow this Question
Related Questions
Gameobject follow mouse on center 1 Answer
Snap object to grid with offset? 1 Answer
RTS building snap to grid 2 Answers
How can I snap to hex tile centers in world x,z coords? 1 Answer
Snap All Axes Hotkey? 0 Answers