Construiți comanda de rachete cu kitul Sprite interacțiunea cu utilizatorul

În tutorialul anterior, am pus bazele jocului nostru de comandă anti-rachetă, prin crearea proiectului, crearea scenei cu un singur jucător și adăugarea interacțiunii cu utilizatorul. În acest tutorial, veți extinde experiența de joc prin adăugarea unui mod cu mai mulți jucători, precum și a fizicii, coliziunilor și exploziilor.


Vizualizare finală

Uitați-vă la următoarea captură de ecran pentru a obține o idee despre ceea ce urmărim.



Pick Up unde am plecat

Dacă nu ați făcut-o deja, vă recomandăm să completați tutorialul anterior pentru a vă asigura că putem construi fundația pe care am stabilit-o în primul tutorial. În acest tutorial, mărim un număr de subiecte, cum ar fi fizica, coliziuni, explozii și adăugarea unui mod cu mai mulți jucători.


1. Activarea fizicii

Cadrul Sprite Kit include un motor fizic care simulează obiecte fizice. Motorul fizic al cadrului Sprite Kit funcționează prin SKPhysicsContactDelegate protocol. Pentru a activa motorul fizic în jocul nostru, trebuie să modificăm Scena mea clasă. Începeți prin actualizarea fișierului antet așa cum se arată mai jos pentru a le spune compilatorului SKScene clasa este conformă cu SKPhysicsContactDelegate protocol.

#import  @ interfață MyScene: SKScene  @Sfârșit

SKPhysicsContactDelegate protocol ne permite să detectăm dacă două obiecte s-au ciocnit unul cu celălalt. Scena mea instanța trebuie să implementeze SKPhysicsContactDelegate dacă dorește să fie informat despre coliziunile dintre obiecte. Un obiect de implementare a protocolului este notificat ori de câte ori o coliziune începe și se termină.

Deoarece vom avea de-a face cu explozii, rachete și monștri, vom defini o categorie pentru fiecare tip de obiect fizic. Adăugați următorul fragment de cod în fișierul antet al Scena mea clasă.

#import  typedef enum: NSUInteger ExplosionCategory = (1 << 0), MissileCategory = (1 << 1), MonsterCategory = (1 << 2)  NodeCategory; @interface MyScene : SKScene  @Sfârșit

Înainte de a începe explorarea motorului fizic al cadrului Sprite Kit, trebuie să setăm gravitatie proprietatea lumii fizicii, precum și a ei contactDelegate. Actualizați initWithSize: așa cum se arată mai jos.

- (dimensiune CGSize) if (auto = [super initWithSize: dimensiune]) auto.backgroundColor = [SKColor colorWithRed: (220.0 / 255.0) verde: (220.0 / 255.0) : 1,0]; // ... // // Configurează lumea fizică self.physicsWorld.gravity = CGVectorMake (0, 0); auto.physicsWorld.contactDelegate = auto;  întoarce-te; 

În jocul nostru, motorul de fizică este folosit pentru a crea trei tipuri de corpuri fizice, gloanțe, rachete și monștri. Când lucrați cu cadrul Sprite Kit, utilizați volume dinamice și statice pentru a simula obiectele fizice. Un volum pentru un grup de obiecte este un volum care conține fiecare obiect al grupului. Volumele dinamice și statice reprezintă un element important pentru îmbunătățirea performanțelor motorului fizic, în special atunci când lucrează cu obiecte complexe. În jocul nostru, vom defini două tipuri de volume, cercuri cu o rază fixă ​​și obiecte personalizate.

În timp ce cercurile sunt disponibile prin SKPhysicsBody clasa, obiectul obișnuit necesită un pic de lucru suplimentar din partea noastră. Deoarece corpul unui monstru nu este circular, trebuie să creăm un volum personalizat pentru el. Pentru a face această sarcină un pic mai ușoară, vom folosi un generator de cale fizică a corpului. Instrumentul este ușor de utilizat. Importați spritele proiectului dvs. și definiți calea de închidere pentru fiecare sprite. Codul Objective-C pentru a recrea calea este afișat sub sprite. De exemplu, aruncați o privire asupra următorului sprite.


Următorul ecran afișează același sprite cu o suprapunere a căii generate de generatorul de cale a corpului fizic.


Dacă un obiect atinge sau suprapune o limită a fizicii unui obiect, suntem informați despre acest eveniment. În jocul nostru, obiectele care pot atinge monștrii sunt rachetele primite. Să începem folosind căile generate pentru monștri.

Pentru a crea un corp fizic, trebuie să folosim a CGMutablePathRef structură, care reprezintă un traseu mutabil. Îl folosim pentru a defini conturul monștrilor din joc.

Revedeți addMonstersBetweenSpace: și să creați o traiectorie mutabilă pentru fiecare tip de monstru, după cum se arată mai jos. Amintiți-vă că în jocul nostru există două tipuri de monștri.

- (void) addMonstersBetweenSpace: (int) spaceOrder pentru (int i = 0; i< 3; i++)  int giveDistanceToMonsters = 60 * i -60; int randomMonster = [self getRandomNumberBetween:0 to:1]; SKSpriteNode *monster; CGMutablePathRef path = CGPathCreateMutable(); if (randomMonster == 0)  monster = [SKSpriteNode spriteNodeWithImageNamed:@"protectCreature4"]; CGFloat offsetX = monster.frame.size.width * monster.anchorPoint.x; CGFloat offsetY = monster.frame.size.height * monster.anchorPoint.y; CGPathMoveToPoint(path, NULL, 10 - offsetX, 1 - offsetY); CGPathAddLineToPoint(path, NULL, 42 - offsetX, 0 - offsetY); CGPathAddLineToPoint(path, NULL, 49 - offsetX, 13 - offsetY); CGPathAddLineToPoint(path, NULL, 51 - offsetX, 29 - offsetY); CGPathAddLineToPoint(path, NULL, 50 - offsetX, 42 - offsetY); CGPathAddLineToPoint(path, NULL, 42 - offsetX, 59 - offsetY); CGPathAddLineToPoint(path, NULL, 29 - offsetX, 67 - offsetY); CGPathAddLineToPoint(path, NULL, 19 - offsetX, 67 - offsetY); CGPathAddLineToPoint(path, NULL, 5 - offsetX, 53 - offsetY); CGPathAddLineToPoint(path, NULL, 0 - offsetX, 34 - offsetY); CGPathAddLineToPoint(path, NULL, 1 - offsetX, 15 - offsetY); CGPathCloseSubpath(path);  else  monster = [SKSpriteNode spriteNodeWithImageNamed:@"protectCreature2"]; CGFloat offsetX = monster.frame.size.width * monster.anchorPoint.x; CGFloat offsetY = monster.frame.size.height * monster.anchorPoint.y; CGPathMoveToPoint(path, NULL, 0 - offsetX, 1 - offsetY); CGPathAddLineToPoint(path, NULL, 47 - offsetX, 1 - offsetY); CGPathAddLineToPoint(path, NULL, 47 - offsetX, 24 - offsetY); CGPathAddLineToPoint(path, NULL, 40 - offsetX, 43 - offsetY); CGPathAddLineToPoint(path, NULL, 28 - offsetX, 53 - offsetY); CGPathAddLineToPoint(path, NULL, 19 - offsetX, 53 - offsetY); CGPathAddLineToPoint(path, NULL, 8 - offsetX, 44 - offsetY); CGPathAddLineToPoint(path, NULL, 1 - offsetX, 26 - offsetY); CGPathCloseSubpath(path);  monster.zPosition = 2; monster.position = CGPointMake(position * spaceOrder - giveDistanceToMonsters, monster.size.height / 2); [self addChild:monster];  

Cu calea gata de utilizare, trebuie să actualizăm monstrul physicsBody proprietate, precum și o serie de alte proprietăți. Uitați-vă la următorul fragment de cod pentru clarificare.

- (void) addMonstersBetweenSpace: (int) spaceOrder pentru (int i = 0; i< 3; i++)  //… // monster.physicsBody = [SKPhysicsBody bodyWithPolygonFromPath:path]; monster.physicsBody.dynamic = YES; monster.physicsBody.categoryBitMask = MonsterCategory; monster.physicsBody.contactTestBitMask = MissileCategory; monster.physicsBody.collisionBitMask = 1; monster.zPosition = 2; monster.position = CGPointMake(position * spaceOrder - giveDistanceToMonsters, monster.size.height / 2); [self addChild:monster];  

categoryBitMask și contactTestBitMask proprietățile physicsBody obiectul reprezintă o parte esențială și poate necesita explicații. categoryBitMask proprietate a physicsBody Obiectul definește categoriile în care aparține nodul. contactTestBitMask proprietatea definește care categorii de corpuri generează notificări de intersecție cu nodul. Cu alte cuvinte, aceste proprietăți definesc obiectele care se pot ciocni cu obiectele.

Pentru că configurăm nodurile monstru, am setat categoryBitMask la MonsterCategory și contactTestBitMask la MissileCategory. Acest lucru înseamnă că monștrii se pot ciocni cu rachete și acest lucru ne permite să detectăm când un monstru este lovit de o rachetă.

De asemenea, trebuie să ne actualizăm implementarea addMissilesFromSky:. Definirea corpului fizicii pentru rachete este mult mai ușoară, deoarece fiecare rachetă este circulară. Uitați-vă la implementarea actualizată de mai jos.

- (void) addMissilesFromSky: (CGSize) dimensiunea int numberMissiles = [get getRandomNumber între 0 și 3]; pentru (int i = 0; i < numberMissiles; i++)  SKSpriteNode *missile; missile = [SKSpriteNode spriteNodeWithImageNamed:@"enemyMissile"]; missile.scale = 0.6; missile.zPosition = 1; int startPoint = [self getRandomNumberBetween:0 to:size.width]; missile.position = CGPointMake(startPoint, size.height); missile.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:missile.size.height/2]; missile.physicsBody.dynamic = NO; missile.physicsBody.categoryBitMask = MissileCategory; missile.physicsBody.contactTestBitMask = ExplosionCategory | MonsterCategory; missile.physicsBody.collisionBitMask = 1; int endPoint = [self getRandomNumberBetween:0 to:size.width]; SKAction *move =[SKAction moveTo:CGPointMake(endPoint, 0) duration:15]; SKAction *remove = [SKAction removeFromParent]; [missile runAction:[SKAction sequence:@[move,remove]]]; [self addChild:missile];  

În acest moment, monștrii și rachetele din jocul nostru ar trebui să aibă un corp de fizică care să ne permită să detectăm când oricare dintre ele se ciocnește unul cu celălalt.

Provocare: Provocările pentru această secțiune sunt următoarele.

  • Citiți și înțelegeți SKPhysicsBody clasă.
  • Creați diferite corpuri de fizică pentru monștri.

2. Coliziuni și explozii

Coliziuni și explozii sunt două elemente care sunt strâns asociate. De fiecare dată când un glonț împușcat de o floare atinge destinația, atingerea utilizatorului, explodează. Această explozie poate provoca o coliziune între explozie și orice rachete din vecinătate.

Pentru a crea explozia când un glonț atinge ținta, avem nevoie de altul SKAction instanță. Acea SKAction instanța este responsabilă de două aspecte ale jocului, definește proprietățile exploziei și corpul fizicii exploziei.

Pentru a defini o explozie, trebuie să ne concentrăm asupra exploziei SKSpriteNode, este zPosition, scară, și poziţie. poziţie este locația atingerii utilizatorului.

Pentru a crea corpul fizicii exploziei, trebuie să setăm nodul physicsBody proprietate așa cum am făcut-o mai devreme. Nu uitați să setați corect categoryBitMask și contactTestBitMask proprietățile corpului fizicii. Creăm explozia în touchesBegan: așa cum se arată mai jos.

- (void) atingeBegan: (NSSet *) atinge cuEvent: (UIEvent *) eveniment pentru (UITouch * atinge în atinge) // ... // SKSpriteNode * bullet = [SKSpriteNode spriteNodeWithImageNamed: @ "flowerBullet"]; bullet.zPosition = 1; bullet.scale = 0.6; bullet.position = CGPointMake (bulletBeginning, 110); bullet.color = [SKColor redColor]; bullet.colorBlendFactor = 0.5; durata flotantului = (2 * location.y) /sizeGlobal.width; SKAction * move = [deplasare SKActionTo: CGPointMake (location.x, location.y) duration: duration]; SKAction * remove = [SKAction removeFromParent]; // Explozie SKAction * callExplosion = [SKAction runBlock: ^ SKSpriteNode * explozie = [SKSpriteNode spriteNodeWithImageNamed: @ "explozie"]; exploion.zPoziția = 3; explosion.scale = 0,1; explozie.locație = CGPointMake (location.x, location.y); explosion.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius: explosion.size.height / 2]; explosion.physicsBody.dynamic = DA; explosion.physicsBody.categoryBitMask = ExplosionCategory; explosion.physicsBody.contactTestBitMask = MissileCategory; explosion.physicsBody.collisionBitMask = 1; SKAction * explosionAction = [Scala SKActionTo: 0.8 Durata: 1.5]; [explozie runAction: [secvența SKAction: @ [explosionAction, remove]]; [self addChild: explozie]; ]; [bullet runAction: [secvența SKAction: @ [move, callExplosion, remove]]; [self addChild: bullet]; 

În touchesBegan:, am actualizat glonţacțiunea. Noua acțiune trebuie să apeleze callExplosion înainte de a fi scos din scenă. Pentru a realiza acest lucru, am actualizat următoarea linie de cod în touchesBegan:.

[bullet runAction: [secvența SKAction: @ [move, remove]];

Secvența acțiunii conține acum callExplosion așa cum se arată mai jos.

[bullet runAction: [secvența SKAction: @ [move, callExplosion, remove]];

Construiți proiectul și rulați aplicația pentru a vedea rezultatul activității noastre. După cum puteți vedea, trebuie să detectăm coliziunile dintre explozii și rachetele care intră. Aici este locul unde SKPhysicsContactDelegate protocolul intră în joc.

Există o metodă delegată care este de interes special pentru noi, didBeginContact: metodă. Această metodă ne va spune când are loc o coliziune între o explozie și o rachetă. didBeginContact: metoda are un argument, o instanță a SKPhysicsContact clasa, care ne spune tot ce trebuie să știm despre coliziune. Permiteți-mi să vă explic cum funcționează.

Un SKPhysicsContact instanța are a bodyA și a bodyB proprietate. Fiecare organism indică un corp de fizică implicat în coliziune. Cand didBeginContact: este invocată, trebuie să detectăm ce tip de coliziune avem de-a face. Poate fi (1) o coliziune între o explozie și o rachetă sau (2) o coliziune între o rachetă și un monstru. Detectăm tipul coliziunii prin inspectarea categoryBitmask proprietatea organismelor de fizică ale SKPhysicsContact instanță.

Identificarea tipului de coliziune cu care ne confruntăm este destul de ușoară datorită categoryBitmask proprietate. Dacă bodyA sau bodyB are o categoryBitmask de tip ExplosionCategory, atunci știm că este o coliziune între o explozie și o rachetă. Uitați-vă la fragmentul de cod de mai jos pentru clarificare.

- (void) didBeginContact: (SKPhysicsContact *) Contact if ((contact.bodyA.categoryBitMask & ExplosionCategory)! = 0 || (contact.bodyB.categoryBitMask & ExplosionCategory)! = 0) NSLog (@ EXPLOSION HIT);  altceva NSLog (@ "MONSTER HIT"); 

Dacă am întâlnit o coliziune între o explozie și o rachetă, atunci apucăm nodul care este asociat cu corpul fizicii rachetei. De asemenea, trebuie să atribuim o acțiune nodului, care va fi executat atunci când glonțul lovește racheta. Sarcina acțiunii este de a scoate racheta din scenă. Rețineți că nu eliminăm imediat explozia din scenă, deoarece ar putea să distrugă alte rachete din apropiere.

Când o rachetă este distrusă, noi creștem missileExploded și modificați eticheta care afișează numărul de rachete pe care le-a distrus până acum. Dacă jucătorul a distrus douăzeci de rachete, câștigă jocul.

- (void) didBeginContact: (SKPhysicsContact *) Contact if ((contact.bodyA.categoryBitMask & ExplosionCategory)! = 0 || (contact.bodyB.categoryBitMask & ExplosionCategory)! = 0) // Collision Between Explosion and Missile SKNode * racheta = (contact.bodyA.categoryBitMask & ExplosionCategory)? contact.bodyB.nod: contact.bodyA.node; [rachetă runAction: [SKAction removeFromParent]]; // explozia continuă, deoarece poate ucide mai multe rachete NSLog (@ "Missile destroyed"); // Actualizarea rachetei explodate cu racheteExplodate ++; [labelMissilesExploded setText: [NSString șirWithFormat: @ "Rachete explodate:% d", rachetăExplodată]]; dacă (rachetăExploded == 20) SKLabelNode * ganhou = [SKLabelNode labelNodeWithFontNamed: @ "Hiragino-Kaku-Gothic-ProN"]; ganhou.text = @ "Câștigi!"; ganhou.fontSize = 60; ganhou.position = CGPointMake (dimensiuneGlobal.width / 2, sizeGlobal.height / 2); ganhou.zPosition = 3; [auto adăugaChild: ganhou];  altfel // Coliziunea dintre rachete și monstru

Dacă avem de-a face cu o coliziune între o rachetă și un monstru, eliminăm nodul de rachetă și monstru din scenă prin adăugarea unei acțiuni [SKAction removeFromParent] la lista de acțiuni executate de nod. De asemenea, creștem valoarea monstersDead și verificați dacă este egală cu 6. Dacă este, jucătorul a pierdut jocul și vom afișa un mesaj care le spune că jocul sa terminat.

- (void) didBeginContact: (SKPhysicsContact *) contact if (contact.bodyA.categoryBitMask & ExplosionCategory)! = 0 || (contact.bodyB.categoryBitMask & ExplosionCategory)! = 0) // Coliziunea între explozie și rachetă ... // altceva // Coliziunea dintre rachete și monstru SKNode * monster = (contact.bodyA.categoryBitMask & MonsterCategory)? contact.bodyA.node: contact.bodyB.node; SKNode * rachetă = (contact.bodyA.categoryBitMask & MonsterCategory)? contact.bodyB.nod: contact.bodyA.node; [rachetă runAction: [SKAction removeFromParent]]; [runAction monstru: [SKAction removeFromParent]]; NSLog (@ "Monster ucis"); monstersDead ++; dacă (monstersDead == 6) SKLabelNode * perdeu = [SKLabelNode labelNodeWithFontNamed: @ "Hiragino-Kaku-Gothic-ProN"]; perdeu.text = @ "Pierzi!"; perdeu.fontSize = 60; perdeu.position = CGPointMake (sizeGlobal.width / 2, sizeGlobal.height / 2); perdeu.zPoziția = 3; [auto addChild: perdeu]; [self moveToMenu]; 

Înainte de a rula jocul pe iPad, trebuie să implementăm moveToMenu metodă. Această metodă este invocată atunci când jucătorul pierde jocul. În moveToMenu, jocul trece înapoi în scena meniului, astfel încât jucătorul să poată începe un nou joc. Nu uitați să adăugați o declarație de import pentru MenuScene clasă.

- (void) moveToMenu SKTransition * tranziție = [SKTransition fadeWithDuration: 2]; MenuScene * myscene = [[MeniuScene alocare] initWithSize: CGSizeMake (CGRectGetMaxX (self.frame), CGRectGetMaxY (auto.frame)); [self.scene.view presentScene: tranziție myscene: tranziție]; 
#import "MyScene.h" #import "MenuScene.h" @interface MyScene () // ... // @end

Este timpul să construiți proiectul și să conduceți jocul pentru a vedea rezultatul final.

Provocare: Provocările pentru această secțiune sunt următoarele.

  • Modificați regulile jocului modificând numărul de monștri și gloanțe.
  • Faceți jocul mai provocator prin modificarea dinamicii jocului. Ați putea, de exemplu, să creșteți viteza rachetelor odată ce ați folosit cinci gloanțe.

3. Multiplayer

În modul multi-player al jocului, doi jucători se pot contesta unul pe celălalt printr-un mod de difuzare pe ecran. Modul multi-player nu schimbă jocul în sine. Principalele diferențe dintre modurile single-player și multi-player sunt enumerate mai jos.

  • Avem nevoie de două seturi de active.
  • Poziția și orientarea activelor trebuie actualizate.
  • Trebuie să implementăm logica jocului pentru cel de-al doilea jucător.
  • Exploziile trebuie să fie testate și capturate pe baza unei explozii.
  • Doar un jucător poate câștiga jocul.

În modul multiplayer, jocul ar trebui să arate ca imaginea de mai jos.


Aceasta este provocarea finală a acestui tutorial. Nu este atât de complicat cum pare. Scopul provocării este de a recrea comandamentul de rachete prin activarea modului multi-player. Fișierele sursă ale acestui tutorial conțin două proiecte Xcode, dintre care unul (racheta de comandă multi-player) conține o implementare incompletă a modului multi-player pentru a vă începe cu această provocare. Rețineți că MultiScene clasa este incompletă și este sarcina dvs. să finalizați implementarea pentru a finaliza cu succes provocarea. Veți găsi sugestii și comentarii (/ * Lucrați AICI - CODUL ESTE LIPSĂ * /) pentru a vă ajuta cu această provocare.

Nu este nevoie să adăugați metode suplimentare sau variabile de instanță pentru a finaliza provocarea. Trebuie doar să vă concentrați asupra implementării logicii pentru modul multiplayer.

Imaginea următoare vă arată starea curentă a modului multi-player.



Concluzie

Am acoperit o mulțime de teren în această serie scurtă pe Sprite Kit. Acum ar trebui să puteți crea jocuri asemănătoare cu comanda rachetelor folosind cadrul Sprite Kit. Dacă aveți întrebări sau comentarii, nu ezitați să ne trimiteți o linie în comentarii.

Cod