Spanish (Español) translation by Andrea J (you can also view the original English article)

En este post vamos a construir un juego sencillo desde cero. En el camino, tocaremos algunos de los aspectos más importantes de la librería de SpriteKit.
Esta publicación se basa en lo que hemos aprendido anteriormente en la serie de Conceptos básicos del SpriteKit. Si quieres actualizar tus conocimientos sobre el SpriteKit, echa un vistazo a algunas de mis otras publicaciones.
- SpriteKitPresentación del SpriteKitJames Tyner
- SpriteKitConceptos básicos del SpriteKit: NodosJames Tyner
- SDK de iOSConceptos básicos del SpriteKit: SpritesJames Tyner
- SpriteKitConceptos básicos del SpriteKit: Acciones y FísicaJames Tyner
Nuevo proyecto
Abre Xcode e inicia un nuevo proyecto desde el menú Archivo > Nuevo > Proyecto. Asegúrate de que iOS esté seleccionado y elige la opción Juego como plantilla.

Asígnale un nombre a tu proyecto y asegúrate de que la opción Idioma esté establecida en Swift, Tecnología de juego esté establecida en SpriteKit y Dispositivos establecido 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 la introducción, una escena del juego principal, y una escena para mostrar las puntuaciones altas, etc.
Para este ejemplo, solo necesitamos una introducción y una escena de juego principal, ya que no realizaremos un seguimiento de las vidas, puntuaciones, etc. SpriteKit ya viene con una escena cuando creas un nuevo proyecto, así que solo necesitamos una escena de introducción.
En el menú de Xcode, elige Archivo > Nuevo > Archivo. Asegúrate de que la opción de iOS esté seleccionada y elige Cocoa Touch Class.

Asígnale un nombre a la clase StartGameScene y asegúrate de que la opción Subclase de esté establecida en SKScene y Lenguaje esté establecido en Swift.

Configuración de GameViewController
Abre GameViewController.swift. Elimina todo lo que esté en ese archivo y sustitúyelo por lo siguiente.
import UIKit import SpriteKit import GameplayKit class 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) } override var prefersStatusBarHidden: Bool { return true } }
Cuando creas un nuevo proyecto, el archivo GameViewController.swift está configurado para cargar GameScene.sks desde el disco. GameScene.sks se utiliza junto con el editor de escenas integrado de SpriteKit, que te permite diseñar visualmente tus proyectos. No usaremos GameScene.sks, y en su lugar crearemos todo a partir del código, así que aquí iniciamos una nueva instancia de StartGameScene y la mostramos.
Crear la escena de introducción
Agrega lo siguiente al StartGameScene.swift que acabamos de crear.
import UIKit import SpriteKit class StartGameScene: SKScene { override func didMove(to view: 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) } override func touchesBegan(_ touches: Set<UITouch>, with event: 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) } }}
Esta escena es bastante simple. En el método didMove
agregamos un logotipo y un botón. Luego, en touchesBegan
detectamos toques en el nuevo botón del juego y respondemos cargando la escena principal GameScene
.
Planificación de las clases de juegos
Lo siguiente que me gusta hacer al crear un nuevo juego es decidir qué clases necesitaré. Puedo decir de inmediato que necesitaré la clase Player
y la clase Enemy
. Ambas clases extenderán el SKSpriteNode
. Creo que para este proyecto vamos a crear el jugador y las balas del enemigo directamente dentro de sus respectivas clases. Si quieres puedes hacer clases separadas de las balas del jugador y de las balas de los enemigos, y te sugiero que lo hagas por tu cuenta.
Por último, están las islas. Estas no tienen ninguna funcionalidad específica sino moverse hacia abajo de la pantalla. En este caso, dado que son solo adornos, creo que también está bien no crear una clase, y solo crearlas en el GameScene
principal.
Creación de la clase Player
En el menú de Xcode, elige la opción Archivo > Nuevo > Archivo. Asegúrate de que iOS esté seleccionado y elige Cocoa Touch Class.

Asegúrate de que Clase esté configurado en Jugador, Subclase de: esté configurado en SKSpriteNode y Lenguaje esté configurado en Swift.

Ahora agrega lo siguiente 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])) } }
Dentro del método init()
, configuramos physicsBody
e invocamos generateBullets()
. El método generateBullets
llama repetidamente a fireBullet()
, que crea una bala, establece su physicsBody
y muévelo hacia abajo de la pantalla.
Cuando el jugador pierde una vida, se invoca el método respawn()
. Dentro del método respawn
, desvanecemos el plano dentro y fuera cinco veces, durante el cual el jugador será invencible. Una vez que el jugador ha agotado todas las vidas, se invoca el método kill()
. El método kill simplemente carga el StartGameScene
.
Creación de la clase Enemy
Elige la opción Archivo > Nuevo > Archivo en el menú de Xcode. Asegúrate de que iOS esté seleccionado y elige Cocoa Touch Class.

Asegúrate de que Clase esté establecido en Enemy, Subclase de: esté establecido en SKSpriteNode y Lenguaje esté establecido en Swift.

Agrega lo siguiente a Enemy.swift.
import UIKit import SpriteKit class Enemy: SKSpriteNode { init() { let texture = SKTexture(imageNamed: "enemy1") super.init(texture: texture, color: .clear, size: texture.size()) self.name = "enemy" self.physicsBody = SKPhysicsBody(texture: self.texture!, size: self.size) self.physicsBody?.isDynamic = true self.physicsBody?.categoryBitMask = PhysicsCategories.Enemy self.physicsBody?.contactTestBitMask = PhysicsCategories.Player | 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.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(to: 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(moveEnemySequence) } func generateBullets(){ let fireBulletAction = SKAction.run{ [weak self] in self?.fireBullet() } let waitToFire = SKAction.wait(forDuration: 1.5) let fireBulletSequence = SKAction.sequence([fireBulletAction,waitToFire]) let fire = SKAction.repeatForever(fireBulletSequence) run(fire) } }
Esta clase es bastante similar a la clase Player
. Establecemos su physicsBody
e invocamos generateBullets()
. El move()
simplemente mueve al enemigo hacia abajo en la pantalla.
Creación de la escena principal del juego
Elimina todo lo que hay en GameScene.swift y agrega lo siguiente.
import SpriteKit import GameplayKit import CoreMotion class GameScene: SKScene, SKPhysicsContactDelegate { let player = Player() let motionManager = CMMotionManager() var accelerationX: CGFloat = 0.0 override func didMove(to view: 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(_ touches: Set<UITouch>, with event: UIEvent?) { } func addEnemies(){ let generateEnemyAction = SKAction.run{ [weak self] in self?.generateEnemy() } let waitToGenerateEnemy = SKAction.wait(forDuration: 3.0) let generateEnemySequence = SKAction.sequence([generateEnemyAction,waitToGenerateEnemy]) run(SKAction.repeatForever(generateEnemySequence)) } func generateEnemy(){ let enemy = Enemy() addChild(enemy) 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) } }
Creamos una instancia de Player
y una instancia de CMMotionManager
. Usamos el acelerómetro para mover al jugador en este juego.
Dentro del método didMove(to:)
apagamos la gravedad, configuramos el ContactDelegate
, agregamos un loop del borde y establecemos la posición player
antes de agregarla a la escena. Luego invocamos setupAccelerometer()
, que configura el acelerómetro e invocamos los métodos addEnemies()
y generateIslands()
.
El método addEnemies()
llama repetidamente al método generateEnemy()
, el cual creará una instancia de Enemy
y la agregará a la escena.
El método generateIslands()
funciona de forma similar al método addEnemies()
en el sentido de que llama repetidamente a createIsland()
el cual crea un SKSpriteNode
y lo agrega a la escena. Dentro de createIsland()
, también creamos un SKAction
que mueve la isla por la escena.
Dentro del método didBegin(_:)
, verificamos qué nodos están haciendo contacto y respondemos eliminando el nodo apropiado de la escena e invocando player.die()
si es necesario. El método createExplosion()
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 de SpriteKit. Terminamos la serie mostrando lo simple que es poner en marcha un juego básico. Todavía hay algunas mejoras que se podrían hacer, como un HUB, puntuaciones altas, y sonidos (Incluí un par de MP3 que se pueden utilizar para esto en el repositorio). Espero que hayas aprendido algo útil a lo largo de esta serie, ¡y gracias por leer!
Si quieres aprender más sobre la programación de juegos con SpriteKit, ¡echa un vistazo a uno de nuestros cursos de vídeo completos! Aprenderás a crear un juego en SpriteKit de la A a la Z.
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