Chapter 2 -- the Rotating Cube
Now that the game concept is clear, we can start with the implementation.
Look at this overview of files you will need and get familiar with what they will be doing. This is the core of your 3D-engine. You don't know yet how to implemement all of them, but you can start with the Controller, the GUI, your custom
NSView, and the game entity arrays from
MYGameWorld. Replace the "MY" in the object names with your initials. Create the other files and leave them empty, then read on below...
MYGameController.m (This is the Cocoa equivalent to
- The application delegate a.k.a. the controller controls the game.
- It creates the window and initializes and deallocates the view.
- It initializes and deallocates game world data.
- It responds to user menu input.
MYGameWorld object initializes and deallocates game world data.
- Game world data is an array of game entities (
MYEntitys), their properties and positions. Later,
MYGameWorld will also implement the
NSCoder interface for saving data to and retrieving it from a file.
MYGameWorld offers accessors to update, add or remove game entities.
MYGameWorld may receive
NSTimer notifications from the main event loop telling it to update the view in regular intervals. Each update means, the game world data is recalculated and redrawn: Transformations (such as rotation) are applied to the game entities locally first. Next, the prepared entities are put into the game world by converting their local coordinates to 3D world coordinates. These 3D world coordinates are then projected to 2D screen coordinates which will finally be drawn to the screen. This approach and its implementation will be explained in great detail in 3D projection and 3D transformation matrices.
- For this demo, the world contains only one cube.
|This object defines game entities such as characters, interactive objects, landscapes or buildings. A |
MYEntity is a 3D object consisting of one or many individual
MYSubentitys and accordingly it offers methods to add or remove
MYSubentitys. If it is told by
MYGameWorld to update itself, it delegates this job recursively to each of its
MYSubentitys, which in turn will recalculate and redraw themselves. The implementation will be explained in great detail in 3D projection.
|This object defines a |
MYSubentity, a part of game entity. A
MYSubentity is a 3D object consisting of one or several polygons. The simplest
MYSubentity is a plane, but it can be as complex a cuboid as you wish.
MYSubentitys are employed to group parts of
MYEntitys that belong and move as one -- typical examples for
MYSubentitys would be one head, one body, two arms and two legs inside an
MYEntity describing a person. You may skip subentities and implemenent general entities first, they are just an optimization.
|This object defines a polygon. Each polygon has a color and is specified by 3-dimensional coordinate points called vertices. Polygons must be coplanar, their vertices must be given in counter-clockwise order, and they are single-sided. Textures and Bezier-curved polgons are not covered in this tutorial. The implementation will be explained in great detail in 3D projection|
|Definition of a vertex, a point in 3D space. Since a 3D image is projected to the screen in four steps, there are four groups of variables: 1) the local vertex coordinates, 2) the current transformed vertex coordinates, 3) the current world coordinates and 4) the 2D current projected screen coordinates of each vertex. The implementation will be explained in great detail in 3D projection
|One of the most important files here: It implements 3D Matrix arithmetics and formulas. I propose precalculating a sinus/cosinus look-up table. The five 3D-transformations implemented here are translation (moving), scaling (resizing) and rotation around three axes. The implementation will be explained in great detail in 3D matrix transformation.
|Auxilary: Some templates and constructors for various kinds of basic |
MYEntity objects (cuboid, tetraedron, plane, etc). It is easier to use templates since for the definition of vertices and polygons, the correct order of vertices is extremely important.
|Auxilary: Just some |
NSColor definitions for coloring the entities.
main.m does nothing but initialize the application. (What in C used to be
MYGameView.m in Cocoa! cf. Cocoa's model-view-controler paradigm.) The NIB file
mainMenu.nib contains the GUI, not much in there but one customized
NSView to draw in.
RSF.pbproj opens and coordinates the whole project in Apple ProjectBuilder (you can also use Apple Xcode.)
| The executable 3D-game engine. |
Like I said, you don't know yet how to implement most of these objects, which represent the core of your 3D game engine. The next two steps will deal with that by giving you the background information you need:
- First, read this introduction to
3D projection to learn how to define and display 3D objects statically.
- Then read about how to manipulate 3D objects efficiently using 3D matrix transformation.
Combine the information you obtained from those two introductions to a first demo application: For starters, we are satisfied with the basic functionality to display a rotating cube.
Intermediate result: A lone multi-colored three-dimensional rotating cube,
drawn with Cocoa's built-in drawing routines (NSBezierPath).
Main Event Loop: How to Set up an NSTimer for a Rotating Demo
Some hints for the use of a main event loop to update the game world and recalculate the display:
- MYGameWorld needs an NSTimer reminding it to move objects around in the game world — NPCs walk, doors slide open, etc.
- MYGameWorld needs to be able to notify MYGameView that something just has changed, so don't forget to sign up MYGameView for a notification observer.
In MYGameWorld's init:, you initialize the NSTimer and set the game's heartbeat, the refresh rate. As a selector, you specify what is to be triggered — supply the name of a custom method, in our case that's the somethingHappens: method. For the demo, there is not much happening in the world, we only want to cube to rotate. When MYGameWorld is ready, the NSTimer sends the custom notification "MYPleaseRedraw" up to the MYGameView's NSNotificationCenter.
self = [super init];
if( !self ) return nil;
heartbeat = 1.0; /* refresh rate in seconds */
worldtimer = [NSTimer
- (void) somethingHappens:(NSTimer*)timer
/* The action happens here! */
[self addToRotationX:6.0 y:4.0 z:5.0]; /* for example: cubes rotates */
/* Enough has happened, we are ready to refresh the display */
In MYGameView, when you pass a new MYGameWorld to the view, apply for a NSNotificationCenter observer for the game world. The observer's message name is the custom notification we are waiting for, MYPleaseRedraw. As a selector, you specify what is to be triggered in MYGameView as a response to the notification — supply the name of a custom method, in our case that's the redrawGameView: method.
- (void) setGameWorld:(MYGameWorld*)gameworld
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[NSNotificationCenter defaultCenter] addObserver:self
- (void) redrawGameView:(NSNotification*)o
/* This triggers the view's drawRect: method! */