tag:blogger.com,1999:blog-35642270486314039222024-03-05T13:46:02.658-08:00A. Patterson ProductionsiOS App DevelopmentUnknownnoreply@blogger.comBlogger14125tag:blogger.com,1999:blog-3564227048631403922.post-63398402803211143412012-03-19T22:23:00.001-07:002014-02-20T17:20:52.350-08:00Adding Game Center achievements to Balls AboundBased on my love of having a checklist to complete in a game, I decided that Balls Abound needed achievements before I could call it finished. The hope, of course, is that achievements will keep people who need a goal playing the game. I'm already supporting Game Center for leaderboards, this should be simple...<br />
<br />
<span style="color: #3d85c6; font-size: large;">Defining the Achievements</span>
<br />
<div>
<br />
First thing to do was figure out what achievements to make available. In a pinch I created 3 or 4 achievements for getting various levels of score in the game. Next, I put a lot of effort into stereo positioned sound for the game, so to encourage people to experience it I added an achievement for plugging in headphones. I also added a couple of hidden achievements for the "easter eggs" in the game. After that things got tougher, and I ended up just mucking around with the game and stumbling on a few ideas I liked. The full list of achievements I decided upon is as follows:<br />
<br /></div>
<div align="center">
<table border="0" cellpadding="2"><tbody>
<tr><td></td><td><b>Name</b></td><td><b>Description</b></td><td><b>Hidden</b></td><td><b>Points</b></td></tr>
<tr><td colspan="5"><hr />
</td></tr>
<tr><td valign="top"><img alt="" src="http://i.imgur.com/byWv2.gif" title="Hosted by imgur.com" /></td><td valign="top"><em>Karma</em></td><td valign="top">purchased the full game</td><td align="center" valign="top">Yes</td><td align="center" valign="top">100</td></tr>
<tr><td colspan="5"><hr />
</td></tr>
<tr><td valign="top"><img alt="" src="http://i.imgur.com/2GTJj.gif" title="Hosted by imgur.com" /></td><td valign="top"><em>Touchdown</em></td><td valign="top">used finger on splash screen</td><td align="center" valign="top">Yes</td><td align="center" valign="top">50</td></tr>
<tr><td colspan="5"><hr />
</td></tr>
<tr><td valign="top"><img alt="" src="http://i.imgur.com/fVlNt.gif" title="Hosted by imgur.com" /></td><td valign="top"><em>Poke the crab</em></td><td valign="top">poked the crab 100 times</td><td align="center" valign="top">Yes</td><td align="center" valign="top">50</td></tr>
<tr><td colspan="5"><hr />
</td></tr>
<tr><td valign="top"><img alt="" src="http://i.imgur.com/FfKJo.gif" title="Hosted by imgur.com" /></td><td valign="top"><em>Century Club</em></td><td valign="top">earned 100 points in a game</td><td align="center" valign="top">No</td><td align="center" valign="top">25</td></tr>
<tr><td colspan="5"><hr />
</td></tr>
<tr><td valign="top"><img alt="" src="http://i.imgur.com/H9wt0.gif" title="Hosted by imgur.com" /></td><td valign="top"><em>High Roller Club</em></td><td valign="top">earned 10,000 points in a game</td><td align="center" valign="top">No</td><td align="center" valign="top">50</td></tr>
<tr><td colspan="5"><hr />
</td></tr>
<tr><td valign="top"><img alt="" src="http://i.imgur.com/bcW3V.gif" title="Hosted by imgur.com" /></td><td valign="top"><em>100 Grand Club</em></td><td valign="top">earned 100,000 points in a game</td><td align="center" valign="top">No</td><td align="center" valign="top">75</td></tr>
<tr><td colspan="5"><hr />
</td></tr>
<tr><td valign="top"><img alt="" src="http://i.imgur.com/MiTNn.gif" title="Hosted by imgur.com" /></td><td valign="top"><em>Mile High Club</em></td><td valign="top">earned 1,000,000 points in a game</td><td align="center" valign="top">No</td><td align="center" valign="top">100</td></tr>
<tr><td colspan="5"><hr />
</td></tr>
<tr><td valign="top"><img alt="" src="http://i.imgur.com/9BVDu.gif" title="Hosted by imgur.com" /></td><td valign="top"><em>Palindrome</em></td><td valign="top">lit the signs in order<br />
'B','A','L','L','S','S','L','L','A','B'</td><td align="center" valign="top">No</td><td align="center" valign="top">50</td></tr>
<tr><td colspan="5"><hr />
</td></tr>
<tr><td valign="top"><img alt="" src="http://i.imgur.com/kTAuM.gif" title="Hosted by imgur.com" /></td><td valign="top"><em>Stuck on you</em></td><td valign="top">cleared three fields of coconuts,<br />
hitting only the letter 'A'</td><td align="center" valign="top">No</td><td align="center" valign="top">100</td></tr>
<tr><td colspan="5"><hr />
</td></tr>
<tr><td valign="top"><img alt="" src="http://i.imgur.com/PTJbB.gif" title="Hosted by imgur.com" /></td><td valign="top"><em>Double Hitter</em></td><td valign="top">turned two signs on in one bounce,<br />
two signs off in one bounce, and<br />
turned one on and one off in one bounce,<br />
all in one game</td><td align="center" valign="top">No</td><td align="center" valign="top">100</td></tr>
<tr><td colspan="5"><hr />
</td></tr>
<tr><td valign="top"><img alt="" src="http://i.imgur.com/s5cP8.gif" title="Hosted by imgur.com" /></td><td valign="top"><em>Audiophile</em></td><td valign="top">plugged in headphones</td><td align="center" valign="top">No</td><td align="center" valign="top">100</td></tr>
<tr><td colspan="5"><hr />
</td></tr>
<tr><td valign="top"><img alt="" src="http://i.imgur.com/x8QA3.gif" title="Hosted by imgur.com" /></td><td valign="top"><em>Nausea</em></td><td valign="top">cleared two fields of coconuts,<br />
while playing upside down</td><td align="center" valign="top">No</td><td align="center" valign="top">75</td></tr>
<tr><td colspan="5"><hr />
</td></tr>
<tr><td valign="top"><img alt="" src="http://i.imgur.com/nAGto.gif" title="Hosted by imgur.com" /></td><td valign="top"><em>Multiball Master</em></td><td valign="top">earned 1,000,000 in games<br />
with each of the types of ball</td><td align="center" valign="top">No</td><td align="center" valign="top">75</td></tr>
<tr><td colspan="5"><hr />
</td></tr>
</tbody></table>
</div>
<br />
Luckily I had created the pelican icons a while back and finally have a use for them.<br />
<br />
In total Balls Abound has 10 visible achievements and 3 hidden ones, for players to work towards. iTunes Connect is used to define the achievements in Game Center. Use iTunes Connect to add localizations, point values, icons, and other achievement properties. The identifiers you assign to the achievements defined in iTunes Connect are referenced from your code when obtaining GKAchievement objects, which are used to communicate progress to Game Center.<br />
<br />
<span style="color: #3d85c6; font-size: large;">Tracking the Achievements</span>
<br />
<div>
<br />
I am rushing to get this game finished, and I am the only person touching the codebase. With this in mind, my main goal is not the most beautiful design right now, I just need things to work.<br />
<br />
I am already using Core Data to track some of the user settings in the game (muted, etc..) so I decided to model a new class in XCode to track achievements progress. Easy enough, now I have a new managed object that holds floats that represent the progress towards each of the achievements; they mirror the data stored in GameKit's GKAchievement objects. Another require for storing achievement data is, multiple Game Center users might play on the same device. So you need to track achievement progress on a per user basis. Adding a userId attribute to the UserAchievements managed object allows tracking achievements on a per user basis, creating one UserAchievements object for each userId. I create or attach the UserAchievements for the current user to my game settings whenever Game Center authentication is completed. I also use this as an opportunity to load the users current achievement status from Game Center, and reconcile any discrepancies between local and remote progress (using the rule, higher progress always wins).<br />
<br />
Now that there is a data model capable of storing user achievement progress, three more things are required.<br />
<ol>
<li>an AchievementTracker that will monitor achievements and update the UserAchievements data</li>
<li>an AchievementBoard UI to listen for UserAchievements changes and put notifications on screen</li>
<li>the ScoreKeeper must listen for UserAchievements changes and report updates to Game Center</li>
</ol>
</div>
<div>
The AchievementTracker class is bound to all the points needed to listen on when detecting achievement progress. The AchievementTracker has the ability to track game state, coconut hits, sign hits, and accelerometer readings. This allows the AchievementTracker to decide when progress towards an achievement is made and update the UserSettings accordingly.</div>
<div>
<br />
<span style="color: #3d85c6; font-size: large;">Listening for the Achievements</span>
<br />
<div>
<br />
The ScoreKeeper and AchievementBoard both listen for achievement updates through KVO on the UserAchievements object. In order to achieve a reasonably performant option, I treat achievement progress internally as an int, rather than a double, which helps limit the number of updates that I have to process. Updates to achievement progress are relatively expensive, they trigger a KVO transaction which is followed by a managed context save and possibly network traffic through GameKit reporting through GKAchievement objects.<br />
<br /></div>
</div>
<div>
</div>
<span style="color: #3d85c6; font-size: large;">Displaying the Achievements</span>
<br />
<div>
<br />
I decided to create my own UI for displaying achievements, the AchievementBoard. This was largely due to the fact that Apple only provide functionality for displaying achievements in iOS 5.0+ and I wanted to support iOS 4.2+.<br />
<br />
I wanted a UI that would animate it's entrance and exit, was capable of handling multiple simultaneous achievement updates, and could display at top and bottom of the screen. The AchievementBoard holds a queue of achievement updates. If progress towards an achievement is made, it will either append the update into the AchievementBoard's queue, or replace the progress in an existing update for the same achievement. The AchievementBoard coordinates the onscreen display of achievement updates that are in it's queue. This system allows multiple achievements to be acted on simultaneously, with no notifications being ignored.<br />
<br />
Coming up with graphics that tied into the game took a number of iterations of producing graphics and redeploying the game to test their visuals. I eventually came up with the following, which I think works well.</div>
<div>
<br /></div>
<div>
<div class="separator" style="clear: both; text-align: center;">
<a href="http://i.imgur.com/vgpLe.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://i.imgur.com/vgpLe.png" width="400" /></a>
<a href="http://i.imgur.com/sdk1k.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://i.imgur.com/sdk1k.jpg" width="400" /></a>
</div>
<br /></div>
<div>
<span style="color: #3d85c6; font-size: large;">The Final Touches</span>
<br />
<div>
<br />
<div>
With achievements being monitored, updates being communicated to both Game Center and the display, there is just one more task before achievements can be considered fully integrated. There needs to be a button on the high score view so people can view their achievements in Game Center. Ideally I would create an in game view for achievements and leaderboards, but that is out of scope for now. So the quick solution is a simple button that opens a default GKAchievementViewController for displaying achievements. This leads to launching Inkscape and drawing a new icon for viewing achievements and updating the icon that already existed for viewing leaderboards. This in turn means running TexturePacker again, and finall PVRTextTool to generate my pvr with mipmaps. Anyway, achievements are finally present in Balls Abound.</div>
<div>
<br /></div>
<br />
Everything for achievements is plugged in and working. Except I'm suddenly down from a nice smooth 60fps to ~40fps in many parts of the game. I'll save the details for another post, but a day or two of work later and I am back to 60fps consistently (my solution involved clipping with glScissor and a hack to CCLabelBMFont). If only everything in life were so straightforward...</div>
<div>
<br /></div>
<div>
<br /></div>
<br />
<br />
<ul>
</ul>
</div>
Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-3564227048631403922.post-43636738010810779602012-03-09T21:07:00.001-08:002012-03-13T09:10:18.903-07:00Didn't make the cutThis little guy just didn't fit into <a href="http://patterware.blogspot.com/p/balls-abound.html">Balls Abound</a> like I thought, but at least he made the blog. And now I'm seriously done making gifs for a bit.<br />
<br />
<div style="text-align: center;">
<a href="http://imgur.com/uuY1R"><img alt="" src="http://i.imgur.com/uuY1R.gif" title="Hosted by imgur.com" /></a></div>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-3564227048631403922.post-29108130210833413552012-03-09T20:55:00.002-08:002012-03-13T09:10:27.721-07:00Halftoned animated gif<div class="separator" style="clear: both; text-align: left;">
This is the last gif for a while, I'm simply wasting time now.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1Q_Enb5QiCLV1GagF_hJ0Dyx-Fido4d43pwUUU8KIteK85EXwPFzS3KisIX2Oc7wMBMzZHbvQyHjzUrUbiegMNcKsV9d_FLxtqu2V7CBUIbqT1tE2pwqahsrYOvJ_mHwliHYRjCmiu5E/s1600/bounce.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1Q_Enb5QiCLV1GagF_hJ0Dyx-Fido4d43pwUUU8KIteK85EXwPFzS3KisIX2Oc7wMBMzZHbvQyHjzUrUbiegMNcKsV9d_FLxtqu2V7CBUIbqT1tE2pwqahsrYOvJ_mHwliHYRjCmiu5E/s1600/bounce.gif" width="360" /></a></div>
<br />
In order to produce a larger image with smaller file size (though still a sizeable 5.5MB) I created a layer when creating this gif in Gimp and rendered a grid with lines 1 pixel wide spaced 2 pixels apart (so every other row and column are painted). Then "selection from alpha" on this layer and delete from each of the animations layers. You are left with a bunch of layers that are mostly alpha right now, just little dots of color floating there now. I then remove the alpha channel from each layer with a background color of black. Finally switch to indexed color mode with no dithering, and then optimize animation for gif. This particular image has 466 layers and runs at 60fps.Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-3564227048631403922.post-85592852993707878632012-03-09T18:39:00.000-08:002012-03-13T09:14:04.991-07:00Balls Abound Splash screen as animated gif<div class="separator" style="clear: both; text-align: left;">
I think this turned out nicely. I outline how I am making gifs <a href="http://patterware.blogspot.com/2012/03/just-gif-of-ball-bouncing.html">here</a>.</div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div style="text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9OySfMdawulPnTqvznDjpzV-IxEJ8-dNgjAWPBwkdOGv3zG7jlOR5kKxyrryCpJCd8iN5eUWRsZzxYZGo90VlCa0d7uT-bkFUISXd89TJ-RYfnZxsZy5Dw2-r7wQj2aw1LeTZEu3gV4w/s1600/balls_abound_splash.gif">
<img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9OySfMdawulPnTqvznDjpzV-IxEJ8-dNgjAWPBwkdOGv3zG7jlOR5kKxyrryCpJCd8iN5eUWRsZzxYZGo90VlCa0d7uT-bkFUISXd89TJ-RYfnZxsZy5Dw2-r7wQj2aw1LeTZEu3gV4w/s1600/balls_abound_splash.gif" />
</a></div>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-3564227048631403922.post-72517930968431666972012-03-09T16:03:00.001-08:002012-03-13T09:10:50.766-07:00Creating 2D animations using Inkscape<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<a href="http://patterware.blogspot.com/p/balls-abound.html">Balls Abound</a> contains a number of animated elements. I used Inkscape to hand render each of the elements below.<br />
<br />
<table align="center" cellpadding="0" cellspacing="4" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="http://imgur.com/kcb3V"><img alt="" src="http://i.imgur.com/kcb3V.gif" title="Hosted by imgur.com" /></a></td>
<td style="text-align: center;"><a href="http://imgur.com/zkunZ"><img alt="" src="http://i.imgur.com/zkunZ.gif" title="Hosted by imgur.com" /></a></td>
<td style="text-align: center;"><a href="http://imgur.com/iG5t3"><img alt="" src="http://i.imgur.com/iG5t3.gif" title="Hosted by imgur.com" /></a></td>
</tr>
<tr><td class="tr-caption" style="text-align: center;">Flame</td>
<td class="tr-caption" style="text-align: center;">Crab Idle</td>
<td class="tr-caption" style="text-align: center;">Crab Walk</td></tr>
</tbody></table>
<table align="center" cellpadding="0" cellspacing="4" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr>
<td style="text-align: center;"><a href="http://imgur.com/K4CMr"><img alt="" src="http://i.imgur.com/K4CMr.gif" title="Hosted by imgur.com" /></a></td>
</tr>
<tr><td class="tr-caption" style="text-align: center;">Pelican Flight</td>
</tr>
</tbody></table>
<br />
Each of the above animations contains at least 2 keyframes that were hand drawn using Inkscape. Inkscape provides a function for creating an interpolated series of shapes that morph from a start shape to an end shape.<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjarsXzFbTLe3_exxj3InAY5yEB6s-3QZJeJbSIKiRYL9cnj7i2QKLyCHI_1O4RXidJ6VPgFhFCIU9U5MiBKtoYBGALA4J4CpmSS_n0AFzI25tOpF5oMrgVAvM2B0IO5obydSqGlL-GaUs/s1600/end_shapes.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="48" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjarsXzFbTLe3_exxj3InAY5yEB6s-3QZJeJbSIKiRYL9cnj7i2QKLyCHI_1O4RXidJ6VPgFhFCIU9U5MiBKtoYBGALA4J4CpmSS_n0AFzI25tOpF5oMrgVAvM2B0IO5obydSqGlL-GaUs/s320/end_shapes.png" width="320" /></a></div>
</td></tr>
<tr><td class="tr-caption" style="text-align: center;">Start and end shapes</td></tr>
</tbody></table>
<br />
Using this function you can select shapes from your key frames as start and end shapes, and interpolate between them to create a series of intermediary shapes.<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhvdmF5etuT92gvyxRrbbHN2gMq0e1DZCf8vSQQlshyABGAXbuCyckCYo-KeOJP897BKWRxVNp9akunXH3LGpCeD7GlaMtnrMLX1bWww-4s9fuqx8sE68B2Qdg5REinLcdG-Hs6tvOMzTA/s1600/interpolate_straight.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="48" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhvdmF5etuT92gvyxRrbbHN2gMq0e1DZCf8vSQQlshyABGAXbuCyckCYo-KeOJP897BKWRxVNp9akunXH3LGpCeD7GlaMtnrMLX1bWww-4s9fuqx8sE68B2Qdg5REinLcdG-Hs6tvOMzTA/s320/interpolate_straight.png" width="320" /></a></div>
</td></tr>
<tr><td class="tr-caption" style="text-align: center;">Straight interpolation, left to right, no exponent</td></tr>
</tbody></table>
<br />
This set of shapes will define the frames of your 2D animation. There are some drawbacks to this unfortunately. You will be given back a group of objects that needs to be separated out into layers. If you want to interpolate between two objects with gradient fills you have some work ahead as well.<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlsPrt-Hph576msD0TpaFx7P4UEcAGAuUf_huGNX0b8FV5tlL6bmFxZoxEleOqu256q1MClty5_kQGRuQkjBwzj2tiS9vFs51oQ9yMgxH00MgE2krQmMAY2TCng2kl-T5MTpdHEz2c3zc/s1600/interpolate_gradient_fill.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="48" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlsPrt-Hph576msD0TpaFx7P4UEcAGAuUf_huGNX0b8FV5tlL6bmFxZoxEleOqu256q1MClty5_kQGRuQkjBwzj2tiS9vFs51oQ9yMgxH00MgE2krQmMAY2TCng2kl-T5MTpdHEz2c3zc/s320/interpolate_gradient_fill.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
</td></tr>
<tr><td class="tr-caption" style="text-align: center;">Gradient fills don't interpolate properly</td></tr>
</tbody></table>
<br />
Solid fills do fortunately interpolate, if you require that.<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEirGLMFc2WaDs1m8lHlQU_mk8Gj7cxoVujCjMdC2KtKZNfEty1dk5gSTHgwjBsvroEuOQvIDa_YoYeCzVUPd9CX55VRU1ecYeI89hUKHwSFDO9U6poSSW1kxgqFRoFJvchgkeE9m_tToGY/s1600/interpolate_solid_fill.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="48" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEirGLMFc2WaDs1m8lHlQU_mk8Gj7cxoVujCjMdC2KtKZNfEty1dk5gSTHgwjBsvroEuOQvIDa_YoYeCzVUPd9CX55VRU1ecYeI89hUKHwSFDO9U6poSSW1kxgqFRoFJvchgkeE9m_tToGY/s320/interpolate_solid_fill.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
</td></tr>
<tr><td class="tr-caption" style="text-align: center;">Solid fills do interpolate correctly</td></tr>
</tbody></table>
<br />
I like to separate each of my animated frames onto it's own layer, and use a dummy layer to hold a rectangle that will be my export viewport. This way I can select the viewport rectangle, hide the dummy layer, and then export the animated frame images by simply turning on the desired layer and exporting. There are likely better tools out there for this, Inkscape is very labour intensive. I'm sure this could be scripted in Inkscape as well, but haven't bother to look into it. You will need to repeat the interpolate process for each shape in your key frames, and gradient fills don't interpolate with the shape, so you will be hand tweaking fills on each frame for each shape. But the end result can be very good if you are ready to invest the time.<br />
<br />
The pelican animation made use of the exponent feature in Inkscape's interpolate function. When interpolating between two shapes you can set an exponent which will create an easing function. The size of the exponent determines the amount of easing.<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEge1ROX_VuXnWiSokFV-o25k7cYFq_lPbNco7Em6WcZgU6EgjmS5TA9DIvRJrO0agRB3JupbAZgtovkUCmJ6cS2fezH7NorWz_pq1jLTrQXGzcAi00Gw0_IE1haJ3bwLvN-QycmYI0qNLU/s1600/left_exp1_interpolation.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="48" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEge1ROX_VuXnWiSokFV-o25k7cYFq_lPbNco7Em6WcZgU6EgjmS5TA9DIvRJrO0agRB3JupbAZgtovkUCmJ6cS2fezH7NorWz_pq1jLTrQXGzcAi00Gw0_IE1haJ3bwLvN-QycmYI0qNLU/s320/left_exp1_interpolation.png" width="320" /></a></div>
</td></tr>
<tr><td class="tr-caption" style="text-align: center;">Exponent = 1, leftmost shape selected first</td></tr>
</tbody></table>
<br />
The order in which you select the two end points of your interpolation determines which end the easing occurs.<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfHvSPr3gM6z6_HXoP9iWnxAnVtSpi9zjlH40c02ncmuGSJfkGKYWsU5RB8U9HlKqGfaqDs5U6hEDiJkTOjgLT4i_CkAtpNXoYXT_m2YFMzugu2G62S2JBhcZ5C0-BVJWvuvghz8Mx4b0/s1600/right_exp1_interpolation.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="48" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfHvSPr3gM6z6_HXoP9iWnxAnVtSpi9zjlH40c02ncmuGSJfkGKYWsU5RB8U9HlKqGfaqDs5U6hEDiJkTOjgLT4i_CkAtpNXoYXT_m2YFMzugu2G62S2JBhcZ5C0-BVJWvuvghz8Mx4b0/s320/right_exp1_interpolation.png" width="320" /></a></div>
</td></tr>
<tr><td class="tr-caption" style="text-align: center;">Exponent = 1, rightmost shape selected first</td></tr>
</tbody></table>
<br />
The easing on the extremes of the pelicans flap are baked into the animated frames, this allows the animation to be run at a constant frame rate in the game, but still appear as natural flight.<br />
<br />
<br />
The torch animation uses a bunch of code to alter the timing of four distinct stages of the flame animation that run in an endless loop. The smoke emitted from the torches is synchronized with the flickering of the flames. Other properties of the flame animation, including the lighting, are affected by the Perlin noise function. Perlin noise with the right inputs can produce output that flickers very much like fire.<br />
<br />
<br />
<br />
I used Blender to create the balls and the coconuts.<br />
<br />
<div style="text-align: center;">
<table align="center" cellpadding="0" cellspacing="16" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: left;"><tbody>
<tr>
<td style="text-align: center;"><a href="http://imgur.com/ektvK"><img alt="" src="http://i.imgur.com/ektvK.gif" title="Hosted by imgur.com" /></a></td>
<td style="text-align: center;"><a href="http://imgur.com/85fhS"><img alt="" src="http://i.imgur.com/85fhS.gif" title="Hosted by imgur.com" /></a></td>
<td style="text-align: center;"><a href="http://imgur.com/xqzWi"><img alt="" src="http://i.imgur.com/xqzWi.gif" title="Hosted by imgur.com" /></a></td>
<td style="text-align: center;"><a href="http://imgur.com/h4UCP"><img alt="" src="http://i.imgur.com/h4UCP.gif" title="Hosted by imgur.com" /></a></td>
<td style="text-align: center;"><a href="http://imgur.com/sSGEO"><img alt="" src="http://i.imgur.com/sSGEO.gif" title="Hosted by imgur.com" /></a></td>
<td style="text-align: center;"><a href="http://imgur.com/VLAdR"><img alt="" src="http://i.imgur.com/VLAdR.gif" title="Hosted by imgur.com" /></a></td>
</tr>
<tr>
<td class="tr-caption" style="text-align: center;">Beach Ball</td>
<td class="tr-caption" style="text-align: center;">Happy Ball</td>
<td class="tr-caption" style="text-align: center;">Wood Ball</td>
<td class="tr-caption" style="text-align: center;">Eight Ball</td>
<td class="tr-caption" style="text-align: center;">Eye Ball</td>
<td class="tr-caption" style="text-align: center;">Coconut</td></tr>
</tbody></table>
<br />
<div style="text-align: left;">
Each of the above renderings used the same technique. Using Blender, create a spherical 3D model and point the camera some distance from it's circumference, and make the ball fill the cameras view. Then setup a timeline where the camera rotates 360 degrees around the ball for however many frames you want the animation to take (I used 64 frames; though most of the balls repeat half way round and have been shrunk to 32 images). In the game the balls have a a bunch of simple math applied to calculate the circumference of the ball and determine the distant that the ball needs to move/roll before the next frame should be displayed. Keeping track of setPosition calls it is possible to adjust the sprites display frame and rotation to provide the illusion of a rolling 3D ball.</div>
<div style="text-align: left;">
<br /></div>
<div style="text-align: left;">
In the game the coconuts rotate in accordance with a Perlin noise function. The rotation of each coconut is based on the output of the Perlin noise function that has been provided x, y, and z as coconut position x, coconut position y, and time, respectively. When the game is in play, the x and y parameters above have an additional play ball offset added to each. This additional offset causes the spin of the coconut grid to be affected by the position of the in play game ball, the effect is subtle.</div>
<div style="text-align: left;">
<br /></div>
</div>Unknownnoreply@blogger.com2tag:blogger.com,1999:blog-3564227048631403922.post-2125920393133909512012-03-09T08:51:00.000-08:002012-03-13T09:14:25.800-07:00Ralph Baer, what a cool guy<div style="text-align: center;">
<iframe allowfullscreen="" frameborder="0" height="600" mozallowfullscreen="" src="http://player.vimeo.com/video/37870722?title=0&byline=0&portrait=0" webkitallowfullscreen="" width="800"></iframe></div>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-3564227048631403922.post-84967646795466893282012-03-08T12:00:00.000-08:002012-03-13T09:11:10.490-07:00Creating an animated gif using Gimp<div class="separator" style="clear: both; text-align: left;">
Here's a few frames taken from <a href="http://patterware.blogspot.com/p/balls-abound.html">Balls Abound</a>.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitBuBkx0OZVDYQggQAyIMxMVpaiaDPBc5TyyFWE6jIOk05224meXBjMidbQL2Y8bBviDtyWxuBK3pRqQ9qVV8sW1INR_sKNydUfuyjqo5RaIAKPJEkpIba2PHaVb49gny10yLUAxoKj9c/s1600/bounce.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitBuBkx0OZVDYQggQAyIMxMVpaiaDPBc5TyyFWE6jIOk05224meXBjMidbQL2Y8bBviDtyWxuBK3pRqQ9qVV8sW1INR_sKNydUfuyjqo5RaIAKPJEkpIba2PHaVb49gny10yLUAxoKj9c/s1600/bounce.gif" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
I created this with <a href="http://www.gimp.org/">Gimp</a>. </div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
There are 120 source images taken from a set I made earlier when <a href="http://patterware.blogspot.com/2012/03/creating-video-of-balls-abound.html">producing video from the game</a>. Using "Open as layers" I opened the source images into a Gimp project and now have a set of layers with the ball running from left to right. I then duplicated the Gimp project, deleted the first and last layer, and reversed the layer order using the layer stack menu. I then save the duplicate project as an xcf file, and return to the first Gimp project which has the original 120 images in order. Now using "Open as layers" again I open up the xcf file (with 118 frames in reverse order) and finally I have 238 frames of the ball running left -> right -> left. Now it's a simple matter of scaling to your desired size, dithering down to indexed color, running the animate for gif optimizer, and save your gif with 16ms between frames (for ~60fps).</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
One day I will make the effort to brush up on my Gimp scripting (another language I have no capacity to retain once learned) and make this easier on myself.</div>
<div class="separator" style="clear: both; text-align: center;">
</div>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-3564227048631403922.post-49147568408833863232012-03-07T12:40:00.000-08:002012-03-13T09:11:21.261-07:00Coming Soon... Balls Abound<div class="separator" style="clear: both; text-align: left;">
Soon, I'm pretty sure... I decided once again to hold off submitting <a href="http://patterware.blogspot.com/p/balls-abound.html">Balls Abound</a> to the app store, and am adding in game center achievements before the version one upload happens. Here's a logo for the game in the meantime:</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizqTG5-fvYQNca8_rvw6i7qoWeTlxfqEgeVbV4y0QZ9tRugai764UCjpyATo4mHwPgtQuQgdrv3xxbYzejIo2NjH6H8kTMvrtibIBGF3kJD_lT6gNG1ObAWkB1cCvjqEVVULVWTQ8-Ew4/s1600/balls-abound.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizqTG5-fvYQNca8_rvw6i7qoWeTlxfqEgeVbV4y0QZ9tRugai764UCjpyATo4mHwPgtQuQgdrv3xxbYzejIo2NjH6H8kTMvrtibIBGF3kJD_lT6gNG1ObAWkB1cCvjqEVVULVWTQ8-Ew4/s640/balls-abound.png" width="640" /></a></div>
<br />
Made using <a href="http://inkscape.org/">Inkscape</a> to assemble a bunch of graphics from the game.Unknownnoreply@blogger.com1tag:blogger.com,1999:blog-3564227048631403922.post-67169287994679197262012-03-06T16:22:00.000-08:002012-03-13T09:06:44.471-07:00Being an indie developerLast year I decided to attempt to earn a living without being required to get up, and go to a cube farm, on most of 5 days, out of each and every week. I was working at a job that was never going to match what I wanted to do every day, and was hard pressed to think of any existing job that did.<br />
<br />
So I saved for a while, and then I quit, and decided I would concentrate on making small mobile apps. Primarily games or something, really specific. Ideally there would be a paying market for small one off novelty items that don't require the thought and effort that goes into building a game, but alas that market doesn't seem to exist. So I'm putting in the effort to build a game, Balls Abound, a game I think is fun.<br />
<br />
Putting the polish on an app is time consuming and can lead to stalls in my productivity, somewhat akin to writers block. But I think the added depth, with the subtle motion of the menus and the text, were important to the overal aesthetic of the game. Making sure game center and in app purchase are working and adding code to detect when achievements have been reached; these things always stall my progress.<br />
<br />
What I really enjoy doing is doodling and fiddling with images. I like seeing funky output on computer displays. So I like combining images with maths to create unique and interesting visual effects. And, at least for now, that's what I am doing. And hopefully what I create can generate enough revenue for me to create more, allowing me to avoid the cube farm and it's low cycle fluorescent flicker.<br />
<br />
I'm not sure I've ever really laid out what my plans after quitting my job were. That may have contributed to the mess of plans that people built for me. Everyone has an idea that you don't care about, it's usually top secret. And everyone thinks that they deserve to be paid for no more effort than the movement of their jaw during an utterance of this idea that you couldn't care less about. I believe they simply do not understand the volume of effort that goes into building good apps.<br />
<br />
I've got a million crappy ideas, maybe a couple decent ones, and with a little spit and polish even a crappy idea can turn out to be a pretty fun toy (or at least that's what I'm banking on). But how does one get their fun toy in front of the general population? That's still to be answered. From where I'm sitting, it seems a great deal of luck can potentially help immensely.Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-3564227048631403922.post-381307324758778792012-03-05T16:05:00.000-08:002012-03-13T09:11:32.815-07:00Exporting 60fps video from cocos2d<div class="separator" style="clear: both; text-align: left;">
Here's the video. It looks a little different when the screen is in your hand being tilted around.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<param name="bgcolor" value="#FFFFFF" />
<div style="text-align: center;">
<object height="480" width="853">
<param name="movie" value="http://www.youtube.com/v/Q4yaO1o2o0k?hd=1&rel=0&fs=1&modestbranding=1">
</param>
<param name="allowFullScreen" value="true">
</param>
<param name="allowScriptAccess" value="always">
</param>
<embed src="http://www.youtube.com/v/Q4yaO1o2o0k?hd=1&rel=0&fs=1&modestbranding=1"
type="application/x-shockwave-flash"
allowfullscreen="true"
allowscriptaccess="always"
width="853" height="480">
</embed>
</object>
</div>
<br />
I couldn't put off creating a video of my upcoming game, Balls Abound, any longer. I still have a couple of tasks to wrap up before submitting the app, but I now have video export working. This is how I achieved it.<br />
<br />
First you have to make the decision, quick and easy by using a video camera to simply record yourself playing the game, or the more difficult route of professional quality hi def 60fps game play footage. I decided on the latter.<br />
<br />
The first step in getting video off your device is recording some playback. For this to work you need your game to be deterministic, and then you need a way to capture state on every frame so that it can be played back later. I am using cocos2d with box2d and building my game with XCode for iOS. By modifying CCTouchDispatcher and CCDirectorIOS (and how cocos2d interacts with the accelerometer, which I discuss in another post) I managed to defer posting of accelerometer and touch inputs to happen within the directors drawScene method. From there I simply record the current inputs to disk on every frame, and fix the delta time to 1/60 (for 60fps; this is important later on when you assemble the video and try to sync audio, which will be at 60fps).<br />
<br />
The next step is to play the game back using the inputs you have now recorded. Again, by modifying the CCDirectorIOS drawScene method, I was able to read the previously recorded inputs on each frame, and feed the stored accelerometer and touch inputs into the current frame's state for rendering. So now I am able to playback a previous game run to the screen in real time, but I need to save these frames to disk if I'm going to make a video.<br />
<br />
I found a solution in the cocos2d forums for grabbing the OpenGL framebuffer into a UIImage, and with another change to CCDirectorIOS I am now dumping each frame to a PNG on the device. I'm not sure this is the quickest solution in terms of performance, but it is functional, and PNG is necessary to ensure the quality of the final video (I tried JPG and was dissatisfied). Unfortunately to playback a 2 minute run of the game generates 7200 images and can take an hour or more using my iPod Touch 4G. And then you have to download the app from you device and extract the images somewhere to your hard drive, which can easily take 10 minutes or more, with well over 1GB of images. But finally it's done and you have a nice image sequence.<br />
<br />
But you still don't have any sound, and we need sound. This got trickier than I thought. The simplest approach seems to be to use Audacity (or whatever you prefer) to simply record the output from my iPod's headphone jack. Remember how we fixed the delta time to 1/60 when recording our game inputs? This is because in order to get our recorded audio to sync with our generated video, they have to share a frame rate. If we allowed the small changes in delta time that occur naturally when checking a display links timestamp then it would be impossible to sync audio and video in our final product. To ensure that the audio and video stay in sync, we need to do whatever we can to ensure the audio we record is played at a consistent 60fps. So, I modify CCDirectorIOS once again, and now I can play back the game through the physics engine only (ie. no OpenGL calls) and get the sound with as close as possible to 0.016s between each frame. I've seen fancier approaches that are a lot more work (keeping track of the data being fed through OpenAL and storing it as a PCM file), but this seems to work well enough.<br />
<br />
Now, I have a mess of different builds that I want to flip between easily. "Record gameplay", "Replay to screen", "Replay to file", "Replay sound only" are all things I want to do. In XCode in the project configurations, alongside Debug and Release, I added new configurations for the four types of build I wanted. I #ifdef'd the heck out of CCDirectorIOS when adding all the record/playback code so I simply add the necessary defines into my build configuration, and finally create four schemes that each use the appropriate build configuration. Now I can simply choose the build scheme I want and launch the game from XCode. Just remember you're still looking at around an hour turnaround time to dump a 2minute run from the game.<br />
<br />
Ok, we've recorded game play, dumped the resulting frames out to disk, recorded the audio that goes alongside and have a pile of input files to create a video now. I've heard ImageMagick will do this, but decided to go the easier route (for me anyway) and splash out $30 for Quicktime Pro. With QuickTime it's a simple matter of "Open image sequence" and point it at all your jpgs. After another short wait and you will have a video with no audio sitting in front of you. You should also have Audacity sitting with some audio that matches your video (which you recorded in by replaying the sound only). In order to align the audio and video, you want some landmark that is both audible and visible. In the movies they use a clapboard for this, but we didn't insert one into the game and maybe we don't need to. What we do have is a ball hitting the ground as the first noise that is heard. So I used QuickTime to find the frame when the ball first hits the ground. Now you can trim everything that happens before that ball hit noise in the audio that you have. Export the audio from Audacity to your favorite format, and then you can add to your movie at the desired frame using QuickTime. Finally, we have a video of the game with perfectly synced audio and video. Now you just need to use QuickTime to export it to a proper video format for publication.Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-3564227048631403922.post-63231593006228215462012-03-04T15:14:00.000-08:002012-03-13T09:11:41.942-07:00Perlin noise everywhereWhen I started making Balls Abound I wanted to create a subtle cartoon environment with a sense of realism unbeknownst in origin to the casual observer. I wanted nothing static on the screen, as there is very little static content in real life. Here is a sample of what I came up with:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPNl2d18P-HLmsrAT_xeEwWRicHuaQrWEMjy5b6iFKuC_tHlQqT8zV4bBFae3ej_2CFiJ7w5O_CMcIMJwjh4P6PmZTby6BqQ25tH847Ol2LHZZfZEI5WEfMeXss7ROj5bGM2RJK_aAq1g/s1600/perlin.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="213" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPNl2d18P-HLmsrAT_xeEwWRicHuaQrWEMjy5b6iFKuC_tHlQqT8zV4bBFae3ej_2CFiJ7w5O_CMcIMJwjh4P6PmZTby6BqQ25tH847Ol2LHZZfZEI5WEfMeXss7ROj5bGM2RJK_aAq1g/s320/perlin.gif" width="320" /></a></div>
<br />
<br />
I had read about <a href="http://en.wikipedia.org/wiki/Perlin_noise">Perlin noise</a> and its use in generating cloud effects in real time a while back, and always wanted to build something with it. I ended up going overboard with Perlin noise in Balls Abound, and it is used all over the place.<br />
<br />
The splash screen uses Perlin noise to adjust the z depth and scale of the characters being rendered. Using a noise equation that takes in x, y, and z parameters I feed in the character x and y position and the current time for z, and you get back a noise value that you can assign to the characters z depth. Playing with the scale of the parameters you pass in to the noise equation you can get back all sorts of interesting patterns.<br />
<br />
Perlin noise is also used heavily in the coconut grid. The coconuts rotation is dictated by Perlin noise generated from the coconut position and current time.<br />
<br />
The flickering of the torches and the swaying of the palm trees are also controlled by Perlin noise. I really can't believe how useful this stuff is. Thank you Ken Perlin!Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-3564227048631403922.post-80711522046806614002012-03-03T14:19:00.000-08:002012-03-13T09:12:08.959-07:00Making an iPhone game, tools usedI'm nearly done making Balls Abound, and it's been more work than I really ever could have imagined. I made a lot of work for myself though, starting the game from a prototype with no game aspect being my biggest mistake. But, I digress.<br />
<br />
<span style="color: #660000; font-size: large;">Blender</span><br />
<br />
The first tool I jumped on when prototyping Balls Abound, was <a href="http://www.blender.org/">Blender</a>, a terrific free 3D modeling tool. I had the idea of putting somewhat realistic rolling balls on the screen, so I needed to create some 3D balls. Blender made this possible, though it has a steep learning curve, and every time I go back to it, after even the briefest absence, I am back to the web trying to figure out what I'm doing. <br />
<br />
To create the rolling ball effect, I generated 64 images from 64 angles around the circumference of the ball. Blender makes this pretty easy (ignoring the barrier to entry in learning Blenders keyboard shortcuts). Just build a model of a sphere, add some texture, then set the camera to change angle to rotate around the ball. I chose 64 frames, meaning 64 / 360 is the angle between frames, so the ball roll can run at 60fps if needed. Some balls repeat after half a rotation, for these you can drop to 32 display frames. I then wrote a bunch of code, in a CCSprite subclass, that keeps track of how far the ball has moved (via setPosition) and updates its displayed frame and rotation angle. I think the illusion works well.<br />
<br />
<span style="color: #660000; font-size: large;">Gimp</span><br />
<br />
I used <a href="http://www.gimp.org/">Gimp</a> to create a number of images and effects in Balls Abound. Most notably the signs were created in Gimp, and the waves in ocean and island reflections were made in Gimp. The water effects were created by using the waves and ripples distort filters and varying the parameters based on the number of images being altered. You end up with a series of images that can be played in a loop, with the wave making one full cycle across your animation sequence.<br />
<br />
<span style="color: #660000; font-size: large;">Inkscape</span><br />
<br />
I used <a href="http://inkscape.org/">Inkscape</a> to put together a number of images, specifically those that make up the crab and pelican animations. Using Inkscape I drew and laid out a number of keyframes for each of the pelican and crab. I then used Interpolate extension (under Generate from path in the extensions menu). You can specify how many steps and an exponent (to simulate easing in or out). Inkscape will then generate a number of objects that transform between your selected key frames. This is labour intensive and I spent a good deal of time generating the pelican and crab animations, but I believe they were worth the effort.<br />
<br />
<span style="color: #660000; font-size: large;">Texture Packer</span><br />
<br />
With all the images generated with the tools listed above I needed something to create texture maps that I could use in cocos2d. I stumbled on <a href="http://www.texturepacker.com/">Texture Packer</a> and bought it. Point it at all of your images and it gives you a sprite sheet with plist file that can be read directly into cocos2d.<br />
<br />
<span style="color: #660000; font-size: large;">PVRTexTool</span><br />
<br />
I use <a href="http://www.imgtec.com/powervr/insider/powervr-pvrtextool.asp">PVRTexTool</a> for generating mipmaps and changing encoding of the texture maps I generate with Texture Packer. Very handy.<br />
<br />
<span style="color: #660000; font-size: large;">Audacity & Garage Band</span><br />
<br />
I really like <a href="http://audacity.sourceforge.net/">Audacity</a>. Generating sound effects for the game has been a fun challenge. I once watched a short documentary on foley artists in the movies, I tried to copy the what I learned from that brief exposure. I took many balls (ping pong, tennis, basket, etc.) and recorded them bouncing with my iPod. I took blocks of wood and clicked them and slid them. I found rarely did I record anything that didn't require a fair bit of post in order to make it work. Using Audacity you can alter speed, tempo, pitch and all sorts of other properties of your recorded samples. You can make clips that are loopable and isolate the exact bit of sound you require. Garage Band is a great source for samples, and if you are musically inclined you can do that too.<br />
<br />
<span style="color: #660000; font-size: large;">cocos2d</span><br />
<br />
I am using <a href="http://www.cocos2d-iphone.org/">cocos2d</a> to provide me easy access to their sprite model and action framework, among other things. cocos2d has been just a great library to work with, though I have made some modifications and some uglier hacks to accomplish what I wanted with Balls Abound. <br />
<br />
<span style="color: #660000; font-size: large;">XCode</span><br />
<br />
I quite like XCode for the most part. It's autocomplete drives me nuts, and the inability to refactor anything because Box2D is in use (and in C++) is annoying. But otherwise I think XCode is pretty damn good. I really like objective c now that I've wrapped my head around the whole messaging vs method invocation thing, it's a pretty nice language to develop in (if not a little verbose in areas, which could be helped out with a more useful autocomplete function in XCode). All the code I've written in XCode and all my builds are done there too. Sources are checked in locally to a git repository, and backed up offsite. Enough said.<br />
<br />
<span style="color: #660000; font-size: large;">iTunes Connect & Provisioning Portal</span><br />
<br />
Not much to say about these, you have to get online and manage a whole whack of stuff. Advertising setup, in app purchases, and game center leaderboards and achievements are all setup here. And then you have to write a bunch of code to work with it. This is also where you submit your app eventually, hopefully soon...<br />
<br />Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-3564227048631403922.post-50292748409095468322012-03-02T08:00:00.000-08:002012-03-13T09:12:18.171-07:00UIAccelerometer input causing UITouch lagSomething I noticed a while back, and finally decided to address, was touch latency issues in Balls Abound. The game uses the accelerometer for the main source of user input, and requires updates at 60fps. Using UIAccelerometer to dispatch the accelerometer updates at 60fps it turns out can induce problems with touch input performance (and possibly issues when debugging and resume after some pause in execution). In Balls Abound, rubbing your finger on the screen for a few seconds would degrade touch input performance from 60fps down to 1-2fps. The effect would clear up if you left the screen alone for a few seconds.<br />
<br />
Unhappy with this I went searching for the cause on Google. I found anecdotal evidence that the root cause was the use of UIAccelerometer for receiving accelerometer updates, and that moving to CMMotionManager would resolve the issue.<br />
<br />
So I tore into cocos2d and refactored the CCLayer to use CMMotionManager to track raw accelerometer data instead of using UIAccelerometer. Turns out, it's much better, consistent 60fps on all inputs no matter how much I rub the screen. To be completely honest, I removed accelerometer bindings from CCLayer altogether and put together a CMMotionDispatcher analogous to the CMTouchDispatcher. This gave me the flexibility to listen for accelerometer data on objects other than the layers. Eventually I would like to improve the game by taking advantage of the rotation rate data that can be read on devices with gyros using the motion manager, but that will have to wait.Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-3564227048631403922.post-20698597560930237412012-03-01T13:17:00.000-08:002014-02-20T17:28:43.231-08:00Smoothing accelerometer input<span style="font-family: inherit;">My upcoming game, Balls Abound, makes heavy use of the accelerometer for user input. The accelerometer readings on iOS devices are quite sensitive, and left untreated produce very jittery game play (especially when you are using them to drive the position of your scenes camera).</span><br />
<span style="font-family: inherit;"><br /></span>
<span style="font-family: inherit;">To make the data usable in my game, I decided to apply a rolling average to act as a sort of low pass filter, and smooth out what was happening on screen. I picked a filter size of 8 samples, tried a couple of either sizes, but they became too "loose" or too "tight", 8 worked well. From here, I just take the average of the last 8 samples, and replace the raw accelerometer data with the averaged data. Here's the code:</span><br />
<br />
<br />
<div class="p1">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"><span class="s1"> //N</span>OTE: z is not being properly handled, as I don't need it</span></div>
<div class="p2">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"> CMAcceleration accel = accelerometerData.acceleration;</span></div>
<div class="p2">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"> <span class="s2">float</span> x = accel.x;</span></div>
<div class="p2">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"> <span class="s2">float</span> y = accel.y;</span></div>
<div class="p3">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"> </span></div>
<div class="p1">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"><span class="s1"> </span>// we have to average samples together for any filter size > 1</span></div>
<div class="p2">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"> <span class="s2">if</span> (_filterSize > <span class="s3">1</span>) {</span></div>
<div class="p1">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"><span class="s1"> </span>// the index into our samples array (this just goes 0->n, 0->n,...)</span></div>
<div class="p2">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"> <span class="s2">int</span> index = _numSamples++ % _filterSize;</span></div>
<div class="p1">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"><span class="s1"> </span>// get a handle to the sample</span></div>
<div class="p2">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"> CGPoint *point = &_samples[index];</span></div>
<div class="p1">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"><span class="s1"> </span>// adjust our accumulator (it holds the sum of all of our samples)</span></div>
<div class="p2">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"> _accumulator.x -= (point->x - x);</span></div>
<div class="p2">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"> _accumulator.y -= (point->y - y);</span></div>
<div class="p1">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"><span class="s1"> </span>// replace the data in our sample array</span></div>
<div class="p1">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"> // with the current accelerometer reading</span></div>
<div class="p2">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"> point->x = x;</span></div>
<div class="p2">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"> point->y = y;</span></div>
<div class="p3">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"> </span></div>
<div class="p1">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"><span class="s1"> </span>// calculate the averaged accelerometer reading,</span></div>
<div class="p1">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"> // we report this to our delegates</span></div>
<div class="p2">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"> x = _accumulator.x / MIN(_numSamples, _filterSize);</span></div>
<div class="p2">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"> y = _accumulator.y / MIN(_numSamples, _filterSize);</span></div>
<div class="p1">
<span style="color: #999999;"><span style="font-family: 'Courier New', Courier, monospace;"> }</span><span style="font-family: 'Courier New', Courier, monospace;"> </span></span></div>
<div class="p1">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"><br /></span></div>
<div class="p3">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"><span class="s3"> </span>// if the idle timer is NOT disabled, then we want to poke the bear</span></div>
<div class="p3">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"><span class="s3"> </span>// every 30s (60s is the minimum settable screen lock, half that seem to </span></div>
<div class="p3">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"><span class="s3"> </span>// work) as long as there is accelerometer input, this way</span></div>
<div class="p3">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"><span class="s3"> </span>// the screen can go dim if the device is put down, but stays lit</span></div>
<div class="p3">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"><span class="s3"> </span>// if the device is being moved but not touched</span></div>
<div class="p1">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"> <span class="s1">if</span> (!_idleTimerDisabled && _count++ > <span class="s2">10</span> * <span class="s2">60</span> <span class="s4">/* assume 60fps */</span>) {</span></div>
<div class="p3">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"><span class="s3"> </span>// any change in x or y magnitude >= 1% </span></div>
<div class="p3">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"> // (remember accel x and y are in the range -1 to 1)</span></div>
<div class="p1">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"> <span class="s1">BOOL</span> changed = fabsf(_lastAccel.x - x) >= <span class="s2">0.01</span>;</span></div>
<div class="p1">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"> <span class="s1">if</span> (!changed) {</span></div>
<div class="p1">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"> changed = fabsf(_lastAccel.y - y) >= <span class="s2">0.01</span>;</span></div>
<div class="p1">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"> }</span></div>
<div class="p2">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"> </span></div>
<div class="p1">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"> <span class="s1">if</span> (changed) {</span></div>
<div class="p1">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"> UIApplication *app = [UIApplication sharedApplication];</span></div>
<div class="p1">
<span style="color: #999999;"><span style="font-family: 'Courier New', Courier, monospace;"> </span><span style="font-family: 'Courier New', Courier, monospace;">app.idleTimerDisabled = </span><span class="s1" style="font-family: 'Courier New', Courier, monospace;">YES</span><span style="font-family: 'Courier New', Courier, monospace;">;</span></span></div>
<div class="p1">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"> app.idleTimerDisabled = <span class="s1">NO</span>;</span></div>
<div class="p1">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"> _count = <span class="s2">0</span>;</span></div>
<div class="p1">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"> }</span></div>
<div class="p1">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"> }</span></div>
<div class="p2">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"> </span></div>
<div class="p1">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"> _lastAccel.x = x;</span></div>
<div class="p1">
<span style="color: #999999; font-family: 'Courier New', Courier, monospace;"> _lastAccel.y = y;</span></div>
<div class="p1">
<span style="font-family: 'Courier New', Courier, monospace; font-size: xx-small;"><br /></span></div>
<div class="p1">
<span style="font-family: inherit;">On each call to drawScene, CCDirectorIOS tells the CCMotionDispatcher to send the _lastAccel value to it's delegates. </span></div>
<div class="p1">
<span style="font-family: inherit;"><br /></span></div>
<div class="p1">
<span style="font-family: inherit;">You may have also noticed some code mucking about with the idle timer. Balls Abound is driven by the accelerometer, so the screen is not touched often. But I didn't want to just disable the idle timer, if a user just put their device down with Balls Abound open then it would kill their battery, as the device would never sleep. So I devised a method of poking the idle timer whenever the accelerometer was actively reading <i>changes</i> in data. </span></div>
Unknownnoreply@blogger.com0