Saturday 15 July 2017

Saving data

At some point in your application, you are very likely to need to save some data permanently. Whether it is the score value, the current game state, the number of unlockables unlocked, you probably need a way to retain this information in order to retrieve it later when the user returns to the app.

In Unity there are different ways to save data on the disk and I'm going to show a few in this post.

1. Player Preferences

This is by far the easiest way to store information permanently. If you have ever done any coding for the Android platform, Player Preferences in Unity works just like Shared Preferences.

It is a Unity built in feature, it automatically creates a file on the disk and store information one by one. Let's assume we have a class which stores some variables the we need to save, like so:

public class SaveData  {

 public string playerName = "";
 public int totalScore = 0;
 public int playerLevel = 0;
 public bool maxLevelReached = false;

 public void print()
 {

  string toPrint = "Player Name: " + playerName + "\n" +
                   "Total Score: " + totalScore + "\n" +
                   "Player Level" + playerLevel + "\n" +
                   "Max Level Reached: " + maxLevelReached;

  Debug.Log (toPrint);

 }

}

In order to save all the variables we need to do something like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public void SaveDataPlayerPrefs()
 {

  SaveData data = new SaveData ();

  data.playerName = "Jeffrey the mighty hero";
  data.playerLevel = 90;
  data.maxLevelReached = true;
  data.totalScore = 10;

  PlayerPrefs.SetInt ("PlayerLevel", data.playerLevel);
  PlayerPrefs.SetInt ("TotalScore", data.totalScore);
  PlayerPrefs.SetString ("PlayerName", data.playerName);
  PlayerPrefs.SetInt ("MaxLevelReached", data.maxLevelReached ? 1 : 0);

 }

As you can see, we need to write a line of code for each variable we want to save. Additionally, every parameter needs to be associated with a key string value, which is then used to retrieve the information. Finally, if you look at line 14 you will notice that something strange happens. That is because PlayerPrefs does not support boolean variables, therefore we need to find a workaround in order to store this type of variable. In this case, I simply turned the boolean into an integer.

If we want to load the information we would proceed as follow:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public void ReadPlayerPrefs()
 {
  SaveData data = new SaveData ();

  data.playerLevel = PlayerPrefs.GetInt ("PlayerLevel",-1);
  data.playerName = PlayerPrefs.GetString ("PlayerName", "_no_name_");
  data.totalScore = PlayerPrefs.GetInt ("TotalScore", -1);

  int maxLevelReachedInt = PlayerPrefs.GetInt ("MaxLevelReached", -1);

  if (maxLevelReachedInt == 1)
   data.maxLevelReached = true;
  else if (maxLevelReachedInt == 0)
   data.maxLevelReached = false;
  else
   Debug.Log ("Problem occured while loading PlayerPrefs");

  data.print ();

 }

We can retrieve all the data we saved previously using the keys we used to identify each variable. We also need to provide a default value in case the data we are trying to find is not available. Notice how I had to check that the integer was a a 1 or a 0  in order to set the boolean variable in our SaveData object. To be honest, we should perform a check on each variable to make sure that the information has been retrieved correctly.

The console should produce this output:


2. Binary formatter

Binary formatter is a "pure C#" method for serializing objects. Simply put, it turns objects data in a bunch of 1s and 0s.

Firstly, in order to use it, we need to mark the SaveData class as Serializable. We can do that very easily by simply adding [Serializable] before the class declaration.

This is the routine which uses the binary formatter:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public void SaveDataBinary()
 {
  string path  = "Assets/Res/SaveFiles/SaveDataFile.data";

  SaveData data = new SaveData ();

  data.playerName = "Jeffrey the mighty hero";
  data.playerLevel = 90;
  data.maxLevelReached = true;
  data.totalScore = 10;

  BinaryFormatter bf = new BinaryFormatter ();
  FileStream fs = File.Open (path, FileMode.Create);
 
  bf.Serialize (fs, data);
  fs.Close ();
 }

After we define the path, which is the location where the file will be created at, we can get a BinaryFormatter object and a FileStream object, passing the path and a FileMode parameter. In this case, we want to create a file. We then simply proceed by telling the formatter to serialize our data object, which is going to be stored in the "SaveDataFile.data" file.

When it's time to get everything back, we can get the object as shown:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
 public void LoadDataBinary()
 {
  string path  = "Assets/Res/SaveFiles/SaveDataFile.data";
  SaveData data = null;

  BinaryFormatter bs = new BinaryFormatter ();
  FileStream fs = File.Open (path, FileMode.Open);
  data = (SaveData)bs.Deserialize (fs);

  data.print ();
 }

Obviously, the file path and name must be the same as the ones we used when we saved the object. We can then create an empty SaveData object and deserialize our file. Notice how we need to cast to a SaveData type as the deserialize method returns a generic objecti type.

3. JSON

Unity has recently added a Json utility feature in order to pass data around using the now popolar JSON format. I believe there's no need to introduce JSON in this post, there's plenty of documentation on the web, but if you have never heard of it all you need to know is that is a particular type of format used to save objects, very easy to use and read.

Here's how you can save your object in JSON format in Unity:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void SaveDataJson()
 {
  string path  = "Assets/Res/SaveFiles/SaveDataFile.json";

  SaveData data = new SaveData ();

  data.playerName = "Jeffrey the mighty hero";
  data.playerLevel = 90;
  data.maxLevelReached = true;
  data.totalScore = 10;

  string datajson = JsonUtility.ToJson (data);

  FileStream fs = new FileStream (path, FileMode.Create);

  StreamWriter sm = new StreamWriter (fs);

  sm.Write (datajson);

  sm.Close ();
  fs.Close ();

 }

In a very similar process followed when using the binary formatter, we indeed create a file, with the extension .json. The object is then converted to JSON format, which is really nothing but a string, which is then written into the file.

Once saved, the file should be preset on your device and you should be able to open it. It should look like this:

1
2
3
{"playerName":"Jeffrey the mighty hero saved in Json",
"totalScore":40,"playerLevel":40,
"maxLevelReached":false}

This is your object saved in JSON format.

Below, a routine that get the information back from this file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public void LoadDataJson()
 {
  string path  = "Assets/Res/SaveFiles/SaveDataFile.json";

  StreamReader reader = new StreamReader (path);

  string saveDataString = "";

  while (!reader.EndOfStream) {

   saveDataString += reader.ReadLine ();
  
  }

  reader.Close ();

  SaveData saveData = JsonUtility.FromJson<SaveData> (saveDataString);

  saveData.print ();
 }

I used the StreamReader to read the .json file as if it was a simple text file and I stored the result into a string.

This variable is then passed into the JsonUtility method which parses the string type and converts it into the object specified in the <>, in our case, our SaveData type. It doesn't get any easier then that.

4. Scriptable Objects

Ok, perhaps this last method does not belong to this list. Yes you can save data permanently using Sciptable Objects, however, it is not recommended to do so during runtime. SOs as mostly used (and useful) in the editor. They are a very convenient way to create assets files which can be modified by simply setting their parameters.

All we have to do is to create a new class identical to the SaveData class we wrote at the beginning of this post, only thus tune we would inherit from Scriptable Object:


1
2
3
4
5
6
7
8
9
[CreateAssetMenu]
public class SaveDataSO : ScriptableObject {

 public string playerName = "";
 public int totalScore = 0;
 public int lastLevelPlayer = 0;
 public bool maxLevelReached = false;

}

Once we created the asset file, we can simply reference it anywhere else in the project and simply modify its parameters, and every change made is permanent.

A very easy method for sure, however, using SOs can sometimes create headaches: common issues when saving information this way are decoupling and the fact that SOs can only serialize certain types of parameters, so you should definitely read more about this should you intend to use scriptable objects.

Conclusion

As always, there are different ways to perform a task in programming. These examples only show the basic of data storage, and they are all used for local saves. Depending on what type of game you are developing, you might need to save data in the cloud, perhaps to allow the user to access your application (and the data saved) from different devices. In that case you should look into services offered by Google Play or Amazon Web Services for example.

Additionally, none of these examples showed any protection. Users could easily get to the data files and modify the content to their advantage, which may or may not affect the overall experience. Implementing safety is of course always a good idea.

Finally, if you find yourself with not enough time/resources/will to write your own save manager, a simple search on the asset store will allow you to get access to a wide variety of data saving scripts with all different levels of complexity and protection, choosing is always a matter of what is really needed in your project.

Tuesday 2 May 2017

Animation sequence

I have been trying for a while now to create a sequence of animation. By that I mean a chain of animations that play without the user input. Very much like a cutscene or a tutorial, that can be played out to the user without interaction.

After looking up for tutorials I couldn't find anything relevant, so I came up with my own implementation.

I have also played around with the Unity Timeline feature, which is still in experimental mode, and while I was able to create some sequences, I still could not play animations the way I wanted, such as, having the player character move around, shoot, jump and do other things.

In the end I found a solution which, although not optimal, works pretty well and allows me to create scripted animations for each GameObject in the current scene.

I'm still using this  assent, just like the previous posts.

The way I had it in my mind was to have all my sequences as asset files, which I could then plug int a sequence player in the scene.

Firstly, let's take a look at the Sequence script:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public abstract class Sequence :  ScriptableObject {

 public bool playOnce;
 public bool hasBeenPlayed = false;
 public abstract void OnSequenceEnd();
 public abstract IEnumerator sequence(GameObject[] actors);

 protected void CloseAllDialogs(GameObject[] actors)
 {
  for(int i=0; i<actors.Length;i++)
  {
   actors [i].GetComponent<Speak> ().CloseComic ();
  }
 }

}

This is nothing but a base class which will use to create our sequences asset files. As you can see, it's a ScriptableObject, which allows us to create an asset out of it.

The animation sequence itself is going to be a coroutine, which is the IEnumerator on line 6, which will be differet for each animation sequence we wish to write.

The CloseAllDialogs method is strictly related to my current project and should not be relevant for this example.

One limitation I had to work around was the inability of ScriptableObjects to reference objects in the scene. It would have been much easier and neat to have all the GameObjects I wanted to animate in my ScriptableObject file, but that is simply not possible. Instead, the SequencePlayer will be able to "select" objects form the scene and pass them to the scriptable object, and that is the actors GameObject array you see being passed to our co routine on line 6.

This is the SequencePlayer script:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class SequencePlayer : Interactable {

 public GameObject[] actors;
 public Sequence sequence;
 public bool playOnAwake = false;
 public bool playOnCollision = false;
 public float startDelay = 0;

 void Start()
 {
  if (playOnAwake)
   Invoke ("PlaySequence", startDelay);
 
 }

 public void PlaySequence()
 {

   GameController.instance.currentLevel.SetCurrentState (new PlayableLevel_ConversationState ());
  StartCoroutine (sequence.sequence (actors)); 
 
 }

 override public void OnInteract()
 {
  if (playOnCollision) {
  
   Invoke ("PlaySequence", startDelay);
   GetComponent<Collider2D> ().enabled = false;
  
  }
 }

}

This is, in fact, a Monobehavior script. You can see it inherits from a class called Interactable, which is simply a base class I'm using in my project that represents objects that can interact with the player.

This script will be placed on an empty GameObject in the scene, which should have a collider attached to detect the presence of the player character. I decided to do it this way because I want my animations to be triggered by two main factors: either the scene starts or the player has interacted with our SequencePlayer GameObject, which means has reached an area where an animation sequence should be played.

The 2 bool variables on line 5 and 6 determine which one is the trigger method. If the sequence should be played on awake, as soon as the script is initialized the animation is triggered, with a delay if necessary (line 12). Otherwise, will be triggered once the collision has been detected (line 24 - 31).

The GameObject array actors will need to be pre filled with all the objects we wish to animate. This array is then passed to our ScriptableObject sequence, which is going to do all the magic for us.

Let's have a quick look at our scene now:


We only have the player character on the right. At the moment it is off camera, in our animation we will have it walking in the viewport, greet the player and leave.

An empty GameObject is present in the scene, called Sequencer, and has the SequencePlayer script attached. The inspector will look like this:


As you can see, I passed the Player object to the actors array, and I plugged a Sequence file called ExmapleSequence into the Sequence public field (which is a ScriptableObject).

This is the ExampleSequence scriptable object:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
[CreateAssetMenu(fileName="ExampleSequence",menuName="AnimationSequence/ExampleSequence")]
public class ExampleSequence : Sequence {

 override public IEnumerator sequence(GameObject[] actors)
 {
  GameObject playerObj = actors [0];

  playerObj.GetComponent<Rigidbody2D> ().isKinematic = false;

  while (playerObj.transform.position.x < 0) {
   playerObj.GetComponent<CharController> ().Movement (new Vector2 (1, 0));

   yield return null;
  }

  playerObj.GetComponent<CharController> ().Movement (new Vector2 (0, 0));

  yield return new WaitForSeconds (2);

  playerObj.GetComponent<Speak> ().Say ("Greetings!");

  yield return new WaitForSeconds (2);

  playerObj.GetComponent<Speak> ().CloseComic ();

  while (playerObj.transform.position.x >-15) {
   playerObj.GetComponent<CharController> ().Movement (new Vector2 (-1, 0));

   yield return null;
  } 

  OnSequenceEnd ();

 }

 override public void OnSequenceEnd()
 {
  //Currently not used
 }

}

The very first line is used to create the actual asset file. In the editor, if we now navigate to Asset->Create we can see our ExampleSequence asset, which can be instantiated as an asset file in our project folder:


Then, we proceed creating the actual sequence, in the IEnumerator.

In order to move the character around and speak I have created a character controller script, which I am not going to display here as it is quite extensive. However, there are many tutorials on how to create a character controller, but really it's just a script that enables our character movement and apply animations through an AnimatorController.

On line 6 we grab the reference to our player object.

We then proceed to set its RigidBody2D to respond to physics. The while loop will call the Movement(,,,) method of the CharController script which simply moves the object to a certain point, checking the X value of the position. This method also applies animation with the AnimatioController whithin the CharController script.

We then stop the movement (line 16), we wait for a couple of seconds and we get the player to "say" something. This is done very simply with the use of a Canvas in world space. Finally, we close the comic and send the player back the way he came.

And that is it.

This what it looks like in the end:


Clearly, this is not optimized. Each time we need to grab components from the player object, and that is not ideal. To improve on this, we could cache all the components at the beginning of the script and call them accordingly. For this example, this is acceptable.

Conclusion

This simple method is a good starting point to create scripted animations. It can be a little time consuming having to write a complete sequence, but it allows for great control over all the objects we wish to animate. I'm also using this technique to create tutorial sequences for the player, in which the animation sequence is not controller by time, rather, by the user input this time. Before proceeding to the next instruction, instead of using WaitForSeconds(..) we can pass in a while loop, like so:

1
2
3
   do {
    yield return null;
   } while (!Input.GetMouseButtonDown (0));

This way, the script will resume only after the user has tapped the screen.

I think a lot can be done to optimize the scripts, however, depending on your project, this could be a decent solution for cutscenes and scripted animations.

Friday 6 January 2017

Unity sprite packer and texture post processing

I'm currently playing around with some 2D assets in Unity and I found one of particular interest. I'll try to build a small mobile game to release on the blog perhaps.

While testing scenes and importing assets I came across a problem I never encountered before: in the downloadable package all the sprites are not packed in a single sprite sheet, as it is often the case, rather, they are all single, separated files. This really is not a big issue, however, I always like to find the best approach to solve problems and I came up with an unusual solution,

The first thing that came to mind was to download Texture Packer and pack all the sprites in different spritesheets (player, enemy, tileset,ecc.). The second option was to use the build-in-Unity feature Sprite Packer. Since I wanted to try something new, I opted for the latter.

If you are not familiar with the feature, this window will let you pack sprites into spritesheet in order to reduce draw calls and improve performance. In order to do so, we have to give a tag to each texture imported from the package. (Fig 1).

Fig 1
We can then go to Window->Sprite Packer and press the Pack button on the top right corner of the window. At this point Unity will create a sprite-sheet for each tag with all the textures with that particular tag. Simple enough. (Fig. 2).



However, the problem came when I realized I had quite a lot of texture assets to set up in order to achieve this. In addition, for each asset file of this type I needed to change some other parameters, like Filter Mode, MipMap, ecc.

The idea of having to do that manually for each texture was not really thrilling and, moreover, I would need to remember to do so for any additional asset of type texture I might import later while developing the project. Basically, I needed some other way to achieve this automatically.

So, after some research, I came across something called AssetPostProcessor.

This class, and I'm quoting the Unity documentation here, "lets you hook into the import pipeline and run scripts prior or after importing assets".

I can now write a very simple script that changes all the parameters I need to change for each texture imported without having to touch any of the asset files.

The sprite processor script

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public class SpriteProcessor : AssetPostprocessor {

 void OnPostprocessTexture(Texture2D texture)
 {
  

  TextureImporter importer = (TextureImporter)assetImporter;

  importer.filterMode = FilterMode.Point;
  importer.maxTextureSize = 64;
  importer.spritePixelsPerUnit = 16;
  importer.mipmapEnabled = false;


  DirectoryInfo parent = Directory.GetParent (importer.assetPath);

  importer.spritePackingTag = parent.Name;


 }
}

Amazingly, the script needed is in fact very simple.

First thing, we need to extend the class AssetPostprocessor.

The code will need to run within the method OnPostprocessTexture, which will run for every single asset file of type texture once it has been imported in the engine.

We can then grab the TextureImporter object and apply our changes to the files. See how I modified some parameters and I assigned the name of the parent folder of the texture file as a packing tag (line 17). This way, for each folder containing textures for a particular animation we will have a sprite-sheet.

Project folder
Inspector

If we pack now all the sprites, we can see we will have many sprite-sheets, one for each folder with all the sprites in it.


Conclusion

Creating tools to help yourself developing the game is the best way to improve your workflow. With this simple script I no longer have to worry about imported textures,knowing that everything is being taken care of in the post processing.. Much more can be done using this class, of course, it always depends on the project and what particular tools can help you creating your game.