Steve Exploring Stuff: October 2011

2011-10-05

Gotchas: Unity Plugins for Multiple Platforms

As mentioned in my last post, I was surprised by some of the platform compatibility issues I experienced with the CAINav code. In this post I'll cover some of the issues and how they can be resolved.

A quick side note before getting into the main topic: The first sample pack that includes source code has been released. It includes various demos and feature explorers that will help you get started using CAINav.

The Basics

I'm not going to cover the basics of Unity plugin development. That is covered well by the Unity documentation. There is nothing surprising on the C++ side. Just make sure you include the necessary conditional compile macros for platform specific code (e.g. "__declspec(dllexport)" for Windows) and don't use platform specific syntax (e.g. nullptr).

Special Cases for the iOS Platform

While you may be aware that Unity provides wide, but still limited support for the standard .NET libraries, you may not be aware that there are differences in the support provided for the different platforms. This is especially true for iOS.

Lets start with a simple plugin method that will work for the desktop and Android platforms.

[DllImport("cai-nav-rcn", EntryPoint = "dtnqFree")] 
public static extern void FreeEx(ref IntPtr query);

The DLLImport attribute indicates the name of the plugin (cai-nav-rcn) and remaps its name to a C# friendly version (dtnqFree to FreeEx). In this case the method frees a query object's unmanaged resources.

Issue #1: iOS does not support remapping the name of the extern method. All method names must match the C function name. So the the signature must be simplified to:

[DllImport("cai-nav-rcn")] 
public static extern void dtnqFree(ref IntPtr query);


Issue #2: iOS requires the custom plugin name __Internal. This makes the iOS signature incompatible with the other platforms.

One easy way to handle this is using conditional compilation as follows:

internal struct InteropUtil 
{ 
#if UNITY_IPHONE && !UNITY_EDITOR 
    public const string PLATFORM_DLL = "__Internal";
#else 
    public const string PLATFORM_DLL = "cai-nav-rcn";
#endif 
}

This allows standardized method signatures throughout the rest of the code. Our example is altered as follows:

[DllImport(InteropUtil.PLATFORM_DLL)] 
public static extern void dtnqFree(ref IntPtr query);


Issue #3: With compile time differences between platforms, how do you deploy the C# code?

Due to the incompatible code, just as with the plugins, you an no longer use a single pre-compiled .NET DLL for all platforms. There may be other options, but the easiest is to drop the use of pre-compiled .NET DLL's and distribute the C# code directly to the Unity project. Unity will then decide which type of build to perform.

Since CAINav targets the Windows platform, I chose a different method. The main Unity package contains pre-compiled DLL's, but the source code is organized so the C# code can be easily dropped directly into a Unity project.

Oh No, What About Serialization

If you are using Unity serialization for everything, then there is no problem. But that may not be possible. Often a plugin object contains references to unmanaged memory. Also, Unity doesn't support serialization of custom structures or unsigned primitives such as uint, ushort, etc.

You may be tempted to implement custom serialization based on the ISerializable interface. But that isn't cross-platform compatible since your iOS platform uses a different run-time DLL than the rest of the platforms. So you can't for example, serialize a Navmesh object in a Windows project and share it with an iOS project.

Since CAINav supports standalone use in .NET applications, it supports two serialization methods. The standard ISerializable interface, plus byte array serialization. Byte arrays can be serialized using the standard .NET serialization process and shared between all platforms.

An example of this is the Navmesh class. The Navmesh.GetSerializedMesh() method returns byte[]. This byte array can be serialized using either Unity or .NET serialization. One of the overloads of Navmesh.Build() can be used to re-create the navigation mesh from a byte array. This is how the BakedNavmesh component handles the serialization issue.

I'm sure there are other gotcha's. But this post should help you avoid some of the headaches involved in cross-platform plugin development.

2011-10-01

CAINav 0.3.0: Path Corridors and More

CAINav v0.3.0 has been released. It is a 'big little' update.  There aren't a lot of new features, but the underlying structure has changed a lot.  

The full change log can be found in both the distribution package and the SVN repository.  But there are several changes worth mentioning here.

Two Boring Items

The NavManager Unity component has been retired and replaced with the NavSource component.  This is worth mentioning here because the functionality of the two components appears to be the same.  And they are pretty much 99% the same.  The difference is that NavSource only provides navigation resources.  It does not handle the CrowdManger.Update() method.  So if you use it you'll need to make sure you add code somewhere to manage the crowd manager. 

The documentation for the last release had some big holes, especially for the crowd manager and introduction to navigation.  That has been mostly addressed.  There is even some navigation related getting started code to hold you over until the new sample pack is released.

The New PathCorridor

The biggest new feature is the addition of the PathCorridor class.  (And U3DPathCorridor Unity extension.)

The NavmeshQuery class contains almost all the pathfinding features needed to hand roll your own navigation solution, whether you just need a list of waypoints or you want to implement complex local movement and steering.  

If you want to manage steering for crowds of agents, then you can use to the CrowdManger.  The crowd manager is great, but it requires that you give it a lot of control and can be overkill for certain situations.

The path corridor fills in the middle ground.  It takes an initial path of polygons (a corridor), then helps you manage it without worrying about locomotion inaccuracies, floating point errors, a moving goal, or any of the other common pathfinding gotchas that make moving along the path difficult.  

Definitely take a look at this new class.  It can help you get rid of a lot of your path management code.

Now, Multi-Platform Friendly

This is where the 'big' comes in, even though most of it is hidden.

CAINav is still supported only on Windows, and is likely to stay that way as long as I don't have a way to validate it on other platforms before release.  But my goal is to keep it friendly for use on the other Unity target platforms

That goal is now much closer to being  met due to a lot of work by Dan Treble down in Australia.  Dan, who is working on an game called Quick Quest, went through the pain and suffering of getting CAINav working with Unity on iPhone and Android.

I expected some minor adjustments to the C++ code.  The surprise was the C# code.  I'll cover that in detail in a separate post.  For now, suffice it to say that the C# syntax for Windows and iPhone plugins is not compatible.

You will have to build your own native libraries.  And for iPhone you will need to copy the C# code directly into your project.  But due to Dan's feedback the CAINav code base should now be useable on both Unity iPhone and Android. The documentation includes some tips.

In closing, here is a much nicer navigation mesh visualization than I can produce, courtesy of Dan:


Until next time, take care of the bobcats.