Conceptos básicos de SpriteKit Poner todo junto

Lo que vas a crear

En este post vamos a construir un juego simple desde cero. En el camino, abordaremos algunos de los aspectos más importantes de la biblioteca SpriteKit.

Esta publicación se basa en lo que hemos aprendido anteriormente en la serie de conceptos básicos de SpriteKit. Si quieres actualizar tu conocimiento de SpriteKit, mira algunas de mis otras publicaciones..

Nuevo proyecto

Abre Xcode y comienza un nuevo proyecto desde el menú. Expediente > Nuevo Proyecto. Asegurarse iOS se selecciona y elige Juego como tu plantilla.

Dale un nombre a tu proyecto y asegúrate de que Idioma se establece en Rápido, Tecnologia de juego se establece en SpriteKit, y Dispositivos se establece en iPad.

Planificación de las escenas del juego

Una de las primeras cosas que me gusta hacer al crear un proyecto es determinar cuántas escenas necesitaré para el proyecto. Normalmente tendré al menos tres escenas: una escena de introducción, una escena principal del juego y una escena para mostrar puntuaciones altas, etc..

Para este ejemplo, solo necesitamos una introducción y una escena de juego principal, ya que no estaremos al tanto de las vidas, las puntuaciones, etc. SpriteKit ya viene con una escena cuando creas un nuevo proyecto, así que solo necesitamos una escena de introducción..

Desde el menú de Xcode, elija Expediente > Nuevo > Expediente. Asegurarse iOS se selecciona, y elija Clase de Cocoa Touch.

Nombra la clase StartGameScene, y asegúrate de que Subclase de se establece en SKSceneIdioma se establece en Rápido.

Configuración de GameViewController

Abierto GameViewController.swift. Elimine todo en ese archivo y reemplácelo con el siguiente.

importar UIKit importar SpriteKit importar GameplayKit clase GameViewController: UIViewController override func viewDidLoad () super.viewDidLoad () let scene = StartGameScene (size: view.bounds.size) let skView = self.view as! SKView skView.showsFPS = false skView.showsNodeCount = false skView.ignoresSiblingOrder = false scene.scaleMode = .aspectFill skView.presentScene (scene) anular var prefersStatusBarHidden: Bool return true 

Cuando creas un nuevo proyecto, GameViewController.swift está configurado para cargar GameScene.sks del disco. GameScene.sks se utiliza junto con el editor de escenas incorporado de SpriteKit, que le permite diseñar visualmente sus proyectos. No vamos a utilizar GameScene.sks, y en su lugar creará todo desde el código, por lo que aquí iniciamos una nueva instancia de StartGameScene y presentarlo.

Crear la escena de introducción

Agrega lo siguiente a la recién creada StartGameScene.swift.

importar UIKit importar SpriteKit clase StartGameScene: SKScene override func didMove (ver: SKView) scene? .backgroundColor = .blue let logo = SKSpriteNode (imageNamed: "bigplane") logo.position = CGPoint (x: size.width / 2 , y: size.height / 2) addChild (logo) permite newGameBtn = SKSpriteNode (imageNamed: "newgamebutton") newGameBtn.position = CGPoint (x: size.width / 2, y: size.height / 2 - 350) newGameBtn. name = "newgame" addChild (newGameBtn) reemplaza func touchesBegan (_ touches: Set, con evento: ¿UIEvent?) guard let touch = touches.first else return let touchLocation = touch.location (in: self) let TouchNode = self.atPoint (touchLocation) if (TouchNode.name == "nuevo juego")  dejar que newScene = GameScene (tamaño: tamaño) newScene.scaleMode = scaleMode view? .presentScene (newScene) 

Esta escena es bastante simple. En el didMove Método, añadimos un logo y un botón. Entonces, en toquesBegan, Detectamos toques en el nuevo botón del juego y respondemos cargando la escena principal GameScene.

Planificación de clases de juegos

Lo siguiente que me gusta hacer al crear un juego nuevo es decidir qué clases necesitaré. Puedo decir de inmediato que necesitaré un Jugador clase y un Enemigo clase. Ambas de estas clases se extenderán SKSpriteNode. Creo que para este proyecto solo crearemos las balas de jugador y enemigo desde sus respectivas clases. Podrías hacer clases de bala de jugador y de bala enemiga por separado si lo prefieres, y te sugiero que intentes hacerlo como un ejercicio por tu cuenta.. 

Por último, están las islas. Estos no tienen ninguna funcionalidad específica pero para desplazarse por la pantalla. En este caso, ya que son solo decoraciones, creo que también está bien no crear una clase, y en su lugar solo crearlas en la parte principal GameScene.

Creando el Jugador Clase

Desde el menú de Xcode, elija Expediente > Nuevo > Expediente.  Asegurarse iOS se selecciona y elige Clase de Cocoa Touch.

Asegúrate de eso Clase se establece en Jugador, Subclase de: se establece en SKSpriteNode, y Idioma se establece en Rápido.

Ahora agregue lo siguiente a Jugador..

importar UIKit importar clase SpriteKit Player: SKSpriteNode private var canFire = true private var invencible = 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]))  

Dentro de en eso() método, configuramos el fisica cuerpo y invocar generarBullets (). los generarBullets método llama repetidamente FireBullet (), que crea una bala, fija su fisica cuerpo, y lo mueve por la pantalla.

Cuando el jugador pierde una vida, el respawn () Se invoca el método. Dentro de reaparecer En este método, hacemos que el avión entre y salga cinco veces, durante ese tiempo el jugador será invencible. Uno el jugador ha agotado todas las vidas, el matar() Se invoca el método. El método de matar simplemente carga el StartGameScene.

Creando la clase enemiga

Escoger Expediente > Nuevo > Expediente del menú de Xcode. Asegurarse iOS se selecciona y elige Clase de Cocoa Touch.

Asegúrate de eso Clase se establece en EnemigoSubclase de: se establece en SKSpriteNode, y Idioma se establece en Rápido.

Agregue lo siguiente a Enemigo. Cambio.

importar UIKit importar SpriteKit class Enemy: SKSpriteNode init () let texture = SKTexture (imageNamed: "enemy1") super.init (texture: texture, color: .clear, size: texture.size ()) self.name = " enemigo "self.physicsBody = SKPhysicsBody (textura: self.texture, tamaño: self.size) self.physicsBody? .isDynamic = true self.physicsBody? .categoryBitMask = PhysicsCategories.Enemy self.physicsBody? .contactTestBitMask = PhysicsCategories PhysicsCategories.PlayerBullet self.physicsBody? .AllowsRotation = false move () generateBullets () required init? (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.tize.height * 2 bullet.physicsBody = SKPhysicsBody (rectangleOf: bullet.size) bullet.physicsBody ?. categoryBitMask = PhysicsCategories.EnemyBullet bullet.physicsBody? .allowsRotation = false scene? .addChild (bullet) deja moveBulletAction = SKAction.move (para: CGPoint (x: self.position.x, y: 0 - bullet.size.height), duration: 2.0) deje removeBulletAction = SKAction.removeFromParent () bullet.run (SKAction.sequence ([moveBulletAction, removeBulletAction)))) func func (). let moveEnemyAction = SKAction.moveTo (y: 0 - self.size. duración: 12.0) deje removeEnemyAction = SKAction.removeFromParent () deje moveEnemySequence = SKAction.sequence ([moveEnemyAction, removeEnemyAction]) correr (moveEn emySequence) func generaBullets () let fireBulletAction = SKAction.run [self débil] in self? .fireBullet () let waitToFire = SKAction.wait (forDuration: 1) FireBulletSequence = [FireBulletAction] ) deja que fire = SKAction.repeatForever (fireBulletSequence) ejecute (fire)

Esta clase es bastante similar a la Jugador clase. Establecemos su fisica cuerpo y invocar generarBullets (). los movimiento() simplemente mueve al enemigo por la pantalla.

Creando la escena principal del juego

Eliminar todo dentro GameScene.swift y agrega lo siguiente.

import SpriteKit import GameplayKit import CoreMotion clase GameScene: SKScene, SKPhysicsContactDelegate let player = Player () permite motionManager = CMMotionManager () var accelerationX.gravity = CGV) , dy: 0.0) self.physicsWorld.contactDelegate = self scene? .backgroundColor = .blue physicsBody = SKPhysicsBody (edgeLoopFrom: frame) physicsBody? .categoryBitMask = PhysicsCategories.EdgeBody player.position = CGPoint (x) : player.size.height) addChild (player) setupAccelerometer () addEnemies () generateIslands () override func touchesBegan (_ toca: Establecer, con evento: ¿UIEvent?)  func addEnemies () let generateEnemyAction = SKAction.run [self débil] in self? .generateEnemy () let waitToGenerateEnemy = SKAction.wait (forDuration: 3.0) let generaEnemySequence = SKAction.sequence ( [generateEnemyAction, waitToGenerateEnemy]) run (SKAction.repeatForever (generateEnemySequence)) func generateEnemy () let enemy = Enemy () addChild (enemy) enemy.position = CGPoint (x: CGFloat (arc4random_uniform (UInt32 (size.wid)) .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)   

Creamos una instancia de Jugador y una instancia de CMMotionManager. Estamos utilizando el acelerómetro para mover al jugador en este juego.

Dentro de didMove (to :) Método apagamos la gravedad, configuramos el contactDelegate, agregar un bucle de borde, y establecer la jugadorPosición antes de añadirlo a la escena. Entonces invocamos setupAccelerometer (), que configura el acelerómetro, e invoca el addEnemies () y generarIslas () metodos.

los addEnemies () método llama repetidamente el generarEnemigo () método, que creará una instancia de Enemigo y agregarlo a la escena.

los generarIslas () método funciona de manera similar a la addEnemies () método en el que repetidamente llama crearIsla () que crea un SKSpriteNode Y lo agrega a la escena. Dentro crearIsla (), también creamos un SKAcción que mueve la isla por la escena.

Dentro de didBegin (_ :) Método, verificamos qué nodos están haciendo contacto y respondemos eliminando el nodo apropiado de la escena e invocando player.die () si necesario. los crearExplosión () El método crea una animación de explosión y la agrega a la escena. Una vez finalizada la explosión, se retira de la escena..

Conclusión

Durante esta serie, aprendimos algunos de los conceptos más importantes utilizados en casi todos los juegos SpriteKit. Terminamos la serie mostrando lo sencillo que es tener un juego básico en funcionamiento. Todavía hay algunas mejoras que se podrían hacer, como un HUB, puntuaciones altas y sonidos (incluí un par de MP3 que puedes usar para esto en el repositorio). Espero que hayas aprendido algo útil en esta serie, y gracias por leer.!

.