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.
Bekijk de volgende screenshot om een idee te krijgen van waar we naar streven.
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.
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.
#importerentypedef 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.
SKPhysicsBody
klasse.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 kogel
actie. 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.
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.
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.
Het volgende screenshot toont u de huidige status van de multi-player-modus.
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.