Î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.
Uitați-vă la următoarea captură de ecran pentru a obține o idee despre ceea ce urmărim.
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.
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ă.
#importtypedef 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.
SKPhysicsBody
clasă.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.
Î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.
Î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.
Imaginea următoare vă arată starea curentă a modului multi-player.
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.