Tuesday, April 6, 2010
Monday, April 5, 2010
Saturday, April 3, 2010
Thursday, April 1, 2010
Tuesday, March 30, 2010
Unless you have a dedicated team of testers working with you, it’s pretty easy to publish an app with bugs in it. Hopefully, the majority of your scenarios work because you’ve tried them out (that is assuming, of course, that you are not publishing titles for a device you’ve never seen - hello, iPad!). However, there is always some weird combination of user input you haven’t thought of that, apparently, everyone else has.
We’ve all been there - you write an app and a friend of yours wants to take a look. You reluctantly give up possession of your precious little baby and cringe in the background as your friend taps on all the wrong places. Three seconds later, he returns the device to you with your app all locked up or, better yet, not running at all. You and your friend never talk again.
Okay, so maybe that’s just me. However, my point is that even in these scenarios, you are pretty safe. Apart from losing a friendship or two, you can go home, try to recreate some of the issues, and fix the bugs you find along the way.
When you ship, however, chasing crashes becomes much more difficult. Apart from getting the occasional “The app crashes and it sucks! Don’t buy it” review, you don’t get much information about how your app performs. You may not even know that it crashes at all; not unless you look at Apple’s iTunes Connect portal in a little more detail.
I’ve just submitted an update for Doodle Blast! today and discovered this handy little button hiding underneath the application details:
Clicking on it, you get a nice little summary of what, if any, crashed have been reported:
Besides being told about the crashes, you can download a .crash file, which magically opens up on your machine and shows you the full stack trace of your app at the time of the crash.
That’s pretty cool! I’ve worked on a variety of platforms where you might get some core dumps but, no matter how hard you try, you can never get your hands on the actual stack trace. Either your symbols are too old, or they are from the wrong build, or you just didn’t clap your hands three times before throwing some salt over your shoulder. Whatever the case, the bottom line is that getting some useful information out of a crash log is always a challenge.
This is not the case here. You just need to know that these crash logs are available. I didn’t… until now. And, sadly, it turns out that Doodle Blast! crashes every now and then. Curious, I looked at the downloaded .crash log and discovered that if you tap on the Doodle Blast! title text in the main menu, bad things happen. Don’t do it, kids. You’ve been warned. Fix coming up in the next update…
Friday, March 26, 2010
Tuesday, March 23, 2010
Saturday, March 20, 2010
Thursday, March 18, 2010
Wednesday, March 17, 2010
Monday, March 15, 2010
"Remember drawing spaceships as a kid and then filling every available bit of their surface areas with guns? This is the tank equivalent! Photos and videos do not do this game justice. You have to play it and control the awesome firepower to really appreciate it."
"I'll take a gamble and say that this game's going to be as big as Doodle Jump and Angry Birds!"
"Cool style, not as good as doodle jump but well worth getting for a mess about"
"Omg this is the next Doodle Jump."
"...Not going to be the next Doodle Jump..."
"This is better and more addicting than Doodle Jump and Angry Birds"
Friday, March 12, 2010
SPOILER ALERT: If you are a player of Doodle Blast!, this post will spoil some of the fun and mystique behind the game. Consider yourself warned.
One of the unexpected challenges I ran into with Doodle Blast! was difficulty. Some people picked up the game only to feel overwhelmed and lost within a few seconds. Others got bored in just about the same amount of time. Seeing these reactions, I began to wonder how I could dynamically adjust the game to adapt to the player’s abilities. If you are like me and enjoy having nice, slow starts, I wanted the game to take its time building up. If, on the other hand, you are like my brother, I wanted the game to hit you with everything it’s got as soon as possible. End result – we would both be happy because the game provided a different experience for the each of us. But how the heck do you solve this problem?
As it turns out, the solution I coded came in three parts:
- Variable aggressiveness – I could tell each enemy how aggressive I wanted it to be
- Feedback mechanism – Each enemy could report back on how effective it was at destroying the player
- “Skynet” – Central logic interpreted the feedback to adjust the aggressiveness of the next wave of enemies
Because I’m a geek and because it seemed right, the first two values were passed around as floats, ranging from 0 to 1. Zero in aggressiveness meant that the enemy was as passive as possible (it wouldn’t shoot, for example). Zero in feedback meant that the enemy was completely useless (it was killed before it even entered the screen). Ones indicated the opposite for both.
Since each type of enemy was its own class, each could also provide its own way of determining its effectiveness at being a killing machine. What I, then, did as a player was try playing against each of the enemies at various “aggressive” values to find a happy medium between being bored and not being able to keep up. This value (which for most enemies hovered around 0.65) was the golden standard that each enemy was trying to achieve. If it fell short, Skynet would yank up the aggressiveness of the next wave proportionately. If it overshot, Skynet would make the next wave less aggressive.
That was the basic idea. However, there are a couple of other tidbits worth mentioning.
Assaults – each wave of enemies of a similar type constituted an “assault”. If you were kicking butt across all assaults, Skynet would begin sending two or more assaults at a time to give you some extra excitement. If that proved to be too difficult, the concurrent assaults scaled back down.
Fire power – the relative aggressiveness of all enemies also adjusted based on the number of guns you carried. Basically, the more guns you had, the more indestructible you became and the more aggressive the enemies turned.
Shooting strategy – it doesn’t matter if you lose guns from your tank. The only thing that matters are the hit points of the tank itself. Hence, a particularly useful way to defeat the system was to continuously shoot only at the bottom right corner, and just pick up new guns when the old ones got destroyed. So much for Skynet. To counteract this strategy, I added "assassins". The game actually takes note of your favorite shooting spots and monitors the “histogram” it produces. Initially, it lets you get by. However, after the first 1000m, it sends a way of deadly assassins after you that target your tank directly without mercy. These assassins get spawned at the histogram minimum - meaning, at the place where you are least likely to shoot.
The end result of all this magic is that if I play poorly, I can get to about 600m. If I play really well, I can get to about 700m. And that was basically the aim – to give players an opportunity to enjoy bulk of the game play regardless of whether they were pros or rookies. But, as always, there is lots of room for error. Take “Nino", for example, topping the leader boards with a whooping 8561m under the belt. Kids these days…
Tuesday, March 9, 2010
Saturday, March 6, 2010
- Stacking guns
- Falling bonuses
- Static buildings, flickable soldiers, flying aircrafts
- Score counting
- Score tick marks
- Hit points, damage, and collisions
- Single and multi-touch support
- Local scores
- Global scores
- Twitter integration
- Loading screen
- I added logic to adjust sound volume based on game progression and the amount of mayhem on the screen.
- In an attempt to make the game playable by rookies as well as advanced destructionists, I added logic to monitor the user and adjust the difficulty level accordingly.
Friday, March 5, 2010
_tableController = [[ScoreTableController alloc] initWithStyle:UITableViewStylePlain];UITableView* table = _tableController.tableView;[[[CCDirector sharedDirector] openGLView] addSubview:table];
Day 1 – prototype: 74 viewsDay 2 – collisions: 105 viewsDay 4 – flicks: 11 views
Wednesday, March 3, 2010
cpVect direction = cpvnormalize(body->v);float theta = -CC_RADIANS_TO_DEGREES(acosf(direction.x) * (direction.y > 0 ? 1.0f : -1.0f));float currentAngle = sprite.rotation;float dTheta = theta - currentAngle;while (dTheta > 180) dTheta -= 360;
while (dTheta < -180) dTheta += 360;
sprite.rotation += fminf(DIRECTIONAL_MAX_THETA_DELTA, fmaxf(-DIRECTIONAL_MAX_THETA_DELTA, (dTheta * DIRECTIONAL_MOVE_FACTOR)));
Today I introduced visual damage model on the player. Interestingly enough, most people playing Doodle Blast! for the first time completely miss it. I wanted to avoid having a health-meter bar or a number somewhere on the top of the screen, so I tried to solve this problem visually instead:
But as I said, most people miss it, so I might need to add a health bar after all.
The other big change in today’s build was the addition of flicking. Following the initial sketch of the game, I wanted to have little soldiers running around. I also wanted them to be flickable as flicking seems to be a popular gesture these days. The implementation was pretty simple.
Each soldier had a state variable, which could contain one of the following values:
- ATTACKING – the soldier is all gong-ho in trying to get the player
- GRABBED – the soldier is “grabbed” by a touch and is now following that touch around the screen
- FALLING – the touch that “grabbed” a soldier ended, and the soldier is now falling
- RECOVERING – the soldier hit the ground, but under some magic threshold speed. A timer has been set, after which the soldier will resume its attack.
To calculate how and whether a soldier was flicked after it was grabbed, I kept track of the movement of each touch. When I received a TouchMoved event, I used the last touch position along with the last touch time-stamp to compute the speed thusly:
touchSpeed = (currentLocation – lastLocation) / touchTimeDelta;
When the touch ended, I used the touchSpeed, clamped by some max value, and simply set the released soldier off in that direction.
This approach, however, created a problem. You suddenly couldn’t just shoot a soldier, because every touch near a soldier’s vicinity turned into a flick gesture. To solve this problem, I defined two constants: FLICK_TIME_EPSYLON and FLICK_DISTANCE_EPSYLON. If a given touch event took less time than FLICK_TIME_EPSYLON (0.3 secs) and if that touch event moved a total of less than FLICK_DISTANCE_EPSYLON (20 pixels), I wouldn’t consider that gesture a flick, but I would consider it a tap instead and still direct one of the guns to shoot at that location.
Finally, there was one more interesting change that I introduced the day before but didn’t get a chance to talk about yet. I wanted the tank and the stack of guns to feel like they are barely holding together as they clank along moving forward on a bumpy road. What I ended up doing was giving a random small impulse to the tank and to a random gun in the stack every 0 – 2 secs. You can see those bumps in the video from yesterday.
Monday, March 1, 2010
- Collision handlers
Item Group LayerPlayer guns 1 PLAYERFriendly fire 2 ENEMYEnemy fire 3 PLAYEREnemies 4 ENEMY | PLAYERBonuses 5 BONUS
Player <-> EnemyPlayer <-> Enemy FireEnemy <-> Friendly Fire
Sunday, February 28, 2010
- Begin: Two shapes just started touching for the first time this step. Chipmunk has not yet calculated collision forces for this collision, and the preSolve() and postSolve() callbacks have not yet been called.
- Pre Solve: Two shapes are touching. Some collision force computation has already taken place.
- Post Solve: Two shapes are touching and their collision response has been processed.
- Separate: Two shapes have just stopped touching for the first time this frame.