1
\$\begingroup\$

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.

Here's my Shader:

    Shader "Custom/TileMapShader" {
Properties {
    _TileSheet ("Tile_Sheet", 2D) = "white" {}
}
SubShader {
    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

Messed up Tile Map

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.

\$\endgroup\$
  • 1
    \$\begingroup\$ 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 \$\endgroup\$ – Connor Hollis Mar 20 '15 at 18:28
  • 1
    \$\begingroup\$ @ConnorHollis, Thanks! I'll take a look at the full article and Demo and see what I get! \$\endgroup\$ – Dusty Mar 20 '15 at 18:42
1
\$\begingroup\$

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;
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.