-

UnityEvents - Differentiating Between Multiple Component Instances

16 Jan, 2016Ariel Schoch

UnityEvents offer a convenient way of adding persistent callbacks at design time through the inspector and help keep your scripts modular.
One of the biggest drawbacks however, is that there is no support for picking a specific target instance when a game object has multiple components of the same type.

After searching for solutions and checking the Asset Store for alternatives I decided to write my own implementation.
The fancy visuals of the standard UnityEvent are done using the undocumented ReorderableList class and using a GenericMenu for the component selection.
Simply numbering duplicate components in the dropdown solves the original problem.

To avoid using reflection at runtime I created a QuickEvent class that implements the ISerializationCallbackReceiver, using the OnAfterDeserialize method to take the callback info and create a delegate from it.

A nice article comparing the performance of delegates versus MethodInfo.Invoke () can be found here.

Someone posted this issue over on the Unity Feedback page where you can upvote their suggestion.
My own implementation of the UnityEvent class can be found on the
Asset Store or over on Bitbucket.


Read More

Flocking Algorithm Optimization

25 Aug, 2015Ariel Schoch

I often encounter a situation where I need all the objects in a scene to react to the positions of all other objects. One such example is when simulating flocking behaviour of hundreds of agents.

If we have n objects (or 'agents') in our scene, then every one of those n agents will need to check its position against every other agent.
In other words we will need to do comparisons.
If we have four agents which we name a, b, c, and d, then the required position comparisons would look something like this:

aa, ab, ac, ad, ba, bb, bc, bd, ca, cb, cc, cd, da, db, dc, dd
Thats 16 checks, giving us the running time: $$T(n) = O(n^2)$$ However, since checking the distance between two positions will be the same regardless of the direction we measure from, we can assume that ab = ba and so on.
aa, ab, ac, ad, ba, bb, bc, bd, ca, cb, cc, cd, da, db, dc, dd
So the new amount of checks we need to do equals: $${n(n+1)} \over {2}$$

In addition, we won't have to check agent positions against themselves, so we can get rid of aa, bb, cc, and dd.

aa, ab, ac, ad, ba, bb, bc, bd, ca, cb, cc, cd, da, db, dc, dd

Which is equivalent to summing all the numbers from one to n minus one. $$1+2+..+(n-1) = {n(n-1) \over 2}$$

The resulting loop:

for (int i = 0; i < n; i++)
{
    for (int j = i; j < n; j++)
    {
        // skip if we are checking against ourselves
        if (i == j)
            continue;
        // pass direction vector info to agent[i] and agent[j]
        Vector3 dir = agent[i] - agent[j];
        agent[i].AddDirection (dir);
        agent[j].AddDirection (dir * -1);
    }
}
Notice that the integer j of the inner for loop is set to i, so that as i increases the number of iterations of the inner loop decreases. In the above demo this improved performance noticeably compared to the standard checks.

Read More

Visualising Functions And Data Using Graphs

18 Aug, 2015Ariel Schoch

Graphs are the standard way of visualising functions and are useful when debugging the return values of a method, given a specific input value.
An alternative to creating a graph system from scratch within Unity or finding an existing solution on the asset store, is to search for browser based solutions such as chart.js.

One of the benefits to this approach is that it isn't application dependant, meaning you can later use the same graph system when working outside of Unity. You can also easily style and modify things inside of a browser using simple css and html.
On the other hand, using this setup is clearly not suited for realtime plotting of in game values.

The way that the data is passed to the graph is either with a GET request (data is added in the url) or with a POST request (useful for large amounts of data).
Here's an example of a graph, passing the data in the url:
www.bytesheepdigital.com/graphs/?data=0,-1,0,-1,0,-1,0,-1,0,-1,0,1,0,1,0, 1,0,1,0,1,0&labels=-10,-9,-8,-7,-6,-5,-4,-3,-2,-1,0,1,2,3,4,5,6,7,8,9,10

f(x) = x % 2 with input range (-10, 10)

The code on the Unity side of things is rather simple, all you need to do is create a string of comma separated values ("1,2,3,4,...") from the output of the function you want to graph.

public float Mod (float _dividend, float _divisor)
{
	return _dividend % _divisor;
}

public void GetData (float _min, float _max, float _step)
{
	int steps = Mathf.FloorToInt ((_max - _min) / _step) + 1;
	float[] data = new float[steps];
	float[] labels = new float[steps];
	
	for (int i = 0; i < steps; i++)
	{
		float x = _min + i * _step;
		labels[i] = x;
		data[i] = Mod (x, 2f);
	}
	// Load graph using data we just collected
	LoadGraph (data, labels);
}

// Open URL containing graph data
public void LoadGraph (float[] _data, float[] _labels)
{
	string url = "http://www.bytesheepdigital.com/graphs/?";
	Application.OpenURL (url + "data=" + ArrayToParameters (_data) + "&labels=" + ArrayToParameters (_labels));
}

// Return a string of comma separated values
public string ArrayToParameters (float[] _array)
{
	StringBuilder sb = new StringBuilder ();
	for (int i = 0; i < _array.Length; i++)
	{
		sb.Append (_array[i].ToString ());
		// Don't add comma to last element
		if (i < _data.Length - 1)
			sb.Append (",");
	}
	return sb.ToString ();
}

The webpage itself can be hosted on a server or simply be a html file on your local machine and will probably look something like this:

<!-- Read data from URL and pass it to chart.js framework to draw a chart on the canvas object -->
<html>
   <head>
      <title>Graph</title>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/1.0.2/Chart.js"></script>
      <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
      <script>
         
         // Return parameter value or true if parameter exists but has no value
         function getUrlParameter(sParam) {
             var sPageURL = decodeURIComponent(window.location.search.substring(1)),
                 sURLVariables = sPageURL.split('&'),
                 sParameterName,
                 i;
         
             for (i = 0; i < sURLVariables.length; i++) {
                 sParameterName = sURLVariables[i].split('=');
         
                 if (sParameterName[0] === sParam) {
                     return sParameterName[1] === undefined ? true : sParameterName[1];
                 }
             }
         };
         
         // Graph settings
         var data = {
            labels: [],
            datasets: [
               {
                  label: "Debug data",
                  fillColor: "rgba(151,187,205,0.2)",
                  strokeColor: "rgba(151,187,205,1)",
                  pointColor: "rgba(151,187,205,1)",
                  pointStrokeColor: "#fff",
                  pointHighlightFill: "#fff",
                  pointHighlightStroke: "rgba(151,187,205,1)",
                  data: []
               }
            ]
         };
         
         $(function() {
            // When document has loaded get the graph data from the url parameters
            var params = getUrlParameter('data').split(',');
            var labels = getUrlParameter('labels').split(',');
            drawGraph(params, labels);
         });
         
         function drawGraph(_params, _labels) {
            // Get context of canvas with jQuery - using jQuery's .get() method
            var ctx = $("#myChart").get(0).getContext("2d");
            // Check whether to use bezier curves
            var bezier = getUrlParameter('bezier');
            // Create a new chart
            var myChart;
            if(getUrlParameter('bar'))
               myChart = new Chart(ctx).Bar(data, { animation: false, responsive: true });
            else
               myChart = new Chart(ctx).Line(data, { bezierCurve: (bezier == true) ? true : false, animation: false, responsive: true });
            
            // Add each point to the graph
            _params.forEach(function(entry, index) {
               if(!_labels[index])
                  _labels[index] = index;
               myChart.addData([entry], _labels[index]);
            });
         }
         
      </script>
   </head>
   <body style="text-align:center;">
      <div style="margin:auto;">
         <canvas id="myChart"></canvas>
      </div>
   </body>
</html>

And finally some more examples:

f(x) = sin(x) with input range (-4, 4)

Bar graph

Read More

Calculating Flat Shaded Water Normals

10 Jul, 2015Ariel Schoch

To give each surface of a mesh a solid colour, every triangle needs to have it's own set of vertices.
The reason for this is due to the fact that normal data is stored on a per vertex basis, meaning that shared vertices between two triangles will create an unwanted smooth edge.

Thus, when creating a water mesh we need to make sure a quad consists of six vertices. This is where the problem is introduced however;
Since we will be modifying the vertex positions at run-time, we will also need some way to recalculate the normals of each vertex in order for our mesh to be shaded correctly.
There are two different ways of modifying our water:

  • Attaching a C# script to our mesh that loops through our vertices
  • A custom shader that offsets vertex positions

In this case we will be using the second method since our shader already has to access each vertex, meaning that there is almost no extra cost to offset a vertices position before passing it to the fragment function.
So far so good, but back to the problem on how to recalculate the triangle normals. Our vertex function isn't aware of any other vertices and thus we can't tell which triangle this vertex is a part of.

This means we have to take the two adjacent vertices we think are connected and create a normal from that.
Of course, this doesn't entirely work because we need to calculate the normal between three very specific verts: our current vertex and the two connected ones forming this triangle of the mesh.

At this point you may be thinking:
Never mind we can just create a c# script, offset the positions there and then finally call Mesh.RecalculateNormals() which will automatically calculate the correct normals for the modified mesh.

This works and is the simplest solution, however the performance hit of recalculating all the normals every frame using this method is quite considerable, especially if you have multiple water planes in your scene at the same time (this can easily halve the frame rate).

OK so back to our custom water shader - we need some way of marking our vertices to show which corner of a triangle they belong to.
What better way than to simply use vertex colours to pass this info to the shader!

Vertex colors are represented as float4 values ranging from 0 to 1, which means we will need to be able to identify each of the six unique vertex positions in a quad through the use of floats. In this case we will only use the red channel, assigning red color values to our quads as follows:

Now we can simply check the color of the vertex we are processing in our vert function and based on that know where its two neighboring verts must be
(e.g. a value of 0.2 would tell us we are at the top left point of a triangle).
To avoid using conditional statements in our shader code, we can turn our float color values (0, 0.2, 0.4, ..) into array indices (0, 1, 2, ..):

int index = round(v.color.r * 5);

This allows us to create an array containing the relative positions of neighbors, meaning we don't need to write code for each of the six scenarios.
Once we know all three corner positions of a triangle we can easily calculate the normals as well.
That got a bit complicated towards the end, so here's the project files if you want to look at the shader source.

Read More