Unity3d is an amazing Game Engine that is revolutionizing the game industry by providing a simple to use game assembly, engine and publishing workflow that allows even very small (think 1 person) game companies to write once and publish their games to Windows, Mac, Linux, Android and iOS simultaneously on a stable performant platform. If you game at all, chances are you've played on unity.
ChatScript is a innovative Natural Language Processing (NLP) engine written and open sourced by Bruce Wilcox which can be used to create believable ChatBots and can also act as a HTN Planner, search engine and undoubtably a number of other arcane thingys. I'm playing around with it as part of my latest hobby project having to do with Unity3d and my secret love of RPGs (oops).
I want these two to work together. I don't want much, right?
ChatScript is mostly expected to run as a stand alone server listening for TCP requests. It's pretty easy to hook that up in Unity3d - you just write some C# that connects to the socket, have the expected conversation and voila.
Chat with Rose |
But... I'm planning on making my multiplayer game _without_ a central server architecture. I don't have that kinda money to blow on a side hobby project in a niche market that is unlikely to gain much of a following and probably couldn't survive with a subscription model. So...
I need to embed ChatScript _inside_ Unity so I don't have to run a central server or do something hokey like secretly launch a local server on the gamer's machine. Unity allows C# and Javascript for coding ( which runs on mono for cross platform goodness ). Chatscript is written in C++. Problem.
SWIG to the rescue! Swig wraps C/C++ code up to be called by your high level language of choice ( 19 currently supported ). It has C# support, so theoretically this should work.
I'm going to just skip all the failure. That is more a story to be told quietly over a good scotch and some 70s punk turned down real low until it's just gibberish screaming in the background like a pair of someone else's headphones. I'll get right to the solution. That's what you want right? Well, I'm not tapping my scotch cabinet for the whole internet, so give that up right now.
Swig works by creating a C wrapper around your C whatever given a definition file. This gets compiled into the needed dynamically loaded library for whatever language you want. I'm going to talk about C#, but I got it to work with Java and Perl as well before I felt I had mastered the process and moved on. ( I'll include those code samples too at the end ).
ChatScript.i
%module ChatScript %{ #include <time.h> #include <stdint.h> #include "common.h" #include "mainSystem.h" %}
That starts us off on what's needed to make a dynamic library out of chat script source. Next we have to tell it how to map the methods we want and the datatypes those methods use.
%include <typemaps.i> %typemap(ctype) char * output "char *" %typemap(imtype) char * output "System.Text.StringBuilder" %typemap(cstype) char * output "System.Text.StringBuilder" %typemap(in) char * ppString (char *tmp = 0) %{ $1=&tmp; %} %typemap(argout) char * ppString %{ strcpy($input, *$1); free(*$1); %}
This swig magic does that. It essentially maps the char* into regular C#s strings and the output buffer char* into a StringBuilder. In C it's common to pass in a pointer to a method, then read from that pointer as a way of returning either complex data types or for other reasons. C# of course doesn't really do that so you have to wave your hands a bit to make the translation. All this is fairly standard stuff you get by reading the docs and looking at the great examples that are provided with the swig source code.
%apply char * output { char *OUTPUT } int InitSystem(int argc, char * argv[],char* unchangedPath = NULL, char* readonlyPath = NULL, char* writablePath = NULL);
void PerformChat(char* user, char* usee, char* incoming,char* ip, char* output);
The two lines at the bottom are telling swig what method signatures to wrap, the line above those is mapping the last input to the PerformChat method as an output variable.
Now that we have a swig .i file we go to ChatScript's src directory and tell swig to do it's magic.
'swig -c++ -csharp -namespace ChatScript ../swig/mac/csharp/ChatScript.i'
This creates the wrapper files in the same directory as the .i file. Handy. Now just to compile them into a dynamic library. I'm going to leave this part out mostly - I've included a sample Xcode project at the end of this but the basic swig instructions should work fine on your system. Besides, I wouldn't claim to be able to create a DLL on windows, then somebody might make me do it and I'm allergic to windows ( gives me Tourettes ).
ExampleClient.cs
// snip snip void InitServer () { GCHandle gch = StringArray2Ptr (args); IntPtr argv = gch.AddrOfPinnedObject (); Console.WriteLine ("Started Chat Server : " + ChatScript.ChatScript.InitSystem (args.Length, new SWIGTYPE_p_p_char (argv, false), chatRoot, chatRoot, chatRoot)); gch.Free (); } public string SendChat (string input) { System.Text.StringBuilder output = new System.Text.StringBuilder (1024); ChatScript.ChatScript.PerformChat (playerName, botName, input, null, output); return output.ToString (); } // snip snip
full code in example swig and Xcode project below
My next post will deal with getting this all into Unity and packaging it up.
For now check out the GitHub Project with all the example files you need.
Enjoy!
references:
http://www.chatbots.org/ai_zone/viewthread/1552/