Saturday, July 5, 2014

Task #8: Save and load game state. Time player.

Practical use of Unity3d engine.


Task #8: Save and load game state. Time player.

  There is such task: we need to implement “Scene player in Unity3d”. This player is able to save positions of all objects in the scene and after saving and can restore game objects’ positions. This functionality may be used in games for “back to time” task implementation and also it is a very cool feature :).
  The application should have such functionality: there are a lot of objects fall on the plane. They push each other and fall from the plane. There is button “Record” on the screen. This button allows to start the recording of the scene. After user presses the “Record” button, the button “Recording…Stop” will appear. This button stops the recording of the scene and shows the slider. This slider allows to revert scene state to any recorded time frame.
  Let’s solve this task. The main idea of the application it is to save positions of all objects on the scene in determinate time frames. So, we should create data types and variables. This will help us to save the scene state.

/// 
/// This is data stored for one frame of the saved data
/// 
public class SavedState
{
    public GameObject GO;
    public float Time;
    public Vector3 Position;
    public Quaternion Rotation;
    public Vector3 ScaleLocal;
}


  Array of Dictionaries it is a variable that will save positions’ of scene objects. Each item of the array will store the scene state in current time frame.

private List< Dictionary < GameObject, SavedState > > mSavedStates = new List < Dictionary < GameObject, SavedState > >();

  Now we should determine how we will enable and disable physical influence on the objects. Any objects of the scene that uses physics has “Rigidbody” component (see: http://docs.unity3d.com/ScriptReference/Rigidbody.html). To enable or disable physical influence on objects we should use the “Rigidbody.isKinematic” property (see: http://docs.unity3d.com/ScriptReference/Rigidbody-isKinematic.html). So, function of enabling or disable the physical influence will be such:

/// 
/// Just set "Kinematic" for all game objects
/// 
/// 
private void EnablePhysics(bool enable)
{
    Rigidbody[] rigid = GameObject.FindObjectsOfType();
    foreach (Rigidbody r in rigid)
    {
        r.isKinematic = !enable;
    }
}


  As far as application has several states (simple, recording, playing) we will create enumeration and variable that will be responsible for the current game state. This variable will be used in all main functions: Update, OnGUI.

/// 
/// Possible application states
/// 
public enum EGameState
{
    None = 0,
    Recording,
    Reproduce
}


  And here is variable:

// current game state
private EGameState mGameState = EGameState.None;


  To save state of each Unity3d object in the scene we should save position, rotation and scale of this object. In such a way save of the state function will be such:

private void SaveGosCurrentState()
{
    if (mGameState != EGameState.Recording)
    {
        return;
    }

    GameObject[] gos = GameObject.FindObjectsOfType();

    // create current time state
    Dictionary stateData = new Dictionary();

     // add game objects to dict
     foreach (GameObject go in gos)
     {
         SavedState state = new SavedState() 
         { 
             GO = go,
             Position = go.transform.position,
             Rotation = go.transform.rotation,
             ScaleLocal = go.transform.localScale,
             Time = Time.realtimeSinceStartup
         };
         stateData[go] = state;
     }

     // add current time state to list
     mSavedStates.Add(stateData);
 }

  And setting the determinate time state is presented here:

private void SetGosStateByDict(Dictionary currentStateData)
{
    // restore time data
    foreach (KeyValuePair kvp in currentStateData)
    {
         SavedState savedData = kvp.Value;
         GameObject goToRestore = savedData.GO;

         // restore transformations
         goToRestore.transform.position = savedData.Position;
         goToRestore.transform.rotation = savedData.Rotation;
         goToRestore.transform.localScale = savedData.ScaleLocal;
    }
 }


  If you need more detailed article description, please write it in comments.
  Demo of the application you can download here:
  https://dl.dropboxusercontent.com/u/20023505/Articles/Unity3d/Lesson8/Build/Build.zip
  Results of development you can see here:
  https://dl.dropboxusercontent.com/u/20023505/Articles/Unity3d/Lesson8/WebPlayer/WebPlayer.html
  Free source codes you can download here:
  https://dl.dropboxusercontent.com/u/20023505/Articles/Unity3d/Lesson8/Sources/Sources.zip
  https://github.com/den-potapenko/Unity3dArticles/tree/master/Lesson8

Задача 8: Сохранение и загрузка состояния сцены. Временной плеер.


Практическое использование движка Unity3d.


Задача 8: Сохранение и загрузка состояние сцены. Временной плеер.


  Решим следующую задачу: необходимо реализовать «Unity3d плеер сцены». Этот плеер умеет сохранять позиции всех объектов в сцене, а после сохранения может восстановить их позиции. Это возможность может использоваться в играх, для создания эффекта «вернуться во времени», ну и вообще это очень прикольная фича :).

  Программа должна выглядеть следующим образом: в приложении падают определенное количество физический объектов на плоскость. Соударяются и падают с плоскости. На экране имеется кнопка «Record», которая позволяет начать запись сцены. По ее нажатию появляется кнопка «Recording…Stop». После того как пользователь нажал на кнопку «Recording…Stop», программа останавливает запись сцены и показывает слайдер, с помощью которого можно вернуть сцену в любой записанный промежуток времени.
  Решим эту задачу. Основная суть приложения – это сохранять позиции объектов в определенные промежутки времени. Поэтому для этого создадим типы данных и переменные, с помощью которых мы сможем сохранять позиции всех объектов.

/// 
/// This is data stored for one frame of the saved data
/// 
public class SavedState
{
    public GameObject GO;
    public float Time;
    public Vector3 Position;
    public Quaternion Rotation;
    public Vector3 ScaleLocal;
}
  Переменная, которая будет хранить позиции этих объектов это массив из Dictionary. Каждая единица массива хранить состояние сцены в текущий момент времени:
private List< Dictionary < GameObject, SavedState > > mSavedStates = new List< Dictionary< GameObject, SavedState > >();


  Теперь нужно понять, как мы будем включать и выключать физическое воздействие на объекты. У любого объекта, который работает по физическим законам в Unity3d есть компонент «Rigidbody» (см. http://docs.unity3d.com/ScriptReference/Rigidbody.html). Для того чтоб включить или выключить физическое взаимодействие физики на данный «Rigidbody», нужно использовать свойство «Rigidbody.isKinematic» (см. http://docs.unity3d.com/ScriptReference/Rigidbody-isKinematic.html). Следовательно, функция включения и выключения взаимодействия физики будет выглядеть следующим образом:
/// 
/// Just set "Kinematic" for all game objects
/// 
/// 
private void EnablePhysics(bool enable)
{
    Rigidbody[] rigid = GameObject.FindObjectsOfType();
    foreach (Rigidbody r in rigid)
    {
        r.isKinematic = !enable;
    }
}


  Т.к. в программе явно существует несколько состояний (обычное, запись, проигрывание) то создадим перечисление и переменную, которая будет отвечать за текущее состояние приложения. Эта переменная будет использоваться во всех основных функциях: Update, OnGUI.

/// 
/// Possible application states
/// 
public enum EGameState
{
    None = 0,
    Recording,
    Reproduce
}


  И переменная:
// current game state
private EGameState mGameState = EGameState.None;

  Для того чтоб сохранить положение любого объекта в Unity3d нужно сохранить позиции, вращение и растяжение объекта. Таким образом функция сохранения состояния будет выглядеть так:
private void SaveGosCurrentState()
{
    if (mGameState != EGameState.Recording)
    {
        return;
    }

    GameObject[] gos = GameObject.FindObjectsOfType();

    // create current time state
    Dictionary stateData = new Dictionary();

     // add game objects to dict
     foreach (GameObject go in gos)
     {
         SavedState state = new SavedState() 
         { 
             GO = go,
             Position = go.transform.position,
             Rotation = go.transform.rotation,
             ScaleLocal = go.transform.localScale,
             Time = Time.realtimeSinceStartup
         };
         stateData[go] = state;
     }

     // add current time state to list
     mSavedStates.Add(stateData);
 }


  А установка определенного состояния будет выглядеть так:
private void SetGosStateByDict(Dictionary currentStateData)
{
    // restore time data
    foreach (KeyValuePair kvp in currentStateData)
    {
         SavedState savedData = kvp.Value;
         GameObject goToRestore = savedData.GO;

         // restore transformations
         goToRestore.transform.position = savedData.Position;
         goToRestore.transform.rotation = savedData.Rotation;
         goToRestore.transform.localScale = savedData.ScaleLocal;
    }
 }


  Демонстрацию работы можно скачать здесь:
https://dl.dropboxusercontent.com/u/20023505/Articles/Unity3d/Lesson8/Build/Build.zip

  Посмотреть онлайн работу можно здесь:
https://dl.dropboxusercontent.com/u/20023505/Articles/Unity3d/Lesson8/WebPlayer/WebPlayer.html
  Исходные коды работы, как всегда бесплатно, можно скачать здесь:
https://dl.dropboxusercontent.com/u/20023505/Articles/Unity3d/Lesson8/Sources/Sources.zip
https://github.com/den-potapenko/Unity3dArticles/tree/master/Lesson8

Sunday, October 6, 2013

Task #7: Getting the size of PNG textures before its full loading

Practical use of Unity3d engine.



Task #7: Getting the size of PNG textures before its full loading

  The next task we will solve today is get Texture2D size before its full loading.
  Unity3d is able to load PNG textures as Texture2D, but when size of the texture is too large, Unity3d application may crash. Therefore, the solution for this issue is to get texture size before its full loading.
  Now, let us solve this task. Each texture has its own file format. When we know the file format of the texture, we able to know the bytes that are responsible for the texture size. In such a way, we can determine size of texture, loading only the first bytes of the texture and even do not create Texture2d object.
  In this article, we will load PNG texture. See PNG format description here: http://www.libpng.org/pub/png/spec/1.2/PNG-Structure.html.
  Texture will be loaded via WWW class in Unity3d. We will analyze size of the texture after loading first 30 bytes. Before determining texture size application will check PNG texture file signature. It’s needed because we should be sure that file is PNG file.
  Let’s take a look to source code of retrieving texture size from bytes:


private void GetPNGTextureSize(byte[] bytes, out float width, out float height)
{
    width = -1.0f;
    height = -1.0f;

    // check only png tex!!! // http://www.libpng.org/pub/png/spec/1.2/PNG-Structure.html
    byte[] png_signature = { 137, 80, 78, 71, 13, 10, 26, 10 };
    
    const int cMinDownloadedBytes = 30;
    byte[] buf = bytes;
    if (buf.Length > cMinDownloadedBytes)
    {
        // now we can check png format
        for (int i = 0; i < png_signature.Length; i++)
        {
            if (buf[i] != png_signature[i])
            {
                Debug.LogWarning("Error! Texture os NOT png format!");
                return; // this is NOT png file!
            }
        }
        
        // now get width and height of texture
        width = buf[16] << 24 | buf[17] << 16 | buf[18] << 8 | buf[19];
        height = buf[20] << 24 | buf[21] << 16 | buf[22] << 8 | buf[23];
        
        Debug.Log("Loaded texture size: width = " + width + "; height = " + height);
        return;
    }
}

And source code of texture loading:

public Texture2D LoadSyncPNGTexture2D(string path)
{
    string resPath = cLocalPathPrefix + path;   
    Debug.Log("Try to load texture at: " + resPath);
    WWW www = new WWW(resPath);

    if (!string.IsNullOrEmpty(www.error))
    {
    Debug.LogWarning("Error! '" + www.error + "'");
    return null;
    }

    bool texChecked = false;
    while (!www.isDone)
    {
        if (!texChecked)
        {
            float texWidth = 0;
            float texHeight = 0;
            GetPNGTextureSize(www.bytes, out texWidth, out texHeight);

            if (texWidth < 0 || texHeight < 0)
            {
                continue;
            }

            texChecked = true;
        }
    }

    if (!string.IsNullOrEmpty(www.error))
    {
        Debug.LogWarning("Error! '" + www.error + "'");
        return null;
    }

    // get texture
    Texture2D tex = www.texture;
    return tex;

}

  If you need more detailed article description, please write it in comments.

  Demo of the application you can download here:
https://dl.dropboxusercontent.com/u/20023505/Articles/Unity3d/Lesson7/Build/Build.zip
  Free source codes you can download here:
https://dl.dropboxusercontent.com/u/20023505/Articles/Unity3d/Lesson7/Sources/Sources.zip
https://github.com/den-potapenko/Unity3dArticles/tree/master/Lesson7



Задача 7: Получение размера PNG текстуры до ее полной загрузки


Практическое использование движка Unity3d.





Задача 7: Получение размера PNG текстуры до ее полной загрузки


  Задача следующая: необходимо определит размер текстуры до ее полной загрузки. Дело в том что при загрузке большой текстуры в память, Unity3d приложение может «упасть». Для того чтоб предотвратить такое поведение приложения необходимо узнать размер загружаемой текстуры до ее полной загрузки.
  Теперь я расскажу, как решить эту задачу. Каждая текстура имеет свой файловый формат. Зная формат, мы знаем где в текстуре располагаются ячейки данных отвечающие за размер текстуры. Таким образом, даже не создавая объект Texture2D мы можем узнать размер текстуры, только по бинарным данным. Более того, большинство форматов текстур располагают данные о размере в начале файла. Таким образом достаточно загрузить только начальную часть файла текстуры, и мы сразу сможем узнать размер текстуры.
  Мы будем рассматривать формат PNG. Подробнее про формат можно прочесть здесь http://www.libpng.org/pub/png/spec/1.2/PNG-Structure.html. Текстуру в программу мы будем загружать через класс WWW в Unity3d. Начнем анализировать текстуру загрузив первые 30 байт. А до определения размера текстуры определим действительно ли это PNG файл. Для этого нужно всего лишь проверить сигнатуру файла. После проверки сигнатуры файла, мы будем считывать размер файла.

Теперь посмотрим на исходный код получения размера текстуры:

private void GetPNGTextureSize(byte[] bytes, out float width, out float height)
{
    width = -1.0f;
    height = -1.0f;

    // check only png tex!!! // http://www.libpng.org/pub/png/spec/1.2/PNG-Structure.html
    byte[] png_signature = { 137, 80, 78, 71, 13, 10, 26, 10 };
    
    const int cMinDownloadedBytes = 30;
    byte[] buf = bytes;
    if (buf.Length > cMinDownloadedBytes)
    {
        // now we can check png format
        for (int i = 0; i < png_signature.Length; i++)
        {
            if (buf[i] != png_signature[i])
            {
                Debug.LogWarning("Error! Texture os NOT png format!");
                return; // this is NOT png file!
            }
        }
        
        // now get width and height of texture
        width = buf[16] << 24 | buf[17] << 16 | buf[18] << 8 | buf[19];
        height = buf[20] << 24 | buf[21] << 16 | buf[22] << 8 | buf[23];
        
        Debug.Log("Loaded texture size: width = " + width + "; height = " + height);
        return;
    }
}

А также исходный код загрузки текстуры:

public Texture2D LoadSyncPNGTexture2D(string path)
{
    string resPath = cLocalPathPrefix + path;   
    Debug.Log("Try to load texture at: " + resPath);
    WWW www = new WWW(resPath);

    if (!string.IsNullOrEmpty(www.error))
    {
    Debug.LogWarning("Error! '" + www.error + "'");
    return null;
    }

    bool texChecked = false;
    while (!www.isDone)
    {
        if (!texChecked)
        {
            float texWidth = 0;
            float texHeight = 0;
            GetPNGTextureSize(www.bytes, out texWidth, out texHeight);

            if (texWidth < 0 || texHeight < 0)
            {
                continue;
            }

            texChecked = true;
        }
    }

    if (!string.IsNullOrEmpty(www.error))
    {
        Debug.LogWarning("Error! '" + www.error + "'");
        return null;
    }

    // get texture
    Texture2D tex = www.texture;
    return tex;

}

Если Вам нужно более детальное описание работы, пожалуйста, пишите в комментариях.

Демонстрацию работы можно скачать здесь:
https://dl.dropboxusercontent.com/u/20023505/Articles/Unity3d/Lesson7/Build/Build.zip


Исходные коды работы, как всегда бесплатно, можно скачать здесь:
https://dl.dropboxusercontent.com/u/20023505/Articles/Unity3d/Lesson7/Sources/Sources.zip
https://github.com/den-potapenko/Unity3dArticles/tree/master/Lesson7