Archivio mensile:aprile 2012

Kingdom:Undead Control Handlers

Nel disegno del codice, mi sono accorto di una cosa che a tendere rendeva illeggibile e poco gestibile il codice, la gestione degli eventi mouse, touch e keyboard da parte dei CCLayer di cocos2d.

Dovendo mostrare diversi CCNode contenenti UI realizzate con cocos, come ad esempio la scheda del giocatore, piuttosto che un menu di scelta, una popup modale magari realizzata con texture fatte adhoc, la gestione degli eventi rimaneva tutta comunque nell’implementazione del cclayer contenente tutta la parte principale del gioco.

Così ho deciso di estendere il cclayer, aggiungendo la possibilità di fornire gli eventi a dei delegati. Questo mi permette di “responsabilizzare” il codice al meglio, lasciando ad esempio la gestione del click su un certo pulsante solo nel ccnode che lo contiene.

Questo e’ un esempio di come estendere il CCLayer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//
// CCLayerControlServer.h
// KingdomUndead
//
// Created by Andrea Magini on 04/04/12.
// Copyright (c) 2012 metalide. All rights reserved.
//

#import
#import "cocos2d.h"
#import "KeyboardControlHandler.h"
#import "MouseControlHandler.h"

@interface CCLayerControlServer : CCLayer{
@private

idkeyboardDelegate;
id mouseDelegate;
}
@property (nonatomic,retain)id keyboardDelegate;
@property (nonatomic,retain)id mouseDelegate;

@end

E questa la sua implementazione:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//
// CCLayerControlServer.m
// KingdomUndead
//
// Created by Andrea Magini on 04/04/12.
// Copyright (c) 2012 metalide. All rights reserved.
//

#import "CCLayerControlServer.h"

@implementation CCLayerControlServer
@synthesize keyboardDelegate;
@synthesize mouseDelegate;
-(id) init
{
if( (self=[super init])) {
self.isMouseEnabled = YES;
self.isKeyboardEnabled = YES;
}
return self;
}

-(BOOL) ccKeyUp:(NSEvent *)event
{

[keyboardDelegate handleKeyUp:event];
return NO;
}

-(BOOL) ccMouseUp:(NSEvent *)event{
[mouseDelegate handleMouseUp:event];
return YES;
}

-(BOOL) ccMouseDragged:(NSEvent *)event{
[mouseDelegate handleMouseDragged:event];
return YES;
}

-(void)dealloc{
[keyboardDelegate release];
[mouseDelegate release];
[super dealloc];
}
@end

In questo modo tutti gli eventi intercettati dal layer vengono passati di volta in volta ai delegati registrati.
In ogni momento posso ad esempio deregistrare il layer come delegato per gli eventi e registrare un particolare CCNODE che gestisce una popup che si e’ appena mostrata a video.

I delegati devono avere un particolare Protocol per rispondere agli eventi mouse, o tastiera (o touch su iOS)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//
//  KeyboardControlHandler.h
//  KingdomUndead
//
//  Created by Andrea Magini on 04/04/12.
//  Copyright (c) 2012 metalide. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <Carbon/Carbon.h>

//per gestire il controllo sui vari ccnode e layers in modo da abilitare e disabilitare il controller

@protocol KeyboardControlHandler <NSObject>
@required
-(void) handleKeyUp:(NSEvent *)event;
@end

Questo permette di snellire il codice del CCLayer evitando di mettere tonnellate di if nei vari mousebuttonup, down, keyup etc e andando invece a gestire gli eventi
in maniera opportuna nei vari ccnode contenuti nella scena principale. Un esempio pratico è quello per la gestione degli eventi sull’HUD , lasciandoli gestire direttamente al CCNODE in questione, separandolo dagli eventi di gioco.

Come registrare un delegato:

1
2
3
@interface HudNode : CCNode<KeyboardControlHandler>{

}

Nel codice del CCLayer principale, durante l’init registro il CCNode dell’hud, e poi lo imposto come delegato per la gestione degli eventi tastiera

1
2
   [self addChild:hudNode];
   [self setKeyboardDelegate:hudNode];

e poi nel CCNode che implementa l’hud, implemento il metodo del Protocol KeyboardControlHandler

1
2
3
4
5
6
7
-(void) handleKeyUp:(NSEvent *)event{
    CCLOG(@"key up: %@", [event characters] );
    UInt16 keyCode = [event keyCode];
    if (keyCode == kVK_Space) {
       //fai qualcosa :)
    }
}

Spero sia chiaro 🙂

Kingdom:Undead sketch e idee

Iniziamo oggi una serie di articoli/appunti/idee sullo sviluppo di un prototipo di gioco per mac osx, iphone e ipad.

Il gioco è un incrocio tra un tower defence e un rpg, in cui si deve gestire il Kingdom, il “regno” di un manipolo di sopravvissuti all’infezione zombie.

Il tutto sarà incentrato nella gestione e nella crescita di questo Kingdom, un insieme di bunker e container che vengono assemblati man mano che si acquisiscono le materie prime.

Lo sviluppo per ora è su XCode 4.2 utilizzando obiective-c e la libreria Cocos2d .

Per semplificare il disegno, sto iniziando a definire tutti i modelli grafici abbozzandoli a matita, e poi scannerizzandoli e scontornandoli a mano.
Consiglio l’uso di PixelMator per questo tipo di attività. E’ un software dal costo moderato e dalle grandi potenzialità. Quando il gioco si troverà ad uno stadio abbastanza maturo, e avrò finito di definire con esattezza la dimensione di ogni singolo asset di gioco, allora passerò il lavoro a uno o più grafici 2d per ridisegnare il tutto.

Non conoscendo bene objective-c e cocos2d, inizio per gradi facendo direttamente training on the job, molte cose quindi magari ovvie a chi conosce meglio questo linguaggio, a primo acchitto potrebbero sfuggirmi. Già nella situazione attuale, iniziata oramai da 2 settimane, mi sono accorto di carenze nel disegno delle classi e ho iniziato a utilizzare funzionalità non previste inizialmente, come CoreData, il NotificationCenter , i Protocolli, e tante altre cose.

Alcuni ignobili schizzi da me eseguiti:

 

Che trasformati, diventeranno la base dei nostri spritesheet dove mettere tutte le animazioni per i sopravvissuti e per gli zombie, e i tileset per la nostra mappa.

Per la creazione del Kingdom invece sto utilizzando una CCTMXTiledMap composta da due righe, una per il piano base e una per il piano superiore, composta da 3 strati di layer, il primo che contiene lo sfondo di ogni cella, il secondo che contiene il centro della cella, con i mobili e soprammobili, il terzo strato che contiene le immagini frontali, le cornici dei bunker. i personaggi si troveranno tra lo sfondo e il layer centrale. Questo permetterà di avere una mappa modulare in cui andare a mischiare diversi background, foreground e middle per ottenere un numero maggiore di personalizzazioni della mappa.

Una prima bozza dopo una settimana di lavoro inizia a prendere forma:

Ci sono già diverse scene per l’implementazione dei credits iniziali, del menu di gioco e della schermata principale, basata su uno sfondo con scrolling in parallasse e la nostra mappa di bunker al centro.

Per poter lavorare al meglio, ho esteso la CCTXMTiledMap, per aggiungere tutte quelle proprietà e metodi che dovranno snellire e semplificare lo sviluppo e la gestione della mappa.

ecco un esempio della sua interfaccia:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#import "cocos2d.h"
#import "CCTMXTiledMap.h"

@interface BunkerMap : CCTMXTiledMap
{
CCTMXLayer *_baseLayer;
int _homeBunkerPosition;
int _lastSelectedBunkerId;
CCSprite* _lastSelectedBunker;
}

@property(nonatomic,retain)CCTMXLayer *baseLayer;
@property(nonatomic,readwrite)int homeBunkerPosition;
@property(nonatomic,readonly)int lastSelectedBunkerId;
//TODO: convertire il lastSelectedBunker da sprite a oggetto Bunker
@property(nonatomic,readonly)CCSprite *lastSelectedBunker;
-(void)selectBunker:(int)bunkerNumber;

-(void)centerToBunkerNumber:(int)bunkerNumber select:(BOOL)flag;
-(int)centerToBunkerPosition:(CGPoint)location select:(BOOL)flag;

-(int) findBunkerByPosition:(CGPoint)location;
@end

ed un esempio di implementazione della funzione per capire su che bunker ho selezionato (per poterlo centrare ad esempio o visualizzarne la sua scheda):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
-(int) findBunkerByPosition:(CGPoint)location{
   
    CGSize s = [self.baseLayer layerSize];
    for( int x=0; x<s.width;x++) {
        for( int y=0; y< s.height; y++ ) {
            CCTMXLayer *l;
            CCARRAY_FOREACH(self.children, l) {
                if([l isKindOfClass:[CCTMXLayer class]]){
                    CCSprite *tile = [l tileAt:ccp(x,y)];
                   
                    if (CGRectContainsPoint([tile boundingBox],[self convertToNodeSpace:location])){
                        // NSLog(@"TILE:%@",[tile position]);
                        return x;
                    }
                }
               
            }
           
           
            //  unsigned int tmpgid =[layer tileGIDAt:ccp(x,y)];
           
           
        }
    }
   
    return -1;
}