Debugging context in Unity3D

| | TrackBacks (0)

A cool little feature of Unity3D scripting I recently found is using conditional compilation for debug statements. The C based languages (but not Java unfortuately) have inbuilt preprocessors that allow developers to only compile certain parts of the code if certain parameters are set (it can also do much more, including macros). Thus you could write the below and the code would only be included in the executable if DEBUG is defined.

#if DEBUG
    // do some special code
#endif

Unity3D has a similar system. Mostly the docs (available here) seem to suggest this is for specialist code for different platforms: iPhone, Web, PC, etc. Thus the defined parameters are set in advance (e.g. UNITY_IPHONE). However, there is one that is useful for debugging, UNITY_EDITOR. This will only be set if the application is running inside the Unity3D GUI. Once you build the app those lines of code will be skipped. It can be used like the below example - very handy.

#if UNITY_EDITOR
    Debug.Log("Debugging");
#endif 

According to this Unity3D forum post it is also possible to define your own project specific preprocessor parameters.

I have been programming mainly in Unity3D over the last fortnight, and thought it time to write some unit tests for my work. I found reference to a few testing frameworks online. The most promising appeared to be SharpUnit, and it is also released under a fairly relaxed license. However, I had to make a few changed to get it running, so this post details that work.

SharpUnit works like a standard unit testing framework for C# code in Unity3D (although all output goes to the console, but that is fine for me). Although my work has largely been in UnityScript (Unity3D's JavaScript like language) and this didn't work so well due to issues with compilation order and SharpUnit's use of .Net custom attributes. After making the changes below, I found SharpUnit works well, and I'll probably be using it for my unit testing.

Unity3D compiles scripts in a set order (as described here). Thus if you have C# code that references a UnityScript class there can be a problem - although the reverse is not such an issue. When testing UnityScript with SharpUnit this means that the C# Unity3D_TestRunner class can be an issue. It references the TestCase classes, which I want to write in UnityScript. To do this I put the SharpUnit code in a folder under the Plugins top-level folder (the Standard Assets folder would have also worked). This means the SharpUnit code is compiled first. Then my tests went in a new folder together with a UnityScript rewrite of Unity3D_TestRunner, as shown below. This UnityScript TestRunner script is then attached to the TestRunner GameObject (in a special testing scene) rather than the C# version as described in the SharpUnit instructions.

#pragma strict

import SharpUnit;

function Start () {
	var suite: TestSuite = new TestSuite();
	suite.AddAll(DummyTest());
	var res: TestResult = suite.Run(null);
	var reporter: Unity3D_TestReporter = new Unity3D_TestReporter();
    reporter.LogResults(res);
}

Other possible solutions to this problem would be to just write my code in C# or to change SharpUnit to load TestCase classes dynamically at runtime. Rewriting the TestRunner seemed easiest.

The other problem encountered is that SharpUnit uses the C# custom attribute [UnitTest] to identify its test methods. The equivalent in UnityScript is a script directive (see the relevant section here). Thus @UnitTest needs to be placed at the top of each test function.

After that change I could run UnityScript tests with SharpUnit. An example test is below:
#pragma strict

import SharpUnit;

class DummyTest extends TestCase {

    var dummy: String;  

    /** Setup test resources, called before each test. */
    function SetUp() {
        dummy = "lkjkjh"; 
    }

    /** Dispose of test resources, called after each test */
    function TearDown() {
        dummy = null; 
    }

    /** Sample test that passes */
    @UnitTest
    function TestDummy_Pass() {
        Assert.NotNull(dummy);
    }

    /** Sample test that fails. */
    @UnitTest
    function TestDummy_Fail() {
        Assert.Null(dummy);
    }
}

Sicily

| | TrackBacks (0)

I've just returned from an underwhelming two week break in Sicily. A Flickr slideshow is viewable here.

Rio Rambo Course

| | TrackBacks (0)

The most oft repeated tale of my time at Rio Tinto R&TD is the Rambo course. Every year a dozen or so staff would be sent on a management training course. Many people were eager to avoid it. Although everyone had to do it at least once and it was advisable to go if you wanted to be promoted (so I was keen). Most people had been multiple times. Since our team was so small, about a third of the facility went each year.

Failure: Unity Javascript in Java VM

| | TrackBacks (0)

School science teachers like to emphasize that the failure of an experiment is as useful a learning experience as its success. Of course the high-achieving student knows that is bollocks and ensures their experiements always succeed - if you need a straight line, only gather two data points! Despite that I think the underlying idea is correct and experimental failure should not be hidden.

A recent experiment of mine failed. I am writing a game with an online component and wanted the same code running on both the client and server - as this would reduce the chance of bugs. For ease of development Unity3D is being used for the client end, so the code there is either Javascript or C#. I have been writing Java servers professionally for years so it seemed silly not to use that knowledge. Clearly there is no common ground there. However, there is a project called Rhino to allow Javascript to run inside the Java JVM.

So can Javascript from Unity run in a Java environment? The short answer is "no", but the longer answer is "sometimes, with many caveats".

Firstly it is possible to run Javascript functions in Java. Load up the Rhino library into the classpath and if you have Javascript code like:
function square(i) {
	return i*i;
}
then you can run it in Java with the folowing:
Context cx = Context.enter();
Scriptable scope = cx.initStandardObjects();
Function f1 = cx.compileFunction(scope, readFile("src/test_fn.js"), "<cmd>", 1, null);
System.out.println(f1.call(cx, scope, f1, new Object[] {5}));
So far so good. If your Unity Javascript is just a collection of simple functions then this is all that is required. However, there is a good chance this is not the case. Obviously any Unity specific functions and classes are not available (eg Ray, Input, etc). However, more important are the big differences between Unity Javascript and standard (ECMA) Javascript.

Many people say that Javascript in Unity should more accurately be referred to as UnityScript. I think that is definitely true. Although it looks like Javascript there are some fundamental changes. Most importantly, Javascript uses a prototype-based scheme to define classes. UnityScript has an inheritance model, and the two schemes are not compatible. To me these differences are enough to prevent want I wanted to achieve.

Lost Code

| | TrackBacks (0)

Recently at work I was asked to find out if anyone was still using a small tactical program I wrote. If it was unused, they apologetically asked me to shut it down. No apology was required, but I understand why it was offered. Some developers can become quite precious about their code and want to see it keep running forever. However, after working in this fast-paced industry for nearly 15 years I have long accepted that most of my code is no longer running. I'd be lucky if most of it is even still backed up somewhere rather than being sent to /dev/null.

Four of my old teams were shutdown with near total loss of code. Another two years were spent at a consulting firm where most of the clients I serviced no longer exist. Those 5 jobs combined mean that half of my working life's production is largely gone. At a guess I would say 10% of the code from my first five working years is still used; around 30% from the second five years; and, over two-thirds of the last 5 years of my code is currently running. My professional code appears to have a half-life of around 4 years. This sounds about right considering the rate of technology change.

Line / Plane Intersection in Unity3D

| | TrackBacks (0)

In a recent Unity3D game prototype I needed to determine the point of intersection between a line and plane. A player would click on the screen where they wanted their spaceship to go, and I needed to work out where that actually was in the world coordinates. The line is defined by the camera point and the point on the screen clicked (as given by camera.ScreenToWorldPoint). The plane was the spaceship (assuming no up or down movement).

If the line was intersecting an object in the scene then this could be easily achieved with Unity3D's built in functions. In this case by casting a ray from the camera and finding the objects it hits (see the Raycast help page). However, my prototype is set in space, there is no object there to hit other than a small spaceship.

First, let's work out the equation of the line from the camera (point 'c') and the mouse click (point 'm'). We can get the position of the camera with var c = camera.transform.position and the position of the mouse click with:
var s = camera.ScreenToWorldPoint(Vector3(Input.mousePosition.x, Input.mousePosition.y, 100.0)); 
Note that in the above, the third vector parameter (the Z value) in the ScreenToWorldPoint call is the distance from the camera of the resulting point. Thus if 0 or mousePosition.z (which is also 0) is used then the resulting point is just the camera location itself. Here I have used a distance of 100 to make sure there is a good separation between the points describing the line. The general equation of a line in 3 dimensions is (for the points c & m):
[c.x + (m.x - c.x) * t, c.y + (m.y - c.y) * t, c.z + (m.z - c.z) * t]
So to find the point of intersection requires solving that simultaneously with the equation of the plane. Here is where my scenario gets easy. The plane this line intersects with in my game is the horizontal plane the spaceship is current only. That is the plane is described by:
y = spaceship.y
At some point on the line the following holds as long as the line intersects at some point:
spaceship.y = c.y + (m.y - c.y) * t
which means:
if (m.y == c.y) {
   t = 0; // no intersection
} else {
   t = (spaceship.y - c.y) / (m.y - c.y); 
}
The case m.y == c.y denotes the case where the line does not intersect the plane. What happens here is up to you. With the value of 't' known the equation for the line can be solved to give the point of intersection. Done.

La Trobe University Archaeology

| | TrackBacks (0)

Available from iTunes or from the La Trobe University Podcasts page (but they will have to be found individually as there does not appear to be a series webpage).

This is a series of talks and interviews from the La Trobe University Archaeology department. The topics vary wildly, from the traditional Ancient Egypt and Cyprus, to relatively modern digs in Melbourne city (founded in 1835). As the series is archaeologically focussed there is more emphasis on the extraction and identification of objects than history. At present there are 12 episodes - 2 are short videos (around 3 minutes) and the remainder are audio only. Two of the audio podcasts are hour long scholarly presentations, while the other 8 are interviews of between 15 to 20 minutes duration. There may be more episodes in the series as the entries so far have trickled in over the last 2 years. Production quality is high for the whole series.

There are two podcasts on archaeology in Oceania, particularly Hawaii. One is an academic presentation, while the other is an interview with the same presenter on the same topic. There is some talk on Jared Diamond's Collapse book. Diamond suggests that society on the Polynesian island Mangareva due to overforestation. However, the presenter's research suggests instead that the deforestation was caused by a lack of phosphorus in the soil due to the killing of sea birds. The discussion of Hawaii is similarly focussed on the affect of intensive cultivation on soil quality. Unsurprisingly the indigenous people started agriculture on the best land and as population grew, expanded out to less productive land. Once all arable areas were cultivated, the land became more subdivided and population growth slowed. Also there is evidence of political consolidation beginning around the same time.

Another paired presentation and interview discuss excavations at the ancient Egyptian capital Amarna. They are focussed on the industry of the time - in particular small metal work and glazed pottery. Details of the materials used are probed with synchrotrons and a bit of experimental archaeology is conducted to determine the techniques used to manufacture them.

Another episode details the search for ships lost in Vietnam by the Chinese emperor Kublai Khan at the Battle of Bach Dang. Three episodes (including both video podcasts) discuss the tools and evolution of early humans, especially the position in the evolutionary tree of some complete skeletons recently found in Sediba. There is also an interview about bronze age burials in Cyprus and other podcasts focus on the archaeological difficulties and discoveries underwater or in cities.

NavigableMap & time-based caches

| | TrackBacks (0)

NavigableMap

I often find myself writing time-based caches. Something like storing the last X hours of ticks from a data feed. Most teams I join usually have a utility class to help with such tasks. In a recent move I joined a team that didn't have such a utility, but did have a need for such a cache. Before coding, I thought I do a quick check to see if anything new had come along to help. At this point I discovered the Java 6 NavigableMap interface.

NavigableMap defines a type of sorted map with handy methods for obtaining submaps. So to get a map of the entries greater than a particular value use the tailMap method. The corresponding method for the submap less than a value is headMap. These methods return a view on the original map. So changes to the submap are reflected in the original map. The JDK provides concurrent and non-concurrent implementations of the interface.

This is very useful for a time-based cache. Create a NavigableMap sorted on the time (I used the epoch milliseconds returned by System.currentTimeMillis()) then tailMap returns the entries younger than a given time, and headMap those older. So to trim the cache of entries older than a certain time use headMap(System.currentTimeMillis() - maxAge).clear(). So easy!

Here is a complete time-based cache class for use as an example.

public class TimeSeriesCache<V> {

    private static final int MILLIS_IN_MINUTE = 60 * 1000;
    private static final int CLEAR_OLD_PERIOD_MINUTES = 1;

    private final NavigableMap<Long, V> series = new ConcurrentSkipListMap<Long, V>();
    private final Set<TickListener<K, Long, V>> subscribers;

    public TimeSeriesCache(int maxAgeMinutes) {
        if (maxAgeMinutes > 0) {
            final int maxAge = maxAgeMinutes * MILLIS_IN_MINUTE;
            Executors.newSingleThreadScheduledExecutor().scheduleWithFixedDelay(
                    new Runnable() {
                        @SuppressWarnings("boxing")
                        @Override public void run() {
                            series.headMap(System.currentTimeMillis() - maxAge).clear();

                        }
                    }, CLEAR_OLD_PERIOD_MINUTES, CLEAR_OLD_PERIOD_MINUTES, TimeUnit.MINUTES);
        }
    }

    public Collection<V> getSeriesAfter(Long fromTimestamp) {
        return series.tailMap(fromTimestamp).values();
    }

    public void add(V value) {
        series.put(System.currentTimeMillis(), value);
    }

    public void add(Long timestamp, V value) {
        series.put(timestamp, value);
    }
}

Kuala Lumpur

| | TrackBacks (0)

I'm not sure how many people who read this blog actually know me in person - probably very few. However, for those that do, I am moving. Towards the end of the year (the exact date is still uncertain), I'm physically moving to Kuala Lumpur. Virtual location (email, website, etc) will remained unchanged. It is not yet known what I'll be doing there - there are some work visa issues to be sorted out.

Categories