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

Monday, April 22, 2013

Task #6: Loading MP3 audio via WWW class in Unity3d

Practical use of Unity3d engine.



Task #6: Loading MP3 audio via WWW class in Unity3d


  Let’s solve the task: Application should implement mp3 audio loading from file system via WWW class in Unity3d.
  Since Unity3d doesn’t support mp3 audio loading via WWW class for windows, we will load mp3 binary data via class WWW and pass binary data to NAudio library. NAudio library is free, and able to play mp3 audio from binary data (read more here: http://naudio.codeplex.com/). 
  At first, download NAudio library, and copy NAudio.dll and NAudio.WindowsMediaFormat.dll files to Assets folder. 
  Then, enable full .NET Framework version in Unity3d. To do this choose menu item «File/Build Settings/Player Settings/Other Settings/Optimization/Api Compatibility Level», and set «.NET 2.0» parameter. 
  On fig.1 you can see process of choosing full .NET Framework.
Figure 1. – Choose full .NET Framework implementation 


  Let’s start to write scripts. At first, check that NAudio library is already attached to project. To do this, expand «References» section in project and check existing of NAudio libraries. If they are not attached, attach them. «References» section is shown on fig. 2.
Figure 2. – «References» section 
Now, add using statements. 
using NAudio;
using NAudio.Wave; 
Create class variables.
private IWavePlayer mWaveOutDevice;
private WaveStream mMainOutputStream;
private WaveChannel32 mVolumeStream;
Create function that wills load audio from bytes array.
private bool LoadAudioFromData(byte[] data)
{
    try
    {
        MemoryStream tmpStr = new MemoryStream(data);
        mMainOutputStream = new Mp3FileReader(tmpStr);
        mVolumeStream = new WaveChannel32(mMainOutputStream);

        mWaveOutDevice = new WaveOut();
        mWaveOutDevice.Init(mVolumeStream);

        return true;
    }
    catch (System.Exception ex)
    {
        Debug.LogWarning("Error! " + ex.Message);
    }

    return false;
}
The algorithm of the function is:
  • Create instance of the «MemoryStream» class; 
  • Since we will load audio in mp3 format, create instance of the «Mp3FileReader» class; 
  • Create instance of the «WaveChannel32» class; 
  • At last, create instance of the «WaveOut» class to play audio; 

Create «LoadAudio» function. 
private void LoadAudio()
{
    System.Windows.Forms.OpenFileDialog ofd = new System.Windows.Forms.OpenFileDialog();
    ofd.Title = "Open audio file";
    ofd.Filter = "MP3 audio (*.mp3) | *.mp3";
    if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
    {
        WWW www = new WWW(cLocalPath + ofd.FileName);
        Debug.Log("path = " + cLocalPath + ofd.FileName);
        while (!www.isDone) { };
        if (!string.IsNullOrEmpty(www.error))
        {
            System.Windows.Forms.MessageBox.Show("Error! Cannot open file: " + ofd.FileName + "; " + www.error);
            return;
        }

        byte[] imageData = www.bytes;

        if (!LoadAudioFromData(imageData))
        {
            System.Windows.Forms.MessageBox.Show("Cannot open mp3 file!");
            return;
        }
        
        mWaveOutDevice.Play();

        Resources.UnloadUnusedAssets();
    }
}
The algorithm of the function is: 
  • Show «OpenFileDialog» to user. Set filters to mp3 files; 
  • Load mp3 binary data via WWW class;
  • Pass loaded data to «LoadAudioFromData» function. If function returns true – play audio; 

To unload audio from memory, create «UnloadAudio» function. 
private void UnloadAudio()
{
    if (mWaveOutDevice != null)
    {
        mWaveOutDevice.Stop();
    }
    if (mMainOutputStream != null)
    {
        // this one really closes the file and ACM conversion
        mVolumeStream.Close();
        mVolumeStream = null;

        // this one does the metering stream
        mMainOutputStream.Close();
        mMainOutputStream = null;
    }
    if (mWaveOutDevice != null)
    {
        mWaveOutDevice.Dispose();
        mWaveOutDevice = null;
    }
}
The algorithm of the function is:
  • Stop playing audio; 
  • Close «WaveChannel32» and «WaveStream»; 
  • Free memory for instance of the «IWavePlayer» class; 

  That's all. Also, there is a good example of loading mp3 audio by means of NAudio library, you can see here: http://naudio.codeplex.com/wikipage?title=MP3.
  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/Lesson6/Builds/Builds.zip 
  Free source codes you can download here: 
https://dl.dropboxusercontent.com/u/20023505/Articles/Unity3d/Lesson6/Sources/Source.zip
https://github.com/den-potapenko/Unity3dArticles/tree/master/Lesson6  

Sunday, April 21, 2013

Задача 6: Загрузка MP3 аудио через класс WWW

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


Задача 6: Загрузка MP3 аудио через класс WWW


  В этой статье рассмотрим следующую задачу: необходимо реализовать загрузку mp3 аудио через класс WWW в Unity3d. К сожалению Unity3d не поддерживает загрузку mp3 аудио через WWW для Windows. Для того чтоб реализовать загрузку mp3 аудио будем использовать бесплатную библиотеку NAudio (подробнее здесь: http://naudio.codeplex.com/).
  Первым делом скачаем библиотеку NAudio, и скопируем файлы NAudio.dll и NAudio.WindowsMediaFormat.dll в папку Assets. 
  После этого необходимо включить поддержку полной версии .NET Framework в Unity3d. Для этого нужно установить в «File/Build Settings/Player Settings/Other Settings/Optimization/Api Compatibility Level» параметр «.NET 2.0». На рис. 1 показан процесс установки полной версии .NET Framework.
Рисунок 1. – Установка полной версии .NET Framework 
  Теперь перейдем к скриптам. Во-первых убедимся что библиотека уже подключена к проекту. Для этого раскрываем «References» в проекте, и проверяем наличие библиотек NAudio. В случае если они не подключены – подключаем их. Структура «References» показана на рис. 2.

 
Рисунок 2. – Секция «References» 


Теперь подключим необходимые сборки в секции «using». 
using NAudio; 
using NAudio.Wave;
Теперь создадим переменные - член класса, с помощью которой мы будем проигрывать звук. 
private IWavePlayer mWaveOutDevice;
private WaveStream mMainOutputStream;
private WaveChannel32 mVolumeStream;
Создадим функцию, которая из массива байт загружает аудио.
private bool LoadAudioFromData(byte[] data)
{
    try
    {
        MemoryStream tmpStr = new MemoryStream(data);
        mMainOutputStream = new Mp3FileReader(tmpStr);
        mVolumeStream = new WaveChannel32(mMainOutputStream);

        mWaveOutDevice = new WaveOut();
        mWaveOutDevice.Init(mVolumeStream);

        return true;
    }
    catch (System.Exception ex)
    {
        Debug.LogWarning("Error! " + ex.Message);
    }

    return false;
}
Алгоритм функции следующий:
  • Создаем объект класса «MemoryStream»; 
  • Т.к. мы будем загружать звук в формате mp3, создадим объект класса «Mp3FileReader»; 
  • Из объекта класса «Mp3FileReader» создадим объект класса «WaveChannel32»; 
  • Наконец, для того чтоб получить возможность воспроизводить звук, создадим объект класса «WaveOut», и инициализируем его; 

Теперь создадим функцию для загрузки звука «LoadAudio». 
private void LoadAudio()
{
    System.Windows.Forms.OpenFileDialog ofd = new System.Windows.Forms.OpenFileDialog();
    ofd.Title = "Open audio file";
    ofd.Filter = "MP3 audio (*.mp3) | *.mp3";
    if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
    {
        WWW www = new WWW(cLocalPath + ofd.FileName);
        Debug.Log("path = " + cLocalPath + ofd.FileName);
        while (!www.isDone) { };
        if (!string.IsNullOrEmpty(www.error))
        {
            System.Windows.Forms.MessageBox.Show("Error! Cannot open file: " + ofd.FileName + "; " + www.error);
            return;
        }

        byte[] imageData = www.bytes;

        if (!LoadAudioFromData(imageData))
        {
            System.Windows.Forms.MessageBox.Show("Cannot open mp3 file!");
            return;
        }
        
        mWaveOutDevice.Play();

        Resources.UnloadUnusedAssets();
    }
}
Алгоритм функции следующий:
  • Показать пользователю «OpenFileDialog». Фильтры диалогу поставим такие, чтоб диалог показывал только mp3 файлы; 
  • С помощью объекта класса WWW загружаем данные mp3 файла; 
  • Передаём загруженные данные в функцию «LoadAudioFromData». В случае если функция не вернула никаких ошибок – проигрываем звук;

Для выгрузки звука из памяти, напишем функцию «UnloadAudio». 
private void UnloadAudio()
{
    if (mWaveOutDevice != null)
    {
        mWaveOutDevice.Stop();
    }
    if (mMainOutputStream != null)
    {
        // this one really closes the file and ACM conversion
        mVolumeStream.Close();
        mVolumeStream = null;

        // this one does the metering stream
        mMainOutputStream.Close();
        mMainOutputStream = null;
    }
    if (mWaveOutDevice != null)
    {
        mWaveOutDevice.Dispose();
        mWaveOutDevice = null;
    }
}
Алгоритм выгрузки звука очень простой:
  • Останавливаем проигрывание звука; 
  • Закрываем «WaveChannel32» и «WaveStream»; 
  • Освобождаем память для объекта класса «IWavePlayer»; 

 Вот и все. Также, для библиотеки NAudio, есть хороший пример для проигрывания mp3 звука, который можно посмотреть здесь: http://naudio.codeplex.com/wikipage?title=MP3

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


Демонстрацию работы можно скачать здесь: 

Исходные коды работы, как всегда бесплатно, можно скачать здесь: 

Sunday, April 7, 2013

Task #5: Loading DDS, BMP, TGA and other textures via WWW class in Unity3d

Practical use of Unity3d engine.



Task #5: Loading DDS, BMP, TGA and other textures via WWW class in Unity3d

  
  This article describes the task: Load images via WWW class. Current version of Unity3d allows you to download images via WWW class only in PNG or JPG format (read more here: http://docs.unity3d.com/Documentation/ScriptReference/WWW-texture.html). But we should implement image loading in such formats as DDS, BMP, GIF, PSD, TGA etc. 


  Demo of application is shown on fig. 1.
Figure 1. – Demo of application
  
  All these image formats are supported by DevIL library (read more here: http://openil.sourceforge.net/features.php).
  There is no need for full application description, because DevIL library is well documented and has a lot of examples. I will show only the short description of implementation:
  • Load texture using WWW class;
  • Get bytes data using «bytes» field of WWW class;
  • Create DevIL texture from this data;
  • Get Color32[] array from DevIL texture;
  • Create Texture2D object based on this color array;

  To illustrate implementation I created a demo application. I want to notice that this application loads also MipMaps data from the texture, and creates proper Texture2D. 
  If you need more detailed description, or implement image loading not supported by unity3d, please write it in comments.


  Application demo you can download here:
  Source code you can download from here:

Задача 5: Загрузка DDS, BMP, TGA и других текстур через класс WWW

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



Задача 5: Загрузка DDS, BMP, TGA и других текстур через класс WWW

  В этой статье рассмотрим следующую задачу: необходимо реализовать загрузку изображение через класс WWW в Unity3d. Т.к. Unity3d поддерживает загрузку изображений через WWW только форматов PNG и JPG (подробнее здесь: http://docs.unity3d.com/Documentation/ScriptReference/WWW-texture.html), то необходимо также реализовать загрузку DDS, BMP, GIF, PSD, TGA etc.


Демонстрация работы программы показана на рис. 1.
Рисунок 1. – Демонстрация работы приложения


  Все необходимые форматы изображений поддерживаются библиотекой DevIL (подробнее здесь: http://openil.sourceforge.net/features.php).
  Т.к. библиотека DevIL имеет отличное описание и множество примеров, нет надобности подробно описывать алгоритм загрузки изображений. Я лишь приведу краткое описание алгоритма работы программы:
  • Загрузить текстуру через класс WWW;
  • Данные получить через поле «bytes» у класса WWW;
  • Из этих данных создать DevIL текстуру;
  • Из DevIL текстуры получить массив цветов Color32[];
  • На основе массива цветов создать Texture2D;

  Для демонстрации реализации загрузки изображений я создал тестовое приложение. Хочу отметить, что это приложение также подгружает все доступные MipMap'ы с текстуры.
  Если Вам нужно более детальное описание работы, или реализовать загрузку изображений не поддерживаемых Unity3d, пожалуйста, пишите в комментариях.

  Демонстрацию работы можно скачать здесь:

Saturday, April 6, 2013

Task #4: Data transfer between applications in Unity3d


Practical use of Unity3d engine.




Task #4: Data transfer between applications in Unity3d


Contents

  • Task #4: Data transfer between applications in Unity3d. 
  • Scene setup 
  • Data transfer between applications 
  • First application implementation (А) 
  • Second application implementation (B) 
  • Conclusion


Task #4: Data transfer between applications in Unity3d.

  This article describes the task: implement data transfer between two Unity3d applications.
  To implement data transfer we need to create two scenes in Unity3d. Each scene will represent separate application: A – sends data, B – receives data.

Scene setup

  Create empty project. Create two folders: «Scenes» - for storing scenes and «Scripts» - for storing scripts. Create the first scene by pressing «File/Save Scene». Save file dialog will be opened. Choose «Scenes» folder and set «AppFirstScene» for the first scene file name. Now, choose menu item «File/Save Scene as» and set the scene file name as «AppSecondScene». The process of saving scene is shown on fig. 1.
Figure 1. – The process of saving scene

  Now set scene as active by double click on «AppFirstScene» scene. The camera is already presented in the scene. Let’s create an entry point for our scripts. Choose menu item «GameObject / Create Empty», and rename created GameObject to «AppRoot».
  Similarly, choose «AppSecondScene» scene and create an entry point.

Result of created objects you can see at fig. 2.

Figure 2. – GameObjects in «AppFirstScene» scene

  Let’s start creating scripts. For each scene we will create scripts: «AppRootFirst» и «AppRootSecond». To do this press right mouse button on “Scripts” folder and choose «Create/C# script». Also, create Constants class. Created file system is shown on fig. 3.
Figure 3. – File system state

  Now assign script «AppRootFirst» to «AppRoot» GameObject in «AppFirstScene» scene. To do this move script «AppRootFirst» to «AppRoot» by pressing left mouse button. So, now script «AppRootFirst» will be entry point in first scene «AppFirst». The process of assigning script «AppRootFirst» to «AppRoot» GameObject is shown on fig. 4.
Figure 4. – Assign script to GameObject

  We need to assign «NetworkView» component to work with network. To do this you should choose «AppRoot» and choose menu item «Component/Miscellaneous/Network View». Now, we can see properties of «AppRoot» in «Inspector» window by pressing left mouse button on «AppRoot» GameObject. Inspector window is shown on fig. 5.
Figure 5. – «AppRoot» object state

  And we should do the same thins in «AppSecondScene» scene. I will not repeat all actions, but here is the short list of main actions:
  • Choose «AppSecondScene» scene by double click;
  • Move «AppRootSecond» script to «AppRoot» object by left mouse button;
  • Select «AppRoot» object and choose menu item «Component/Miscellaneous/Network View»;
  • That’s all. Now all scenes are ready. Let’s start writing scripts.


Data transfer between applications

  At first, I’ll tell some theory. There are a lot of ways to implement data transfer between applications, but in this article I will use one of the Unity3d native ways.
  I will use «RPC» technology (read more here: http://en.wikipedia.org/wiki/Remote_procedure_call). The main idea is to call function in another application with required parameters. Unity3d already has implementation of RPC procedures (read more here: http://docs.unity3d.com/Documentation/Components/net-RPCDetails.html).
  There are some peculiarities in this technology. I’ll tell about them in this article.

First application implementation (А)

  Application will contain only two buttons and one text field. By pressing the first button application will send text message to the second one. By pressing the second button, application “A” will send data to application “B”. The main part of application is stored in «AppRootFirst» class. Take a look to its implementation.
Create three variables that will contain buttons and text field positions.
private readonly Rect cSendHelloRect = new Rect(20, 200, 200, 200); 
private readonly Rect cSendDataRect = new Rect(240, 200, 200, 200);
private readonly Rect cDebugMsgRect = new Rect(20, 420, 200, 100);
  Also create variables that will store debug message and passed data.
private string mSendMessage = "No messages";
private byte[] mDataToSend = new byte[] { 0x0b, 0x1f, 0x3c };
  In «OnGUI» function output two buttons and one text field:
public void OnGUI()
{
    …
    if (GUI.Button(cSendHelloRect, "Hello"))
    {
    …
    }

    if (GUI.Button(cSendDataRect, "Send data"))
    {
    …
    }
    GUI.Label(cDebugMsgRect, mSendMessage);
}
  Now, implement sending message and data by means of RPC functions. To pass messages by means of RPC functions one of the applications should start a server and the other one should be connected to a server as client. As far as the first application is the server, create «InitNet» function to initialize it. This function is very simple, but in future we can add an additional code, for server initialization. Unity3d has static function «Network.InitializeServer» to run a server. The first argument is number of connections. In this application we will set this value to 3. The second argument is the connection port. Variable that store connection port is located in Constants class.
private void InitNet()
{
    Network.InitializeServer(3, Constants.cServerPort, false);
}
Call «InitNet» function in «Start» function.
public void Start()
{
    InitNet();
}
  To run RPC function we need to call «NetworkView.RPC» function. This component we already added to «AppRoot» object during scene setup. One of the RPC peculiarities is that each RPC function should have «[RPC]» attribute. Also I should mention that this function should be implemented both in class that calls function and class in which function should be called. This is very important, otherwise RPC procedures will not work.
  Sending messages also has its own peculiarities. If send messages on each frame, sending messages interrupts. So, some delay should be implemented before next message send.
  To implement delay for messages create variables, that will store current delay time and maximum delay time. 
// timer to create some delay for sending messages
private float mWaitTimeUpdateLaserTracker = 0.0f;
private const float cMaxWaitTimeUpdateLaserTracker = 0.1f;
And now «OnGUI» function will have such view.
// increment wait time 
if (mWaitTimeUpdate < cMaxWaitTimeUpdate)
{
    mWaitTimeUpdate += Time.deltaTime;
}

if (GUI.Button(cSendHelloRect, "Hello"))
{
    if (mWaitTimeUpdate > cMaxWaitTimeUpdate)
    {
    
    //
    this.networkView.RPC(Constants.cRPCSendMessage, Constants.cSendMessagesMode, "Hello");
    
    //
    mWaitTimeUpdate = 0.0f;

    //
    mSendMessage = "'Hello' message sent";
    }
}
if (GUI.Button(cSendDataRect, "Send data"))
{
    if (mWaitTimeUpdate > cMaxWaitTimeUpdate)
    {
        //
        this.networkView.RPC(Constants.cRPCSendData, Constants.cSendMessagesMode, mDataToSend);
        //
        mWaitTimeUpdate = 0.0f;
        //
        mSendMessage = "Data sent";
    }
}
GUI.Label(cDebugMsgRect, mSendMessage);
  I want to notice that it’s very comfortable to store names of RPC functions in Constants class, because RPC function names may be used in several classes.
  In this class we will create empty RPC functions.
[RPC]
public void RPCSendMessage(string msg)
{

}

[RPC]
public void RPCSendData(byte[] data)
{

}
  This is the end of development of the first application (A). To build it, press menu item «File/Build Settings». In the settings, in «ProductName» field set «AppFirst», and add «AppFirstScene» scene to scenes list. Press «Build» button.
  Now, take a look to second application (B) implementation.

Second application implementation (B)

  
  Client implementation is also very simple.
  Create variable that will store received message.
// received message
private string mReceiveMessage = "No messages";
  Create variable that will store text field position.
// rect for displaying of received message 
private readonly Rect cMsgRect = new Rect(20, 420, 200, 100);
  And now «OnGUI» function will have such implementation.
public void OnGUI()
{
    GUI.Label(cMsgRect, mReceiveMessage);
}
  To implement connection with server Unity3d has static function «Network.Connect». As server is located on local computer, server IP is «127.0.0.1». Connection port is the same for server and for client.
//
Network.Connect(Constants.cServerIp, Constants.cServerPort);
  Now, create RPC functions implementation.
[RPC]
public void RPCSendMessage(string msg)
{
mReceiveMessage = "Message received = " + msg;
}

[RPC]
public void RPCSendData(byte[] data)
{
    mReceiveMessage = "Data received. Data length = " + data.Length;
}
  That’s all. To build the second application, choose menu item «File/Build Settings». In the setting, in «ProductName» field set «AppFirst», and add scene «AppSecondScene» to scenes list. And press «Build» button.

Conclusion

  As you can see, data transfer implementation is very simple. You should to know a few main rules and you can start.

Results of development you can download here:
https://dl.dropbox.com/u/20023505/Articles/Unity3d/Lesson4/Builds/Builds.zip

As always for free you can download source codes from here:
https://dl.dropbox.com/u/20023505/Articles/Unity3d/Lesson4/Sources/Sources.zip
https://github.com/den-potapenko/Unity3dArticles/tree/master/Lesson4

Задача 4: Обмен данными между приложениями в Unity3d

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




Задача 4: Обмен данными между приложениями в Unity3d



Cодержание

  • Реализация первого приложения (А)
  • Реализация второго приложения (Б)
  • Замечания



Задача 4: Обмен данными между приложениями в Unity3d.

 В этой статье рассмотрим следующую задачу: необходимо реализовать передачу данных или сообщений между двумя приложениями, разработанными в Unity3d. Для демонстрации передачи сообщений между двумя приложениями необходимо будет создать две сцены. Каждая сцена будет в результате представлять собой отдельное приложение: А – посылает данные и Б – принимает данные. 

Настройка сцен 

Создадим пустой проект. Сразу создадим в этом проекте 2 папки: «Scenes» -для хранения сцен и «Scripts» - для хранения скриптов. Создадим первую сцену, нажав «File/Save Scene». Откроется диалог для сохранения сцены на диске. Выберем каталог «Scenes» и сохраним сцену под названием «AppFirstScene». Теперь нажмем на пункт меню «File/Save Scene as» и сохраним сцену под названием «AppSecondScene». Процесс сохранения сцены в файловой системе, показан на рис. 1.

Рисунок 1. – Сохранение сцены 

  Теперь нажмем двойным щелчком на сцену «AppFirstScene», для того чтоб ее выбрать ее как активную. В сцене у нас уже есть камера. Создадим входную точку для работы скриптов. Для этого выберем пункт меню «GameObject / Create Empty», и переименуем созданный объект в «AppRoot».

  Аналогично выберем сцену «AppSecondScene» и создадим точно такую же входную точку.

В результате в окне Hierarchy для обеих сцен можно будет увидеть объекты, показанные на рис. 2.
Рисунок 2. – Объекты в сцене «AppFirstScene»

  Приступим к созданию скриптов. Для каждой из сцен соответственно создадим два скрипта: «AppRootFirst» и «AppRootSecond». Для этого нажмем левую клавишу мыши на папке Scripts и выберем «Create/C# script». После этого создадим класс констант «Constants». И так, теперь файловая система будет иметь вид, показанный на рис. 3.

Рисунок 3. – Состояние файловой системы проекта

Теперь в сцене «AppFirstScene» переместим с помощью левой клавиши мыши скрипт «AppRootFirst» на объект «AppRoot». Теперь скрипт «AppRootFirst» будет нашей входной точкой в первой сцене «AppFirst». На рис. 4 показан процесс назначения скрипта «AppRootFirst» на объект «AppRoot».
Рисунок 4. – Назначение скрипта для входной точки приложения

  Также для работы с сетью назначим на объект «AppRoot» компонент «NetworkView». Для этого необходимо выбрать объект «AppRoot» и выбрать пункт меню «Component/Miscellaneous/Network View». Теперь если выбрать «AppRoot» и посмотреть в окне «Inspector» его свойства, можно увидеть состояние, показанное на рис. 5.

Рисунок 5. – Состояние объекта «AppRoot»

Теперь аналогичные действия нам нужно проделать в сцене «AppSecondScene». Заново повторять все действия не буду, но приведу краткий список действий:
  • Выбираем двойным щелчком сцену «AppSecondScene»;
  • Перемещаем скрипт «AppRootSecond» на объект «AppRoot» с помощью левой клавиши мыши;
  • Выделяем объект «AppRoot» и выбираем пункт меню «Component/Miscellaneous/Network View»;
  • Вот и всё, сцены настроены. Переходим к написанию скриптов.

Передача данных между приложениями

  И так, начнем с теории. Существует множество способов передавать данные между приложениями, но в данной статье я буду использовать один из нативных способов для Unity3d способ передачи данных между приложениями.
  Я буду использовать технологию под названием «RPC» или «Удалённый вызов процедур» (подробнее здесь: http://ru.wikipedia.org/wiki/Удалённый_вызов_процедур). Идея в том, чтобы вызвать в другом приложении процедуру с необходимыми нам параметрами. В движке Unity3d есть реализация RPC процедур (подробнее здесь: http://docs.unity3d.com/Documentation/Components/net-RPCDetails.html).
  При работе с этой технологией есть несколько особенностей. О них я также расскажу в этой статье.

Реализация первого приложения (А)

  Приложение будет иметь всего две кнопки и одно текстовое поле. При нажатии на первую кнопку, приложение посылает текстовое сообщение второму. При нажатии на вторую кнопку, приложение А посылает данные приложению Б. Основная часть приложения А расположена в классе «AppRootFirst». Рассмотрим его реализацию.
  Создадим три переменные, которые будут хранить позицию кнопок и текстового поля.
private readonly Rect cSendHelloRect = new Rect(20, 200, 200, 200); 
private readonly Rect cSendDataRect = new Rect(240, 200, 200, 200);
private readonly Rect cDebugMsgRect = new Rect(20, 420, 200, 100);
  Также создадим переменные, которые будут хранить отладочное сообщение и передаваемые данные соответственно.
private string mSendMessage = "No messages";
private byte[] mDataToSend = new byte[] { 0x0b, 0x1f, 0x3c };
  В функции «OnGUI» выведем две кнопки и одно текстовое поле:
public void OnGUI()
{
    …
    if (GUI.Button(cSendHelloRect, "Hello"))
    {
    …
    }

    if (GUI.Button(cSendDataRect, "Send data"))
    {
    …
    }
    GUI.Label(cDebugMsgRect, mSendMessage);
}
  Теперь реализуем отправку сообщений и данных через RPC функции. Для того чтоб передавать сообщения через RPC функции, одно из приложений должно выполнять роль сервера, а остальные приложения будут выполнять роль клиентов. Т.к. первое приложение будет реализовать функциональность сервера, создадим метод «InitNet» для инициализации сервера. Функция будет очень маленькая, но впоследствии, туда можно будет добавить дополнительный функционал, который необходим при инициализации сервера. Для инициализации сервера в Unity3d есть статический метод «Network.InitializeServer». Первый параметр – это количество соединений. В этом приложении мы поставим это значение равным «3». Вторым параметром функции является порт, по которому будут передаваться сообщения. Порт я вынес в класс констант.
private void InitNet()
{ Network.InitializeServer(3, Constants.cServerPort, false); }
  Метод «InitNet» вызовем в функции «Start».
public void Start()
{
    InitNet();
}
  Для того чтоб вызывать RPC функции, нужно использовать метод «RPC» у компонента «NetworkView», который мы добавили к объекту «AppRoot» при настройке сцены. Одной из особенностей RPC процедур является то что каждая RPC функция должна быть с атрибутом «[RPC]», причём этот метод должен быть И в классе который вызывает RPC процедуру И в классе в котором должна быть вызвана RPC процедура. Это очень важно, иначе вызов RPC процедур не будет работать.
  Отправка сообщений тоже имеет свои особенности. Если отправлять сообщение на каждом кадре, посылка сообщений прерывается. Поэтому необходимо поставить некоторую задержку перед отправкой сообщения повторно.
  Для реализации задержки передачи сообщений сразу создадим переменные, которые в себе будут хранить текущее время задержки, и максимальное время задержки.
// timer to create some delay for sending messages 
private float mWaitTimeUpdateLaserTracker = 0.0f;
private const float cMaxWaitTimeUpdateLaserTracker = 0.1f;
  И теперь метод «OnGUI» будет выглядеть следующим образом.
// increment wait time
if (mWaitTimeUpdate < cMaxWaitTimeUpdate)
{
    mWaitTimeUpdate += Time.deltaTime;
}
if (GUI.Button(cSendHelloRect, "Hello"))
{
    if (mWaitTimeUpdate > cMaxWaitTimeUpdate)
    {

        //
        this.networkView.RPC(Constants.cRPCSendMessage, Constants.cSendMessagesMode, "Hello");
        //
        mWaitTimeUpdate = 0.0f;

        //
        mSendMessage = "'Hello' message sent";
    } 
}

if (GUI.Button(cSendDataRect, "Send data")) 
{
    if (mWaitTimeUpdate > cMaxWaitTimeUpdate)
    {
        //
        this.networkView.RPC(Constants.cRPCSendData, Constants.cSendMessagesMode, mDataToSend);

        //
        mWaitTimeUpdate = 0.0f;

        //
        mSendMessage = "Data sent";
    }
}

GUI.Label(cDebugMsgRect, mSendMessage); 
  Хочу отметить, что названия RPC функций очень удобно хранить в классе констант, т.к. названия RPC функций могут использоваться в нескольких классах.
  Также в этом классе создадим пустые RPC функции.
[RPC]
public void RPCSendMessage(string msg)
{

}

[RPC]
public void RPCSendData(byte[] data)
{

}
  На этом разработка первого приложения закончена. Для того чтоб его собрать, необходимо выбрать пункт меню «File/Build Settings». В настройках, в поле «ProductName» указать «AppFirst», а в список сцен добавить сцену «AppFirstScene». И нажимаем кнопку «Build».
  Теперь рассмотрим реализацию второго приложения (Б)


Реализация второго приложения (Б)

  Реализация клиента тоже очень проста.
  Создадим переменную, которая будет хранить сообщение, которое прислали.
// received message 
private string mReceiveMessage = "No messages";
  Создадим переменную, которая будет хранить позицию текстового поля для сообщения.
// rect for displaying of received message 
private readonly Rect cMsgRect = new Rect(20, 420, 200, 100);
  Соответственно метод «OnGUI» будет выглядеть следующим образом.
public void OnGUI()
{
    GUI.Label(cMsgRect, mReceiveMessage);
}
  Соединяемся с сервером. Для этого в Unity3d есть статический метод «Network.Connect». Т.к. сервер находится на локальном компьютере то IP адрес сервера «127.0.0.1». Порт для соединения используем тот же что и север. Переменная, которая хранит IP адрес, также находится в константах.
//
Network.Connect(Constants.cServerIp, Constants.cServerPort); 
  Осталось только написать реализацию RPC процедур.
[RPC]
public void RPCSendMessage(string msg)
{
    mReceiveMessage = "Message received = " + msg;
}

[RPC] 
public void RPCSendData(byte[] data)
{
    mReceiveMessage = "Data received. Data length = " + data.Length;
}
  Вот и все. Для того чтоб собрать второе приложение, необходимо выбрать пункт меню «File/Build Settings». В настройках, в поле «ProductName» указать «AppFirst», а в список сцен добавить сцену «AppSecondScene». И нажимаем кнопку «Build».

Заключение

  Как видите, реализация передачи данных между приложениями очень проста. Необходимо всего лишь знать несколько основных принципов и можно начинать работу.

Демонстрацию работы можно скачать здесь:

Исходные коды Вы как всегда сможете бесплатно скачать здесь:

Saturday, March 16, 2013

Task #3: Object selection and highlight in Unity3d

Practical use of Unity3d engine.



Task #3: Objects selection and highlight in Unity3d

Contents

  • Task #3: Objects selection and highlight in Unity3d.
  • 1. Object selection
  • 2. Object highlight
  • Remarks


Task #3: Objects selection and highlight in Unity3d.

  This article will show solution for the task: application should implement object selection by left mouse button down. Also application should highlight selected object. This article is base on the second one (http://denis-potapenko.blogspot.com/2013/03/task-2-rendering-hotspots-in-unity3d.html). So, I suggest reading the second article first. Figure B shows result we want to achieve:
Figure В. – Result scene

Object selection

  At first, create class variable, which will store the selected object.
// selected GameObject
private GameObject mSelectedObject;

  Also, let’s create property for this object.
/// 
/// Gets or sets selected GameObject
/// 
public GameObject SelectedObject
{
    get
    {
        return mSelectedObject;
    }
    set
    {
        ...

        // assign new game object
        mSelectedObject = value;

        ...
    }
}
  Full implementation of this property will be described below.
  And now, create method, which implementation is object selection. We need to determine what object is located under the mouse. Object selection pipeline is:

  • Convert mouse position from screen space to three-dimensional space (A);
  • Build ray, directed from mouse position to “camera forward” way (B);
  • Find intersection of ray and any object in the scene;
  • Find the first object that occurred on intersection; 

  Convert mouse position to ray that spreads to “camera forward” (Combine A and B) using «ScreenPointToRay» method.
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
  Now, let’s find ray intersection with any object in scene. To implement this call function «Physics.Raycast».

RaycastHit hit;
if (Physics.Raycast(ray, out hit, Constants.cMaxRayCastDistance))

  I should notice, that each object we want to get intersection should have «Collider» component. Colliders mainly used in physics, particularly to determine object intersection. To assign «Collider» to GameObject you need to select the object in the scene and choose menu item «Component / Physics / Box Collider». In future articles we will have more detailed description of this component.
  So, if intersection exist, function «Physics.Raycast» returns «true» and sets «hit» variable. Now, let’s determine intersected GameObject. 
// get game object
GameObject rayCastedGO = hit.collider.gameObject;

  And finally, set this object as selected.
// select object
this.SelectedObject = rayCastedGO;
  And now, we need to call function “SelectObjectByMousePos” in «Update» one. But we will call it only when left mouse button is pressed.
// process object selection
if (Input.GetMouseButtonDown(0))
{
    SelectObjectByMousePos();
}
  And now, let’s take a look to «SelectedObject» function implementation.

Object highlight

  At first, I’ll tell a bit of theory. Object highlight we can do by a lot of ways. I will describe a way that we can achieve without any add-ons to Unity3d engine. But after that, I will describe you a better way of highlight implementation, but it will require doing some special actions. 
  So highlight implementation is divided to such parts:
  • Create two materials: the first one is simple material, and the second material is used for highlight;
  • Pass these materials to script;
  • When object is selected set its material to highlighted;
  • To object that was selected before, set simple material;
  Now, let’s take a look to implementation. In Unity3d editor create folder with materials and name it «Materials». In this folder create two materials: simple “SimpleMat” and highlighted “HighlightedMat”. Result folder tree is shown on figure 2.1.

Figure 2.1 – Files and folders structure with created materials

  I want to notice that “Diffuse” shader is set to both materials. Materials are configured identically, except that the material «HighlightedMat» has diffuse color set to red. Material settings of «HighlightedMat» are shown on figure 2.2.
Figure 2.2 – Settings of «HighlightedMat» material

  Now, create two public class variables in “AppRoot” class, which will contain materials data.
// materials for highlight
public Material SimpleMat;
public Material HighlightedMat;
  Set value to this variables. We can do it directly in the editor. Process of assigning values to materials is shown on figure 2.3.
Figure 2.3 – Process of assigning materials to the script "AppRoot" 


  So, finally let’s have a look to implementation of the setter of the property “SelectedObject”. 
Get «old» selected object.

// get old game object
GameObject goOld = mSelectedObject;
  Set new selected object.
// assign new game object
mSelectedObject = value;
  If selected object is not changed, just leave this function.
// if this object is the same - just not process this
if (goOld == mSelectedObject)
{
    return;
}
  Set material to old selected object.
// set material to non-selected object
if (goOld != null)
{
    goOld.renderer.material = SimpleMat;
}
  Set material to selected object.
// set material to selected object
if (mSelectedObject != null)
{
    mSelectedObject.renderer.material = HighlightedMat;
}
  As a result, we have got a scene, in which user by means of left mouse button can select objects and that object will be highlighted to red color. 

Remarks

  As I mentioned, I don’t think that described above way is the best way to highlight the object. I consider that the more efficient and flexible way to highlight the object is to use shader with «Emissive» component. This component is multiplied to result color by special way, so the object gets only “coloring” of red color but not the full red color. You can do such shader yourself, and in further articles I will tell you how to do it. From existing set of Unity3d shaders I found one that can fit to highlight purpose in some situations. It’s named «Reflective/Diffuse». You can use this shader and its field “Reflection Color” instead of “Emissive”.


Results of development you can see here:
https://dl.dropbox.com/u/20023505/Articles/Unity3d/Lesson3/WebPlayer/WebPlayer.html

As always for free you can download source codes from here:
https://dl.dropbox.com/u/20023505/Articles/Unity3d/Lesson3/Sources/Sources.zip
https://github.com/den-potapenko/Unity3dArticles/tree/master/Lesson3