Analytics Manager: Haxe & OpenFL

This week I want to talk about a very useful and simple tool I made for collecting gameplay information and basic (anonymous) information from devices where players play. You can fork and use the extension from github. I haven’t tested this with the new version of OpenFL and haxe but it should work, if for any reason doesn’t work please let me know.

How to Use it: Client Side

First of all you have to instantiate the AnalyticsManager class:

actionscript3

  1. var analyticsManager : AnalyticsManager;
  2. var analyticsDB : String = "DataBase"; //You should write the name of the database you created to store the data
  3. analyticsManager = AnalyticsManager.InitInstance(analyticsDB);

There is a class called AnalyticsData, you just create a new class and make it extend from this class.

actionscript3

  1. class AnaTest extends AnalyticsData
  2. {
  3.    public function new()
  4.    {
  5.       super();
  6.       //These are just test values, you can use parameters here
  7.       this.AddValue("par1", "val1");
  8.       this.AddValue("par2", 65);
  9.       this.AddValue("par3", 42.028);
  10.    }
  11. }

The class above is just a sample, you can add parameters to the constructor and pass different values to it depending on the case.

After you create a custom class to pass store whatevergameplay data you want to store, you need to call the AnalyticsManager and create a new instance with the data you want to send.

actionscript3

  1. var url : String = "URLToMyServer"; //You have to create a script on the server side
  2. var table : String = "TableName"
  3. AnalyticsManager.SendDataToServer(url,table,new AnaTest());

The data is automatically converted to JSON so you can just parse that on the server side and store it. This default behavior can be changed as well but I have to implement it.

How to Use it: Server Side

I add a sample of a very simple script in PHP to take the data sent fromthe server and do something with it. Of course you can write your own script usign whatever language you prefer.

  1. $table = $_POST["table"];
  2. //Get the data and decode from JSON
  3. $json = urldecode($_POST["data"]);
  4. $array = json_decode($json);
  5.  
  6. foreach($array as $key => $val)
  7. //Do whatever you want with the attributes
  8.  
  9. echo "success"; //this tells the client everything is ok

Please notice that this probably needs some security, just to protect your data from bad people, you know. Be careful with the way you send the data.

With these few steps you should be able to use the extension and store some interesting data from gameplay and then improve your game.

How is the Extension Structured

There are three different base classes, each one with a clear purpose for this extension: AnalyticsData, AnalyticsLoader and AnalyticsManager.

AnalyticsData

This is an abstraction of the data you want to send to the database. Basically this class stores pairs of values: (Key,Value) to be passed to the server and then eventually stored in a table which contains columns of the same names of those keys.

actionscript3

  1. class AnalyticsData
  2. {
  3.    private var values : Map<String,Dynamic>;
  4.    public function new()
  5.    {
  6.       values = new Map<String,Dynamic>();
  7.    }
  8.  
  9.    private function AddValue(key : String, value : Dynamic) : Void
  10.    {
  11.       values.set(key, value);
  12.    }
  13.  
  14.    public function ToString() : String
  15.    {
  16.       var data : String;
  17.       data = "";
  18.       for (k in values.keys())
  19.       {
  20.          data += k + "=" + values.get(k);
  21.          data += "&";
  22.       }
  23.  
  24.       return data.substring(0, data.length - 1);
  25.    }
  26.  
  27.    public function ToJSON() : String
  28.    {
  29.       return Json.stringify(values);
  30.    }
  31. }

Use the AddValue function to include more pairs (key,value) to the instance of this class and before sending it to the server, use ToJSON or ToString to be sent as parameter in a long string.

AnalyticsLoader

This class has common event functions such as onComplete and onIOError, in order to send data to the server, a loader is needed. Once the data is successfully sent or an error occurs, this class manages that response.

actionscript3

  1. class AnalyticsLoader extends URLLoader
  2. {
  3.    private var onComplete : Dynamic -> Void;
  4.    private var onIOError : Dynamic -> Void;
  5.  
  6.    public function new(?request:URLRequest,?onComplete : Dynamic -> Void,onIOError : Dynamic -> Void)
  7.    {
  8.       super(request);
  9.       this.onComplete = onComplete;
  10.       this.onIOError = onIOError;
  11.       if(onComplete != null)
  12.          addEventListener(Event.COMPLETE, onComplete);
  13.  
  14.       if(onIOError != null)
  15.          addEventListener(IOErrorEvent.IO_ERROR, onIOError);
  16.    }
  17.  
  18.    public function Clean() : Void
  19.    {
  20.       if (onComplete != null)
  21.       {
  22.          if (hasEventListener(Event.COMPLETE))
  23.             removeEventListener(Event.COMPLETE,onComplete);
  24.       }
  25.  
  26.       if (onIOError != null)
  27.       {
  28.          if (hasEventListener(IOErrorEvent.IO_ERROR))
  29.          removeEventListener(IOErrorEvent.IO_ERROR,onIOError);
  30.       }
  31.    }
  32. }

Calling the Clean function will remove all the handlers from this class.

AnalyticsManager

This is the core class of the extension, handling calls and managing the data internally without the hassle of doing the same process again and again. This class is a singleton, that’s why can only be instanciated from the available methods: first InitInstance and GetInstance if you need to use it.

actionscript3

  1. class AnalyticsManager
  2. {
  3.    /*
  4.    * Analytics manager instance.
  5.    */
  6.    private static var instance : AnalyticsManager;
  7.    private static var loaders : Array;
  8.    private static var database : String;
  9.    public static function InitInstance(database : String = ""): AnalyticsManager
  10.    {
  11.       if (instance == null)
  12.          instance = new AnalyticsManager(database);
  13.  
  14.       return instance;
  15.    }
  16.  
  17.    /*
  18.    * Creates and returns a analyrics manager instance if it's not created yet.
  19.    * Returns the current instance of this class if it already exists.
  20.    */
  21.    public static function GetInstance(): AnalyticsManager
  22.    {
  23.       if ( instance == null )
  24.          throw "The Analytics Manager is not initialized. Use function 'InitInstance'";
  25.  
  26.       return instance;
  27.    }
  28.  
  29.    /*
  30.    * Constructor
  31.    */
  32.    private function new(db : String = "")
  33.    {
  34.       loaders = new Array();
  35.       database = db;
  36.    }
  37.  
  38.    public static function SendDataToServer(url : String,table : String, anaData : AnalyticsData,onComplete : Dynamic -> Void =
  39.    null,onIOError : Dynamic -> Void = null, db : String = "")
  40.    {
  41.       var request : URLRequest;
  42.       var loader : AnalyticsLoader;
  43.       var variables : URLVariables;
  44.       var databaseName : String;
  45.  
  46.       databaseName = db != "" ? db : database;
  47.       request = new URLRequest(url);
  48.       //I also decided to do every request through POST method, if needed could be change in the future
  49.       request.method = URLRequestMethod.POST;
  50.       //I decided to use only JSON, we could change this in the future
  51.       request.data = "database=" + databaseName + "&table=" + table + "&data=" + anaData.ToJSON();
  52.       trace(request.data);
  53.       loader = new AnalyticsLoader(request,onComplete,onIOError);
  54.  
  55.       loaders.push(loader);
  56.    }
  57.  
  58.    public static function Clean() : Void
  59.    {
  60.       for (l in loaders)
  61.         l.Clean();
  62.    }
  63. }

An array of all the loaders is kept here for easy cleaning whenever the programmers needs to do it. This class also stores the name of the database (in the server side), currently this extension only handles one database per game.

The SendDataToServer function creates a loader and with it sends the data to the server and let the loader handle whatever response comes from it.

Wrapping Up

In addition to the three base classes I explained here, there is a BasicData class which contains general information you might be interested in storing from the player’s device. This information is anonymous so the privacy of that user is protected in any case.

This was the post for today, a very simple way to collect and store gameplay information to improve your creations. If you have questions or comments, just let me know.

 

Analytics Manager: Haxe & OpenFL

Tile Editor for Unity: Part II

As I mentioned before, for those who only want to use the editor, as soon as the Asset is available in the Unity Asset Store, you will be able to download it for free and use it.

This is the second part of a series of posts about a small tile editor I’m creating for Unity. If you haven’t read the first part yet, you can check it out here.

The whole explanation is divided in the following parts:

  1. Editor Window Creation
  2. Saving Status
  3. Menus
  4. Game Objects Actions: Create, Delete, Edit
  5. Layers
  6. Grid
  7. Snapping
  8. Transformations
  9. Snapping: Extended

This week we are going to cover point number 3: menus.

Menus

This section includes the concept of game object menu, which is an abstraction of a group of game objects (represented by prefabs) that are loaded inside a menu for the user to dynamically create instances of them in the editor.

As you can see in the picture above, this section was called “Game Objects” and represents a list of menus used to add new prefabs to the editor. It’s possible to add different menus, each one with a set of prefabs to add to the scene, it’s also possible to customize the menu a little to better organize the elements inside of it and remove it if necessary.

In the previous post, we created the main class file for out extension “LevelCreatorEditor”, which handles how the menus are created, all the operations for each menu and general operations as well. As we mentioned before, all the menus are rendered inside the method “OnGUI” and for this new section we added the “BuildMenuObjects” method.

  1. List menus;
  2. EditorObjectsMenu objMenu;
  3.  
  4. void OnGUI()
  5. {
  6.    if (menus == null)
  7.       menus = new List();
  8.    //Header
  9.    BuildMenuHeader();
  10.   //Objects
  11.   BuildMenuObjects();
  12. }
  13.  
  14. void BuildMenuObjects()
  15. {
  16.    int curSel;
  17.    List removedMenus;
  18.  
  19.    //If the group is empty, we disable this section
  20.    EditorGUI.BeginDisabledGroup(layers.Count <= 0);
  21.    //You can change the label if you want
  22.    GUILayout.Label("GAME OBJECTS", EditorStyles.boldLabel);
  23.    //Add menu button
  24.    if (GUILayout.Button("Add Menu"))
  25.       menus.Add(new EditorObjectsMenu());
  26.    removedMenus = new List();
  27.    //GUILayout.BeginVertical();
  28.    gameobjectsScrollPos = GUILayout.BeginScrollView(gameobjectsScrollPos, false, true);
  29.    //Menus
  30.    foreach (EditorObjectsMenu m in menus)
  31.    {
  32.       //Title
  33.       GUILayout.Label("Menu: " + m.folder, EditorStyles.boldLabel);
  34.       //Columns
  35.       m.columns = EditorGUILayout.IntField("Columns", m.columns);
  36.       //Folder's location
  37.       EditorGUILayout.BeginHorizontal();
  38.       m.folder = EditorGUILayout.TextField("Folder Name", m.folder);
  39.       if (GUILayout.Button("Load"))
  40.          m.LoadPrefabs();
  41.       EditorGUILayout.EndHorizontal();
  42.       //Load elements
  43.       if (m.tiles != null && m.tiles.Length >= 0 && m.columns >= 0)
  44.       {
  45.          curSel = GUILayout.SelectionGrid(m.selGridInt, m.tiles, m.columns, GUILayout.Width(position.width - 20),GUILayout.Height(100));
  46.          if (curSel != m.selGridInt)
  47.          {
  48.             m.selGridInt = curSel;
  49.             objMenu = m;
  50.             DeactivateMenus();
  51.          }
  52.       }
  53.  
  54.       //Remove menu
  55.       if (GUILayout.Button("Remove"))
  56.          removedMenus.Add(m);
  57.    }
  58.    GUILayout.EndScrollView();
  59.    EditorGUI.EndDisabledGroup();
  60.  
  61.    //Clean menus
  62.    foreach (EditorObjectsMenu m in removedMenus)
  63.       menus.Remove(m);
  64. }
  65.  
  66. void DeactivateMenus()
  67. {
  68.    foreach (EditorObjectsMenu m in menus)
  69.    {
  70.       if (objMenu == null)
  71.          m.selGridInt = -1;
  72.       else
  73.       {
  74.          if (objMenu != m)
  75.             m.selGridInt = -1;
  76.       }
  77.    }
  78. }

Menus are logically represented using a List of “EditorObjectsMenu” which is a new class that has handles all the functionality inside a menu: loading new prefabs and converting them into usable icons, serializing the data to store changes for future use, etc.

  1. public class EditorObjectsMenu
  2. {
  3.    //Default folder that appears in the folder field
  4.    public const string DEFAULT_FOLDER = "Tiles";
  5.    //This will change depending on the folder's name
  6.    public string name;
  7.    //Folder's name
  8.    public string folder;
  9.    //Selected element
  10.    public int selGridInt;
  11.    //To organize the elements graphically, number of columns
  12.    public int columns;
  13.    //Textures of each prefab (from SpriteRenderer)
  14.    public Texture[] tiles;
  15.    //Prefabs that represent game objects
  16.    public Object[] prefabs;
  17.  
  18.    public EditorObjectsMenu()
  19.    {
  20.       selGridInt = -1;
  21.       name = DEFAULT_FOLDER;
  22.       folder = DEFAULT_FOLDER;
  23.       columns = 3;
  24.    }
  25.  
  26.    /// Load all the prefabs in folder "folder"
  27.    public void LoadPrefabs()
  28.    {
  29.       GameObject obj;
  30.       prefabs = Resources.LoadAll(folder, typeof(Object));
  31.       tiles = new Texture[prefabs.Length];
  32.       for (int i = 0; i &lt; prefabs.Length; i++)
  33.       {
  34.          obj = ((GameObject)prefabs[i]);
  35.  
  36.          if (obj.GetComponent().sprite == null)
  37.          {
  38.             for (int j = 0; j &lt; obj.transform.childCount; j++)
  39.             {
  40.                if (obj.transform.GetChild(j).GetComponent().sprite != null)
  41.                {
  42.                   tiles[i] = obj.transform.GetChild(j).GetComponent().sprite.texture;
  43.                   break;
  44.                }
  45.             }
  46.          }
  47.          else
  48.             tiles[i] = obj.GetComponent().sprite.texture;
  49.       }
  50.    }
  51.  
  52.    /// Get the the selected element
  53.    public Object GetCurrentSelection()
  54.    {
  55.       Object sel;
  56.  
  57.       sel = null;
  58.       if (selGridInt != -1)
  59.          sel = prefabs[selGridInt];
  60.  
  61.       return sel;
  62.    }
  63.  
  64.    /// Transform the object's parameters into a string (to save the state of the object)
  65.    public string Serialize()
  66.    {
  67.       return name + "," + folder + "," + selGridInt + "," + columns;
  68.    }
  69.  
  70.    /// Takes a serialized string and loads all the parameters of the object
  71.    public void Deserialize(string data)
  72.    {
  73.       string[] attributes = data.Split(',');
  74.  
  75.       name = attributes[0];
  76.       folder = attributes[1];
  77.       selGridInt = int.Parse(attributes[2]);
  78.       columns = int.Parse(attributes[3]);
  79.    }
  80. }

The EditorObjectsMenu class has properties such as: name, folder, selGridInt, columns, tiles and prefabs. Name and folder are the same but one is used to display the id in the GUI and the other to decide where should the prefabs be loaded from; selGridInt represents the selected element (prefab); columns, is there to customize the number of columns each menu displays; tiles is an array of textures that are mapped to the prefabs and help to display the prefabs buttons inside the menu.

LoadPredabs takes the folder path (which should be inside the physical folder “Resources” and loads all the prefabs inside that path. This data is stored in the textures and prefabs arrays to be displayed in the menu.

GetCurrentSelection just returns which of the current element was selcted by the user and with that we can create instances of that object in the editor.

Finally Serialize and Deserialize were created to easily convert the data to strings and store it as we explained in the previous post.

Going back to the previous method “BuildMenuObjects” now that the EditorsObjectMenu is defined, we can see that the new attribute called “menus” is a list of elements of EditorsObjectMenu class. With this variable we represent logically all the menus that are rendered in the GUI.

Adding Menus

We create a new button inside the GUI called “Add Menu”, the code can be seen in the “BuildMenuObjects” function above:

  1. if (GUILayout.Button("Add Menu"))
  2.    menus.Add(new EditorObjectsMenu());

Here we just create a new instance of the class and add it to the menu list and it automatically will render the information and inputs for that new menu.

Rendering Menus

All the menus are rendered inside a foreach instruction that handles each input of the menu separately:

  1. foreach (EditorObjectsMenu m in menus)
  2. {
  3.    //Title
  4.    GUILayout.Label("Menu: " + m.folder, EditorStyles.boldLabel);
  5.    //m.name = EditorGUILayout.TextField("Title", m.name);
  6.    //Columns
  7.    m.columns = EditorGUILayout.IntField("Columns", m.columns);
  8.    //Folder's location
  9.    EditorGUILayout.BeginHorizontal();
  10.    m.folder = EditorGUILayout.TextField("Folder Name", m.folder);
  11.    if (GUILayout.Button("Load"))
  12.    m.LoadPrefabs();
  13.    EditorGUILayout.EndHorizontal();
  14.    //Load elements
  15.    if (m.tiles != null && m.tiles.Length >= 0 && m.columns >= 0)
  16.    {
  17.       curSel = GUILayout.SelectionGrid(m.selGridInt, m.tiles, m.columns, GUILayout.Width(position.width - 20),GUILayout.Height(100));
  18.       if (curSel != m.selGridInt)
  19.       {
  20.          m.selGridInt = curSel;
  21.          objMenu = m;
  22.          cursorState = CursorState.Add;
  23.          DeactivateMenus();
  24.       }
  25.    }
  26.  
  27.    //Remove menu
  28.    if (GUILayout.Button("Remove"))
  29.       removedMenus.Add(m);
  30. }

 

Here basically we handle basic information for the menu: name, location. There is also a field called “Folder Name” that specifies the path of the data. After the user writes the name of thr folder down, clicks “Load” and all the prefabs will be loaded automatically inside the menu, using the SpriteRenderer’s texture as icon image. In case that the SpriteRenderer for a prefab is empty, the algorithm automatically searches inside the children of this prefab and takes the first non null texture and use it.

Removing Menus

In the previous code we can see that a “removedMenus” list was created in additio to the general menus list. This is emptied everytime before the cycle for rendering menus starts so if the user removes a menu using the remove button, it will add it to this new list and delete all menus from the main list after the cycle finishes. This is done to prevent collection changes inside the cycle.

Deactivate Menus

Finally, this method verifies if any of the elements on any menu was selected and in case it was not selected it deactivates the whole menu. This was used to have only one element selected at the time, if we do not do this, then two different menus could have one element selected each and we don’t want that when creating new instances.

With the menu deactivation I want to close this post, I’ll continue explaining the rest of the code in upcoming articles. If you have questions, leave in them in the comments.

 

Tile Editor for Unity: Part II

Tile Editor for Unity: Part I

NEW IMAGE & NEW WEBSITE

Before starting to talk about today’s topic, I would like to announce we have a new image: new logo, appeareance and website. This new website includes released games, current projects and prototypes that will hopefully become released projects soon. Hope you like it!

TILE EDITOR

So, today’s post consists of the first of a series of short posts about how the tile editor for Unity was created and explain the basic functionality of it.

For those who only want to use it and are not really interested about how it was built, as soon as the Asset is available in the Unity Asset Store, you will be able to download it for free and use it.

This is only the beginning of this tool, I would like to keep improving it along with the development of the game I’m currently creating.

The whole explanation is divided in the following parts:

  1. Editor Window Creation
  2. Saving Status
  3. Menus
  4. Game Objects Actions: Create, Delete, Edit
  5. Layers
  6. Grid
  7. Snapping
  8. Transformations
  9. Snapping: Extended

For this post we will cover only the first two parts, the rest of the explanation will be covered in the upcoming weeks.

EDITOR WINDOW CREATION

The first thing we need to do is to create a folder called “Editor” in our Project manager, this is a special folder designed to extend the functionality of the basic Editor.

As you can see, I added the folder inside a folder called “FSLevelEditor”, you can really put this folder wherever you want and you can also create multiple folders in different locations, if they are named “Editor”, that should be enough for it to work well.

After creating this folder, inside of it, we want to create a new class which will be the main class for our extension. In this particular case, I called the main class “LevelCreatorEditor”, you can customize this name as you like.

  1. using UnityEditor;
  2. using UnityEngine;
  3. public class LevelCreatorEditor : EditorWindow
  4. {
  5.    // Add menu named "My Window" to the Window menu
  6.    [MenuItem("Window/Level Editor")]
  7.    static void Init()
  8.    {
  9.       //Get existing open window or if none, make a new one:
  10.       LevelCreatorEditor window;
  11.       window = (LevelCreatorEditor)EditorWindow.GetWindow(typeof(LevelCreatorEditor));
  12.       window.Show();
  13.    }
  14. }

This new class should inherit from “EditorWindow” which is the base class for new extensions in Unity. You can see more details about this class in the manual. The class EditorWindow belongs to the UnityEditor library so we need to import it as well.

In this class a new static method called “Init” has to be added to show the new window and assign a name for it in the menu. In line 6 in the code above you can see that I set the path of the new menu to “Window/Level Editor”, the location “Window” represents the Window menu in the Unity editor, you can choose whichever menu you prefer to have your new extension. You can also customize the name of the new item in the menu by changing the “Level Editor” for whatever you like.

  1. int gridSize;
  2. float gridX;
  3. float gridY;
  4. Color gridColor = Color.white * 0.8f;
  5. Color cursorColor = Color.red;
  6. Color selectColor = Color.blue;
  7. bool showGrid = true;
  8. void OnGUI()
  9. {
  10.    //Header
  11.    BuildMenuHeader();
  12. }
  13. void BuildMenuHeader()
  14. {
  15.    GUILayout.Label("GRID SETTINGS", EditorStyles.boldLabel);
  16.    gridSize = EditorGUILayout.IntField("Grid Size (px)", gridSize);
  17.    gridX = gridSize / 100f;
  18.    gridY = gridSize / 100f;
  19.    gridColor = EditorGUILayout.ColorField("Grid Color", gridColor);
  20.    cursorColor = EditorGUILayout.ColorField("Cursor Color", cursorColor);
  21.    selectColor = EditorGUILayout.ColorField("Select Color", selectColor);
  22.    showGrid = EditorGUILayout.Toggle("Show Grid",showGrid);
  23. }

The “OnGUI” function is the one that renders all the elements you want inside your new window. In this particular case, I’m adding general properties for the grid just to illustrate how it works. You can directly put the GUI code inside this function but I’m getting the exact code I made for the tile editor, that’s why I added the “BuildMenuHeader” function.

The “BuildMenuHeader” basically includes all the elements that affect the grid and change the variable values according to whatever the user chooses.

We can see in line 16 a short example about how the variables are modified in real time, “EditorGUILayout.ColorField” takes a couple of parameters, the label for that element and also the current value, this function returns the modified value in real time, that’s why we use and modify the “gridSize” variable in the same line.

SAVING THE STATUS

The previous section was about creating a very basic window with few elements that make our variables change in real time. The other important step for this post is about how to take those values we change and keep using them after we close the window. If we don’t do this, everytime that we close and re-open our new extension’s window, all the parameters will be initiallized again.

  1. bool loaded;
  2. void OnEnable()
  3. {
  4.    LoadData();
  5. }
  6.  
  7. void OnDisable()
  8. {
  9.    SaveData();
  10. }
  11.  
  12. void OnDestroy()
  13. {
  14.    SaveData();
  15. }
  16.  
  17. ///
  18. /// Saves the current editor's status
  19. ///
  20. public void SaveData()
  21. {
  22.    string id;
  23.  
  24.    id = PlayerSettings.productName;
  25.    EditorPrefs.SetInt(id + "-GridSize", gridSize);
  26.    EditorPrefs.SetString(id + "-CursorColor", FromColorToString(cursorColor));
  27.    EditorPrefs.SetString(id + "-GridColor", FromColorToString(gridColor));
  28.    EditorPrefs.SetString(id + "-SelectColor", FromColorToString(selectColor));
  29.    EditorPrefs.SetBool(id + "-ShowGrid", showGrid);
  30. }
  31.  
  32. ///
  33. /// Convert an object from class Color into a serialized string
  34. ///
  35. public string FromColorToString(Color color)
  36. {
  37.    return color.r + "," + color.g + "," + color.b + "," + color.a;
  38. }
  39.  
  40. ///
  41. /// Convert a string into an object from Color
  42. ///
  43. public Color FromStringToColor(string color)
  44. {
  45.    string[] desColor;
  46.    desColor = color.Split(',');
  47.  
  48.    return new Color(float.Parse(desColor[0]), float.Parse(desColor[1]), float.Parse(desColor[2]), float.Parse(desColor[3]));
  49. }
  50.  
  51. ///
  52. /// Restore the editor's status
  53. ///
  54. public void LoadData()
  55. {
  56.    string id;
  57.  
  58.    id = PlayerSettings.productName;
  59.  
  60.    if (EditorPrefs.HasKey(id + "-GridSize"))
  61.       gridSize = EditorPrefs.GetInt(id + "-GridSize");
  62.  
  63.    if (EditorPrefs.HasKey(id + "-ShowGrid"))
  64.       showGrid = EditorPrefs.GetBool(id + "-ShowGrid");
  65.  
  66.    if (EditorPrefs.HasKey(id + "-CursorColor"))
  67.       cursorColor = FromStringToColor(EditorPrefs.GetString(id + "-CursorColor"));
  68.  
  69.    if (EditorPrefs.HasKey(id + "-GridColor"))
  70.       gridColor = FromStringToColor(EditorPrefs.GetString(id + "-GridColor"));
  71.  
  72.    if (EditorPrefs.HasKey(id + "-SelectColor"))
  73.       selectColor = FromStringToColor(EditorPrefs.GetString(id + "-SelectColor"));
  74.  
  75.    loaded = true;
  76. }

We have the “OnEnable” function that is executed every time the window is enabled; the “OnDisable” function that runs everytime the window is disabled and finally the “OnDestroy” function that runs when the window is closed. In these functions the status of the editor is saved and restored.

There are a couple of helpers in this part that make easier to save and restore objects from the Color class. “FromStringToColor” and “FromColorToString” are functions that transform the data to make the saving and restoring process easier.

The “SaveData” function basically stores all the important values in an internal storage (Editor Preferences). Depending on the type of data we want to save, we use different functions.

One important thing that has to be highlighted here is this: EditorPrefs.SetInt(id + “-variable id”, variable), the reason why we are using the “id” variable (this represents the name of the project) in the variable’s name is that this internal storage does not distinguish between projects, it means that if we do not add a project identifier, those values will be replaced if you try to use the editor again and we don’t want that.

The “LoadData” function just restores the data in the same way that the we save it but using the appropriate functions.

SUMMARY

In this first post of a series on how to create a tile editor extension for Unity, we covered two basic steps: how to create a new window and how to store the data we modify using that window.

With these basic concepts you can create whatever you want, add more elements to the menu, include a different functionality or develop a completely different extension, I recommend testing all the elements you can add to a menu and play with it to understand better how it works, that’s what I did.

Upcoming posts will have details about the rest of the code and I will let you know as soon as the full extension is approved in the Unity Asset Store so you can download it and use it for your projects.

Tile Editor for Unity: Part I

Progress, Level Edition and Game Jam’s Feedback

This week I would like to talk about three different topics, the progress I’ve been making on the games I’m creating, the status of the Unity Level Editor and some thoughts on the past Ludum Dare 38 experience.

Progress

Fiery Squirrel is currently working on a couple of games. One of them is (tentatively) called Zin, which is the game that is benefiting from the new tile editor. There is also another game I mentioned in previous posts, Kuon’s Saga, developed in collaboration with Gabriel Uguet, illustrator and creator of Serpentarius, an online comic.

ZIN

Zin is a action-puzzle game that narrates the story of a little creature unable to move by itself due to a magic curse. The creature needs to recover its powers and escape from its captors. This is a very simple game for computer and consoles that involves mastering different kinds of abilities and gameplay mechanics.

The game is still in very early stage of development, I’m currently focused on designing and testing the first level, including concept art, sounds, etc. The game is divided in four different chapters, each one with four levels.

In case you are curious, you can play a very early prototype made for Ludum Dare some time ago in Newgrounds. The game now includes many more things and it’s bigger but the essence is still the same.

For this week, the focus was to create and test levels with the new editor, which is really helping a lot with the new tiles and perspective of the art. I hope next will show some new screenshots and progress.

Kuon’s Saga

This is a game that has been in development for some time, despite its simplicity and its casual nature, we created different versions of the game and tested too many things on a very slow development pace.

The game is planned to be released for free at some point next month and will include one level with 24 waves for players to enjoy. Progressively, depending on players’ feedback and reactions, we will keep adding new content with new levels and challeneges. This will be available for Android and iOS.

This week has been basically working on completing all the final elements for the gameplay, including graphical stuff and level design. By next week we will have a very solid demo to show.

Level Edition

I have good news about the Unity Tile Editor. All the basic functionality is already working properly and we submitted it for approval to the Unity Asset Store. The editor will be free and available for people to download.

This first version is a beta that will be expanded in the upcoming weeks, there are a lot of things to improve, from the functionality side of the editor to the usability, we plan to add new stuff to help automatize cumbersome tasks and create levels faster and easier.

For all of you that are interested in the details on how was the editor created, I’ll be posting details about the code in future articles. If you have specific questions, please let me know.

The video shows all the current features that will be available for people when the asset is approved on the Asset Store.

Game Jam’s Feedback

As I mentioned in a previous post, I was part of the Ludum Dare 38 three weeks ago. After playing a lot of different games and receiving feedback from people on the game I made, I have some thoughts that might be useful for people making games out there.

First of all I want to thank people for their observations, getting feedback is really nice and one of the reasons I like participating in this event. Comments from people really help improving our work, they show us a perspective that we probably do not have or just different ideas that enrich our way of thinking.

Feedback

When I talk about feedback, it’s not only players’ comments, ideas and bug reports. Feedback is also related to that concept of making your game react to the player’s input and be able to clearly show in the screen or  through speakers what is going on in your game.

I decided to talk a little about this because my most recurrent comment on other people’s games was exactly that: “it would improve a lot if it had more feedback”.

And this is something that I really think developers can easiy do. Feedback does not really require super high art making skills or 3D modeling years of experience, this is all about the feeling of the game, this is related to showing the status of the game in a very clear way.

In SORLD for example, although I know there is a lot to improve and much more feedback to add, the basic actions have clear feedback and they were implemented in very few time. Shooting, being hit, recovering, dying and making progress are some of the basic actions that this game includes and they were represented with particles, screen shake, sprite rotation, sound effects, etc.

Seriously, with few changes on the feedback side, adding particles, sound effects, changing the rotation a little, zooming in and out, in most of the cases make the game feel much better and meaninful and it’s not really a difficult thing to do.

Jan Willem Nijman from Vlambeer has more experience than me on this topic and he gave a nice talk about “Game Feel” which is related to what I mentioned here. Enjoy the talk if you haven’t already and hope this helps you make better games!

This was basically it for this week. A lot of things going on, I’m trying to keep people up to date about what we are doing here and get some comments on what would be interesting to talk about for future post.

Have a great weekend!

Progress, Level Edition and Game Jam’s Feedback

Inkscape Parser for OpenFL (Haxe)

Last week I mentioned the small Inkscape parser I created for OpenFL and I said it was very helpful, not only to have it but also understanding how the Inkscape’s XML file is structured. This week I decided to briefly explain the code for that because it might be useful for someone working with OpenFL.

Editor XML (Shift + Ctrl + X)

This is the first cool thing about Inkscape I learned before writing the parser. Maybe you already know this but you can see an XML editor for the document you are seeing inside Inkscape if you press shift + ctrl + x. It basically shows a bunch of nodes that represent elements you have in your document.

In the XML you can find anything you add to the document, if we add a new element to the scene, it’s automatically added to the XML too and you can easily see it and its information when you select it in the document.

With these two things, we can easily write a very simple parser that maps positions from the XML document to our game. The following structure is the most basic one to get the elements inside layers in the document.

The Code

actionscript3

  1. //This code is written in Haxe but Wordpress does not highlight haxe so I just chose ActionScript3 :)
  2.  
  3. //This is what we use to load the Inkscape file (as it is)
  4.  
  5. var xml:Xml;
  6.  
  7. //You can use any variable you want but for this article I chose position, radius and id
  8.  
  9. var x, y, radius : Float;
  10. var id : String;
  11.  
  12. xml = LoadXML(YOUR_FILE_PATH + "name_of_your_file.svg");
  13.  
  14. try
  15. {
  16.    //Iterate over all the nodes of the file
  17.    for (e in xml.iterator())
  18.    {
  19.       if (e.nodeType == Xml.Element)
  20.       {
  21.          if (e.nodeType == Xml.Element)
  22.          {
  23.             //This is the layer's label (you can see the image above, it's called "Capa 1"
  24.             switch(e.get("inkscape:label"))
  25.             {
  26.                case "your_layer_name":
  27.                   id = e1.get("id");
  28.                   //The size of the Inkscape file should be the same in GraphicSystem here
  29.                   x = Std.parseFloat(e1.get("x"));
  30.                   y = Std.parseFloat(e1.get("y"));
  31.                   radius = Std.parseFloat(e1.get("r"));
  32.                   //This is only a sample, you can read whatever you want here
  33.             }
  34.          }
  35.       }
  36.    }
  37. }
  38. catch (e : String)
  39. {
  40.    trace(e); //Catch the exception beautifully
  41. }
  42.  
  43. //This function belongs to a bigger library I created as a helper for easily doing annoying stuff like this
  44. public static function LoadXML(path : String) : Xml
  45. {
  46.    var xml : Xml;
  47.    var str : String;
  48.    
  49.    str = Assets.getText(path);
  50.    xml = Xml.parse(str).firstElement();
  51.  
  52.    return xml;
  53. }

I believe the code is kind of self explanatory but I’ll comment a bit on it. Basically we load the XML file, put it in a variable and iterate over all of its nodes to see which one contains the data we need: layers. If you want to customize your parser and add whatever information you need from the document you can also do that. Once we find the layer we are looking for (it should be the same name in the XML file), we use the data, store it in variables and use it as we want.

There are some transformation involved, depending on what you want to do with the data you get from Inkscape but it totally depends on the purpose of your code, if you have any questions related to this, please let me know.

Custom Attributes

Something that I find very helpful from Inkscape, is the possibility to include custom attributes, which enables us to take the editor further and add specific information for our elements.

Let’s say you have a game object that has “speed”. You want to customize each game object in the editor for different speeds so your game object is different depending on its type or something like that.

In the image above you can see fields for each attribute in the element if you click on the element and then click on the attribute. If you write a new name in the field for name and assign a value to it, it will be automatically created after you click “Accept” (it’s “Aceptar” in Spanish in the image).

actionscript3

  1. switch(e.get("inkscape:label"))
  2. {
  3.    case "your_layer_name":
  4.       id = e1.get("id");
  5.       //The size of the Inkscape file should be the same in GraphicSystem here
  6.       x = Std.parseFloat(e1.get("x"));
  7.       y = Std.parseFloat(e1.get("y"));
  8.       radius = Std.parseFloat(e1.get("r"));
  9.      //New Attribute
  10.      speed = Std.parseFloat(e1.get("speed"));
  11. }

In the example above, you can see how we read the custom attribute using the same name we added in the XML editor.

I think that with these basic concepts you can easily write a whole map editor or if you prefer, a menu editor taking advantage of the Inkscape’s functionality. There are probably better or more general ways to do this but I wanted to discuss about the one I implemented.

Also, I want to clarify that an in-game editor would be much more helpful and comfortable to work with but either you need the time to make it or the money to buy it. This works for me and the simple games I’m creating, if you have advices or ideas, please let me know in the comments.

Inkscape Parser for OpenFL (Haxe)