In questo post costruiremo un semplice gioco da zero. Lungo il percorso, toccheremo alcuni degli aspetti più importanti della libreria SpriteKit.
Questo post si basa su ciò che abbiamo appreso in precedenza nella serie SpriteKit Basics. Se vuoi aggiornare la tua conoscenza di SpriteKit, dai un'occhiata ad alcuni dei miei altri post.
Apri Xcode e avvia un nuovo progetto dal menu File > Nuovo > Progetto. Assicurarsi iOS è selezionato e scelto Gioco come modello.
Dai un nome al tuo progetto e assicurati che linguaggio è impostato per veloce, Tecnologia di gioco è impostato per SpriteKit, e dispositivi è impostato per iPad.
Una delle prime cose che mi piace fare quando creo un progetto è determinare quante scene mi serviranno per il progetto. Di solito ho almeno tre scene: una scena di introduzione, una scena di gioco principale e una scena per mostrare i punteggi più alti, ecc.
Per questo esempio, abbiamo solo bisogno di un'introduzione e una scena di gameplay principale poiché non terremo traccia delle vite, dei punteggi, ecc. SpriteKit è già dotato di una scena quando si crea un nuovo progetto, quindi abbiamo solo bisogno di una scena di introduzione.
Dal menu di Xcode, scegli File > Nuovo > File. Assicurarsi iOS è selezionato e scegliere Cocoa Touch Class.
Dai un nome alla classe StartGameScene, e assicurati che Sottoclasse di è impostato per SKScene e linguaggio è impostato per veloce.
Aperto GameViewController.swift. Elimina tutto in quel file e sostituiscilo con quanto segue.
import UIKit import SpriteKit import GameplayKit class GameViewController: UIViewController override func viewDidLoad () super.viewDidLoad () let scene = StartGameScene (size: view.bounds.size) lascia skView = self.view as! SKView skView.showsFPS = false skView.showsNodeCount = false skView.ignoresSiblingOrder = false scene.scaleMode = .aspectFill skView.presentScene (scena) sostituisce var prefersStatusBarHidden: Bool return true
Quando crei un nuovo progetto, GameViewController.swift è impostato per caricare GameScene.sks dal disco. GameScene.sks è usato insieme all'editor di scene incorporato di SpriteKit, che ti consente di disegnare visivamente i tuoi progetti. Non useremo GameScene.sks, e creerò invece tutto dal codice, così qui iniziamo una nuova istanza di StartGameScene e presentarlo.
Aggiungi quanto segue al nuovo creato StartGameScene.swift.
import UIKit import SpriteKit class StartGameScene: SKScene override func didMove (per visualizzare: SKView) scene? .backgroundColor = .blue let logo = SKSpriteNode (imageNamed: "bigplane") logo.position = CGPoint (x: size.width / 2 , y: size.height / 2) addChild (logo) let newGameBtn = SKSpriteNode (imageNamed: "newgamebutton") newGameBtn.position = CGPoint (x: size.width / 2, y: size.height / 2 - 350) newGameBtn. name = "newgame" addChild (newGameBtn) sostituisce func toccaBegan (_ tocca: Imposta, con evento: UIEvent?) guard let touch = touches.first else return let touchLocation = touch.location (in: self) let touchedNode = self.atPoint (touchLocation) if (touchedNode.name == "newgame") let newScene = GameScene (size: size) newScene.scaleMode = scaleMode view? .presentScene (newScene)
Questa scena è piuttosto semplice. Nel didMove
metodo, aggiungiamo un logo e un pulsante. Quindi, in touchesBegan
, rileviamo i tocchi sul nuovo pulsante di gioco e rispondiamo caricando la scena principale GameScene
.
La prossima cosa che mi piace fare quando creo un nuovo gioco è decidere quali classi mi serviranno. Posso dire subito che avrò bisogno di un Giocatore
classe e un Nemico
classe. Entrambe queste classi si estenderanno SKSpriteNode
. Penso che per questo progetto creeremo il giocatore e i proiettili nemici direttamente dalle loro rispettive classi. Potresti creare classi separate per proiettili e proiettili nemici se preferisci, e ti suggerisco di provare a farlo come esercizio da solo.
Infine, ci sono le isole. Questi non hanno alcuna funzionalità specifica ma per spostarsi verso il basso sullo schermo. In questo caso, dal momento che sono solo decorazioni, penso che sia anche bene non creare una classe, e invece crearli nella parte principale GameScene
.
Giocatore
ClasseDal menu di Xcode, scegli File > Nuovo > File. Assicurarsi iOS è selezionato e scelto Cocoa Touch Class.
Assicurati che Classe è impostato per Giocatore, Sottoclasse di: è impostato per SKSpriteNode, e linguaggio è impostato per veloce.
Ora aggiungi il seguente a Player.swift.
import UIKit import SpriteKit class Player: SKSpriteNode private var canFire = true private var invincible = false private var lives: Int = 3 didSet if (lives < 0) kill() else respawn() init() let texture = SKTexture(imageNamed: "player") super.init(texture: texture, color: .clear, size: texture.size()) self.physicsBody = SKPhysicsBody(texture: self.texture!,size:self.size) self.physicsBody?.isDynamic = true self.physicsBody?.categoryBitMask = PhysicsCategories.Player self.physicsBody?.contactTestBitMask = PhysicsCategories.Enemy | PhysicsCategories.EnemyBullet self.physicsBody?.collisionBitMask = PhysicsCategories.EdgeBody self.physicsBody?.allowsRotation = false generateBullets() required init?(coder aDecoder: NSCoder) super.init(coder: aDecoder) func die () if(invincible == false) lives -= 1 func kill() let newScene = StartGameScene(size: self.scene!.size) newScene.scaleMode = self.scene!.scaleMode let doorsClose = SKTransition.doorsCloseVertical(withDuration: 2.0) self.scene!.view?.presentScene(newScene, transition: doorsClose) func respawn() invincible = true let fadeOutAction = SKAction.fadeOut(withDuration: 0.4) let fadeInAction = SKAction.fadeIn(withDuration: 0.4) let fadeOutIn = SKAction.sequence([fadeOutAction,fadeInAction]) let fadeOutInAction = SKAction.repeat(fadeOutIn, count: 5) let setInvicibleFalse = SKAction.run self.invincible = false run(SKAction.sequence([fadeOutInAction,setInvicibleFalse])) func generateBullets() let fireBulletAction = SKAction.run [weak self] in self?.fireBullet() let waitToFire = SKAction.wait(forDuration: 0.8) let fireBulletSequence = SKAction.sequence([fireBulletAction,waitToFire]) let fire = SKAction.repeatForever(fireBulletSequence) run(fire) func fireBullet() let bullet = SKSpriteNode(imageNamed: "bullet") bullet.position.x = self.position.x bullet.position.y = self.position.y + self.size.height/2 bullet.physicsBody = SKPhysicsBody(rectangleOf: bullet.size) bullet.physicsBody?.categoryBitMask = PhysicsCategories.PlayerBullet bullet.physicsBody?.allowsRotation = false scene?.addChild(bullet) let moveBulletAction = SKAction.move(to: CGPoint(x:self.position.x,y:(scene?.size.height)! + bullet.size.height), duration: 1.0) let removeBulletAction = SKAction.removeFromParent() bullet.run(SKAction.sequence([moveBulletAction,removeBulletAction]))
All'interno del dentro()
metodo, abbiamo impostato il physicsBody
e invocare generateBullets ()
. Il generateBullets
metodo chiama ripetutamente fireBullet ()
, che crea un proiettile, imposta il suo physicsBody
, e lo sposta lungo lo schermo.
Quando il giocatore perde una vita, il respawn ()
il metodo è invocato. All'interno del respawn
metodo, sbiadiamo l'aereo dentro e fuori cinque volte, durante i quali il giocatore sarà invincibile. Uno il giocatore ha esaurito tutte le vite, il uccidere()
il metodo è invocato. Il metodo kill semplicemente carica il StartGameScene
.
Scegliere File > Nuovo > File dal menu di Xcode. Assicurarsi iOS è selezionato e scelto Cocoa Touch Class.
Assicurati che Classe è impostato per Nemico, Sottoclasse di: è impostato per SKSpriteNode, e linguaggio è impostato per veloce.
Aggiungi il seguente a Enemy.swift.
import UIKit import SpriteKit class Enemy: SKSpriteNode init () let texture = SKTexture (imageNamed: "enemy1") super.init (trama: texture, colore: .clear, dimensione: texture.size ()) self.name = " nemico "self.physicsBody = SKPhysicsBody (trama: self.texture !, dimensione: self.size) self.physicsBody? .isDynamic = true self.physicsBody? .categoryBitMask = PhysicsCategories.Enemy self.physicsBody? .contactTestBitMask = PhysicsCategories.Player | PhysicsCategories.PlayerBullet self.physicsBody? .AllowsRotation = false move () generateBullets () init necessario (coder aDecoder: NSCoder) super.init (coder: aDecoder) func fireBullet () let bullet = SKSpriteNode (imageNamed: " bullet ") bullet.position.x = self.position.x bullet.position.y = self.position.y - bullet.size.height * 2 bullet.physicsBody = SKPhysicsBody (rectangleOf: bullet.size) bullet.physicsBody ?. categoryBitMask = PhysicsCategories.EnemyBullet bullet.physicsBody? .allowsRotation = false scene? .addChild (bullet) let moveBulletAction = SKAction.move (a: CGPoint (x: self.position.x, y: 0 - bullet.size.height), duration: 2.0) let removeBulletAction = SKAction.removeFromParent () bullet.run (SKAction.sequence ([moveBulletAction, removeBulletAction])) func move () let moveEnemyAction = SKAction.moveTo (y: 0 - self.size.height, duration: 12.0) let removeEnemyAction = SKAction.removeFromParent () let moveEnemySequence = SKAction.sequence ([moveEnemyAction, removeEnemyAction]) run (moveEn emySequence) func generateBullets () let fireBulletAction = SKAction.run [self debole] in self? .fireBullet () let waitToFire = SKAction.wait (forDuration: 1.5) let fireBulletSequence = SKAction.sequence ([fireBulletAction, waitToFire] ) let fire = SKAction.repeatForever (fireBulletSequence) run (fire)
Questa classe è molto simile al Giocatore
classe. Abbiamo impostato il suo physicsBody
e invocare generateBullets ()
. Il mossa()
sposta semplicemente il nemico sullo schermo.
Elimina tutto all'interno GameScene.swift e aggiungi il seguente.
import SpriteKit import GameplayKit importa la classe CoreMotion GameScene: SKScene, SKPhysicsContactDelegate let player = Player () lascia motionManager = CMMotionManager () var accelerationX: CGFloat = 0.0 funzione override didMove (per visualizzare: SKView) physicsWorld.gravity = CGVector (dx: 0.0 , dy: 0.0) self.physicsWorld.contactDelegate = self scene? .backgroundColor = .blue physicsBody = SKPhysicsBody (edgeLoopFrom: frame) physicsBody? .categoryBitMask = PhysicsCategories.EdgeBody player.position = CGPoint (x: size.width / 2, y : player.size.height) addChild (player) setupAccelerometer () addEnemies () generateIslands () override func touchesBegan (_ tocca: Imposta, con evento: UIEvent?) func addEnemies () let generateEnemyAction = SKAction.run [self debole] in self? .generateEnemy () lascia waitToGenerateEnemy = SKAction.wait (forDuration: 3.0) lascia generareEnemySequence = SKAction.sequence ( [generateEnemyAction, waitToGenerateEnemy]) run (SKAction.repeatForever (generateEnemySequence)) func generateEnemy () let enemy = Enemy () addChild (nemico) enemy.position = CGPoint (x: CGFloat (arc4random_uniform (UInt32 (size.width - enemy .size.width))), y: size.height - enemy.size.height) func didBegin (_ contact: SKPhysicsContact) var firstBody: SKPhysicsBody var secondBody: SKPhysicsBody if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask) firstBody = contact.bodyA secondBody = contact.bodyB else firstBody = contact.bodyB secondBody = contact.bodyA if((firstBody.categoryBitMask & PhysicsCategories.Player != 0) && (secondBody.categoryBitMask & PhysicsCategories.Enemy != 0)) player.die() secondBody.node?.removeFromParent() createExplosion(position: player.position) if((firstBody.categoryBitMask & PhysicsCategories.Player != 0) && (secondBody.categoryBitMask & PhysicsCategories.EnemyBullet != 0)) player.die() secondBody.node?.removeFromParent() if((firstBody.categoryBitMask & PhysicsCategories.Enemy != 0) && (secondBody.categoryBitMask & PhysicsCategories.PlayerBullet != 0)) if(firstBody.node != nil) createExplosion(position: (firstBody.node?.position)!) firstBody.node?.removeFromParent() secondBody.node?.removeFromParent() func createExplosion(position: CGPoint) let explosion = SKSpriteNode(imageNamed: "explosion1") explosion.position = position addChild(explosion) var explosionTextures:[SKTexture] = [] for i in 1… 6 explosionTextures.append(SKTexture(imageNamed: "explosion\(i)")) let explosionAnimation = SKAction.animate(with: explosionTextures, timePerFrame: 0.3) explosion.run(SKAction.sequence([explosionAnimation, SKAction.removeFromParent()])) func createIsland() let island = SKSpriteNode(imageNamed: "island1") island.position = CGPoint(x: CGFloat(arc4random_uniform(UInt32(size.width - island.size.width))), y: size.height - island.size.height - 50) island.zPosition = -1 addChild(island) let moveAction = SKAction.moveTo(y: 0 - island.size.height, duration: 15) island.run(SKAction.sequence([moveAction, SKAction.removeFromParent()])) func generateIslands() let generateIslandAction = SKAction.run [weak self] in self?.createIsland() let waitToGenerateIslandAction = SKAction.wait(forDuration: 9) run(SKAction.repeatForever(SKAction.sequence([generateIslandAction, waitToGenerateIslandAction]))) func setupAccelerometer() motionManager.accelerometerUpdateInterval = 0.2 motionManager.startAccelerometerUpdates(to: OperationQueue(), withHandler: accelerometerData, error in guard let accelerometerData = accelerometerData else return let acceleration = accelerometerData.acceleration self.accelerationX = CGFloat(acceleration.x) ) override func didSimulatePhysics() player.physicsBody?.velocity = CGVector(dx: accelerationX * 600, dy: 0)
Creiamo un'istanza di Giocatore
e un'istanza di CMMotionManager
. Stiamo usando l'accelerometro per muovere il giocatore in questo gioco.
All'interno del didMove (a :)
metodo abbiamo spento la gravità, impostare il contactDelegate
, aggiungere un loop di bordo e impostare il giocatore
La posizione prima di aggiungerla alla scena. Quindi invochiamo setupAccelerometer ()
, che imposta l'accelerometro e invoca il addEnemies ()
e generateIslands ()
metodi.
Il addEnemies ()
metodo chiama ripetutamente il generateEnemy ()
metodo, che creerà un'istanza di Nemico
e aggiungilo alla scena.
Il generateIslands ()
metodo funziona in modo simile al addEnemies ()
metodo in quanto chiama ripetutamente createIsland ()
che crea un SKSpriteNode
e lo aggiunge alla scena. Entro createIsland ()
, creiamo anche un SKAction
che sposta l'isola verso il basso nella scena.
All'interno del didBegin (_ :)
metodo, controlliamo per vedere quali nodi stanno prendendo contatto e rispondiamo rimuovendo il nodo appropriato dalla scena e invocando player.die ()
se necessario. Il createExplosion ()
metodo crea un'animazione esplosione e la aggiunge alla scena. Una volta terminata l'esplosione, viene rimosso dalla scena.
Durante questa serie, abbiamo imparato alcuni dei concetti più importanti utilizzati in quasi tutti i giochi SpriteKit. Abbiamo concluso la serie mostrando quanto sia semplice ottenere un gioco di base attivo e funzionante. Ci sono ancora alcuni miglioramenti che potrebbero essere apportati, come un HUB, punteggi alti e suoni (ho incluso un paio di MP3 che puoi usare per questo nel repository). Spero che tu abbia imparato qualcosa di utile in questa serie e grazie per la lettura!
.