In this article I would like to share with you the story of creating our company's first iOS game at a hackathon event, using a wonderful 2d graphics engine
Cocos2d. It covers some technical problems, we've bumped into, while developing the game, as well as the process of creating the actual gameplay. The resulting app can be found
here.
How We Did It
It was about 6 o'clock in the morning in Munich, when we met with my colleagues Anton and Valentine to work on an idea for an inside-company hackathon, which has kind of turned into a monthly event at Empatika. None of us had had an experience with any serious game development, but we thought it would be kind of cool to develop a game, since we were all tied up in the regular app projects for so long and wanted to try something new and exciting.
The initial idea we chose was a pie slicer game, where you had a nice round piece of pie, which you had to vigorously cut into small pieces in a limited amount of time. The pieces were to be me moved with the power of some kind of physics engine, so it all wouldn't look too boring. After some research and poking around, we found out, that we would be most productive with
cocos2d (since Anton I are both iOS-Devs) and
box2d (since it's free and plays nicely with cocos2d), and if we would limit ourselves only to the iOS platform.
The core for the project was found in the nice
tutorial by Allen Tan, so we didn't have to go all hardcore on the implementation of cutting and triangulation algorithms. The tutorial relies on the
PRKit library, which allows drawing of a convex textured polygon and extends its PRFilledPolygon class in order to provide some additional functionality like synching with the box2d's physical body. We decided to borrow this extended class to build our implementation on top of it.
In spite of the hardest part already being written for us, the first complications came soon. After the inital project setup and a couple of test runs we found out about the famous 8 vertices per body limitation of box2d. In order to use the example code and the provided libraries, the pie had to be a polygon, because box2d doesn't allow a shape to be a segment of a circle (which we would get after cutting the initial shape into multiple pieces). So since the pie had to be at least relatively round and cuttable at the same time, we had to compose it from an array of 8-verticed shapes. It created some minor texturing problems, since the initial tutorial only went in detail about the implementation of such for the whole bodies. However, after some fiddling, we managed to overcome this difficulty by feeding the PRFilledPolygon an array of vertices, composing the outer body edge.
Everything seemed to be fine and dandy so far - our pie was floating in 0 gravity in the unpromising blackness of the iPad screen:
However the initial cutting algorithm for sprites had to be modified to support the bodies composed from multiple shapes. After some thinking we decided to overcome this difficulty by simply increasing the 8-vertices per shape limit of the box2d. So we bumped up that number to 24 vertices (which would be definitely too crazy for any relatively serious project). The profiling showed, that in our use case it didn't make a huge difference, whether the pieces were composed of 8 or 24 vertices. However there was another problem: when the amount of small-cut pieces was close to 200, the FPS dropped to about 10 frames, and made it pretty much impossible to play the game. Part of that was calculation of the collisions (about 20% of the processor time) and another part was drawing and animating all the mini-pieces, bumping into each other after each cut.
The decision came quickly. As soon as a piece turned small enough, we turned off the collisions calculation for it. The game was still pretty slow, which pushed us to slightly change the gameplay: the small pieces were to be removed from the screen and added to the payer's "jar". The size of the cleared area determined the performance of the player. Some degree of linear and angular damping were also applied to the pieces, so they wouldn't fly around the screen in a crazy manner:
By this time Valentine had drawn a nice-looking pie picture. It looked awesome but seemed to realistic for such an oversimplified cutting process. So we decided to change it to a simply drawn pizza (the credit for the textures goes to their original rights owners):
However it also felt too unnatural, and at this point it was clear that the design had to be changed to something not as realistic as a pie or a pizza. Cutting of simple geometrical primitives seemed like the way to go. Since the redesign was easy and played nicely with the chosen technology (PRFilledPolygon basically allowed to do exactly that), we implemented it pretty quickly. Every cut polygon was also stroked, which was done by adding a CCDrawNode to each slice and feeding it an array of vertices, shaping the outer body of the polygon. It turned out to be pretty slow, but still faster and nicer-looking than using the standard ccDraw methods:
The game started to take the right direction, but the gameplay wasn't quite there yet. It definitely lacked some challenge. And what makes a better challenge than some obstacles and enemies? So we introduced a simple enemy - a red dot, that would interfere with cutting of the primitive. Good, but it could be better. How about some moving lasers? Done. The implementation was simple and only involved calculation of the point-line distance to the user's touch point.
With the game design and enemies down, we wrote a world-based level system. All the levels were stored in separate .plist files and described the shape, texturing rules, enemies positions, level duration and some other parameters. The game-objects tree was populated from the .plists using the standard Objective-C KVC. For example:
//......
- (void)setValue:(id)value forKey:(NSString *)key{
if([key isEqualToString:@"position"] && [value isKindOfClass:[NSString class]]){
CGPoint pos = CGPointFromString(value);
self.position = pos;
}
else if([key isEqualToString:@"laserConditions"]){
NSMutableArray *conditions = [NSMutableArray array];
for(NSDictionary *conditionDescription in value){
LaserObstacleCondition *condition = [[LaserObstacleCondition alloc] init];
[condition setValuesForKeysWithDictionary:conditionDescription];
[conditions addObject:condition];
}
[super setValue:conditions forKey:key];
}
else{
[super setValue:value forKey:key];
}
}
//......
//Afterawrds the values got set with the dictionary, read from the plist file:
[self setValuesForKeysWithDictionary: dictionary];
To represent the world-level system, we used the standard CCMenu with some additions to it:
CCMenu+Layout (lets you layout the items in a grid with a proper padding) and
CCMenuAdvanced (has a scroll addition to it). So Valentine got busy with the level design, and Anton and I took off to write some effects.
For the visual effects part we gladly borrowed
CCBlade, which animates the user's touches, and powered it with some cool Star Wars-like sounds. The other effect we did, was disappearing of the small pieces. Cutting them without any interface feedback was too boring, so we decided to make them fade out with a small plus sign over them.
The fade out part involved adopting the CCLayerRGBA protocol by the PRfilledPolygon. To do that we changed the default shader programm to kCCShader_PositionTexture_uColor:
-(id) initWithPoints:(NSArray *)polygonPoints andTexture:(CCTexture2D *)fillTexture usingTriangulator:(id)polygonTriangulator{
if( (self=[super init])) {
//Changing the default shader program to kCCShader_PositionTexture_uColor
self.shaderProgram = [[CCShaderCache sharedShaderCache] programForKey:kCCShader_PositionTexture_uColor];
}
return self;
}
and passed the color uniform to it:
//first we configure the color in the color setter:
colors[4] = {_displayedColor.r/255.,
_displayedColor.b/255.,
_displayedColor.g/255.,
_displayedOpacity/255.};
//then we pass this color as a uniform to the shader program, where
colorLocation = glGetUniformLocation( _shaderProgram.program, "u_color")
-(void) draw {
//...
[_shaderProgram setUniformLocation:colorLocation with4fv:colors count:1];
//...
}
It looked kind of nice, but with the stroke and the other effects the FPS dropped pretty low, especially when cutting through a bulk of pieces, which involved a lot of animations. A quick googling didn't really give us anything, and we decided to move on by simply increasing the minimum area of the piece, that could be still present on the screen. It allowed a smaller amount of pieces to be simultaneously drawn and animated, which boosted the FPS. The fade out effect was also removed, and all the plus sign sprites were moved into a batch node (which was dumb of us not to use in the first place):
The sound effects were done by writing a small convenience wrapper around the Simple audio engine. While implementing it, we bumped into the format problem: the .wav files we used, had to be converted into 8 or 16 bit PCM. In the other case they either wouldn't be played at all or played with some noticeable cracking sound.
After all of that done we finally implemented the shop, where a user could buy stars if he/she hadn't earned enough of them, while pacing through the game worlds, or share a picture in one of the social networks to get the stars for free:
At this point the competition's time pressure was starting to get high and it was time to release the game to the public. Frantically fixing some late-found bugs, we uploaded the binary to the app store in the hopes of it passing its first review.
Once again, the resulting app can be found
here
Awesome article. Thanks for sharing!