Cocos2d Sprite Sheets (blinking animation)

Posted: November 7th, 2010 | Author: | Filed under: Cocos2d, Objective-c, Tutorials | 1 Comment »

I struggled with this as a n00b in Cocos2d and I finally figured it out so I wanted to share it here in case someone else runs into the same challenge. You may or may not be aware of sprite sheets in traditional 2D games and in CSS where you combine a series of images into a single image, then use x/y offsets to expose the bits of the image you want. Cocos2d makes this process very easy but I wish it was documented better. I’m new to this so please let me know if you think there’s a better way to do it.

This example assumes you know your way around an XCode project and know the basics of Objective-C and Cocos2d. If not, there are plenty of good tutorials around. See my previous post for some useful links. Also, I’m using Cocos2d 0.99.5 so some of this syntax may not work on older versions.

Now, onto the tutorial:

The first thing you want to do is get yourself a sprite sheet and associated plist file. Cocos2d has a bunch of these in its examples folder but you can also make your own using the excellent Zwoptex app. The app will let you add a bunch of images and it will then re-arrange them, export a single image and also generate a plist file containing all the coordinates. This means you won’t have to mess around with x/y pixels to get everything lined up.

Important: make sure in the Zwoptex publish settings you select Cocos2d for the coordinates format.

Once you have added the two files to your project, you can load all the individual frames in the cache and use them however you like.

1
2
// load the plist file into the frame cache
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@"mysprite.plist"];

That’s it. Zwoptex will give each frame its own name. Look inside the plist file to see what they are called. These will most likely be the original file names of the images before you combined them into one.

Now that the frames are in the cache, we can create and display them as individual sprites like this

1
2
CCSprite *sprite = [CCSprite spriteWithSpriteFrameName:@"dude1.png"];
[self addChild: sprite];

You can add any of the other frames to the scene the same way, just make sure you use the correct frame name like dude2.png, dude3.png etc. Again, if you’re not sure what the names are, just open the plist and look at the frames node.

Now that we have all our frames ready to use, we can do some cool things with it like animate them or use them as rollover effects for menu buttons. In the game I’m currently developing I wanted the character to blink from time to time. So I used two sprite images, one with the character’s eyes open and one with its eyes shut. You can then create a sequence and animate them like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// Load sprite plist file
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@"dude_blinking.plist"];
 
// Create a blank array to store all the frames	
NSMutableArray *blinkAnimFrames = [NSMutableArray array];
 
// Add the frames 3 times. One with eyes open, one shut and one open again	
[blinkAnimFrames addObject: [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:[NSString stringWithFormat:@"dude_open.png"]]];
[blinkAnimFrames addObject: [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:[NSString stringWithFormat:@"dude_shut.png"]]];
[blinkAnimFrames addObject: [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:[NSString stringWithFormat:@"dude_open.png"]]];
 
// Create a 3-frame animation using the array and use 0.2 seconds delay between each frame	
CCAnimation *blinkanimation = [CCAnimation animationWithName:@"blinker" frames:blinkAnimFrames];
CCAnimate *blink = [CCAnimate actionWithDuration:0.2f animation:animation restoreOriginalFrame:NO];
 
// Now create the blinking action sequence	
CCAction *blinkAction = [CCRepeatForever actionWithAction:
    [CCSequence actions:
        [CCDelayTime actionWithDuration:2.0f],
        blink,
        [CCDelayTime actionWithDuration:3.0f],
        blink,
        [CCDelayTime actionWithDuration:0.2f],
        blink,
        [CCDelayTime actionWithDuration:2.0f],
    nil]
];
 
// Add the actual sprite (first frame from cache) to the scene
CCSprite *dude = [CCSprite spriteWithSpriteFrameName:@"dude_open.png"];
[self addChild: dude];
 
// Finally, run the blinking animation
[dude runAction: blinkAction];

If you study the blinkAction carefully, you’ll see we have just created an endless loop that runs the 3-frame blink action with some pauses in the middle. The sequence is:

(start loop)
1 – Pause for 2 seconds
2 – Blink
3 – Pause for 3 seconds
4 – Blink
5 – Pause for 0.2 seconds
6 – Blink
7 – Pause for 2 seconds
(end loop)

This gives us a semi-realistic blinking animation like this:

Have fun!

1 Comment »

Hello Cocos2d

Posted: November 1st, 2010 | Author: | Filed under: Cocos2d, Objective-c | No Comments »

I’ve spent the last few weeks testing out various 2D iPhone game engines and frameworks such as GameSalad and Corona. But I’ve decided to dedicate my time to learning Cocos2d instead. GameSalad uses an intuitive GUI where you can drag and drop sprites and write behaviours to quickly develop games. Similarly, Corona lets you create games using the simple Lua language. But they both cost money to publish to the app store (however, I think GameSalad have just launched a new plan where they take a cut from your sales).

Cocos2d on the other hand is open source and has a pretty big community of developers so it’s relatively easy to get help by searching or posting questions on its dev forums. It is a pretty mature and flexible framework that comes with a bunch of templates and libraries including Box2D and Chipmunk physics engines. It’s a proven framework since there are hundreds of games in the app store that were developed using Cocos2d. Trainyard, for example, was made entirely with Cocos2d and you probably know it’s one of the most popular games in the app store at the moment.

It is, however, all in Objective-C and in my opinion a pretty steep learning curve if you don’t know the language. There are alternatives as mentioned before but if you’re planning to seriously build games as well as apps for iOS you probably should try and learn Objective-C now.

The Cocos2d documentation isn’t fantastic so I’ve had to learn it slowly by looking at tutorials and code samples. Objective-C is an alien world to me so it’s like learning 2 languages at once. But once you get your head around it, it’s actually a lot of fun. I’m going to try and post a few tutorials on my blog from time to time but if you decide to sit down and learn Objective-C and Cocos2d, you may want to start here:

1. Objetive-C for Absolute Beginners (book)
2. The New Boston Objective-C tutorial screencasts
3. The New Boston Cocos2d tutorial screencasts
4. Ray Wenderlich’s Cocos2d game tutorial (more on his site)

No Comments »

Test-Driven Development – How Do I Start?

Posted: July 22nd, 2010 | Author: | Filed under: Python, Tutorials | 2 Comments »

There seem to be a lot of developers who like the idea of Test-Driven Development (TDD) and can clearly see the benefit of having tests written for their code but can’t seem to get their head around the process. How do you start writing unit tests before writing the actual code?

Let’s start with an example. You want to write a method that takes a URL as an argument and have it tell you through a boolean return if it’s the correct domain or not. It seems simple enough. Just write your method, pass the URL through some regular expression and you’re done.

But you yourself already know which domains are allowed and which are not so before writing the actual code you can run some tests in your head. E.g. I only want www.bbc.co.uk and its sub-domains on http and https. Nothing else should be allowed. So https://beta.bbc.co.uk/iplayer should return TRUE and http://www.bbcbb.com should return FALSE etc.

The process behind TDD is that you first write a failing test. Then you write the actual code and adjust until the test passes.

So let’s write some tests for our domain checker. I’m using Python and Unittest here:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import unittest
 
# this is what we're going to be testing
class Utils():
    def is_bbc(self, val):
        pass #placeholder
 
# this is the actual test
class TestUtils(unittest.TestCase):
    def setUp(self):
        self.u = Utils()
 
    def test_is_bbc(self):
        self.assertTrue(self.u.is_bbc('http://www.bbc.co.uk/iplayer'))
        self.assertTrue(self.u.is_bbc('http://www.bbc.co.uk/food'))
        self.assertTrue(self.u.is_bbc('http://www.bbc.co.uk'))
        self.assertTrue(self.u.is_bbc('https://www.bbc.co.uk'))
        self.assertTrue(self.u.is_bbc('http://beta.bbc.co.uk'))
        self.assertFalse(self.u.is_bbc('http://www.bbc.com'))
        self.assertFalse(self.u.is_bbc('http://www.bbbc.co.uk'))
        self.assertFalse(self.u.is_bbc('http://.bbc.co.uk'))
 
suite = unittest.TestLoader().loadTestsFromTestCase(TestUtils)
unittest.TextTestRunner(verbosity=2).run(suite)

I’ve created a an empty method where our domain checker is going to live but as you can see it doesn’t do anything. The tests should immediately make sense. We pass a bunch of domain variations and we know which ones should pass or fail. Naturally, running the test right now will fail:

$ python sample.py
test_is_bbc (__main__.TestUtils) ... FAIL
 
======================================================================
FAIL: test_is_bbc (__main__.TestUtils)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "sample.py", line 14, in test_is_bbc
    self.assertTrue(self.u.is_bbc('http://www.bbc.co.uk/iplayer'))
AssertionError
 
----------------------------------------------------------------------
Ran 1 test in 0.000s
 
FAILED (failures=1)

Now you can start writing the actual code and keep running the same tests until it passes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import unittest
import re
 
# this is what we're going to be testing
class Utils():
    def is_bbc(self, val):
        return re.match('^https?://([^/]+)?\.bbc\.co\.uk', val)
 
# this is the actual test
class TestUtils(unittest.TestCase):
    def setUp(self):
        self.u = Utils()
 
    def test_is_bbc(self):
        self.assertTrue(self.u.is_bbc('http://www.bbc.co.uk/iplayer'))
        self.assertTrue(self.u.is_bbc('http://www.bbc.co.uk/food'))
        self.assertTrue(self.u.is_bbc('http://www.bbc.co.uk'))
        self.assertTrue(self.u.is_bbc('https://www.bbc.co.uk'))
        self.assertTrue(self.u.is_bbc('http://beta.bbc.co.uk'))
        self.assertFalse(self.u.is_bbc('http://www.bbc.com'))
        self.assertFalse(self.u.is_bbc('http://www.bbbc.co.uk'))
        self.assertFalse(self.u.is_bbc('http://.bbc.co.uk')) #this should fail
 
suite = unittest.TestLoader().loadTestsFromTestCase(TestUtils)
unittest.TextTestRunner(verbosity=2).run(suite)

The regex may look like it’s correct but running the test will fail again:

$ python sample.py
test_is_bbc (__main__.TestUtils) ... FAIL
 
======================================================================
FAIL: test_is_bbc (__main__.TestUtils)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "sample.py", line 22, in test_is_bbc
    self.assertFalse(self.u.is_bbc('http://.bbc.co.uk'))
AssertionError
 
----------------------------------------------------------------------
Ran 1 test in 0.001s
 
FAILED (failures=1)

It has failed on the final assert because our code will also allow http://.bbc.co.uk and we obviously don’t want that. But as you can see we’ve caught this edge case before deploying our app so we can promptly fix our code.

Hopefully this example demonstrates why it’s a good idea to start with tests. This is obviously a simple example but on bigger projects predicting the outcome of your system can save you a lot of debugging time in the future.

2 Comments »

Saving data with Titanium mobile

Posted: July 15th, 2010 | Author: | Filed under: Mobile, Tutorials | No Comments »

In my previous post I talked about getting a simple table view up and running quickly using Titanium. A few people have asked me to continue the tutorial and explain how to save individual items and then display saved items in a separate window.

Saving data is quite simple. You first need to create the database and schema. You obviously only need to do this the first time the user opens your app. So in your app.js file you can do something like this:

1
2
var db = Titanium.Database.open('mydb');
db.execute('CREATE TABLE IF NOT EXISTS SAVEDITEMS  (NAME TEXT)');

This should look very familiar to you if you’re used to SQL. It first instantiates a database object then checks to see if the “SAVEDITEMS” table exists. If not, it will simply create it. If it does exist, it will just ignore that command. This will create a table with a single text field for storing the name of a selected item.

Now that we have our table set up, we can read and write to it whenever we like. If you wanted to save an item, you would do this:

1
db.execute("INSERT INTO SAVEDITEMS ( NAME ) VALUES ('my cool item')");

To get a list of all items in the database:

1
2
var rows = db.execute("SELECT * FROM SAVEDITEMS");
rows.close();

The last line is very important. You must close your query results to avoid memory leakage.

You can now loop through the saved items and stick them in a table view:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// create blank table view
var tableview = Titanium.UI.createTableView({});
Titanium.UI.currentWindow.add(tableview);
 
// create an empty array
var data = [];
 
// get a list of all saved items
var rows = db.execute('SELECT * FROM SAVEDITEMS');
 
// loop through all items
while (rows.isValidRow())
{
   // add each item to the data array and set the table row title
   data.push({title:rows.field(0),hasChild:true});
   rows.next();
}
 
// load db data into the table view
tableview.setData(data);
rows.close();

That should help you get started. You can look at how to use events to save items and also check to see which items have already been saved so you don’t show the save button etc. Take a look at the demo app I’ve created to see how I handled the saving logic. Specifically the details.js file.

No Comments »

Building a native mobile app with Titanium in less than an hour

Posted: July 13th, 2010 | Author: | Filed under: Mobile, Tutorials | 24 Comments »

UPDATE: 24 Nov 2010
Please note I no longer develop apps using Titanium and no longer follow version updates so the examples below may no longer work.



When I wrote my last blog entry, I favoured objective-c over Appcelerator’s Titanium framework thinking it just wasn’t good enough. Almost 6 months later and I would like to take back that comment. Titanium is no longer an embedded web browser inside an app. It uses Javascript to compile an entire iPhone/Xcode project with objective-c, so it uses native UI elements using clever APIs. This is also true for Android apps, iPad and now Blackberry.

This is not to say there’s no need to use Xcode ever again. But if you’re planning to build a general navigation-based app with loads of menus, windows and the ability to pull/push data from the web, it can be done much faster with Titanium. Plus you’ll be able to create the same app for multiple platforms without re-writing the code.

So I wanted to put together a relatively simple app that lets me browse and search through a list of items, in this example, animals; and be able to group them alphabetically like the iPhone’s contacts directory. Then I want to click on an item to see more details and be able to save my favourite items for easy access later.

I’ve created a sample app that you can download and use right now. All you have to do is add your own data and you’re done.

I’m going to quickly run you through how to set up the tabs, the list, alphabetical grouping, search and detail view for each item. I won’t explain the saving feature here but once you do the main part below you should be able to easily follow the rest by looking at the code.

Here’s how it works. See the sample app to work out where to place individual files.

1. Create a tab group. One for the main list and one tab for saved items:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// create tab group
var tabGroup = Titanium.UI.createTabGroup();
 
// create base UI tab and root window
var mainList = Titanium.UI.createWindow({
    title:'Animals',
    backgroundColor:'#ffffff',
    barColor:'#333',
    url:'mainList.js',
    tabBarHidden: false
});
var tabMainList = Titanium.UI.createTab({
    icon:'book.png',
    title:'Animals',
    window:mainList
});
 
// create saved items tab
var faves = Titanium.UI.createWindow({
    title:'Saved Items',
    backgroundColor:'#ffffff',
    barColor:'#333',
    url:'faves.js',
    tabBarHidden: false
});
var tabFaves = Titanium.UI.createTab({
    icon:'star.png',
    title:'Saved Items',
    window:faves
});
 
//  add tabs
tabGroup.addTab(tabMainList);
tabGroup.addTab(tabFaves);
tabGroup.open(tabMainList);
 
Titanium.UI.currentWindow.add(tabGroup);

That’s it. We have now created two tabs. mainList.js will take care of the content for the first tab and faves.js for the saved items tab.

2. Create a JSON doc (data.js) containing your data for the list:

1
2
3
4
5
6
7
var myData = [
    {"title":"Armadillo", "description":"Armadillos eat ants but they are not Anteaters"},
    {"title":"Bear", "description":"Bears are awesome and they like honey"},
    {"title":"Dog", "description":"Dogs are the best and they woof"},
    {"title":"Deer", "description":"D'oh! a deer..."},
    {"title":"Zebra", "description":"Zebras are stripey and cool"}
];

3. Set up your directory list in the first tab (mainList.js)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// include the data
Titanium.include('data.js');
 
var data = [];
var firstLetter;
var oldFirstLetter;
 
// loop through data and add to list
for (var i = 0; i < myData.length; i++)
{
	// get first letter of each item for grouping
	firstLetter = myData[i].title.substr(0,1);
	if(i == 0){
	    oldFirstLetter = firstLetter;
	    Ti.API.info(oldFirstLetter);
	} else {
	    if(firstLetter == oldFirstLetter){
    	        oldFirstLetter = null;
    	    } else {
    	        oldFirstLetter = firstLetter;
    	    }
        }
        data.push({title:myData[i].title,hasChild:true,header:oldFirstLetter});
        oldFirstLetter = firstLetter;
}
 
// add search bar
var search = Titanium.UI.createSearchBar({
	showCancel:false
});
 
// create table view
var tableview = Titanium.UI.createTableView({
    data:data,
    search:search,
    filterAttribute:'title'
});
 
Titanium.UI.currentWindow.add(tableview);
 
// create table view event listener
tableview.addEventListener('click', function(e)
{
    var win = Titanium.UI.createWindow({
		url:'details.js',
		backgroundColor:'#ffffff',
                barColor:'#333333',
		title:e.rowData.title
	});
 
	// send selected item's title to detail page
	win.currentItem = e.rowData.title;
 
	Titanium.UI.currentTab.open(win,{animated:true});
});

Notice in the tableview event listener we are telling it to just open a new window on click and pass the item’s title.

4. Display the selected item’s data in details.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// include the data
Titanium.include('data.js');
 
win = Titanium.UI.currentWindow;
 
var desc = '';
 
// create scroll view for the content
var scrollView = Titanium.UI.createScrollView({
	contentWidth:'auto',
	contentHeight:'auto',
	top:0,
	showVerticalScrollIndicator:true,
	showHorizontalScrollIndicator:false
});
 
 
// loop through data and get correct item by title (this is easier than using Id's incase you add new items later)
for (var i = 0; i < myData.length; i++){
    if(myData[i].title == win.currentItem){
        desc = myData[i].description;
    }
}
 
// create description label
var label = Titanium.UI.createLabel({
	text:desc,
	height:'auto',
	width:300,
	top:10,
	font:{fontSize:16},
	color:'#333333',
	textAlign:'left'
});

That’s it. I said an hour to develop this but in reality it will probably take you much less once you get used to the APIs. Of course for the data source you can use XHR to get data from the web but if you want your app to be usable without an internet connection, you would have to store the data locally like this.

I hope this was helpful. Let me know if you have any questions.

24 Comments »