.NET Game Programming for OS X

I’m taking a hiatus from this project in order to finish up a couple of other things. I hope to get back to it soon, at which time I’ll make the switch from Carbon to Cocoa and push ahead on user input and full-screen rendering.

As good fortune would have it, I recently acquired a MacBook Pro and have set about porting the most recent incarnation of my simulation framework to run under OS X. In my travels over the tubes I was not able to find a comprehensive, step-by-step guide to basic OS X game programming so in the great programmer tradition of NIH I decided to write one.

What You’ll Learn

This tutorial currently covers:

In the not-too-distant future I’ll be adding:

Later I’d also like to add:

About the Code

I happen to use .NET for my development (and enjoy it greatly) so the code samples in this guide are written in C#. But since I’m really just making native API calls it should be useful to anyone looking to get up to speed on OS X game programming. Whether you are using C++ or Java or Python or Scala you will still make the same system calls in the same order. In fact, if anyone wants to recode the examples in another language I’d be happy to host those versions here as well.

You are free to use the included code samples in any manner you choose, including commercial use. If you do, some credit and a link back to this tutorial would be appreciated (but not required).

If you are interested, everything I learn about and discuss here will end up in my Flat Four project, an open source framework for games and simulations (which just so happens to run on OS X).

A Disclaimer

Let me state up front that I am no OS X expert. I am learning as I go and will undoubtedly make a gaffe or two along the way. That’s actually why I am writing this guide: so that I can have my errors pointed out in excruciating detail, in the hope of making my code the best it can be. Comments and corrections are encouraged — don’t worry, I’ve got a thick skin! You can leave a comment at the bottom of any page.

Good luck, and have fun!

.NET on OS X

Mono Logo

As I mentioned in my introduction, I use .NET for all of my development, even on OS X. It should be easy to translate my examples from C# to another language — they are very simple, and mostly calls to system functions — but in case you are interested in trying .NET out for yourself, here is a short introduction.

First, you’ll need to install Mono, an open source implementation of .NET managed by Novell. It is delivered as a .dmg which you install like any other application. Mono includes all of the necessary libraries as well as compilers for C#, VB.NET, IronPython, with more in the works. I happen to like C#, but there are lots of other language options out there.

Whatever language you choose, you’ll eventually want to compile it. For the simple examples in this guide a few commands entered at the terminal are all that is needed. Larger projects, at the time of this writing, are a bit more troublesome. There is a C# plugin for XCode under development (which I really should try one of these days). If you have the budget, there is a nice looking cross-platform IDE available from Omnicore. And of course you can use makefiles (I’m working on a tool called Premake that can generate those for you if you want).

I actually cheat: I run Visual Studio 2005 in Parallels, build to a folder shared with OS X, then hop over to a terminal window to run it. If this sounds like something you want to try, you can get the Express versions of Visual Studio for free.

If someone has a better way I’d love to hear about it (and if anyone from Omnicore wants to send me copy of XDevelop, I’m okay with that too).

Carbon vs. Cocoa

For those unfamiliar with OS X programming, Apple actually provides two different APIs for system-level programming: Cocoa and Carbon. Cocoa is based on Objective C, has more features, and is the preferred method of writing OS X applications. Carbon uses a more traditional C API, is generally lower-level, and is intended to ease the process of porting software from other platforms.

After evaluating both alternatives I decided to go with Carbon. The .NET CLR includes a facility for calling C functions in shared libraries, so I can write my platform layer without the need for bridging tricks or third-party libraries. And the more traditional API style makes it a little easier on people trying to develop Windows and Linux ports in parallel (like me).

If you want to learn more about Carbon, Learning Carbon from O’Reilly is a great place to start. If you are interested in .NET programming for Cocoa, take a look at Cocoa#.

Setting Up

I’ll start with a simple application skeleton, which will be the basis of the examples in the rest of this guide. If you are already familiar with .NET you can probably just skim the code (find the link at the bottom of the article) and skip ahead to the next page. For those of you new to .NET I’ll just give a quick overview.

The skeleton consists of two files: Program.cs which contains the program logic, and Osx.cs which contains the bindings for the MacOS X types, enumerations, and functions.

The program starts by creating an application object. Since this is just a skeleton it doesn’t actually do anything with the application except immediately shut it down again. Later on I will add the event loop here.

using (new OsxGameDevApplication())
{
  // event loop goes here
}

In case you aren’t familiar with C#, you can read more about the using keyword and IDisposable interface. But in a nutshell, the code above amounts to this:

OsxGameDevApplication _obj;
try
{
  _obj = new OsxGameDevApplication();
  // event loop goes here
}
finally
{
  _obj.Dispose();
}

So it creates the application object at the start, and then makes sure that the Dispose() method is called no matter what happens inside the block, even if an exception occurs.

In the application object constructor I call into MacOS X to get the current screen resolution (this isn’t really part of the skeleton, I just wanted to demonstrate the technique). The screen resolution is written to the console so I can make sure that everything is working correctly.

IntPtr mainDisplayId = Osx.CGMainDisplayID();
CGRect bounds = Osx.CGDisplayBounds(mainDisplayId);
Console.WriteLine("Resolution is " + bounds.Width + "x" + bounds.Height);

The bindings to the MacOS X methods, as well as the CGRect data type, are defined in Osx.cs.

[StructLayout(LayoutKind.Sequential)]
public struct CGRect
{
  public float X, Y;
  public float Width, Height;
}

public class Osx
{
  [DllImport("/System/Library/Frameworks/Carbon.framework/Versions/Current/Carbon")]
  public static extern CGRect CGDisplayBounds(IntPtr displayID);

  [DllImport("/System/Library/Frameworks/Carbon.framework/Versions/Current/Carbon")]
  public static extern IntPtr CGMainDisplayID();
}

Program cleanup code should go into the Dispose() method at the point indicated by the comments.

public void Dispose()
{
  Console.WriteLine("Game is finishing");

  // game cleanup code goes here

  Console.WriteLine("Game is finished");
}

The source code for this example is available from the link below. The following sequence of commands, entered in Terminal, will compile and run it. You should see the status messages as they are written to the console.

$ cd OsxGameDev
$ mcs -out:Game.exe *.cs
$ mono Game.exe

And that should cover everything. With that out of the way, it is time to create a window!

Creating a Window

The next step is to create a window for our game, a display area for the flashy graphics we’ll be adding later on (you do have flashy graphics, right?). I’ve added a new method to our application class to do just that. I’ll be building on the application skeleton I introduced on the last page.

public IntPtr CreateDisplay(string title, int width, int height)
{

The method accepts a window title and pixel dimensions as parameters and returns a reference to a Carbon window object. For those not familiar with .NET, IntPtr is the standard way to pass around references to unmanaged objects.

I start by calculating a position for the new window that will center it on the screen, based on the requested pixel dimensions. This isn’t strictly necessary of course, but I figured it would be a nice touch.

// Get the size of the desktop, and use it to center my new display window
IntPtr mainDisplayId = Osx.CGMainDisplayID();
CGRect bounds = Osx.CGDisplayBounds(mainDisplayId);
Console.WriteLine("Desktop resolution is " + bounds.Width + "x" + bounds.Height);

int left = (int)((bounds.Width - width) / 2);
int top = (int)((bounds.Height - height) / 2);
Rect rect = new Rect(left, top, left + width, top + height);

Rect is a MacOS X type, and is defined in Osx.cs. It is not compatible with the .NET Rect structure, so be sure to use the right one!

Next I define the window class and attributes, which control the appearance and behavior of the new window. I took the liberty of creating enumerations for these symbols, which you’ll find in — you guessed it — Osx.cs.

// Create the window object
WindowClass windowClass = WindowClass.kDocumentWindowClass;

WindowAttributes attributes =
       WindowAttributes.kWindowStandardDocumentAttributes |
       WindowAttributes.kWindowStandardHandlerAttribute |
       WindowAttributes.kWindowLiveResizeAttribute;

The kDocumentWindowClass and kWindowStandardDocumentAttributes values give us a normal looking desktop window. The kWindowStandardHandlerAttribute hooks up the new window to the default system event handler, which provides support for things like the Quit menu item and other standard system behaviors. Finally, the kWindowLiveResizeAttribute allows the window to redraw itself as it is being resized.

Now, we’re ready to actually create the window.

IntPtr window;
OSStatus status = Osx.CreateNewWindow(windowClass, attributes, ref rect, out window);
if (status != OSStatus.noErr)
   throw new SystemException("CreateNewWindow failed with error code " + status);

Now we’ve got a window, but there are still a couple more things to do. For one, the window doesn’t have a title; I’ll tackle that next. Unfortunately, its not as easy as passing a string to Carbon; you need to use a special string type instead.

// Set the window title
IntPtr cfStr = Osx.__CFStringMakeConstantString(title);
Osx.SetWindowTitleWithCFString(window, cfStr);
Osx.CFRelease(cfStr);

Almost done now. This next block of code sets up an event handler and registers for the “window closed” and “window resized” messages. We’ll actually handle those events in another method, which I’ll talk about in a moment.

// Register interest in some events
EventTypeSpec[] windowEvents = new EventTypeSpec[]
{
  new EventTypeSpec(EventClass.kEventClassWindow, EventKind.kEventWindowClosed),
  new EventTypeSpec(EventClass.kEventClassWindow, EventKind.kEventWindowBoundsChanged)
};

WindowEventHandler handler = new WindowEventHandler(MyEventHandler);
IntPtr uppHandler = Osx.NewEventHandlerUPP(handler);

Osx.InstallEventHandler(Osx.GetWindowEventTarget(window), uppHandler,
windowEvents.Length, windowEvents, window, IntPtr.Zero);

WindowEventHandler is a .NET delegate type, which is the equivalent of a C callback function. So here I am creating a new instance of a callback to the MyEventHandler method. I then create a Carbon-friendly callback pointer from it, and finally install the callback into my window.

Our window has been created. All that is left to do is show it, and return the reference back to the caller.

   // All done
   Osx.ShowWindow(window);
   return window;
}

That covers the window creation. Now we need to kick off the event loop, otherwise our application will exit before we get a chance to admire our new creation. It’s as easy as adding one new line to main method.

using (new OsxGameDevApplication())
{
   Osx.RunApplicationEventLoop();
}

The definition of RunApplicationEventLoop() is in Osx.cs, and looks like this:

[DllImport("/System/Library/Frameworks/Carbon.framework/Versions/Current/Carbon")]
public static extern void RunApplicationEventLoop();

And of course I need to call CreateDisplay().

public OsxGameDevApplication()
{
   Console.WriteLine("Game is starting");
   CreateDisplay("OsxGameDev Tutorial", 640, 480);
   Console.WriteLine("Game started");
}

Just one last block of code to cover now: the event handler.

// Event callback
static OSStatus MyEventHandler(IntPtr inCallRef, IntPtr inEvent, IntPtr userData)
{
   EventKind code = Osx.GetEventKind(inEvent);
   switch (code)
   {
       case EventKind.kEventWindowClosed:
           Osx.QuitApplicationEventLoop();
           return OSStatus.noErr;

        default:
           return OSStatus.eventNotHandledErr;
   }
}

Again, I created an enumeration for the event kinds. All our event handler does right now is wait for the window to close, at which point it calls QuitApplicationEventLoop() which causes the event loop to…well, quit. RunApplicationEventLoop() will return control to our main method and the application can exit.

That covers all of the code, which you can get from the download link below. To build it all, follow this sequence of commands.

$ cd OsxGameDev
$ mcs -out:Game.exe *.cs
$ macpack Game.exe
$ open Game.app

Notice that you can’t just run the executable after you build it. You must create an application bundle before MacOS X will recognize it as valid application. The macpack command creates a bundle — which is really just a directory structure — named “Game.app”. You can view the contents of the bundle by right-clicking on it in Finder and choosing “Show Contents”.

Once you have your bundle, the “open” command actually launches it. If you’ve done everything correctly you should see something like this.

Screenshot

One last caveat: you might have noticed that nothing got written to the Terminal window, even though our calls to Console.WriteLine() are still in the code. MacOS X applications write to a special log file instead of the terminal, which you can view anytime, even after the application has exited. You can find the console viewer by typing “console” into Spotlight, or get it directly at /Applications/Utilities/Console.

Next up, the animation loop.

The Idle Loop

The next thing I want to do is find out when my application is idle so that I can update my animation.

The code is done, see the link below. I’ll be writing up the article shortly.

To build, follow this sequence of commands.

$ mcs -out:Game.exe *.cs
$ macpack Game.exe
$ open Game.app

If everything works, your console should look like this.

Console screenshot

More to Come!

That’s all I’ve got time for right now, but stay tuned as I add more over the next few weeks. I will be sure to mention any updates on my home page.