Welcome!

Welcome to my site. I initially created it with the purpose of showcasing my development experience while creating my first iPhone game, StickWars. However, after the release and huge market response to the game, this site mainly serves to provide news and support for StickWars. You can find answers to many questions in my forums as well as post any other questions you might have.

While a lot of the posts are about updates about StickWars, I try to post code snippets and tips to get through roadblocks that I experienced while building my first iPhone game. If you have questions or would like to see some code to help you along, contact me and I’ll try to throw something up.

Beginning with Cocos2d

For those of you who are still struggling with learning cocos2d, I would recommend Cocos2d for iPhone 0.99 Beginner’s Guide from Packt Publishing. They were nice enough to provide me a copy of the book, and I’ve been slowly going through it while preparing to write a complete review.

The examples the book used were very relevant to the types of games that many developers are trying to create, and the book does a good job of breaking down the steps of putting the games together. Again, for those of you who are new to cocos2d, I would suggest checking it out.

StickWars 2 is ALIVE

After a long time in development, the sequel to one of the top iPhone games is finally ready for release.

StickWars 2 has all the gloriously simple gameplay that made the original game so addictive, but adds a vast amount of new levels, spells, enemies, and other content. There is now helpful screens to guide a player how to use the more advanced features of the games, as well as a bit of a storyline for the campaign.

I can say much more about it, but I’d rather you check it out yourself on the AppStore. The game is $0.99, same price as the original, and like the original will receive updates in the coming weeks to flesh out the content, balance, and spells in response to player feedback.

StickWars is FREE for a limited time!

The title pretty much explains it all…StickWars (the full version) is the current FAAD main feature, meaning if you always wanted to upgrade to the full version but couldn’t afford the massive $0.99, now is your chance.

With the full version, you get unlimited levels, two free Unlimited Challenge games, can Challenge OpenFeint friends to beat your score with push notifications and get online high score rankings.

Get it FREE here in the iTunes App Store!

Also join our new official Facebook page for news and updates!

StickWars gets a Second Wind

StickWars was featured on Apple’s list of best games of 2009 and the list of best selling apps of 2009. Each of the lists features 30 apps each and are very well promoted within iTunes. I have no idea why they would chose to feature it twice on somewhat similar lists, but I can imagine it has to do something with the ~3 month delay getting my last update approved. While this is nothing but a guess, if true then it’s fantastic of Apple to take that into account. Or maybe they just really like playing StickWars…

Between the new visibility from being featured as well as what I imagine to be  a lot of word of mouth promotion during the holiday season as people open their new iPod Touches, sales have jumped up and StickWars has risen up the lists. It is now the top 40 paid app in the US, and it is actually back on the top grossing list at 99. For anybody who doesn’t know, the spike in sales of apps during Christmas holiday is like a big fat Christmas bonus to iPhone developers, and it’s a great feeling getting this final push before headed into the new year.

I meant to make a long post detailing my frustrating experience trying to get version 1.7 of StickWars approved for over 3 months, but I’ll sum it up by saying Apple had some unexpected technical problems with their approval process, they worked on it for a while, fixed the issues, and called me personally to help StickWars through the approval process once the issues were fixed. While it was very frustrating to develop a big release (including all those new challenge mini-games available as an in-app purchase) and then have to sit on it as StickWars slipped down the charts for 3 months, Apple has done what they can to make the process quicker and less painful, and being featured prominently twice by them is a unexpected gift that really helps me let go of my anger over the previous delay :) .

Slime Ball has completed its first cycle of rising up and then falling back down the charts. Both the free and the paid version are now of the top 100 lists, although we have some fun updates planned that will hopefully breath some life back into them. It’s time to go hit up the keyboard and get back to work…

Slime Ball Release on the AppStore

I finally have something new I’ve worked on in the AppStore! While the game is created and owned by Pinger, I had a part in bringing it to life.

The game is called Slime Ball, and it’s a fun remake of Slime Volleyball. It’s very simple to pick up and play, and is addictive to play both against the computer and against another human.

Paid version
Lite version

I had a fun time working on it and can say that I believe most people who try it out will find it fun. We spent a ton of time tweaking the controls and other subtle elements that really make gameplay fun, and I think that effort shows in this release.

You can play against many levels of computer players which get progressively more difficult, or you can play against another player over Bluetooth. The game uses OpenFeint to track achievements and leaderboards.

StickWars Ultimate Challenge hits the AppStore

I’m pleased to announce that StickWars Ultimate Challenge is live on the AppStore. It is a collection of mini-games based on the addictive StickWars gameplay, and allows you to directly challenge your OpenFeint friends to compete for the top scores.

You can read more about it here, or just click here to visit the iTunes page.

I have been working on this update for months, and while it was originally slated to be an in-app purchase included with StickWars, technical limitations imposed by Apple dictated  that I release it as a completely separate app. You can try out two of these minigames for free in StickWars or StickWars Lite version 1.7. Have fun, and as always I welcome all feedback, so visit my forums and let me know what you think.

Easy To Create Buttons with Cocos2D

Those of you who use cocos2d a lot might understand why I created this class as some point. Hopefully it may save some of you those nasty 5 line blobs that you normally need to create a simple button. Usage is simple, just do:

[self addChild:[Button buttonWithText:@"back" atPosition:ccp(80, 50) target:self selector:@selector(back:)]];
[self addChild:[Button buttonWithImage:@"openFeint.png" atPosition:ccp(400, 50) target:self selector:@selector(openOpenFeint:)]];

You’ll need to create your own button.png and button_p.png (the second one is the image shown when you are touching the button). Also you’ll need to choose your own font. Here is the code…

//
//  Button.h
//  StickWars - Siege
//
//  Created by EricH on 8/3/09.
//
 
@interface Button : Menu {
}
+ (id)buttonWithText:(NSString*)text atPosition:(CGPoint)position target:(id)target selector:(SEL)selector;
+ (id)buttonWithImage:(NSString*)file atPosition:(CGPoint)position target:(id)target selector:(SEL)selector;
@end
 
@interface ButtonItem : MenuItem {
	Sprite *back;
	Sprite *backPressed;
}
+ (id)buttonWithText:(NSString*)text target:(id)target selector:(SEL)selector;
+ (id)buttonWithImage:(NSString*)file target:(id)target selector:(SEL)selector;
- (id)initWithText:(NSString*)text target:(id)target selector:(SEL)selector;
- (id)initWithImage:(NSString*)file target:(id)target selector:(SEL)selector;
@end
//
//  Button.m
//  StickWars - Siege
//
//  Created by EricH on 8/3/09.
//
 
#import "Button.h"
 
 
@implementation Button
+ (id)buttonWithText:(NSString*)text atPosition:(CGPoint)position target:(id)target selector:(SEL)selector {
	Menu *menu = [Menu menuWithItems:[ButtonItem buttonWithText:text target:target selector:selector], nil];
	menu.position = position;
	return menu;
}
 
+ (id)buttonWithImage:(NSString*)file atPosition:(CGPoint)position target:(id)target selector:(SEL)selector {
	Menu *menu = [Menu menuWithItems:[ButtonItem buttonWithImage:file target:target selector:selector], nil];
	menu.position = position;
	return menu;
}
@end
 
@implementation ButtonItem
+ (id)buttonWithText:(NSString*)text target:(id)target selector:(SEL)selector {
	return [[[self alloc] initWithText:text target:target selector:selector] autorelease];
}
 
+ (id)buttonWithImage:(NSString*)file target:(id)target selector:(SEL)selector {
	return [[[self alloc] initWithImage:file target:target selector:selector] autorelease];
}
 
- (id)initWithText:(NSString*)text target:(id)target selector:(SEL)selector {
	if(self = [super initWithTarget:target selector:selector]) {
		back = [[Sprite spriteWithFile:@"button.png"] retain];
		back.anchorPoint = ccp(0,0);
		backPressed = [[Sprite spriteWithFile:@"button_p.png"] retain];
		backPressed.anchorPoint = ccp(0,0);
		[self addChild:back];
 
		self.contentSize = back.contentSize;
 
		Label* textLabel = [Label labelWithString:text fontName:@"take_out_the_garbage" fontSize:22];
		textLabel.position = ccp(self.contentSize.width / 2, self.contentSize.height / 2);
		textLabel.anchorPoint = ccp(0.5, 0.3);
		[self addChild:textLabel z:1];
	}
	return self;
}
 
- (id)initWithImage:(NSString*)file target:(id)target selector:(SEL)selector {
	if(self = [super initWithTarget:target selector:selector]) {
 
		back = [[Sprite spriteWithFile:@"button.png"] retain];
		back.anchorPoint = ccp(0,0);
		backPressed = [[Sprite spriteWithFile:@"button_p.png"] retain];
		backPressed.anchorPoint = ccp(0,0);
		[self addChild:back];
 
		self.contentSize = back.contentSize;
 
		Sprite* image = [Sprite spriteWithFile:file];
		[self addChild:image z:1];
		image.position = ccp(self.contentSize.width / 2, self.contentSize.height / 2);
	}
	return self;
}
 
-(void) selected {
	[self removeChild:back cleanup:NO];
	[self addChild:backPressed];
	[super selected];
}
 
-(void) unselected {
	[self removeChild:backPressed cleanup:NO];
	[self addChild:back];
	[super unselected];
}
 
// this prevents double taps
- (void)activate {
	[super activate];
	[self setIsEnabled:NO];
	[self schedule:@selector(resetButton:) interval:0.5];
}
 
- (void)resetButton:(ccTime)dt {
	[self unschedule:@selector(resetButton:)];
	[self setIsEnabled:YES];
}
 
- (void)dealloc {
	[back release];
	[backPressed release];
	[super dealloc];
}
 
@end

2D Scrolling Game with Cocos2D TileMap with Zoom!

A common question I see on the cocos2d forums is ‘when I want to make my game scroll, do I move the camera or the layer?’ or some variant of that. I also got some more detailed questions about how to make a functioning 2D scroller, so I’m going to described how I got it to work.

First of all, the answer to the above question is you move the layer. If you follow the examples in the cocos2d download, you have a “GameScene” and a “GameLayer”. Well, everything that needs to be moved when your game scrolls should be added as a child to that GameLayer. If you are using a TileMap as a background, this includes that tilemap. The only thing that you don’t add as a child to the GameLayer is stuff that does not move with the scrolling view, such as your HUDLayer that has text that shows your characters health. Other than that, your character, the background, other characters, should all be added to the GameLayer.

You have to remember which objects are absolute (attached to your GameScene or other layers) versus those that are relative (a child of your GameLayer) when you set up your touch handling code. For my HUD that has buttons you can press at any time, say to pause the game, you want to add the touch handling object to your GameScene or HUDLayer class, since it doesn’t move. But if you want to be able to touch objects that scroll along with your view in the game itself, your touch handling code needs to be in an object that is a child of your GameLayer.

This might be a little confusing, so let’s see some code:

	gameLayer = [GameLayer node];
	[gameScene addChild:gameLayer z:zOrder_GameLayer];
 
	hudLayer = [HUDLayer node];
	[gameScene addChild:hudLayer z:zOrder_HudLayer];
 
	tileMap = [BGTileMap node];
	[gameLayer addChild:tileMap z:-1];
 
	[gameScene addChild:[PauseGameButton node] z:zOrder_GameButtons];
	[gameLayer addChild:[FireGunAtTouchPoint node]];

The pause game button is always on your screen, while the point at which your character fires the gun depends on how how far your view has been scrolled (by moving GameLayer).

Let’s see some of the code that actually moves this game layer:

- (void)setViewpointCenter:(CGPoint)point {
	CGPoint centerPoint = ccp(240, 160);
	viewPoint = ccpSub(centerPoint, point);
 
	// dont scroll so far so we see anywhere outside the visible map which would show up as black bars
	if(point.x < centerPoint.x)
		viewPoint.x = 0;
	if(point.y < centerPoint.y)
		viewPoint.y = 0;
 
	// while zoomed out, don't adjust the viewpoint
	if(!isZoomedOut)
		gameLayer.position = viewPoint;
}

When do you call that method? Well, it depends on what you want, but generally these scrolling games follow around the movement of your ‘main’ character, right? So whatever character the you want to follow, add this to override the standard CocosNode setPosition method so you update your viewpoint whenever the character moves

- (void)setPosition:(CGPoint)point {
	[[StandardGameController sharedSingleton] setViewpointCenter:point];
	[super setPosition:point];
}

Note that the StandardGameController is a construct of mine that I use to separate the game logic out from the display code. It doesn’t matter exactly how you do it, you just need a way to have your main character object call back to something that contains a reference to GameLayer so it can adjust the position of your GameLayer.

Now remember, for your background to scroll properly, you need to add your background tileMap as a child of your GameLayer that is being moved around.

That being said, I found that an important method was missing from the cocos2d tilemap that I need to use in order to detect collisions based on the types of tiles encountered. I created a subclass of TMXTiledMap and added in these methods:

- (CGPoint)coordinatesAtPosition:(CGPoint)point {
	return ccp((int)(point.x / self.tileSize.width), (int)(self.mapSize.height - (point.y / self.tileSize.height)));
}
 
- (unsigned int)getGIDAtPosition:(CGPoint)point {
	return [layer tileGIDAt:[self coordinatesAtPosition:point]];
}

That way it’s easy to figure out what tile any individual object is colliding with. For example, in my main character object I can have code that runs in step: function with this:

	BGTileMap* tileMap = [StandardGameController sharedSingleton].tileMap;
	CGPoint coordinate = [tileMap coordinatesAtPosition:self.position];
	BBLog(@"Right now on tile %d",[tileMap.layer tileGIDAt:coordinate]);

Now I know what type of tile I am overlapping, and I can respond to the environment accordingly.

This is really all the code that you need to make a scrolling game view…I think some people overthink it and try adjusting the position of every object individually with some offset, but it’s not necessary since your objects can use relative positions with their parent.

I have one last bit of code to add, and this is something kind of fun. It’s not complete, but at least it’s a start. What this allows you to do is ‘zoom out’ so you can see your entire map with ALL the objects shrunk down, and then zoom back in to your character. The only tricky part is when you zoom back in, you have to slowly adjust your viewpoint in steps so the zoom in action is centered on your character, instead of jumping at the end.

#define ZOOM_BACK_IN_INTERVALS 10
#define ZOOM_OUT_RATE 0.3
// TODO need to refine this so for each step it uses the new viewpoint
- (void)setZoom:(BOOL)zoomedIn {
	BBLog(@"Zooming in %d", zoomedIn);
 
	// this scales it out so the whole height of the tilemap is in the screen
	float zoomScaleFactor = 320 / (tileMap.mapSize.height * tileMap.tileSize.height);
	if(zoomedIn) {
		[gameLayer runAction:[ScaleTo actionWithDuration:ZOOM_OUT_RATE scale:1.0]];
		[gameLayer runAction:[MoveTo actionWithDuration:ZOOM_OUT_RATE position:viewPoint]];
		[self schedule:@selector(setZoomedBackIn:) interval:ZOOM_OUT_RATE]; // need this for the transistion
	} else {
		[gameLayer runAction:[ScaleTo actionWithDuration:ZOOM_OUT_RATE scale:zoomScaleFactor]];
		[gameLayer runAction:[MoveTo actionWithDuration:ZOOM_OUT_RATE position:ccp(0,0)]];
		isZoomedOut = YES;
	}
}
 
// need this small correction at the end to account of the player is moving and the viewpoint has changed to avoid jitter
#define ZOOM_OUT_CORRECTION_RATE 0.3
- (void)setZoomedBackIn:(ccTime)dt {
	[self unschedule:@selector(setZoomedBackIn:)];
	[gameLayer runAction:[MoveTo actionWithDuration:ZOOM_OUT_CORRECTION_RATE position:viewPoint]];
	[self schedule:@selector(setZoomedBackInFinished:) interval:ZOOM_OUT_CORRECTION_RATE]; // need this for the transistion
}
 
- (void)setZoomedBackInFinished:(ccTime)dt {
	[self unschedule:@selector(setZoomedBackInFinished:)];
	isZoomedOut = NO;
}

Now you see why my viewPoint variable was a class member…you’ll need it for these methods to work.

The reason this code isn’t complete is when it starts zooming in, it creates the actions to zoom in on the current viewpoint, but if the player is moving by the time the zoom animation is done, that viewpoint is changed and needs to ‘snap’ to the new viewpoint. That is the reason for the setZoomedBackIn: method, which really shouldn’t have to move the gameLayer anymore. However, I haven’t yet written the code to continuously create smaller move actions to take into account a moving viewpoint as the animation continues, but doing so shouldn’t be that hard. If you want to see that bit when I finish, post in the comments and I’ll add it in.

Using Box2D Physics Engine with Cocos2D iPhone

Starting work on my new project, I’ve found Box2D to be a far superior physics engine than chipmunk. It is more mature, the API more flexible, and it seems to even perform faster. However, the cocos2d code for it was rather space, so here is a sort of helper file I had to create to make it work with my game.

Keep in mind this game is in progress and I’ve only been using this for a few days, so it may have issues that might pop up later on. It seems to be working very well for now though, with 40-50 objects on screen moving around with ~40 FPS.

Here is the header

//
//  Box2DEngine.h
//
//  Created by EricH on 7/22/09.
//  Copyright 2009 __MyCompanyName__. All rights reserved.
//
 
#import "Box2D.h"
 
// made this an extern constant to avoid obj c lookup overhead
extern b2World *bb_world;
 
@interface Box2DEngine : CocosNode {
}
 
+ (Box2DEngine *)sharedSingleton;
- (void)createWorld:(CGSize)size;
- (void)deleteWorld;
- (void)runSimulation;
@end

The .mm file

//
//  Box2DEngine.mm
//
//  Created by EricH on 7/22/09.
//  Copyright 2009 __MyCompanyName__. All rights reserved.
//
 
#import "HookActor.h"
 
 
#import "Box2DEngine.h"
#import "SuperBox2DActor.h"
 
b2World* bb_world;
 
const float32 timeStep = 1.0f / 60.0f;
const int32 velocityIterations = 10;
const int32 positionIterations = 10;
 
#define MAX_NUM_COLLISIONS 2048 // TODO make sure this buffer is the right size
b2ContactResult contactResultCache[MAX_NUM_COLLISIONS];
int32 contactResultCount = 0;
 
class MyContactListener : public b2ContactListener {
public:
	void Add(const b2ContactPoint* point) {
	}
 
	void Persist(const b2ContactPoint* point) {
	}
 
	void Remove(const b2ContactPoint* point) {
	}
 
	void Result(const b2ContactResult* point) {
		// TODO we are making a deep copy of every contact point here
		// check the box2d contact masks to make sure we minimize unwanted contact results
		contactResultCache[contactResultCount++] = *point; 
	}
};
 
void handleCachedContactResults() {
	b2ContactResult contactResult;
	for(int i = 0; i < contactResultCount; i++) {
#ifdef BBDEBUG
		if(contactResultCount >= MAX_NUM_COLLISIONS) {
			NSLog(@"RAN OUT OF BUFFER");
			assert(NO);
		}
#endif
		contactResult = contactResultCache[i];
		SuperBox2DActor* actorOne = (SuperBox2DActor*)contactResult.shape1->GetBody()->GetUserData();
		SuperBox2DActor* actorTwo = (SuperBox2DActor*)contactResult.shape2->GetBody()->GetUserData();
		if(!actorOne.isDead && !actorTwo.isDead) {
			[actorOne collisionResultOne:&contactResult withActor:actorTwo];
			[actorTwo collisionResultTwo:&contactResult withActor:actorOne];
		}
	}
 
	// clear the collision cache
	contactResultCount = 0;
}
 
void removeDeadActors() {
	b2Body* node = bb_world->GetBodyList();
	while (node)	{
		b2Body* b = node;
		node = node->GetNext();
 
		SuperBox2DActor* actor = (SuperBox2DActor*)b->GetUserData();
		if (actor.isDead) {
 
			// remove from physics  engine
			bb_world->DestroyBody(b);
 
			// remove from game engine (cocos2d)
			[[StandardGameController sharedSingleton] removeGameActor:actor];
		}
	}
}
 
 
@implementation Box2DEngine
+ (Box2DEngine*)sharedSingleton {
	static Box2DEngine* sharedSingleton;
	if (!sharedSingleton)
		sharedSingleton = [[Box2DEngine alloc] init];
 
	return sharedSingleton;
}
 
- (void)createWorld:(CGSize)size {
	b2AABB worldAABB;
	worldAABB.lowerBound.Set(0, 0);
	worldAABB.upperBound.Set(size.width * BOX2D_SCALE_FACTOR_INVERSE, size.height * BOX2D_SCALE_FACTOR_INVERSE); // TODO this shouldnt be inverse?
 
	b2Vec2 gravity(0.0f, -30.0f);
	bool doSleep = true;
 
	bb_world = new b2World(worldAABB, gravity, doSleep);
	bb_world->SetContactListener(new MyContactListener());
}
 
- (void)deleteWorld {
	[self unschedule:@selector(step:)];
	delete bb_world;
	bb_world = NULL;
}
 
- (void)runSimulation {
	[self schedule:@selector(step:)];
}
 
- (void)step:(ccTime)dt {
 
	// step the world
	bb_world->Step(dt, velocityIterations, positionIterations); // TODO do i use timestep or dt here?
 
	//BBLog(@"Num of contact results is %d",contactResultCount);
	// do stuff with collisions
	handleCachedContactResults();
 
	// remove all actors that are marked as dead
	removeDeadActors();
 
	// update cocosnode positions
	for (b2Body* b = bb_world->GetBodyList(); b; b = b->GetNext()) {
		if (b->GetUserData() != NULL) {
			SuperBox2DActor *actor = (SuperBox2DActor*)b->GetUserData();
			b2Vec2 position = b->GetPosition();
			actor.position = b2toCGPoint(position);
			actor.rotation = -1 * CC_RADIANS_TO_DEGREES(b->GetAngle());
			//NSLog(@"obj %@ %4.2f %4.2f\n",actor, position.x, position.y);
		}
	}
 
}
 
@end

StickWars 1.5 Live after nearly a Month

After nearly a month of delay, StickWars 1.5 is live with important bug fixes and a few new features, including a few achievements. Luckily I’m all ready to push out v1.6 which includes a new boss enemy, the giant stick figure. I also added a lot of small fun features, so as the ability to throw and slam stick figures into one another to kill them. You can also now kill the more powerful enemies by slamming many small stick figures into them. I think this adds yet another way for talented players to save money and reach higher levels and scores :) .

In addition, I’ve re-written parts of the code for the basic functions behind levels and sound, and somehow I managed to fix a bug that I didn’t know existed. The game no longer crashes sometimes when you attempt to load a new level–the bug before was due to my background sound engine, except none of my debugging tools had located it there. Basically the last crash bug has been fixed, and since I’ve been adding features to this version for the past week I have not seen it crash once. I’m really proud of the stability of the game now.

There is a new loading screen that I’m particularly proud of–it allows me to include short hints that answer the most common questions I get from users based on reviews, emails, and forum posts. It’s a lot nicer to be able to read a short hint while you are waiting 3-6 seconds for the level to load, and maybe help out your gameplay, rather then just looking at the word ‘loading’ over your current screen. I can also throw in a few words about upcoming versions, such as my brief request for feedback about charging in game $0.99 for the new multi-player challenges coming in the next version. I’d love to get some player feedback on this–I feel most people are happy paying only $0.99 for StickWars, and I don’t want to raise the price for the same basic game (campaign mode), but these multi-player challenges are a whole new ballpark that I feel deserves another $0.99. However, I’m willing to listen to my users who feel strongly enough about this to contact me. I will be providing a few of the challenges free so a player can test them out.

The bad news is StickWars has fallen off the top 10 for the first time since it first rose up there in April. But sitting at #11, I’m hoping these new features and fixes will bring new life back to the game and drive more players to it.