„Ninja Course“ (Jump’n’Run)

Hier finden Sie das komplette Projekt (Objective C für iOS5, XCode), sowie die Quellcodes und verwendeten Grafiken zum Workshop „Ninja Course“ aus dem iX-Sonderheft 03/2012.


#import <UIKit/UIKit.h>

@interface Sprite : UIView {
  UIImageView *pic;
  double width;
  double height;
  double scale;
}

@property (nonatomic, strong) UIImageView *pic;

- (id) initWithImage: (NSString*) imageName
           addToView: (UIView*) parentView;

- (void) x: (double) x
         y: (double) y
     scale: (double) faktor;

- (void) flipX: (BOOL) x
         flipY: (BOOL) y;

- (CGPoint) position;
- (double) width;
- (double) height;
- (double) scale;
- (CGRect) frame;

@end
#import "Sprite.h"

@implementation Sprite
@synthesize pic;

- (id) initWithImage: (NSString*) imageName
           addToView: (UIView*) parentView {
  if (self == [super init]) {
    UIImage* img = [UIImage imageNamed:imageName];
    
    pic = [[UIImageView alloc] initWithImage:img];
    [self addSubview:pic];
    [parentView addSubview:self];
    width = img.size.width;
    height = img.size.height;
  }
  return self;
}

- (void) x: (double) x
         y: (double) y
     scale: (double) faktor {
  scale = faktor;
   pic.frame = CGRectMake(x, y, width*faktor, height*faktor);
}

- (void) flipX: (BOOL) x
         flipY: (BOOL) y {
  pic.transform = CGAffineTransformMakeScale((x?-1:1),(y?-1:1));
}

- (CGPoint) position {return CGPointMake(pic.frame.origin.x,pic.frame.origin.y);}
- (double) width {return width;}
- (double) height {return height;}
- (double) scale {return scale;}
- (CGRect) frame {return CGRectMake(pic.frame.origin.x, pic.frame.origin.y, width*scale, height*scale);}

@end
#import <UIKit/UIKit.h>
#import "Sprite.h"

@interface ViewController : UIViewController {
  // Generell:
  UIImageView *canvas;  // Gamescreen
  double z;             // Animationszaehler
  double screenWidth;   // Displaybreite
  double screenHeight;  // Displayhoehe
 
  // Ninja
  Sprite *player;       // Ninja-Hauptsprite
  NSMutableArray *foot; // beide Fusssprites
  BOOL ninjaJumps;      // springt?
  double playerX;       // x-Koordinate
  double playerY;       // y-Koordinate
  double speed;         // Bewegung in x-Richtung (laufen)
  double gravity;       // Bewegung in y-Richtung (fallen/springen)

  // andere Sprites
  NSMutableArray *platforms;
  NSMutableArray *bush;
  Sprite *star;

  // Head-Up-Display (HUD)
  Sprite *energyIcon;
  UIView *energyBar;
  UIView *energyBarBg;
  UILabel *score;
  double energy;    // Energie
  int distance;     // Fortschrittsanzeige
}

@property (nonatomic,strong) UIImageView *canvas;
@property (nonatomic,strong) Sprite *player;
@property (nonatomic,strong) Sprite *star;
@property (nonatomic,strong) Sprite *energyIcon;
@property (nonatomic,strong) UIView *energyBar;
@property (nonatomic,strong) UIView *energyBarBg;
@property (nonatomic,strong) UILabel *score;
@property (nonatomic, strong) NSMutableArray *platforms;
@property (nonatomic, strong) NSMutableArray *bush;
@property (nonatomic, strong) NSMutableArray *foot;

-(void)newGame;
-(void)gameEngine;
-(void)renderHeadUpDisplay;
-(void)renderBushs;
-(void)renderPlatforms;
-(void)checkIfNinjaIsOnPlatform;
-(void)renderStar;
-(void)checkIfNinjaTouchesStar;
-(void)renderNinja;

@end
#import "ViewController.h"
#import <QuartzCore/QuartzCore.h>

#define worldGravity 0.35
#define maxJumpPower -10
#define maxFallingSpeed 16

@implementation ViewController
@synthesize canvas, player, star, platforms, bush, foot;
@synthesize energyIcon, energyBar, energyBarBg, score;

- (void)viewDidLoad {
  [super viewDidLoad];
  
  // Display: Vollbild und Dauerbeleuchtung an
  [[UIApplication sharedApplication] setStatusBarHidden:YES];
  [[UIApplication sharedApplication] setIdleTimerDisabled:YES];
  
  // Hintergrundbild vorbereiten
  canvas = [[UIImageView alloc] init];
  canvas.frame = [[UIScreen mainScreen] applicationFrame];
  canvas.image = [UIImage imageNamed:@"bg.png"];
  canvas.userInteractionEnabled = YES;
  self.view = canvas;
  
  // Displaygroesse auslesen (Vorsicht, mit Absicht vertauscht!)
  screenWidth = self.view.bounds.size.height; // = iPhone: 480 Pixel
  screenHeight = self.view.bounds.size.width; // = iPhone: 320 Pixel
  
  // Sprite: Buschwerk (Deko)
  bush = [[NSMutableArray alloc] init];
  for (int i = 0; i < 4; i++) {
    Sprite* tmp = [[Sprite alloc] initWithImage: @"bush.png"
                                      addToView: canvas];
    [bush addObject:tmp];
  }
  // spezielle Busch-Praeperationen
  [[bush objectAtIndex:0] x:0 y:-100 scale:1];
  [[bush objectAtIndex:1] x:0 y:140 scale:1];
  [[bush objectAtIndex:2] x:0 y:180 scale:1];
  [[bush objectAtIndex:3] x:0 y:250 scale:1];
  [[bush objectAtIndex:2] flipX:YES flipY:NO];
  [[bush objectAtIndex:0] flipX:YES flipY:YES];
  
  // Sprite: Plattformen
  platforms = [[NSMutableArray alloc] init];
  for (int i = 0; i < 3; i++) {
    Sprite* tmp = [[Sprite alloc] initWithImage: @"platform.png"
                                      addToView: canvas];
    [platforms addObject:tmp];
  }

  // Sprite: Energiesternchen
  star = [[Sprite alloc] initWithImage: @"star.png"
                             addToView: canvas];
  
  // Sprite: Ninja (besteht aus Koerper (player) und Fuessen
  
  // Fuesse
  foot = [[NSMutableArray alloc] init];
  for (int i = 0; i < 2; i++) {
    Sprite* tmp = [[Sprite alloc] initWithImage: @"ninjafoot.png"
                                      addToView: canvas];
    [foot addObject:tmp];
  } 
  
  // Koerper
  player = [[Sprite alloc] initWithImage:@"ninja.png"
                               addToView:canvas];
  // Vorderen Fuss vor Koerper setzen
  [self.view bringSubviewToFront:[foot objectAtIndex:0]];
  // vordersten Busch ganz nach vorne setzen
  [self.view bringSubviewToFront:[bush objectAtIndex:3]];

  // Head-Up-Display (HUD)
  // Energieanzeige
  energyIcon = [[Sprite alloc] initWithImage:@"ninja.png"
                                   addToView:canvas];
  [energyIcon x:10 y:10 scale:0.4];
  
  // schwarzer Hintergrundbalken
  energyBarBg =[[UIView alloc] initWithFrame:CGRectMake(39,10,102,5)];
  energyBarBg.backgroundColor=[UIColor blackColor];
  [canvas addSubview:energyBarBg];
  
  // roter Energiebalken
  energyBar = [[UIView alloc] init];
  energyBar.backgroundColor=[UIColor yellowColor];
  [canvas addSubview:energyBar];
  // Fortschrittanzeige
  score = [ [UILabel alloc ] initWithFrame:CGRectMake(40,15,100,20)];
  score.textColor = [UIColor yellowColor];
  score.backgroundColor = [UIColor clearColor];
  score.font = [UIFont fontWithName:@"Arial Rounded MT Bold" size:12];
  [canvas addSubview:score];
  
  // Level initiieren
  [self newGame];
  
  // Timer fuer Game Engine starten (30fps)
  CADisplayLink *callGameEngine =
  [CADisplayLink displayLinkWithTarget:self
                              selector:@selector(gameEngine)];
  [callGameEngine setFrameInterval:2];
  [callGameEngine addToRunLoop:[NSRunLoop currentRunLoop]
                       forMode:NSDefaultRunLoopMode];
}

-(void)newGame {
  distance = 0;
  energy = 100;
  z = 0;
  speed = 3;
  playerX = 70;
  playerY = -player.height;
  gravity = 0;
  [star x:1000 y:20+arc4random()%80 scale:1];
  for (int i=0; i< [platforms count]; i++)
    [[platforms objectAtIndex:i] x:50+i*400
                                 y:80+arc4random()%160
                             scale:0.35+0.1*i];
}

-(void)gameEngine {
  // UPDATE
  z++; // unabhaengiger Animationszaehler

  // update NINJA
  // Ninja bis 20 automatisch beschleunigen
  if (speed<20) speed+=0.01;
  
  // Springen oder Fallen?
  if (ninjaJumps) {
    if (gravity>maxJumpPower) gravity-=1; else ninjaJumps=NO; // jump
  } else if (gravity<maxFallingSpeed) gravity+=worldGravity; // fallen
  
  // update HUD
  energy-=0.1;
  distance+=speed;
  

  // RENDER PLATTFORMEN
  [self renderPlatforms];
  
  // Kollision Ninja + Plattformen
  [self checkIfNinjaIsOnPlatform];
  
  // RENDER NINJA
  [self renderNinja];

  // RENDER BUSCHWERK
  [self renderBushs];
  
  // RENDER STERN
  [self renderStar];
  [self checkIfNinjaTouchesStar];
  
  // RENDER HUD
  [self renderHeadUpDisplay];

  // SPIELENDE?
  if (playerY>600) [self newGame];
  if (playerX<-screenWidth/2) [self newGame];
  if (energy<=0) [self newGame];
}

-(void)renderBushs {
  for (int i = 0; i < [bush count]; i++) {
    Sprite* tmp = [bush objectAtIndex:i];
    
    // Buschwerk-Sprites unterschiedlich schnell bewegen und
    // nach einer Bildschirmbreite wieder zuruecksetzen
    [tmp x:fmod(tmp.position.x-speed*(0.3+i*0.3),screenWidth)
         y:tmp.position.y
     scale:tmp.scale];
  }  
}

-(void)renderPlatforms {
  for (int i = 0; i < [platforms count]; i++) {
    Sprite* tmp = [platforms objectAtIndex:i];
    
    // Plattform bewegen
    [tmp x:tmp.position.x-speed y:tmp.position.y scale:tmp.scale];
    
    // Wenn Plattform vorbei...
    if (tmp.position.x+tmp.width*tmp.scale<0) {
      
      // ... hole Daten der hintersten Plattform...
      Sprite* last = [platforms objectAtIndex:(i+2)%3];
      double lastPos = last.position.x+last.width*last.scale;
      
      // ...und setze Plattform zufaellig dahinter
      // (je schneller das Spiel, desto weiter der Abstand)
      [tmp x: lastPos + fmod(arc4random(),(20*speed))
           y: 80 + arc4random()%160
       scale: 0.5 + 0.1*(arc4random()%5)]; // scale: 0.5 - 1.0
    }
  }
}

-(void)checkIfNinjaIsOnPlatform{
  // Alle Plattformen checken
  for (int i = 0; i < [platforms count]; i++) {
    Sprite* tmp = [platforms objectAtIndex:i];
    // Ueberschneiden sich deren Frames mit Ninja?
    if (CGRectIntersectsRect(player.frame, tmp.frame)) {
      // Falls Beruehrung von oben...
      if (playerY+player.height < tmp.position.y+32*tmp.scale) {
        // ...und der Ninja faellt gerade oder steht bereit...
        if (gravity>=0) {
          // ...dann Ninja fest auf Plattform setzen (kein Fallen!)
          gravity=0;
          playerY = tmp.position.y + 10*tmp.scale-player.height;
          // Falls Ninja nach links verschoben wurde, waehrend des
          // Laufens langsam wieder auf Ursprungsposition zurueck
          if (playerX < 100) playerX++;
        }
      } else {
        // andernfalls (Sprite beruehrt Plattform von links):
        // Ninja scrollt mit (bleibt an Plattformwand haengen)
        playerX-=speed;
        distance-=speed;
      }
    }    
  }  
}

-(void)renderStar{
  // schoene Flugbahn beschreiben (Sinus + Kosinus) + flimmern
  [star x:star.position.x-speed+sin(z/15)*2
        y:star.position.y+cos(z/20)*2
    scale:0.5+fabs(sin(z/25)*0.5)];
  
  // Falls Stern vorbei ist, hinten zufaellig neu ansetzen
  if (star.position.x<-100) {
    [star x:screenWidth+500+arc4random()%1000
          y:20+arc4random()%80
      scale:1];
  }
}

-(void)checkIfNinjaTouchesStar {
  if (CGRectIntersectsRect(player.frame, star.frame)) {
    // Stern hinten ansetzen
    [star x:screenWidth+500+arc4random()%1000
          y:20+arc4random()%80
      scale:1];
    // Energie erhoehen (maximal 100%)
    energy+=20;
    if (energy>100) energy=100;
  }
}

-(void)renderNinja {
  // nachtraeglich y-Koordinate updaten, da sie evtl.
  // durch Kollisionsabfrage geaendert wurde
  playerY+=gravity;
  
  // Ninja auf feste x-Position setzen
  // Sinusberechnung in y sorgt fuer kleine Koerperbewegung
  [player x:playerX y:playerY+sin(speed*20)*2 scale:1];
  
  // Fuesse animieren
  // Laufen (ist der Fall, wenn Gravitation gerade 0 ist)
  if (gravity==0) {
    // vorderer Fuss
    [[foot objectAtIndex:0] x:playerX+12-speed/2+sin(-speed*30)*7
                            y:playerY+player.height-5+cos(-speed*30)*3
                        scale:1];
    // hinterer Fuss (durch Pi um 180 Grad phasenverschoben)
    [[foot objectAtIndex:1] x:playerX+12-speed/2+sin(-speed*30+M_PI)*7
                      y:playerY+player.height-5+cos(-speed*30+M_PI)*3
                        scale:1];
  } else {
    // Sprunganimation
    // (Beine hinten bei Absprung, Beine vorne beim Landen)
    // vorderer Fuss
    [[foot objectAtIndex:0] x:playerX+9+gravity
                      y:playerY+player.height-6+fabs(gravity/3)
                        scale:1];
    // hinterer Fuss
    [[foot objectAtIndex:1] x:playerX+6+gravity
                      y:playerY+player.height-4+fabs(gravity/2)
                        scale:1];    
  }
}

-(void)renderHeadUpDisplay {
  energyBar.frame = CGRectMake(40, 11, energy, 3);
  score.text = [NSString stringWithFormat: @"%dm", (int)distance/50];
}

- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  // nur Springen, wenn Ninja gerade auf Plattform steht
  if (gravity==0) {
    gravity=-3;
    ninjaJumps = YES;
  }
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
  ninjaJumps = NO;
}

// Screen im Landscape-Modus
- (BOOL)shouldAutorotateToInterfaceOrientation:
(UIInterfaceOrientation)interfaceOrientation{
  return (interfaceOrientation==UIInterfaceOrientationLandscapeLeft);
}

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

@end

Einen Kommentar schreiben

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.