• Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
0
Question by kobra.28 · Aug 12, 2014 at 02:45 PM · spritedrawcallstexture atlas

Tile Game slow. Lots of sprites. Texture atlas?

I have a simple tile game with two different in colour mono-coloured same sized tiles combined into 600x600 tile area; camera view is 80x80 tiles. All tiles are now sprites and I get around 30 fps on my computer. But as I want to run this game on my netbook (where I got around 5 fps :D), it is not sufficient. I searched on internet and found that texture atlases might be a solution for my problem.

BUT each tile must BE ABLE to be DESTROYED (or just change colour to e.g. black and save information that it has been destroyed by deleting its collider for example) BTW should I use colliders for every tile? But what if I have a texture atlas?. AND the map is generated on run-time.

I also found out that Unity has some texture batching to save drawcalls; each tile has the same sprite-default material so does it batch in my game? (I hope it does not yet). Any ideas how to improve performance of this dumb game, which for performace reasons I tried to program with Python+Pygame, C#+WinForms and now Unity, but I guess it tends to SDL.

Stats: FPS: 3 when moving -30 stable (in editor, otherwise around 30 everytime) Main Thread: ~30ms, ~70ms when destroying some tiles Draw Calls: ~3000, Saved by batching ~3000 Tris: 12.6k Verts: 25.1k (what are they?) VBO Total: 14 - 198.3 KB

Comment
Add comment · Show 13
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image meat5000 ♦ · Aug 12, 2014 at 02:20 PM 0
Share

What do the rest of the stats say?

Screener it

avatar image robertbu · Aug 12, 2014 at 04:21 PM 0
Share

As @meat500 says, click on the stats button in the upper right corner of the Game window to see how much batching is going on. Based on the numbers, I doubt that your current approach of using individual game object (no matter what performance enhancements you make) will solve your problem. You are viewing 6400 game objects at a time out of 360000 game objects.

The solution to a grid of this size is to build each tile out of a quad in a mesh. Look up 'Voxels' or '$$anonymous$$inecraft' on Unity Answers and the net in general for information on how 'objects' are constructed in a single mesh. While most of the information you find for voxels concerns cubes, the same concept applies to tiles. Unity limits each mesh to a maximum of 64k of vertices, so you would need approximately 25 meshes each with 125 x 125 tiles to get your 600 x 600 grid. You change the contents of each tile by setting the uv in the mesh to map to the appropriate area of the texture atlas. Hit testing for figuring out what tile is hit is based on the triangle index in the mesh, or it can be done mathematically.

There is a significant learning curve to figuring this out, and the resulting code will be more complicated than your current approach, but you'll get a significant performance improvement...it will likely run on most mobile devices.

avatar image kobra.28 · Aug 12, 2014 at 07:35 PM 0
Share

@robertbu Thanks. But I don't know where to start. I don't need triangles for 2D, how exactly will they help in collision detection, wouldn't be verteces enough? O$$anonymous$$ I looked up how to programatically create a mesh. How do I do the material for it? Should I enable the shader (have no idea what shader to choose) and set colour or set the shader to unlit and add png texture of my "colour". Since my texture is only a colour and I don't need to scale anything I can use only a shader without any texture, but 'shader' sounds like something fancy that consumes power and that there needs to be some light :D? I don't have any light sources in my game.

avatar image robertbu · Aug 12, 2014 at 08:50 PM 0
Share

What I outline for a solution is not a beginner topic. It will take you some time to figure it all out. Take steps:

  • Build a single quad mesh. Vertices don't doing anything in terms of display. Unity displays things by triangles. So you will be creating 4 vertices and then from those vertices creating two triangles. Getting the winding right so the normals face the correct way is the main beginner challenge in building a mesh.

  • Figure out how to set the UV array for your quad to map different parts of texture to your quad. There are good resources on the net to understand UVs. Here is one answer where I show how to use a simple, hand-done texture atlas to put different images on the sides of a cube.

  • Next figure out how to build a a mesh with four quads. Each Quad must have its own set of vertices. You cannot share vertices.

  • Everything that is displayed by Unity requires a shader. For what you are doing, start by creating a material that uses Unlit/Texture for the shader and has the texture atlas for the texture. If each one of your tiles is really only a color, then eventually you may want to move to a shader that supports uv color and has no texture at all. It is a bit more efficient solution than using Unlit/Texture. The Unlit/Texture shader does not require any light. There are several other unlit textures in the Unity Wiki.

avatar image kobra.28 · Aug 13, 2014 at 09:11 PM 0
Share

@robertbu I created a simple quad two triangle mesh with material with coloured shader, but it's kind of dark. When I add light nothing changes. Culling $$anonymous$$ask is set to everything, I also tried moving the light along Z axis, but it's dark. Why?

Show more comments

2 Replies

· Add your reply
  • Sort: 
avatar image
2
Best Answer

Answer by robertbu · Aug 16, 2014 at 06:54 AM

I had worked up some code for another question and realized that what I had was about 80% of the way towards what you were looking for. So I spent a bit of time and reworked the code. It produces a grid of a specified size. 600 x 600 is no problem and runs at 60+ fps in the editor. There are not a whole lot of lines of code, but the code is intricate. If you are going to try and use it, I encourage you to spend the time to understand every line...and there's no guarantee that I got everything right. I included a SetQuadUV method that will set a cell specified by row and column to a specified sprite sheet cell. I think from your description you will need cell-level hit testing. You could expand this code so that each mesh could report triangle hits, but I think you are better off putting a quad over the top of this mesh and doing the hit testing mathematically.

Here is the sprite sheet I used to test the code:

alt text

And here is the code:

 using UnityEngine;
 using System.Collections;
 
 public class TileGrid : MonoBehaviour {
     public Material material;
     public float cellWidth = 1.0f;  
     public float cellHeight = 1.0f;
     public int rows = 10;
     public int cols = 10;
     public int spriteRows = 2;      
     public int spriteCols = 2;
 
     private const int maxQuadsPerMesh = 16380;
     private int quadsPerMesh;
     private GameObject[] goMeshes;
     private Mesh[] meshes;
     private float spriteCellWidth;
     private float spriteCellHeight;
     private int spriteCellCount;
     private int rowsPerMesh;
 
     // Sets specified row/col to the specified sprite sheet cell 
     public void SetQuadUV(int row, int col, int cell) {
         int imesh = row / rowsPerMesh;
         int r = row % rowsPerMesh;
         int baseIndex = (r * cols + col) * 4;
         Vector2[] uvs = meshes[imesh].uv;
         SetMeshQuadUV(uvs, baseIndex, cell);
         meshes[imesh].uv = uvs;
     }
 
     void Awake() {
         spriteCellWidth =  1.0f / spriteCols;
         spriteCellHeight = 1.0f / spriteRows;
         spriteCellCount = spriteRows * spriteCols;
 
         CreateMeshes();
     }
 
     void CreateMeshes() {
         if (rows <= 0 || cols <= 0) return;
 
         quadsPerMesh = (maxQuadsPerMesh / cols) * cols;
 
         rowsPerMesh = quadsPerMesh / cols;
         int currRow = 0;
         
         int meshCount = (rows * cols) / quadsPerMesh + 1;
         int quadsLastMesh = (rows * cols) % quadsPerMesh;
 
         float height = rows * cellHeight;
         float width = cols * cellWidth;
         Vector3 offset = new Vector3(-width / 2.0f, height / 2.0f, 0.0f);
 
         goMeshes = new GameObject[meshCount];
         meshes = new Mesh[meshCount];
         
         for (int i = 0; i < meshCount; i++) {
             GameObject go = new GameObject();
             go.transform.parent = transform;
             MeshFilter mf = go.AddComponent<MeshFilter>();
             Mesh mesh = new Mesh();
             mf.mesh = mesh;
             Renderer rend = go.AddComponent<MeshRenderer>();
             rend.material = material;
             go.AddComponent<MeshCollider>();
 
             Vector3[] vertices;
             int l = 0;
             if (i != meshCount - 1) {
                 vertices = new Vector3[4 * quadsPerMesh];
                 l = quadsPerMesh / cols;
             }
             else {
                 vertices = new Vector3[4 * quadsLastMesh];
                 l = quadsLastMesh / cols;
             }
             
             for (int j = 0; j < cols; j++) {
                 for (int k = 0; k < l; k++) {
 
                     vertices[(k * cols + j) * 4]      = new Vector3(j * cellWidth, -(k + currRow)* cellHeight, 0.0f) + offset;
                     vertices[(k * cols + j) * 4 + 1]  = new Vector3(j * cellWidth, -(k + currRow + 1) * cellHeight, 0.0f) + offset;
                     vertices[(k * cols + j) * 4 + 2]  = new Vector3((j + 1) * cellWidth, -(k + currRow + 1) * cellHeight, 0.0f) + offset;
                     vertices[(k * cols + j) * 4 + 3]  = new Vector3((j + 1) * cellWidth, - (k + currRow) * cellHeight, 0.0f) + offset;
                 }
             }
 
             currRow += rowsPerMesh;
 
             mesh.vertices = vertices;
 
             int[] triangles = new int[mesh.vertices.Length / 2 * 3];
                 
             for (int j = 0; j < vertices.Length / 4; j++) {
 
                 triangles[j * 6 + 0] = j * 4 + 0;    //     0_ 3        0 ___ 3
                 triangles[j * 6 + 1] = j * 4 + 3;    //   | /         |    /|
                 triangles[j * 6 + 2] = j * 4 + 1;    //  1|/            1|/__|2
                 
                 triangles[j * 6 + 3] = j * 4 + 3;    //       3
                 triangles[j * 6 + 4] = j * 4 + 2;    //    /|
                 triangles[j * 6 + 5] = j * 4 + 1;    //  1/_|2
             }
 
             mesh.triangles = triangles;
 
             Vector2[] uvs = new Vector2[vertices.Length];
 
             // Sets random UVs for each cell
             for (int j = 0; j < vertices.Length / 4; j++) {
                 SetMeshQuadUV(uvs, j * 4, Random.Range (1, spriteCellCount));
             }
 
             mesh.uv = uvs;
             goMeshes[i] = go;
             meshes[i] = mesh;
         }
     }
 
     // Sets the uvs for one quad in a provided array of uvs
     void SetMeshQuadUV(Vector2[] uvs, int baseIndex, int cell) {
         cell = cell % spriteCellCount;
         float x = cell % spriteCols * spriteCellWidth;
         float y = cell / spriteRows * spriteCellHeight;
 
         uvs[baseIndex] = new Vector2(x + 0.01f, y + spriteCellWidth - 0.01f);
         uvs[baseIndex + 1] = new Vector2(x + 0.01f, y + 0.01f);
         uvs[baseIndex + 2] = new Vector2(x + spriteCellWidth - 0.01f, y + 0.01f);
         uvs[baseIndex + 3] = new Vector2(x + spriteCellWidth - 0.01f, y + spriteCellHeight - 0.01f);
     }
 
     // Press space bar to set random grid cell to sprite sheet cell 0
     void Update() {
         if (Input.GetKeyDown (KeyCode.Space)) {
             int i = Random.Range (0, rows);
             int j = Random.Range (0, cols);
 
             Debug.Log ("Row = " + i + "  Col = " + j);
             SetQuadUV(i, j, 0);
         }
     }
 }



gridtile.png (41.8 kB)
Comment
Add comment · Show 9 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image kobra.28 · Aug 19, 2014 at 03:56 PM 0
Share

Thanks, but I don't get those % operators in Set$$anonymous$$eshQuadUV. Why not /?

avatar image Landern · Aug 19, 2014 at 04:01 PM 0
Share

because % or mod or modulo gets the re$$anonymous$$der after division, not the result of the division.

avatar image robertbu · Aug 19, 2014 at 04:14 PM 0
Share

Cell is the cell number to use in the sprite sheet. 'cell' is a one dimensional 'index' into a two dimensional layout of images using row and then column.

The '%' operator get the remainder, so it allows me to calculate the column. So I can take the 1D 'cell' specifier and calculates the UV for the lower left corner of the cell in the sprite sheet.

You could rewrite this code to just take a row and a col, but with tiles, the sprite sheet usually contains some state...active, inactive, destroyed, occupied, etc...so implementing a single index makes matching state to cells easier.

After I posted the above, I created a tiled surface 4000 x 4000 so 16 million tiles. There was no frame rate drop in the inspector.

avatar image siddharth3322 · Oct 27, 2014 at 10:39 AM 0
Share

Using above code, I was getting this type of grid structure. alt text

I have used following type of tile image for this purpose. Check with other type of shader also.

alt text

actualoutput.png (52.4 kB)
csg-544e1c639cadf.png (7.1 kB)
avatar image Keseren siddharth3322 · Jan 15, 2016 at 06:58 PM 0
Share
      void Set$$anonymous$$eshQuadUV(Vector2[] uvs, int baseIndex, int cell) {
          cell = cell % spriteCellCount;
          float x = cell % spriteCols * spriteCellWidth;
          float y = cell / spriteRows * spriteCellHeight;
  
          uvs[baseIndex] = new Vector2(x + 0.01f, y + spriteCellWidth - 0.01f);
          uvs[baseIndex + 1] = new Vector2(x + 0.01f, y + 0.01f);
          uvs[baseIndex + 2] = new Vector2(x + spriteCellWidth - 0.01f, y + 0.01f);
          uvs[baseIndex + 3] = new Vector2(x + spriteCellWidth - 0.01f, y + spriteCellHeight - 0.01f);
      }

          uvs[baseIndex] = new Vector2(x + 0.01f, y + spriteCellWidth - 0.01f);

Here is the error, the spriteCellWidth is inside the Height part. Replace it with spriteCellHeight and it will work again

Btw, you should remove those 0.01f additions and subtractions. Its better to just disable anti alias, which fixes the problem in a non clunky way.

Edit: Nvm the 0.01f additions is important. But I would rather use 1f/texture.width and 1f/texture.height. So that it only removes the one pixel needed. 0.01f may be a too big number if the texture is too big.

avatar image robertbu · Oct 27, 2014 at 04:27 PM 1
Share

I can reproduce your issue, but I cannot find the problem in the limited time I have this morning. You can get around the problem by duplicating your texture vertically to produce a 2 x 2 square sprite sheet.

Show more comments
avatar image
0

Answer by hellfreezes · Feb 25, 2015 at 08:29 AM

Don't foget about: mf.mesh.RecalculateBounds(); mf.mesh.RecalculateNormals();

Comment
Add comment · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Welcome to Unity Answers

If you’re new to Unity Answers, please check our User Guide to help you navigate through our website and refer to our FAQ for more information.

Before posting, make sure to check out our Knowledge Base for commonly asked Unity questions.

Check our Moderator Guidelines if you’re a new moderator and want to work together in an effort to improve Unity Answers and support our users.

Follow this Question

Answers Answers and Comments

5 People are following this question.

avatar image avatar image avatar image avatar image avatar image

Related Questions

SpriteRenderer and texture atlas 0 Answers

different atlas groups makes more draw calls ? 0 Answers

Is it possible to utilize iphone dynamic batching with "traditional" animated sprite techniques? 1 Answer

how can i draw many (1000+) sprites 1 Answer

Sprite Editor/Pack Atlas Question for 2D Animating 0 Answers


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges