Friday, 26 February 2021

Coming back (with asset bundles)

 Oh dear, to say that it's been a while it's very much an understatement.

Last time I posted here was something like 3 years ago.

Well, I have been up to some other things, mostly completing my computer science degree, and getting a job in the industry. Also, other personal stuff that kept me away from all this.

Turns out, I may have some extra spare time now to dedicate to this blog again, especially because I realized I actually missed it. I don't really know how often I'll be able to post here, but I'll try to put up something of interest every now and then. In fact, we started using Unity at my company now, and I got to learn some things.

With that out of  the way, today's topic is....

Asset bundles


A relatively less exciting topic probably, but something I had to deal with in a couple of occasions.

In Unity, the only way you can load external assets at runtime is through asset bundles. Don't get confused here: I'm talking about assets that are not part of your project, files that are not in your "Assets" folder when the game starts. The idea is to load in those assets that reside outside of your project.

Please, start your "bundle" word count now.

Creating asset bundles

First thing to do is of course, create the asset bundle(s).
Let's say we want to export a prefab, a simple sphere (classic). 
After creating a simple sphere prefab, take a look at the bottom of the inspector window for the prefab:




Yes, there's an asset bundle property. All you have to do is to assign a name (as shown in the image) to that property, and that will be the asset bundle name.

Once you assigned all the names to all your assets you want to "bundle", it's time to build them. To do so, we use a very simple script:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
using System.IO;
using UnityEditor;

public class AssetBundleCreate 
{
    [MenuItem("Assets/Build AssetBundles")]
    static void BuildAllAssetBundles()
    {
        const string assetBundleOutputDir = "Assets\\AssetBundles";
        if (!Directory.Exists(assetBundleOutputDir))
        {
            Directory.CreateDirectory(assetBundleOutputDir);
        }
        BuildPipeline.BuildAssetBundles(assetBundleOutputDir, BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows);
    }
}

Keep in mind that this is an editor script, so it should be placed in a folder called Editor, under Assets.

This will create a menu option which will export your bundles in the specified folder:


Ok, I have a question: what if I want to bundle something that is actually not in the project?

Well, that's possible to do, all we have to do is to sismply copy the content in the project first, and assign it a bundle name and that's it, when building the bundles, it will be exported.

You can use any method to copy files/folder into your project, check  this directory copy method.

Then, let's say you copy a folder and you want to turn all your .PNG to asset bundles, because why the hell not, you can change the previous method to this:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static void BuildAllAssetBundles()
    {
        const string assetBundleOutputDir = "Assets\\AssetBundles";
        const string localPNGFolder = "Assets\\PNGs";
        if (!Directory.Exists(assetBundleOutputDir))
        {
            Directory.CreateDirectory(assetBundleOutputDir);
        }

        DirectoryCopy("sourceFolder", localPNGFolder, true);
        AssetDatabase.Refresh(); //Important

        string[] files = Directory.GetFiles(localPNGFolder, ".PNG", SearchOption.AllDirectories);

        //Assigne bundle name to imported asset
        foreach(string f in files)
        {
            string name = Path.GetFileNameWithoutExtension(f);
            AssetImporter.GetAtPath(f).SetAssetBundleNameAndVariant(name, "");
        }

        BuildPipeline.BuildAssetBundles(assetBundleOutputDir, BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows);
    }

The method will do the following:
  • Grab the external folder
  • Copy it into the project (in the folder specified at line 4)
  • Grab all .PNG files from the local folder
  • Assign a bundle name to each, using the name of the file itself (or use any custom name you need)
  • Build bundles
That's it, now all your PNGs are exported as asset bundles.

Loading asset bundles


Ok well, if you export asset bundles is likely because at some point you want to import them. 
The whole idea behind asset bundles is that they can be loaded in at runtime. So, we can create a MonoBehavior script that will do just that:


1
2
3
4
5
6
7
8
9
 public void LoadBundlePrefab()
    {
        var bundle = AssetBundle.LoadFromFile("pathToTheAssetBundleFile");
        if(bundle != null)
        {
            var prefab = bundle.LoadAsset<GameObject>("Sphere");
            Instantiate(prefab);
        }
    }

That's all you need. One important thing to mention: notice how I passed the name "Sphere" when I grab the pfefab in the bundle. That;s because it's the name of the actual GameObject that we exported, so it's important that, if you need to get the actual prefab from the bundle, you keep track of the name. Remember, the name doesn't have to be the same as the one you gave to the asset bundle, rather, is the name of the prefab itself you created before exporting.

Ok, I have another question: the prefab in my asset bundle is enormous, instantiating it takes 5 seconds, which hangs my game and breaks everything. Can I load it async?

Yes, you can load it async:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public void LoadPrefabAsync()
{
    StartCoroutine(LoadBundlePrefabAsync()):
}

    public IEnumerator LoadBundlePrefabAsync()
    {
        var bundleRequest = AssetBundle.LoadFromFileAsync("pathToTheAssetBundleFile");
        yield return bundleRequest;

        var bundle = bundleRequest.assetBundle;
        if (bundle != null)
        {
            var prefabRequest = bundle.LoadAssetAsync<GameObject>("Sphere");
            yield return prefabRequest;

            var prefab = prefabRequest.asset;
            Instantiate(prefab);
        }
    }

The method above is just the async version of the previous one, plus that simple function that creates the coroutine. This won't hang your game as the bundle is loading.

Conclusion

If you need to use asset bundles, this post is for you.
There's something I haven't mentioned yet, which is the asset bundle variant. That is the second input field you see in the inspector, which you can ssign via code but we left empty

1
AssetImporter.GetAtPath(f).SetAssetBundleNameAndVariant(name, "");

The documentation doesn't say much about it besides that you can have different variations of your bundle. If you assign a value, the name of your bundle file will have that value as the extension. I haven't experiemented much with it yet, so for now, I'll let you explore this on your own in the wilderness of the internet. Should I understand fully how to use it, I will update this post!

Sunday, 4 March 2018

2D Dynamic water with refraction effect.

I was always curious about how to get that distorted effect you see on water surfaces, which is something I have recently learnt how to do in OpenGL. I managed to create a water surface from scratch which can reflect and refract the environment.

I now want to try a similar experiment in unity: water, which can refract objects behind it.
Additionally, we are going to make the water dynamic using a similar method we use in the "Perlin noise for ocean waves post".

First thing first, create an empty GameObject and add these components to it: a script called "Water2D", a MeshFilter, a MeshRenderer and an EdgeCollider2D.

Then create a material. You can call it whatever you want of course, "Water_Refractive" sounds ok to me. You can leave the standard shader for now as we will firstly get the mesh to work properly and then we will apply a refractive shader. Assign the material to the MeshRenderer of our GameObject:



Part 1: dynamic mesh generation

The mesh is going to be re-created every frame. Certainly not the best approach if performance is your priority, but this will allow us to change the number of vertices during runtime, which is what I wanted. The script, previously added to the GameObject, called "Water2D", looks like this:


  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
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Water2D : MonoBehaviour {

 public int topVerticesCount = 10;
 public float waterHeight = 10;
 public float waterSpeed = 1;
 public float density = 50;
 public float amplitude = 5;

 Mesh mesh;
 MeshFilter m_filter;
 EdgeCollider2D m_collider;
 List<Vector3> topVertices;

 void Start () {

  topVertices = new List<Vector3> ();
  m_filter = GetComponent<MeshFilter> ();
  m_collider = GetComponent<EdgeCollider2D> ();
  mesh = new Mesh ();
 
 } 

 void Update () {

  UpdateTop ();
  GenerateMesh ();

 }

 void GenerateMesh()
 {
  List<Vector3> meshVertices = new List<Vector3>();
  List<Vector3> bottomVertices = new List<Vector3>();

  List<int> meshTriangles = new List<int>();
  List<Vector2> meshUVs = new List<Vector2> ();

  //Add bottom vertices
  for (int i = 0; i < topVerticesCount; i++) {

   float y = topVertices [i].y + waterHeight;

   bottomVertices.Add (new Vector3 (topVertices [i].x,topVertices [i].y - y,topVertices [i].z));  
  
  } 

  int totalVertivesCount = topVertices.Count + bottomVertices.Count;
  int totalTriangles = totalVertivesCount - 2;

  for (int i = 0; i < totalTriangles/2; i++) {
  
   meshVertices.Add (topVertices [i]);
   meshUVs.Add (new Vector2 ((float)i/topVerticesCount, 1.0f));

   meshVertices.Add (bottomVertices [i+1]);
   meshUVs.Add (new Vector2 ((float)(i+1)/topVerticesCount, 0.0f));

   meshVertices.Add (bottomVertices [i]);
   meshUVs.Add (new Vector2 ((float)i/topVerticesCount, 0.0f));


   meshVertices.Add (topVertices [i]);
   meshUVs.Add (new Vector2 ((float)i/topVerticesCount, 1.0f));

   meshVertices.Add (topVertices [i+1]);
   meshUVs.Add (new Vector2 ((float)(i+1)/topVerticesCount, 1.0f));

   meshVertices.Add (bottomVertices [i+1]);
   meshUVs.Add (new Vector2 ((float)(i+1)/topVerticesCount, 0.0f));
  
  }

  for (int i = 0; i < meshVertices.Count; i++)
   meshTriangles.Add (i);

  mesh.vertices = meshVertices.ToArray ();
  mesh.triangles = meshTriangles.ToArray ();
  mesh.uv = meshUVs.ToArray ();

  mesh.RecalculateBounds ();
  mesh.RecalculateNormals ();
  mesh.RecalculateTangents();
  m_filter.mesh = mesh;

 

 }

 void UpdateTop()
 {

  //Generate top vertices
  topVertices.Clear ();
  if (topVerticesCount < 2)
   topVerticesCount = 2;
  for (float i = 0; i < topVerticesCount; i++) {
   float y =amplitude *  Mathf.PerlinNoise ((i + Time.time * waterSpeed) / density, (i + Time.time * waterSpeed) / density);
   topVertices.Add (new Vector3 (i, y, 0));
  }

  //Update edge collider
  Vector2[] paths = new Vector2[topVertices.Count];

  for (int i = 0; i < paths.Length; i++)
   paths [i] = new Vector2 (topVertices [i].x, topVertices [i].y);

  m_collider.points = paths;
 }
}

If you are not familiar with meshes and how to create them there are plenty of tutorials online, especially on the official Unity website. To give you a quick crash course, a mesh is a collection of vertices and triangles which are drawn using those vertices. In this approach, I firstly create a line of horizontal vertices (which i called "topVertices"). The minimum amount for the top vertices is 2 of course (line 98 - 99). I then apply Perlin noise to the y parameter of each vertex in order to get them to wave (lines 100 - 103). The edge collider is also updated according to these vertices so objects can collide with the waves.

In the GenerateMesh method I simply "triangulate" the vertices: I create a "bottom vertex" for each "top vertex" and create triangles. The bottom vertices are placed at a certain distance from their respective top vertices. This distance is a public parameter (waterHeight)which basically determines the water height.

If you apply a basic color to the material and press play, you should see this:


Part 1: refraction effect

Indeed, it's shader writing time.

To create the refraction effect I used a special type of map called DUDV map. If you perform a quick google search for it you will find many of these maps to download. Save one and import it as a new asset.

Also, before proceeding, create a simple sprite and place it in the scene. Any image will do, we'll use it to show the refraction effect. I used one of the available sprites in Unity and tinted it red:




At this point, the shader itself. The code is very much based on the "Glass effect shader", which can be found here. I modified just enough to use the map I wanted to and added parameters for altering the effect.



  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
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
Shader "FX/Custom Water Distortion" {
Properties {
 _Color ("Tint", Color) = (1,1,1,1)
 _DistortionStrength  ("Distortion", range (0,15)) = 1
 _DistortionSpeedX  ("Distortion Speed X", range (-5,5)) = 0
 _DistortionSpeedY  ("Distortion Speed Y", range (-5,5)) = 0
 _MainTextureSpeedX  ("Main texture speed X", range (-10,10)) = 0
 _MainTextureSpeedY  ("Main texture speed Y", range (-10,10)) = 0
 _MainText("Main texture",2D) = "white"{} 
 _DistMap ("Distortion map", 2D) = "white" {}
}

Category {
 
 Tags { "Queue"="Transparent" "RenderType"="Opaque" }

 SubShader {

  //Create a texture with all that is on the screen behin our mesh.
  // This texture is accessible via the parameter _GrabTexture
  GrabPass {       
   Name "BASE"  
   }
  
  Pass {
   Name "BASE"
   Tags { "LightMode" = "Always" }
   
CGPROGRAM
#pragma vertex vert
#pragma fragment frag

#include "UnityCG.cginc"

struct appdata_t {
 float4 vertex : POSITION;
 float2 texcoord: TEXCOORD0;
};

struct v2f {
 float4 vertex : POSITION;
 float4 uvgrab : TEXCOORD0;
 float2 uvdist : TEXCOORD1;
 float2 uvmain : TEXCOORD2;
};

float _DistortionStrength;
float _MainTextureSpeedX;
float _MainTextureSpeedY;

float4 _DistMap_ST;
float4 _MainText_ST;
float4 _Color;

v2f vert (appdata_t v)
{
 v2f o;
 o.vertex = UnityObjectToClipPos(v.vertex);
 #if UNITY_UV_STARTS_AT_TOP
 float scale = -1.0;
 #else
 float scale = 1.0;
 #endif
 o.uvgrab.xy = (float2(o.vertex.x, o.vertex.y * scale) + o.vertex.w) * 0.5;
 o.uvgrab.zw = o.vertex.zw;
 o.uvdist = TRANSFORM_TEX( v.texcoord, _DistMap );
 o.uvmain = TRANSFORM_TEX( v.texcoord, _MainText );
 return o;
}

sampler2D _GrabTexture;
float4 _GrabTexture_TexelSize;
float _DistortionSpeedX;
float _DistortionSpeedY;
sampler2D _DistMap;
sampler2D _MainText;

float4 frag( v2f i ) : COLOR
{
 // Distortion map sampling. 
 half2 dist = tex2D(_DistMap, float2(i.uvdist.x + _Time.x*_DistortionSpeedX,i.uvdist.y - _Time.x*_DistortionSpeedY)).rg; 
 float2 offset = dist * _DistortionStrength * _GrabTexture_TexelSize.xy ;

 //Apply distortion to the grab texture by modifying the uv coordinates
 i.uvgrab.xy = offset * i.uvgrab.z + i.uvgrab.xy;
  
 float4 col = tex2Dproj( _GrabTexture, UNITY_PROJ_COORD(i.uvgrab)); 
 float4 mainColor = tex2D(_MainText,float2(i.uvmain.x + _Time.x * _MainTextureSpeedX,i.uvmain.y + _Time.x *_MainTextureSpeedY));

 return col * _Color * mainColor;
}
ENDCG
  }
 }



}

}

Simply described, the shader samples the distortion map, which represents a series of two dimensional vectors and creates an offset (using those vector 2) which is used to then sample the _GrabTexture (line 81 - 87). This is how the refraction effect is created.

This shader has parameters that let you move the textures and increase or decrease the distortion effect. Also, you can apply a normal texture if you want to.

Apply the shader to the material and this is the final result:



Conclusion

Water effect with refraction. A nice effect to add to your 2D game.
Remember, collisions are also updated, which means that objects can collide with your water. Specifically, with the top of the water, as we used an EdgeCollider.

If you place an object in the scene, with a 2D collider and a rigidbody, you should see it colliding with the water:




Tuesday, 9 January 2018

Tilemap: custom brush for prefabs.

Finally, a new post.

I haven't been using Unity much recently as I got sucked into OpenGL development, however, with the recent updates that came to Unity I was drawn back to it.

Being a big fan of 2D, I was excited to see the new features added, in particular, the tilemap system: a very easy and fast way to lay out 2D maps and create levels.

I then started messing around with custom brushes and I came up with a slightly modified version of the prefab brush that is provided in this tutorial project. I modified the PrefabBrush script in order to be able to select different "prefab palettes" and display the preview in the scene editor.

Also, for this tutorial I re-used this asset you have seen before.

To set it up, I created 4 prefabs in 2 different folders "Collectibles" and "Enemies". I have a "Coin" and a "Heart" prefab in the "Collectibles" folder and a "Slime" and "Bat" prefab in the "Enemies" folder. These prefabs are going to be GameObjects that we will place in the scene using the brush we are creating. The brush will allow you to choose which prefab palette you wish to use and iterate through the GameObjects of that particular palette.

Firstly we need to create the template for the prefab palette: it does not get any easier that this script down here:


[CreateAssetMenu]
public class PrefabPalette : ScriptableObject {
 public GameObject[] prefabs;

}

I kid you not, that is all you need.

This script will enable you to create an asset, which is really a file in which you can place your prefabs:



You can now create 2 prefab palettes, one for the enemies, one for the collectables.

We can now take a look at the PrefabBrush script. I will highlight the modifications I made. It is important to notice how the script is placed inside a folder called "Editor", that is because it incorporates editor scripting as well and it must be inside that folder.


  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
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102


namespace UnityEditor
{
 [CreateAssetMenu]
 [CustomGridBrush(false, true, false, "Prefab Brush")]
 public class PrefabBrush : GridBrush
 {

  public PrefabPalette prefabPalette;

  [HideInInspector] public int index = 0;

  public override void Paint(GridLayout grid, GameObject brushTarget, Vector3Int position)
  {   

   GameObject prefab = prefabPalette.prefabs[index];
   GameObject instance = (GameObject) PrefabUtility.InstantiatePrefab(prefab);
   Undo.RegisterCreatedObjectUndo((Object)instance, "Paint Prefabs");
   if (instance != null)
   {
    instance.transform.SetParent(brushTarget.transform);
    instance.transform.position = grid.LocalToWorld(grid.CellToLocalInterpolated(new Vector3Int(position.x, position.y,0) + new Vector3(.5f, .5f, .5f)));
   
   }
  }

  public override void Erase(GridLayout grid, GameObject brushTarget, Vector3Int position)
  {
   // Do not allow editing palettes
   if (brushTarget.layer == 31)
    return;

   Transform erased = GetObjectInCell(grid, brushTarget.transform, new Vector3Int(position.x, position.y, position.z));
   if (erased != null)
    Undo.DestroyObjectImmediate(erased.gameObject);
  }

  private static Transform GetObjectInCell(GridLayout grid, Transform parent, Vector3Int position)
  {
   int childCount = parent.childCount;
   Vector3 min = grid.LocalToWorld(grid.CellToLocalInterpolated(position));
   Vector3 max = grid.LocalToWorld(grid.CellToLocalInterpolated(position + Vector3Int.one));
   Bounds bounds = new Bounds((max + min)*.5f, max - min);

   for (int i = 0; i < childCount; i++)
   {
    Transform child = parent.GetChild(i);
    if (bounds.Contains(child.position))
     return child;
   }
   return null;
  }
 
 }

 [CustomEditor(typeof(PrefabBrush))]
 public class PrefabBrushEditor : GridBrushEditor
 {
  private PrefabBrush prefabBrush { get { return target as PrefabBrush; } }
   
  GameObject holder;

  public override void OnMouseLeave ()
  {
   base.OnMouseLeave (); 

   if(holder)
    DestroyImmediate (holder);
  }

 
  public override void OnPaintSceneGUI (GridLayout gridLayout, GameObject brushTarget, BoundsInt position, GridBrushBase.Tool tool, bool executing)
  {

   base.OnPaintSceneGUI (gridLayout, null,position, tool, executing); 

   Event e = Event.current;

   if (e.type == EventType.KeyDown && e.keyCode == KeyCode.C) {
    //Debug.Log ("Click, index " + prefabBrush.index);

    prefabBrush.index++;
    if (prefabBrush.index == prefabBrush.prefabPalette.prefabs.Length)
     prefabBrush.index = 0;

    DestroyImmediate (holder);
    holder = Instantiate (prefabBrush.prefabPalette.prefabs [prefabBrush.index]);

   }

   if (!holder)
    holder = Instantiate (prefabBrush.prefabPalette.prefabs [prefabBrush.index]);

   holder.transform.position = position.position + new Vector3(.5f,0.5f,0.0f);

  }  

 }
}

Up to line 53 is the "normal" script, the actual PrefabBrush class. From line 53 onward is the custom editor script.

The PrefabBrush class is pretty much the same as it was in the original script. The method Pain is overridden in order to instantiate the currently selected prefab. This prefab is chosen according to an int value, which is the index of the array of GameObjects found in the PrefabPalette public object. The other 2 methods are straight from the original script and override erase functionality in order to delete the instantiated prefab rather than the underneath tiles that might be present in the tilemap.

The custom editor part is where I put my hands mostly. In order to show the "preview" of the prefab in the scene editor I used a trick: I instantiate an object of the selected prefab (the object holder in the script) and I constantly update its position. OnMouseLeave is called whenever the mouse leaves the editor window. In this method, I delete the holder gameobject so it is no longer present in the scene.

The method OnPaintSceneGUI is continuously called as long as the mouse is in the scene editor window. Here I do a couple of things: I check that the key "C" is pressed: if so, the value of index is incremented (and checked for boundaries), so the prefab will change. If that occurs, I delete the current holder and I create a new one with the new selected prefab from the prefab palette.

I also create a holder object anyway (line 90-91), as the "preview" of the GameObject must be present even if the key "C" is not pressed. I finally update the position of the holder object to follow the value of position which is the cell in the tilemap the mouse is currently hovering on.

At this point, we simply need to create a Prefab Brush object in the project folder and it will then be available in the Tile Palette window:




We can then select the prefab palette we created and start placing prefabs!






Conclusion

The new tilemap feature is fantastic if you are planning to create a 2D game and quickly need to draft levels.

It is important to remember:

- Always separate different tiles/prefabs in different tilemaps: just like in the example above, enemies prefabs are placed in a different tilamap than collectibles.

- Make sure to select the actual tilemap you wish to draw on in the Tile Palette window:


It's easy to forget that and place all the tiles/prefabs in one single tilemap.

- Check the tool you are using: occasionally, the "erase" tool remains selected and it's easy to get confused on "why is it not painting??". Silly mistakes, but can happen.

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.

Sunday, 6 November 2016

Perlin noise for ocean waves

Apologies for the tardiness of this post, I was busy the last few weeks as I was doing some work experience with a local software company. I had a chance to learn a few things about game development on consoles using C++.

Meanwhile, I put together a quick example on how to use Perlin noise to create basic waves in order to simulate ocean movement. The example you will see also allow to modify specular highlights, which was part of the project I was working on for the aforementioned company. The water texture came form here.

This is a picture of the final result:



Setting up

Drag in several planes and places them one next to each other and create a larger plane with all those combined of whatever size you want. 

For the shader, I used a slightly modified version of a standard pixel lighting shader, which you can find here. This post is about the wave movement, so I will not post anything about the shader itself, also because I pretty much used the one I linked.

The "Wave" script

We only need one script and, just like we previously saw in the other project which involved Perlin noise, it's very easy.


 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
42
43
44
45
46
47
public class Waves : MonoBehaviour {

 public float scale = 10.0f;
 public float speed = 1.0f;
 public float resolution = 1;

 Mesh mesh;
 MeshCollider mc;

 Vector3[] vertices;
 Vector3[] newvertices;

 float randomizer = 1.0f;

 void Start () {

 
  mc = GetComponent<MeshCollider> ();
  mesh = GetComponent<MeshFilter> ().mesh; 
 
 }
 

 void Update () {

  randomizer = -Time.time/2;
  vertices = mesh.vertices;
  newvertices = new Vector3[vertices.Length];

  if (resolution <= 0)
   resolution = 0;


  for (int i = 0; i < vertices.Length; i++) {

   Vector3 ver = vertices [i];
   ver.y = Mathf.PerlinNoise ( (speed * randomizer) +(vertices [i].x + transform.position.x) / resolution, -(speed * randomizer) + (vertices [i].z+ transform.position.z) / resolution) * scale;
   newvertices [i] = ver;

  }
                mesh.vertices = newvertices
  mc.sharedMesh = mesh;
  mesh.RecalculateBounds ();
  mesh.RecalculateNormals ();
 
 }
}

The 3 public variables will allow you to change the waves a bit, as you will see later.

The "randomizer" float parameter is nothing but a time variable.

As we can see, in the Start method we simply get our plane mesh and the mesh collider.
 We can choose whether to update the mesh collider or not and we'll see what difference will make in a moment.

In the Update method we update the randomizer variable first. We then get the vertices of the current mesh and create a new array of Vector3 which will store the new vertices. In the for loop we simply iterate through the vertices of the mesh and assign a new y value to them, in order to create height. This new vertex is then places in the recently created array which, at the end of the for loop, will replace the current mesh vertex array (line 41). On the next line we update the mesh collider as well, based on the just updated plane mesh.

If we were to omit line 42, which updates the mesh collider, this is what we will see in the scene editor:


Even though the plane mesh has waves, the collider is still flat. Obviously, that happens because we did not update the mesh collider which, currently, is still using the standard plane mesh.

If we assign the new plane mesh (with waves) to the mesh collider, the collider itself will match the shape of our plane.

Conclusion

This is an easy way to create ocean movement. Using the standard planes provided by Unity is no the best idea, we should use ocean meshes, which normally have much greater vertex density which allow for much smoother undulation.

Example scene here.