Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. iOS SDK

Crea un Juego de Blackjack en Swift 3 y Spritekit

by
Difficulty:IntermediateLength:LongLanguages:

Spanish (Español) translation by Rafael Chavarría (you can also view the original English article)

Final product image
What You'll Be Creating

En este tutorial crearás un juego de blackjack en SpriteKit usando Swift 3. Aprenderás sobre implementar toque, crear animaciones visuales, y muchos otros conceptos que serán útiles cuando construyas un juego SpriteKit.

1. Creando el Proyecto e Importando Recursos

Abre Xcode y elige Create a new Xcode project o elige New > Project... desde el menú File. Asegúrate de que iOS está seleccionado y elige la plantilla Game.

new_project

Después, elige lo que desees para el Product Name, Organization Name, y Organization Identifier. Asegúrate de que Language está establecido a Swift, Game Technology está establecido a SpriteKit, y Devices está establecido a iPad.

project_options

Especifica una ubicación para guardar los archivos de proyecto y da clic en Create.

Importando las Clases Helper

Descarga el repositorio GitHub para este proyecto. Dentro de este verás una carpeta classes. Abre esta carpeta y arrastra todos los archivos a la carpeta que tiene el mismo nombre o como hayas nombrado a tu proyecto, por ejemplo, blackjack. Asegúrate de que Copy items if needed está marcado a´si como el objetivo principal en la lista de objetivos.

File options with Copy items if needed box checked

Importando las Imágenes

También dentro del repositorio tutorial GitHub está una carpeta llamada tutorial images. Dentro del navegador de proyecto, abre Assets.xcassets y arrastra las imágenes a la barra lateral. Xcode creará automáticamente atlases de textura desde estas imágenes.

Tutorial images folder in GitHub

2. Configurando el Proyecto

Dentro del navegador de proyecto hay dos archivos que puedes borrar (Gamescene.sks y Actions.sks). Borra estos dos archivos y selecciona Mover A Papelera. Estos archivos son usados por el editor de escena integrado de Xcode, el cuál puede ser usado para diseñar visualmente tus proyectos. Estaremos creando todo a través de código, así que estos archivos no son necesarios.

Abre GameViewController.swift, borra sus contenidos, y reemplázalos con lo siguiente.

La clase GameViewController hereda de UIViewController y tendrá un SKView como su vista. Dentro del método viewDidLoad, degradamos la propiedad view a una instancia SKView, usando el operador de tipo as!, y configuramos la vista.

Si ejecutaras este proyecto cuando lo creaste, notarías texto en la parte inferior derecha de la pantalla. Para esto son las propiedades showsFPS y showsNodeCount, mostrando los cuadros por segundo a los que el juego se está ejecutando y el número de SKNodes visibles en la escena. No necesitamos esta información, así que los establecemos a false.

La propiedad ignoreSiblingOrder es usada para determinar el orden de dibujo de los SKNodes dentro del juego. Establecemos este a false aquí porque necesitamos que nuestros SKNodes se dibujen en el orden en que son agregados a la escena.

Por último, establecemos el modo de escala a .aspectFill, el cuál causará que el contenido de la escena se escale para llenar la pantalla entera. Después invocamos el método presentScene(_:) en el skView que presenta o "muestra" la escena.

Después, borra todo en GameScene.swift y reemplázalo con lo siguiente.

Ahora puedes probar el proyecto, y deberías ver una pantalla negra vacía. En el siguiente paso comenzaremos agregando contenido a nuestra escena.

3. Variables y Constantes

Ingresa el siguiente código al inicio de la clase GameScene justo debajo de donde GameScene hereda de SKScene.

Aquí estamos creando un número de SKSpriteNodes. Los SKSpriteNodes son usados para crear un nodo de color, o más comúnmente de un SKTexture, el cuál es más frecuentemente una imagen. Usamos el inicializador de conveniencia init(color:size:) para crear un  nodo transparente coloreado moneyContainer. El moneyContainer será usado para retener el dinero que el jugador apuesta, y al final de cada ronda lo animaremos moviéndolo hacia quien haya ganado el juego. Colocar todo el dinero en este solo nodo hace más fácil animar todo el dinero de una vez.

Después, creamos las contantes dealBtn, hitBtn, y standBtn. Como los nombres sugieren, estos serán usados en el juego para repartir, ir y quedarse respectivamente. Estamos usando el inicializador de conveniencia init(imageNamed:), el cuál toma como parámetro el nombre de la imagen sin una extensión.

Después creamos las tres constantes money10money25, y money50, que son del tipo Money. Money es es una clase personalizada que extiende a SKSpriteNode y dependiendo del tipo de moneyValue pasado como parámetro crea uno de tres tipos diferentes de dinero. El parámetro moneyValue es de tipo MoneyValue, que es un enum. Echa un vistazo a la clase Money en el repositorio GitHub para ver cómo funciona todo esto.

Por último creamos un SKLabelNode usando el inicializador de conveniencia init(text:) que toma como parámetro el texto a ser mostrado dentro de la etiqueta.

4. Implementando setupTable

Agrega lo siguiente debajo de la función didMove(to:).

Aquí inicializamos una constante table y la agregamos a la escena usando addChild(_:) que toma como un parámetro al nodo a agregar a la escena. Establecemos la position de table dentro de la escena y establecemos su zPosition a -1. La propiedad zPosition controla el orden en que los nodos son dibujados. El número más bajo es dibujado primero, con números más altos siendo dibujados en orden. Debido a que necesitamos la table debajo de todo lo demás, establecemos su zPosition a -1. Esto asegura que es dibujado antes que cualquier otro nodo.

También agregamos el moneyContainer y instructionText a la escena. Establecemos el fontColor del instructionText a negro (el valor por defecto es blanco).

Actualiza didMove(to:) a lo siguiente.

El método didMove(to:) es llamado inmediatamente después de que la escena es presentada por la vista. Generalmente, aquí es donde harás la configuración para tu escena y creas tus recursos. Si pruebas ahora, deberías ver que table e instructionText han sido agregados a la escena. El moneyContainer está ahí también pero no puedes verlo porque lo creamos con un color transparente.

5. Implementando setupMoney

Agrega lo siguiente debajo del método setupTable.

Aquí simplemente agregamos las instancias de dinero y establecemos su posición. Invoca este método dentro de didMove(to:).

6. Implementando setupButtons

Agrega lo siguiente debajo del método setupMoney que creaste en el paso anterior.

Como hicimos con los dineros en el paso anterior, agregamos los botones y establecemos sus posiciones. Aquí usamos la propiedad name para que podamos identificar cada botón a través de código. También establecemos hitBtn y standBtn para estar ocultos, o invisibles, estableciendo la propiedad isHidden a true.

Ahora invoca este método dentro de didMove(to:).

Si ejecutas la aplicación ahora, deberías ver que las instancias de dinero y botones han sido agregadas a la escena.

7. Implementando touchesBegan

Necesitamos implementar el método touchesBegan(_:with:) para poder saber cuando los objetos en la escena han sido tocados. Este método es llamado cuando uno o más dedos han tocado la pantalla. Agrega lo siguiente dentro de touchesBegan.

La propiedad multiTouchEnabled de la la vista de la escena está establecida a false por defecto, lo que significa que la vista solo recibe el primer toque de una secuencia multi-táctil. Con esta propiedad deshabilitada, puedes recoger el toque usando la primera propiedad first computada de los toques establecidos ya que solo hay un objeto en el conjunto.

Podemos obtener la touchLocation dentro de la escena por la propiedad location del toque. Podemos entonces descifrar cuál nodo fue tocado invocando atPoint(_:) y pasando la touchLocation.

Revisamos si la propiedad name de touchNode es igual a "money", y si lo es sabemos que han tocado sobre una de las tres instancias de dinero. Inicializamos una constante money degradando el touchedNode a Money, y después llamamos el método bet invocando al método getValue() en la constante money.

8. Implementando bet

Ingresa lo siguiente debajo de la función setupButtons que creaste en el paso de arriba.

Primero nos aseguramos de que el jugador no está intentando más dinero del que tiene, y si lo hacen simplemente regresamos de la función. De otro modo, agregamos el betAmount al pot, creamos una constante tempMoney, establecemos su anchorPoint a (0,0), y lo agregamos al moneyContainer. Después establecemos su position y ocultamos el dealBtn estableciendo su propiedad isHidden a false.

Los SKSpriteNodes tienen una propiedad anchorPoint que tiene por defecto (0.5,0.5). El sistema de coordenadas coloca (0,0) en la parte inferior izquierda y (1,1) en la parte superior derecha. Cambiarías esta propiedad de su valor por defecto si estuvieras rotando el SKSpriteNode y quisieras rotarlo alrededor de un punto diferente. Por ejemplo, si cambiaras la propiedad anchorPoint a (0,0) entonces el SKSpriteNode rotaría desde tu esquina inferior izquierda. Frecuentemente cambiarás esta propiedad para ayudar con el posicionamiento, como lo tenemos aquí.

Necesitamos crear una instancia de las clases Pot y Player para que este código funcione. Agrega lo siguiente junto con las otras contantes y variables.

Si tu pruebas ahora puedes presionar cualquiera de los dineros y agregarlo al moneyContainer.

9. Implementando deal

Agrega lo siguiente junto con el resto de tus constantes y variables.

El arreglo allCards será usado para retener todas las cartas dentro del juego. Esto hará sencillo ciclar a través de ellas y quitarlas de la escena todo en un intento. Las constantes dealerCardsY and playerCardsY son las posiciones de las cartas en el eje y. Esto nos ayudará cuando coloquemos nuevas cartas. El currentPlayerType es usado para indicar a quién repartir después. Será igual a dealer o player1.

Dentro de didMove(to:), agrega lo siguiente.

En el código anterior, inicializamos currentPlayerType a una instancia sin nombre de la clase Player. Aquí lo establecemos a player1.

Necesitamos crear un nuevo mazo de cartas antes de implementar el método deal. Ingresa lo siguiente dentro de setupTable.

Ahora podemos implementar la función deal. Agrega lo siguiente debajo del método bet.

Este método es bastante grande, pero necesario para implementar la lógica de repartición. Tomémoslo paso a paso. Inicializamos una constante tempCard a una instancia de Card, establecemos su posición, y la agregamos a la escena. Necesitamos esta carta dibujada en una posición zPosition mayor a 0, debido a que la primera carta del repartidor necesita estar en 0. Establecemos esto a un número arbitrario---100 funcionará. También creamos una constante newCard invocando el método getTopCard() de deck.

Después, inicializamos dos variables, wichPosition y whichHand, y después ejecutamos alguna lógica para determinar sus valores finales. Después agregamos newCard a la mano apropiada (ya sea el jugador o del repartidor). La constante xPos determina la posición x final de la carta una vez que se ha terminado de animar.

La clase SKAction tiene un número de métodos de clase que puedes llamar para cambiar las propiedades de un nodo tal como posición, escala, y rotación. Aquí llamamos al método move(to:duration:), el cuál moverá el nodo de una posición a otra. Sin embargo, para ejecutar en realidad la SKAction, tienes que invocar el método run(_:) de un nodo y pásalo al SKAction como un parámetro. Aquí, sin embargo, estamos invocando el método run(_:completion:), el cuál causará que el código se ejecute dentro del cierre de término después de que la acción complete ejecución.

Después de que la acción se ejecute hasta el término, permitimos al jugador apostar invocando setCanBet(canBet:) en la instancia de player1. Después revisamos si el currentPlayerType es una instancia de Dealer, y revisamos que el dealer solo tenga una carta invocando hand.getLength(). Si este es el caso, establecemos la primera carta del dealer, que necesitaremos al final del juego.

Debido a que la primera carta del dealer siempre está boca a bajo hasta el final del juego, necesitamos una referencia para la primera carta para que podamos mostrarla después. Agregamos esta carta al arreglo allCards para que podamos removerla después, y después establecemos su propiedad zPositon a 0 ya que necesitamos esta carta debajo de las otras cartas. (Recuerda que las otras cartas tienen z-position 100.)

Si el currentPlayerType no es una instancia de Dealer, y la longitud de la mano no es igual a 1, entonces quitamos la tempCard y ponemos la newCard en la misma posición, asegurándonos de establecer su zPosition a 100.

De acuerdo a las reglas del blackjack, tanto el repartidor como el jugador tienen dos cartas para comenzar el juego. Aquí estamos revisando cuál es el currentPlayerType y cambiándolo al opuesto. Debido a que el repartidor tiene menos de dos cartas, invocamos a la función deal de nuevo. De otro modo, revisamos que tanto el dealer como el player1 tengan dos cartas, y si este es el caso, revisamos para ver si cualquier tiene cartas con un valor total de 21---una mano ganadora. Si cualquiera de los dos tiene 21 entonces el juego termina porque uno de ellos ha obtenido blackjack. Si ninguno de los dos tiene 21 entonces mostramos los botones standBtn y hitBtn y el juego continua.

Las reglas del blackjack establecen que el dealer debe quedarse en 17 o mayor. Las siguientes pocas líneas de código revisan si el valor de la mano del raprtidor es menos de 17 y si es así invoca al método deal. Si es 17 o mayor entonces el juego termina. Por último, si el valor de la mano del player1 es mayor que 21 entonces el juego termina porque se han pasado.

¡Esto fue mucha lógica que recorrer! Si cualquier cosa es poco clara, solo léela de nuevo y toma tu tiempo para entenderla.

Después, necesitamos implementar el método gameover.

Necesitamos poder decir cuando el usuario ha presionado el botón repartir. Agrega el siguiente código al método touchesBegan(_:with:).

10. Implementando doGameOver

Después, ingresa lo siguiente debajo del método deal que creaste en el paso de arriba.

Obtenemos la posición x y y de la primer carta en el arreglo allCards, que es la primer carta del repartidor. Después instanciamos una constante tempCard invocando getFirstCard sobre el repartidor. ¿Recuerdas que establecimos esta Card anteriormente en el método deal? Aquí lo agregamos a la escena, establecemos su posición usando las constantes tempCardX y tempCardY, y establecemos tu zPosition a 0 para que estuviera debajo de las otras cartas.

Necesitamos saber quién ganó el juego, así que inicializamos una variable winner estableciéndola igual a player1, aunque esto podría cambiar dependiendo si el dealer en realidad ganó el juego.

Después recorremos alguna lógica para determinar quién ganó el juego. Si el parámetro hasBlackjack fuera verdadero, entonces deducimos quien ganó y devolvemos de la función. De otro modo, continuamos con la lógica para deducir quién ganó el juego. No voy a recorrer paso a paso esta lógica ya que debería ser clara de entender. Sin importar quién ganó, invocamos moveMoneyContainer(position:), el cuál toma como parámetro la posición a la cuál mover el contenedor de dinero. Esto será la posición y de las cartas del dealer o del player1.

11. Implementando moveMoneyContainer

Ingresa el siguiente código debajo del método doGameOver.

El método moveMoneyContainer(position:) mueve el moneyContainer a quien haya ganado el juego, ya sea el jugador o el repartidor. Cuando la SKAction se completa, invocamos resetMoneyContainer.

12. Implementando resetContainer

El método resetMoneyContainer remueve todos los dineros invocando el método removeAllChildren(), reestablece el moneyContainer a su posición original, e invoca a newGame.

13. Implementando newGame

Agrega lo siguiente debajo del método resetMoneyContainer que implementaste en el paso anterior.

Aquí reiniciamos todas las variables necesarias y removemos todas las cartas de la escena ciclando a través del arreglo allCards e invocando removeFromParent() sobre cada elemento.

14. Implementando hitBtn y standBtn

Todo lo que resta para completar nuestro juego es implementar los toques en los botones hitBtn y standBtn. Ingresa lo siguiente dentro del método touchesBegan(_:with:).

Y ahora implementaremos los métodos llamados en el manejador de evento. Ingresa los siguientes dos métodos debajo del método newGame.

Dentro del método hit, nos aseguramos de que ese jugador puede apostar, y si ese es el caso, establecemos el currentPlayerType a player1, y después invocamos el método deal y evitamos que el jugador siga apostando.

Dentro del método stand, invocamos setYielding sobre player1, pasando true. Después revisamos si el valor de la mano del repartidor es menos a 17, y si ese es el caso llamamos a deal, y si el valor de la mano del repartidor es 17 o mayor significa que el juego ha terminado.

Ahora puedes probar el juego completado.

Conclusión

Este fue un tutorial largo con mucha lógica escondida en el método deal. No implementamos usando el Pot y agregando en sustraer dinero del banco del jugador. ¿Por qué no intentas eso como un ejercicio para terminar la aplicación?

Ahora tienes un juego blackjack del cuál estar orgulloso. Gracias por leer, y espero que encuentres este tutorial útil. Mientras estás aquí, ¡revisa algunos de nuestros otros cursos y tutoriales sobre programar aplicaciones con Swift y SpriteKit!

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.