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.

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.
| Attachment | Size |
|---|---|
| OsxGameDev_Window.tar.gz | 2 KB |