I'm trying to write a shader in unity that will take tiles from a sprite sheet and draw them onto a tile map.

I'm new to shaders so this might be totally wrong, I think it might just be a math error on my part, but maybe I'm misunderstanding how shaders work.

    Shader "Custom/TileMapShader" {
Properties {
_TileSheet ("Tile_Sheet", 2D) = "white" {}
}
Pass {
CGPROGRAM

#pragma vertex vert
#pragma fragment frag

#include "UnityCG.cginc"

uniform sampler2D _TileSheet;
uniform float2 _MapSize;

struct vertexInput {
float4 vertex : POSITION;
float2 texcoord: TEXCOORD0;
float4 tilecoord : TEXCOORD1;
};

struct vertexOutput {
float4 pos : SV_POSITION;
float2 tex: TEXCOORD0;
float2 tileIndex : VECTOR;
};

vertexOutput vert (vertexInput v)
{
vertexOutput output;

output.pos = mul (UNITY_MATRIX_MVP, v.vertex);
output.tex = v.texcoord;
output.tileIndex = v.tilecoord.xy; // <-- Using UV2 might be a bit hacky
//     to get the tile index but it
//     seemed better than creating a
//     texture to do it...

return output;
}

float4 frag (vertexOutput i) : SV_Target
{
int xTile = (i.tileIndex.x * 16);
int yTile = (i.tileIndex.y * 16);

float2 uv = float2(xTile, yTile + 1) / 16;

float xoffset = frac( i.tex.x * _MapSize.x ) / 16;
float yoffset = frac( i.tex.y * _MapSize.y ) / 16;

uv += float2( xoffset, -yoffset );

uv.y = 1.0 - uv.y;

return tex2D(_TileSheet, uv);
}

ENDCG
}
}
}


I stumbled across this http://connorhollis.com/code/fast-tilemap-shader/ and modified it to get what I have above.

this is how I create my Mesh: (I don't think this is the issue but who knows)

    void BuildMesh()
{
int numTiles = (int) (mapSize.x * mapSize.y);
int numTris = numTiles * 2;

int vsize_x = (int)mapSize.x + 1;
int vsize_z = (int)mapSize.y + 1;
int numVerts = vsize_x * vsize_z;

// Generate the mesh data
Vector3[] vertices = new Vector3[numVerts];
Vector3[] normals = new Vector3[numVerts];
Vector2[] uv = new Vector2[numVerts];

//Tiles
_tileMap = new Vector2[numVerts];

int[] triangles = new int[numTris * 3];

int x, z;
for (z = 0; z < vsize_z; z++)
{
for (x = 0; x < vsize_x; x++)
{
vertices[z * vsize_x + x] = new Vector3(x * TileSize, 0, -z * TileSize);
normals[z * vsize_x + x] = Vector3.up;
uv[z * vsize_x + x] = new Vector2((float)x / (int)mapSize.x, 1f - (float)z / (int)mapSize.y);
//Tiles TEMP
switch (Random.Range(0, 2))
{                                             // Tile Selection
case 0:                                   // X              Y
_tileMap[z * vsize_x + x] = new Vector2((1 * 0.0625f), (0 * 0.0625f));
break;
case 1:
_tileMap[z * vsize_x + x] = new Vector2((2 * 0.0625f), (0 * 0.0625f));
break;
}

}
}

for (z = 0; z < (int)mapSize.y; z++)
{
for (x = 0; x < (int)mapSize.x; x++)
{
int squareIndex = z * (int)mapSize.x + x;
int triOffset = squareIndex * 6;
triangles[triOffset + 0] = z * vsize_x + x + 0;
triangles[triOffset + 2] = z * vsize_x + x + vsize_x + 0;
triangles[triOffset + 1] = z * vsize_x + x + vsize_x + 1;

triangles[triOffset + 3] = z * vsize_x + x + 0;
triangles[triOffset + 5] = z * vsize_x + x + vsize_x + 1;
triangles[triOffset + 4] = z * vsize_x + x + 1;
}
}

//Populate mesh with the data
_mesh.vertices = vertices;
_mesh.triangles = triangles;
_mesh.normals = normals;
_mesh.uv = uv;
_mesh.uv2 = _tileMap;

_material.SetVector("_MapSize", new Vector4(mapSize.x, mapSize.y));
}


And this is how it all ends up looking

and this is the tile sheet I'm using to test. There are 16x16 tiles, and each tile is 16x16 pixels

http://i.imgur.com/XzwmOdm.png

Thank you for the Help, I've been stuck on this for a few days now, I tried to keep things short and to the point.

• Hey @Dusty I wrote that! Here is the full article if you didn't see it yet. link I also uploaded the sample project I built to dropbox here – Connor Hollis Mar 20 '15 at 18:28
• @ConnorHollis, Thanks! I'll take a look at the full article and Demo and see what I get! – Dusty Mar 20 '15 at 18:42

It turns out it was the Mesh creation and the shader was working fine.

In my above example vertices are shared between different tiles, below vertices are only shared between the 2 triangles that make each tile.

        void BuildMesh()
{
Vector3[] vertices = new Vector3[(int) mapSize.x * (int) mapSize.y * 4];
Vector3[] normals = new Vector3[vertices.Length];
Vector2[] uv = new Vector2[vertices.Length];

_tileMap = new Vector2[vertices.Length];

int[] triangles = new int[vertices.Length * 6];

float width =  (int)mapSize.x;
float height = (int)mapSize.y;

float x, z;
for (z = 0; z < mapSize.y; z++)
{
for (x = 0; x < mapSize.x; x++)
{
int squareVal = (int)((z * width + x) * 4);

vertices[squareVal + 0] = new Vector3(x, 0, -z);
vertices[squareVal + 1] = new Vector3(x + 1, 0, -z);
vertices[squareVal + 2] = new Vector3(x, 0, -z - 1);
vertices[squareVal + 3] = new Vector3(x + 1, 0, -z - 1);

normals[squareVal + 0] = Vector3.up;
normals[squareVal + 1] = Vector3.up;
normals[squareVal + 2] = Vector3.up;
normals[squareVal + 3] = Vector3.up;

uv[squareVal + 0] = new Vector2((x + 0f) / width, 1f - (z + 0f) / height);
uv[squareVal + 1] = new Vector2((x + 1f) / width, 1f - (z + 0f) / height);
uv[squareVal + 2] = new Vector2((x + 0f) / width, 1f - (z + 1f) / height);
uv[squareVal + 3] = new Vector2((x + 1f) / width, 1f - (z + 1f) / height);

_tileMap[squareVal + 0] = Vector2.zero;
_tileMap[squareVal + 1] = Vector2.zero;
_tileMap[squareVal + 2] = Vector2.zero;
_tileMap[squareVal + 3] = Vector2.zero;

int triangleVal = squareVal * 6;

triangles[triangleVal + 0] = squareVal + 0;
triangles[triangleVal + 1] = squareVal + 1;
triangles[triangleVal + 2] = squareVal + 2;

triangles[triangleVal + 3] = squareVal + 1;
triangles[triangleVal + 4] = squareVal + 3;
triangles[triangleVal + 5] = squareVal + 2;
}
}

//Populate mesh with the data
_mesh.vertices = vertices;
_mesh.triangles = triangles;
_mesh.normals = normals;
_mesh.uv = uv;
_mesh.uv2 = _tileMap;

_material.SetVector("_MapSize", new Vector4(mapSize.x, mapSize.y));
}


and to set a tile I do this for each tile

void setTile(Vector2 tile, Vector2 sprite)
{
int squareVal = (int)((tile.y * mapSize.x + tile.x) * 4);

_tileMap[squareVal + 0] = sprite * 0.0625f;
_tileMap[squareVal + 1] = sprite * 0.0625f;
_tileMap[squareVal + 2] = sprite * 0.0625f;
_tileMap[squareVal + 3] = sprite * 0.0625f;
}


and call this when I want to push the changes.

    _mesh.uv2 = _tileMap;