I expect to send out the final beta of Removr this weekend and submit it some time next week. I’m just waiting for more levels.
Removr now has its own web site, removrapp.com, Facebook page, and Twitter account.
iOS and Mac developer
I expect to send out the final beta of Removr this weekend and submit it some time next week. I’m just waiting for more levels.
Removr now has its own web site, removrapp.com, Facebook page, and Twitter account.
The last iPhone update went out yesterday, with some new & improved levels and a few graphic enhancements & bug fixes.
I also added file sharing for access to the level database. I’ll release the level editor to the general public when I release the app and at some point I plan to have level design contests. I’ll also probably make additional levels available through in-app purchasing.
I now have a fully working level editor for Removr, which I just sent to my beta testers. For the next beta, I hope to have some nicer levels. If anyone is interested in joining the next beta, let me know.
A few weeks ago I wrote about why I chose SQLite rather than CoreData for my level maps. One big advantage is that levels can be added, modified or deleted by executing SQL statements. If I add in-app purchasing of additional levels, this will make it very easy to do.
During the beta, I have it check for available database updates on my server, so I can add new levels or replace the level database without requiring an app update.
The first Removr beta went out today. I wasn’t happy with how some of the pieces looked on my iPhone 4, so yesterday I cleaned up the graphics and added Retina Display support. Cocos2d doesn’t have built-in support for the retina display, so I had to check the scaling and load a @2x graphic every place I load any image. If you’re interested in seeing how I did it, I posted some code here.
I’m now working on making my level editor more usable. At some point in the future I may release it publicly and allow user-contributed levels.
I hope to send out the first Removr beta in a few days, and I still have a few more slots open for beta testers. If you’re interested, send me your iPhone’s UDID via my contact form.
Removr is now feature complete. I’m only waiting for the level maps & I’m still working on the sound effects with xfxr. In the next week or so I hope to send out the first beta.
A lot has been written about the relative benefits of Core Data or SQLite in an iPhone app. In most cases, Core Data is easier and has some performance benefits, but the data store is difficult to create & modify from outside of your application. SQLite, on the other hand, has some excellent tools like SQLiteManager for creating & editing data. Therefore, if you have a large amount of pre-loaded data that you want to be able to edit easily, SQLite makes more sense.
This is exactly the case with Removr. Game levels are defined by an object that specifies the background image and a bitmap of the layout of pieces on the screen. Although Core Data could easily fetch the object as needed in Removr, it would be very difficult for an external level editor to create those objects and update the persistent store.
My level object happens to be very simple and can easily be mapped to a database structure.
// Level.h @interface Level : NSObject { NSData * _map; NSString * _background; NSNumber * _index; } @property (nonatomic, retain) NSData * map; @property (nonatomic, retain) NSString * background; @property (nonatomic, retain) NSNumber * index; @end // Level.m #import "Level.h" @implementation Level @synthesize map = _map, background = _background, index = _index; - (void)dealloc { self.map = nil; self.background = nil; self.index = nil; [super dealloc]; } @end
The corresponding database structure looks like this:
CREATE TABLE levels ( ix integer NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, background text, map blob NOT NULL );
Fetching the objects from sqlite takes more code than using Core Data but isn’t too difficult. I need to deal with two different SQLite3 objects for the database connection and the prepared query. Rather than interpreting the SQL query every time you execute it, you will prepare the query once and execute it as many times as you want.
A good introduction to the SQLite3 C interface is available here.
Here is my level manager. My init method opens the database connection and prepares the query that will be used later. Note that the query includes a ‘?’ for variable substitution.
The GetLevel method is where the interesting stuff happens. The function sqlite3_bind_int() tells it which level number we’re looking for and sqlite3_step() actually executes the query. In this case, I’m only interested in a single row, but in many cases it will be called repeatedly as long as it returns SQLITE_ROW. I then use the sqlite3_column functions to extract data from the result row and populate my Level object. Finally, calling sqlite3_reset() will allow the prepared statement to be reused.
// LevelManager.h @interface LevelManager : NSObject { NSString *_dbpath; sqlite3 * db; sqlite3_stmt * query; } @property (retain,nonatomic) NSString *dbpath; @end // LevelManager.m #import "LevelManager.h" #import "Level.m" @implementation LevelManager @synthesize dbpath = _dbpath; - (id) init { if ((self = [super init])) { self.dbpath = [[NSBundle mainBundle] pathForResource:@"levels" ofType:@"sqlite3"]; sqlite3_open([self.dbpath UTF8String] , &db); // this query will be used to obtain a level from the database. // The '?' will be replaced with the level number when we perform the query sqlite3_prepare_v2(db, "SELECT * FROM levels WHERE ix=?", -1, &query, NULL); } return self; } - (void)dealloc { sqlite3_finalize(query); sqlite3_close(db); self.dbpath = nil; [super dealloc]; } - (Level*)GetLevel: (int)number { Level *lvl = nil; // specify the level number we want for the query sqlite3_bind_int(query, 1, number); // request a row from the query result if (sqlite3_step(query) == SQLITE_ROW) { void *blob; int nbytes; // first, create a new level object lvl = [[Level alloc] init]; // integer columns are easy lvl.index = [NSNumber numberWithInt: sqlite3_column_int(query, 0)]; // string columns are a bit more complex since we need to convert a C string to a NSString lvl.background = [NSString stringWithCString:(char*)sqlite3_column_text(query, 1) encoding:NSUTF8StringEncoding]; // BLOB columns require two calls to obtain the actual data and the length blob = (void*)sqlite3_column_blob(query,2); nbytes = sqlite3_column_bytes(query,2); // we use the bytes & length to create a NSData if (blob && (nbytes > 0)) { lvl.map = [NSData dataWithBytes:blob length:nbytes]; } } // get ready to reuse the query sqlite3_reset(query); // we should return an autoreleased object return [lvl autorelease]; } @end
I got the final artwork for Removr and I have the basic gameplay working. I still need to design all of the levels (I only have 4 simple ones so far). I’ll have something to show by WWDC.
You might have seen Patrick Mandia’s post about upcoming projects, which included this screenshot:
Removr is one of the two cocos2d-based games I’m working on. Patrick is working with Toffeenut Design on the artwork, while I’m doing the coding.