C# - Procedural mesh generation

Hey Unity community!

I am trying to make a Build selecting system for a RTS-game.
The problem I’m currently having is that the Grid position seem to mess up, I tried many diffrent things to fix it.

The mesh creation script I use to make this:

[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(MeshRenderer))]
public class MeshCreator : MonoBehaviour {
	/// <summary>
	/// The Unity unit size (1 = default)
	/// </summary>
	public Vector2 TileWorldSize = new Vector2(1, 1);

	/// <summary>
	/// The amount of tiles on the Tile Sheet
	/// </summary>
	public Vector2 TileAmount = new Vector2(10, 10);

	/// <summary>
	/// The default tile, normally 0,0
	/// </summary>
	public Vector2 DefaultTile;

	public bool drawRandomTiles;

	private int _gridWidth;
	private int _gridHeight;

	private float _gridHalfWidth;
	private float _gridHalfHeight;

	private Vector2 offset;
	public MeshFilter meshFilter;

	void Awake() {
		meshFilter = GetComponent<MeshFilter>();
	}

	void Update() {
		if (drawRandomTiles) {
			DrawRandomTiles();
		}
	}

	private void DrawRandomTiles() {
		int tileColumn = Random.Range(0, (int)TileAmount.x);
		int tileRow = Random.Range(0, (int)TileAmount.y);

		int x = Random.Range(0, _gridWidth);
		int y = Random.Range(0, _gridHeight);

		UpdateGrid(new Vector2(x, y), new Vector2(tileColumn, tileRow));
	}

	public void UpdateGrid(Vector2 gridIndex, Vector2 tileIndex) {
		Mesh mesh = meshFilter.mesh;
		Vector2[] uvs = mesh.uv;

		float tileSizeX = 1.0f / TileAmount.x;
		float tileSizeY = 1.0f / TileAmount.y;

		//Debug.Log("TileSizeX: " + tileSizeX + " - TileSizeY: " + tileSizeY);

		mesh.uv = uvs;

		//Debug.Log("Mesh UV: " + mesh.uv.Length);

		//Debug.Log("======================= Grid: =======================");
		//Debug.Log("Pos: " + (int)(_gridWidth * gridIndex.x + gridIndex.y) * 4 + 0 + " == " + new Vector2(tileIndex.x * tileSizeX, tileIndex.y * tileSizeY));
		//Debug.Log("Pos: " + (int)(_gridWidth * gridIndex.x + gridIndex.y) * 4 + 1 + " == " + new Vector2((tileIndex.x + 1) * tileSizeX, tileIndex.y * tileSizeY));
		//Debug.Log("Pos: " + (int)(_gridWidth * gridIndex.x + gridIndex.y) * 4 + 2 + " == " + new Vector2((tileIndex.x + 1) * tileSizeX, (tileIndex.y + 1) * tileSizeY));
		//Debug.Log("Pos: " + (int)(_gridWidth * gridIndex.x + gridIndex.y) * 4 + 3 + " == " + new Vector2(tileIndex.x * tileSizeX, (tileIndex.y + 1) * tileSizeY));

		uvs[(int)(_gridWidth * gridIndex.x + gridIndex.y) * 4 + 0] = new Vector2(tileIndex.x * tileSizeX, tileIndex.y * tileSizeY);
		uvs[(int)(_gridWidth * gridIndex.x + gridIndex.y) * 4 + 1] = new Vector2((tileIndex.x + 1) * tileSizeX, tileIndex.y * tileSizeY);
		uvs[(int)(_gridWidth * gridIndex.x + gridIndex.y) * 4 + 2] = new Vector2((tileIndex.x + 1) * tileSizeX, (tileIndex.y + 1) * tileSizeY);
		uvs[(int)(_gridWidth * gridIndex.x + gridIndex.y) * 4 + 3] = new Vector2(tileIndex.x * tileSizeX, (tileIndex.y + 1) * tileSizeY);

		mesh.uv = uvs;
	}

	public void CreatePlane(int gridWidth, int gridHeight) {
		_gridWidth = gridWidth;
		_gridHeight = gridHeight;

		_gridHalfWidth = _gridWidth / 2.0F;
		_gridHalfHeight = _gridHeight / 2.0F;
		
		Mesh mesh = new Mesh();
		meshFilter.mesh = mesh;

		float tileSizeX = 1.0f / TileAmount.x;
		float tileSizeY = 1.0f / TileAmount.y;

		List<Vector3> vertices = new List<Vector3>();
		List<int> triangles = new List<int>();
		List<Vector3> normals = new List<Vector3>();
		List<Vector2> uvs = new List<Vector2>();

		int index = 0;

		for (int x = 0; x < gridWidth; x++) {
		    for (int y = 0; y < gridHeight; y++) {

				AddVertices((int)TileWorldSize.y, (int)TileWorldSize.x, y, x, vertices);

				index = AddTriangles(index, triangles);

				AddNormals(normals);
				AddUvs((int) DefaultTile.x, (int) DefaultTile.y, tileSizeY, tileSizeX, uvs);
			}
		}

		mesh.vertices = vertices.ToArray();
		mesh.normals = normals.ToArray();
		mesh.triangles = triangles.ToArray();
		mesh.uv = uvs.ToArray();
		mesh.RecalculateNormals();
	}

	private void AddVertices(int tileHeight, int tileWidth, int y, int x, ICollection<Vector3> vertices) {
		x = x - (int)_gridHalfWidth;
		y = y - (int)_gridHalfHeight;

		vertices.Add(new Vector3((x * tileWidth), 0, (y * tileHeight)));
		vertices.Add(new Vector3((x * tileWidth) + tileWidth, 0, (y * tileHeight)));
		vertices.Add(new Vector3((x * tileWidth) + tileWidth, 0, (y * tileHeight) + tileHeight));
		vertices.Add(new Vector3((x * tileWidth), 0, (y * tileHeight) + tileHeight));
	}

	private int AddTriangles(int index, ICollection<int> triangles) {
		triangles.Add(index + 2);
		triangles.Add(index + 1);
		triangles.Add(index);
		triangles.Add(index);
		triangles.Add(index + 3);
		triangles.Add(index + 2);

		index += 4;

		return index;
	}

	private void AddNormals(ICollection<Vector3> normals) {
		normals.Add(Vector3.up);
		normals.Add(Vector3.up);
		normals.Add(Vector3.up);
		normals.Add(Vector3.up);
	}

	private void AddUvs(int tileRow, int tileColumn, float tileSizeY, float tileSizeX, ICollection<Vector2> uvs) {
		uvs.Add(new Vector2(tileColumn * tileSizeX, tileRow * tileSizeY));
		uvs.Add(new Vector2((tileColumn + 1) * tileSizeX, tileRow * tileSizeY));
		uvs.Add(new Vector2((tileColumn + 1) * tileSizeX, (tileRow + 1) * tileSizeY));
		uvs.Add(new Vector2(tileColumn * tileSizeX, (tileRow + 1) * tileSizeY));
	}
}

Here is an example of my problem:

As you can see, the red tile isn’t on the right position anymore…
Oddly it only seems to work on even sizes (3x3, 4x4, etc)

The code I use to render the red tile:

meshCreator.CreatePlane((int)buildSelection.x, (int)buildSelection.y);
meshCreator.UpdateGrid(new Vector2(1, 1), cantBuild);

WIth “buildSelection” being a Vector2 with the build size.

When I use 4x6 it’s even more messed up:

Does anyone know how to fix this?
Please let me know! (I’m getting desperate)

So, it works correctly on square grids, but the more rectangular it gets, the more “offset” to the downward direction it gets. Seem right?

That seems to me like a classic “got the x and the y flipped around somewhere” bug. Lines 69-72 in your code look like the place that would have happened. Does flipping the x and y in those array references make it correct? If I assume the grid is laid out the way my grids are always laid out in 1-dimensional arrays, then the array is arranged in rows, so the Y should be the bigger number (multiplied by the width, that is), and the X should be the smaller.

I’ve tried swapping the X Y in the array, no result, then I tried swapping the X Y of the Vector2 created, nothing.
Then I tried switching both, still the same :confused:

…NO effect? Not even an incorrect effect? It should have changed SOMETHING.

On lines 100 and 105, you call two similar functions. I can’t help but notice that one of the functions has its parameters as “x,y,x,y”, while the other has parameters in the order of “x,y,y,x”. Could that be causing a similar issue?

Yeah fixed that, didn’t do anything noticable tho, maybe you should try and copy/paste the code and check some positions in a own build?

When you are reassigning UV’s in the grid update, you’re using grid width but what you want is grid height. You are trying to multiply the x position by the max x unit but what you really want is to multiply x by the max y unit to actually move to the next column.

this chunk in UpdateGrid

uvs[(int)(_gridWidth * gridIndex.x + gridIndex.y) * 4 + 0] = new Vector2(tileIndex.x * tileSizeX, tileIndex.y * tileSizeY);
uvs[(int)(_gridWidth * gridIndex.x + gridIndex.y) * 4 + 1] = new Vector2((tileIndex.x + 1) * tileSizeX, tileIndex.y * tileSizeY);
uvs[(int)(_gridWidth * gridIndex.x + gridIndex.y) * 4 + 2] = new Vector2((tileIndex.x + 1) * tileSizeX, (tileIndex.y + 1) * tileSizeY);
uvs[(int)(_gridWidth * gridIndex.x + gridIndex.y) * 4 + 3] = new Vector2(tileIndex.x * tileSizeX, (tileIndex.y + 1) * tileSizeY);

should actually be

uvs[(int)(_gridHeight * gridIndex.x + gridIndex.y) * 4 + 0] = new Vector2(tileIndex.x * tileSizeX, tileIndex.y * tileSizeY);
uvs[(int)(_gridHeight * gridIndex.x + gridIndex.y) * 4 + 1] = new Vector2((tileIndex.x + 1) * tileSizeX, tileIndex.y * tileSizeY);
uvs[(int)(_gridHeight * gridIndex.x + gridIndex.y) * 4 + 2] = new Vector2((tileIndex.x + 1) * tileSizeX, (tileIndex.y + 1) * tileSizeY);
uvs[(int)(_gridHeight * gridIndex.x + gridIndex.y) * 4 + 3] = new Vector2(tileIndex.x * tileSizeX, (tileIndex.y + 1) * tileSizeY);

Oh wow, how could have I missed that xD
It works great now! Thanks!

For anyone who would like to use it:

[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(MeshRenderer))]
public class MeshCreator : MonoBehaviour {
	/// <summary>
	/// The Unity unit size (1 = default)
	/// </summary>
	public Vector2 TileWorldSize = new Vector2(1, 1);

	/// <summary>
	/// The amount of tiles on the Tile Sheet
	/// </summary>
	public Vector2 TileAmount = new Vector2(10, 10);

	/// <summary>
	/// The default tile, normally 0,0
	/// </summary>
	public Vector2 DefaultTile;

	public bool drawRandomTiles;

	private int _gridWidth;
	private int _gridHeight;

	private float _gridHalfWidth;
	private float _gridHalfHeight;

	private Vector2 offset;
	public MeshFilter meshFilter;

	void Awake() {
		meshFilter = GetComponent<MeshFilter>();
	}

	void Update() {
		if (drawRandomTiles) {
			DrawRandomTiles();
		}
	}

	private void DrawRandomTiles() {
		int tileColumn = Random.Range(0, (int)TileAmount.x);
		int tileRow = Random.Range(0, (int)TileAmount.y);

		int x = Random.Range(0, _gridWidth);
		int y = Random.Range(0, _gridHeight);

		UpdateGrid(new Vector2(x, y), new Vector2(tileColumn, tileRow));
	}

	public void UpdateGrid(Vector2 gridIndex, Vector2 tileIndex) {
		Mesh mesh = meshFilter.mesh;
		Vector2[] uvs = mesh.uv;

		float tileSizeX = 1.0f / TileAmount.x;
		float tileSizeY = 1.0f / TileAmount.y;

		uvs[(int)(_gridHeight * gridIndex.x + gridIndex.y) * 4 + 0] = new Vector2(tileIndex.x * tileSizeX, tileIndex.y * tileSizeY);
		uvs[(int)(_gridHeight * gridIndex.x + gridIndex.y) * 4 + 1] = new Vector2((tileIndex.x + 1) * tileSizeX, tileIndex.y * tileSizeY);
		uvs[(int)(_gridHeight * gridIndex.x + gridIndex.y) * 4 + 2] = new Vector2((tileIndex.x + 1) * tileSizeX, (tileIndex.y + 1) * tileSizeY);
		uvs[(int)(_gridHeight * gridIndex.x + gridIndex.y) * 4 + 3] = new Vector2(tileIndex.x * tileSizeX, (tileIndex.y + 1) * tileSizeY);

		mesh.uv = uvs;
	}

	public void CreatePlane(int gridWidth, int gridHeight) {
		_gridWidth = gridWidth;
		_gridHeight = gridHeight;

		_gridHalfWidth = _gridWidth / 2.0F;
		_gridHalfHeight = _gridHeight / 2.0F;
		
		Mesh mesh = new Mesh();
		meshFilter.mesh = mesh;

		float tileSizeX = 1.0f / TileAmount.x;
		float tileSizeY = 1.0f / TileAmount.y;

		List<Vector3> vertices = new List<Vector3>();
		List<int> triangles = new List<int>();
		List<Vector3> normals = new List<Vector3>();
		List<Vector2> uvs = new List<Vector2>();

		int index = 0;

		for (int x = 0; x < gridWidth; x++) {
		    for (int y = 0; y < gridHeight; y++) {

				AddVertices((int)TileWorldSize.y, (int)TileWorldSize.x, y, x, vertices);

				index = AddTriangles(index, triangles);

				AddNormals(normals);
				AddUvs((int) DefaultTile.y, (int) DefaultTile.x, tileSizeY, tileSizeX, uvs);
			}
		}

		mesh.vertices = vertices.ToArray();
		mesh.normals = normals.ToArray();
		mesh.triangles = triangles.ToArray();
		mesh.uv = uvs.ToArray();
		mesh.RecalculateNormals();
	}

	private void AddVertices(int tileHeight, int tileWidth, int y, int x, ICollection<Vector3> vertices) {
		x = x - (int)_gridHalfWidth;
		y = y - (int)_gridHalfHeight;

		vertices.Add(new Vector3((x * tileWidth), 0, (y * tileHeight)));
		vertices.Add(new Vector3((x * tileWidth) + tileWidth, 0, (y * tileHeight)));
		vertices.Add(new Vector3((x * tileWidth) + tileWidth, 0, (y * tileHeight) + tileHeight));
		vertices.Add(new Vector3((x * tileWidth), 0, (y * tileHeight) + tileHeight));
	}

	private int AddTriangles(int index, ICollection<int> triangles) {
		triangles.Add(index + 2);
		triangles.Add(index + 1);
		triangles.Add(index);
		triangles.Add(index);
		triangles.Add(index + 3);
		triangles.Add(index + 2);

		index += 4;

		return index;
	}

	private void AddNormals(ICollection<Vector3> normals) {
		normals.Add(Vector3.up);
		normals.Add(Vector3.up);
		normals.Add(Vector3.up);
		normals.Add(Vector3.up);
	}

	private void AddUvs(int tileRow, int tileColumn, float tileSizeY, float tileSizeX, ICollection<Vector2> uvs) {
		uvs.Add(new Vector2(tileColumn * tileSizeX, tileRow * tileSizeY));
		uvs.Add(new Vector2((tileColumn + 1) * tileSizeX, tileRow * tileSizeY));
		uvs.Add(new Vector2((tileColumn + 1) * tileSizeX, (tileRow + 1) * tileSizeY));
		uvs.Add(new Vector2(tileColumn * tileSizeX, (tileRow + 1) * tileSizeY));
	}
}

Hey, don’t feel bad, I missed it too. (Despite knowing exactly where to look for it!)

Mesh generation code fries your brain. I’ve been there.

I’ve done worse, wondering why I’m getting out of bounds issues and finding I’m making the array out of width and width.