• Home
  • Anleitungen
  • Tutorial: Kleines Spiel für das iPhone programmieren mit Cocos2D

Tutorial: Kleines Spiel für das iPhone programmieren mit Cocos2D

 

Bei diesem Text handelt es sich um eine (sinngemäße) Übersetzung des englischen Tutorials “How To Make A Simple iPhone Game with Cocos2D Tutorial” von Ray Wenderlich. Eigene Anmerkungen von mir sind besonders gekennzeichnet.

Was ist Cocos2D eigentlich?

Bei Cocos2D handelt es sich um eine Bibliothek, die das Programmieren von Spielen für das iPhone vereinfacht und eine Menge Zeit spart, da viele nützliche Funktionen integriert sind. Dazu gehören beispielsweise die Unterstützung von Sprites, grafische Effekte, Animationen, Unterstützung von Physik-Effekten, eine Sound Engine und noch weitere Funktionen. Eine Übersicht zum Thema Cocos2D findet ihr in unserer Übersicht zu Cocos2D.

Dieses Tutorial soll einen kurzen Einstieg in das Cocos2D Framework darstellen. Am Ende des Tutorials wird ein kleines, fertiges Spiel stehen. Wer möchte kann das Game Schritt für Schritt nachbauen oder sich das Projekt direkt am Ende des Artikels herunterladen.

“And yes. There will be ninjas.”

Vorbereitung

Zunächst einmalmuss Cocos2D heruntergeladen und installiert werden. Das Framework kann von der Cocos2D Google Code Seite heruntergeladen werden.

Nach dem Download muss Cocos2D noch installiert werden. Dazu öffnet ihr ein neues Terminalfenster, geht in den Ordner, in dem sich Cocos2D befindet und gebt den folgenden Befehl ein: “./install_template.sh”. Wer Xcode nicht im Standard-Verzeichnis installiert hat, kann hier noch einen entsprechenden Parameter übergeben. Dies kann beispielsweise der Fall sein, wenn ihr verschiedene Versionen des SDK installiert habt.

Der Klassiker – ein Hello World! Programm

Kaum ein Einsteiger-Tutorial kommt ohne das obligatorische Hello World Programm aus. Dies soll hier natürlich auch hier nicht fehlen und so starten wir mit einem simplen Beispiel.

Zunächst müsst ihr Xcode starten und ein neues Cocos2D Projekt erstellen. Dazu einfach das Cocos2D Application Template auswählen udn einen Namen, beispielsweise “Coso2DSimpleGame”, eingeben.

Nachdem das Projekt erstellt wurde, einfach mal Build and Run drücken. Wenn alles geklappt habt, sollte jetzt folgender Bildschirm erscheinen:

Relativ einfach, oder? ;-)

Das Konzept der “Scenes”

Cocos2D ist in sogenannten “Scenes” organisiert. Diese Scenes kann man am ehesten als Levels oder Bildschirme vorstellen. So gibt es beispielsweise eine Scene für das Menü des Spiels, eine für das eigentliche Spielgeschehen und eine für die Game Over Anzeige.

Innerhalb dieser Scenes gibt es verschiedene Layer (ganz ähnlich wie in Photoshop). Diese Layer können verschiedene Objekte, wie beispielsweise Sprites, Labels, Menüs oder ähnliches enthalten. Zudem lässt sich dies noch verschachteln, sprich Sprites können noch Unter-Sprites enthalten.

Schauen wir uns nun das Sample Projekt genauer an. Dieses enthält lediglich eine einzige Scene – die HelloWorldScene. Diese sorgt für den Bildschirm, den wir eben gesehen haben. Innerhalb dieser Scene werden wir nun unser Gameplay integrieren. Habt ihr die Datei geöffnet, seht ihr, dass die init Methode ein Label erstellt, welches für die Anzeige von “Hello World” verantwortlich ist. Diese Zeile löschen wir nun und werden stattdessen hier einen Sprite zeichnen lassen.

Hinzufügen eines Sprites

Bevor wir überhaupt ein Sprite zeichnen können, brauchen wir zunächst ein Bild, mit dem wir arbeiten können. Entweder könnt ihr dazu ein eigenes benutzen, oder das Bild aus dem Original-Tutorial verwenden. Für das Spiel werden drei Bilder gebraucht: Der Spieler, ein Projektil und ein Gegner.

Habt ihr die Bilder gespeichert, müsst ihr diese nur noch in den Ressource-Ordner eures Xcode Projektes hinein ziehen. Im erscheinenden Dialog-Feld noch den Haken bei “Copy items into destinations groups folder (if needed)” setzen. Damit sind die Grafiken nun in Xcode verwendbar.

Als nächstes müssen wir uns überlegen, wo der Spieler platziert werden soll. In Cocos2D stellt die linke, untere Ecke den Koordinatenursprung dar, hat also die Koordinaten (0,0).

Anmerkung: Dies ist eine Eigenart von Cocos2D im Gegensatz zu (beispielsweise) Core Graphics. Hier liegt der Koordinatenursprung in der linken, oberen Ecke.

Da das Projekt nur im Landscape Modus spielbar sein soll, steht eine Fläche von 480 x 320 Pixeln zur Verfügung. Die rechte obere Ecke trägt damit die Koordinaten (480, 320).

Platziert man ein Objekt auf dem Bildschirm, wird die Postion relativ zu dem Mittelpunkt des jeweiligen Sprits angegeben. Wenn wir unsere Spielfigur jetzt also am linken Rand und vertikal zentriert platzieren wollen, sehen die Koordinaten folgendermaßen aus:

  • Die X-Koordinate berechnet sich zu: [Player Sprite Width]/2
  • Die Y-Koordinate berechnet sich zu: [View Height]/2

Das folgende Bild illustriert dies genauer:

So, jetzt gehts los mit dem eigentlich Code. Öffnet den Class-Ordner und öffnet die HelloWorldScene.m Datei. Die init-Methode ersetzen wir jetzt durch folgenden Code:

-(id) init
{
  if( (self=[super init] )) {
    CGSize winSize = [[CCDirector sharedDirector] winSize];
    CCSprite *player = [CCSprite spriteWithFile:@"Player.png"
      rect:CGRectMake(0, 0, 27, 40)];
    player.position = ccp(player.contentSize.width/2, winSize.height/2);
    [self addChild:player];
  }
  return self;
}

Wird das Projekt jetzt kompiliert, sollte die Spielfigur auf dem Bildschirm erscheinen. Leider ist die Default-Einstellung für den Bildschirmhintergrund auf schwarz eingestellt, was für unser Beispiel nicht unbedingt ideal ist. Ein weißer Hintergrund würde hier deutlich besser aussehen. Eine Möglichkeit die Hintergrundfarbe in Cocos2D zu verändern, ist die Benutzung der CCColoredLayer Klasse. Dazu verändern wir in der HelloWordlScene.h die Interface Deklaration und ergänzen diese zu:

@interface HelloWorld : CCColorLayer

In der HelloWorldScene.m müssen wir nun noch eine kleine Anpassung machen. Und zwar muss noch die Hintergrundfarbe weiß übergeben werden. Dies geschieht in der init-Methode:

if( (self=[super initWithColor:ccc4(255,255,255,255)] )) {

Wird das Projekt nun kompiliert, sollte der Hintergrund weiß erscheinen. Sieht so aus, als ob der Ninja bereit für den Kampf wäre!

Bewegliche Ziele

Als nächstes wollen wir einige Ziele zu unserer Szene hinzufügen, damit der Ninja auch etwas zu bekämpfen hat. Um das Ganze etwas interessanter zu gestalten, sollen sich die Ziele bewegen, ansonsten wäre es wohl kaum eine Herausforderung die Ziele zu treffen. Wir erschaffen die Targets nicht auf dem sichtbaren Teil des Screens, sondern leicht nach rechts versetzt, sodass diese von rechts in das Bild hineinfliegen und sich dann immer weiter nach links bewegen. Wir fügen folgende Methode direkt vor unserer init-Methode ein:

-(void)addTarget {
 
  CCSprite *target = [CCSprite spriteWithFile:@"Target.png"
    rect:CGRectMake(0, 0, 27, 40)]; 
 
  // Determine where to spawn the target along the Y axis
  CGSize winSize = [[CCDirector sharedDirector] winSize];
  int minY = target.contentSize.height/2;
  int maxY = winSize.height - target.contentSize.height/2;
  int rangeY = maxY - minY;
  int actualY = (arc4random() % rangeY) + minY;
 
  // Create the target slightly off-screen along the right edge,
  // and along a random position along the Y axis as calculated above
  target.position = ccp(winSize.width + (target.contentSize.width/2), actualY);
  [self addChild:target];
 
  // Determine speed of the target
  int minDuration = 2.0;
  int maxDuration = 4.0;
  int rangeDuration = maxDuration - minDuration;
  int actualDuration = (arc4random() % rangeDuration) + minDuration;
 
  // Create the actions
  id actionMove = [CCMoveTo actionWithDuration:actualDuration
    position:ccp(-target.contentSize.width/2, actualY)];
  id actionMoveDone = [CCCallFuncN actionWithTarget:self
    selector:@selector(spriteMoveFinished:)];
  [target runAction:[CCSequence actions:actionMove, actionMoveDone, nil]];
 
}

Der erste Teil des Codes sollte soweit verständlich sein, da es sich lediglich um Dinge handelt, die wir bereits behandelt haben. Es werden einige einfache Rechnungen durchgeführt, um zu bestimmen, wo unser Objekt erstellt werden soll. Danach weisen wir diese Position dem Objekt zu und lassen es zeichnen. Die selbe Vorgehensweise haben wir auch bei dem Spieler verwendet.

Neu ist, dass wir hier einige sogenannten “Actions” hinzufügen. Cocos2D bringt eine Menge von solchen Actions mit, die bereits fest integriert sind. Dazu zählen z.B. Actions, die dazu genutzt werden können um unsere Sprites zu animieren. Sodass Dinge wie Bewegungen, Hüpfen, Ein/Ausblenden und weitere relativ einfach implementiert werden können. In unserem Fall benutzen wir drei verschiedene Actions:

  • CCMoveTo: Wir benutzen die CCMoveTo Action dazu, um unsere Objekte in den sichtbaren Bereich des Screens hinein und nach links zu bewegen. Wir können dabei auswählen, wie lange die Bewegung dauern soll. Wir wählen eine Zufallszahl im Bereich von 2 bis 4 Sekunden.
  • CCCallFuncN: Die CCCallFuncN Funktion erlaubt uns einen Callback zu definieren, der ausgeführt wird, wenn unsere Action abgelaufen ist. Wir rufen hier “spriteMoveFinished” auf. Diese Funktion existiert noch nicht, mehr dazu weiter unten.
  • CCSequence: CCSequence erlaubt die Aneinanderreihung von mehreren Actions, die anschließend nacheinander angearbeitet werden. Aus diesem Grund können wir auch zuerst die CCMoveTo Action ausführen und danach die CCCallFuncN Action.

Als nächstes fügen wir die Funktion hinzu, die wir in der CCCallFuncN Action verwenden. Fügt folgenden Code vor addTarget ein:

-(void)spriteMoveFinished:(id)sender {
  CCSprite *sprite = (CCSprite *)sender;
  [self removeChild:sprite cleanup:YES];
}

Der Sinn dieser Funktion liegt darin, das Sprite aus der Szene zu entfernen, sobald es nicht mehr auf dem Screen zu sehen ist. Dies ist wichtig, damit der für das Sprite genutzte Speicher wieder frei gegeben wird. Ein eleganter Weg ist dies nicht, aber da dies ein Anfänger-Tutorial sein soll, beschränken wir uns hier auf den einfachen Weg. Eine bessere Alternative wäre es beispielsweise ein Array von Sprites zu benutzen, die immer wieder verwendet werden.

Ein wichtiger Bestandteil fehlt jetzt noch: Wir müssen die Methode aufrufen, um überhaupt Ziele zu erzeugen! Dabei wollen wir die Ziele nicht einmalig erscheinen lassen, sondern zeitlich konstant immer wieder welche erzeugen. In Cocos2D können wir dies einfach realisieren, indem wir eine Funktion periodisch aufrufen. Einmal pro Sekunde sollte für unser Beispiel reichen. Also fügen wir folgenden Code in unsere init-Methode vor dem Return ein:

[self schedule:@selector(gameLogic:) interval:1.0];

Nun muss unsere Callback Funktion noch implementiert werden:

-(void)gameLogic:(ccTime)dt {
  [self addTarget];
}

Los gehts! Kompiliert euer Projekt und lasst es loslaufen. Auf dem Bildschirm solltet ihr nun die ersten Gegner sehen.

Projektile verschiessen

An diesem Punkt fleht unser Ninja förmlich nach etwas Action – also tun wir ihm diesen Gefallen! Es gibt zahlreiche Möglichkeiten Angriffe, für dieses Beispiel möchten wir unseren Charakter schiessen lassen, wenn wir den Screen berühren. Die Richtung soll dabei der Stelle entsprechen, an welcher der Spieler auf den Screen getippt hat.

Wir verwenden an dieser Stelle die CCMoveTo Action, um das ganze nicht zu kompliziert zu machen. Um diese Action zu benutzen, ist jedoch etwas Mathematik notwendig. Wir können nicht einfach nur die CCMoveTo Action benutzen, weil diese eine Position erwartet. Benutzen wir die Position der Berührung, würde unser Projektil einfach nur zu diesem Punkt fliegen, jedoch nicht weiter. Wir wollen, dass unser Projektil weiter fliegt und schließlich den sichtbaren Bereich verlässt.

Folgendes Bild macht das ganze vielleicht etwas klarer:

Wie ihr also sehen könnt, konstruieren wir ein Dreieck, das den Abstand von unserer Spielfigur zu unserem Touch-Punkt bezeichnet. Alles war wir nun machen müssen, ist ein neues Dreieck erstellen, welches größere Ausmaße, aber das gleich Seitenverhältnis hat. Zudem soll einer der Endpunkte außerhalb des sichtbaren Bereich liegen.

Weiter geht es mit dem Code. Zunächst müssen wir Touch-Events auf unserem Layer erlauben. Dazu fügt ihr folgenden Code in die init-Methode ein:

self.isTouchEnabled = YES;

Since we’ve enabled touches on our layer, we will now receive callbacks on touch events. So let’s implement the ccTouchesEnded method, which is called whenever the user completes a touch, as follows:

Nachdem wir nun die Touch-Events auf unserem Layer erlauben, wird bei einem Touch-Event jeweils die Callback aufgerufen. Diese Funktion, die ccTouchesEnde Methode, ist zur Zeit noch nicht implementiert. Dies werden wir nun tun:

- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
 
  // Choose one of the touches to work with
  UITouch *touch = [touches anyObject];
  CGPoint location = [touch locationInView:[touch view]];
  location = [[CCDirector sharedDirector] convertToGL:location];
 
  // Set up initial location of projectile
  CGSize winSize = [[CCDirector sharedDirector] winSize];
  CCSprite *projectile = [CCSprite spriteWithFile:@"Projectile.png"
    rect:CGRectMake(0, 0, 20, 20)];
  projectile.position = ccp(20, winSize.height/2);
 
  // Determine offset of location to projectile
  int offX = location.x - projectile.position.x;
  int offY = location.y - projectile.position.y;
 
  // Bail out if we are shooting down or backwards
  if (offX <= 0) return;
 
  // Ok to add now - we've double checked position
  [self addChild:projectile];
 
  // Determine where we wish to shoot the projectile to
  int realX = winSize.width + (projectile.contentSize.width/2);
  float ratio = (float) offY / (float) offX;
  int realY = (realX * ratio) + projectile.position.y;
  CGPoint realDest = ccp(realX, realY);
 
  // Determine the length of how far we're shooting
  int offRealX = realX - projectile.position.x;
  int offRealY = realY - projectile.position.y;
  float length = sqrtf((offRealX*offRealX)+(offRealY*offRealY));
  float velocity = 480/1; // 480pixels/1sec
  float realMoveDuration = length/velocity;
 
  // Move projectile to actual endpoint
  [projectile runAction:[CCSequence actions:
    [CCMoveTo actionWithDuration:realMoveDuration position:realDest],
    [CCCallFuncN actionWithTarget:self selector:@selector(spriteMoveFinished:)],
    nil]];
 
}

Im ersten Teil des Codes wählen wir ein Touch Event aus, lesen die Position aus und rufen dann die Funktion convertToGL auf, um die Koordinaten zu transformieren.

Anmerkung: Wie weiter oben bereits angemerkt, benutzt Cocos2D ein etwas anderes Koordinaten-System. locationInView liefert jedoch Koordinaten, die in Core Graphics verwendet werden. Deswegen muss hier mittels der Funktion convertToGL die Koordinaten-Transformation durchgeführt werden.

Als nächstes laden wir das Sprite des Projektils und legen die Position fest. Als nächstes müssen wir angeben, wo sich unser Projektil hin bewegen soll. Dies bewerkstelligen wir über den Algorithmus, der weiter oben erklärt wurde. Optimal ist der Algorithmus nicht geschrieben. So bewegt sich unser Projektil beispielsweise noch weiter, auch wenn es in Y-Richtung schon lange vom Bildschirm verschwunden ist. Hier besteht noch Verbesserungspotential, für unseren Fall reicht der derzeitige Stand aber erstmal aus.

Als letztes müssen wir noch die Dauer der Bewegung heraus finden, damit unsere Projektile alle die selbe Geschwindigkeit haben – unabhängig von der Richtung in welche sie abgeschossen werden. Dafür ist wieder ein wenig Mathematik nötig. Um heraus zu finden, wie weit unsere Bewegung sein wird, benutzen wir den Satz des Pythagoras. Haben wir die Länge unserer Bewegung, dividieren wir diese durch unsere Geschwindigkeit, um die Dauer der Bewegung zu erhalten. Der Rest des Codes ist lediglich dafür da, die Actions zu setzen – genau wie wir es vorher für die Gegner gemacht haben.

Der Code kann jetzt kompiliert und gestartet werden. Euer Ninja sollte nun auf die Ziele schiessen können.

 

Kollisionsabfrage

Unser Ninja kann jetzt also seine Projektile verschiessen. Das bringt uns leider noch nicht so viel, da keines der Projektile einen Einfluss auf unsere Gegner hat. Im nächsten Schritt müssen wir uns also mit einer Kollisionsabfrage beschäftigen. Dazu gibt es viele Möglichkeiten, unter anderem auch die Physik-Bibliotheken, die in Cocos2D bereits integriert sind. Namentlich sind das Box2D und Chipmunk. Um die Sache nicht unnötig kompliziert zu machen, werden wir an dieser Stelle eine simple Kollisionsabfrage selber schreiben. Um das zu tun, brauchen wir etwas mehr Informationen über die Gegner und die umher fliegenden Projektile. Folgendes fügen wir zur Deklaration der HelloWorldScene Klasse hinzu:

NSMutableArray *_targets;
NSMutableArray *_projectiles;

Diese Arrays müssen zudem in der init-Methode initialisiert werden:

_targets = [[NSMutableArray alloc] init];
_projectiles = [[NSMutableArray alloc] init];

In der dealloc-Methode muss natürlich auch der Speicher wieder frei gegeben werden:

[_targets release];
_targets = nil;
[_projectiles release];
_projectiles = nil;

In der addTarget-Methode müssen wir jetzt dafür sorgen, dass das Ziel zum entsprechenden Array hinzugefügt wird:

target.tag = 1;
[_targets addObject:target];

Gleiches müssen wir in der ccTouchesEnded-Methode tun und die Projektile zum entsprechenden Array hinzufügen (der Tag wird für die weitere Verwendungen gebraucht):

projectile.tag = 2;
[_projectiles addObject:projectile];

Finally, modify your spriteMoveFinished method to remove the sprite from the appropriate array based on the tag:

Als letzten Schritt müssen wir noch die spriteMoveFinished-Methode anpassen und das jeweilige Sprite, basierende auf seinem Tag, löschen:

if (sprite.tag == 1) { // target
  [_targets removeObject:sprite];
} else if (sprite.tag == 2) { // projectile
  [_projectiles removeObject:sprite];
}

An dieser Stelle könnt ihr den Code einmal kompilieren und prüfen ob alles funktioniert. An dieser Stelle wird von unseren Änderungen noch nichts sichtbar sein, aber wir haben das Fundament für unsere Kollisionsabfrage gelegt.

Wir fügen jetzt folgende Methode in die HelloWorldScene ein:

- (void)update:(ccTime)dt {
 
  NSMutableArray *projectilesToDelete = [[NSMutableArray alloc] init];
  for (CCSprite *projectile in _projectiles) {
    CGRect projectileRect = CGRectMake(
      projectile.position.x - (projectile.contentSize.width/2),
      projectile.position.y - (projectile.contentSize.height/2),
      projectile.contentSize.width,
      projectile.contentSize.height);
 
    NSMutableArray *targetsToDelete = [[NSMutableArray alloc] init];
    for (CCSprite *target in _targets) {
      CGRect targetRect = CGRectMake(
        target.position.x - (target.contentSize.width/2),
        target.position.y - (target.contentSize.height/2),
        target.contentSize.width,
        target.contentSize.height);
 
      if (CGRectIntersectsRect(projectileRect, targetRect)) {
        [targetsToDelete addObject:target];
      }
    }
 
    for (CCSprite *target in targetsToDelete) {
      [_targets removeObject:target];
      [self removeChild:target cleanup:YES];
    }
 
    if (targetsToDelete.count > 0) {
      [projectilesToDelete addObject:projectile];
    }
    [targetsToDelete release];
  }
 
  for (CCSprite *projectile in projectilesToDelete) {
    [_projectiles removeObject:projectile];
    [self removeChild:projectile cleanup:YES];
  }
  [projectilesToDelete release];
}

An dieser Stelle solltet ihr den obigen Code gut verstehen können. Wir iterieren durch unsere Projektile und Ziele, erschaffen jeweils eine Box mit den Abmessungen und prüfen, ob es irgendwo Überschneidungen (mittels CGRectInteresectsRect) gibt. Werden Überschneidungen ( = Kollision) gefunden, werden die entsprechenden Objekte aus der Scene entfernt. Solange wir durch unseren Array iterieren, können wir jedoch keine Objekte entfernen. Deswegen müssen wir unsere zu entfernenden Elemente einem “Löschen-Array” übergeben. Es existieren zwar schönere Wege, um dies zu lösen, aber um das Programm nicht zu kompliziert werden zu lassen, belassen wir es an dieser Stelle dabei.

Jetzt fehlt nur noch ein kleines Detail. Wir wollen diese Methode so oft wie möglich aufrufen. Dies realisieren wir, indem wir folgende Zeile zu unserer init-Methode hinzufügen:

[self schedule:@selector(update:)];

Jetzt kann der Code kompiliert werden und es sollte alles – inklusive unserer Kollisionsabfrage – funktionieren.

Finale Überarbeitung

Wir sind nun ziemlich nah dran, unser erstes (sehr einfaches) Spiel fertig zu stellen. Was noch fehlt, sind Game Sounds und etwas Hintergrundmusik. Denn welches Game kommt schon ohne Sound-Effekte aus?

Die Integration von Sounds in Cocos2D ist relativ simpel. Zunächst müssen die entsprechenden Sound-Files per Drag&Drop dem Ressourcen-Ordner in Xcode hinzugefügt werden. Wer mag, kann die vorgefertigren Sounds von Ray Wenderlich nutzen (Hintergrundmusik und Sound-Effekte). Am Anfang unserer HelloWorldScene.m muss nun folgendes eingefügt werden:

#import "SimpleAudioEngine.h"

In der init-Methode starten wir unsere Hintergrundmusik:

[[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"background-music-aac.caf"];

In der ccTouchesEnded rufen wir den Sound-Effekt auf:

[[SimpleAudioEngine sharedEngine] playEffect:@"pew-pew-lei.caf"];

Now, let’s create a new scene that will serve as our “You Win” or “You Lose” indicator. Click on the Classes folder and go to File\New File, and choose Objective-C class, and make sure subclass of NSObject is selected. Click Next, then type in GameOverScene as the filename, and make sure “Also create GameOverScene.h” is checked.
Then replace GameOverScene.h with the following code:

Im nächsten Schritt wollen wir noch eine neue Scene erstellen, die uns entweder “You Win” oder “You Lose” anzeigt – je nachdem ob wir nun gewonnen oder verloren haben. Dazu erstellen wir eine neue Klasse. Über File -> New File -> Objective-C class erstellen wir eine Subklasse von NSObject. Als Dateinamen wählen GameOverScene und setzen den Haken bei “Also create GameOverScene.h”.

Den vorhandenen Code in der GameOverScene.h überschreiben wir nun durch folgenden Code:

#import "cocos2d.h"
 
@interface GameOverLayer : CCColorLayer {
  CCLabelTTF *_label;
}
@property (nonatomic, retain) CCLabelTTF *label;
@end
 
@interface GameOverScene : CCScene {
  GameOverLayer *_layer;
}
@property (nonatomic, retain) GameOverLayer *layer;
@end

Den Code in der GameOverScene.m überschreiben wir nun mit folgendem Code:

#import "GameOverScene.h"
#import "HelloWorldScene.h"
 
@implementation GameOverScene
@synthesize layer = _layer;
 
- (id)init {
 
  if ((self = [super init])) {
    self.layer = [GameOverLayer node];
    [self addChild:_layer];
  }
  return self;
}
 
- (void)dealloc {
  [_layer release];
  _layer = nil;
  [super dealloc];
}
 
@end
 
@implementation GameOverLayer
@synthesize label = _label;
 
-(id) init
{
  if( (self=[super initWithColor:ccc4(255,255,255,255)] )) {
 
    CGSize winSize = [[CCDirector sharedDirector] winSize];
    self.label = [CCLabelTTF labelWithString:@"" fontName:@"Arial" fontSize:32];
    _label.color = ccc3(0,0,0);
    _label.position = ccp(winSize.width/2, winSize.height/2);
    [self addChild:_label];
 
    [self runAction:[CCSequence actions:
      [CCDelayTime actionWithDuration:3],
      [CCCallFunc actionWithTarget:self selector:@selector(gameOverDone)],
      nil]];
 
  }
  return self;
}
 
- (void)gameOverDone {
 
  [[CCDirector sharedDirector] replaceScene:[HelloWorld scene]];
 
}
 
- (void)dealloc {
  [_label release];
  _label = nil;
  [super dealloc];
}
 
@end

Hierbei muss beachtet werden, dass in dem Code zwei verschiedene Objekte angesprochen werden. Eine Scene und ein Layer. Die Scene kann mehrere Layer beinhalten, in diesem Beispiel enthält unsere Scene jedoch nur einen Layer. Der Layer sorgt dafür, dass ein Label, also ein Schriftzug, in der Mitte des Bildschirms erscheint und für eine Zeit von drei Sekunden angezeigt wird.

Um zu ermitteln, ob der Spieler gewinnt oder verliert, müssen wir noch eine sogenannte “Game logic” einfügen. Dazu werden wir die Anzahl der vom Spieler zerstörten Projektile zählen. Hierfür benutzen wir eine sogenannte Member Variable. In der HelloWorldScene.h fügen wir folgendes hinzu:

int _projectilesDestroyed;

In der HelloWorldScene.m inkludieren wir nun die GameOverScene Klasse:

#import "GameOverScene.h"

Increment the count and check for the win condition in your update method inside the targetsToDelete loop right after removeChild:target:

In der Update Methode integrieren wir nun unseren Zähler für die zerstörten Gegner und prüfen, ob der Spieler gewonnen hat (mehr als 30 Ziele zerstört). Dafür fügen wir folgenden Code innerhalb der targetsToDelete Schleife, direkt nach “removeChild:target” ein:

_projectilesDestroyed++;
if (_projectilesDestroyed > 30) {
  GameOverScene *gameOverScene = [GameOverScene node];
  [gameOverScene.layer.label setString:@"You Win!"];
  [[CCDirector sharedDirector] replaceScene:gameOverScene];
}

Nun fehlt noch die Bedingung, die erfüllt sein muss, damit der Spieler verliert. Wir legen fest, dass der Spieler verliert, sobald ein Ziel an ihm vorbei kommt. Dazu verändern wir unsere spriteMoveFinished Methode und fügen folgenden Code innerhalb der “tag == 1″ Bedingung, gleich hinter “removeChild:sprite” ein:

GameOverScene *gameOverScene = [GameOverScene node];
[gameOverScene.layer.label setString:@"You Lose :["];
[[CCDirector sharedDirector] replaceScene:gameOverScene];

Das Game kann jetzt kompiliert und gestartet werden. Die Win/Lose Bedingungen sollten nun einwandfrei funktionieren.

Der komplette Code

Den kompletten Code gibt es hier.

Wie geht es jetzt weiter?

Der jetzige Code liefert eine gute Basis und lässt noch jede Menge Freiraum für Verbesserungen. So könnte man beispielsweise eine Anzeige integrieren, wie viele Gegner der Spieler schon eliminiert hat. Oder neue Animation einfügen, wenn ein Gegner getroffen wird. Ein anschließendes Tutorial findet sich bei Ray Wenderlich, indem ein sich rotierender Turm hinzugefügt wird. Weitere Tutorials zum Thema Cocos2D finden sich auf der Cocos2D Überssichtsseite.

 
 

10 Kommentare zu “Tutorial: Kleines Spiel für das iPhone programmieren mit Cocos2D”

  1. Vielen Dank…schön erklärt und macht Spaß…es Schritt für Schritt nach zubauen. 2 Verbesserungen:

    1) bei mir wird statt > < im Browser angezeigt – da könnte jemand Probleme mit bekommen

    2) Weiß nicht ob Anfänger wissen, wo sie in der .h – Datei dinge hinzugügen können.

    Nur so als Idee… mfg

     
  2. Danke für das Feedback. Das mit der falschen Darstellung hängt wohl mit der Code-Darstellung zusammen. Werde mal versuchen das Problem zu beheben.

    Deine zweite Anmerkung verstehe ich irgendwie noch nicht ganz ;) Meinst du damit genau die Stelle innerhalb der Header-Datei wo Code hinzugefügt werden kann?

     
  3. Ich möchte langsam aber doch mit iOS beginnen und finde dieses Tutorial sehr schön übersetzt. Für mich sind die engl. sprachigen Tuorials zwar kein Problem, es ist aber dennoch schön, auch deutschsprachige zu finden

     
    • Mit Cocos2D hast du dir da ein schönes Framework ausgesucht, um in die Spieleprogrammierung für das iPhone einzusteigen.

       
      • Christoph
      • Antworten
  4. Ich kann die App nicht über den iPad Simulator abspielen. Beim Ladescreen ist Schluss. Woran kann das liegen?

     
    • Hallo L.M.,

      was erscheint denn für eine Fehlermeldung? Stürzt der Simulator ab oder erscheint nur ein schwarzer Bildschirm?

      Welche Version von Cocos2D benutzt du? Das Tutorial bezieht sich auf die Version 1.x . Mit einer Version 2.x wird es nicht funktionieren, da hier ein paar Anpassungen nötig wären. Siehe dazu auch:

      http://www.iphone-entwicklung.net/cocos2d-version-1-vs-version-2/

       
  5. Hi, Glückwunsch zu diesem Tutorial. Habe es nachvollzogen mit xcode4.2 und erfolgreich ein paar winzige Problemchen gelöst.

    Beim Built kam es zu Problemen bei der Verwendung von

    @interface HelloWorld : CCColorLayer und

    @interface GameOverLayer : CCColorLayer

    Lösung war dann

    @interface HelloWorld : CCLayerColor

    Happy Coding

    Thomas

    @interface GameOverLayer : CCLayerColor

     
    • Thomas T.
    • Antworten
    • Hallo Thomas,

      freut mich, dass dir das Tutorial gefällt.

      Danke für die Anmerkungen!

       
  6. Hallo

    vielen Dank für das tolle Tutorial! :) Ich hab noch eine Frage: Bei mir steht anstatt Scene immer Layer in den Klassen und Dateiennamen? Liegt das an meiner Xcode version?(4.1) Und dann bekomme ich auch eine Fehlermeldung beim überschreiben von der GameOverScene in der Deklarierung… Wahrscheinlich muss ich andere Namen eingeben. Wie zb schon Thomas meinte : CCLayerColor anstatt CCColorLayer.

    @property (nonatomic, retain) CCLabelTTF *label;

    Beim *label gibt er mir die Meldung: Property label requires method label to be defined – use @synthesize ….

    @interface GameOverScene : CCScene {

    hier sagt er mir etwas von duplicate interface definition for class

    Vielen vielen Dank nochmal für das tolle Tutorial. Grüße Max T.

     

Kommentieren

© 2011 iPhone-Entwicklung.net