oreilly.comSafari Books Online.Conferences.


AddThis Social Bookmark Button

Building a Game Engine with Cocoa
Pages: 1, 2, 3

Cover the Essentials

Believe it or not, all of the remaining work will be done right here in the GameBoard class. A sample project will be available at the end of this article, so instead of trying to actually develop and refactor the entire project file, let's step through the finished product at a high level, discussing the more interesting parts of the project. You can always work though some of the more specific details afterward and use the TalkBacks at the end of this article for specific questions or ideas you want to share. Of course, the full header and implementation files for GameBoard might be useful to have open in another tab while you follow along.

The first few methods we'll develop are fairly routine and get us through some of the typical initialization and setup.

//designated initializer for superclass
- (id) initWithFrame:(NSRect) frame {
    if (self = [super initWithFrame: frame]) {
        //set up player colors
        playerColors[PLAYER_1] = [NSColor redColor];
        playerColors[PLAYER_2] = [NSColor blackColor];
        //set up board colors
        boardColors[0] = [NSColor lightGrayColor];
        boardColors[1] = [NSColor darkGrayColor];
        //initialize board
        int i; int j;
        for (i=0; i< DIMENSION; i++)
            for (j=0; j <DIMENSION; j++) {
                board[i][j] = EMPTY;
                piecesPaths[i][j] = nil;
        //set up initial layout for a game of LOA
        for (i=0; i <DIMENSION; i += DIMENSION-1)
            for (j=1; j <DIMENSION-1; j++) {
                board[i][j] = PLAYER_1;
                board[j][i] = PLAYER_2;
        //nothing is hovered or selected yet
        hoveredCoord = NIL_POINT;
        selectedCoord = NIL_POINT;
        //current dimension of a square on the board
        squareDim = frame.size.width/DIMENSION;
    return self;

//outlets, etc. have been setup by here...
- (void)awakeFromNib {    
    //keep the window a square if the user resizes
    //(also remember to set the springs in interface builder)
    [[self window] setContentAspectRatio:NSMakeSize(1.0,1.0)];
    //register for mouse events
    [[self window] setAcceptsMouseMovedEvents: YES];

//for receiving mouse events
- (BOOL) acceptsFirstResponder {
    return YES;

//recalculate the dimension for a board square when the view is resized
- (void) setFrame: (NSRect) frame {
    [super setFrame: frame];
    squareDim = frame.size.width/DIMENSION;

//draws the view, although never called explicitly
- (void)drawRect:(NSRect)rect {
    [self drawBoardBackgroundInRect:rect];
    //draw all pieces on the board by using the "board"
    //also store the bezier paths for detecting mouseovers
    int i; int j;
    for (i=0; i < DIMENSION; i++)
        for (j=0; j < DIMENSION; j++)
            if (board[i][j] == PLAYER_1) {
                if (piecesPaths[i][j] != nil)
                    [piecesPaths[i][j] release];

                piecesPaths[i][j] = [[self drawPieceForCoord:NSMakePoint(i,j) andPlayer:PLAYER_1] retain];
            else if (board[i][j] == PLAYER_2) {
                if (piecesPaths[i][j] != nil)
                    [piecesPaths[i][j] release];
                piecesPaths[i][j] = [[self drawPieceForCoord:NSMakePoint(i,j) andPlayer:PLAYER_2] retain];

//clean up piecesPaths
- (void)dealloc {    
    int i; int j;
    for (i=0; i< DIMENSION; i++)
        for (j=0; j< DIMENSION; j++)
            if (piecesPaths[i][j] != nil)
                [piecesPaths[i][j] release];
    [super dealloc];

The initWithFrame: method is the designated initializer for NSView and is the area in which you can set up initial values for the gameboard, so it is natural that we assign colors for the board squares and each player here. One thing particularly worth pointing out is that we're calculating and storing an instance variable called squareDim that designates the size of a square on our board. Even though we could recalculate this value on the fly every single time we need it and probably never even begin to notice the impact, it's good form to go ahead and store this value away. As we'll see in setFrame:, we'll only recalculate this value whenever the window is resized, and the board needs to be redrawn proportional to that new size.

The method awakeFromNib is an ideal place to register for mouse events since it's safe to reference the window by this point. Once the GameBoard registers for mouse events via setAcceptsMouseMovedEvents, it'll receive them since it was set in Interface Builder to be the window's first responder. The actual way our board handles the mouse events is by implementing NSResponder's methods mouseMoved: and mouseDown: (more on these toward the end of the article.) In case you were wondering, NSResponder is an abstract class that the core event handling classes such as NSWindow and NSview inherit from.

In awakeFromNib, we also set the aspect ratio of the window to be 1:1 so that the window will remain square if it is resized. This property keeps the board drawing simple, since boards for these types of games are usually square.

In addition to registering for mouse events in awakeFromNib, the view must explicitly declare itself capable of being the first responder, so we take care of this in acceptsFirstResponder:. Setting the first responder status is the mechanism that allows the view to actually receive events such as mouse drags and mouse clicks. Setting setAcceptsMouseMovedEvents specifies that the view has methods in place for handling events such as the mouse moving.

Whenever a window resize occurs, setFrame: is called, so we'll use it to recalculate the size of squares on the board as previously mentioned, since we'd like for the board to be able to size along with the window. As you'll see, calculating the proper dimensions for board squares is the only remotely tricky thing that's necessary to make this happen.

Inside drawRect is where the drawing action happens, although it does use the helper functions drawBoardBackgroundInRect: and drawPieceForCoord: for a lot of the labor. Assuming the helper functions do what it sounds like they do, we can read through drawRect: and see that it first draws the empty board rectangle that defines the view, and then proceeds to iterate through each of the squares on the board using a simple matrix that keeps track of the pieces.

If a particular board spot is occupied, a routine is called that draws the appropriate piece in that spot using an NSBezierPath, and that path is retained so that we'll have access to it later. The reason for keeping these paths around is because NSBezierPath has a method that makes it rather trivial to determine whenever the mouse hovers it; we'll use this ability to provide a rollover effect. It is noteworthy that the retains you see in this method are the only ones that appear in the entire class, and scanning on down to dealloc shows that we clean them up when the time comes. All other drawing is accomplished with bezier paths that are autoreleased and consequently don't need to be retained.

Pages: 1, 2, 3

Next Pagearrow