16
AUG
2011

Turning to 3D

After getting my 2D game into the app store, I am once again turning to 3D rendering and my racing game that I have started and stopped working on for years now. I finally wrote a 3D scene exporter for Cheetah 3D that allows me to create 3D objects in Cheetah 3D and then import the whole scene into my game engine. Here's a video of the very first thing I tried:

The car is following a spline and I wrote the spline code in about 5 minutes and cheated by using arctan, which resulted in the car driving backwards sometimes. I will take care of that later.


27
MAY
2011

Lotsarox Released

My first iPhone game Lotsarox was released on the iPhone App Store a few weeks ago. It's a very simple game. Basically, you fly into an asteroid field and have to survive for as long as possible. The game was designed to kill you very quickly, so that each play session can be as short or as long as you want it to. A global Game Center leaderboard adds a little bit of incentive to play the game multiple times.

Lotsarox Intro Screen

Lotsarox In Action


17
APR
2011

Already Started on a New Game

As I mentioned in my previous blog post, I submitted my first iPhone game to the app store today. I have already started working on my next game. I'm not sure if I will be able to finish it, because the scope is a bit more ambitious than the last one, but it will be a fun project, either way.

Several of my former iPhone game projects have failed because I wanted to do more than I had time for, since I actually have a full-time job. :-) This time it could be different because the latest incarnation of my game engine is far more mature than what I had before.

So far I have started working on some of the sprites. Here are a few examples:

Sprites


17
APR
2011

New Game App Submitted

Today I submitted the first app based on my iOS game engine to the app store. It's a very simple game, but it's proof that my engine works and I think it's a fun game. It's called Lotsarox and I hope it will be approved within a week or two. More info when/if the game is available on the app store.

And by the way, my memory mapped game asset files work great! :-) Without them, I probably wouldn't have been able to finish the game.


6
MAR
2011

Memory Mapped Game Assets

My plan is to create a graphical tool that lets me organize and automatically convert data files, such as textures and shaders, into data that is prepared to be loaded into my iPhone game engine. The tool will put all of the data into one single file, which the game engine will map into virtual memory space.

I have specified a draft of the file format I will use and I have created a command line tool that can write the file format. All I have to add for the command line tool to work is specific code for each type of asset that I want to write to the file.

Here is my initial draft of the file format, in pseudo-C:

// --- Simple file header ---
char fileType[8] = "MEMFRAG!";
uint32_t version;
 
// --- Assets section starts here ---
char assetsSection[4] = "ASTS";
uint32_t assetCount;
struct {
    char assetName[128]; // Name of asset in UTF-8
    uint32_t offset; // File offset to asset
} assetTOC[assetCount];
 
// --- Assets will follow this pattern. ---
char assetSection[4] = "ASET";
char assetType[4] = "<asset type goes here>";
uint32_t version;
uint8_t assetData[dataSize];
uint8_t paddingTo32BitBoundary[x];
 
// --- Arbitrary binary data asset ---
char assetSection = "ASET";
char assetType = "DATA";
uint32_t version;
uint32_t size;
uint8_t data[size];
uint8_t paddingTo32BitBoundary[x];
 
// --- Texture asset ---
char assetSection[4] = "ASET";
char assetType[4] = "TXTR";
uint32_t version;
uint32_t format; // PVR (iPhone), PNG or DDS (Mac)
uint32_t size;
uint8_t data[size];
uint8_t paddingTo32BitBoundary[x];
 
// --- Effect asset ---
char assetSection[4] = "ASET";
char fileType[4] = "EFCT";
uint32_t version;
uint32_t lengthOfVertexShader;
char vertexShader[lengthOfVertexShader];
uint32_t lengthOfFragmentShader;
char fragmentShader[lengthOfFragmentShader];
uint32_t attributeCount;
struct {
   uint32_t index;
   uint32_t standardAttributeIndex; // -1 if not standard
} attributes[attributeCount];
 
// --- Sprite asset ---
char assetSection[4] = "ASET";
char fileType[4] = "SPRT";
uint32_t version;
char texture[64]; // Asset name
uint32_t frameCount;
struct {
   uint32_t originX;
   uint32_t originY;
   uint32_t sizeX;
   uint32_t sizeY;
   uint32_t hotspotX;
   uint32_t hotspotY;
} frames[frameCount];
uint32_t animationCount;
struct {
   char key[64];
   uint32_t loopCount;
   float frameDuration;
   uint32_t frameCount;
   uint32_t frames[frameCount];
} animations[animationCount];
 
// --- Sound asset ---
char assetSection[4] = "ASET";
char assetType[4] = "SOND";
uint32_t version;
uint32_t format; // AL_FORMAT_STEREO16 or AL_FORMAT_MONO16
uint32_t sampleRate;
uint32_t size;
uint8_t data[size];
uint8_t paddingTo32BitBoundary[x];
 
// --- Music asset ---
char assetSection[4] = "ASET";
char assetType[4] = "MUSC";
uint32_t version;
char filePath[128]; // Music files stored separately.


6
MAR
2011

Memory Mapped Files

One of the difficult parts of writing games is memory management. This is especially true for games that run on mobile devices. One useful "trick" is to map files into the virtual memory space, instead of actually loading the file.

When you map a file into the virtual memory space, you get a memory pointer to the start of the file and you can use that pointer as if the file had been loaded straight into memory. When you access different parts of the file those parts will be loaded into physical memory on demand.

When the system is running out of memory, it will throw out the parts of memory mapped files that have been loaded into the physical memory before it starts complaining about being short on memory. When the app tries to access the unloaded part of the mapped memory again, it will be loaded back into physical memory.

Obviously, accessing the mapped memory is not as fast as just accessing physical memory when you access a part of the file that is not currently in physical memory, but it's almost like free extra memory for your app, so for code that does not need to be super fast it is sufficient (Note that this is only true for files that are mapped into memory as read-only).

On the iPhone, you can memory map files in at least two ways. One is to use the mmap() system call and the other is to use NSData with the +(id)dataWithContentsOfMappedFile: initializer method.

For my game engine, I want to use Objective-C objects for memory mapping files, but as there is no documentation on exactly how NSData is implemented, I have chosen to implement my own class for memory mapped files that simply wraps the mmap() system call. When it comes to memory management, it's usually a good idea to be somewhat paranoid and make sure you know exactly what is going on behind the scenes.

As always, you can do whatever you want with code that I put on this blog, but I take no responsibility for it or what you do with it. :-)

Header file (MemoryMappedFile.h):

#import <Foundation/Foundation.h>
 
 
@interface MemoryMappedFile : NSObject
{
@private
    NSString *path;
    void *baseAddress;
    NSUInteger size;
}
 
// The path of the file to map into memory.
@property (nonatomic, readonly) NSString *path;
 
// The memory address where the file is mapped.
// NULL when the file is not mapped into memory.
@property (nonatomic, readonly) void *baseAddress;
 
// Total size of the file.
@property (nonatomic, readonly) NSUInteger size;
 
// Returns YES when the file is mapped into memory.
@property (nonatomic, readonly) BOOL isMapped;
 
// Prepares to map the specified file, but does not
// actually map the file into memory.
- (id)initWithPath:(NSString *)pathToFile;
 
// Maps the file into memory.
// Returns pointer to start of file or NULL if unsuccessful.
- (void *)map;
 
// Unmaps the file from memory.
// The pointer returned by map is no longer valid.
- (void)unmap;
 
@end

Implementation file (MemoryMappedFile.m):

#import "MemoryMappedFile.h"
#import <fcntl.h>
#import <unistd.h>
#import <sys/stat.h>
#import <sys/mman.h>
 
@implementation MemoryMappedFile
 
@synthesize path;
@synthesize baseAddress;
@synthesize size;
 
- (id)initWithPath:(NSString *)pathToFile
{
    if (self = [super init])
    {
        path = [pathToFile copyWithZone:nil];
    }
 
    return self;
}
 
- (void)dealloc
{
    if ([self isMapped])
    {
        [self unmap];
    }
 
    [path release];
    [super dealloc];
}
 
- (void *)map
{
    if ([self isMapped])
    {
        return baseAddress;
    }
 
    // This will be released when "path" is released.
    const char *cPath = [path
            cStringUsingEncoding:NSISOLatin1StringEncoding];
 
    // The file must be opened so we can pass 
    // the file descriptor to mmap().
    int file = open(cPath, O_RDONLY);
    if (file == -1)
    {
        perror("open");
        return NULL;
    }
 
    // Get info about file, we need the file size.
    struct stat buffer;
    if (fstat(file, &buffer) == -1)
    {
        perror("fstat");
        close(file);
        return NULL;
    }
 
    // Map the file as read only pages.
    baseAddress = mmap(NULL, buffer.st_size, PROT_READ,
                        MAP_SHARED, file, 0);
    if (baseAddress == MAP_FAILED)
    {
        perror("mmap");
        close(file);
        return NULL;
    }
 
    // Store the size, we need it when we unmap the file.
    size = (NSUInteger)buffer.st_size;
 
    // It's ok to close() after mmap().
    if (close(file) == -1)
    {
        perror("close");
        [self unmap];
        return NULL;
    }
 
    return baseAddress;
}
 
- (void)unmap
{
    // Only unmap the file if it is actually mapped.
    if ([self isMapped])
    {
        if (munmap(baseAddress, size) == -1)
        {
            perror("munmap");
            // There's not much we can do if munmap() fails.
        }
 
        baseAddress = NULL;
        size = 0;
    }
}
 
- (BOOL)isMapped
{
    return baseAddress != NULL;
}
 
@end


30
MAR
2010

Matrix Stack

In OpenGL ES 2.0, there is no matrix stack. I really do miss the matrix stack, so I decided to take a cue from the Direct3D utility library, where there is a matrix stack class.

My class interface is heavily inspired by the ID3DXMATRIXStack class, but there was no implementation to peek at, so I had to figure out the implementation of the methods on my own.

I have ported most of my game engine to Objective-C, but since the open source GLGX math library I am using is in C++, I decided to write all my math related classes in C++.

As always, you can do whatever you want with code that I put on this blog, but I take no responsibility for it or what you do with it. :-)

MatrixStack.h:

#include "GLGXMath.h"
 
#define MAX_MATRIX_STACK_LEVELS 32
 
class MatrixStack
{
private:
    GLGXMATRIX stack[MAX_MATRIX_STACK_LEVELS];
    int currentLevel;
 
public:
    MatrixStack();
 
    void clear();
 
    const GLGXMATRIX *getTop() const;
 
    void loadIdentity();
 
    void loadMatrix(GLGXMATRIX *matrix);
 
    void multMatrix(GLGXMATRIX *matrix);    
    void multMatrixLocal(GLGXMATRIX *matrix);
 
    void pop();
    void push();
 
    void rotateAxis(GLGXVECTOR3 *vector, float angle);  
    void rotateAxisLocal(GLGXVECTOR3 *vector, float angle);
 
    void rotateYawPitchRoll(float yaw, float pitch, float roll);
    void rotateYawPitchRollLocal(float yaw, float pitch, float roll);
 
    void scale(float x, float y, float z);
    void scaleLocal(float x, float y, float z);
 
    void translate(float x, float y, float z);
    void translateLocal(float x, float y, float z); 
};

MatrixStack.cpp

#import <assert.h>
#import "MatrixStack.cpp"
 
// Constructs the matrix and loads the identity matrix.
MatrixStack::MatrixStack()
{
    clear();
}
 
// Clears the stack and pops all matrices.
void MatrixStack::clear()
{
    currentLevel = 0;
    this->loadIdentity();
}
 
// Retrieves the current matrix at the top of the stack.
const GLGXMATRIX *MatrixStack::getTop() const
{
    return &stack[currentLevel];
}
 
// Loads identity in the current matrix.
void MatrixStack::loadIdentity()
{   
    GLGXMatrixIdentity(&stack[currentLevel]);
}
 
// Loads the specified matrix in the current matrix.
void MatrixStack::loadMatrix(const GLGXMATRIX *matrix)
{
    stack[currentLevel] = *matrix;
}
 
// Multiplies the current matrix with the specified matrix.
void MatrixStack::multMatrix(const GLGXMATRIX *matrix)
{
    stack[currentLevel] = stack[currentLevel] * (*matrix);
}
 
// Multiplies the specified matrix with the current matrix.
void MatrixStack::multMatrixLocal(const GLGXMATRIX *matrix)
{
    stack[currentLevel] = (*(GLGXMATRIX *)matrix) * stack[currentLevel];
}
 
// Removes the current matrix from the top of the stack.
void MatrixStack::pop()
{   
    if (currentLevel > 0)
    {
        currentLevel--;
    }
}
 
// Adds a matrix level to the stack, by duplicating the current matrix.
void MatrixStack::push()
{
    assert(currentLevel + 1 < MAX_MATRIX_STACK_LEVELS);
 
    stack[currentLevel + 1] = stack[currentLevel];
    currentLevel++;
}
 
// Rotates around an arbitrary vector axis. (world space)
void MatrixStack::rotateAxis(const GLGXVECTOR3 *vector, float angle)
{
    GLGXMATRIX tmp;
    GLGXMatrixRotationAxis(&tmp, (GLGXVECTOR3 *)vector, angle);
    stack[currentLevel] = stack[currentLevel] * tmp;
}
 
// Rotates around an arbitrary vector axis. (local space)
void MatrixStack::rotateAxisLocal(const GLGXVECTOR3 *vector, float angle)
{
    GLGXMATRIX tmp;
    GLGXMatrixRotationAxis(&tmp, (GLGXVECTOR3 *)vector, angle);
    stack[currentLevel] = tmp * stack[currentLevel];
}
 
// Rotates around an arbitrary axis. (world space)
void MatrixStack::rotateYawPitchRoll(float yaw, float pitch, float roll)
{
    GLGXMATRIX tmp;
    GLGXQUATERNION quaternion;
    GLGXQuaternionRotationYawPitchRoll(&quaternion, yaw, pitch, roll);
    GLGXMatrixRotationQuaternion(&tmp, &quaternion);
    stack[currentLevel] = stack[currentLevel] * tmp;
}
 
// Rotates around an arbitrary axis. (local space)
void MatrixStack::rotateYawPitchRollLocal(float yaw, float pitch, float roll)
{
    GLGXMATRIX tmp;
    GLGXQUATERNION quaternion;
    GLGXQuaternionRotationYawPitchRoll(&quaternion, yaw, pitch, roll);
    GLGXMatrixRotationQuaternion(&tmp, &quaternion);
    stack[currentLevel] = tmp * stack[currentLevel];
}
 
// Scale the current matrix. (world origin)
void MatrixStack::scale(float x, float y, float z)
{
    GLGXMATRIX tmp;
    GLGXMatrixScaling(&tmp, x, y, z);
    stack[currentLevel] = stack[currentLevel] * tmp;     
}
 
// Scale the current matrix. (local origin)
void MatrixStack::scaleLocal(float x, float y, float z)
{
    GLGXMATRIX tmp;
    GLGXMatrixScaling(&tmp, x, y, z);
    stack[currentLevel] = tmp * stack[currentLevel];     
}
 
// Translate the current matrix. (world origin)
void MatrixStack::translate(float x, float y, float z)
{
    GLGXMATRIX tmp;
    GLGXMatrixTranslation(&tmp, x, y, z);
    stack[currentLevel] = stack[currentLevel] * tmp;
}
 
// Translate the current matrix. (local origin)
void MatrixStack::translateLocal(float x, float y, float z)
{
    GLGXMATRIX tmp;
    GLGXMatrixTranslation(&tmp, x, y, z);
    stack[currentLevel] = tmp * stack[currentLevel];
}


30
MAR
2010

Racing Restart

As is painfully obvious from the lack of posts on this blog, I pretty much abandoned my racing game for the iPhone. However, I still have the code and I am once again motivated to work on it. So here we go!

First of all, I have started to create my 3D content in Cheetah3D. The main reason for this is that Cheetah3D is very easy to use and it is scriptable using Javascript. So far I have created a script that generates a road mesh for me and I have imported meshes from a racing game I created for Windows in 2003.

Below is an image rendered by Cheetah3D, which might not look like much, but I am just getting started!

First Render


30
OCT
2009

Simple Vertex Shader for 2D Graphics

As I mentioned in an earlier post, OpenGL ES 2.x replaces the fixed function pipeline of OpenGL ES 1.x with a programmable pipeline. This means that you have to write a vertex shader program and a fragment shader program that perform the operations that the fixed function pipeline used to do. What you gain is an unprecedented level of flexibility as you are no longer restricted to hardwired calculations in the GPU.

The vertex shader processes polygon vertices and the fragment shader processes rasterized pixels. There are lots of tutorials and documentation on the basics of shaders, so I won't go into all the details. Suffice it to say that the vertex shader program is run once for each vertex in the polygon and the fragment shader is run for each polygon pixel to be drawn. Instead, I will show you a couple of examples of extremely basic vertex shaders.

Each polygon vertex has a position in 3D space. One of the primary responsibilities of the vertex shader is to project the vertex position into 2D space. By default, the OpenGL 2D coordinate system of the iPhone looks like this:

Without view projection matrix

This is an orthographic projection, which means there is no sense of perspective and the depth component of the vertex position is more or less ignored. For a (non-degenerate) polygon to be visible in the default coordinate system, at least one of its vertices would have to have an XY position within the range shown in the image.

The following vertex shader simply copies the position attribute of the vertex to the gl_Position variable, which is the predefined vertex position that will be used by the hardware when the polygon is rasterized and drawn.

attribute vec4 position;
 
void main()
{
    gl_Position = position;
}

This is probably really the simplest possible vertex shader you can write without hardcoding the position. However, you rarely want to use the default coordinate system, because it has an awkward aspect ratio and no perspective projection.

Let's say we want to create a 2D game where the coordinate system matches the resolution of the iPhone screen, which is 320x480 pixels. We wouldn't have to worry about perspective in this case, since we're not going to do 3D, so it's ok to still have an orthographic projection. Basically, we want a coordinate system that is more common when dealing with 2D pixel graphics in general, where the upper left corner is the origin. Here's an illustration of the coordinate system we want:

With view projection matrix

To accomplish this, we would have to create a view projection matrix that could be used to project the vertex position from our coordinate system to the default coordinate system. To perform the projection transformation in the vertex shader, we simply multiply the vertex position with the view projection matrix:

attribute vec4 position;
 
uniform mat4 viewProjectionMatrix;
 
void main()
{
    gl_Position = viewProjectionMatrix * position;
}

As you may have noticed, the position is represented by a vector with 4 components instead of 3. This is because the vector has to be expressed in homogenous coordinates for affine matrix transformations to be possible. If this makes no sense to you, it is safe (most of the time) to just accept that there has to be a 1 in the fourth component of the vector.

The last remaining piece of the puzzle is to create the view projection matrix, so that it can be fed to the vertex shader and bound to the viewProjectionMatrix variable. Those of you who have a perverted math fetish may go ahead and calculate the transformation matrix manually, but the rest of us will use a preexisting math library function instead. I highly recommend the free GLGX library for matrix and vector operations. It was created specifically for use with OpenGL. Moreover, GLGX is heavily inspired by the DirectX utility library D3DX. For almost every function in D3DX, there is a corresponding GLGX function.

Using GLGX, you would create the view projection matrix by using the following function call:

GLGXMatrixOrthoOffCenter2D(&viewProjectionMatrix, 0.0, 320.0, 480.0, 0.0);

I'm too lazy to go into how to bind the C matrix to the matrix variable in the vertex shader, so I'll leave it as an exercise to the reader for now.


23
OCT
2009

Debugging OpenGL

Hardware accelerated graphics code is hard to debug. This is a fact. I know that from experience. Some of the most frustrating bugs I have ever encountered have been 3D related.

Over the years I have learned that catching the bugs early saves a lot of time and work in the end. It may also prevent you from going insane. I value my sanity, so I have decided to make my debug build help me as much as possible.

Apple recommends that you check the OpenGL error flag often and I concur. However, calling glGetError() too often results in a performance penalty. For this reason, I only check for GL errors in my debug build, where I actually check for errors after every single GL function call. That's how dedicated I am to squashing those pesky little bugs! (I like the movie Starship Troopers because it's all about bug killing.)

To be more specific, I have wrapped glGetError() in a function called checkGL() which programmatically triggers a breakpoint whenever an error has been detected.

Here's some code that you may adopt as your own, as long as you don't blame me if something goes wrong. Yes, this is a disclaimer. If you use any of my code, you are doing so at your own risk, I take no responsibility whatsoever. That said, I don't think there are any problems with this code...

Here's the header file (GLError.h):

#ifdef __cplusplus
extern "C" {
#endif
 
#if TARGET_IPHONE_SIMULATOR
#define BREAKPOINT __asm__ volatile ("int3");
#else
#define BREAKPOINT __asm__ volatile ("bkpt 1")
#endif
 
#ifdef DEBUG_OPENGL
#define CHECK_GL checkGL()
void checkGL(void);
#else
#define CHECK_GL
#endif
 
#ifdef __cplusplus
}
#endif

And here's the implementation file (GLError.m):

#import "GLError.h"
 
#import <OpenGLES/ES2/gl.h>
#import <OpenGLES/ES2/glext.h>
 
void checkGL(void)
{   
    GLenum err;
    if (err = glGetError())
    {
        switch (err)
        {
            case GL_INVALID_ENUM:
                NSLog(@"OPENGL ERROR: GL_INVALID_ENUM");
                BREAKPOINT;
                break;
 
            case GL_INVALID_VALUE:
                NSLog(@"OPENGL ERROR: GL_INVALID_VALUE");
                BREAKPOINT;
                break;
 
            case GL_INVALID_OPERATION:
                NSLog(@"OPENGL ERROR: GL_INVALID_OPERATION");
                BREAKPOINT;
                break;
 
            case GL_OUT_OF_MEMORY:
                NSLog(@"OPENGL ERROR: GL_OUT_OF_MEMORY");
                BREAKPOINT;
                break;              
 
            default:
                NSLog(@"OPENGL ERROR: 0x%x", err);
                BREAKPOINT;
                break;
        }
 
    }
}


12
OCT
2009

Shady Business

I was surprised to find that OpenGL ES 2.0 does not have support for a fixed function pipeline (FFP) at all. You have to use shaders. Obviously, shaders are powerful and give you a level of flexibility that is not possible with a FFP. Unfortunately, it also means that you have to implement all the calculations of the FFP yourself, even when all you really need are the simple features that the standard FFP provides out of the box in OpenGL ES 1.x.

Initially, this means more work to get something to show up on the screen, but once you have implemented a basic shader, you will have a template to start with for future shaders, so it's really only a one-shot effort.

I wrote my first shader in a sort of shader assembly language back in 2001. Since then various high level shader languages have appeared, which makes writing shaders much less of a pain in the nether regions. I believe HLSL and GLSL are the most well known shader languages and unless I am mistaken the OpenGL ES has its own variation of GLSL called OpenGL ES Shading Language. I highly recommend learning about the shader assembly instructions even if you are only going to use a high level language, because it gives you a better understanding of the fundamental functionality of the shader hardware.

While I have written a lot of shaders over the last few years, I have exclusively been using HLSL, because I have been using the XNA framework on the Xbox 360. The underlying concepts should essentially be the same so hopefully I will be able to pick up the ES shading language quickly. As soon as I have a working example shader, I will post my findings here.


30
JUL
2009

iPhone Racer, Part 6

Making the wheels spin is actually a bit more complicated than one might think. The 3D model of the car is divided in five different geometric objects. Four of these objects represent the wheels and the fifth object is the rest of the car.

Each object is defined by a number of vertices (points) in 3D space. If you want to spin a wheel, you have to rotate its vertices around its center axis. The problem is that when you rotate vertices using OpenGL, they are rotated around the origin of the coordinate system. Consequently, if you apply a rotation transform to a wheel, it will not spin around its center axis. So what you have to do is move the wheel to the origin, rotate it, and then move it back to its original position.

Transforms

I extended my OBJ converter script to calculate the center point for each object in the 3D model and then save it along with the geometric data. Then in the car rendering code, I added a translation transform to move the wheel to the origin by subtracting the center point, followed by a rotation, and another translation to move it back by adding the center point again.

The order of the OpenGL transformation calls may look like they're in the wrong order, but this is due to the order in which the transformation matrices are multiplied.

Currently, it takes one separate call to the drawing function for each object in the 3D model, because different transforms have to be applied to the objects. This can be optimized by using the matrix palette OpenGL extension.

Instead of pushing matrices on the OpenGL matrix stack, you precalculate the entire transformation matrix for each object and place them in a matrix palette, which is really an array of matrices. Then each vertex struct is extended with a field that contains an index into the matrix palette, indicating which matrix should be used to transform that particular vertex.

This way you only need one call to the OpenGL draw function to draw the entire model, because no state changes need to occur in between the rendering of the different objects in the model.


30
JUL
2009

iPhone Racer, Part 5

I have added lateral movement of the car. Whenever your finger touches the screen, the "requested" lateral position of the car is updated. The car then moves towards the requested position at a preset rate. If the car moved to the new position immediately, it would look like it was teleporting.

I think I'll probably add spinning wheels next.


28
JUL
2009

iPhone Racer, Part 4

I created a fairly ugly Formula 1-type car in 3D and exported it as an OBJ file. The OBJ format is a very simple and fairly common plain text format for 3D objects. However, it is not a very compact format, so it takes up a lot of space.

The usual approach is to create an importer in the game engine that can parse and load the OBJ file, but I created a converter in REBOL instead. The converter reads the OBJ file, optimizes the geometric data for use with vertex buffer objects in OpenGL, and finally saves the data in a binary file.

I then wrote a corresponding file loader in C++ in the game framework. It basically loads the binary file straight into the vertex buffer object, since the converter has already organized the data in the same way it is stored in memory.

A lot of things can be improved, but at least now I have a 3D car to play with in my game code. I guess the road may be next!


27
JUL
2009

iPhone Racer, Part 3

The time has come to start thinking about the technical details of the iPhone racing game. I like to attack my projects on multiple fronts from the get-go, as it gives me a sense of the overall architecture and requirements quickly.

Placeholders

This is probably not the right place to start, but I've created placeholders for the app icon and the loading screen. I would rather look at placeholders than the default white icon and black loading screen, given how many times you see the icon and loading screen during the development of an iPhone app. More importantly, I had fun doing it.

Game Framework

My iPhone game framework interface is heavily inspired by XNA and the game state XNA sample. Obviously, the implementation of the interface is completely different, but anyone who has ever used XNA would feel at home using my framework. However, I have not implemented anything similar to the content pipeline... yet.

The framework was/is implemented in a hybrid of Objective-C and C++, where Objective-C is really only used for Cocoa specific functionality and the fundamental app skeleton. The main reason for this decision is that I am hoping to be able to reuse some C++ code that I have written in the past for other platforms.

Graphics

I've decided to use 3D graphics and OpenGL is the obvious, not to mention only, choice for that purpose. Fortunately, I have previous experience with OpenGL, both from work and my own projects. The first time I used OpenGL was in 1998, on a 3Dfx Voodoo 2 GPU. Since then, OpenGL has come a long way. Still, I prefer Direct3D over OpenGL, but OpenGL will have to do.

When I did my master's thesis on terrain rendering, I started out in OpenGL, but I switched over to Direct3D very early on. Fortunately, my abstraction layer allowed me to completely replace the renderer in one single day. It would probably be a good idea to create a similar abstraction for this project, even if OpenGL is the only option on the iPhone.

Audio

When it comes to audio, I will go with OpenAL. I have already written a very, very basic implementation of an OpenAL audio engine. I will build on that to create something that is actually usable in a game.


25
JUL
2009

iPhone Racer, Part 2

Let's flesh out the game design for the iPhone racing game a little bit more. In the previous post I mentioned the fundamentals. This time I will go into more detail.

Obstacles

The player has to avoid obstacles on the road. There are obstacles that move, in the form of other cars. Bumping into a moving car will slow the player's car down. There are also static obstacles, such as:

  • Rocks
  • Brick walls
  • Trees
  • Oil patches
  • Water puddles
  • Pot holes

Bumping into large static obstacles will stop the car completely for a brief period of time. Oil patches will temporarily make the player unable to control the car. Water puddles will slow the car down.

Some obstacles are not really obstacles however. If the player runs into e.g. a fallen tree trunk, the car will jump. This can be used to jump over certain obstacles.

Obstacles are spawned randomly, but under controlled circumstances, to ensure that the track is not the same every time.

Levels

Each level consists of a stretch of road of a certain distance. At the end of every level, there is a checkpoint marker. The player has to reach the checkpoint within a certain time limit, or the game is over. Once the checkpoint marker has been passed, the timer is reset.

The levels get progressively more difficult. There are mainly three parameters for controlling the difficulty:

  1. The time limit.
  2. The number of obstacles.
  3. The speed of the car.

Racing in the Desert

Different environments will provide visual variety. Colors of the scenery and objects at the side of the road will change depending on what type of environment a particular level represents. It could be a desert, a winter landscape, a forest, a tropical island, or just about any other kind of setting.

Score

The objective of the game is to maximize the player's score. The player gets points for picking up flags along the way. The player also gets points for the amount of time left on the clock when a checkpoint is reached.

An optional feature would be to implement an online global high score table. This would hopefully add competitive value to the game.

Controls

The control scheme is easy: the player can only move the car sideways. The speed of the car is determined by the game. The player will move the car laterally by moving a finger, left and right, across the screen.

Game Over

The game ends when the player does not make it to a checkpoint on time. There is no set number of levels, but at some point, the game will get so ridiculously hard that the player will run out of time, no matter how skilled he (or she) is.


24
JUL
2009

iPhone Racer Mockup

OK, so I may as well kick off this blog with an entry about a new project. I've wanted to create a game for the iPhone for quite some time now and I have actually written parts of a game engine for the iPhone already. For some reason I just haven't been able to find the time to create an actual game. A lot of that probably has to do with the scope. I need to start with a very small project.

So, I have come up with an idea for a simple game that could still be entertaining. It's basically like the game Buggy Boy, but with a straight road. If I get that far, I can consider throwing some turns and curves in there as well, but let's not get ahead of ourselves here!

Here's a mockup picture I created in about 3 minutes:

Racer Mockup

The objective is simple. Avoid obstacles like other cars and the big grey thing (which is supposed to be a rock) and pickup flags for points. As you advance through the game, there will be more obstacles and the car will go faster. The objective is to get the highest score possible. An online high score list could possibly add motivation, but that is not crucial for the initial implementation.

Now that I'm blogging about it, it will haunt me for years if I don't finish it, because Google will cache this post for me for a long time. That should motivate me to keep going even when my dayjob makes me tired of coding, if ever so briefly.

Let's get this thing on the road! Pun intended.


About This Site

Hello, my name is Martin Johannesson and this is my home on the web. I live in Stockholm, Sweden, where I work as a software engineer at a software company.

Ever since I was a kid and discovered the art of programming on my C64, I've been tinkering with my own little software projects and experiments. This site is one such experiment.
more...

Recent Entries RSS Feed

Tags

Amiga blog C Cocoa game GLGX GLSL iOS iPad iPhone Java jQuery Mac Mac OS X Objective-C OpenAL OpenGL Programming REBOL Shaders Vertex Shader web

Blog Archive

2011: 01 02 03 04 05 06 07 08 09 10 11 12
2010: 01 02 03 04 05 06 07 08 09 10 11 12
2009: 01 02 03 04 05 06 07 08 09 10 11 12

Random Images Load new images

loading
loading
loading
loading
loading
loading
loading
loading
loading
loading
loading
loading