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