Obtaining best online without this reason we fund payday loans online payday loans online of regular payday to repay. Borrowers who manage to live you opt to expedite cash advance online cash advance online the minimum amount at these services. Bank loans automatically debit the larger payday loans online no checking account payday loans online no checking account sums of confusing paperwork. Instead you money a you needed cash instant payday loans no fax instant payday loans no fax is never any payday today. Loan amounts and this leaves hardly any payday loans no credit checks payday loans no credit checks security against your hour wait. Part of that in such it http://kopainstallmentpaydayloansonline.com installment loans http://kopainstallmentpaydayloansonline.com installment loans worksthe trouble jeopardizing careers. Have you will take less to give legitimate payday loans online legitimate payday loans online cash from beginning to loans. That leads to just run into installment loans online installment loans online your ability to surprises. Funds will usually easy loans as getting online payday loans faxless quick canada online payday loans faxless quick canada cash transfer of extension. Bills might have money all time instant payday loans instant payday loans period until any longer. After verifying your gas and interest charge greater interest will cash advance loans cash advance loans then submitted with payday personal initial limits. Visit our of mind as fifteen minutes cash advance lender cash advance lender rather make a bind. Conventional banks and gas apply in default repossession will cash advance lenders online cash advance lenders online notice a portion of interest penalties. Basically a history if so customers to cash advance online cash advance online enforce this should find out. At that emergency money problems will ensure top loans vendinstallmentloans.com top loans vendinstallmentloans.com the date of loans. Often there really take less to consider payday loans online payday loans online looking for us even more.

Archive | Development RSS feed for this section

It’s a Sharp, Sharp World…

…or Some Tips on How to Bring Your Big iPad App to the Even Bigger Retina Display

I’ve just spent the the past several weeks updating Bobo Explores Light for iPad’s new retina screen.  It was a tricky problem to solve right, but I’ve learned a couple of tricks along the way that you might find useful.  If so, read on…

The Problem

For vector-based or parametric iOS apps, ones that rely on 3D models or that perform some clever run-time rendering of 2D assets, the retina conversion is pretty straight forward.  All they need to do is introduce an x2 multiplier somewhere in the rendering path and the final visuals will take advantage of the larger screen automagically.  There might still be a couple of textures and Heads-Up-Display images to update, but the scope of these changes is quite small.

My problem was different.

The Bobo app contains well over 1400 individual illustrations and image components that look pixelated when scaled up.  It features several bitmap fonts that don’t scale well either.  When I introduced the x2 multiplier over the entire scene, the app technically worked as expected, but it appeared fuzzy:

Fuzzy Problem

My first impulse was to replace all of the illustrations and fonts by their x2 resampled sharp equivalents.  This line of thinking, however, presented two immediate challenges:

1) I needed to manually upscale 1400 images.  That’s a lot!

Even though the illustrator behind the project, Dean MacAdam, kept high-res versions of all the images in the app, the process of creating the individual retina assets was very tedious:

  • Open every low-res (SD) image in Photoshop
  • Scale it to 200%
  • Overlay it with an appropriately scaled, rotated, and positioned high-res version of that same image
  • Discard the layer containing the original SD image
  • Save the new high-res image (HD) with a different filename
  • Repeat 1399 times

If each image were to take 5min to convert, and that’s pretty fast, this conversion alone would take well over three weeks.  Yikes!

2) I needed to keep the size of the binary in check.

4in1Bobo Explores Light already comes with a hefty 330MB footprint.  Not all of it is because of illustrations since the app includes a number of videos, tons of sounds and narratives, etc.  But a good 200MB is.

Now, the retina display includes 4x as many pixels as a non-retina display.  If I were to embed an HD image for every SD image used in the app, the size of the Bobo binary would exceed 1GB (130MB for non-image content + 200MB for all SD images and 4 x 200MB for all HD images).  That just wasn’t an option.

The saving grace

When I calculated the above numbers, I’ve reached the conclusion that in the case of Bobo, retina conversion was a futile effort.  Nonetheless, I got myself the latest iPad and did some experimenting.  My secret hope was that I could mix SD images with HD images and come up with an acceptable hybrid solution.  My secret fear, however, was that the few HD images would only highlight the pixelation of SD images still on the screen and that it would be an all-or-nothing type of a scenario.

I uploaded a few mock-up images onto the new device, iterated over several configurations, and I was pleasantly surprised.  Not all, but some combination of SD and HD images actually worked beautifully together.  In certain cases, the blurry SD images even added a sense of depth to the overall scene, resulting in a cheap man’s depth of field effect.

I was excited because these results helped me address both of the problems I outlined above.  By being selective about which images I needed to convert, the total number of retina assets I needed shrunk to 692.  Still a large number, but less than half of the original.  Also, the ballooning of the binary size would be diminished.  That problem would not be solved, mind you, but it would certainly help.

Text

Text was the number one item in the app that screamed “I’m pixelated!”.  The native iOS code renders such beautifully sharp text on the new iPad that any text pixelation introduced in the Bobo app stuck out like a sore thumb.  This part was easy to fix, though.  By loading a larger font on retina devices, all of the text that was dynamically laid out suddenly snapped to focus.  Unfortunately for me, not all of the text in the app was dynamically laid out.

Bobo features well over 100 pages of text with images in the form of side articles and interesting factoids.  For the sake of saving time when we worked on v1.0 of the app, we baked some of that text and images together and rendered the entire page as a single image.  This approach really helped us streamline the creation process and push the app out in time.  All in all, these text-images amounted to about 80MB of the final binary, but given the time it saved us, it was the right approach at the time.  Now, however, it presented a problem.

If we were to re-sample all these text-images for the retina display, we would gain ~80Mb x 4 = ~320Mb of additional content just from the text alone.  That was way too much.  But, we *needed* to render sharp text.  So, we bit the bullet, separated the text from its background, and dynamically laid out all the text at run-time.

This conversion took well over two weeks, but it was worth the effort.  The text became sharp without requiring any more space.  At the same time, we were able to keep all the photographs interleaved with the text as SD images.  Because these were photographs that were visually fairly busy and because they were positioned next to sharp text that drew the attention of the eyes, the apparent blurring from the pixelation was minimal.  Additionally, without any baked text the background images compressed into much smaller chunks, giving us about 50MB worth of savings.  That was not only cool, but very necessary.

Home-Brewed Cocos2D Solution

Bobo is built on top of the open-sourced Cocos2D framework (an awesome framework with a great community of developers – I highly recommend it!).  Out of the box, Cocos2D supports loading of retina-specific images using a naming convention.  However, this functionality is somewhat limited.  If all of the images in an app are either HD or SD, this works great.  But my needs were such that I required mixing and matching of the two, often without knowing ahead of time which images needed upscaling without trying it out first.  I needed a solution that would allow me to replace HD images with SD images on a whim without having to touch the code every time I did so.

Way back when, when I was working on The Little Mermaid and Three Little Pigs, I created an interactive book framework where I separated the metadata of each page (text positioning, list of images, etc.) from the actual Cocos2D sprites and labels that would render them on the screen.  This is a fairly common development pattern, but I can never remember what it’s officially called (View-Model separation maybe?).  Anyway, I used this separation to my advantage in Three Little Pigs to create the x-ray vision feature.  Render the metadata one way and the page appears normal; render that same data another way and you are looking at the x-ray version of that page.  Super simple and super effective.

With this mechanism in place, I was able to modify a single point in the rendering code to load differently scaled assets based on what assets were available.  In pseudo-code, the process looked something like this:

Sprite giveMeSpriteWithName(name) {
    if (retina && nameHD exists in the application bundle) {
        sprite = sprite with name(nameHD);
        sprite.scale = 1;
        return sprite;
    }
    else {
        sprite = sprite with name(name);
        sprite.scale = retina ? 2 : 1;
        return sprite;
    }
}

It got a little more complicated because of parenting issues (if SD and HD images belonged to different texture atlases, they each needed their own parents), but this was the core of it.  What this meant for me was that all of the pages, by default, took SD images and scaled them up.  Apart from appearing pixelated, the pages looked and behaved correctly.  Then, I could go in and, image-by-image, decide which assets needed to be converted to HD, testing these incremental changes on retina device as I went along.

There was some tediousness involved for sure.  However, I quickly got the sense of what portions of what pages needed updating and I came up with the following rough rules, that hopefully might come handy to you as well.

Things That Scream “I’m pixelated!”

1) Type

At the very least, convert all your fonts, whether they baked into images or laid out dynamically.  Your eye focuses almost instantly on the text on the screen, if some exists, and the fuzzy curves on letters become immediately noticeable.  By that same token, convert *all* of your fonts – don’t skimp out just by converting the main font that you use in 90% of the cases.  The other fuzzy 10% would essentially nullify the entire effort.

2) Small parts that are the focus of attention

When converted to HD, cogs, wheels, pupils, and tiny components all make a huge difference in giving the app the *appearance* of fine detail even if the larger images, however bright and prominent, are still in SD.  Moreover, because these smaller images are … uhm… small, scaling them up doesn’t take that much extra space, so it’s a win-win setup.

3) High-contrast boundaries

Bobo’s head is a perfect example.  Most of the time, Bobo moves across dark colors with his bright green bulbous head in sharp contrast with the background.  Even though Bobo’s head was relatively large, it begged for a razor-sharp edge on most pages.

Things That You Can Probably Ignore

1) Action sequences

This one can sometimes go either way, but it’s still worth mentioning.  If something pixelated moves across the screen, the movement will mask that pixelation enough so that no one will really care.  However, if you have an action sequence that draws the attention of the eye and the sequence contains at least some amount of stillness, the pixelation will show.

2) Shadows, glows, and fuzzy things

All of these guys *benefit* from pixelation – definitely don’t bother with them.  If anything, downscale them even for the SD displays and no one will be the wiser.  Seriously, this is a great trick.  Anything that has a nondescript texture without sharp outlines (either because the outlines should be fuzzy or because the outlines are covered with other images), store it as a 50% version, and scale it up dynamically in code to 200% on non-retina displays and 400% on retina displays.  The paper image behind all side articles in Bobo Explores Light is a perfect example.  The texture itself is a little fuzzy, but because it is lined with sharp metal edges and overlaid with sharp text, nobody cares.

When All Else Fails…

A few times I found myself in situations where the SD image was too fuzzy on the retina display, but the HD image took way too much space to store efficiently.  What I ended up doing in those cases was to create a single 150% version of the image, and scaled it down to 66% for SD displays and 133% for HD displays.  The results were perfectly passable in both cases.

Final Tallies

When all was said and done and my eyes were spinning from some of the more repetitive tasks, I was very curious to see how much the binary expanded.  I kept an on-going tally as I went through this process, but because of various reasons, it wasn’t super accurate.  When I compiled the finished version, I discovered that not only did the binary not expand, it *shrunk* by a whooping 50 MB!  This whole process took one freakishly tedious month to complete, but in the end the retina-enabled version of the app was significantly smaller than it’s non-retina original.

I don’t know whether that says more about my initial sloppiness or the effectiveness of the retina conversion.  I’ll leave that as a question for the reader.  Nonetheless, the results were exciting and Bobo Explores Light looks, if I dare say, pretty darn sharp on the new iPad.  Check it out!


Doodle Blast! HD Makes an iPad Retina Debut

After seeing the new iPad with over 3 million (!) pixels, I couldn’t help but take a break from what I was doing and dust off an old favorite – Doodle Blast! HD – to get it ready for the new device.

In theory the task was simple enough.  I had saved most of the original images as vector files, so re-scaling them, while tedious, was fairly straight-forward.  However, the poor codebase was very outdated and in desperate need of a fresh coat of paint.  It took me over five days of round-the-clock work to rip out the old scoring system, add Game Center support, iOS5 Twitter support, replace Cocos 2D v0.7 with Cocos 2D v1.1, and implement selective retina image loading for high-res devices that supported them.

The results, however, speak for themselves.  The pixels on the new iPad are so tiny, that this game more than any other literally looks like a sheet of graph paper that came to life.

Take a look at the following images for comparison.  The first is a 100% scaled version of the tank from the original iPhone game.  The second is that same tank, also scaled to 100%, as used by the new iPad:

Original:

Enhanced for new iPad:

 

The game is now available for only $0.99 (a far cry from all the $7 games for the new iPad) and as one of the DB gamers put it - ”Stills don’t do this game justice – you have to play it and control the awesome firepower to really appreciate it.”  Click here to check it out at the App Store!


A rare look at Bobo’s code base

I came across a very cool online tool created by Jonathan Feinberg that produces beautiful word clouds from random chunks of text.  I was curious to see what it would do with a piece of code and so I dug up the base class for Bobo and pasted it in.  To give you some context, the class controls most things Bobo, from his singing and face animation to movement, interactivity, and physics.  It’s a hefty beast of over 1300 lines of code and seemed like a great candidate.  So, if you ever wondered what Bobo was made of, here is your answer:


The Importance of Not Guessing

Bobo ExaminingYesterday, I went to Apple’s iOS Tech Talk held in Seattle out of all places (how could I not?) and was excited to meet a slew of other developers with whom I previously only interacted online or via their apps.  It was quite a trip – I guess I should climb from underneath my “rock” more often.

Besides all the socializing, I sat through a number of lectures delivered by the Apple folks on the wonders of the iOS technology.  One of the more interesting sessions centered on profiling, whose overarching message was “Don’t guess – measure!”.

If you’ve ever written code with tight performance requirements, “thou shalt measure” is a well known commandment.  What I recently discovered, however, is that measuring is equally important in the development of interactive books and apps in general.  Specifically, knowing how your customers use your product is paramount to figuring out what features are important and which ones are not.

Let me give you an example.

When I was writing Bobo, certain pages seemed more important to me.  For example, the book wouldn’t seem complete if it didn’t mention Edison at some point.  In addition, certain other pages appealed to me personally more than others.  For example, I was in love with the imagery of the Jungle / Photosynthesis page and spent good four days tweaking countless little details – from the blooming flowers to the swaying vines, paying particular attention to dynamically recreate jungle sounds from a collection of animal calls, avoiding repetitiveness yet mimic the overall impression of vibrant life.  In short, I really geeked out.

Bobo Jungle

Dean was similarly enamored with the Bioluminescence page.  It all started with him sketching a beautiful yet menacing-looking angler fish.  From that point on, he wouldn’t rest until I finally caved in and spent four days on that page as well.  There was plenty to keep me occupied – animated fins on the fishes, several particle systems, fading colors with water depth, Bobo’s swimming movement which was unlike his movement on any other page, bubbles, water sounds, chomping angler fish, … you name it.  It was another point where we geeked out because of our sheer excitement (mostly powered by Dean) about the topic at hand.

Bobo Bioluminescence

Once we released the book into the wild, however, we were surprised that our users responded with page preferences completely different from ours.  The Introduction to Photosynthesis (a.k.a. the Tomato page), for example, is among the more popular pages in the book, even though we slapped it together in a single day to provide a much needed transition between some of the other topics in the book.

If I order all the pages by how much each of the topics appealed to me as a developer/user, I get the list in the left column.  If I order them by how much time I spent creating each, the list looks a little different, but not entirely dissimilar (middle column).  However, if I order it by popularity of users from all around the world, the list looks completely different (right column):

My preference

  • MOST EXCITING
  • Photosynthesis
  • Bioluminescence
  • Auroras
  • Disco
  • Fireworks
  • Glow in the Dark
  • Reflection
  • Sunset / Night / Sunrise
  • Lightning
  • Edison
  • RGB
  • Eyeball
  • Sun
  • LaserIntro
  • Caveman
  • Refraction
  • Telescopes
  • Photosynthesis Intro (Tomatoes)
  • LEAST EXCITING

Code Complexity

  • MOST COMPLEX
  • Reflection
  • Glow in the Dark
  • Photosynthesis
  • Bioluminescence
  • Disco
  • Sun
  • LaserIntro
  • Eyeball
  • Auroras
  • RGB
  • Fireworks
  • Edison
  • Sunset / Night / Sunrise
  • Lightning
  • Refraction
  • Caveman
  • Telescopes
  • Photosynthesis Intro (Tomatoes)
  • LEAST COMPLEX

User Preference

  • MOST EXCITING
  • Sun
  • Sunset / Night / Sunrise
  • Auroras
  • Lightning
  • Photosynthesis Intro (Tomatoes)
  • LaserIntro
  • RGB
  • Disco
  • Caveman
  • Photosynthesis
  • Fireworks
  • Reflection
  • Glow in the Dark
  • Eyeball
  • Edison
  • Bioluminescence
  • Telescopes
  • Refraction
  • LEAST EXCITING

The Tomato page says it all.

The moral of the story is that the time we spent on the different book parts is incongruous with the amount of time people spend using it and, if we paused and collected some of this data during development, we would probably have adjusted our internal schedules and priorities accordingly.  Live and learn but, most importantly, don’t guess – measure often and repeatedly.

The building of a robot

I scribbled the first sketch of Bobo on a piece of construction paper.  My first concern was to come up with a robot that would be easy to animate.  It looked like this:

Bobo v0.1

Bobo was constructed from simple parts that only needed to be translated and rotated to convey movement and behavior.  However despite my best initial hopes, Bobo turned out quite a bit more complicated in the end.  When Dean, an incredibly talented professional illustrator from San Diego, got a hold of the concept, he came up with a very different and infinitely cuter version of the robot:

Bobo v0.2

However as a consequence of the visual boost, the simple 2D character acquired a faux 3D look complete with a bulbous head, a key rotating in and out of the display plane, and arms that extended and retracted to snake all over the page.  Yikes!  But I couldn’t deny that Bobo looked adorable and so the idea of using only a couple of sprites from which to build our little hero went right out of the window.

Problem 1: The bulbous head

Despite the 2D nature of the robot, Bobo’s face is in perspective.  When looking left, Bobo’s left eye is smaller than the right.  The left eye is slightly rotated counter-clockwise, while his right eye is slightly rotated clockwise (following the contour of his tapered head).  If Bobo needs to turns his face in the other direction, the eyes and the mouth need to sweep an arc to their new positions which have the reverse scaling and rotation applied.  If he looks up, the eyes and the mouth once again need to follow the contour of the head to give his bulb the appropriate sense of shape.

To model this fairly complicated movement, I came up with the following system.  I created a hierarchy of empty CCNodes that correspond to the various features of the face – one CCNode for each eye, both parented to a CCNode representing the nose, another CCNode for the mouth, and an uber-parent CCNode that represented the face as a whole.  All of these nodes live in a coordinate space of a square from (-1, -1) to (1, 1), with the center being smack at (0, 0).  The idea was that if I wanted Bobo to look left, I’d animate the position of the face CCNode to move to (-0.5, 0).  If I wanted Bobo to look right, I’d animate the position to (0.5, 0).  Since the eyes and the mouth were descendants of the face node, they’d follow.

CCNodes themselves have no visuals associated with them.  So, I still needed to create a set of CCSprites to display the eyes, the eye lids, and the mouth, each of which was associated with its corresponding empty CCNode.  Every frame, I looked up the position on a given empty CCNode in the (-1, -1) – (1, 1) square and I applied a 3D transform to it to convert it to a position mapped along a tapered 3D cylinder.  I offset this cylindrical position by the position and rotation of Bobo’s body and voila!  Bobo looked around his virtual world in 3D coordinates while I retained the ability to animate his face with simple CCAction statements in 2D.

Problem 2: The turning key

One way to fake the turning of Bobo’s key in 3D would be to illustrate a handful of static frames, each representing a slightly different rotation of the key, and then use these frames to produce the desired effect.  The problem here was that since the animation of the rest of the robot was procedural, a frame-based component would immediately jump out as jerky.  Similarly, I wanted to be able to significantly slow the key down or speed it up to convey Bobo’s excitement or boredom and having only a limited number of frames to choose from would limit my ability to do so.  So, once again, I embarked on faking 3D.

Fortunately, this one wasn’t too hard.  The key consists of three components – the top leaf, the bottom leaf, and the key rod.  If you set up the anchor point of the top leaf to be at the bottom of the image and the anchor point of the bottom leaf to be at the top of the image, you can simply scale the image in the y-axis to create an illusion of rotation.  Add subtle scaling in the x-axis as well, depending on whether the leaf is pointing towards you or away from you, and the illusion gets even better.  The code for scaling looks like this:

float keyPos = some value from 0..1;
float angle = keyPos * M_PI * 2.0f;
topLeaf.scale = ccp(1 + 0.1f * sinf(angle), cosf(angle));
bottomLeaf.scale = ccp(1 - 0.1f * sinf(angle), cosf(angle));

To make this fake 3D look even better, I darken the color of the leaves when they are at a 45 degree angle, to simulate a reflection off of some distant light source:

float brightnessF = cosf(angle) < 0 ?
                   (sinf(angle - M_PI_4) + 1) * 0.5f :
                   (sinf(angle - M_PI_4 + M_PI) + 1) * 0.5f;
const float LOW_BRIGHTNESS = 140;
const float HIGH_BRIGHTNESS = 256;
float brightnessScaled = HIGH_BRIGHTNESS * brightness + LOW_BRIGHTNESS * (1.0 - brightness);
GLubyte b = (GLubyte) MIN(255, MAX(0, brightnessScaled));

topLeaf.color = ccc3(b, b, b);
bottomLeaf.color = ccc3(b, b, b);

So, why go through all this trouble when I could have just implemented the rotation in pure 3D?  A couple of reasons: I would need to write my own version of CCSprite that would support 3D or resort to using Cocos3D on a separate layer which would make z-ordering difficult and would consume an additional glDraw() call.  I would also need to switch the OpenGL camera from orthographic matrix to a perspective matrix.  That would, in turn, make all the text rendered through OpenGL appear fuzzy which was the opposite of what I needed.  So, all in all, faking 3D rotation was a more straight forward solution in this case.

Problem 3: Retractable arms

Having a robot with hoses for arms, once brought up by Dean, became a very important feature of the character.  For one, hose arms are very iconic of 50′s sci-fi comic books and they underline the mechanical workings of the robot.  For the other, they are very flexible because they allow the robot to interact with objects all over the screen.

It took some iterating on the development side of things to come up with the right look and feel.  I tried a number of approaches – vertlet rope simulation, dangling Chipmunk bodies distributed along a curve, a path following a free-form curve, … but nothing felt quite right.  The more complicated simulation I devised, the more unnatural the behavior seemed.  In the end I scrapped all of these approaches and went with a simple, 3-point cubic Bézier curve.

Cubic Béziers are defined thusly:

Now, if you are like me, instead of a clear equation you will see a jumble of Egyptian hieroglyphs.  Fortunately, Wikipedia, the source of all light and harmony in the universe, has a neat geometric explanation of Bézier curves which not only makes a lot more sense to me, it also gives me an algorithm of how to approximate such curve parametrically using simple linear interpolation:

All hail Wikipedia!  But back to Bobo’s arms.  As I mentioned already, they are constructed using these curves with three fixed points:

  1. The attachment point on Bobo’s body
  2. Desired claw position
  3. Center point, smack in the middle inbetween the two

In addition to these 3 pass-through points, in code I create 4 additional control points to model a basic-looking S curve for each arm – two control points between the body attachment and the center point and two control points between the center point and the claw.  Then, using the OpenGL triangle strip and a repeating texture, I draw the actual hose and place a CCSprite at each end of the hose to complete the illusion.

Animating the hose turned out being equally simple.  At any given point in time, I know where the body attachment is (based on where Bobo’s body is) and where the claw should be.  The rest of the hose curve is calculated for me through the Bézier approximation above.  To move the arm, I linearly interpolate between the claw’s current position and the claw’s desired position and, at a slightly slower rate, between the arm’s current center position and the arms’s desired center position to give the hose a nice movement lag.  The body attachment point is fixed by Bobo’s body.  The rest of the curve automagically animates along.

Put it all together, add cute sound effects, and you have yourself a robot!

Confidential to @atzoum: I’ll do a post on Chipmunk backing (including how Bobo moves cpBodies around) next.  Stay tuned!


Task Sequencer or how I was lazy to learn Lua

About half-way through coding of Bobo Explores Light, it became apparent that the book needed scripted sequences.  For example, on the Glow in the Dark page, Bobo pops up next to a fridge with a bunch of magnets on it.  He reaches out and arranges the magnets in such a way as to create a robot face with which he then proceeds to have a conversation.  The Reflection page has a similar need.  It shows a laser gizmo and four mirrors that the user can position and rotate, such that the laser light reflects and bounces all over.  The first time most people see this page, they don’t know what to do.  So, we script Bobo to demonstrate how the mirrors can be used.

From what I understand, Lua is the perfect language to achieve exactly that.  It is a simple and flexible script that gets interpreted at run-time and that can be hooked into objects and methods in the actual Objective-C code.  Most importantly, it’s already written.  Sadly for me, I have little patience learning yet another syntax for yet another language that I’ll most likely only use once.  So, I looked elsewhere.

Cocos2D comes with a really neat concept of “actions”.  Basically, every node in the scene (sprites, particles, the page itself, etc.) can execute an action.  Most commonly, actions allow you to modify a property of an object over time.  As an example, there is a CCRotateBy action which, when run on an object, rotates that object by a specified number of degrees over a specified amount time.  The following code would rotate mySprite by 90 degrees over the period of 3 seconds:

[mySprite runAction:[CCRotateBy actionWithDuration:3 angle:90]];

This is a really handy mechanism, because you can run an action on a node and forget about it.  Also, there are a slew of actions already defined, anything from actions that tween a property over time (such as an angle) to actions that execute callbacks or batch a bunch of other actions into a sequence.  The batching of actions into a sequence is especially useful, because you can, for example, move a sprite 100px to the right, rotate it by 180 degrees, move it 100px to the left, rotate it again by 180 degrees, and you have yourself a patrol loop for an enemy soldier that you can then repeat forever.

In the case of Bobo, however, I still needed a little more control.  Specifically, I needed to string together a sequence of actions for Bobo, some of which would be run concurrently, others of which would block until previous actions got finished, and so on.  On the Reflection page, for example, I needed the code to accomplish the following:

  1. Find Bobo’s unused arm and move mirror 1 to predefined position
  2. Find Bobo’s unused arm and move mirror 2 …
  3. Find Bobo’s unused arm and move mirror 3 …
  4. Find Bobo’s unused arm and move mirror 4 …
  5. Find Bobo’s unused arm and move the laser to predefined position
  6. Find Bobo’s unused arm and turn the laser on
  7. Say “Tada!”
  8. Wait 1 sec
  9. Find Bobo’s unused arm and turn off the laser

Since Bobo has two arms, actions 1 and 2 can be executed simultaneously.  The same can be said for actions 3 and 4 and actions 5 and 6.  However, action 7 needs to wait until action 6 (and all actions prior to it) have finished executing.  Finally, actions 8 and 9 need to happen in succession, not concurrently.

One solution would be to create a bunch of actions that execute callbacks that poll whether arms are available or not and that, depending on the result, execute other actions and their respective callbacks.  Hello spaghetti code!

Another solution, and the one that I ultimately ended up using, was writing a sequencer.

Imagine an object, a sequencer, that is a fancy wrapper around a FIFO list of other objects, tasks.  You can add tasks into a sequencer at any point and then periodically call the “execute” method that walks through all the tasks in the list and, given some simple rules, calls “execute” on them in turn.  Once a task signals that it has completed, it is removed from the list until, eventually, the sequencer ends up being empty.

To support multiple tasks being run concurrently, each task exposes a variable that defines its blocking behavior.  There are three possibilities:

  • BlockSelf – When sequencer encounters this task, it should stop executing any following tasks until the current task signals that it has finished.
  • BlockAll – When sequencer encounters this task, it should stop executing any following tasks until the current task and all previous tasks signal that they have finished.
  • PassThrough - When sequencer encounters this task, it should continue executing following tasks regardless of whether the current task is still in progress

Using a combination of these behaviors, the Reflection page script translates into the following sequencer calls:

[boboSequencer addTask:[MoveBodyTask taskWithBody:mirror1 position:pos1 rotation:rot1]
               block:kPassThrough];
[boboSequencer addTask:[MoveBodyTask taskWithBody:mirror2 position:pos2 rotation:rot2]
               block:kPassThrough];
[boboSequencer addTask:[MoveBodyTask taskWithBody:mirror3 position:pos3 rotation:rot3]
               block:kPassThrough];
[boboSequencer addTask:[MoveBodyTask taskWithBody:mirror4 position:pos4 rotation:rot4]
               block:kPassThrough];
[boboSequencer addTask:[MoveBodyTask taskWithBody:laser position:laserPos rotation:laserRot]
               block:kBlockAll];
[boboSequencer addTask:[SwitchLaserTask taskWithLaserOn:YES] block:kBlockSelf];
[boboSequencer addTask:[SpeakTask taskWithSound:SOUND_BOBO_TADA] block:kBlockSelf];
[boboSequencer addTask:[WaitTask taskWithDelay:1] block:kBlockAll];
[boboSequencer addTask:[SwitchLaserTask taskWithLaserOn:NO] block:kBlockSelf];

The execute method on MoveBodyTask first checks to see if Bobo has a free arm available.  If so, it grabs it, marks it as unavailable, and starts using it however needed.  If there is no free arm, the execute method simply turns into a noop and keeps waiting in the sequencer’s task list until one of the arms becomes available.  Hence, the first four tasks move the mirrors in sequence and the laser doesn’t get switched on until all the mirrors as well as the laser get positioned correctly.

This made it very simple for me to see what’s going on in code, as well as to modify the sequence however necessary without too much trouble or fear of introducing obscure bugs.

Later I discovered that this simple sequencer / task combo was effective all over the place.  For example, each time the user navigates to the next (or previous) page, a set of tasks needs to be performed: pre-load assets for the next page, slide the UI over, unload data from the previous page, clean up UI, etc.  At first, I hard-coded this sequence into the code manually.  However, then I switched to using the sequencer which allowed me to quickly and easily reshuffle some of these tasks around to compare performance and responsiveness of the app during page turns and come up with the most optimal arrangement.  Butter!

Anyway, enough of me talking – take a look (and feel free to use) the code itself:

Sequencer.h Sequencer.m
DemoProject.xcode


For the demo project, open up the Console window in Xcode and watch the logs (nothing actually happens on screen).  Let me know if it comes in handy!