Game Developers Diary 1: OpenGL and Cocoa

I am currently doing some basic research, how one could make games with basic OpenGL, OpenAL and system dependent libraries. At the end of which a simple multi-plattform game engine should be the goal.

I will post updates to the things I found out here, in form of a diary/tutorial as soon as I reached another milestone along the way.

Since I start developing on my Mac, we will also start supporting the Mac and move up from there.

Creating an OpenGL enabled application on OsX Lion

Our first project, to get a hand on OpenGL on the Mac, will be, to create a simple application, that draws a rotating cube inside a Cocoa View and behaves nicely with the system. It should work in Window-Mode and support the Lion’s way of doing fullscreen.

A New Project

We should first create a new Cocoa Project in Xcode. To do that, go to “File > New > New Project” or use the shortcut: Shift+Command+N.

In the new Project Dialog, choose a Mac OS X > Application > Cocoa Application, give it a name and tell Xcode, where to save it.

You should now have your shiny new Cocoa Project ready for you. Before we can get dirty with it, you need to add the OpenGL framework to the Project. You do that, by selecting the Project itself (click on the Project Name in the lefthand navigator in Xcode) and in the Editor coming up, selecting ‘Build Phases’. There you can open the ‘Link Binary With Libraries’ category and click on the ‘+’ Sign; search for the OpenGL.framework and click ‘Add’.

There you are all set and we can start doing something for real.

The OpenGLView

Apple has made it quite simple, to access the OpenGL functionality in a Cocoa application, by giving us the OpenGLView view object, which already binds an OpenGL context and pixel format to itself. Which means, that we should use it.

Create a new subclass of NSView by selecting ‘New File…’; choosing “Mac OS X” > “Cocoa” > “Objective-C class” and click ‘Next’. Give your new class a name and select ‘NSView’ from the ‘Subclass of’ drop-down menu. Click ‘Next’ and ‘Create’ and you’re done.

Open the newly created implementation file (the one, ending with ‘.m’). Since we want to do OpenGL code here, first you have to import the OpenGL header.
Also, since we want to be able to tell, how long a single frame took to draw, we will need the time.h headers.

#import <OpenGL/gl.h>
#import

In Objective C, the #import directive does the same as the #include directive in normal c, but ensures, that you cannot include the same header file multiple times.

So in short, it saves you from having to write a load of Precompiler-prefixes.

Since we want to use, whatever the OpenGLView has provided for us, we also need to subclass it, instead of the basic NSView we have right now.

To do that, go into your view’s header file and change the line:

@interface MyOpenGLView : NSView

to

@interface MyOpenGLView : NSOpenGLView

Being nice to the screen

To fit in well, with the OsX world, we mainly need to be able to resize our view, and need to link our OpenGl flush calls to the screen refresh rate.

To sync to the screen refresh rate, we need to overwrite prepareOpenGL and set the so called ‘CPSwapInterval’ to 1.

- (void)prepareOpenGL {
    // Synchronize buffer swaps with vertical refresh rate
    GLint swapInt = 1;
    [[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval];
}

To be able to resize our OpenGL viewport whenever our window is resized, we need to overwrite reshape with the following:

- (void)reshape {
    NSRect rect = [self bounds];

    glViewport(0, 0, rect.size.width, rect.size.height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(50, rect.size.width/rect.size.height, .1, 30);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glEnable(GL_DEPTH_TEST);
}

First we get the current size of the view and recreate the OpenGL Viewport with those values. That basically tells OpenGL, where in the view it is allowed to draw.

To prevent a stretched image after resizing the viewport, we also need to reset the perspective with with the 3d scene is translated to 2d.

Cunning readers will have noticed, that we change into a different OpenGL Matrix, to do that. I will go into the meaning of the different OpenGL matrixes later, but basically the matrix we use here to set the perspective is the so called projection matrix and that command may very likely be the only command you’ll ever need to perform on that matrix.

Now our view will play nice, inside the system. But we are not doing anything interesting until now.

Prepare for drawing

The first thing, we can do, to see that something is actually happening in our view, is to black out the entire view in the beginning of the draw loop.

Also we should initiate a continues drawing loop, by setting Cocoa’s dirty flag for views needsDisplay at the end of the drawing function.

Since we want to be able to do rotation in the end, we need to enable the OpenGL depth buffer and clear both the depth and the color buffer at the start of the drawing function.

To do that, enter the following code into the drawRect function:

- (void)drawRect:(NSRect)dirtyRect {
    glEnable(GL_DEPTH_TEST);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glClearColor(0, 0, 0, 0);
    glFlush();
    [self setNeedsDisplay:YES];
}

Our view is now able to show a black screen and to start putting some 3d fancy in there. But we still have to somehow show it to the user.

Building the UI

To be able to access our OpenGL view, we need to bind it to the interface file.

In a Cocoa application, a non-compiled configuration file bind’s interface controller code (like our view) to the actual interface. The in Xcode integrated Interface Builder lets us do just that.

By clicking on the file ‘MainMenu.xib’, Xcode opens up the Interface builder.

Here we can select the application’s main window from one of the buttons on the left.


When you have found it, open up the Object Library on the Bottom Right and search for a OpenGL View object. Drag it into the middle of your window and resize it, so that it fills the complete window.

With the OpenGL View selected, you should now be able to select the ‘Identity Inspector’ from the menu on the right. To tell the application, that we want our own OpenGL view as this view, overwrite the value in the Custom Class field in the ‘Identity Inspector’ with the name of your own OpenGL view.


The interface editor can also set some basic switches for our OpenGL view, that we will need for later. To enable depth buffering and play with some other OpenGL parameters if you like, you can choose the Attributes Inspector and change the parameters there.
For the moment, we need to set two parameters here: First, we need to switch on the depth buffering, by setting the depth buffer attribute to 24; Next, we need to tell the Interface Builder, that it’s OK for our view, to draw concurrently. There is also a Checkbox for that in this inspector.

There is one last thing to do in the interface builder. That is, to enable fullscreen. With Lion, Os X introduced a whole new (and awesome) fullscreen mode, that is pretty easy to enable for our application and does integrate with the system, perfectly.

To enable it on our main window, activate the window itself in the interface builder. If you did, you should now see in the Attribute Inspector a parameter, called ‘Fullscreen’ with a drop-down to choose a value from. Change that value to ‘Primary Window’ and you’re all set.

From now on, when you click ‘Build and Run’ for your Project, you should actually get a result on the screen.

Let’s start drawing shit.

The scene is set, the window is build, it’s time to prove, that it has worked. For that, we will draw a rotating cube, that rotates on a time-based scale, which means, that the rotation speed is independent from the actual frame rate.

To do that, we need to remember the time delta, it took us from the last frame to this one; We need to remember the rotation angle of our cube and add an amount to it, and we need to actually rotate our MODELVIEW matrix and draw the cube.

The Clock… Tick, Tock…

To remember a time delta, there are several possibilities on the Mac, but since we are really only interested in the difference in time between two frames and no actual date, we will use the simplest one: time.h, which is also available on any other POSIX compliant system, contains a function called clock() which will tell us the basic processor-clock ticks, that went past, since the application started. It also provides a static value, to enable us, to transform from clock-ticks to seconds and vice versa.

To measure delta time, using clock, we first need a class field, that holds the last measured clock ticks. For that, change the interface definition in your view’s header file, to this:

@interface MyOpenGLView : NSOpenGLView {
    long lastTicks;
}

Also we need to initialize this value in the init function of our view, so that it’s value can be used on the first call to drawRect. To do that, overwrite the init() method of your view:

- (id)init {
    self = [super init];
    if (self) {
        lastTicks = clock();
    }
    return self;
}

Now that our clock has been initialized, let’s use it. Just because we can, we will also calculate the actual Frames per second from this and change the window’s title, to view that value. To do that, we need to add some code to the beginning of the drawRect function:

- (void)drawRect:(NSRect)dirtyRect {
    long ticks = clock();
    //delta_t in millis.
    int delta_t = (int)((ticks - lastTicks)/(CLOCKS_PER_SEC/1000));
    int fps = delta_t > 0?(int) 1000/delta_t:1000;

    [[self window] setTitle:[NSString stringWithFormat:@"%d fps",
                             fps]];
    lastTicks = ticks;

    //Your other code follows here...
}

We end up with an int variable called delta_t which holds the time gone past, in milliseconds.

like a circle in a spiral like a wheel within a wheel

As with the time delta, to calculate the new rotation angle for any given next frame, we have to store the current rotation angle, so that we can add a certain amount to it. And again, to do that we need a class field, to store it. So let’s go to the header file again:

@interface MyOpenGLView : NSOpenGLView {
    long lastTicks;
    float cubeRotationAngle;
}

And like the lastTicks variable, we need to initialize this one in the init function:

- (id)init {
    self = [super init];
    if (self) {
        lastTicks = clock();
        cubeRotationAngle = 0;
    }
    return self;
}

To do the actual rotation, we need to add a certain amount to the rotation angle, every frame, that is multiplied with delta_t to remain stable, no matter the frame rate.

Then, the OpenGL MODELVIEW matrix is rotated to the given angle, so that the Vertices we draw afterwards will appear rotated. Update your drawRect function and add the following after the glClearColor that is already in there:

    glClearColor(0, 0, 0, 0);

    cubeRotationAngle = cubeRotationAngle+(.5*delta_t);

    glLoadIdentity();
    glRotatef(cubeRotationAngle, 1, 1, 1);

Do it then, Draw!

Now all that is left to do, is to actually draw the cube. Add the code below between the glRotatef command and the glFlush command at the end of the method:

    float s = .25;

    glBegin(GL_QUADS);
    {
        glColor3f(0, 0, 1);
        glVertex3f(-1*s,  1*s, -1*s); //F T L
        glColor3f(1, .75, 0);
        glVertex3f( 1*s,  1*s, -1*s); //F T R
        glColor3f(0, 1, 0);
        glVertex3f( 1*s, -1*s, -1*s); //F B R
        glColor3f(1, 0, 0);
        glVertex3f(-1*s, -1*s, -1*s); //F B L

        glColor3f(1, 0, 0);
        glVertex3f(-1*s, -1*s, -1*s); //F B L
        glColor3f(0, 1, 0);
        glVertex3f( 1*s, -1*s, -1*s); //F B R
        glColor3f(0, .5, 0);
        glVertex3f( 1*s, -1*s,  1*s); //B B R
        glColor3f(.5, 0, 0);
        glVertex3f(-1*s, -1*s,  1*s); //B B L

        glColor3f(0, 0, .5);
        glVertex3f(-1*s,  1*s,  1*s); //B T L
        glColor3f(0, 1, 1);
        glVertex3f( 1*s,  1*s,  1*s); //B T R
        glColor3f(0, .5, 0);
        glVertex3f( 1*s, -1*s,  1*s); //B B R
        glColor3f(.5, 0, 0);
        glVertex3f(-1*s, -1*s,  1*s); //B B L

        glColor3f(0, 0, .5);
        glVertex3f(-1*s,  1*s,  1*s); //B T L
        glColor3f(0, 0, 1);
        glVertex3f(-1*s,  1*s, -1*s); //F T L
        glColor3f(1, 0, 0);
        glVertex3f(-1*s, -1*s, -1*s); //F B L
        glColor3f(.5, 0, 0);
        glVertex3f(-1*s, -1*s,  1*s); //B B L

        glColor3f(0, 0, .5);
        glVertex3f(-1*s,  1*s,  1*s); //B T L
        glColor3f(0, 1, 1);
        glVertex3f( 1*s,  1*s,  1*s); //B T R
        glColor3f(1, .75, 0);
        glVertex3f( 1*s,  1*s, -1*s); //F T R
        glColor3f(0, 0, 1);
        glVertex3f(-1*s,  1*s, -1*s); //F T L

        glColor3f(1, .75, 0);
        glVertex3f( 1*s,  1*s, -1*s); //F T R
        glColor3f(0, 1, 1);
        glVertex3f( 1*s,  1*s,  1*s); //B T R
        glColor3f(0, .5, 0);
        glVertex3f( 1*s, -1*s,  1*s); //B B R
        glColor3f(0, 1, 0);
        glVertex3f( 1*s, -1*s, -1*s); //F B R

    }
    glEnd();

Conclusion

You have now seen, what is needed, to create an OpenGL enabled application in Mac OsX.

I hope it was entertaining and understandable and you enjoyed reading it. Please feel free to post any thoughts, questions or commends you have on the subject in the comments section below.

If you want to have a look at my source-code for the example, you can download it here: GameDevDiary1.zip

In the next Episode, we are going to try our hands at OpenAL: Read on!

Thanks for reading, and so long…

—-

Errata (1. Feb ’12): I fixed the segment about the resize method. While it worked somehow, it really wasn’t the way to do it and is much better realized now.

7 thoughts on “Game Developers Diary 1: OpenGL and Cocoa

    • Hi,

      Yeah, sorry. The file uploader I was using at the time seems to shut down stuff that’s been uploaded with free accounts after some time.

      Have a look at Part 5 of this Tutorial; You can download the code from Github now. The link is there.

      One of this days, I will get around to fix the link in the first Entry to this series as well. Thanks for reminding me.

  1. Where do i put the being nice to the screen examples ? In what file ? You say overwrite but where do these functions exist?

    Also, your mediafire account expired, i can’t download the code.
    Many thanks

    • Hi,

      You need to do that inside your MyOpenGLView.m implementation.

      Regarding the source-code: Yeah, that’s a pitty. You can download a later Version of this tutorial from github. See Part 4 and 5 for the links.

      • Hi,
        I would really appreciate it if you could explain to me how to integrate different viewports in this code. I’d really like for the screen to be split up in 4 viewports, preferable by pressing 1,2,3,4. I’m also trying to figure out how to integrate cocoa controls (i want to make a eventviewer on screen to put system log messages in). I’m still learning about objective c and Cocoa, so i’m racking my brains out how to do this.
        Many thanks,
        atv

    • Have a look at ‘BEING NICE TO THE SCREEN’. If your camera appears to be positioned wrong, then you have most likely forgotten to switch a matrix or something like that, after you did set up your viewport.

Leave a reply to Mr. Raven Cancel reply