martes, 22 de febrero de 2011

CCSequenceHelper, CCSequence con NSMutableArray



Hoy quiero compartir con todos vosotros un pequeño fragmento de código que os puede ser útil en algún momento de vuestro desarrollo para iOS.

Si eres programador de cocos2d, sabrás que existen lasAnimaciones, (llamadasCCAnimation), y que puedes crear una secuencia de animaciones mediante la clase CCSequence.

El problema de esta clase es que solo puedes pasar una lista fija de animaciones, mediante el método 







(id) actions:



Sabemos que este método tiene como argumentos una lista de animaciones acabadas con nil, pero ¿Que pasa si no sabemos cuantas acciones queremos añadir?, o si estas acciones dependen de un bucle que tengamos.

Para solucionar este problema, voy a mostraros una solución basada en el uso de un NSMutableArray.

El objetivo es poder crear una secuencia de animaciones de forma dinámica, añadirlas a una variable de tipo NSMutableArray y pasar esta variable a una función que nos cree una secuencia. 

Para implementar esta solución, he decidido crear una clase Helper, denominada CCSequenceHelper. La utilización de la misma es muy fácil, simplemente creamos un NSMutableArray y añadimos CCAnimations a el (en este ejemplo, utilizo una animación especial que se encarga de ejecutar un método mediante un selector CCCallFuncND)



-(void) muestraAnimacionPreguntar:(NSMutableArray*) carasOcultar
{
int count = [carasOcultar count];
NSNumber *number;
NSMutableArray * secuencias = [NSMutableArray arrayWithCapacity:count+1];

for (int i = 0; i < count ;i++)
{
number = ((NSNumber*)[carasOcultar objectAtIndex:i]);
CCCallFunc * showX = [CCCallFuncND actionWithTarget:self selector:@selector(muestraCancelaCara:data:) data:(void*)[number intValue]];
showX.duration = 1;
[secuencias addObject:showX];
}
[secuencias addObject:[CCCallFunc actionWithTarget:self selector:@selector(endAnimacionPreguntar)]];
[self runAction:[CCSequenceHelper actionMutableArray:secuencias]];
}


y aquí la implementación de la clase Helper.


//
// CCSequenceHelper.h
//
// Created by Jose Antonio Andújar Clavell on 09/02/11.
// Alias "jandujar"
//
// More snippets on http://www.jandujar.com
//
// License http://creativecommons.org/licenses/by/3.0/
//
// Based upon code by daemonk
// http://www.cocos2d-iphone.org/forum/topic/2547
//

#import
#import "cocos2d.h"

@interface CCSequenceHelper : CCSequence {

}

+(id) actionMutableArray: (NSMutableArray*) _actionList;
@end



//
// CCSequenceHelper.m
// Created by Jose Antonio Andújar Clavell on 09/02/11.
// Alias "jandujar"
//
// More snippets on http://www.jandujar.com
//
// License http://creativecommons.org/licenses/by/3.0/
//
// Based upon code by daemonk
// http://www.cocos2d-iphone.org/forum/topic/2547
//

#import "CCSequenceHelper.h"


@implementation CCSequenceHelper

+(id) actionMutableArray: (NSMutableArray*) _actionList {
CCFiniteTimeAction *now;
CCFiniteTimeAction *prev = [_actionList objectAtIndex:0];

for (int i = 1 ; i < [_actionList count] ; i++) {
now = [_actionList objectAtIndex:i];
prev = [CCSequence actionOne: prev two: now];
}

return prev;
}

@end


Si os ha sido útil este fragmento de código, os animo a compartirlo. Saludos!!!

jueves, 10 de febrero de 2011

CCMenuItem con Imagen, Texto y tamaño dinámico

Hoy toca ser un poco productivos.

En esta ocasión, he decidido crear una clase para crear botones de menú de tamaño dinámico, que a su vez pueden tener un texto en su interior, con la fuente que se quiera. 

Seguramente no abreis entendido nada de lo que he dicho anteriormente, así que mostraré un video para que veais como funciona.

[yframe url='http://www.youtube.com/watch?v=eX_LKX71hpM&feature=mfu_in_order&list=UL']

Como desarrollador de iphone / ipad en algún momento necesitaras crear botones de menú. Para realizar estos botones, lo que se suele hacer es crear botones con photoshop/gimp de diferentes tamaños/colores. Este proceso (además de ser un engorro), nos acarrea ciertos problemas, ya que si disponemos de muchos botones, podemos quedarnos fácilmente sin memoria, o consumir mucha mas de la necesaria.

Para estos casos, he creado la clase JACDynamicButton que a su vez, utiliza una modificación de la clase Scale9Spriteobtenida del foro de cocos2d

Para este proceso es necesario que tengamos una o varias imagenes de tamaño 64x64 (se podría modificar, pero yo he optado por este tamaño, ya que es múltiplo de 2 y corresponde a una textura válida para iOS) como las siguientes.



A simple vista, puede parecer que es la misma imagen, pero no lo son, una tiene aura y servirá para saber cuando está pulsado el botón.

Mediante estas dos imagenes, podremos crear botones como los siguientes.



Por ejemplo, para realizar estos botones, solo ha sido necesario este fragmento de código.


JACDynamicButton* button1 = [JACDynamicButton itemWithText:@"Hello" font:@"Arial" fontSize:30 minSize:CGSizeMake(100,100) normalImage:@"boton_verde.png" selectedImage:@"boton_verde_sel.png" target:self selector:@selector(doMenu:)];
JACDynamicButton* button2 = [JACDynamicButton itemWithText:@"Hello Large" font:@"Arial" fontSize:30 minSize:CGSizeMake(200,50) normalImage:@"boton_verde.png" selectedImage:@"boton_verde_sel.png" target:self selector:@selector(doMenu:)];


Como podeis observar, se utiliza una fuente Arial con tamaño 30, esto se podría cambiar y se pide el tamaño mínimo del botón (que usualmente será el tamaño del mismo, a menos que la fuente nos obligue a hacer el botón mas grande) 

Y este es el código utilizado.


//
// JACDynamicButton.h
// JACDynamicButton
//
// Created by Jose Antonio Andújar Clavell on 09/02/11.
// Alias "jandujar"
//
// More snippets on http://www.jandujar.com
//
// License http://creativecommons.org/licenses/by/3.0/

#import "cocos2d.h"

@interface JACDynamicButton : CCMenuItemSprite {

}

+(id) itemWithText: (NSString*)text font:(NSString*)f fontSize:(int)fs minSize:(CGSize)ms normalImage:(NSString*)ni selectedImage:(NSString*)si target:(id) t selector:(SEL) s;

@end



//
// JACDynamicButton.m
// JACDynamicButton
//
// Created by Jose Antonio Andújar Clavell on 09/02/11.
// Alias "jandujar"
//
// More snippets on http://www.jandujar.com
//
// License http://creativecommons.org/licenses/by/3.0/

#import "JACDynamicButton.h"
#import "Scale9Sprite.h"

@implementation JACDynamicButton

+(id) itemWithText: (NSString*)text font:(NSString*)f fontSize:(int)fs minSize:(CGSize)ms normalImage:(NSString*)ni selectedImage:(NSString*)si target:(id) t selector:(SEL) s
{
//Create the label
CCLabelTTF *label = [CCLabelTTF labelWithString:text fontName:f fontSize:fs];

//Calculate the Dynamic Button size
int minWidth = label.contentSize.width>ms.width?label.contentSize.width:ms.width;
int minHeight = label.contentSize.height>ms.height?label.contentSize.height:ms.height;

CGSize size=CGSizeMake(minWidth+30,minHeight+24);

[label setPosition:CGPointMake(size.width*0.5, size.height*0.5)];

//Sprite normal
Scale9Sprite *normalSprite = [[[Scale9Sprite alloc] initWithFile:ni centreRegion:CGRectMake(20, 20, 24, 24)] autorelease];
[normalSprite setContentSize:size];

//Sprite selected
Scale9Sprite *selectedSprite = nil;
if(si){
selectedSprite = [[[Scale9Sprite alloc] initWithFile:si centreRegion:CGRectMake(20, 20, 24, 24)] autorelease];
[selectedSprite setContentSize:size];
}

//Create the CCMenuItemSprite
CCMenuItemSprite* returnItem;
returnItem = [self itemFromNormalSprite:normalSprite selectedSprite:selectedSprite target:t selector:s];


[returnItem addChild:label];

NSLog(@"Size = %f %f", returnItem.contentSize.width,returnItem.contentSize.height);

return returnItem;
}


@end

 Por último, comentaros que teneis a vuestra disposición el código fuente de este ejemplo, así como el fichero PSD para que podais modificar el color del botón a vuestro antojo.

martes, 1 de febrero de 2011

Implementación de Selector de Menu para Cocos2D

Hoy voy a mostrar un pequeño fragmento de código que he desarrollado para mi primera aplicación de iPad, la cual estoy desarrollando.

Este pequeño snippet os permitirá crear un menu con N elementos separados en M filas. Para el ejemplo que os he puesto he creado una lista de 9 botones agrupados en 2 filas.

Pero como una imagen vale mas que 1000 palabras, aquí os paso un video que os mostrará el funcionamiento del mismo.

[yframe url='http://www.youtube.com/watch?v=FcK1uQXfLGs&feature=channel_video_title']

Por último, os adjunto el código fuente del selector de menu, solo necesitareis importar las clasesJACSelector.h y JACSelector.m a vuestro proyecto.

Para cualquier duda, dejar un comentario.

 


//
// JACSelector.h
// SelectorMenu
//
// Created by Jose Antonio Andújar Clavell on 01/02/11.
// Alias "jandujar"
//
// More snippets on http://www.jandujar.com
//
// License http://creativecommons.org/licenses/by/3.0/

#import "cocos2d.h"


@interface JACSelector : CCLayer
{
tCCMenuState state; // State of our menu grid. (Eg. waiting, tracking touch, cancelled, etc)
CCMenuItem *selectedItem; // Menu item that was selected/active.

CGPoint padding; // Padding in between menu items.
CGPoint menuOrigin; // Origin position of the entire menu grid.
CGPoint touchOrigin; // Where the touch action began.
CGPoint touchStop; // Where the touch action stopped.
CGPoint relativePosition; // Position of the menu when touch action began

float fMaxY; // Max offset of menu position

bool bMoving; // Is the menu currently moving?

float fMoveDelta; // Distance from origin of touch and current frame.
float fAnimSpeed; // 0.0-1.0 value determining how slow/fast to animate the paging.
}

+(id) menuWithArray:(NSMutableArray*)items rows:(int)rows position:(CGPoint)pos padding:(CGPoint)pad ;

-(id) initWithArray:(NSMutableArray*)items rows:(int)rows position:(CGPoint)pos padding:(CGPoint)pad ;

-(void) buildMenuSelector:(int)rows itemWidth:(int)iw itemHeight:(int)ih;

-(CCMenuItem*) GetItemWithinTouch:(UITouch*)touch;
- (CGPoint) GetRelativePosition:(float)offset;
- (void) checkLimits;



@property (nonatomic, readwrite) CGPoint padding;
@property (nonatomic, readwrite) CGPoint menuOrigin;
@property (nonatomic, readwrite) CGPoint touchOrigin;
@property (nonatomic, readwrite) CGPoint touchStop;
@property (nonatomic, readwrite) CGPoint relativePosition;
@property (nonatomic, readwrite) bool bMoving;
@property (nonatomic, readwrite) float fMoveDelta;
@property (nonatomic, readwrite) float fAnimSpeed;
@property (nonatomic, readwrite) float fMaxY;

@end



//
// JACSelector.m
// SelectorMenu
//
// Created by Jose Antonio Andújar Clavell on 01/02/11.
// Alias "jandujar"
//
// More snippets on http://www.jandujar.com
//
// License http://creativecommons.org/licenses/by/3.0/

#import "JACSelector.h"


@implementation JACSelector

@synthesize padding;
@synthesize menuOrigin;
@synthesize touchOrigin;
@synthesize touchStop;
@synthesize relativePosition;
@synthesize bMoving;
@synthesize fMoveDelta;
@synthesize fAnimSpeed;
@synthesize fMaxY;

+(id) menuWithArray:(NSMutableArray*)items rows:(int)rows position:(CGPoint)pos padding:(CGPoint)pad
{
return [[self alloc] initWithArray:items rows:rows position:pos padding:pad];
}

-(id) initWithArray:(NSMutableArray*)items rows:(int)rows position:(CGPoint)pos padding:(CGPoint)pad
{
if ((self = [super init]))
{
self.isTouchEnabled = YES;

CCMenuItem* item =[items objectAtIndex:0];


int itemWidth = item.contentSizeInPixels.width;
int itemHeight = item.contentSizeInPixels.height;

CGSize size = CGSizeMake(itemWidth+2*pad.x,rows*(itemHeight +pad.y) +pad.y);

[self setContentSize:size];

int z = 1;
for (id item in items)
{
[self addChild:item z:z tag:z];
++z;
}

padding = pad;
bMoving = false;
menuOrigin = pos;
fAnimSpeed = 1;

[self buildMenuSelector:rows itemWidth:itemWidth itemHeight:itemHeight];

self.position = menuOrigin;
}

return self;
}

-(void) dealloc
{
[super dealloc];
}

-(void) buildMenuSelector:(int)rows itemWidth:(int)iw itemHeight:(int)ih
{
int row = 0;
int i=0;
fMaxY = menuOrigin.y;
for (CCMenuItem* item in self.children)
{
// Calculate the position of our menu item.
item.position = CGPointMake( padding.x + iw/2,
self.contentSize.height +ih/2 - (row+1)*((ih) + padding.y));

if(row%rows==(rows-1)){
fMaxY = -item.position.y + rows*(ih + padding.y) -ih/2 + menuOrigin.y ;
}
++row;
i++;
}
}

-(void) addChild:(CCMenuItem*)child z:(int)z tag:(int)aTag
{
return [super addChild:child z:z tag:aTag];
}

-(CCMenuItem*) GetItemWithinTouch:(UITouch*)touch
{
// Get the location of touch.
CGPoint touchLocation = [[CCDirector sharedDirector] convertToGL: [touch locationInView: [touch view]]];

// Parse our menu items and see if our touch exists within one.
for (CCMenuItem* item in [self children])
{
CGPoint local = [item convertToNodeSpace:touchLocation];

CGRect r = [item rect];
r.origin = CGPointZero;

// If the touch was within this item. Return the item.
if (CGRectContainsPoint(r, local))
{
return item;
}
}

// Didn't touch an item.
return nil;
}

-(void) registerWithTouchDispatcher
{
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:INT_MIN+1 swallowsTouches:YES];
}

-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
// Convert and store the location the touch began at.
touchOrigin = [[CCDirector sharedDirector] convertToGL:[touch locationInView:[touch view]]];
relativePosition = CGPointMake(self.position.x, self.position.y);

// If we weren't in "waiting" state bail out.
if (state != kCCMenuStateWaiting)
{
return NO;
}

// Activate the menu item if we are touching one.
selectedItem = [self GetItemWithinTouch:touch];
[selectedItem selected];


state = kCCMenuStateTrackingTouch;
return YES;
}

// Touch has ended. Process sliding of menu or press of menu item.
-(void) ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event
{
// User has been sliding the menu.
if( bMoving )
{
bMoving = false;

[self checkLimits];
[selectedItem unselected];

}
// User wasn't sliding menu and simply tapped the screen. Activate the menu item.
else
{
[selectedItem unselected];
[selectedItem activate];
}

// Back to waiting state.
state = kCCMenuStateWaiting;
}

// Check the limits of the menu Selector
- (void) checkLimits
{
if (self.position.y < (menuOrigin.y)) {
// Perform the action
id action = [CCMoveTo actionWithDuration:(fAnimSpeed*0.5) position:CGPointMake(self.position.x, menuOrigin.y)];
[self runAction:action];
}else if (self.position.y > (fMaxY)) {
// Perform the action
id action = [CCMoveTo actionWithDuration:(fAnimSpeed*0.5) position:CGPointMake(self.position.x, fMaxY)];
[self runAction:action];
}

}

-(void) ccTouchCancelled:(UITouch *)touch withEvent:(UIEvent *)event
{
[selectedItem unselected];

state = kCCMenuStateWaiting;
}

-(void) ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event
{
// Calculate the current touch point during the move.
touchStop = [[CCDirector sharedDirector] convertToGL:[touch locationInView:[touch view]]];

// Distance between the origin of the touch and current touch point.
fMoveDelta = touchStop.y - touchOrigin.y;

// Set our position.
[self setPosition:[self GetRelativePosition:fMoveDelta]];
bMoving = true;
}

- (CGPoint) GetRelativePosition:(float)offset
{
return CGPointMake(relativePosition.x,relativePosition.y+offset);
}


#pragma mark Clipping logic


- (void) visit {
if (!self.visible)
return;

glPushMatrix();

glEnable(GL_SCISSOR_TEST);

glScissor(menuOrigin.x, menuOrigin.y, self.contentSize.width , self.contentSize.height);
//glScissor(menuOrigin.x, menuOrigin.y, 100 , 100);


[super visit];

glDisable(GL_SCISSOR_TEST);
glPopMatrix();
}

@end