miércoles, 4 de septiembre de 2013

Resources framework for "universal" apps

When I started programming for iOS, the projects I were involved in were all for iPad devices only (only a pack of image resources), but when I started coding I did it in such a way that with the same code it was valid for iPad and iPhone. It was a great idea because later on the iPad apps were turned into universal apps and the changes in the code were just a few details. The idea is the following:
  1. Initialize the scale of the background with respect to the current screen size and the resources dimensions and save this ratio.
  2. Scale the background and images with the previously calculated ratio. As the iPhone has not the same dimensions of the iPad, it uses scaleX and scaleY, and the images get a bit stretched.
  3. Never use points for positions, but use logic of dynamic positions with respect to the screen: top-left corner of the screen is (size.width*0, size.height*1) and the center-right point is (size.width*1, size.height*0.5).
One of the problems I found in my way was the memory constraint, the iPad 1 couldn't load big files of images. I had to change the resources target from iPad retina to iPad non-retina screen. Enabling retina display but without the -hd resources makes the graphics on iPad retina not blurred nor with pixels, so it was a good solution and saved a lot of mega bytes.

Here you have the framework and an example of the usage:
//
// ResourcesManagerStretched.h
//
// Created by Elena Vielva on 31/04/12.
// Copyright (c) 2012 Elena Vielva. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "cocos2d.h"
@interface ResourcesManagerStretched : NSObject {
float scaleX;
float scaleY;
CGSize size;
CCSpriteFrameCache *frameCache;
}
@property (readonly) float scaleX;
@property (readonly) float scaleY;
@property (readonly) CGSize size;
+ (ResourcesManagerStretched *) sharedResourcesManager;
- (CCSprite *) addBackground:(NSString*)bg To:(CCLayer*)layer;
- (CCSprite *) addSpriteWithName:(NSString*)name Position:(CGPoint)pos Layer:(int)z To:(CCNode*)layer Tag:(int) tag;
- (CCAnimate*) generateAnimationWithName:(NSString*)name NumberOfImages:(int)n Delay:(float) delay Repeticion:(BOOL) rep Restore:(BOOL)restore;
@end
//
// ResourcesManagerStretched.m
//
// Created by Elena Vielva on 31/04/12.
// Copyright (c) 2012 Elena Vielva. All rights reserved.
//
#import "ResourcesManagerStretched.h"
static ResourcesManagerStretched *shared;
@implementation ResourcesManagerStretched
@synthesize scaleX = scaleX;
@synthesize scaleY = scaleY;
@synthesize size = size;
+ (ResourcesManagerStretched *) sharedResourcesManager {
if (shared) {
return shared;
}
shared = [[ResourcesManagerStretched alloc] init];
return shared;
}
+ (id)alloc {
NSAssert(shared == nil, @"Attempted to allocate a second instance of a singleton.");
return [super alloc];
}
-(id) init {
self = [super init];
if (self) {
size = [[CCDirector sharedDirector] winSize];
frameCache = [CCSpriteFrameCache sharedSpriteFrameCache];
}
return self;
}
/** ADDBACKGROUND:TO
* Adds a background image to the scene. For this:
* - Gets the file from the name
* - Scales it
* - Places it in the center of the screen
* - Adds it to the z-height -1 (to make sure its in the background)
* Input: NSString -> name of the background image
* CCLayer -> parent layer to add the background to
* Output: CCSprite -> the sprite
*/
- (CCSprite *) addBackground:(NSString *)bg To:(CCLayer *)layer {
CCSprite *background = [CCSprite spriteWithFile:bg];
if ((scaleX==0) || (scaleY==0)) {
scaleX = size.width / background.contentSize.width;
scaleY = size.height / background.contentSize.height;
}
background.scaleX = scaleX;
background.scaleY = scaleY;
NSAssert(background!=nil,@"Error couldn't load background");
[background setPosition:ccp(size.width/2, size.height/2)];
[layer addChild:background z:-1];
return background;
}
/** ADDSPRITEWITHFRAME:POSITION:LAYER:TO:TAG
* Adds the sprite to the scene. For this:
* - Gets the frame from the name
* - Gets the sprite from the frame
* - Scales the sprite according with the current device
* - Places it in the given position
* - Adds it to the parent node
* Input: NSString -> name of the file
* CGPoint -> position where the sprite is placed
* int -> z-layer (height) where the sprite is placed
* CCNode -> parent node where the sprite is added to
* Int -> tag
* Output: CCSprite -> the sprite
*/
- (CCSprite *) addSpriteWithName:(NSString*)name Position:(CGPoint)pos Layer:(int)z To:(CCNode *)layer Tag:(int) tag{
CCSpriteFrame * frame = [frameCache spriteFrameByName:name];
NSAssert(frame!=nil,@"Frame es nil");
CCSprite *sprite = [CCSprite spriteWithSpriteFrame:frame];
if ((scaleX==0) || (scaleY==0)) {
scaleX = size.width / sprite.contentSize.width;
scaleY = size.height / sprite.contentSize.height;
}
if (sprite!=nil) {
sprite.scaleX = scaleX;
sprite.scaleY = scaleY;
[sprite setPosition:pos];
[layer addChild:sprite z:z tag:tag];
}
return sprite;
}
/** GENERATEANIMATIONWITHNAME:NUMBEROFIMAGES:DELAY:REPETITION:TO {:RESTORE}
* The name of the sprites range from name0001.png to name0023.png for example
* Generates a cartoon-style animation. For this:
* - For each image of the animation: "calculates its name" and adds it to an array
* - When all the images are added to the array, creates the animation
* - It the animation should be a loop, makes the animation repeat forever
* Input: NSString -> Base name of the animations (before 0001.png)
* int -> Number of images of the animation
* float -> Delay between the animation frames
* BOOL -> Whether the animation is a loop or not
* BOOL -> Whether the animation should restore to the first frame when finished or not
* Output: CCAnimate -> The animation
*/
- (CCAnimate*) generateAnimationWithName:(NSString*)name NumberOfImages:(int)n Delay:(float) delay Repeticion:(BOOL) rep Restore:(BOOL)restore {
NSMutableArray *animFrames = [NSMutableArray array];
for (int i=1; i<=n; i++) {
NSString * spriteName;
if (i<10) {
spriteName = [NSString stringWithFormat:@"%@000%d.png",name, i];
} else if (i<100) {
spriteName = [NSString stringWithFormat:@"%@00%d.png",name, i];
} else if (i<1000) {
spriteName = [NSString stringWithFormat:@"%@0%d.png",name, i];
} else if (i<10000) {
spriteName = [NSString stringWithFormat:@"%@%d.png",name, i];
}
CCSpriteFrame * frame = [frameCache spriteFrameByName: spriteName];
[animFrames addObject: frame];
}
CCAnimation *animation = [CCAnimation animationWithFrames:animFrames delay:delay];
CCAnimate *action = [CCAnimate actionWithAnimation:animation restoreOriginalFrame:restore];
if (rep) {
action = [CCRepeatForever actionWithAction:action];
}
return action;
}
@end
+(CCScene *) scene {
CCScene *scene = [CCScene node];
TestLayer *layer = [TestLayer node];
[scene addChild: layer];
return scene;
}
- (id) init {
self = [super init];
if (self) {
ResourcesManagerStretched *helper = [ResourcesManagerStretched sharedResourcesManager];
[helper addBackground:@"background.jpg" To:self];
size = helper.size;
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@"resources.plist"
textureFile:@"resources.pvr.ccz"];
sp1 = [helper addSpriteWithName:@"spName.png" Position:ccp(size.width*0.5,size.height*0.18) Layer:5 To:self];
sp2 = [helper addSpriteWithName:@"otherSprite.png" Position:ccp(size.width*0.1,size.height*0.1) Layer:5 To:self];
}
return self;
}
view raw TestLayer.mm hosted with ❤ by GitHub

When the iPhone 5 ready apps were mandatory for new submissions (and updates), it was also possible to keep stretching the backgrounds and sprites, but now our characters have got too much fat. I had to think a new way of using only a pack of images and taking advance of all screen sizes. Pen and paper and a calculator for operations, I draw something like this:


The small rectangle should have all the important information for the app (buttons, information, ...) but the background have to fill all the space, in order to take advance of the iPhone screens. The reference size is the dimension of this rectangle in the different devices: in the iPad is the same as its size, while in the iphone will fulfill that 4*iphoneHeight = 3*iphoneWidth. Thus, the left reference point is the (screen size - reference size) / 2 and the point most on the right would be refLeft+refWidht*1.0.

Here there is the new framework and an example of how to use it:
//
// ResourcesManager.h
//
// Created by Elena Vielva on 15/04/13.
// Copyright (c) 2012 Elena Vielva. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "cocos2d.h"
#import "Constants.h"
@interface ResourcesManager : NSObject {
float _scale;
CGSize _size;
CGSize _refSize;
CCSpriteFrameCache *frameCache;
}
@property (readonly) float scale;
@property (readonly) CGSize size;
@property (readonly) CGSize refSize;
+ (ResourcesManager *) sharedResourcesManager;
- (CCSprite *) addBackground:(NSString*)bg To:(CCLayer*)layer;
- (CCSprite *) addSpriteWithName:(NSString*)name Position:(CGPoint)pos Layer:(int)z To:(CCNode*)layer Tag:(int) tag;
- (CCAnimate*) generateAnimationWithName:(NSString*)name NumberOfImages:(int)n Delay:(float) delay Repeticion:(BOOL) rep Restore:(BOOL)restore;
@end
//
// ResourcesManager.m
//
// Created by Elena Vielva on 15/04/13.
// Copyright (c) 2012 Elena Vielva. All rights reserved.
//
#import "ResourcesManager.h"
static ResourcesManager *shared;
@implementation ResourcesManager
@synthesize scale = _scale;
@synthesize size = _size;
@synthesize refSize = _refSize;
+ (ResourcesManager *) sharedResourcesManager {
if (shared) {
return shared;
}
shared = [[ResourcesManager alloc] init];
return shared;
}
+ (id)alloc {
NSAssert(shared == nil, @"Attempted to allocate a second instance of a singleton.");
return [super alloc];
}
-(id) init {
self = [super init];
if (self) {
_size = [[CCDirector sharedDirector] winSize];
frameCache = [CCSpriteFrameCache sharedSpriteFrameCache];
[self initRefSizes];
}
return self;
}
/** INITREFSIZES
* Initializes the reference sizes (dimensions) for the current device
* Reference size is the dimensions of the screen in which will be the information and important elements of the scene
* - iPads have as reference size the iPad non-retina dimension
* - iPhone reference size: keep the height; the width is calculated from its height with the ratio of iPad screen dimensions
*/
- (void) initRefSizes {
CGSize pixelSize = [[CCDirector sharedDirector] winSizeInPixels];
if (pixelSize.width == 1024) {
// iPad non-retina
_refSize = CGSizeMake(1024, 768);
}else if (pixelSize.width == 2048) {
// iPad retina
_refSize = CGSizeMake(1024, 768);
}else if (pixelSize.width == 1136) {
// iPhone 5
_refSize = CGSizeMake(427, 320);
}else if (pixelSize.width == 960) {
// iPhone retina
_refSize = CGSizeMake(427, 320);
}else if (pixelSize.width == 480) {
// iPhone non-retina
_refSize = CGSizeMake(427, 320);
}
_scale = pixelSize.height/768;
}
/** ADDBACKGROUND:TO
* Adds a background image to the scene. For this:
* - Gets the file from the name
* - Scales it
* - Places it in the center of the screen
* - Adds it to the z-height -1 (to make sure its in the background)
* Input: NSString -> name of the background image
* CCLayer -> parent layer to add the background to
* Output: CCSprite -> the sprite
*/
- (CCSprite *) addBackground:(NSString *)bg To:(CCLayer *)layer {
CCSprite *background = [CCSprite spriteWithFile:bg];
background.scale = _scale;
NSAssert(background!=nil,@"Error al cargar el fondo");
[background setPosition:ccp(_size.width/2, _size.height/2)];
[layer addChild:background z:-1];
return background;
}
/** ADDSPRITEWITHFRAME:POSITION:LAYER:TO:TAG
* Adds the sprite to the scene. For this:
* - Gets the frame from the name
* - Gets the sprite from the frame
* - Scales the sprite according with the current device
* - Places it in the given position
* - Adds it to the parent node
* Input: NSString -> name of the file
* CGPoint -> position where the sprite is placed
* int -> z-layer (height) where the sprite is placed
* CCNode -> parent node where the sprite is added to
* Int -> tag
* Output: CCSprite -> the sprite
*/
- (CCSprite *) addSpriteWithName:(NSString*)name Position:(CGPoint)pos Layer:(int)z To:(CCNode *)layer Tag:(int) tag{
CCSpriteFrame * frame = [frameCache spriteFrameByName:name];
NSAssert(frame!=nil,@"Frame es nil");
CCSprite *sprite = [CCSprite spriteWithSpriteFrame:frame];
if (sprite!=nil) {
sprite.scale = _scale;
[sprite setPosition:pos];
[layer addChild:sprite z:z tag:tag];
}
return sprite;
}
/** GENERATEANIMATIONWITHNAME:NUMBEROFIMAGES:DELAY:REPETITION:TO {:RESTORE}
* The name of the sprites range from name0001.png to name0023.png for example
* Generates a cartoon-style animation. For this:
* - For each image of the animation: "calculates its name" and adds it to an array
* - When all the images are added to the array, creates the animation
* - It the animation should be a loop, makes the animation repeat forever
* Recibe: NSString -> Base name of the animations (before 0001.png)
* int -> Number of images of the animation
* float -> Delay between the animation frames
* BOOL -> Whether the animation is a loop or not
* BOOL -> Whether the animation should restore to the first frame when finished or not
* Devuelve: CCAnimate -> The animation
*/
- (CCAnimate*) generateAnimationWithName:(NSString*)name NumberOfImages:(int)n Delay:(float) delay Repeticion:(BOOL) rep Restore:(BOOL)restore {
NSMutableArray *animFrames = [NSMutableArray array];
for (int i=1; i<=n; i++) {
NSString * spriteName;
if (i<10) {
spriteName = [NSString stringWithFormat:@"%@000%d.png",name, i];
} else if (i<100) {
spriteName = [NSString stringWithFormat:@"%@00%d.png",name, i];
} else if (i<1000) {
spriteName = [NSString stringWithFormat:@"%@0%d.png",name, i];
} else if (i<10000) {
spriteName = [NSString stringWithFormat:@"%@%d.png",name, i];
}
CCSpriteFrame * frame = [frameCache spriteFrameByName: spriteName];
[animFrames addObject: frame];
}
CCAnimation *animation = [CCAnimation animationWithSpriteFrames:animFrames delay:delay];
CCAnimate *action = [CCAnimate actionWithAnimation:animation restoreOriginalFrame:restore];
if (rep) {
action = [CCRepeatForever actionWithAction:action];
}
return action;
}
- (void) dealloc {
NSLog(@"Deallocating %@",self);
[super dealloc];
}
@end
+ (CCScene *) scene{
CCScene *scene = [CCScene node];
SomeLayer *layer = [SomeLayer node];
[scene addChild:layer];
return scene;
}
- (id) init{
self = [super init];
if (self) {
// ask director for the window size
CGSize size = [[CCDirector sharedDirector] winSize];
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@"sceneResources.plist"];
ResourcesManager *helper = [ResourcesManager sharedResourcesManager];
float refWidth = helper.refSize.width;
float refLeft = size.width/2-refWidth/2;
[helper addBackground:@"sceneBackground.jpg" To:self];
sp1 = [helper addSpriteWithName:@"spriteName.png" Position:ccp(refLeft+refWidth*0.5, size.height*0.18) Layer:2 To:self];
sp2 = [helper addSpriteWithName:@"otherSprite.png" Position:ccp(refLeft+refWidth*0.1, size.height*0.1) Layer:2 To:self];
}
return self;
}
view raw SomeLayer.mm hosted with ❤ by GitHub

jueves, 6 de junio de 2013

Adding sound to your game with Cocos2D

If you are developing a game probably you need to add sound to your app.

As many other things, although Apple offers you a library to manage the audio, Cocos2D has it's own class: SimpleAudioEngine. It makes you really easy to insert background music and sound effects. It also allows you to modify basic properties such as pitch, pan and gain, and the possibility of looping a sound.

You can preload the sound (if you have a lot and heavy sounds). For loopings you need a CDSoundsource class:
soundEngine = [SimpleAudioEngine sharedEngine];
[soundEngine preloadEffect:@"sound1.mp3"];
[soundEngine preloadEffect:@"sound2.mp3"];
[soundEngine preloadBackgroundMusic:@"backgroundMusic.mp3"];
loopingSound = [[soundEngine soundSourceForFile:@"loopingSound.mp3"] retain];
loopingSound.looping = YES;
view raw gistfile1.m hosted with ❤ by GitHub
Playing effects and background is very easy:
[soundEngine playBackgroundMusic:@"backgroundMusic.mp3" loop:YES];
[soundEngine playEffect:@"sound1.mp3"];
view raw gistfile1.m hosted with ❤ by GitHub
Setting other properties is also easy:
[soundEngine setBackgroundMusicVolume:0.4];
[soundEngine playEffect:@"sound1.mp3" pitch:1 pan:0 gain:0.4];
[loopingSound setMute:YES];
view raw gistfile1.txt hosted with ❤ by GitHub
Playing around with looping sounds:
[loopingSound play];
[loopingSound pause];
[loopingSound rewind];
[loopingSound stop];
view raw gistfile1.m hosted with ❤ by GitHub
// Never forget to release the looping sound in the dealloc method
[loopingSound release];
view raw gistfile2.txt hosted with ❤ by GitHub

lunes, 6 de mayo de 2013

I thought I removed the file...

Sometimes, when you are sure that everything is alright because the app runs perfectly on all your devices, one of your testers report a bug saying that the app shuts down while loading in some scene (well, they just say something like "yeah.. I select all the items and then, it closes and I cannot continue"). You have very few information, so you start summarizing:
  1. It cannot be memory warning (always first guess, but you test with an iPad 1, the iPad 3 cannot raise this problem)
  2. It cannot be a weird behavior of the inner logic because it is just a background and some sprites...
  3. This scene had previously worked, and then I changed the background...
  4. If the background is not found?
  5. (After look in the project) But the background is not in the project... Why is it working in my devices if there is no image?!
It has happened to me to many times so I decided to write this post.
The first time I saw this issue was when refactoring my code and stop using retina resources, so it turned from having background-hd.jpg and background.jpg to just background.jpg. I removed the file from the project, clean the cache and made sure it wasn't the old file there anymore. But, somehow, XCode seemed to remember it, so when I asked for this file in my devices was ok but my testers iPad hadn't that resource and crashed.
I was very crossed, because I didn't want to rename all files to solved the problem. So, I asked the Almighty and found some solutions on Stackoverflow. The problem relied on XCode's derived cache, and there were offered some solutions:
  1. Clean via XCode (source)
    • The problem remained
  2. Going to the folder in your laptop and cleaning the data manually: ~/Library/Developer/Xcode/DerivedData/ (source)
    • It didn't worked for me, my corresponding folder was empty
  3. Delete app, clean everything, empty caches (source)
    • Still there
How did I solved the problem? I did not solve it :( I solved my concrete bug manually, by renaming the files and the path names in the code.
Why did I write this post? To show you potential problems you may have with XCode and the resources, and a non-perfect solution for it. Also, if you find a good way to get rid of this ridiculous issue that makes stupid names of our resources, feel free to share it with a comment.




viernes, 5 de abril de 2013

'Kamcording' your app

When you are a programmer, you get used to searching for new tools, fighting with poorly documented frameworks, and searching for solutions of problems that come with new staff. You also end up learning when to discard a tool. Tools may be bad, may not adjust to your problem, may give you more problems than solutions... or may be exactly what you are looking for... But what makes a tool great is the people behind it, a technical support that turns problems into solutions. And that's exactly how I would describe Kamcord.

The moment I saw this tool I knew it could be perfect for the new game we are designing, so I decided to make a project to test it. I had two problems: one with the rotation and the other was causing a crash with certain iOS version. Thus, I wrote to the supporting team. The answer was really fast and solved the orientation issue. They needed more information about my devices and project in order to solve the crashing problem so we exchanged several mails, projects, framework's updates... In just 5 days I had the whole project perfectly working. I didn't have to insist, but they were working on my problem till they got to the solution. And every mail was really polite. I'm very happy with the support I've received :)

Kamcord, this amazing tool, is a framework that allows your app's users to record the flow of the app and share it with friends. Easy and well documented.

Available for Cocos2d and Unity. Download from the web and follow the tutorial, it's well explained.
  1. Copy the Kamcord folder into your project.
  2. Add the framework and other supporting frameworks (twitter, mediaplayer, ... ).
  3. Adjust the settings for iOS < 4 (turn to optional some of the just added frameworks).
  4. Add to 'other linker flags': -ObjC -all_load -lxml2

At this point, it should compile, if not, check all the steps. At my first attempt it failed but I realized I  had skipped the third step.

On the other hand, Kamcord uses their own glview, therefore you have to do some changes in your AppDelegate.
  1. Make the root view controller extend from KCViewController
  2. Instantiate the glview as a KCGLView instead of an eaglview
  3. Set the root view controller to the window
  4. Configure Kamcord and set your developer key, secret and app name.
#import <Kamcord/Kamcord.h>
@implementation AppDelegate
@synthesize window;
//** Other functions **//
- (void) applicationDidFinishLaunching:(UIApplication*)application {
// Init the window
window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Try to use CADisplayLink director
// if it fails (SDK < 3.1) use the default director
if( ! [CCDirector setDirectorType:kCCDirectorTypeDisplayLink] )
[CCDirector setDirectorType:kCCDirectorTypeDefault];
CCDirector *director = [CCDirector sharedDirector];
//** NEW **//
// Init the View Controller
// viewController = [[RootViewController alloc] initWithNibName:nil bundle:nil];
// viewController.wantsFullScreenLayout = YES;
// Create the EAGLView manually
// 1. Create a RGB565 format. Alternative: RGBA8
// 2. depth format of 0 bit. Use 16 or 24 bit for 3d effects, like CCPageTurnTransition
//
//
// EAGLView *glView = [EAGLView viewWithFrame:[window bounds] pixelFormat:kEAGLColorFormatRGB565 // kEAGLColorFormatRGBA8
// depthFormat:0]; // GL_DEPTH_COMPONENT16_OES
// attach the openglView to the director
// [director setOpenGLView:glView];
// Enables High Res mode (Retina Display) on iPhone 4 and maintains low res on all other devices
// if( ! [director enableRetinaDisplay:YES] )
// CCLOG(@"Retina Display Not supported");
// VERY IMPORTANT:
// If the rotation is going to be controlled by a UIViewController
// then the device orientation should be "Portrait".
// IMPORTANT:
// By default, this template only supports Landscape orientations.
// Edit the RootViewController.m file to edit the supported orientations.
//#if GAME_AUTOROTATION == kGameAutorotationUIViewController
// [director setDeviceOrientation:kCCDeviceOrientationPortrait];
//#else
// [director setDeviceOrientation:kCCDeviceOrientationLandscapeLeft];
//#endif
[director setAnimationInterval:1.0/60];
[director setDisplayFPS:YES];
// make the OpenGLView a child of the view controller
// [viewController setView:glView];
//** Kamcord **//
//** Instantiate a KCGLView, which is a subclass of EAGLView with special recording functionality.
KCGLView * glView = [KCGLView viewWithFrame:[window bounds]
pixelFormat:kEAGLColorFormatRGB565
depthFormat:0];
//** Kamcord uses UIKit for autorotation, which requires special logic to handle rotations.
window.rootViewController = [ [KCViewController alloc] initWithNibName:nil bundle:nil];
window.rootViewController.view = glView;
//** Tell Kamcord about the root view controller and the KCGLView
[Kamcord setParentViewController:window.rootViewController];
[Kamcord setOpenGLView:glView];
//** Set the device orientation. Must use Kamcord, not CCDirector!
[Kamcord setDeviceOrientation:CCDeviceOrientationLandscapeLeft];
[Kamcord setDeveloperKey:@"--your key--"
developerSecret:@"--your secret--"
appName:@"testingKamcord"];
//** End of Kamcord code **//
// make the View Controller a child of the main window
[window addSubview: viewController.view];
[window makeKeyAndVisible];
// Default texture format for PNG/BMP/TIFF/JPEG/GIF images
// It can be RGBA8888, RGBA4444, RGB5_A1, RGB565
// You can change anytime.
[CCTexture2D setDefaultAlphaPixelFormat:kCCTexture2DPixelFormat_RGBA8888];
// Removes the startup flicker
[self removeStartupFlicker];
// Run the intro Scene
[[CCDirector sharedDirector] runWithScene: [HelloWorldLayer scene]];
}
view raw gistfile1.m hosted with ❤ by GitHub
#import <Kamcord/Kamcord.h>
//** RootViewController now extends from this new view controller
@interface RootViewController : KCViewController {
}
view raw gistfile2.m hosted with ❤ by GitHub
Notation for the comments:
Indented // comments: cocos2d autogenerated;
Non-indented // comments: code not anymore needed;
Indented //* comments: Kamcord explanations;
Non-indented //* comments: mine

Now, adding the functionality is really easy:
  1. Import the Kamcord framework on each scene you want.
  2. Call startRecording when you want to start the video
  3. Call stopRecording when you want to stop the recording
  4. Call showView (normally after you have record) to show the replay and share options of Kamcord
//
// HelloWorldLayer.m
// testingkamcord
//
// Created by Elena Vielva on 27/03/13.
// Copyright __MyCompanyName__ 2013. All rights reserved.
//
// Import the interfaces
#import "HelloWorldLayer.h"
#import <Kamcord/Kamcord.h>
#import "AppDelegate.h"
// HelloWorldLayer implementation
@implementation HelloWorldLayer
+(CCScene *) scene {
// 'scene' is an autorelease object.
CCScene *scene = [CCScene node];
// 'layer' is an autorelease object.
HelloWorldLayer *layer = [HelloWorldLayer node];
// add layer as a child to scene
[scene addChild: layer];
// return the scene
return scene;
}
// on "init" you need to initialize your instance
-(id) init {
if( (self=[super init])) {
// create and initialize a Label
label = [CCLabelTTF labelWithString:@"¡¡Hola mundo!!" fontName:@"Marker Felt" fontSize:64];
// ask director the the window size
CGSize size = [[CCDirector sharedDirector] winSize];
// position the label on the center of the screen
label.position = ccp( size.width /2 , size.height/2 );
// add the label as a child to this Layer
[self addChild: label z:10];
// Button
button = [CCSprite spriteWithFile:@"PanicButton.png"];
button.position = ccp(size.width*0.9, size.height*0.1);
[self addChild:button z:5];
// Grabar con Kamcord
startRec = [CCLabelTTF labelWithString:@"Start recording" fontName:@"Marker Felt" fontSize:56];
startRec.position = ccp(size.width/2, size.height*0.3);
startRec.color = ccGREEN;
startRec.opacity=0;
[self addChild:startRec z:5];
stopRec = [CCLabelTTF labelWithString:@"Stop recording" fontName:@"Marker Felt" fontSize:56];
stopRec.position = ccp(size.width/2, size.height*0.3);
stopRec.color = ccRED;
stopRec.opacity=0;
[self addChild:stopRec z:5];
share = [CCLabelTTF labelWithString:@"Share the video" fontName:@"Marker Felt" fontSize:26];
share.position = ccp(size.width*0.85, size.height*0.2);
share.color = ccORANGE;
share.opacity=0;
[self addChild:share z:5];
[Kamcord setDefaultEmailBody:@"¡Ey! Mira el baile que he hecho con la etiqueta de texto...\r\n"];
[Kamcord setDefaultTitle:@"Baile"];
touched = NO;
self.isTouchEnabled = YES;
}
return self;
}
- (void) ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:touch.view];
location = [[CCDirector sharedDirector] convertToGL:location];
if (CGRectContainsPoint(label.boundingBox, location)) {
label.position = location;
touched = YES;
return;
}
if (CGRectContainsPoint(button.boundingBox, location)) {
if (recording) {
recording = NO;
[Kamcord stopRecording];
[share runAction:[CCFadeIn actionWithDuration:0.2]];
[stopRec runAction:[CCSequence actions:[CCFadeIn actionWithDuration:0.1],[CCDelayTime actionWithDuration:0.2],[CCFadeOut actionWithDuration:0.3], nil]];
}else {
recording = YES;
share.opacity = 0;
[Kamcord startRecording];
[startRec runAction:[CCSequence actions:[CCFadeIn actionWithDuration:0.1],[CCDelayTime actionWithDuration:0.2],[CCFadeOut actionWithDuration:0.3], nil]];
}
return;
}
if (share.opacity>0&&CGRectContainsPoint(share.boundingBox, location)) {
[Kamcord showView];
return;
}
}
- (void) ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:touch.view];
if (touched) {
location = [[CCDirector sharedDirector] convertToGL:location];
label.position = location;
}
}
- (void) ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
CGSize size = [[CCDirector sharedDirector] winSize];
if (touched) {
label.position = ccp(size.width/2, size.height/2);
touched = NO;
}
}
// on "dealloc" you need to release all your retained objects
- (void) dealloc {
// in case you have something to dealloc, do it in this method
// in this particular example nothing needs to be released.
// cocos2d will automatically release all the children (Label)
// don't forget to call "super dealloc"
[super dealloc];
}
@end
view raw gistfile1.m hosted with ❤ by GitHub
//
// HelloWorldLayer.h
// testingkamcord
//
// Created by Elena Vielva on 27/03/13.
// Copyright __MyCompanyName__ 2013. All rights reserved.
//
// When you import this file, you import all the cocos2d classes
#import "cocos2d.h"
#import <AVFoundation/AVFoundation.h>
// HelloWorldLayer
@interface HelloWorldLayer : CCLayer <UIImagePickerControllerDelegate, UINavigationControllerDelegate> {
CCLabelTTF *label;
BOOL touched;
BOOL recording;
CCSprite *button;
CCLabelTTF *startRec;
CCLabelTTF *stopRec;
CCLabelTTF *share;
UIWindow *window;
UIImage *newImage;
}
// returns a CCScene that contains the HelloWorldLayer as the only child
+(CCScene *) scene;
@end
view raw gistfile2.m hosted with ❤ by GitHub

It will look like this









And you can see the video here :)


viernes, 8 de marzo de 2013

Arabic, a language written from right to left 

Lately I had to face inserting text in my app in different languages: English, Spanish, German, Chinese, Arabic, ...

What I first thought was how wonderful it would be to have all the texts in a string and change the label content depending on the selected language. Now, how intelligent labels are? Can they "understand odd symbols"? Actually, I was surprised of how simple was with the chinese: copy and paste the symbols. Tip: if you just want to insert Chinese symbols (for example, if you don't have yet the text and just want to try), don't search for "chinese symbols", instead use the google translator ;)
It doesn't depend on the font, it works with all I have tried (in this site)

So, if it's so simple, why did I write a post? Because after that I realized that the Arabic was a bit more complicated than Chinese or Japanese. It's written from right to left!

As the Arabic is written in this different direction, the label must be aligned on the right. In addition, it's not possible to change the property once the label is initialized. For this reason, I had to change the structure of the labels because before I had one label and I changed the text (which, actually, wasn't performing better than creating a new label).

But it wasn't working yet, the text was written backwards, so there was still something I was missing. There was no possibility of changing it in the label. I was surprised I couldn't find in internet any solution for this, what, in my opinion, should be a common problem. Maybe I'm loosing my ability of search in google and find an answer in Stackoverflow.

The solution was given to me by my good fairy, Alejandro, with a Wikipedia article about RLM and the wonderful unicode codes. It was like magic, just inserting \u200f at the beginning of the Arabic text and the problem was fixed. Checking whether a text is correct or not is king of fun when you don't even know the language.

For short, what did I do to insert text in Arabic in Cocos2D?

  1. Write in a string the text starting with the rlm unicode mark at the beginning.
  2. Create a label with the correct align.
  3. Add the label to the corresponding layer


NSString *text=@"\u200fمرحبا! هذا هو اختبار لمادة من بلدي بلوق";
CCLabelTTF *myLabel = [CCLabelTTF labelWithString:text dimensions:sizeTextBox alignment:UITextAlignmentRight lineBreakMode:UILineBreakModeWordWrap fontName:@"Arial" fontSize:10];
[self addChild:text];
view raw gistfile1.m hosted with ❤ by GitHub

Only three steps and very easy, isn't it? I hope this helps anyone with the same problem


viernes, 25 de enero de 2013

Sending mail inside the app with Cocos2D

First of all, I would like to say that if you are looking for a way of sending mails without using the Apple's mail composer you are not looking in the proper site. However I had the same purpose at the beginning, but I didn't want to deal directly with the SMTP protocol and I couldn't find any supported library.

Instead of a custom mail scene, you are going to have the Apple's standard mail composer. Something like that:


Implementing this is not difficult using MFMailComposeViewController, but with Cocos2D you have to pay attention at several points.

The general idea is simple: create the composer, check if it can send mails, set the delegate, fill the mail and tell the controller to show the composer.

So, the steps to follow are;

  1. Add the MessageUI framework to the project.
  2. Make your class implement MFMailComposeViewController.
  3. Instantiate the mail composer
  4. Check if the device can send the mail. This checks whether the iPhone Mail app is synchronized with an account or not. In this last case, you should break here and show an error.
  5. Set the delegate to self. Your class now must contain the method mailComposeController:didFinishWithResult:error:
  6. Fill the mail with the subject, recipients, body...
  7. Tell the UI view controller to show the composer


The main problems with Cocos2D are the animation and that you don't work with view controllers, instead you have scenes, layers, ...
For the first problem, it's as easy as pausing the director before showing the composer and resuming it after the mail is sent.
For the controller issue, I found in the cocos-2d forum a solution which involved creating a view controller and setting the view to the openGL view. That worked pretty fine with iOS5, but arise a uncaught exception when testing in iOS6. More than one view controller cannot be associated with the same view.

I finally got to the solution, take the view controller, stored in the appDelegate (it implies a little modification in the appDelegate class), and use it to show the mail view.

Finally, the code should be like that:
#import <UIKit/UIKit.h>
@class RootViewController;
@interface AppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
RootViewController *viewController;
}
@property (nonatomic, retain) UIWindow *window;
- (RootViewController *) getVController; //New method
@end
view raw AppDelegate.h hosted with ❤ by GitHub
- (RootViewController *) getVController {
return viewController;
}
view raw AppDelegate.mm hosted with ❤ by GitHub
#import <MessageUI/MessageUI.h>
@interface YourLayer : CCLayer <MFMailComposeViewControllerDelegate> {
MFMailComposeViewController *picker;
UIViewController *rootViewController;
}
- (void) displayComposerSheet;
@end
view raw YourLayer.h hosted with ❤ by GitHub
// Call this method when you want to display the composer
- (void) displayComposerSheet {
// Pause the animation
[[CCDirector sharedDirector] pause];
// Get the UI view controller
rootViewController = (UIViewController*)[(AppDelegate*)[[UIApplication sharedApplication] delegate] getVController];
// Instantiate the mail composer and check if it can send mails
picker = [[MFMailComposeViewController alloc] init];
if ((!picker) || ![MFMailComposeViewController canSendMail]) {
NSLog(@"Impossible to send mail");
return;
}
picker.mailComposeDelegate = self;
// Fill the mail content
NSArray *toArray = [NSArray arrayWithObjects:@"recipient1@themail.com",@"recipient2@themail.com", nil];
[picker setToRecipients:toArray];
[picker setSubject:@"This test is working!"];
[picker setMessageBody:@"Oh my God! This is working! Even I can add a <a href=\"http://www.google.com\">link</a>! <br/> Me" isHTML:YES];
// Display the view
[rootViewController presentModalViewController:picker animated:YES];
// We don't need it anymore
[picker release];
}
// This method is triggered on mail composer actions
- (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error {
// Resume the animation
[[CCDirector sharedDirector] resume];
// All the possible results
switch (result) {
case MFMailComposeResultCancelled:
NSLog(@"Cancelled");
break;
case MFMailComposeResultSaved:
NSLog(@"Mail saved not sent");
break;
case MFMailComposeResultFailed:
NSLog(@"Failed sending mail");
break;
case MFMailComposeResultSent:
NSLog(@"Mail sent");
default:
break;
}
// Hide the composer
[rootViewController dismissModalViewControllerAnimated:YES];
}
view raw YourLayer.mm hosted with ❤ by GitHub
Remark. All of this is assuming that you are working with cocos2d 1.x. This is reasonable assumption since the new version in cocos2d is quite recent, it has several changes from the older version and doesn't support iOS <4 or iPhone <3GS.
With Cocos2d 2.x and this documentation, I suppose (I haven't tried it yet) that the problem is no more there, because our SharedDirector will be itself our view controller (we don't need to ask the AppDelegate nor create the rootViewController variable)


miércoles, 9 de enero de 2013

Add a framework in a XCode project

Since I started programming with more advanced tools (video, mails, analytics, ...)  I've had to add new frameworks to my project.

It's very easy, but I always spend a lot of time looking for the place where I can press this plus button.

On the project navigator select your project (the blue sheet)



Then go to the project-target panel and select your target

On the left grey big panel, on the top menu select the summary tab

Now, look for Linked Frameworks and Libraries and press the plus button

You will see a new window where you can write the new library or framework you want to add



















Once you added the framework, you will see it in the project navigator



lunes, 7 de enero de 2013

The beginning: Cocos2D

I didn't have any previous knowledge of Objective-C before I started programming with the framework Cocos2d. I only read a tutorial of the basics of this programming language which was provided by our mentors in the Praktikum I was starting.

It wasn't difficult for me because of my experience in C and OOP in Java. The most difficult thing was remembering the order of the arguments and colons in the signature ;-)

For me it was important to learn on demand. Do you like (or just know, it may be sufficient) functional programming in Haskell? I'm like lazy evaluation ;-) No, just kidding... But it's true in some sense. I started in small team and each one had assigned some tasks with features to be developed. I looked for it in the internet, in my book, ask teammates, ...
The first week I learnt how to set the scene and characters, the next one, how to make an animation with frames, ... All of this tasks were surely not completely correct at the beginning but the knowledge have grown with my experience and I still like learning new ways of doing what I had implemented before.
Thus, my tip for you would be to think a simple game or app and try to develop it in order to learn.

Installing Cocos2D is not difficult, just download from the official site, read the instructions contained in the folder and run the instal-templates.sh.
Now, creating a project using this framework is really easy: You just have to choose a Cocos2D template. There are three options:



Which one should you use? It depends on your needs and your programming skills on C or C++:

  • The basic "cocos2d": recommended for beginners and for projects without physics. Don't be confused with the name, professional games have been developed with this template.
  • Cocos2d with Box2D: the most widely used in projects with physics because it's written in C++. It demands a change of the extension to .mm to all files importing this library. An advantage of this library is that there are a lot of documentation, tutorials, ...
  • Cocos2d with Chipmunk: my favorite for projects with physics. It doesn't affect the code and you don't need to change any extension. It's simple, easy and it's written in C.


The main bibliography I've used as reference are:
  • The tutorial I mentioned at the beginning of the post
  • A digital version of a Cocos2D book
Also, it's very useful to check in the oficial site forum and sometimes at the Apple's web for developers (like this). This last one only as reference. 


But this is just the beginning. The fun starts now...