En la parte anterior de esta serie, implementamos los talones para las clases principales del juego. En este tutorial, haremos que los invasores se muevan, disparen balas tanto para los invasores como para el jugador, e implementemos la detección de colisiones. Empecemos.
Usaremos la escena. actualizar
Método para mover a los invasores. Cuando quiera mover algo manualmente, el actualizar
El método es generalmente donde querrías hacer esto..
Antes de hacer esto, sin embargo, necesitamos actualizar el a la derecha
propiedad. Inicialmente se estableció en 0, Porque necesitamos usar la escena tamaño
para establecer la variable. No pudimos hacer eso fuera de cualquiera de los métodos de la clase, así que actualizaremos esta propiedad en el didMoveToView (_ :)
método.
anular func didMoveToView (ver: SKView) backgroundColor = SKColor.blackColor () rightBounds = self.size.width - 30 setupInvaders () setupPlayer ()
A continuación, implementa el mover los invasores
método debajo del setupPlayer
Método que creaste en el tutorial anterior..
func moveInvaders () var changeDirection = false enumerateChildNodesWithName ("invader") node, stop in let invader = node as! SKSpriteNode let invaderHalfWidth = invader.size.width / 2 invader.position.x - = CGFloat (self.invaderSpeed) if (invader.position.x> self.rightBounds - invaderHalfWidth || invader.position.x < self.leftBounds + invaderHalfWidth) changeDirection = true if(changeDirection == true) self.invaderSpeed *= -1 self.enumerateChildNodesWithName("invader") node, stop in let invader = node as! SKSpriteNode invader.position.y -= CGFloat(46) changeDirection = false
Declaramos una variable., cambia la direccion
, para realizar un seguimiento cuando los invasores necesitan cambiar de dirección, moverse hacia la izquierda o hacia la derecha. Entonces usamos el enumerateChildNodesWithName (usingBlock :)
Método, que busca a los hijos de un nodo y llama al cierre una vez para cada nodo coincidente que encuentre con el nombre correspondiente. "invasor". El cierre acepta dos parámetros., nodo
es el nodo que coincide con el nombre
y detener
es un puntero a una variable booleana para terminar la enumeración. No vamos a utilizar detener
Aquí, pero es bueno saber para qué se utiliza..
Echamos nodo
a una SKSpriteNode
instancia que invasor
Es una subclase de, obtener la mitad de su ancho. InvaderHalfWidth
, y actualizar su posición. Entonces comprobamos si es posición
está dentro de los límites, Límite izquierdo
y a la derecha
, y, si no, ponemos cambia la direccion
a cierto
.
Si cambia la direccion
es cierto
, negamos invaderSpeed
, que cambiará la dirección en la que se mueve el invasor. Luego enumeramos a través de los invasores y actualizamos su posición y. Por último, nos propusimos cambia la direccion
de regreso falso
.
los mover los invasores
método se llama en el actualizar(_:)
método.
anular la actualización de la función (currentTime: CFTimeInterval) moveInvaders ()
Si prueba la aplicación ahora, debería ver a los invasores moverse hacia la izquierda, hacia la derecha y luego hacia abajo si llegan a los límites que hemos establecido en cada lado..
FireBullet
De vez en cuando queremos que uno de los invasores dispare una bala. Tal como está ahora, los invasores en la fila inferior están configurados para disparar una bala, porque están en la InvadersWhoCanFire
formación.
Cuando un invasor es golpeado por una bala de jugador, entonces el invasor una fila arriba y en la misma columna se agregará a la InvadersWhoCanFire
matriz, mientras que el invasor que recibió el golpe será eliminado. De esta manera, solo el invasor de abajo de cada columna puede disparar balas..
Añade el FireBullet
método para el InvaderBullet
clase en InvaderBullet.swift.
func fireBullet (scene: SKScene) let bullet = InvaderBullet (imageName: "laser", bulletSound: nil) bullet.position.x = self.position.x bullet.position.y = self.position.y - self.size. height / 2 scene.addChild (bullet) deja moveBulletAction = SKAction.moveTo (CGPoint (x: self.position.x, y: 0 - bullet.size.height), duración: 2.0) deja removeBulletAction = SKAction.removeFromParent () bullet .runAction (SKAction.sequence ([moveBulletAction, removeBulletAction]))
En el FireBullet
método, instanciamos un InvaderBullet
instancia, pasando en "láser" para Nombre de la imágen
, y porque no queremos que suene un sonido, pasamos nulo
para bulletSound
. Establecemos su posición
para ser el mismo que el del invasor, con un ligero desplazamiento en la posición y, y agregarlo a la escena.
Creamos dos SKAcción
instancias, moveBulletAction
y removeBulletAction
. los moveBulletAction
La acción mueve la bala a un cierto punto durante un cierto tiempo, mientras que la acción removeBulletAction
La acción lo saca de la escena. Invocando el secuencia(_:)
método en estas acciones, se ejecutarán secuencialmente. Por eso mencioné el waitForDuration
Método al reproducir un sonido en la parte anterior de esta serie. Si creas un SKAcción
objeto invocando playSoundFileNamed (_: waitForCompletion :)
y establecer waitForCompletion
a cierto
, entonces la duración de esa acción será por el tiempo que se reproduzca el sonido, de lo contrario, saltará inmediatamente a la siguiente acción en la secuencia.
invokeInvaderFire
Añade el invokeInvaderFire
Método debajo de los otros métodos que has creado en GameScence.swift.
func invokeInvaderFire () permite a fireBullet = SKAction.runBlock () self.fireInvaderBullet () permite a waitToFireInvaderBullet = SKAction.wait recreación en el estado de la embarcación (1.5) ) runAction (repeatForeverAction)
los runBlock (_ :)
método de la SKAcción
clase crea un SKAcción
instancia e inmediatamente invoca el cierre pasado a la runBlock (_ :)
método. En el cierre, invocamos el fireInvaderBullet
método. Debido a que invocamos este método en un cierre, tenemos que usar yo
llamarlo.
Entonces creamos un SKAcción
instancia nombrada waitToFireInvaderBullet
invocando waitForDuration (_ :)
, pasando el número de segundos a esperar antes de continuar. A continuación, creamos un SKAcción
ejemplo, InvaderFire
, invocando el secuencia(_:)
método. Este método acepta una colección de acciones que son invocadas por el InvaderFire
acción. Queremos que esta secuencia se repita para siempre, así que creamos una acción llamada repetir para siempre
, pasar en el SKAcción
Objetos a repetir, e invocar. runAction
, pasando en el repetir para siempre
acción. El método runAction se declara en el SKNode
clase.
fireInvaderBullet
Añade el fireInvaderBullet
método debajo del invokeInvaderFire
método que ingresaste en el paso anterior.
func fireInvaderBullet () let randomInvader = invadersWhoCanFire.randomElement () randomInvader.fireBullet (self)
En este método, llamamos a lo que parece ser un método llamado elemento aleatorio
Eso devolvería un elemento aleatorio fuera de la InvadersWhoCanFire
matriz, y luego llamar a su FireBullet
método. Lamentablemente, no hay ninguna elemento aleatorio
método en el Formación
estructura. Sin embargo, podemos crear un Formación
extensión para proporcionar esta funcionalidad.
elemento aleatorio
Ir Expediente > Nuevo > Expediente… y elige Archivo rápido. Estamos haciendo algo diferente que antes, así que asegúrate de que estás eligiendo Archivo rápido y no Clase de Cocoa Touch. prensa Siguiente y nombra el archivo Utilidades. Agregue lo siguiente a Utilities.swift.
importar la extensión de Foundation Array func randomElement () -> T let index = Int (arc4random_uniform (UInt32 (self.count))) return self [index]
Extendemos el Formación
estructura para tener un método llamado elemento aleatorio
. los arc4random_uniform
La función devuelve un número entre 0 y lo que pase. Debido a que Swift no convierte implícitamente los tipos numéricos, nosotros debemos hacer la conversión nosotros mismos. Finalmente, devolvemos el elemento de la matriz en el índice. índice
.
Este ejemplo ilustra lo fácil que es agregar funcionalidad a la estructura y las clases. Puede leer más sobre la creación de extensiones en The Swift Programming Language.
Con todo esto fuera del camino, ahora podemos disparar las balas. Agregue lo siguiente a la didMoveToView (_ :)
método.
función de reemplazo didMoveToView (ver: SKView) … setupPlayer () invokeInvaderFire ()
Si prueba la aplicación ahora, cada segundo o así debería ver a uno de los invasores de la fila inferior disparar una bala.
FireBullet (escena :)
Agregue la siguiente propiedad al Jugador
clase en Jugador..
Clase Player: SKSpriteNode private var canFire = true
Queremos limitar la frecuencia con la que el jugador puede disparar una bala. los canFire
La propiedad será utilizada para regular eso. A continuación, agregue lo siguiente a la FireBullet (escena :)
método en el Jugador
clase.
func fireBullet (escena: SKScene) if (! canFire) return else canFire = false let bullet = PlayerBullet (imageName: "laser", bulletSound: "laser.mp3") bullet.position.x = self.position. x bullet.position.y = self.position.y + self.size.height / 2 scene.addChild (bullet) deja moveBulletAction = SKAction.moveTo (CGPoint (x: self.position.x, y: scene.size.height + bullet.size.height), duración: 1,0) dejar que removeBulletAction = SKAction.removeFromParent () bullet.runAction (SKAction.sequence ([moveBulletAction, removeBulletAction])) dejar que waitToEnableFire = SKAction.waitForDuration (0.5) runAction (waitToEnableFire, completado: self.canFire = true)
Primero nos aseguramos de que el jugador pueda disparar al verificar si canFire
se establece en cierto
. Si no es así, volvemos inmediatamente del método..
Si el jugador puede disparar, ponemos canFire
a falso
por lo que no pueden disparar de inmediato otra bala. Entonces creamos una instancia de PlayerBullet
instancia, pasando en "láser" Para el imageNamed
parámetro. Porque queremos que se reproduzca un sonido cuando el jugador dispara una bala, pasamos "laser.mp3" Para el bulletSound
parámetro.
Luego establecemos la posición de la bala y la agregamos a la pantalla. Las siguientes líneas son las mismas que las Invasor
esFireBullet
Método en el que movemos la bala y la sacamos de la escena. A continuación, creamos un SKAcción
ejemplo, waitToEnableFire
, invocando el waitForDuration (_ :)
método de clase. Por último, invocamos runAction
, pasando en waitToEnableFire
, y en el set de finalización canFire
de regreso cierto
.
Siempre que el usuario toque la pantalla, queremos disparar una bala. Esto es tan simple como llamar FireBullet
sobre el jugador
objeto en el TocaBegan (_: conEvento :)
método de la GameScene
clase.
anular func toques Empiezan (toques: Establecer, Evento withEvent: UIEvent) player.fireBullet (self)
Si prueba la aplicación ahora, debería poder disparar una bala cuando toque la pantalla. Además, debe escuchar el sonido del láser cada vez que se dispara una bala.
Para detectar cuándo los nodos chocan o hacen contacto entre sí, usaremos el motor de física incorporado del Sprite Kit. Sin embargo, el comportamiento predeterminado del motor de física es que todo choca con todo cuando se les agrega un cuerpo de física. Necesitamos una forma de separar lo que queremos interactuar unos con otros y podemos hacerlo creando categorías a las que pertenecen cuerpos físicos específicos..
Estas categorías se definen mediante una máscara de bits que utiliza un entero de 32 bits con 32 indicadores individuales que pueden estar activados o desactivados. Esto también significa que solo puedes tener un máximo de 32 categorías para tu juego. Esto no debería presentar un problema para la mayoría de los juegos, pero es algo a tener en cuenta.
Agregue la siguiente definición de estructura a la GameScene
clase, debajo de la invasorNum
declaración en GameScene.swift.
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
Usamos una estructura, CollsionCategories
, para crear categorías para el Invasor
, Jugador
, InvaderBullet
, y PlayerBullet
clases Estamos usando cambio de bits para activar los bits.
Jugador
y InvaderBullet
ColisiónInvaderBullet
para colisiónAgregue el siguiente bloque de código a la init (imageName: bulletSound :)
método en InvaderBullet.swift.
sobrescribir init (imageName: String, bulletSound: String?) super.init (imageName: imageName, bulletSound: bulletSound) self.physicsBody = SKPhysicsBody (textura: self.texture, size: self.size) self.physicsBody? .dynamic = true self.physicsBody? .usesPreciseCollisionDetection = true self.physicsBody? .categoryBitMask = CollisionCategories.InvaderBullet self.physicsBody? .contactcikacity.com
Hay varias formas de crear un cuerpo de física. En este ejemplo, usamos el init (textura: tamaño :)
Inicializador, que hará que la detección de colisiones use la forma de la textura que pasamos. Hay varios otros inicializadores disponibles, que se pueden ver en la referencia de la clase SKPhysicsBody..
Podríamos haber usado el init (rectangleOfSize :)
Inicializador, porque las balas tienen forma rectangular. En un juego tan pequeño no importa. Sin embargo, tenga en cuenta que el uso de init (textura: tamaño :)
El método puede ser computacionalmente costoso ya que tiene que calcular la forma exacta de la textura. Si tienes objetos de forma rectangular o circular, deberías usar esos tipos de inicializadores si el rendimiento del juego se está convirtiendo en un problema..
Para que la detección de colisiones funcione, al menos uno de los cuerpos que está probando debe estar marcado como dinámico. Estableciendo el usesPreciseCollisionDetection
propiedad a cierto
, Sprite Kit utiliza una detección de colisión más precisa. Establezca esta propiedad en cierto
En cuerpos pequeños, rápidos como nuestras balas..
Cada cuerpo pertenecerá a una categoría y usted lo define estableciendo su categoríaBitMask
. Ya que esta es la InvaderBullet
clase, lo ponemos a CollisionCategories.InvaderBullet
.
Para saber cuándo este cuerpo se ha puesto en contacto con otro cuerpo en el que está interesado, debe configurar contactBitMask
. Aquí queremos saber cuándo el InvaderBullet
Ha hecho contacto con el jugador por lo que usamos. CollisionCategories.Player
. Debido a que una colisión no debería desencadenar ninguna fuerza física, establecemos collisionBitMask
a 0x0
.
Jugador
para CollsionAgregue lo siguiente a la en eso
método en Jugador..
anular init () let texture = SKTexture (imageNamed: "player1") super.init (texture: texture, color: SKColor.clearColor (), size: texture.size ()) self.physicsBody = SKPhysicsBody (texture: self. textura, tamaño: self.size) self.physicsBody? .dynamic = true self.physicsBody? .PreciseCollisionDetection = false self.physicsBody? .categoryBitMask = CollisionCategories.Player selfies.physicsBody?. CollisionCategories.Invader self.physicsBody? .CollisionBitMask = 0x0 animate ()
Mucho de esto debería ser familiar del paso anterior, así que no lo repetiré aquí. Hay dos diferencias para notar sin embargo. Uno es que usesPreciseCollsionDetection
se ha establecido en falso
, cual es el predeterminado Es importante darse cuenta de que solo uno de los organismos de contacto necesita esta propiedad configurada para cierto
(que fue la bala). La otra diferencia es que también queremos saber cuándo el jugador se pone en contacto con un invasor. Puedes tener más de una contactBitMask
categoría separándolos con el bitwise o (|
) operador. Aparte de eso, deberías notar que es básicamente opuesto a la InvaderBullet
.
Invasor
y PlayerBullet
ColisiónInvasor
para colisiónAgregue lo siguiente a la en eso
método en Invader.swift.
anular init () let texture = SKTexture (imageNamed: "invader1") super.init (texture: texture, color: SKColor.clearColor (), size: texture.size ()) self.name = "invader" self.physicsBody = SKPhysicsBody (textura: self.texture, tamaño: self.size) self.physicsBody? .Dynamic = true self.physicsBody? .UsesPreciseCollisionDetection = false self.physicsBody? Recreo de animales / animales / animales / animales / animales / animales / animales PlayerBullet | CollisionCategories.Player self.physicsBody? .CollisionBitMask = 0x0
Todo esto debería tener sentido si has estado siguiendo a lo largo. Establecimos el fisica cuerpo
, categoríaBitMask
, y contactBitMask
.
PlayerBullet
para colisiónAgregue lo siguiente a la init (imageName: bulletSound :)
en JugadorBullet.swift. De nuevo, la implementación ya debería ser familiar..
sobrescribir init (imageName: String, bulletSound: String?) super.init (imageName: imageName, bulletSound: bulletSound) self.physicsBody = SKPhysicsBody (textura: self.texture, size: self.size) self.physicsBody? .dynamic = true self.physicsBody? .usesPreciseCollisionDetection = true self.physicsBody? .categoryBitMask = CollisionCategories.PlayerBullet self.physicsBody? .contactcikisionBitMacityCity
GameScene
Tenemos que configurar el GameScene
clase para implementar el SKPhysicsContactDelegate
Así podemos responder cuando chocan dos cuerpos. Agregue lo siguiente para hacer el GameScene
clase conforme a la SKPhysicsContactDelegate
protocolo.
clase GameScene: SKScene, SKPhysicsContactDelegate
A continuación, tenemos que configurar algunas propiedades en la escena fisica mundo
. Introduzca lo siguiente en la parte superior de la didMoveToView (_ :)
método en GameScene.swift.
anular la función didMoveToView (ver: SKView) self.physicsWorld.gravity = CGVectorMake (0, 0) self.physicsWorld.contactDelegate = self ...
Establecemos el gravedad
propiedad de fisica mundo
a 0 de modo que ninguno de los cuerpos físicos en la escena es afectado por la gravedad. También puede hacer esto por cada cuerpo en lugar de configurar el mundo entero para que no tenga gravedad al configurar el afectado por Gravedad
propiedad. También establecemos el contactDelegate
propiedad del mundo de la física para yo
, la GameScene
ejemplo.
SKPhysicsContactDelegate
ProtocoloPara conformar el GameScene
clase a SKPhysicsContactDelegate
protocolo, necesitamos implementar el didBeginContact (_ :)
método. Este método se llama cuando dos cuerpos hacen contacto. La implementación de la didBeginContact (_ :)
el metodo se ve asi.
func didBeginContact (contact: SKPhysicsContact) var firstBody: SKPhysicsBody var secondBody: SKPhysicsBody si contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask firstBody = contact.bodyA secondBody = contact.bodyB else firstBody = contact.bodyB secondBody = contact.bodyA if ((firstBody.categoryBitMask & CollisionCategories.Invader != 0) && (secondBody.categoryBitMask & CollisionCategories.PlayerBullet != 0)) NSLog("Invader and Player Bullet Conatact") if ((firstBody.categoryBitMask & CollisionCategories.Player != 0) && (secondBody.categoryBitMask & CollisionCategories.InvaderBullet != 0)) NSLog("Player and Invader Bullet Contact") if ((firstBody.categoryBitMask & CollisionCategories.Invader != 0) && (secondBody.categoryBitMask & CollisionCategories.Player != 0)) NSLog("Invader and Player Collision Contact")
Primero declaramos dos variables. FirstBody
y SecondBody
. Cuando dos objetos hacen contacto, no sabemos qué cuerpo es cuál. Esto significa que primero tenemos que hacer algunas comprobaciones para asegurarnos FirstBody
es el que tiene el inferior categoríaBitMask
.
A continuación, vamos a través de cada escenario posible utilizando el bitwise Y
operador y las categorías de colisión que definimos anteriormente para verificar qué se está haciendo contacto. Registramos el resultado en la consola para asegurarnos de que todo funciona como debería. Si prueba la aplicación, todos los contactos deberían estar funcionando correctamente..
Este fue un tutorial bastante largo, pero ahora los invasores se están moviendo, las balas se disparan desde el jugador y los invasores, y la detección de contactos funciona mediante el uso de máscaras de bits de contacto. Estamos en la recta final hasta el juego final. En la siguiente y última parte de esta serie, tendremos un juego completo.