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


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