Cool Times has been updated to version 1.1 and the free version with ads has also been released.
New version includes
- Theme change instructions
- Initial warning about battery consumption
- Word Clock Themes
- Firework Theme
- Menu Tweaks and Theme labels
- Neon Clock now has a flicker effect
Paid version (without ads): http://itunes.apple.com/us/app/cool-times/id420853663?mt=8
Free version: http://itunes.apple.com/us/app/cool-times-free/id515867313?mt=8
Hello, I’ve just released my first iPhone application. Its called “Cool Times”. It is a universal binary so it works on the iphone/itouch & ipad devices.If you did purchase this application (currently 99c) and have some type of trouble with it PLEASE let me know. I’m also very interested in knowing suggestions for new themes or improvements to current ones. You can either submit an comment here, email me at dan@dansgizmos.com or write me on my facebook.
For those who have not purchased it and are interested you can visit the iTunes page HERE .
Developers – if you are curious how I may have done a particular function please let me know, i’ll look into writing a tutorial for it.
Thanks!
Lets start this final phase by looking back at what we have accomplished:
- Created a logo that displays for a couple seconds before going to the clock
- Added a digital time layer that allows us to see the normal military time
- Created some sprites to represent the lights of the clock
Our goals to complete the project are now left up to:
- Setting all lights to off for initialization
- Getting the current time and converting it to a string of binary characters
- Creating an method to update the lights to display the correct time
Turning Out The Lights
The idea when adding the lights was to simulate an “off” effect by setting the opacity to be pretty low. This is a function already built into Cocos2D and is the simplest way of making it appear that the light is off without it completely disappearing. So lets begin by creating a method of shutting out the lights for all of the columns.
Open up binaryClock.h and add this function
-(void) turnOffLights:(CCArray *)lightArray; |
Now, we want to set how “bright” the lights are when they are off and when they are on. So lets set up a constant to use through out the rest of the code so that if we wish, we can change it later without much hassle. Open up binaryClock.m and add these lines ABOVE the @implementation and AFTER the #imports.
#define LIGHTOFF_OPACITY 75 #define LIGHTON_OPACITY 255 |
Now lets add our turnOffLights method, preferably somewhere after the init method.
- (void) turnOffLights:(CCArray *)lights { CCSprite *node; CCARRAY_FOREACH(lights,node){ [node setOpacity:LIGHTOFF_OPACITY]; } |
The only thing new here is that we called the sprite’s setOpacity: method and set it to our preset constant. Now lets use this method on all of our columns. In the init: method add the following lines after the arrangeLights: call.
//Set the lights to be off initially [self turnOffLights:t_Hours]; [self turnOffLights:s_Hours]; [self turnOffLights:t_Minutes]; [self turnOffLights:s_Minutes]; [self turnOffLights:t_Seconds]; [self turnOffLights:s_Seconds]; |
Go ahead and compile the project, your lights should change and look like my screenshot below.
Getting The Current Time (Again)
Okay, so we have to get the current time again. We could have made the current time some type of global variable that can be accessed by all CCLayers but since this is just a simple clock we are just going to keep everything how it is and reimplement parts of what we did in the TimeLabel layer. The difference between this time and last time is that we are only using the digits of the current time for converting it to a binary format. Open up binaryClock.h and add the following variables to your binaryClock object.
NSDate *currentTime; NSDateFormatter *dateFormat; NSString *dateString; |
And like before we have to add code to our init: method. This time we do it in binaryClock.m.
//Initialize the date format dateFormat = [[NSDateFormatter alloc] init]; //Set the date format [dateFormat setDateFormat:@"HHmmss"]; //get the current time currentTime = [NSDate date]; //Turn the currentTime into a string with the chosen date format dateString = [dateFormat stringFromDate:currentTime]; |
You may notice that we are changing the way we set the date format. We no longer need the colon to separate the time fields. We are going to be using this date format simply for conversion purposes so it doesn’t need to be visibly clear.
Now that we allocated dateFormat we need to release it in the dealloc method.
[dateFormat release]; |
Not quite done yet. We have to do something to make the time update. We did this before in the TimeLabel layer by adding a scheduler. Well, turns out thats what needs to happen again. At the end of the init: method add the scheduler just like before.
And lets create the method…
-(void) tick: (ccTime) dt { currentTime = [NSDate date]; dateString = [dateFormat stringFromDate:currentTime]; } |
Again, all of this should be familiar. In this phase we are going to use tick: to update the lights. Think of tick: as the main loop for this layer. It will run and update everything we need.
Lastly, to make the scheduler run we must add it to the init method. After all of the addnodes, add the following line.
[self schedule: @selector(tick:) interval:0.5]; |
If you compile your project you won’t notice any changes to the application, we are just getting the current time in the background.
Animating the Lights
This is the last and biggest part of the binary clock. We are going to do a few things here..
- Set up a method that converts digits to binary strings
- Set up a method that updates each column with the appropriate light on/off sequence
- Update clock using our new method
The first step is to break apart each digit of the clock string. Then we convert it to a binary string and set our arrays to match it up. This sounds confusing so I created a visual demonstration below.
Now we have to do this for each column to make sure the correct lights representing the current time are on. So lets begin by creating a simple method of converting the digits to binary. Open up BinaryClock.h and add the following code after the turnLightsOff method.
-(NSString *) binaryConversion:(char) number; |
Now lets create the method in BinaryClock.m
-(NSString *) binaryConversion:(char)number { NSString *convBinary; switch (number) { case '0': convBinary = @"0000"; break; case '1': convBinary = @"0001"; break; case '2': convBinary = @"0010"; break; case '3': convBinary = @"0011"; break; case '4': convBinary = @"0100"; break; case '5': convBinary = @"0101"; break; case '6': convBinary = @"0110"; break; case '7': convBinary = @"0111"; break; case '8': convBinary = @"1000"; break; case '9': convBinary = @"1001"; break; default: convBinary = @"0000"; break; } return convBinary; } |
This is a very simple method. I am aware that this probably isn’t the best way of doing it; but for the sake of simplicity this is what I went with. There are recursive functions out there that do a much better job and don’t have a limitation of 9 digits. Due to the nature of the binary clock, all we really need is to convert digits up to 9 as we are counting per column. Additionally, I would also like to think that this method takes less processing than the recursive way.
The next thing we gotta do is create a method to turn the lights on or off based on the element position within the CCArray. Open up BinaryClock.h and add the following line.
-(void) updateLights:(CCArray *)lightArray withBinaryString:(NSString *)binaryString; |
Now open up BinaryClock.m and add the implementation of the method.
-(void) updateLights:(CCArray *)lightArray withBinaryString:(NSString *)binaryString; { CCSprite *node; NSInteger strPos; //Position within the binary string at which we start //If we have a column with less than 4 lights we need to start later //in the string strPos = [binaryString length] - [lightArray count]; //Loop through each element in the array //and set it to be on or off CCARRAY_FOREACH(lightArray,node){ if([binaryString characterAtIndex:strPos] == '1') [node setOpacity:LIGHTON_OPACITY]; else [node setOpacity:LIGHTOFF_OPACITY]; strPos++; } } |
This method is built to simply loop through each element of the array and the light to be either on or off. To do this we need the array we are looking at lightArray and the string of binary numbers binaryString. Lets take a look at a few new methods used.
- [NSString length] - Returns the total amount of characters within the string
- [CCArray count] – Returns the amount of elements within the CCArray
- [NSString characterAtIndex:] – Returns a single character at any position within the string
strPos represents the position within the binaryString that we are currently looking at. Due to the fact that not all columns (clock digits) have 4 lights, we have to start later in the string. To do that we simply subtract the length of binaryString by the amount of lights in lightArray using the new methods listed above.
In our for CCARRAY_FOREACH loop we do a character comparison and check to see if the current character being looked at in binaryString is a ’1′. If it is we turn the light on, if not we turn it off. Pretty simple right?
We now have a way of changing the light on and off. Its time to actually get these lights moving! In BinaryClock.m navigate to the tick: method and add the following code just before the end .
[self updateLights:s_Seconds withBinaryString:[self binaryConversion:[dateString characterAtIndex:5]]]; [self updateLights:t_Seconds withBinaryString:[self binaryConversion:[dateString characterAtIndex:4]]]; [self updateLights:s_Minutes withBinaryString:[self binaryConversion:[dateString characterAtIndex:3]]]; [self updateLights:t_Minutes withBinaryString:[self binaryConversion:[dateString characterAtIndex:2]]]; [self updateLights:s_Hours withBinaryString:[self binaryConversion:[dateString characterAtIndex:1]]]; [self updateLights:t_Hours withBinaryString:[self binaryConversion:[dateString characterAtIndex:0]]]; |
This launches the functions that do the animation! Everytime tick: is called it updates each row’s lights and sets it to be on or off. The updateLights:WithBinaryString accepts our arrays of lights and the binaryString that we get from calling [dateString characterAtIndex:] with the specified digit from our dateString.
Compile and watch your creation FINALLY come to life!!!
The Plan:
- Create a new Clock Layer.
- Create and position the binary clock sprites.
The Approach:
- Create a circle sprite with a transparent background to represent the LEDs for the binary clock.
- Set up CCArrays for each of the rows holding our circle sprites and their position.
- Develop an method that initializes the position of the sprites.
Binary Clock Layer
I feel that it would be beneficial to separate the “lights” code from our working time label code. It makes sense to do it this way so that we may manage each part individually and cleanly. If you have gotten this far, you should be familiar by now with creating a new file. Go ahead and create a new CCNode set under the Layers folder. I named mine binaryClock.m and binaryClock.h
We really don’t want a CCNode, we want a CCLayer so in binaryClock.h change the parent class to be CCLayer instead.
#import #import "cocos2d.h" @interface binaryClock : CCLayer { } @end |
Also, lets prepare our blank init method in binaryClock.m
#import "binaryClock.h" @implementation binaryClock // initialize your instance here -(id) init { if( (self=[super init])) { } return self; } @end |
Now that we created a skeleton of a layer, lets add it to our scene. If we don’t add it to our scene nothing will show up no matter what we put in the layer! Open up Clock.h and add the new layer.
#import "cocos2d.h" #import "TimeLabel.h" #import "binaryClock.h" @interface Clock : CCScene { TimeLabel *timeLayer; binaryClock *clockLayer; } @end |
All we did is add an #import to reference our binaryClock layer and add instance of it called clockLayer.
Now lets initialize our layer in Clock.m
#import "Clock.h" @implementation Clock // initialize your instance here - (id) init { if( (self=[super init])) { timeLayer = [[TimeLabel alloc] init]; [self addChild:timeLayer]; clockLayer = [[binaryClock alloc] init]; [self addChild:clockLayer]; } return self; } // on "dealloc" you need to release all your retained objects - (void) dealloc { [timeLayer release]; [clockLayer release]; // don't forget to call "super dealloc" [super dealloc]; } @end |
Absolutely nothing new here. We did the exact same thing to the clockLayer as we did to the timeLayer. We allocated resources for it and released it in our dealloc method. Now that we are all prepped up we can begin adding our lights!
The “LED” (light)
There are a couple ways to mimic the light effect of a real binary clock. One way would be to create 2 images, one representing when the light was on and one when its off (dimmed). The better way is to keep the sprites the same but to change the opacity instead. This is less resource expensive because we are not needing to allocate and dealloc sprites each time a light needs to change.
You might ask why I even mention the first method. The iPhone has a finite amount of resources and only a certain amount of battery life. The idea is to efficiently use/reuse the resources provided so that your application may run longer and more efficiently. The point being that you must keep your methods in mind the entire time you create your application and learn to think of less expensive alternatives if possible.
Feel free to make a light of your own. You will want to create a .png with a transparent background (or you can use a black background). Additionally, I created a sprite thats 50×50 pixels. I’m not going to go into detail on how to create your own but you will need something like illustrator or GIMP to make the image.
If you wish, you can use the attached image in your project.
Add the .png resource to your project’s resources folder and open up binaryClock.h. Make the changes to your header file to match the code below:
#import #import "cocos2d.h" @interface binaryClock : CCLayer { CCArray *t_Hours; //Tens of Hours CCArray *s_Hours; //Single Hours CCArray *t_Minutes; //Tens of Minutes CCArray *s_Minutes; //Single Minutes CCArray *t_Seconds; //Tens of Seconds CCArray *s_Seconds; //Single Seconds } @end |
Ok hold on a second, whats a CCArray? CCArray was added to Cocos2d v0.99.4. It is used to be a bit faster than NSMutableArray. Well, thats enough to sell me. There is no bounds checking or anything with this array so you have make sure you are asking for something that exists within the array or it will crash.
Take a quick look at how we plan to set this up. Each column represents a part of the military time digits.
CCArrays are set up like this:
- t_Hours -> Tens of Hours
- s_Hours -> Single Hours
- t_Minutes -> Tens of Minutes
- s_Minutes -> Single Minutes
- t_Seconds -> Tens of Seconds
- s_Seconds – > Single Seconds
Now as for the elements, they may look like they are upside down. You would normally think to have the bottom most element at 0 and then increment as you move up. The reason we are doing it this way is to make converting digits to binary easier. We will get to this later. For now the concept is that we have an array for each column that represents each digit of the clock.
Open up BinaryClock.m and add the following code..
#import "binaryClock.h" @implementation binaryClock // initialize your instance here -(id) init { if( (self=[super init])) { //Set the arrays with the amount of elements (lights) that it contains t_Hours = [CCArray arrayWithCapacity:2]; s_Hours = [CCArray arrayWithCapacity:4]; t_Minutes = [CCArray arrayWithCapacity:3]; s_Minutes = [CCArray arrayWithCapacity:4]; t_Seconds = [CCArray arrayWithCapacity:3]; s_Seconds = [CCArray arrayWithCapacity:4]; //Create CCSprite objects for each of the lights in the arrays for (int i = 0; i < t_Hours.capacity; i++) [t_Hours addObject: [CCSprite spriteWithFile:@"red.png"]]; for (i = 0; i < s_Hours.capacity; i++) [s_Hours addObject: [CCSprite spriteWithFile:@"red.png"]]; for (int i = 0; i < t_Minutes.capacity; i++) [t_Minutes addObject: [CCSprite spriteWithFile:@"red.png"]]; for (int i = 0; i < s_Minutes.capacity; i++) [s_Minutes addObject: [CCSprite spriteWithFile:@"red.png"]]; for (int i = 0; i < t_Seconds.capacity; i++) [t_Seconds addObject: [CCSprite spriteWithFile:@"red.png"]]; for (int i = 0; i < s_Seconds.capacity; i++) [s_Seconds addObject: [CCSprite spriteWithFile:@"red.png"]]; //Retain the objects [t_Hours retain]; [s_Hours retain]; [t_Minutes retain]; [s_Minutes retain]; [t_Seconds retain]; [s_Seconds retain]; //Add all the sprites to the layer CCSprite *node; CCARRAY_FOREACH(t_Hours,node){ [self addChild:node]; } CCARRAY_FOREACH(s_Hours,node){ [self addChild:node]; } CCARRAY_FOREACH(t_Minutes,node){ [self addChild:node]; } CCARRAY_FOREACH(s_Minutes,node){ [self addChild:node]; } CCARRAY_FOREACH(t_Seconds,node){ [self addChild:node]; } CCARRAY_FOREACH(s_Seconds,node){ [self addChild:node]; } } return self; } - (void) dealloc { [t_Hours release]; [s_Hours release]; [t_Minutes release]; [s_Minutes release]; [t_Seconds release]; [s_Seconds release]; // don't forget to call "super dealloc" [super dealloc]; } @end |
There is a TON of new stuff here, so lets break down what we are trying to accomplish. Lets first look at the new CCArray stuff.
- initWithCapacity: – This is our initialization method that allows us to specify the amount of elements in our array. Notice that the capacity for each light column is consistent with the amount for each row in the binary clock image.
- addObject: - Adds an object at a specified index.
- capacity - Integer value specifying the total amount of objects in the array.
- CCARRAY_FOREACH(array,CCNode) – A macro to easily loop through all of the objects in the array. To use this you must specify what kind of object is in the array.
Take a second to look at what we are doing. Most of the code is just repeated for each array so i’m going to explain one section at a time.
t_Hours = [CCArray arrayWithCapacity:2]; |
We are initializing the t_Hours array with a capacity of 2. Like mentioned above, this number represents each column in the binary clock. In this example we are using the tens of hours at which there are only 2 lights.
for (int i = 0; i < t_Hours.capacity; i++) [t_Hours addObject: [CCSprite spriteWithFile:@"red.png"]]; |
This is a standard for loop that iterates through each of the lights. It uses array.capacity to figure out how many elements are in the array. It then adds a CCSprite object with our .png file. That part should be old news.
[t_Hours retain]; |
We are going to be using these arrays outside of the init method so we need to make sure that they don’t get dumped. To do that we use the retain command. Since we retain an object, we have to make sure to release it which is why we have to release it in the dealloc method.
CCSprite *node; CCARRAY_FOREACH(t_Hours,node){ [self addChild:node]; } |
This macro is pretty much the same thing we did above with less typing. The thing about it is we have to specify what type of object we have in the array. We then add each array element (light) to the layer so that it is displayed on the screen.
Go ahead and compile. If all goes well you should have overlapping lights in the bottom left corner of the screen. It should look like the image below.

Light Positioning
We need to position the lights on the screen so they mimic the binary clock as shown earlier in this phase. We are going to do this through adding a new method to the BinaryClock layer. Open up BinaryClock.h and add the following line before @end.
-(void) arrangeLights; |
Now open up BinaryClock.m and create the implementation. Add the following method anywhere before the @end in the file.
-(void) arrangeLights { int startX = 75; //Left most position on the screen int startY = 84; int lightOffset = 65; //Distance between each light //Tens of hours [[t_Hours objectAtIndex:0] setPosition:ccp(startX,startY + lightOffset)]; [[t_Hours objectAtIndex:1] setPosition:ccp(startX,startY)]; //Hours [[s_Hours objectAtIndex:0] setPosition:ccp(startX + lightOffset,startY + (lightOffset * 3))]; [[s_Hours objectAtIndex:1] setPosition:ccp(startX + lightOffset,startY + (lightOffset * 2))]; [[s_Hours objectAtIndex:2] setPosition:ccp(startX + lightOffset,startY + lightOffset)]; [[s_Hours objectAtIndex:3] setPosition:ccp(startX + lightOffset,startY)]; //Tens of Minutes [[t_Minutes objectAtIndex:0] setPosition:ccp(startX + (lightOffset * 2),startY + (lightOffset * 2))]; [[t_Minutes objectAtIndex:1] setPosition:ccp(startX + (lightOffset * 2),startY + lightOffset)]; [[t_Minutes objectAtIndex:2] setPosition:ccp(startX + (lightOffset * 2),startY)]; //Minutes [[s_Minutes objectAtIndex:0] setPosition:ccp(startX + (lightOffset * 3),startY + (lightOffset * 3))]; [[s_Minutes objectAtIndex:1] setPosition:ccp(startX + (lightOffset * 3),startY + (lightOffset * 2))]; [[s_Minutes objectAtIndex:2] setPosition:ccp(startX + (lightOffset * 3),startY + lightOffset)]; [[s_Minutes objectAtIndex:3] setPosition:ccp(startX + (lightOffset * 3),startY)]; //Tens of Seconds [[t_Seconds objectAtIndex:0] setPosition:ccp(startX + (lightOffset * 4),startY + (lightOffset * 2))]; [[t_Seconds objectAtIndex:1] setPosition:ccp(startX + (lightOffset * 4),startY + lightOffset)]; [[t_Seconds objectAtIndex:2] setPosition:ccp(startX + (lightOffset * 4),startY)]; //Seconds [[s_Seconds objectAtIndex:0] setPosition:ccp(startX + (lightOffset * 5),startY + (lightOffset * 3))]; [[s_Seconds objectAtIndex:1] setPosition:ccp(startX + (lightOffset * 5),startY + (lightOffset * 2))]; [[s_Seconds objectAtIndex:2] setPosition:ccp(startX + (lightOffset * 5),startY + lightOffset)]; [[s_Seconds objectAtIndex:3] setPosition:ccp(startX + (lightOffset * 5),startY)]; } |
Setting up the position of the lights should be done during initialization. In the init method we need to add the following line just before our CCARRAY_FOREACH loops.
//Move the lights to the correct position [self arrangeLights]; |
I have already played with the settings to get the lights sort of centered in the screen. You can see each of the lights were separated by the variable lightOffset. This number is what I came up with and felt looked the best. Additionally, the startX and startY variables represent the bottom left of the clock. In other words, its the bottom light in the tens of hours column. Feel free to play with these variables and see what happens. You should notice that it moves all the lights at the same time.
We use the setPosition and ccp methods to indicate the position of each light. You should be familiar with these methods already from previous phases. Go ahead and compile, the result should look like the image below. We are now done with Phase 3! If something didn’t work right the zip is attached at the end of this post.
Binary Clock Phase 3
In Phase 2 we have quite a few problems to solve.
- Create a Time Label.
- Get the current military time.
- Update the time label with the current military time.
New Project Files
To be a little more organized, lets create a new group in xcode called ‘layers’. Control click the classes folder on the left panel and select Add->New Group. Name this new group ‘layers’.
In our new layers group we create the time label layer. Control click the layers group and select Add->New File. This will be a CCNode class which should be the default selection. Hit next. Name the file TimeLabel and make sure the “Also create .h file” checkbox is selected. Click finish to add the files to your project.
By default the TimeLabel.h file creates a CCNode subclass, we need to change this to be a CCLayer subclass instead. Just as a heads up for future projects, CCLayers are what can handle touch events.
Navigate to TimeLabel.h and change
@interface TimeLabel : CCNode |
To
@interface TimeLabel : CCLayer |
Now lets set up the Layers init method. Open TimeLabel.m. After @implementation add the following code.
// initialize your instance here -(id) init { if( (self=[super init])) { } return self; } // on "dealloc" you need to release all your retained objects - (void) dealloc { // don't forget to call "super dealloc" [super dealloc]; } @end |
Text in Cocos2D
We need to create text in Cocos2D in order to display the current time. This will require the text to be updated at least every second so we can have it mesh with the binary clock.
There are a couple ways to create text within Cocos2D. Lets look at the different methods.
- CCLabel - CCLabel is the easiest way to create a label, however it is expensive to create and update. It uses True Type Fonts to create the text on the screen.
- CCLabelAtlas – This is still in Cocos2D for compatibility, however it has been superseded by CCBitmapFontAtlas.
- CCBitmapFontAtlas – CCLabel is the fastest way to create and update text. This downside is that it requires a created bitmap file with the letters and locations of each letter which takes third party utilities to create.
Because we need to update the text frequently, the best type of font to use would be the CCBitmapFontAtlas. This tutorial does not go through how to create a bitmap font atlas, instead we are going to use one that has been included in the cocos2d folder.
Navigate to the Cocos2D folder and go to the Resources/Fonts directory. Drag and drop the bitmapFontTest.png and the bitmapFontTest.fnt files to your projects resource folder. Select the Copy items into destinations folder checkbox. Your resources folder should look like the image below.
Lets create a little bit of text to test this whole bitmapFontAtlas thing. First thing we need to do is declare a new bitmapFontAtlas in our header file. Open TimeLabel.h and add the following code:
CCBitmapFontAtlas *militaryTime; |
Now lets initialize it. Open TimeLabel.m and add the code below in your init method after the self.isTouchEnabled bit.
//Initialize the CCLabelAtlas and load the ascii bitmap militaryTime = [CCBitmapFontAtlas bitmapFontAtlasWithString:@"Testing Bitmap" fntFile:@"bitmapFontTest.fnt"]; [militaryTime retain]; //Get ScreenSize CGSize screenSize = [CCDirector sharedDirector].winSize; //Set initial position to the botton center of the screen militaryTime.position = ccp(screenSize.width/2,25); [self addChild: militaryTime]; |
Now in your dealloc method add
[militaryTime release]; |
Now we have some explaining to do. The first piece of code is bitmapFontAtlasWithString:fntFile:. This method allocates a new bitmapFontAtlas with the text stating “Testing Bitmap” using the font file bitmapFontTest.fnt that we added earlier to our projects resources folder.
The second line is retaining the object. I want to make sure that we are not creating a new instance of this variable each time we update it (when we have it update with the current military time) so I went ahead and made sure that we have control over when its memory is released. Each time you retain or alloc an object you must release it to prevent memory leaks so we added the release code to our dealloc method because we want to keep this object as long as the layer is running. The 3rd line should be familiar; we are asking the CCDirector for the screen size. The last line should also be a bit familiar, we are setting the position of our time label to the middle of the screen and 25 pixels up.
Lets go ahead and compile the project to make sure there are no errors.
If you had no errors and ran the application in the simulator you will notice that there still is no text! Remember, cocos2d is set up to have a current ‘Scene’ and each scene can have multiple layers. Since we just created a new layer we need to add it to our scene so that it will render on screen.
Navigate to Clock.h and make the following changes.
// #import #import "cocos2d.h" #import "TimeLabel.h" @interface Clock : CCScene { TimeLabel *timeLayer; } @end |
We first are adding a reference to our TimeLabel layer. Then we declare an instance of our layer and name it timeLayer
Navigate now to Clock.m. We never did create our init and dealloc methods for this. Add the following code after the @implementation Clock
// initialize your instance here - (id) init { if( (self=[super init])) { //allocate our Time Label Layer timeLayer = [[TimeLabel alloc] init]; [self addChild:timeLayer]; } return self; } // on "dealloc" you need to release all your retained objects - (void) dealloc { [timeLayer release]; // don't forget to call "super dealloc" [super dealloc]; } |
In our init method we are allocating memory for our timeLayer and then adding it to the Clock Scene. Again, if we allocate something we need also need to release it which we did in our dealloc method. Compile and you should see your new bitmap text.
Getting The Time
Now that our bitmapFontAtlas is working, we need to put the current time in it. Lets visit TimeLabel.h and add a few variables.
#import #import "cocos2d.h" @interface TimeLabel : CCLayer { CCBitmapFontAtlas *militaryTime; NSDate *currentTime; // Our Current Time NSDateFormatter *dateFormat; // The date format NSString *dateString; // String of current time in our format } @end |
These new variables are not cocos2D related but we need them to get our current time.
- NSDate – An time object that represents a specific moment in time. We will need to poll this often to get the “current” time.
- NSDateFormatter – Used to set the way we want to see our time. We are using military time so we want to have an HH:mm:ss format where HH is a 24 hour format.
- NSString – Used to hold a string format for our current time that we will put in the BitmapFontAtlas.
Now lets work with these new variables. Open up TimeLabel.m and navigate to the init method.
// initialize your instance here -(id) init { if( (self=[super init])) { // enable touches self.isTouchEnabled = YES; //Initialize the date format dateFormat = [[NSDateFormatter alloc] init]; [dateFormat setDateFormat:@"HH:mm:ss"]; currentTime = [NSDate date]; dateString = [dateFormat stringFromDate:currentTime]; //Initialize the CCLabelAtlas and load the ascii bitmap militaryTime = [CCBitmapFontAtlas bitmapFontAtlasWithString:dateString fntFile:@"bitmapFontTest.fnt"]; [militaryTime retain]; //Get ScreenSize CGSize screenSize = [CCDirector sharedDirector].winSize; //Set initial position to the botton center of the screen militaryTime.position = ccp(screenSize.width/2,25); [self addChild: militaryTime]; } return self; } |
Initially we allocate some memory for the NSDateFormatter object. Next we use the setDateFormat: method to set our our 24 hour (military time) by putting in the string “HH:mm:ss”. After we set up our desired format we use [NSDate date] to get our current time. Lastly we use stringFromDate: and return a nicely formatted string to dateString. This we can now use with our bitmapFontAtlas. After bitmapFontAtlasWithString: replace @”Testing Bitmap” with dateString.
Again, anytime we alloc anything we need to tell the compiler when to release it.
// on "dealloc" you need to release all your retained objects - (void) dealloc { [militaryTime release]; [dateFormat release]; // don't forget to call "super dealloc" [super dealloc]; } |
Go ahead and compile the project again. You should see the current time but its static. In the next section we will make it update!
Updating The Time
Remember that scheduler we used in the Splash scene? Well, we are going to set up another one. This time we need to set it up to run every .5 seconds. This can probably be tweaked a little, but I wanted to make sure that there was enough time to update and display the current time on the screen before the next second arrived.
At the bottom of our init method in TimeLabel.m add the following scheduler.
[self schedule: @selector(tick:) interval:0.5]; |
This creates a scheduler that runs the tick: method every .5 seconds. Now lets create the tick: method…
-(void) tick: (ccTime) dt { currentTime = [NSDate date]; dateString = [dateFormat stringFromDate:currentTime]; [militaryTime setString:dateString]; } |
Compile the project again and hopefully you will see results like below. If all is well, move on to Phase 3!
If you are following the tutorial and trying to replicate this yourself, please take some time to learn how to install the Cocos2D template and start a project. You can find a great tutorial here.
It is also worth checking out the basic concepts page for Cocos2D here. It does a decent job getting you familiar with how they set up the framework.
Goals for this phase
- Install Cocos2D and create the BinaryClock Project.
- Create startup loading scene/layer.
- Create Temporary scene (main loop) with nothing going on.
Project Setup
Start Xcode and select “Create a new Project”. If you installed the templates correctly you should have the ability to select the Cocos2D project under user templates. We don’t use any collision detection or physics in this application so lets stick with the basic Cocos2D application.
New Project Dialog Box:
When the dialog box comes up, save the project as Binary Clock or something along those lines.
Save Project Dialog Box:
Lets go ahead and delete HelloWorldScene.h and HelloWorldScene.m from the project. We will create our own scenes and layers. After deleting the two files lets add our own. Ctrl click (or right click on the Classes folder and select Add->New File.. from the drop down menu. Save the file as Splash.m and make sure the Also create Splash.h checkbox is created.
New File Dialog Box:
New File Creation Box:
Scene Setup
Lets get to the code. Before we do anything we need to make sure our scene is loaded first. Go to Binary_ClockAppDelegate.m and import our Splash.h file and either comment out or remove the import code for HelloWorldScene.h (since we deleted the files).
#import "Splash.h" //#import "HelloWorldScene.h" |
Also, we want to load our scene first and not the HelloWorld scene (again, we deleted it) so make this change at the bottom of the applicationDidFinishLaunching method.
[[CCDirector sharedDirector] runWithScene: [Splash scene]]; |
Lets start working on our Splash.h file. Our Splash class is currently set to be a subclass of CCNode. Instead lets make it a subclass of CCLayer. If you read the Cocos2D basic concepts page you may be wondering why we are creating a CCLayer subclass and not a CCScene one. Well, we are within a Splash method called +(id) scene but our focus isn’t so much on the scene because we are not going to have anything but our splash screen load up at this time.
#import "cocos2d.h" @interface Splash : CCLayer { } +(id) scene; @end |
Open up Splash.m and lets implement our bare bone Scene, init and dealloc methods. Add the following code:
#import "Splash.h" @implementation Splash +(id) scene { // 'scene' is an autorelease object. CCScene *scene = [CCScene node]; // 'layer' is an autorelease object. Splash *layer = [Splash node]; // add layer as a child to scene [scene addChild: layer]; // return the scene return scene; } // initialize your instance here -(id) init { if( (self=[super init])) { } return self; } // on "dealloc" you need to release all your retained objects - (void) dealloc { // don't forget to call "super dealloc" [super dealloc]; } @end |
In scene: we create a scene object and a layer object (our Splash layer) to the scene and return it. nothing new here, the default Hello World project does the same exact thing.
Adding Our Logo
The clock is going to be in landscape mode. So we are working with dimensions of 480×320. By default, the iPhone loads “Default.png” in portrait mode even if you start out running in landscape. So the work around is to have a portrait version and a landscape version of the same logo. Create two .png files and name them Default.png and Title.png.
You will notice that the ones I have created below are EXACTLY the same picture besides one being in landscape and one being in portrait mode.
Title.png
Default.png
We need to add these to our project. Cocos2D has a default.png image already. Locate your resources folder in your xcode project and delete (or rename) the image as we are going to replace it. Then add your saved images to the project. Make sure to have the Copy items into destination group’s folder (if needed) box checked.
Copy Items Dialog Box:
After we add our images to the project we need to tell our program how to add them. Cocos2D has a really cool class called CCSprite that makes this extremely simple. Modify the init method in Splash.m to look match the code below.
// initialize your instance here -(id) init { if( (self=[super init])) { CGSize screenSize = [CCDirector sharedDirector].winSize; //Set up sprite CCSprite *introSprite = [CCSprite spriteWithFile:@"Title.png"]; introSprite.position = ccp(screenSize.width/2,screenSize.height/2); [self addChild: introSprite]; } return self; } |
The first thing you will notice is the that we are making a call to the CCDirector and asking what the window size is. We save this data into a CGSize variable called screenSize. We can now access the length and width of the screen.
CGSize screenSize = [CCDirector sharedDirector].winSize; |
Now for our Splash screen image. We are calling one of the CCSprite initializers called spriteWithFile: and telling it we need to load our Title.png file and save a pointer to it called introSprite. Next we position the sprite. An important thing to remember is that in Cocos2D the position is set by the center of the image. We set the image to be in the center of the entire screen by dividing the screensize width and height by 2. We then create a create a CGPoint variable using the ccp(x,y) method and set our introSprites position to it. Lastly, we call addChild to add our introSprite to our CCLayer.
CCSprite *introSprite = [CCSprite spriteWithFile:@"Title.png"]; introSprite.position = ccp(screenSize.width/2,screenSize.height/2); [self addChild: introSprite]; |
We can now safely compile the project. All we should see is our logo just sit there until we quit the application. So we are almost done with Phase 1!
Scene Transition
What we have so far is a pretty boring application. We only want to show our logo for a second or two and then transition to clock. We are not going to setup the Clock in phase 1 but what we ARE going to do is set up a Cocos2D scene that this clock with reside in. This will just be an empty scene to test the transition between our logo and the meat of the application.
If you read the beginning of the tutorial you should be familiar with adding a new .m and .h file. Lets go ahead and create a set called Clock.h and Clock.m.
In Clock.h we need to change it so it is a subclass of CCScene instead of CCNode. Additionally, we no longer need the scene method so its okay to go ahead and remove that.
Note: you really don’t have to change it to a CCScene if you don’t want to because CCScene is really the same thing as CCNode but with a different name. I like to change it just to make it more clear what the class is being used for.
// // Splash.h // Binary Clock // // Created by Daniel on 7/30/10. // Copyright 2010 N/A. All rights reserved. // #import #import "cocos2d.h" @interface Splash : CCScene { } @end |
We really don’t need to add anything to Clock.m at the moment, so leave that as it is.
Lets go back to Splash.m. First we need to add a reference to our new class since we are transitioning to it. Add this line:
#import "Clock.h" |
Now finally we need to create a way to go to our new scene after 2 seconds. In Cocos2D there are things called schedulers. They allow us to call any method we create every X amount of time. Go to the init method within Splash.m. After the addChild introSprite code add this line.
//Show our splash screen for 2 seconds before moving to clock [self schedule: @selector(tick:) interval:2.0]; |
This is telling Cocos2D that we are want to run the method called tick: at an interval of 2.0 seconds. Now we create the tick: method. After the init method add this code:
-(void) tick: (ccTime) dt { //Switch to the clock scene [[CCDirector sharedDirector] replaceScene: [Clock node]]; } |
Really not much to this method at all. (ccTime) dt is just the second interval we passed when creating the schedule.
The only code in this method is making a call to the CCDirector and asking it to replace the current scene with the Clock one. replaceScene removes our current scene from memory completely. [Clock node] just calls the default initializer for our scene. We are now done with Phase 1. Compile and make sure everything works okay. When ran in the simulator we should see our logo for 2 seconds and it should then disappear to a brand new blank scene.
Phase 1 Source Code
Below is an example of the effect that we are attempting to replicate on the iPhone. There are three sets of numbers, the hours, the minutes and the seconds. This clock is used to display military time only, although it could be a cool feature to have an AM/PM light and only 12 hour format.
Basically you add up what the lights represent together for the tens of hours, singles of hours, tens of minutes, etc. and thats what gives you the time. The image below demonstrates what each light represents. It seems like it would be easiest to treat each row individually and then gather the end result.
To accomplish this I’ve broke it down to several “Phases” so that creating this project wasn’t so overwhelming. These are the initial phases and some may be added later as needed.
Phase 1:
- Install Cocos2D and create the BinaryClock Project.
- Create startup loading screen.
- Create Temporary scene (main loop) with nothing going on.
Phase 2:
- Create a Time Label.
- Get the current military time.
- Update the time label with the current military time.
Phase 3:
- Create a new Clock Layer.
- Create and position the binary clock sprites.
Phase 4:
- Create a method of converting digits to a string of binary characters
- Create an method to update the lights to display the correct time
I love games. I’ve always wanted to make my own ever since I played.. Lately i’ve been kicking around the idea of putting one together for the iPhone.
I’m fairly new to the Mac OS/Xcode and Objective C in general. To realistically tackle this upward battle, creating something a bit more simple than a full fledged game seemed more feasible. I decided on creating the millionth version of a binary clock for the iPhone and writing how i’m doing it on this site.
I have been eyeing up Cocos2D for a while now as my framework. I decided that creating a binary clock would help get me more familiar with using this framework. Lets get started. Please continue on to the Overview post if you are interested in following along.




















