Hey Cocos2d Noob - A Letter To Past Me: Part 3

If you missed them, catch Part 1 here, and Part 2 here.

Wow, it's been a long time. I shouldn't have left you. Without a new post to review. I was sucked into a new project that started off as a Ludum Dare Jam and then turned into a full game (check it out after you read this). I learned a lot going through the process and I recommend a game jam for any aspiring game dev. Unfortunately, adding a new project into the mix meant the next installment of this series was delayed. Time to dig back into my archive of horrible mistakes...

Filename@2x.png

When the retina display came out Apple gave us a simple way to automagically load retina assets instead of "SD" assets. Adding "@2x" to the retina version of the asset solved all our woes.

When I first came to cocos2d I was already in the habit of adding "@2x" to all of my retina assets, so I continued to do so. This is not the recommended naming convention for cocos2d because Apple's special handling of the "@2x" images can cause subtle bugs when interacting with cocos2d. Instead we should add "-hd" to all retina display images. Do this from the start and avoid having to unleash your command-line-fu to rename all your images. It is amazing how many issues can be avoided by just reading the documentation.

Another bonus of the "-hd" suffix is that it works for other asset types such as plists, fonts, and TMX files. If you need to load different assets for retina than for sd, then just slap an "-hd" on the end of the retina version.

Ticking Away The Moments

Within the main game loop a monster lies in wait. It will devour noob game developers indiscriminately. It doesn't matter if you are using cocos2d, another framework, or no framework at all. This monster's name: Time.

A very important thing to recognize is that not every step through the game loop will take the same amount of time. This is especially true as you move from one device for another.

A Story: You have carefully tweaked your monster speed to charge at your player at a rate of 10.698 units per update. The time through your update loops are variable, but you haven't caught on because they are always similar enough for the effect to go unnoticed. Now you load the game on your friend's shiny new iPhone 5 with it's awesomely powerful processor. Holy badunks! You can no longer react fast enough to escape the monsters! This game is impossible!

Since you used a fixed delta per game loop iteration and since the faster device can iterate more times per second, you have ended up with a game that no longer works as intended on a faster device. Don't feel too bad about it, many older games built for a specific platform suffer the same fate when run on newer hardware.

Luckily, this is an easy to solve problem if you address it from the start. Take the delta into account with each update and you will avoid this problem entirely. If you have a step method that looks like this:

-(void) step: (ccTime) delta;

Multiply the time delta by a velocity to determine the positional delta.

If you are using a physics system like chipmunk, then the delta should be passed into the step method (cpSpaceStep). The physics engine will then take care of all of the grunt work for you, but you still need to be careful with anything you update from outside the physics system.

Aside: There are two ways that physics engines can be updated, fixed step or variable step. There are pros and cons to each approach. Engines will facilitate one or both approaches. A good starting place to learn more is this thread on gamedev.stackexchange.

Shuffling

The last tip of the day comes in the form of some FREE code. The internet is just full of this stuff! (Always check the attached licenses) Odds are you will eventually need to shuffle the elements of an array. An easy way to do this is to create a category for NSMutableArray. This will give you a highly reusable piece of code, and then you won't have to write new shuffling code everywhere you need to randomize array elements.

A discussion on shuffling an NSMutable array using a category can be found over on StackOverflow. I have included my own slightly modified version below for ease of Copy-Paste.

52616e646f6d; 52616e646f6d

If you want to randomize an array the first time you generate it, and then want to be able to regenerate that same sequence again in the future then you can make a few modifications to the code above (or add a new method).

Replace:

int n = (arc4random() % nElements) + i;

With:

int n = (rand() % nElements) + i;

Then before calling shuffle, call srand() and pass in your saved seed value.

// Our first play of Taco Madness

srand(423452345);

[tacos shuffle];

// resulting order => beef, chicken, fish, pork, brain

 

// Replaying Taco Madness later using the same seed

srand(423452345);

[tacos shuffle];

// resulting order => beef, chicken, fish, pork, brain

// same results as first run

 

// Starting a new game of Taco Madness and therefore using a new seed

srand(76864432);

[tacos shuffle];

// resulting order => pork, chicken, brain, fish, beef

// new results due to new seed

This will guarantee that shuffling will return the same result as the last time you shuffled the same array with the same seed. They call them pseudo-random for reason.

Next Time

In the next installment I am going to go through an entirely new class of errors related to process of releasing a game on the App Store. It will be chock full of mistakes you will absolutely want to avoid.

Editor's Notes:

  1. Glad to see that tacos were used in an example. I was beginning to grow worried.
  2. A video of the Melbourne Shuffle… really?
  3. I am imaginary.
  4. Speaking of tacos:

Hey Cocos2d Noob - A Letter To Past Me: Part 2

If you missed it, catch Part 1 here.

Another week has slipped into the past and I've yet to find a Delorean, an FTL spacecraft, a TARDIS, a wrinkle in spacetime, or any other means of time travel. I guess I am back to leaving notes in the now for alternate me's.

While working on a project, I tend to use an old fashion notebook in which I keep ideas, todo lists, discovered gotchas, and other scribbled bits of madness. There is something about crossing an item off a list or working through an idea on paper that I enjoy. Call me old school.

Fortunately for everyone else, this notebook is littered with evidence of my past mistakes and misunderstandings. I took some time to review this catalogue of errors to pull out some of the more helpful pointers for anyone who finds this.

Yet Another Point

CGRect

I covered CGPoints and all the hidden gems in CGPointExtension last time, so I thought it was high time to move on to the second dimension. Really, this is more of an Objective-C slash Apple Library thing than a cocos2d thing, but often both are learned at the same time. Here are a few key helpers, I also suggest you read the docs.

CGRectMake - Construct a rectangle given the x and y of the origin, the width, and the height.

CGRectIntersectsRect - Check if two rectangles intersect.

CGRectContainsPoint - Check if a rectangle contains a point.

Rectangles are great for collision and hitbox checks that are not part of a physics system. Once you start dialing up the complexity or simulating physics you should use something like chipmunk or box2d.

Back to My Point - A few forgotten helpers

In building a game that had a HUD layer and a world layer ("Level") that panned and zoomed, I found myself having to convert coordinates between the two distinct spaces. CCNode provides a few helpers that will save you a ton of conversion math.

My explanations assumes that the HUD and the Level are both children of the game layer (if you are using the cocos2d Hello World example this would be your HelloWorld layer). It also assume that the HUD has a fixed position and is in the same coordinate space as your HelloWorld layer (or HelloWorld layer equivalent).

convertToWorldSpace - Converts a point to world space. This means you can take a point in your panned and zoomed Level and figure out the corresponding point on your HUD.

convertToNodeSpace - Converts a point to node space. You can take a point on the HUD and figure out the corresponding point in the game world. ie [level convertToNodeSpace:targetPosition]

convertTouchToNodeSpace - Converts a touch to node space. This is extremely useful in figuring out where a touch point corresponds to in the Level.

Multitouch

There comes a point in every little games life (well, "every" is hyperbole) where it wants to be touched in two places at once. This usually results in a burst of outrage or a post to the cocos2d forums. Some cocos2d samples start with the single touch delegate enabled, as if rubbing your belly and patting your head at the same time wasn't hard enough. There is actually a good reason for this, it is much easier to handle one touch than a set of them. Anywho… on to the magic:

Inside your scene's registerWithTouchDispatcher you need to add the standard delegate instead of the targeted one. Edit: The cocos2d templates should all have the standard delegate enabled by default rather than the targeted delegate. In that case skip to the other steps.

Replace:

 

[[CCTouchDispatcher sharedDispatcheraddTargetedDelegate:self priority: swallowsTouches:YES]

 

With:

[[CCTouchDispatcher sharedDispatcheraddStandardDelegate:self priority:0];;

If you are currently handling touch begin, moved, and ended events for single touch, then you will have to replace them with the multitouch versions:

ccTouchBegan:withEvent:event becomes ccTouchesBegan:withEvent:event

ccTouchMoved:withEvent:event becomes ccTouchesMoved:withEvent:event

ccTouchEnded:withEvent:event becomes ccTouchesEnded:withEvent:event

Note that the first argument passed to the multitouch version is a set of touches rather than a single touch.

After doing all this work you might get excited that you have your fancy new multitouch code working, only to be disappointed when you realize that it is still only handling a single touch! One last change to make: In your app delegate be sure to enable multitouch. Somewhere after you initialize and set your EAGLView, do this:

[glView setMultipleTouchEnabled:YES];

Caveat: If you are using gesture recognizers then they may be swallowing additional touches. If you have all this code in place, are using gesture recognizers, and your multitouch code isn't getting multiple touches, then the gestures are the likely culprit.

Edit: As Gaminghorror points out you need to clean up your delegates as well to prevent crashes and memory leaks with:

[[CCTouchDispatcher sharedDispatcher] removeDelegate:self];

And if you are looking for other material to learn cocos2d go check you his site: learn-cocos2d.com.

CCParticleSystem & Particle Designer

Rolling my own particle system is one of those bad habits that I have. There comes a point when working with a new framework and/or language that I decide to build a particle system. It is usually a good exercise in building something more complex than a Hello World application. Needless to say, I did this for cocos2d. Also needless to say, it was a lot of fun but did not move the ball forward on actually getting a game built.

Cocos2d comes with a decent particle system, CCParticleSystem. Go read the documentation: http://www.cocos2d-iphone.org/wiki/doku.php/prog_guide:particles Welcome back! In all seriousness, odds are that this will meet all of your needs, it will be updated and maintained by other people, and if you really need to do something magical it is open source.

I blabbered on about the glorious merits of Texture Packer last time so I figured I would pick a new tool this time. Particle Designer, which you read all about at that link up there is an excellent tool. True to my nature, I started rolling my own and then I bought Particle Designer when I realized I had games to build. It doesn't support everything that CCParticleSystem does and there are some minor differences, but they are pretty well documented. I recommend buying Particle Designer after reading the CCParticleSystem docs. The source code for CCParticleSystem is also highly informative.

Protip: Don't be like me, read the docs and learn about the tools available.

Protip 2: Reading other people's source code will learn you good.

Next Time

I am not going to bother pretending I have a plan. I have a near limitless bag of errors for my past self to avoid, I will attempt to dig out the most useful stuff and share it. Worst case scenario, I will try something new, make lots of mistakes, and share those.

Editor's Notes:

  1. I think there is a word I forgot to use in this part of the series.
  2. Next time will likely be a few weeks away. I plan on covering a few other hopefully useful things first.
  3. There has been an underuse of taco references lately, please correct. -ed

Hey Cocos2d Noob - A Letter To Past Me: Part 1

You know those moments where you suddenly realize The Right Way (TM) to do something and you slap yourself on the forehead for wasting untold hours. I've had a number of those while working with cocos2d and I wish I could go back in time and reveal all those little discoveries to past me. Unfortunately, I lack access to a DeLorean or other means of time travel so I will do the best I can and write down my notes here in case any other noobs stumble across it. This will be a multi-part semi-regular series in which, in no particular order, I will reveal the wondrous errors and oversights I made on my journey to cocos2d near-competence.

Points - Do I have one?

It is hard to make a game without dealing with points, coordinates, Euclidean distance, etc. The project I am currently working on is no exception. In the beginning I created CGPoints. I saw that the CGPoints were good, and I did lots of math with them. Unfortunately, I made the mistake of breaking out x and y, typing up some basic math and littering my code with things like the distance formula. After a few instances of this, I decided that I should write up some helper functions as it was likely I would be doing these basic operations a number of times. Not long after starting my own CGPointHelper file, I was reading through some sample code and saw ccpAdd. I quickly scrambled to find the source for it and discovered this: CGPointExtension.h. Mindsplosion!

CGPointExtension is an absolute gold mine that will prevent you from writing a bunch of code that you probably can't live without. I'll go over a few of the star players, but I suggest you read through the entire header file.

ccpAdd, ccpSub - Need to add (or subtract) two points from each other? Then these are your homies.

ccpLength, ccpDistance - Determine the distance between two points. ccpLength is a double whammy giving you either the distance from the origin or the magnitude of the vector.

ccpFuzzyEqual - Are we really close enough? Checks if points are equal within a specified variance.

ccpMidPoint - Let's meet halfway. Gives you the midpoint between two points.

ccpAngle, ccpRotateByAngle, etc - There is a ton of great stuff if you need to work with angles or translate coordinates. Isn't it fantastic that someone has done all this work for you? If you aren't familiar with the underlying math, check out the source for some learnin'!

ccpLengthSq - Great for fast distance or magnitude checks. This is effectively ccpLength without the costly square root operation. Keep this one in your bag to save your FPS.

Texture Packer

While prototyping I eventually needed to animate some sprites. I read the cocos2d docs and ended up using Zwoptex to create my spritesheets because they had an awesome free version. Free has a strong appeal. Zwoptex served me well, but I spent a lot of time maintaining both an sd and an hd version of every spritesheet. I stumbled across Ray Wenderlich's review of Texture Packer and discovered a tool that gave me everything I needed: command line interface, integration with xcode, auto-sd, saving in various pixel format modes. I bought Texture Packer and haven't looked back, the amount of time it has saved me has paid for itself a hundred times over. I'm not going to go over all the details because Ray has already done an excellent job. Read his review and then go buy it now!

Clean Up

Cleaning up after yourself is as important in Objective-C and cocos2d as it is in the rest of life. If you don't you will eventually be crushed under the weight of a wall for trash falling on top of you as you wander the trash filled maze you call a house. Or in our case, you end up leaking a ton of memory and your app crashes.

This isn't meant to be a introduction to memory management or an all inclusive guide. That would be an entire series of blog posts of its own. This is just a short list of things to keep in mind to help clean up after yourself.

  • Use debug logging to verify that you are dealloc-ing what you need to dealloc.
  • Release anything you retain. Children will be released on their own unless you retain them as well.
  • In your scene's onExit call clean up touch handlers. This includes setting isTouchEnabled to NO.
  • If you retain CCActions in a custom CCNode you must release them before the node's dealloc will be called.
  • If you are using chipmunk, clean up your bodies and shapes if you retain them.
  • Because it bears repeating: Release anything you retain.
  • Don't over release or you will cause a crash.
  • You can free up a lot of memory by freeing unused sprite frames and textures:

[[CCSpriteFrameCache sharedSpriteFrameCache] removeUnusedSpriteFrames];

[[CCTextureCache sharedTextureCache] removeUnusedTextures];

If you are using any scene transitions be careful cleaning up sprite frames and textures. You can run into timing issues where you remove sprite frames that were just loaded prior to using them in the new scene. This can cause a crash.

Next Time

In the next installment I will go over other advice to myself that I wrote out in my dev notebook for the project. I may also go into more detail on clean up and memory management.

Editor's notes:

  1. This series will likely bathe in extreme overuse of the word "unfortunately". This is because I unfortunately made a number of unfortunate oversights during the initial learning period and likely continue to do so.
  2. There is at least one super secret hint as to what Taco Graveyard's Top Secret Project is in this post. Unfortunately, no one will ever pickup on it.

Cocos2d Score Label / Ticker

In last week's review of Zombieville USA 2, I pointed out the upticking score label as a nice bit of polish. Today we are going to build one of our own.

My current project uses cocos2d so we are going to extend one of the existing label classes to build our score label. If you aren't familiar with cocos2d, then I suggest you head over to their website and run through some of the beginner tutorials or at least the documentation. Before we start coding let's define what we want.

  • Label that displays a score
  • Can roll from value to another
  • Can roll either up or down
  • Can roll more than once
  • Can target value can be updated mid-roll
  • The displayed string is formatable
  • Customizable alignment and size

We want to be able to setup and start rolling the label like this:

Now let's start by defining our class in the header:

We don't have anything too crazy going on here. We track the current score as a double rather than as an int so we can track fractional updates per interval. The score, format string, and update rate (pointsPerSecond) are all exposed as public properties so users of the class can set their own values. It is important to note that we are retaining formatString, so we will have to release it in our dealloc and if we assign a new value to the formatString property we should release the old value.

We are building off of CCLabelTTF which is one of the labels that cocos2d provides. CCLabelTTF is not nearly as performant as CCLabelAtlas. The cost of setString: in CCLabelTTF is much higher than in CCLabelAtlas. This label was built to be used on a post gameplay scoreboard so performance is not a huge issue. If you need a score label in an area of your game where you are fighting for every FPS, then you may want to build a similar label based on CCLabelAtlas.

Let's take a look at rollToScore: the method you would call to start the score rolling/ticking.

The first thing we do is update the target score to the new target. Next we check if we are currently rolling. If we are then we don't need to do anything because the update_: method will take care of the magic for us. If we aren't updating then we set the update flag and schedule an update. This will cause the update_: method to fire repeatedly after the number of seconds specified by interval_.

The actual updating of the label occurs in... you guessed it update_:.

The first thing we do here is determine which direction we should be rolling in. Remember that one of our requirements was that we could roll the score up or down. There are a number of ways you can accomplish this, but we stuck with the straight forward comparison.

Next we update the score. Note the use of the double for pointChange to match the type of curScore_. Depending on the value you are using for pointsPerSecond you might only update a fractional point per tick and you don't want your score stuck in the mud. We multiple by dt rather than by interval_ because dt will give us the actual time passed rather than the scheduled time.

Then we make sure that we haven't overshot our target score. Alternatively you could use min and max functions here.

Now we are ready to update our displayed string. Straight forward stringWithFormat call here. One of our assumptions is that the format string is looking for an int. We cast curScore_ to an int to match that assumption.

The last thing we need to do is check if we hit our target score and if so stop updating and unschedule future updates.

I hope that all makes sense and this is something that will be useful in your own projects. Click here for all the code at once. Hopefully the rest is fairly self explanatory, but if it isn't please ask questions in the comments. You can download the full sample from the gist.

*Disclaimer: The code presented in this post is far from perfect. Please improve on it.*