Spanish (Español) translation by Muhamad Zulfiqor (you can also view the original English article)
En esta serie, estamos aprendiendo cómo usar SpriteKit para construir juegos 2D para iOS. En esta publicación, aprenderemos sobre dos características importantes de SpriteKit: acciones y física.
Para seguir con este tutorial, simplemente descargue el repositorio de GitHub repo que lo acompaña. Tiene dos carpetas: una para acciones y otra para física. Simplemente abra cualquiera de los proyectos iniciales en Xcode y ya está todo listo.
Acción
Para la mayoría de los juegos, querrá que los nodos hagan algo como mover, escalar o rotar. La clase SKAction
fue diseñada con este propósito en mente. La clase SKAction
tiene muchos métodos de clase que puede invocar para mover, escalar o rotar las propiedades de un nodo durante un período de tiempo.
También puede reproducir sonidos, animar un grupo de texturas o ejecutar código personalizado utilizando la clase SKAction
. Puede ejecutar una sola acción, ejecutar dos o más acciones una tras otra en una secuencia, ejecutar dos o más acciones al mismo tiempo juntas como un grupo e incluso repetir cualquier acción.
Moción
Hagamos que un nodo se mueva por la pantalla. Ingrese lo siguiente dentro de Example1.swift.
class Example1: SKScene { let player = SKSpriteNode(imageNamed: "player") override func didMove(to view: SKView) { player.position = CGPoint(x: 200, y: player.size.height) addChild(player) let movePlayerAction = SKAction.moveTo(y: 900, duration: 2) player.run(movePlayerAction) } }
Aquí creamos un SKAction
e invocamos el método de clase moveTo (y: duration :)
, que toma como parámetro la posición y
para mover el nodo y la duración
en segundos. Para ejecutar la acción, debe llamar al método run (_ :)
de un nodo y pasar el SKAction
. Si prueba ahora, debería ver un avión moverse hacia arriba en la pantalla.
Existen varias variedades de métodos de movimiento, incluido move(to:duration:)
, que moverá el nodo a una nueva posición tanto en el eje x como en el eje y, y move(by: duration:)
, que moverá un nodo relativo a su posición actual. Le sugiero que lea toda la documentación sobre SKAction
para conocer todas las variedades de los métodos de movimiento.
Cierres de finalización
Existe otra variedad del método de ejecución que le permite llamar a algún código en un cierre de finalización. Ingrese el siguiente código dentro de Example2.swift.
class Example2: SKScene { let player = SKSpriteNode(imageNamed: "player") override func didMove(to view: SKView) { player.position = CGPoint(x: 200, y: player.size.height) addChild(player) let movePlayerAction = SKAction.moveTo(y: 900, duration: 2) player.run(movePlayerAction, completion: { print("Player Finished Moving") }) } }
El método run(_: completion:)
le permite ejecutar un bloque de código una vez que la acción se haya completado completamente. Aquí ejecutamos una declaración de impresión simple, pero el código puede ser tan complejo como lo necesite.
Secuencias de acciones
Algunas veces querrá ejecutar acciones una tras otra, y puede hacer esto con el método de sequence(_:)
. Agregue lo siguiente a Example3.swift.
class Example3: SKScene { let player = SKSpriteNode(imageNamed: "player") override func didMove(to view: SKView) { player.position = CGPoint(x: 100, y: player.size.height) addChild(player) let movePlayerAction = SKAction.moveTo(y: 900, duration: 2) let scalePlayerAction = SKAction.scale(to: 3, duration: 2) let sequenceAction = SKAction.sequence([movePlayerAction, scalePlayerAction]) player.run(sequenceAction) } }
Aquí creamos dos SKActions
: una usa el moveTo(y: duration:)
, y la otra usa la scale(to:duration:)
, que cambia la escala xey del nodo. A continuación, invocamos el método de sequence (_:)
, que toma como parámetro una matriz de SKActions
que se ejecutarán una después de la otra. Si prueba ahora, debería ver que el avión se mueve hacia arriba en la pantalla, y una vez que ha llegado a su destino, crecerá hasta tres veces su tamaño original.
Acciones agrupadas
En otras ocasiones, es posible que desee ejecutar acciones juntas como un grupo. Agregue el siguiente código a Example4.swift.
class Example4: SKScene { let player = SKSpriteNode(imageNamed: "player") override func didMove(to view: SKView) { player.position = CGPoint(x: 100, y: player.size.height) addChild(player) let movePlayerAction = SKAction.moveTo(y: 900, duration: 2) let scalePlayerAction = SKAction.scale(to: 3, duration: 2) let groupAction = SKAction.group([movePlayerAction, scalePlayerAction]) player.run(groupAction) } }
Aquí estamos utilizando los mismos métodos moveTo
y scale
que en el ejemplo anterior, pero también invocamos el método group(_:)
, que toma como parámetro una matriz de SKActions
que se ejecutará al mismo tiempo. Si tuviera que probar ahora, vería que el avión se mueve y escala al mismo tiempo.
Invertir acciones
Algunas de estas acciones se pueden revertir invocando el método reverse()
. La mejor manera de descubrir qué acciones admiten el método reversed()
es consultar la documentación. Una acción que es reversible es el fadeOut(withDuration:)
, que desvanecerá un nodo a la invisibilidad al cambiar su valor alfa. Hagamos que el avión se desvanezca y luego vuelva a fundirse. Agregue lo siguiente a Example5.swift.
class Example5: SKScene { override func didMove(to view: SKView) { let player = SKSpriteNode(imageNamed: "player") player.position = CGPoint(x: 100, y: player.size.height) addChild(player) let fadePlayerAction = SKAction.fadeOut(withDuration: 2) let fadePlayerActionReversed = fadePlayerAction.reversed() let fadePlayerSequence = SKAction.sequence([fadePlayerAction, fadePlayerActionReversed]) player.run(fadePlayerSequence) } }
Aquí creamos un SKActione
invocamos el método fadeOut (withDuration:)
. En la siguiente línea de código, invocamos el método reversed()
, que hará que la acción revierte lo que acaba de hacer. Pruebe el proyecto y verá que el plano se desvanece y luego vuelve a fundirse.
Acciones repetitivas
Si alguna vez necesita repetir una acción un número específico de veces, los métodos repeat(_:count:)
y repeatForever(_:)
lo tienen cubierto. Hagamos que el avión se apague y se apague repetidamente para siempre. Ingrese el siguiente código en Example6.swift.
class Example6: SKScene { let player = SKSpriteNode(imageNamed: "player") override func didMove(to view: SKView) { player.position = CGPoint(x: 100, y: player.size.height) addChild(player) let fadePlayerAction = SKAction.fadeOut(withDuration: 2) let fadePlayerActionReversed = fadePlayerAction.reversed() let fadePlayerSequence = SKAction.sequence([fadePlayerAction, fadePlayerActionReversed]) let fadeOutInRepeatAction = SKAction.repeatForever(fadePlayerSequence) player.run(fadeOutInRepeatAction) } }
Aquí invocamos el método repeatForever(_:)
, pasando el fadePlayerSequence
. Si prueba, verá que el avión se desvanece y luego regresa para siempre.
Detener las Acciones
Muchas veces deberá evitar que un nodo ejecute sus acciones. Puede usar el método removeAllActions()
para esto. Hagamos que el nodo del jugador deje de desvanecerse cuando toquemos la pantalla. Agregue lo siguiente dentro de Example7.swift.
class Example7: SKScene { let player = SKSpriteNode(imageNamed: "player") override func didMove(to view: SKView) { player.position = CGPoint(x: 100, y: player.size.height) addChild(player) let fadePlayerAction = SKAction.fadeOut(withDuration: 2) let fadePlayerActionReversed = fadePlayerAction.reversed() let fadePlayerSequence = SKAction.sequence([fadePlayerAction, fadePlayerActionReversed]) let fadeOutInRepeatAction = SKAction.repeatForever(fadePlayerSequence) player.run(fadeOutInRepeatAction) } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { player.removeAllActions() } }
Si tocas en la pantalla, el nodo de jugador tendrá todas las acciones eliminadas y ya no se desvanecerá.
Llevando un registro de las Acciones
Algunas veces necesita una forma de realizar un seguimiento de sus acciones. Por ejemplo, si ejecuta dos o más acciones en un nodo, es posible que desee una forma de identificarlas. Puede hacerlo registrando una clave con el nodo, que es una cadena de texto simple. Ingrese lo siguiente dentro del Example8.swift.
class Example8: SKScene { let player = SKSpriteNode(imageNamed: "player") override func didMove(to view: SKView) { player.position = CGPoint(x: 100, y: player.size.height) addChild(player) let fadePlayerAction = SKAction.fadeOut(withDuration: 2) let fadePlayerActionReversed = fadePlayerAction.reversed() let fadePlayerSequence = SKAction.sequence([fadePlayerAction, fadePlayerActionReversed]) let fadeOutInRepeatAction = SKAction.repeatForever(fadePlayerSequence) player.run(fadeOutInRepeatAction, withKey: "faderepeataction") } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { if player.action(forKey: "faderepeataction") != nil { player.removeAction(forKey: "faderepeataction") } } }
Aquí estamos invocando el método run(_:withKey:)
del nodo, que, como se mencionó, toma una cadena de texto simple. Dentro del método touchesBegan(_:with:)
, estamos invocando action(forKey:)
para asegurarnos de que el nodo tenga la clave que le asignamos. Si lo hace, invocamos .removeAction(forKey:)
, que toma como parámetro la clave que ha establecido previamente.
Acciones de Sonido
Muchas veces querrás reproducir un sonido en tu juego. Puede lograr esto usando el método de clase playSoundFileNamed(_:waitForCompletion:)
. Ingrese lo siguiente dentro de Example9.swift.
class Example9: SKScene { override func didMove(to view: SKView) { let planeSound = SKAction.playSoundFileNamed("planesound", waitForCompletion: false) run(planeSound) } }
PlaySoundFileNamed(_:waitForCompletion:)
toma como parámetros el nombre del archivo de sonido sin la extensión y un booleano que determina si la acción esperará hasta que el sonido esté completo antes de continuar.
Por ejemplo, supongamos que tiene dos acciones en una secuencia, siendo el sonido la primera acción. Si waitForCompletion
era verdadero
, la secuencia esperaría hasta que el sonido terminara de reproducirse antes de pasar a la siguiente acción dentro de la secuencia. Si necesita más control sobre sus sonidos, puede usar un SKAudioNode
. No cubriremos el SKAudioNode
en esta serie, pero definitivamente es algo que debe considerar durante su carrera como desarrollador de SpriteKit.
Animación de Cuadros
Animar un grupo de imágenes es algo que muchos juegos requieren. La animate(con:timePerFrame:)
tiene cubierto en esos casos. Ingrese lo siguiente dentro de Example10.swift.
class Example10: SKScene { let explosion = SKSpriteNode(imageNamed: "explosion1") override func didMove(to view: SKView) { 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(explosionAnimation) } }
La animate (with:timePerFrame:)
toma como parámetro una matriz de SKTextures
, y un valor timePerFrame
que será el tiempo que lleve entre cada cambio de textura. Para ejecutar esta acción, invoque el método de ejecución de un nodo y pase el SKAction
.
Acciones de código personalizado
El último tipo de acción que veremos es uno que le permita ejecutar código personalizado. Esto podría ser útil cuando necesite hacer algo en el medio de sus acciones, o simplemente necesite una forma de ejecutar algo que la clase SKAction
no proporciona. Ingrese lo siguiente dentro de Example11.swift.
class Example11: SKScene { override func didMove(to view: SKView) { let runAction = SKAction.run(printToConsole) run(runAction) } func printToConsole(){ print("SKAction.run() invoked") } }
Aquí invocamos el método run(_:)
de la escena y pasamos una función printToConsole()
como parámetro. Recuerde que las escenas también son nodos, por lo que también puede invocar el método run(_:)
en ellas.
Esto concluye nuestro estudio de acciones. Hay muchas cosas que puede hacer con la clase SKAction
, y le sugiero que, después de leer este tutorial, explore la documentación de SKActions
.
Física
SpriteKit ofrece un motor robusto de física de fábrica, con poca configuración requerida. Para comenzar, simplemente agrega un cuerpo de física a cada uno de sus nodos y listo. El motor de física está construido sobre el popular motor Box2d. Sin embargo, la API de SpriteKit es mucho más fácil de usar que la API original de Box2d.
Comencemos añadiendo un cuerpo de física a un nodo y veamos qué sucede. Agregue el siguiente código a Example1.swift.
... let player = SKSpriteNode(imageNamed: "enemy1") override func didMove(to view: SKView) { ... player.physicsBody = SKPhysicsBody(circleOfRadius: player.size.width/2) player.physicsBody?.affectedByGravity = false player.position = CGPoint(x: size.width/2 , y: size.height - player.size.height) addChild(player) } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { ... player.physicsBody?.affectedByGravity = true }
Adelante, prueba el proyecto ahora. Verás el avión sentado en la parte superior de la escena. Una vez que presionas en la pantalla, el avión se caerá de la pantalla y seguirá cayendo para siempre. Esto muestra lo simple que es comenzar a usar la física: simplemente agrega un cuerpo de física a un nodo y todo está listo.
La physicBody
Shape
La propiedad physicsBody
es del tipo SKPhysicsBody
, que va a ser un bosquejo aproximado de la forma de tu nodo ... o un esquema muy preciso de la forma de tu nodo, dependiendo de qué constructor usas para inicializar esta propiedad
Aquí hemos utilizado el inicializador init(circleOfRadius:)
, que toma como parámetro el radio del círculo. Hay varios otros inicializadores, incluido uno para un rectángulo o un polígono de un CGPath. Incluso puede usar la propia textura del nodo, lo que haría que physicsBody
sea una representación casi exacta del nodo.
Para ver lo que quiero decir, actualice el archivo GameViewController.swift con el siguiente código. He comentado la línea que se agregará.
override func viewDidLoad() { super.viewDidLoad() let scene = GameScene(size:CGSize(width: 768, height: 1024)) let skView = self.view as! SKView skView.showsFPS = false skView.showsNodeCount = false skView.ignoresSiblingOrder = false skView.showsPhysics = true // THIS LINE ADDED scene.scaleMode = .aspectFill skView.presentScene(scene) }
Ahora el physicsBody
del nodo se perfilará en verde. En la detección de colisiones, la forma de PhysicsBody
es lo que se evalúa. Este ejemplo tendría el círculo alrededor del plano que guía la detección de colisión, lo que significa que si una bala, por ejemplo, golpeara el borde exterior del círculo, eso sería una colisión.

Ahora agregue lo siguiente a Example2.swift.
override func didMove(to view: SKView) { player.physicsBody = SKPhysicsBody(texture: player.texture!, size: player.size) player.physicsBody?.affectedByGravity = false player.position = CGPoint(x: size.width/2 , y: size.height - player.size.height) addChild(player) }
Aquí estamos usando la textura del sprite. Si prueba el proyecto ahora, debería ver que el contorno ha cambiado a una representación casi exacta de la textura del sprite.

Gravedad
Establecimos la propiedad affectedByGravity
de physicsBody
en false
en los ejemplos anteriores. Tan pronto como agregue un cuerpo de física a un nodo, el motor de física se hará cargo. ¡El resultado es que el avión cae inmediatamente cuando se ejecuta el proyecto!
También puedes establecer la gravedad por nodo, como aquí, o puedes desactivar la gravedad por completo. Agregue lo siguiente a Example3.swift.
let player = SKSpriteNode(imageNamed: "enemy1") override func didMove(to view: SKView) { ... player.physicsBody = SKPhysicsBody(texture: player.texture!, size: player.size) player.position = CGPoint(x: size.width/2 , y: size.height - player.size.height) addChild(player) physicsWorld.gravity = CGVector(dx:0, dy: 0) } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { ... physicsWorld.gravity = CGVector(dx:0 , dy: -9.8) }
Podemos establecer la gravedad usando la propiedad de physicsWorld
gravity
. La propiedad de gravity
es de tipo CGVector
. Establecemos los componentes dx
y dy
en 0, y luego cuando se toca la pantalla establecemos la propiedad dy
en -9.8. Los componentes se miden en metros, y el valor predeterminado es (0, -9,8), que representa la gravedad de la Tierra.
Edge Loops
Tal como está ahora, cualquier nodo agregado a la escena simplemente se caerá de la pantalla para siempre. Podemos agregar un bucle de borde alrededor de la escena usando el método init(edgeLoopFrom:).
Agregue lo siguiente a Example4.swift.
let player = SKSpriteNode(imageNamed: "enemy1") override func didMove(to view: SKView) { ... player.physicsBody = SKPhysicsBody(texture: player.texture!, size: player.size) player.position = CGPoint(x: size.width/2 , y: size.height - player.size.height) addChild(player) physicsWorld.gravity = CGVector(dx:0, dy: 0) physicsBody = SKPhysicsBody(edgeLoopFrom: frame) } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { ... physicsWorld.gravity = CGVector(dx:0 , dy: -9.8) }
Aquí hemos agregado un cuerpo de física a la escena en sí. El init(edgeLoopFrom:)
toma como parámetro un CGRect
que define sus bordes. Si prueba ahora, verá que el avión aún se cae; sin embargo, interactúa con este bucle de borde y ya no se sale de la escena. También rebota e incluso gira un poco de lado. Este es el poder del motor de física: obtienes toda esta funcionalidad de manera gratuita. Escribir algo así por tu cuenta sería bastante complejo.
Bounciness
Hemos visto que el avión rebota y gira de lado. Puede controlar el rebote y si el cuerpo físico permite la rotación. Ingrese lo siguiente en Example5.swift.
let player = SKSpriteNode(imageNamed: "enemy1") override func didMove(to view: SKView) { ... player.physicsBody = SKPhysicsBody(texture: player.texture!, size: player.size) player.physicsBody?.restitution = 0.8 player.physicsBody?.allowsRotation = false player.position = CGPoint(x: size.width/2 , y: size.height - player.size.height) addChild(player) physicsWorld.gravity = CGVector(dx:0, dy: 0) physicsBody = SKPhysicsBody(edgeLoopFrom: frame) } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { ... physicsWorld.gravity = CGVector(dx:0 , dy: -9.8) }
Si pruebas ahora, verás que el player
es muy activo y tarda unos segundos en calmarse. También notará que ya no gira. La propiedad de restitution
toma un número de 0.0 (menos animoso) a 1.0 (muy animoso), y la propiedad allowsRotation
es un booleano simple.
Fricción
En el mundo real, cuando dos objetos se mueven uno contra el otro, hay un poco de fricción entre ellos. Puedes cambiar la cantidad de fricción que tiene un cuerpo físico, esto equivale a la 'rugosidad' del cuerpo. Esta propiedad debe estar entre 0.0 y 1.0. El valor predeterminado es 0.2. Agregue lo siguiente a Example6.swift.
let player = SKSpriteNode(imageNamed: "enemy1") override func didMove(to view: SKView) { … player.physicsBody = SKPhysicsBody(texture: player.texture!, size: player.size) player.physicsBody?.allowsRotation = false player.physicsBody?.restitution = 0.0 player.name = "player" player.position = CGPoint(x: size.width/2 , y: size.height - player.size.height) addChild(player) let rectangle = SKSpriteNode(color: .blue, size: CGSize(width: size.width, height: 20)) rectangle.physicsBody = SKPhysicsBody(rectangleOf: rectangle.size) rectangle.physicsBody?.isDynamic = false rectangle.zRotation = 3.14 * 220 / 180 rectangle.physicsBody?.friction = 0.0 rectangle.position = CGPoint(x: size.width/2, y: size.width/2) addChild(rectangle) physicsWorld.gravity = CGVector(dx:0, dy: 0) physicsBody = SKPhysicsBody(edgeLoopFrom: frame) } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { ... physicsWorld.gravity = CGVector(dx:0 , dy: -9.8) }
Aquí creamos un Sprite rectangular y establecemos la propiedad de fricción
en su physicsBody
en 0.0. Si prueba ahora, verá que el avión se desliza rápidamente hacia abajo en el rectángulo girado. Ahora cambie la propiedad de fricción a 1.0 y vuelva a probar. Verás que el avión no se desliza por el rectángulo con la misma rapidez. Esto es por la fricción. Si quisieras que se moviera aún más lentamente, podrías aplicar más fricción a la fhysicBody
del player
. Cuerpo (recuerda que el valor predeterminado es 0.2).
Densidad y masa
Hay un par de otras propiedades que puede cambiar en el cuerpo de la física, como la density
y la masa. Las propiedades de densidad y mass
están interrelacionadas, y cuando cambia una, la otra se vuelve a calcular automáticamente. Cuando crea por primera vez un cuerpo de física, la propiedad del área
del cuerpo se calcula y nunca cambia después (solo es de lectura). La densidad y la masa se basan en la fórmula mass = density * area
.
Cuando tiene más de un nodo en una escena, la densidad y la masa afectarían la simulación de cómo los nodos rebotan e interactúan. Piense en una pelota de baloncesto y una de boliche; son aproximadamente del mismo tamaño, pero una bola de boliche es mucho más densa. Cuando colisionan, la pelota cambiará de dirección y velocidad mucho más que la bola de boliche.
Fuerza e Impulso
Puedes aplicar fuerzas e impulsos para mover el cuerpo físico. Un impulso se aplica inmediatamente y solo una vez. Una fuerza, por otro lado, generalmente se aplica para un efecto continuo. La fuerza se aplica desde el momento en que agrega la fuerza hasta que se procesa el siguiente cuadro de la simulación. Para aplicar una fuerza continua, necesitaría aplicarla en cada cuadro. Agregue lo siguiente a Example7.swift.
let player = SKSpriteNode(imageNamed: "enemy1") override func didMove(to view: SKView) { ... player.physicsBody = SKPhysicsBody(texture: player.texture!, size: player.size) player.physicsBody?.allowsRotation = false player.name = "player" player.position = CGPoint(x: size.width/2 , y: size.height - player.size.height) addChild(player) physicsWorld.gravity = CGVector(dx:0, dy: 0) physicsBody = SKPhysicsBody(edgeLoopFrom: frame) } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { ... physicsWorld.gravity = CGVector(dx:0 , dy: -9.8) if(touchedNode.name == "player"){ player.physicsBody?.applyImpulse(CGVector(dx:0 , dy: 100)) } }
Ejecute el proyecto y espere hasta que el jugador descanse en la parte inferior de la pantalla y luego toque el reproductor. Verás que el jugador vuela por la pantalla y finalmente vuelve a descansar en la parte inferior. Aplicamos un impulso usando el método applyImpulse (_:)
, que toma como parámetro un CGVector
y se mide en Newton-segundos.
¿Por qué no probar lo contrario y agregar una fuerza al nodo reproductor? Recuerde que necesitará agregar la fuerza continuamente para que tenga el efecto deseado. Un buen lugar para hacerlo es en el método de actualización de la update(_:)
. Además, es posible que desee intentar aumentar la propiedad de restitución en el reproductor para ver cómo afecta la simulación.
override func update(_ currentTime: TimeInterval) { //APPLY FORCE HERE }
Detección de colisiones
El motor de física tiene un sistema robusto de detección de colisión y contacto. Por defecto, dos nodos con cuerpos de física pueden colisionar. Has visto esto en ejemplos anteriores; no se requiere ningún código especial para indicar a los objetos que interactúen. Sin embargo, puede cambiar este comportamiento estableciendo una 'categoría' en el cuerpo de física. Esta categoría se puede usar para determinar qué nodos colisionarán entre sí y también se puede usar para informarle cuándo ciertos nodos hacen contacto.
La diferencia entre un contacto y una colisión es que se utiliza un contacto para saber cuándo dos cuerpos de física se están tocando entre sí. Una colisión, por otro lado, evita que dos cuerpos de física se crucen en el espacio del otro -cuando el motor de física detecta una colisión, aplicará impulsos opuestos para separar los objetos otra vez. Hemos visto colisiones en acción con el jugador y el bucle de borde y el jugador y el rectángulo de los ejemplos anteriores.
Tipos de physicBodies
.
Antes de continuar con la configuración de nuestras categorías para los cuerpos de física, debemos hablar sobre los tipos de physicBodies
. Hay tres:
- Un dynamic volume simula objetos con volumen y masa. Estos objetos se ven afectados por fuerzas y colisiones en el mundo de la física (por ejemplo, el avión en los ejemplos anteriores).
- Un static volume no se ve afectado por fuerzas y colisiones. Sin embargo, debido a que sí tiene volumen, otros cuerpos pueden rebotar e interactuar con él. Establece la propiedad
isDynamic
del cuerpo de física en falso para crear un volumen estático. Estos volúmenes nunca son movidos por el motor de física. Vimos esto en acción más temprano con el ejemplo seis, donde el avión interactuó con el rectángulo, pero el rectángulo no se vio afectado por el plano ni por la gravedad. Para ver lo que quiero decir, vuelva al ejemplo seis y elimine la línea de código que establecerectangle.physicsBody? .isDynamic = false
. - El tercer tipo de cuerpo de física es un edge, que es un cuerpo estático, sin volumen. Hemos visto este tipo de cuerpo en acción con el bucle de borde que creamos alrededor de la escena en todos los ejemplos anteriores. Los bordes interactúan con otros cuerpos basados en el volumen, pero nunca con otro borde.
Las categorías usan 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. Esto no debería representar un problema para la mayoría de los juegos, pero es algo a tener en cuenta.
Creando Categorías
Cree un nuevo archivo Swift yendo a File> New> File y asegurándose de que el Swift File esté resaltado.

Ingrese PhysicsCategories como el nombre y presione Create.

Ingrese lo siguiente en el archivo que acaba de crear.
struct PhysicsCategories { static let Player : UInt32 = 0x1 << 0 static let EdgeLoop : UInt32 = 0x1 << 1 static let Redball : UInt32 = 0x1 << 2 }
Usamos una estructura de PhysicsCategories
para crear categorías para Player
, EdgeLoop
y RedBall
. Estamos utilizando el cambio de bit para activar los bits.
Ahora ingrese lo siguiente en Example8.swift.
... let player = SKSpriteNode(imageNamed: "enemy1") var dx = -20 var dy = -20 override func didMove(to view: SKView) { player.physicsBody = SKPhysicsBody(texture: player.texture!, size: player.size) player.physicsBody?.allowsRotation = false player.physicsBody?.restitution = 0.0 player.physicsBody?.categoryBitMask = PhysicsCategories.Player player.physicsBody?.contactTestBitMask = PhysicsCategories.RedBall player.physicsBody?.collisionBitMask = PhysicsCategories.EdgeLoop player.position = CGPoint(x: size.width/2 , y: size.height - player.size.height) addChild(player) player.physicsBody?.applyImpulse(CGVector(dx: dx, dy: dy)) let redBall = SKShapeNode(circleOfRadius: 100) redBall.fillColor = SKColor.red redBall.position = CGPoint(x: size.width/2, y: size.height/2) redBall.physicsBody = SKPhysicsBody(circleOfRadius: 100) redBall.physicsBody?.isDynamic = false redBall.physicsBody?.categoryBitMask = PhysicsCategories.RedBall addChild(redBall) physicsWorld.gravity = CGVector(dx:0, dy: 0) physicsBody = SKPhysicsBody(edgeLoopFrom: frame) physicsWorld.contactDelegate = self physicsBody?.categoryBitMask = PhysicsCategories.EdgeLoop physicsBody?.contactTestBitMask = PhysicsCategories.Player } 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.RedBall != 0)){ print("Player and RedBall Contact") } if((firstBody.categoryBitMask & PhysicsCategories.Player != 0) && (secondBody.categoryBitMask & PhysicsCategories.EdgeLoop != 0)){ print ("Player and EdgeLoop Contact") dx *= -1 dy *= -1 player.physicsBody?.applyImpulse(CGVector(dx: dx, dy: dy)) } }
Aquí creamos el player
como de costumbre, y creamos dos variables dx
y dy
, que se usarán como componentes de un CGVector
cuando aplicamos un impulso al player
.
Dentro de didMove(to:)
, configuramos el reproductor y agregamos la categoríaBitMask
, contactBitMask
y collisionBitMask
. La categoryBitMask
debe tener sentido, este es el reproductor, por lo que lo configuramos en PhysicsCategories.Player
. Nos interesa saber cuándo el jugador entra en contacto con la redball
, por lo que configuramos contactBitMask
en PhysicsCategories.RedBall
. Por último, queremos que colisione y se vea afectado por la física con el bucle de borde, por lo que establecemos su collisionBitMask
en PhysicsCategories.EdgeLoop
. Finalmente, aplicamos un impulso para que se mueva.
En el redBall
, simplemente establecemos su categoryBitMask
. Con edgeLoop
, establecemos su categoryBitMask
, y como estamos interesados en que el player
haga contacto con él, establecemos su contactBitMask
.
Al configurar el contactBitMask
y collisionBitMask
, solo uno de los cuerpos necesita hacer referencia al otro. En otras palabras, no es necesario configurar ambos cuerpos como contacto o colisión con el otro.
Para el edgeLoop
, lo configuramos para contactar con el jugador. Sin embargo, podríamos haber configurado el reproductor para que interactúe con EdgeLoop
usando el operador bit a bit o (|
). Con este operador, puede configurar múltiples máscaras de contacto o de colisión. Por ejemplo:
player.physicsBody?.contactTestBitMask = PhysicsCategories.RedBall | PhysicsCategories.EdgeLoop
Para poder responder cuando dos cuerpos hacen contacto, debe implementar el protocolo SKPhysicsContactDelegate
. Es posible que haya notado esto en el código de ejemplo.
class Example8: SKScene,SKPhysicsContactDelegate{ … physicsWorld.contactDelegate = self ...
Para responder a eventos de contacto, puede implementar los métodos didBegin didBegin(_:)
y didEnd(_:)
. Serán llamados cuando los dos objetos hayan comenzado a hacer contacto y cuando hayan terminado el contacto respectivamente. Seguiremos con el método didBegin(_:)
para este tutorial.
Aquí está el código una vez más para el método didBegin (_:)
.
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.RedBall != 0)){ print("Player and RedBall Contact") } if((firstBody.categoryBitMask & PhysicsCategories.Player != 0) && (secondBody.categoryBitMask & PhysicsCategories.EdgeLoop != 0)){ print ("Player and EdgeLoop Contact") dx *= -1 dy *= -1 player.physicsBody?.applyImpulse(CGVector(dx: dx, dy: dy)) } }
Primero, configuramos dos variables firstBody
y secondBody
. Los dos cuerpos en el parámetro de contacto no se pasan en un orden garantizado, por lo que usaremos una instrucción if para determinar qué cuerpo tiene una contactBitMask
más baja y establecer eso en firstBody
.
Ahora podemos verificar y ver qué cuerpos físicos están haciendo contacto. Comprobamos para ver a qué cuerpos de física nos enfrentamos mediante anding (&&
) la category BitMask
con la PhysicsCategory
que configuramos anteriormente, y si el resultado es distinto de cero, sabemos que tenemos el cuerpo correcto.
Finalmente, imprimimos qué cuerpos están haciendo contacto. Si fue el jugador y EdgeLoop
, también invertimos las propiedades dx
y dy
aplicamos un impulso al jugador. Esto mantiene al jugador en constante movimiento.
Esto concluye nuestro estudio del motor de física de SpriteKit. Hay muchas cosas que no estaban cubiertas, como SKPhysicsJoint, por ejemplo. El motor de física es muy robusto, y le sugiero que lea todos los aspectos del mismo, comenzando con SKPhysicBody.
Conclusión
En este post aprendimos sobre acciones y física, dos partes muy importantes del marco de SpriteKit. Observamos muchos ejemplos, pero todavía hay mucho que puedes hacer con acciones y física, y la documentación es un excelente lugar para aprender.
En la siguiente y última parte de esta serie, reuniremos todo lo que hemos aprendido haciendo un juego simple. ¡Gracias por leer y te veré allí!
Mientras tanto, ¡consulta algunos de nuestros cursos integrales sobre el desarrollo de Swift y SpriteKit!
- SwiftCodifique un juego de desplazamiento lateral con Swift 3 y SpriteKitDerek Jensen
- iOSVaya más allá con Swift: animación, redes y controles personalizadosMarkus Mühlberger
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Update me weeklyEnvato Tuts+ tutorials are translated into other languages by our community members—you can be involved too!
Translate this post