Menu

11 Aralık 2012 Salı

Pack It Up !

Draw Calls! We all hate it,we all have problems with it and it makes our games slow!

As you know there are lot of variables which might affect your games performance which i have explained in my earlier post.But even if you apply all those fixes,tricks whatever you call them,sometimes it's still not enough.So here it is another great way of optimizing performance.

First things first,What will you learn from that arcticle ?

I will explain Texture Atlases,Material sharing and a little bit about UV mapping.I will not go into detail of eveything,but you can ask in comments if you have any questions.

Texture Atlases


They are basicly a bunch of textures put in to a single big texture.But why do it ?First reason is preventing little hickups caused by loading textures.The catch is,when you put some textures together into one big image,vram only loads 1 texture and uses that for multiple objects in your game.This saves you from loading and unloading multiple textures all the time and with Material Sharing which i will explain later you will even save a lot of Draw Calls.
But how to do it ? I need to talk about a few more things before i can explain that.

 

 

 

 

Material Sharing

Imagine you have 5 objects,lets call them planets.They all need a different texture and a different material to hold this texture.And lets say all those planets will use the same shader.Well normally you would create 5 materials and assign 5 different textures to those materials.But that will cause 5 draw calls to draw all those planets.This is where material sharing becomes usefull,It reduces Draw Calls to 1 and also reduces the texture loading hickups with the help of Texture Atlases.

After explaining UV maps i can finally explain how to do it step by step.Because understanding those parts are quite important.


UV Mapping

Well,what is that UV thing ?Pretty straight forward,It maps your texture into your model.In the exmaple you can see a monkey texture mapped onto a head model.













 

 

But,How to do it ?

We will be using our planets again as an example.
  1. We have 5 planets and 5 textures in our hands.
  2. First we have to convert them into a Texture Atlas.
  3. You can use Texture Packer for that job.It works quite good and has a lot of options for different needs.
  4. To create a basic Texture Atlas.

    1. Run the program.
    2. Drag your textures to the right pane.
    3. Choose a place to save your file by filling Texture file box.
    4. After that you should play with the settings and choose what is best for your file.Sometimes the best atlas is when there is minimal empty spaces left or sometimes its best when all the textures are at the same size.Depends on your needs.
    5. Then you have to assign your new texture into a single material in Unity.
    6. Untill here we have created our Texture Atlas and material which will be shared across multiple objects.
      After this part we need to make a little bit coding.
      Read the code below carefully.It divides a single texture into defined rows and columns,then maps the UV to the desired part of that Texture.
      This script only works for planes which has 4 UV points.But it can be extended very easily.(As an example the one i'm using in my project can work with any model and also can animate the textures on them).
      I just didn't wanted to give you everything for free(No i'm not talking about money:)).Read the code,understand and make some simple changes to make it work with any model.Trust me just a few lines will make it work with any model !
      using UnityEngine;
      using System.Collections;
      
      public class TextureAtlasReader : MonoBehaviour
      {
      
          public int totalColumns = 3;
          public int totalRows = 3;
          public int row;
          public int column;
          Mesh _mesh;
          Vector2[] _uvs;
          Vector2 _dimensions;
          void Awake()
          {
              _mesh = gameObject.GetComponent<meshfilter>().mesh;
              Texture _texture = renderer.sharedMaterial.mainTexture;
              _dimensions = new Vector2((_texture.width / totalColumns) / (float)_texture.width,
                                       -((_texture.height / totalRows) / (float)_texture.height));
              Vector2[] _uvs = new Vector2[_mesh.uv.Length];
      
              Vector2 offset = new Vector2(column * _dimensions.x, 1 - (-_dimensions.y * row));
              _uvs[0] = offset + new Vector2(_dimensions.x * 0.0f, _dimensions.y * 1.0f);
              _uvs[1] = offset + new Vector2(_dimensions.x * 1.0f, _dimensions.y * 1.0f);
              _uvs[2] = offset + new Vector2(_dimensions.x * 0.0f, _dimensions.y * 0.0f);
              _uvs[3] = offset + new Vector2(_dimensions.x * 1.0f, _dimensions.y * 0.0f);
              _mesh.uv = _uvs;
          }
      }
      
    7. The last step ! 
      1. Just attach this script to the "Plane" which you want to use Texture Atlasing.
      2. From Editor GUI;Enter total row and column count.
      3. From Editor GUI;Enter the row and column  which you want to use for each object.
    8. And thats all ! Now you are sharing Material between multiple objects and drawing them in just a single Draw Call.

 Things to be aware of:

  • Sometimes it can be better not to use Texture Atlases.
  • You can use this code wherever you want.But if you use in your game it would be good drop by and leave me a message.So i can check you game :)
  • If you have made a better version,i would be glad to see it.
  • I'm not saying this is the best method.
  • My english is not the best. :)
  • For further Performance Optimization read Draw Call Batching from Unity website.
It took great amount of time to learn it when i was just beginning game development.So i hope it will save you a little bit of time.

26 Kasım 2012 Pazartesi

The Other Ideas

When developing games and probably all the creative stuff that requires team work,you will face some moments that you think that your idea is the best but the other people in your team just can't understand you.

Well,we all have those moments.But what i've learned from all those projects  and teams is sometimes you just have to stop for second ,clear your mind and take look at your self from their eyes.I know it sounds so cliche,but hey,it works.I have been in so many arguments that i thought my idea was better.But after really thinking about their idea and considering it "seriously",i realized sometimes their ideas are just better or sometimes we can even find a new idea by mixing our ideas together.

Sometimes we care more for ourselves than the product we are trying to build.We do this by knowing our unconsciously.But when you take a breath and see yourself from far,you will understand you are a team and you should discuss everything,but you should respect others ideas as much as yours.When you don't do that,the decisions on the project will be quite hard to finalize and you will start to lose the team spirit.

So whatever you do,if you want a publish a good game,remember that you are not the sole developer on that project and it's always good to hear out what other people had to say.They can see the points your are missing or find a better alternative.

Always trust in your team !


4 Kasım 2012 Pazar

Spikes Everywhere !

I'm sure when you start developing a new you are seeing those nasty spikes on your profiler.(If you are not using it, better start now :) )Those spikes are very important especially if you are developing for mobile.There are a lot of reasons for those spikes,unoptimized code,oversized textures etc.
I will try to cover solutions for those problems,

Cache,Cach,Cache
If you are a researcher type or read some artciles about optimization you already now that you should Cache everything you can.
So what does it mean caching anyways ?

using UnityEngine;
using UnityEngine;
using System.Collections;

public class example : MonoBehaviour 
{ 
    //Create the variable to hold our cached transform.
    private Transform _transform;
 
    void Awake() 
    {
        //Cache the Transform in the _transform variable.
        _transform = transform;
    }

    void Update()
    {
        _transform.position = Vector3.Zero;
    } 

} 

Lets interpret the code together.
First we have created a new variable with the type of Transform.Then at the Awake() method we  have cached our transform object into _transform variable.After that whenever we need to access the Transform object we can just use the _transform variable and save a lot of processing power.If you don't do this whenever you call transform component,Unity will have to make an expensive component lookup.

And another great thing,you can this for all the objects you will have to acces a lot of times.

Spike when using GetComponent Method
Whatever you do,try to avoid this and similiar methods that can cause Component Lookups.Everytime you use this methods Unity will have to search all the components to find what you are looking for.And it can be a really slow and expensive process.Try to cache that kind of items at the Awake() or your Start() methods which will be little bit slower to initiate but a lot more faster to use in game.

Instantiating Objects
Well instantiating objects can really be an expensive process.There are a couple ways to avoid this problem.One of them is using a Pool Manager.A Pool Manager basicly instantiates the objects that will be used in game at the loading screen and keeps them in memory for later use.And this saves us lot of cpu and gpu power when we actually need the game objects.We only have to enable and reposition the objects we have instantiated and kept in memory at the beginning.

Destroying Objects
Destroying objects are actually a hidden cost.Destroying a couple of objects usually won't create an instant spike.But everything starts when the Garbage Collector comes to clean around.This will create a spike if you have Destroyed a lot of objects.The reason behind this is Garbage Collector got called from time to time automatically to free up the unnessecary memory(Destroyed objects etc...).

There are 2 solutions i know for this problem.First one is triggering GC.Collect() method with short intervals to force Garbage Collector to clean memory more frequently which can make garbage collecting faster due to cleaning less memory each time.But i don't usually use this method since it can also create spikes sometimes.Another method is Object Pooling which i have explained before.When u are done with an object just disable it and wait for the time when u will need it.That way there is no objects destroyed and there wont be much job for garbage collector to do.And it also saves time when enabling that object instead of instantiating new one.

Big Spike On The Obcects First Appearance
This problem is one of the most common problems and it can be solved pretty easy with preloading objects.

Preloading is actually a simple yet very effective idea.The idea is showing each object to the camera at loading screen and let the camera render the each object.This way all your objcets will already be rendered and loaded into vram and thanks to that,Loading the texture etc. won't be necesary when the object is actually needed.

Note that preloading objects and Pool Manager methods are solutions for two different problems.Preloading is to save from first rendering time.Object Pooling wont make any visual preloading into memory.

Use Correct Shader
Shaders are very important for performance.And sometimes choosing wrong shader can cost you a lot of fps.First step is to check if you really need all the features of the shader you are using.If not find another shader which can fit better for your needs.For example if you are developing for Mobile you should always use shaders under the Mobile category in Unity.Those shaders are highly optimized for Mobile.But if you still have performance problems caused by shaders you even write your own to save even more performance.

Correct Compression Method
Choosing correct compression method for images and other media is also very important.For example using a 1024x1024 texture for an object that would look the same with 256x256 makes your game use a lot more memory then needed.And if you are using advanced mode for texture compression carefully decide which compression method you are using.Not all platforms support all of the compression methods.You will have to do a little research and experiment with compression methods the find the best fit for your needs.
Also for sounds and other media try to use ogv and ogg extensions which is natively supported by Unity.

Coroutines Are Your Friend
Using Corotuines for big loops or a method with lot of calculations etc. can be a great performance saver since it will not block other objects from doing their job.You can inspect the below example which is quite self explanatory.

using UnityEngine;
using System.Collections;

public class example : MonoBehaviour {
    //This method will take 1000 frames to finish its job but will not block other scripts or methods from running.
    //And it will let other object to their job each frame due to yield method WaitForEndOfFrame().
    IEnumerator Routine() 
    {
 for(i = 0;i < 1000;i++)
 {
  Debug.Log(i);
  yield return new WaitForEndOfFrame();
 }
    }
    //This will take less time to finish but it will block everything else till it finishes its job.
    void Normal()
    {
 for(i = 0;i < 1000;i++)
 {
  Debug.Log(i);
 }
    }
    void Start() 
    {
        StartCorotuine(Routine());
 Normal();
    }
}

Stop Animating Invisible Objects
Animating an invisible object is not a very logical thing to do.Stopping animation and other complex calculations while the object is not visible can save a lot of performance.In below example the Animation is Stopped when the object became invisible and Played when the object is visible again.

using UnityEngine;
using System.Collections;

public class example : MonoBehaviour{
    void OnBecameVisible() 
    {
        animation.Play();
    }
    void OnBecameInvisible() 
    {
        animation.Stop();
    }
} 

It's pretty much the basic and most important stuff for optimizing your game.I will try to keep you updated when i will have more time to share more with you !

If you have questions feel free to ask.I will try to answer them all.

Also check out those great pages by Unity team,
- www.docs.unity3d.com/Documentation/Manual/iphone-PracticalGuide.html
- www.blogs.unity3d.com/.../shadowgun-optimizing-for-mobile-sample-level/

3 Kasım 2012 Cumartesi

Changing The Topic

Sorry !
Sorry for not posting anything for longtime from everyone who was following this blog.I was quite busy at these last months(i know almost a year after my last post.).

The Change ?
OK,my working area has been changed a little bit since the last time i have posted.Well,I'm not a student anymore and started developing games for mobile and desktop platforms.So from now on i will try to post articles about game development.Mostly about the problems we have experienced with our team,the solutions we have found and some know-hows and best practices.

Since I'm a developer,the content will be highly focused on coding.But since I'm also an old designer and a little bit inside of the project management stuff,you will see posts about them as well.
For our new cross platform project(sssh its secret) with our team,we are using Unity3d Game Engine,which works quite well with most paltforms.

I hope it will be helpfull for those who want to start game developed or those having problems with game development.

Stay tuned !