Build Missile Command met Sprite Kit User Interaction

In de vorige tutorial hebben we de basis gelegd voor onze Missile Command-game door het project te maken, de singleplayer-scène in te stellen en gebruikersinteractie toe te voegen. In deze zelfstudie breidt u de gamebeleving uit door een modus voor meerdere spelers toe te voegen, evenals natuurkunde, botsingen en explosies.


Laatste voorbeeld

Bekijk de volgende screenshot om een ​​idee te krijgen van waar we naar streven.



Ga verder waar we zijn gebleven

Als je dit nog niet hebt gedaan, raden we je ten zeerste aan de vorige zelfstudie te voltooien om ervoor te zorgen dat we kunnen voortbouwen op de basis die we in de eerste zelfstudie hebben gelegd. In deze zelfstudie zoomen we in op een aantal onderwerpen, zoals fysica, botsingen, explosies en het toevoegen van een multi-player modus.


1. Fysica inschakelen

Het Sprite Kit-framework bevat een physics-engine die fysieke objecten simuleert. De physics engine van het Sprite Kit-framework werkt via de SKPhysicsContactDelegate protocol. Om de physics engine in onze game mogelijk te maken, moeten we de Mijn scene klasse. Begin door het header-bestand bij te werken zoals hieronder is aangegeven om de compiler het te laten weten SKScene klas voldoet aan de SKPhysicsContactDelegate protocol.

#importeren  @interface MyScene: SKScene  @einde

De SKPhysicsContactDelegate protocol stelt ons in staat om te detecteren of twee objecten met elkaar in botsing zijn gekomen. De Mijn scene instantie moet het SKPhysicsContactDelegate protocol als het op de hoogte wil worden gebracht van botsingen tussen objecten. Een object dat het protocol implementeert, wordt op de hoogte gebracht wanneer een botsing begint en eindigt.

Omdat we te maken hebben met explosies, raketten en monsters, definiëren we een categorie voor elk type fysiek object. Voeg het volgende codefragment toe aan het headerbestand van de Mijn scene klasse.

#importeren  typedef enum: NSUInteger ExplosionCategory = (1 << 0), MissileCategory = (1 << 1), MonsterCategory = (1 << 2)  NodeCategory; @interface MyScene : SKScene  @einde

Voordat we de physics-engine van het Sprite Kit-framework kunnen verkennen, moeten we de zwaartekracht eigendom van de fysische wereld evenals zijn contactDelegate. Werk het initWithSize: methode zoals hieronder getoond.

- (id) initWithSize: (CGSize) size if (self = [super initWithSize: size]) self.backgroundColor = [SKColor colorWithRed: (198.0 / 255.0) groen: (220.0 / 255.0) blauw: (54.0 / 255.0) alpha : 1.0]; // ... // // Fysica Wereld configureren self.physicsWorld.gravity = CGVectorMake (0, 0); self.physicsWorld.contactDelegate = self;  terugkeer zelf; 

In ons spel wordt de physics-engine gebruikt om drie soorten fysicalichamen, kogels, raketten en monsters te maken. Wanneer u met het Sprite Kit-framework werkt, gebruikt u dynamische en statische volumes om fysieke objecten te simuleren. Een volume voor een groep objecten is een volume dat elk object van de groep bevat. Dynamische en statische volumes zijn een belangrijk element voor het verbeteren van de prestaties van de physics engine, vooral bij het werken met complexe objecten. In onze game definiëren we twee soorten volumes, cirkels met een vaste straal en aangepaste objecten.

Terwijl cirkels beschikbaar zijn via de SKPhysicsBody Klasse, aangepast object vereist een beetje extra werk van onze kant. Omdat het lichaam van een monster niet cirkelvormig is, moeten we er een aangepast volume voor maken. Om deze taak een beetje gemakkelijker te maken, zullen we een physics body path generator gebruiken. De tool is eenvoudig te gebruiken. Importeer de sprites van uw project en definieer het insluitende pad voor elke sprite. De Objective-C-code om het pad opnieuw aan te maken, wordt onder de sprite weergegeven. Bekijk als voorbeeld de volgende sprite.


De volgende schermafbeelding toont dezelfde sprite met een overlay van het pad gegenereerd door de physics body path generator.


Als een object de fysica-grens van een object raakt of overlapt, worden we op de hoogte gebracht van deze gebeurtenis. In onze game zijn de objecten die de monsters kunnen raken de binnenkomende raketten. Laten we beginnen met het gebruik van de gegenereerde paden voor de monsters.

Om een ​​fysisch lichaam te creëren, moeten we een CGMutablePathRef structuur, die een veranderlijk pad vertegenwoordigt. We gebruiken het om de contouren van de monsters in het spel te definiëren.

opnieuw bezoeken addMonstersBetweenSpace: en maak een veranderlijk pad voor elk monstertype, zoals hieronder weergegeven. Vergeet niet dat er twee soorten monsters in onze game zitten.

- (void) addMonstersBetweenSpace: (int) spaceOrder for (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];  

Met het pad klaar voor gebruik, moeten we de monster's updaten physicsBody eigendom evenals een aantal andere eigenschappen. Bekijk het volgende codefragment ter verduidelijking.

- (void) addMonstersBetweenSpace: (int) spaceOrder for (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];  

De categoryBitMask en contactTestBitMask eigenschappen van de physicsBody objecten zijn een essentieel onderdeel en hebben misschien nog wat uitleg nodig. De categoryBitMask eigendom van de physicsBody object definieert tot welke categorieën het knooppunt behoort. De contactTestBitMask eigenschap definieert welke categorieën van instanties kruispuntmeldingen met het knooppunt veroorzaken. Met andere woorden, deze eigenschappen bepalen welke objecten kunnen botsen met welke objecten.

Omdat we de knooppunten van het monster configureren, hebben we de categoryBitMask naar MonsterCategory en contactTestBitMask naar MissileCategory. Dit betekent dat monsters kunnen botsen met raketten en dit stelt ons in staat om te detecteren wanneer een monster wordt geraakt door een raket.

We moeten ook onze implementatie van updaten addMissilesFromSky:. Het definiëren van het fysicalichaam voor de raketten is veel gemakkelijker omdat elke raket cirkelvormig is. Bekijk de bijgewerkte implementatie hieronder.

- (void) addMissilesFromSky: (CGSize) size int numberMissiles = [self getRandomNumberBtween: 0 to: 3]; voor (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];  

Op dit punt moeten de monsters en raketten in ons spel een fysisch lichaam hebben dat ons in staat stelt te detecteren wanneer een van hen tegen elkaar botst.

Uitdaging: De uitdagingen voor deze sectie zijn als volgt.

  • Lees en begrijp het SKPhysicsBody klasse.
  • Maak verschillende fysicalichamen voor de monsters.

2. Botsingen en explosies

Botsingen en explosies zijn twee elementen die nauw met elkaar zijn verbonden. Elke keer als een kogel geschoten door een bloem zijn bestemming bereikt, ontploft de gebruiker. Die explosie kan een botsing veroorzaken tussen de explosie en eventuele raketten in de buurt.

Om de explosie te creëren wanneer een kogel zijn doel bereikt, hebben we een andere nodig SKAction aanleg. Dat SKAction instance heeft de leiding over twee aspecten van het spel, definieert de eigenschappen van de explosie en de fysica van de explosie.

Om een ​​explosie te definiëren, moeten we ons concentreren op de explosies SKSpriteNode, haar zPosition, schaal, en positie. De positie is de locatie van de aanraking van de gebruiker.

Om de fysica van de explosie te creëren, moeten we de knooppunten instellen physicsBody eigendom zoals we eerder deden. Vergeet niet om de categoryBitMask en contactTestBitMask eigenschappen van het fysische lichaam. We creëren de explosie in touchesBegan: zoals hieronder getoond.

- (ongeldig) raaktBegan: (NSSet *) raakt aan metEvent: (UIEvent *) -gebeurtenis for (UITouch * touch in aanraking) // ... // 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; zweefduur = (2 * location.y) /sizeGlobal.width; SKAction * move = [SKAction moveTo: CGPointMake (location.x, location.y) duration: duration]; SKAction * remove = [SKAction removeFromParent]; // Explosion SKAction * callExplosion = [SKAction runBlock: ^ SKSpriteNode * explosion = [SKSpriteNode spriteNodeWithImageNamed: @ "explosion"]; explosion.zPosition = 3; explosion.scale = 0,1; explosion.position = CGPointMake (location.x, location.y); explosion.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius: explosion.size.height / 2]; explosion.physicsBody.dynamic = YES; explosion.physicsBody.categoryBitMask = ExplosionCategory; explosion.physicsBody.contactTestBitMask = MissileCategory; explosion.physicsBody.collisionBitMask = 1; SKAction * explosionAction = [SKAction scaleTo: 0.8 duration: 1.5]; [explosie runActie: [SKAction sequence: @ [explosionAction, remove]]]; [self addChild: explosion]; ]; [bullet runAction: [SKAction-reeks: @ [move, callExplosion, remove]]]; [self addChild: bullet]; 

In touchesBegan:, we hebben het kogelactie. De nieuwe actie moet de callExplosion actie voordat deze van de scène wordt verwijderd. Om dit te bereiken, hebben we de volgende regel code bijgewerkt touchesBegan:.

[bullet runAction: [SKAction-reeks: @ [verplaatsen, verwijderen]]];

De volgorde van de actie bevat nu callExplosion zoals hieronder getoond.

[bullet runAction: [SKAction-reeks: @ [move, callExplosion, remove]]];

Bouw het project en voer de applicatie uit om het resultaat van ons werk te zien. Zoals je kunt zien, moeten we nog steeds botsingen detecteren tussen de explosies en de binnenkomende raketten. Dit is waar de SKPhysicsContactDelegate protocol komt om de hoek kijken.

Er is één gedelegeerde methode die voor ons van bijzonder belang is, de didBeginContact: methode. Deze methode zal ons vertellen wanneer een botsing tussen een explosie en een raket plaatsvindt. De didBeginContact: methode neemt een argument, een exemplaar van de SKPhysicsContact klasse, die ons alles vertelt wat we moeten weten over de botsing. Laat me uitleggen hoe dit werkt.

Een SKPhysicsContact instantie heeft een bodyâ en een bodyB eigendom. Elk lichaam wijst naar een natuurkundig lichaam dat betrokken is bij de botsing. Wanneer didBeginContact: wordt aangeroepen, moeten we detecteren met wat voor soort botsing we te maken hebben. Het kan zijn (1) een botsing tussen een explosie en een raket of (2) een botsing tussen een raket en een monster. We detecteren het botsingstype door de categoryBitmask eigendom van de fysische lichamen van de SKPhysicsContact aanleg.

Het vinden van het soort botsing waar we mee te maken hebben, is vrij eenvoudig dankzij de categoryBitmask eigendom. Als bodyâ of bodyB heeft een categoryBitmask van type ExplosionCategory, dan weten we dat het een botsing is tussen een explosie en een raket. Bekijk het onderstaande codefragment voor meer informatie.

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

Als we een botsing tussen een explosie en een raket hebben tegengekomen, pakken we de knoop die geassocieerd is met het fysische lichaam van de raket. We moeten ook een actie toewijzen aan het knooppunt, dat zal worden uitgevoerd wanneer de kogel de raket raakt. De taak van de actie is om de raket van de scène te verwijderen. Merk op dat we de explosie niet onmiddellijk uit de scène verwijderen, omdat het andere raketten in de nabije omgeving kan vernietigen.

Wanneer een raket wordt vernietigd, verhogen we de missileExploded instantievariabele en update het label dat het aantal raketten weergeeft dat de speler tot nu toe heeft vernietigd. Als de speler twintig raketten heeft vernietigd, winnen ze het spel.

- (void) didBeginContact: (SKPhysicsContact *) contact if ((contact.bodyA.categoryBitMask & ExplosionCategory)! = 0 || (contact.bodyB.categoryBitMask & ExplosionCategory)! = 0) // Botsing tussen explosie en raket SKNode * raket = (contact.bodyA.categoryBitMask & ExplosionCategory)? contact.bodyB.node: contact.bodyA.node; [raket runActie: [SKAction removeFromParent]]; // de explosie gaat door, omdat meer dan één raket NSLog (@ "Raket vernietigd") kan worden gedood; // Update Missile Exploded missileExploded ++; [labelMissilesExploded setText: [NSString stringWithFormat: @ "Missiles Exploded:% d", missileExploded]]; if (missileExploded == 20) SKLabelNode * ganhou = [SKLabelNode labelNodeWithFontNamed: @ "Hiragino-Kaku-Gothic-ProN"]; ganhou.text = @ "Je wint!"; ganhou.fontSize = 60; ganhou.position = CGPointMake (sizeGlobal.width / 2, sizeGlobal.height / 2); ganhou.zPosition = 3; [self addChild: ganhou];  else // Botsing tussen raket en monster

Als we te maken hebben met een botsing tussen een raket en een monster, verwijderen we het raket- en monsterknooppunt uit de scène door een actie toe te voegen [SKAction removeFromParent] naar de lijst met acties die door het knooppunt worden uitgevoerd. We verhogen ook de monstersDead instantievariabele en controleer of deze gelijk is aan 6. Als dat zo is, heeft de speler het spel verloren en geven we een bericht weer dat de game voorbij is.

- (void) didBeginContact: (SKPhysicsContact *) contact if ((contact.bodyA.categoryBitMask & ExplosionCategory)! = 0 || (contact.bodyB.categoryBitMask & ExplosionCategory)! = 0) // Botsing tussen explosie en raket // ... // else // Collision Between Missile and Monster SKNode * monster = (contact.bodyA.categoryBitMask & MonsterCategory)? contact.bodyA.node: contact.bodyB.node; SKNode * raket = (contact.bodyA.categoryBitMask & MonsterCategory)? contact.bodyB.node: contact.bodyA.node; [raket runActie: [SKAction removeFromParent]]; [monster runAction: [SKAction removeFromParent]]; NSLog (@ "Monster gedood"); monstersDead ++; if (monstersDead == 6) SKLabelNode * perdeu = [SKLabelNode labelNodeWithFontNamed: @ "Hiragino-Kaku-Gothic-ProN"]; perdeu.text = @ "You Lose!"; perdeu.fontSize = 60; perdeu.position = CGPointMake (sizeGlobal.width / 2, sizeGlobal.height / 2); perdeu.zPosition = 3; [self addChild: perdeu]; [self moveToMenu]; 

Voordat u het spel op uw iPad uitvoert, moeten we het moveToMenu methode. Deze methode wordt aangeroepen wanneer de speler het spel verliest. In moveToMenu, het spel gaat terug naar de menuscène zodat de speler een nieuw spel kan starten. Vergeet niet om een ​​importinstructie toe te voegen voor de MenuScene klasse.

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

Het is tijd om het project te bouwen en het spel uit te voeren om het eindresultaat te zien.

Uitdaging: De uitdagingen voor deze sectie zijn als volgt.

  • Verander de spelregels door het aantal monsters en kogels te wijzigen.
  • Maak het spel uitdagender door de dynamiek van het spel aan te passen. Je zou bijvoorbeeld de snelheid van de raketten kunnen verhogen als je eenmaal vijf kogels hebt gebruikt.

3. Multi-speler

In de multi-player-modus van het spel kunnen twee spelers elkaar uitdagen via een modus met gesplitst scherm. De multiplayer-modus verandert het spel zelf niet. De belangrijkste verschillen tussen de modi voor één speler en meerdere spelers staan ​​hieronder vermeld.

  • We hebben twee reeksen activa nodig.
  • De positie en oriëntatie van de activa moeten worden bijgewerkt.
  • We moeten spellogica implementeren voor de tweede speler.
  • Explosies moeten per explosie worden getest en gevangen.
  • Slechts één speler kan het spel winnen.

In de multi-player-modus moet het spel er uitzien als de onderstaande schermafbeelding.


Dit is de laatste uitdaging van deze tutorial. Het is niet zo ingewikkeld als het lijkt. Het doel van de uitdaging is om Missile Command opnieuw te maken door de multi-player-modus in te schakelen. De bronbestanden van deze zelfstudie bevatten twee Xcode-projecten, waarvan er één (Missile Command Multi-Player) een onvolledige implementatie van de multiplayermodus bevat om u op weg te helpen met deze uitdaging. Merk op dat de MultiScene De les is onvolledig en het is jouw taak om de implementatie af te maken om de uitdaging met succes af te ronden. U vindt hints en opmerkingen (/ * Werk hier - CODE IS ONTBREEKBAAR * /) om u te helpen met deze uitdaging.

U hoeft geen extra methoden of instantievariabelen toe te voegen om de uitdaging te voltooien. U hoeft zich alleen te concentreren op het implementeren van de logica voor de multi-player-modus.

Het volgende screenshot toont u de huidige status van de multi-player-modus.



Conclusie

We hebben veel aandacht besteed aan deze korte serie over Sprite Kit. Je zou nu in staat moeten zijn om games te maken die lijken op Missile Command met behulp van het Sprite Kit-framework. Als u vragen of opmerkingen heeft, kunt u ons een bericht sturen in de opmerkingen.