Crea Space Invaders con Swift y Sprite Kit finaliza el juego

Lo que vas a crear

En la parte anterior de esta serie, hicimos que los invasores se movieran, el jugador y los invasores dispararon balas e implementamos la detección de colisiones. En la cuarta y última parte de esta serie, agregaremos la capacidad de mover al jugador usando el acelerómetro, administrar los niveles y asegurarnos de que el jugador muera al ser alcanzado por una bala. Empecemos.

1. Terminando el Jugador Clase

Paso 1: Agregar propiedades

Agregue las siguientes propiedades al Jugador clase debajo del canFire propiedad.

private var invencible = false private var lives: Int = 3 didSet if (lives < 0) kill() else respawn()    

los invencible La propiedad se usará para hacer que el jugador sea invencible temporalmente cuando pierde una vida. los vive La propiedad es el número de vidas que el jugador tiene antes de ser asesinado..

Estamos usando un observador de propiedad en el vive propiedad, que se llamará cada vez que se establezca su valor. los se estableció Se llama al observador inmediatamente después de establecer el nuevo valor de la propiedad. Al hacer esto, cada vez decrementamos la vive propiedad que comprueba automáticamente si vive es menor que cero, llamando al matar Método si es. Si al jugador le quedan vidas, el reaparecer Se invoca el método. Los observadores de propiedades son muy prácticos y pueden ahorrar una gran cantidad de código adicional.

Paso 2: reaparecer

los reaparecer Este método hace que el jugador sea invencible por un corto período de tiempo y hace que el jugador se desvanezca para indicar que es temporalmente invencible. La implementación de la reaparecer El método se ve así:

func respawn () invincible = true permite que fadeOutAction = SKAction.fadeOutWithDuration (0.4) permita que fadeInAction = SKAction.fadeInWithDuration (0.4) esta última en la que se ejecute la imagen de cada una de las personas a la que se refiere. 5) deje setInvicibleFalse = SKAction.runBlock () self.invincible = false runAction (SKAction.sequence ([fadeOutInAction, setInvicibleFalse)))) 

Nosotros fijamos invencible a cierto y crear una serie de SKAcción objetos. A estas alturas, ya debería estar familiarizado con cómo SKAcción trabajos de clase.

Paso 3: morir

los morir El método es bastante simple. Comprueba si invencible es falso y, si lo es, decrementa la vive variable.

 func die () if (invencible == false) vidas - = 1

Etapa 4: matar

los matar método reinicia invasorNum a 1 y lleva al usuario de nuevo a la StartGameScene para que puedan comenzar un nuevo juego.

func kill () invaderNum = 1 deja gameOverScene = StartGameScene (tamaño: self.scene! .size) gameOverScene.scaleMode = self.scene! .scaleMode let transitionType = SKTransition.flipHorizontalWithDuration (0.5) self.scene! (gameOverScene, transición: tipo de transición) 

Este código debe ser familiar para usted, ya que es casi idéntico al código que usamos para pasar al GameScene desde el StartGameScene. Tenga en cuenta que forzamos desenvolver el escena  para acceder a la escena tamaño y scaleMode propiedades.

Esto completa el Jugador clase. Ahora necesitamos llamar al morir  y matar métodos en el didBeginContact (_ :) método.

func didBeginContact (contacto: SKPhysicsContact) … if ((firstBody.categoryBitMask & CollisionCategories.Player! = 0) && (secondBody.categoryBitMask & CollisionCategories.InvaderBullet! = 0)) player.die ()) if ((firstBody & CollisionCategories.Invader! = 0) && (secondBody.categoryBitMask & CollisionCategories.Player! = 0)) player.kill ()

Ahora podemos probar todo. Una forma rápida de probar el morir método es comentando el mover los invasores llamar al actualizar(_:) método. Después de que el jugador muera y reaparece tres veces, debes regresar a la StartGameScene.

Para probar el matar método, asegúrese de que el mover los invasores La llamada no está comentada. Selecciona el invaderSpeed propiedad a un alto valor, por ejemplo, 200. Los invasores deben alcanzar al jugador muy rápidamente, lo que resulta en una muerte instantánea. Cambio invaderSpeed de regreso una vez que hayas terminado de probar.

2. Acabando disparando invasores

Tal como está el juego en este momento, solo la fila inferior de invasores puede disparar balas. Ya tenemos la detección de colisión para cuando una bala de jugador golpea a un invasor. En este paso, eliminaremos un invasor golpeado por una bala y agregaremos al invasor una fila hasta la matriz de invasores que pueden disparar. Agregue lo siguiente a la didBeginContact (_ :) método.

func didBeginContact (contacto: SKPhysicsContact) … if ((firstBody.categoryBitMask & CollisionCategories.Invader! = 0) && (secondBody.categoryBitMask & CollisionCategories.PlayerBullet! = 0)) if (contact.bodyA.node. nil || contact.bodyB.node? .parent == nil) return deja invadersPerRow = invaderNum * 2 + 1 deja theInvader = firstBody.node como! Invader deja que newInvaderRow = theInvader.invaderRow - 1 deja newInvaderColumn = theInvader.invaderColumn if (newInvaderRow> = 1) self.enumerateChildNodesWithName ("invader") node, stop in let invader = node as! Invader if invader.invaderRow == newInvaderRow && invader.invaderColumn == newInvaderColumn self.invadersWhoCanFire.append (invader) stop.memory por lo que se puede hacer por ejemplo. invaderIndex! = nil) invadersWhoCanFire.removeAtIndex (invaderIndex!) theInvader.removeFromParent () secondBody.node? .removeFromParent ()

Hemos eliminado el NSLog declaración y primer cheque si contact.bodyA.node? .parent y contact.bodyB.node? .parent no son nulo. Ellos estarán nulo Si ya hemos procesado este contacto. En ese caso, volvemos de la función..

Calculamos el InvadersPerRow Como hemos hecho antes y puesto el invasor a firstBody.node, echándolo a un Invasor. A continuación, obtenemos la nuevoInvaderRow restando 1 y el nuevoInvaderColumn, que permanece igual.

Solo queremos permitir que los invasores disparen si el nuevoInvaderRow es mayor o igual a 1, De lo contrario estaríamos intentando establecer un invasor en la fila. para poder disparar No hay fila 0 así que esto causaría un error.

A continuación, enumeramos a través de los invasores, buscando el invasor que tiene la fila y la columna correctas. Una vez encontrado, lo añadimos a la InvadersWhoCanFire matriz y llamada Stop.memory a cierto por lo que la enumeración se detendrá temprano.

Necesitamos encontrar al invasor que fue alcanzado con una bala en el InvadersWhoCanFire matriz para que podamos eliminarlo. Normalmente, los arrays tienen algún tipo de funcionalidad como una índice de Método o algo similar para lograr esto. En el momento de escribir esto, no existe un método de este tipo para las matrices en el lenguaje Swift. La biblioteca estándar de Swift define una encontrar función que podríamos usar, pero encontré un método en las secciones sobre genéricos en la Guía del lenguaje de programación Swift que cumplirá con lo que necesitamos. La función tiene un nombre apropiado índice de búsqueda. Agregue lo siguiente al fondo de GameScene.swift.

func findIndex(array: [T], valueToFind: T) -> Int? for (índice, valor) en enumerar (matriz) si valor == valueToFind return index return nil 

Si tiene curiosidad acerca de cómo funciona esta función, le recomiendo que lea más sobre genéricos en la Guía del lenguaje de programación Swift..

Ahora que tenemos un método que podemos usar para encontrar al invasor, lo invocamos, pasando el InvadersWhoCanFire matriz y el invasor. Comprobamos si invaderIndex no es igual a nulo y eliminar el invasor de la InvadersWhoCanFire matriz utilizando el removeAtIndex (índice: Int) método.

Ahora puedes probar si funciona como debería. Una forma fácil sería comentar dónde está la llamada. player.die en el didBeginContact (_ :) método. Asegúrate de eliminar el comentario cuando hayas terminado de probar. Observe que el programa se bloquea si mata a todos los invasores. Arreglaremos esto en el siguiente paso..

La aplicación se bloquea, porque tenemos un SKAcción Repetir acciónAnterior (_ :) llamando a los invasores a disparar balas. En este punto, no quedan invasores para disparar balas, por lo que los juegos fallan. Podemos arreglar esto comprobando el esta vacio propiedad en el InvadersWhoCanFire formación. Si la matriz está vacía, el nivel ha terminado. Ingrese lo siguiente en el fireInvaderBullet método.

func fireInvaderBullet () if (invadersWhoCanFire.isEmpty) invaderNum + = 1 levelComplete () else let randomInvader = invadersWhoCanFire.randomElement () randomInvader.fireBullet (self)

El nivel está completo, lo que significa que incrementamos invasorNum, que se utiliza para los niveles. Tambien invocamos Nivel completado, que todavía tenemos que crear en los pasos que vienen.

3. Completar un nivel

Necesitamos tener un número establecido de niveles. Si no lo hacemos, después de varias rondas tendremos tantos invasores que no cabrán en la pantalla. Añadir una propiedad maxlevels al GameScene clase.

clase GameScene: SKScene, SKPhysicsContactDelegate … let player: Player = Player () let maxLevels = 3

Ahora agregue el Nivel completado método en la parte inferior de GameScene.swift.

func levelComplete () if (invaderNum <= maxLevels) let levelCompleteScene = LevelCompleteScene(size: size) levelCompleteScene.scaleMode = scaleMode let transitionType = SKTransition.flipHorizontalWithDuration(0.5) view?.presentScene(levelCompleteScene,transition: transitionType) else invaderNum = 1 newGame()  

Primero verificamos si invasorNum es menor o igual que el maxlevels hemos establecido Si es así, hacemos la transición a la LevelCompletScene, de lo contrario reiniciamos invasorNum a 1 y llama nuevo juego. LevelCompleteScene no existe aún ni lo hace el nuevo juego método así que vamos a abordar estos uno a la vez en los próximos dos pasos.

4. Implementando el LevelCompleteScene Clase

Crear un nuevo Clase de Cocoa Touch llamado LevelCompleteScene eso es una subclase de SKScene. La implementación de la clase se ve así:

importar base importar clase SpriteKit LevelCompleteScene: SKScene override func didMoveToView (ver: SKView) self.backgroundColor = SKColor.blackColor () permite startGameButton = SKSpriteNodePacturePacom_comp_power_png size.height / 2 - 100) startGameButton.name = "nextlevel" addChild (startGameButton) anula la función touchesBegan (toca: Establecer, Evento withEvent: UIEvent) let touch = touches.first as! UITouch deja que touchLocation = touch.locationInNode (self) letToNombreNombre = self.nodeAtPoint (touchLocation) if (extractoNodo.nombre == "nextlevel") deja que gameOverScene = GameScene (tamaño: tamaño) gameOverScene.scaleMode = scaleMode permite la transicióntipo. flipHorizontalWithDuration (0.5) view? .presentScene (gameOverScene, transition: transitionType)

La implementación es idéntica a la StartGameScreen clase, excepto para que configuramos el nombre propiedad de startGameButton a "siguiente nivel". Este código debe ser familiar. Si no es así, vuelve a la primera parte de este tutorial para un repaso..

5. nuevo juego

los nuevo juego método simplemente transiciones de nuevo a la StartGameScene. Agregue lo siguiente al fondo de GameScene.swift.

func newGame () let gameOverScene = StartGameScene (tamaño: tamaño) gameOverScene.scaleMode = scaleMode let transitionType = SKTransition.flipHorizontalWithDuration (0.5) view? .presentScene (gameOverScene, transition: transitionType) 

Si prueba la aplicación, puede jugar algunos niveles o perder algunos juegos, pero el jugador no tiene forma de moverse y esto lo convierte en un juego aburrido. Vamos a arreglar eso en el siguiente paso..

6. Moviendo el jugador usando el acelerómetro

Usaremos el acelerómetro para mover al jugador. Primero necesitamos importar el CoreMotion marco de referencia. Agregue una declaración de importación para el marco en la parte superior de GameScene.swift.

importar SpriteKit importar CoreMotion

También necesitamos un par de nuevas propiedades..

let maxLevels = 3 let motionManager: CMMotionManager = CMMotionManager () var accelerationX: CGFloat = 0.0

A continuación, agregue un método. setupAccelerometer en el fondo de GameScene.swift.

func setupAccelerometer () motionManager.accelerometerUpdateInterval = 0.2 motionManager.startAccelerometerUpdatesToQueue (nSOperationQueue. ))

Aquí ponemos la acelerómetroActualizaciónIntervalo, cuál es el intervalo en segundos para proporcionar actualizaciones al controlador. encontré 0.2 Funciona bien, puedes probar diferentes valores si lo deseas. Dentro del manejador; un cierre, obtenemos la accelerometerData.acceleration, que es una estructura de tipo Ccelceleración.

struct CMAcceleration var x: Double var y: Double var z: Double init () init (x x: Double, y y: Double, z z: Double)

Sólo nos interesa el X propiedad y utilizamos la conversión de tipo numérico para convertirlo en un CGFloat para nuestro aceleraciónX propiedad.

Ahora que tenemos el aceleraciónX conjunto de propiedades, podemos mover el jugador. Hacemos esto en el didimular la física método. Agregue lo siguiente al fondo de GameScene.swift.

función de reemplazo didSimulatePhysics () player.physicsBody? .velocity = CGVector (dx: accelerationX * 600, dy: 0)

Invocar setupAccelerometer en didMoveToView (_ :) y deberías poder mover al jugador con el acelerómetro. Solo hay un problema. El jugador puede moverse fuera de la pantalla hacia cualquier lado y demora unos segundos recuperarlo. Podemos arreglar esto usando el motor de física y las colisiones. Lo hacemos en el siguiente paso..

anular func didMoveToView (ver: SKView) … setupInvaders () setupPlayer () invokeInvaderFire () setupAccelerometer ()

7. Restricción del movimiento del jugador

Como se mencionó en el paso anterior, el jugador puede moverse fuera de la pantalla. Esta es una solución simple utilizando el motor de física de Sprite Kit. Primero, agrega un nuevo ColisionCategoria llamado EdgeBody.

struct CollisionCategories estático deja Invader: UInt32 = 0x1 << 0 static let Player: UInt32 = 0x1 << 1 static let InvaderBullet: UInt32 = 0x1 << 2 static let PlayerBullet: UInt32 = 0x1 << 3 static let EdgeBody: UInt32 = 0x1 << 4 

Establece esto como el jugador collisionBitMask en su en eso método.

anular init () … self.physicsBody? .categoryBitMask = CollisionCategories.Player self.physicsBody? .contactTestBitMask = CollisionCategories.InvaderBullet | CollisionCategories.Invader self.physicsBody? .CollisionBitMask = CollisionCategories.EdgeBody animate ()

Por último, creamos un fisica cuerpo en la escena misma Agregue lo siguiente a la didMoveToView (ver: SKView) método en GameScene.swift.

 override func didMoveToView (ver: SKView) self.physicsWorld.gravity = CGVectorMake (0, 0) self.physicsWorld.contactDelegate = self self.physicsBody =. 

Inicializamos un cuerpo de física invocando. init (edgeLoopFromRect :), pasando en la escena de cuadro. El inicializador crea un bucle de borde desde el fotograma de la escena. Es importante tener en cuenta que un borde no tiene volumen ni masa y siempre se trata como si la propiedad dinámica fuera igual a falso. Los bordes también pueden chocar solo con cuerpos físicos basados ​​en el volumen, que nuestro jugador es.

También establecemos el categoríaBitMask a CollisionCategories.EdgeBody. Si prueba la aplicación, puede notar que su nave ya no puede moverse fuera de la pantalla, pero a veces gira. Cuando un cuerpo de física choca con otro cuerpo de física, es posible que esto resulte en una rotación. Este es el comportamiento predeterminado. Para remediar esto, nos propusimos permiteRotación a falso en Jugador..

anular init () … self.physicsBody? .collisionBitMask = CollisionCategories.EdgeBody self.physicsBody? .allowsRotation = false animate ()

8. Campo de estrellas

Paso 1: Creando el campo de estrellas

El juego tiene un campo de estrellas en movimiento en el fondo. Podemos crear el campo de inicio utilizando el motor de partículas de Sprite Kit..

Crea un nuevo archivo y selecciona Recurso desde el iOS sección. Escoger SpriteKit archivo de partículas como la plantilla y haga clic Siguiente. Para el Plantilla de partículas escoger lluvia y guardarlo como Campo de estrellas. Hacer clic Crear Para abrir el archivo en el editor. Para ver las opciones, abre el SKNode Inspector a la derecha derecha.


En lugar de revisar cada configuración aquí, lo que llevaría mucho tiempo, sería mejor leer la documentación para conocer cada configuración individual. Tampoco voy a entrar en detalles acerca de la configuración del campo de inicio. Si está interesado, abra el archivo en Xcode y eche un vistazo a la configuración que usé.

Paso 2: Agregar el campo de estrellas a las escenas

Agregue lo siguiente a didMoveToView (_ :) en StartGameScene.swift.

override func didMoveToView (ver: SKView) backgroundColor = SKColor.blackColor () permite que starField = SKEmitterNode (fileNamed: "StarField") starField.position = CGPointMake (size.width / 2, size.height / 2) starField.zPosition = - 1000 addChild (starField)

Usamos un SKEmitterNode para cargar el StarField.sks archivo, establecer su posición y darle una baja zPosición. El motivo de la baja. zPosición es asegurarse de que no impida que el usuario toque el botón de inicio. El sistema de partículas genera cientos de partículas, por lo que al establecerlo realmente bajo, superamos ese problema. También debe saber que puede configurar manualmente todas las propiedades de las partículas en un SKEmitterNode, Aunque es mucho más fácil usar el editor para crear un .sks archivarlo y cargarlo en tiempo de ejecución.

Ahora agrega el campo de estrellas a GameScene.swift y LevelCompleteScene.swift. El código es exactamente el mismo que el de arriba..

9. Implementando el PulsatingText Clase

Paso 1: Crea el PulsatingText Clase

los StartGameScene y LevelCompleteScene Tener texto que crece y se contrae repetidamente. Vamos a subclase SKLabeNode y usa un par de SKAcción instancias para lograr este efecto.

Crear un nuevo Clase de Cocoa Touch eso es una subclase de SKLabelNode,nombralo PulsatingText, y añádele el siguiente código.

importar UIKit importar SpriteKit clase PulsatingText: SKLabelNode func setTextFontSizeAndPulsate (theText: String, theFontSize: CGFloat) self.text = theText; self.fontSize = theFontSize permite scaleSequence = SKAction.sequence ([SKAction.scaleTo (2, duration: 1), SKAction.scaleTo (1.0, duration: 1)]) permite a scaleForever = SKAction.repeatActionForever (scaleSequence) self.runAAtion (scale )

Una de las primeras cosas que puede haber notado es que no hay inicializador. Si su subclase no define un inicializador designado, hereda automáticamente todos sus inicializadores designados de superclase.

Tenemos un metodo setTextFontSizeAndPulsate (theText: theFontSize :), Lo que hace exactamente lo que dice. Establece el SKLabelNodees texto y tamaño de fuente propiedades, y crea una serie de SKAcción instancias para hacer que el texto se amplíe y luego retroceda, creando un efecto pulsante.

Paso 2: Añadir PulsatingText a StartGameScene

Agregue el siguiente código a StartGameScene.swift en didMoveToView (_ :).

 override func didMoveToView (ver: SKView) backgroundColor = SKColor.blackColor () le permitió a invaderText = PulsatingText (fontNamed: "ChalkDuster") invaderText.setTextFontSext en el estado de la mano. , size.height / 2 + 200) addChild (invaderText)

Inicializamos un PulsatingText ejemplo, texto invasor, y invocar setTextFontSizeAndPulsate (theText: theFontSize :) en eso. Entonces establecemos su posición y agregarlo a la escena.

Paso 3: Añadir PulsatingText a LevelCompleteScene

Agregue el siguiente código a LevelCompleteScene.swift en didMoveToView (_ :).

 override func didMoveToView (ver: SKView) self.backgroundColor = SKColor.blackColor () let invaderText = PulsatingText (fontNamed: "ChalkDuster") invaderText.setTextFontTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextComPactTextTextoTechTextoTraducciónExtraficio width / 2, size.height / 2 + 200) addChild (invaderText)

Este es exactamente el mismo que el paso anterior. Solo el texto que estamos pasando es diferente..

10. Llevando el juego más lejos

Esto completa el juego. Tengo algunas sugerencias sobre cómo podría ampliar más el juego. Dentro de imagenes carpeta, hay tres imágenes invasoras diferentes. Cuando agregue invasores a la escena, elija al azar una de estas tres imágenes. Deberá actualizar el inicializador del invasor para aceptar una imagen como parámetro. Referirse a Bala clase para una pista.

También hay una imagen ovni. Intenta que aparezca y muévete por la pantalla cada quince segundos aproximadamente. Si el jugador lo golpea, dale una vida extra. Es posible que desee limitar la cantidad de vidas que pueden tener si hace esto. Por último, trata de hacer un HUD para las vidas de los jugadores..

Estas son sólo algunas sugerencias. Intenta hacer el juego tuyo..

Conclusión

Esto pone fin a esta serie. Debes tener un juego que se parezca mucho al juego original de Space Invaders. Espero que hayas encontrado este tutorial útil y hayas aprendido algo nuevo. Gracias por leer.