RuTh's  RuThLEss  HomEpAgE


3D Game DesignEnglish
Programming Links
* INDEX * 3D game to-do list * 3D Engine to-do list ( chapter 1, 2, 3, 4, 5, 6, 7 ) *
* projection formula * transformation matrices * Bresenham algorithm * Scanline Polygonfill algorithm *
* Spieldesign / game design * Troubleshooting 3D * Irrlicht 3D engine * Blender for Beginners *

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 main.c) Controller:
  • 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.m Model:
  • The 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.
MYGameView.m View:
  • The View loads a list of what to view: A game world data object (MYGameWorld).
  • The View updates the screen by drawing its customized instance of an NSView everytime the drawRect: method is called.
  • The drawRect: method loops over the list of game entities in the world and delegates drawing to each entity's draw: method.
  • The View applies for an NSNotificationCenter object to be notified of changes in game world data during the main event loop (NSTimer). In this case, it will refresh the display.
  • The View also implements some NSResponder methods; it can respond to keyboard input and mouse clicks by changing the game world's state.
  • To save CPU time, you should deactivate anti-aliasing for the Rotating Cube demo:
    [[NSGraphicsContext currentContext] setShouldAntialias:NO];

MYEntity.mThis 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.
MYSubentity.mThis 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.
MYPolygon.mThis 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
MYVertex.mDefinition 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

MYMatrix.mOne 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.

MYTemplate.mAuxilary: 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.
MYColors.mAuxilary: Just some NSColor definitions for coloring the entities.
main.m, mainMenu.nib, RSF.pbprojCocoa's main.m does nothing but initialize the application. (What in C used to be main.c is MYGameController.m and 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.)
RSF.app 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).

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.

-(id) init
    self = [super init];
    if( !self )	return nil;
    heartbeat = 1.0; /* refresh rate in seconds */
    worldtimer = [NSTimer 
    [worldtimer retain];
    return self;

- (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 */
    [[NSNotificationCenter defaultCenter] 
        postNotificationName:MYPleaseRedraw object:self];

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
    [self setNeedsDisplay:YES];
    /* This triggers the view's drawRect: method! */

Related Links