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

Conceptos básicos de SpriteKit: acciones y física

by
Difficulty:IntermediateLength:LongLanguages:
This post is part of a series called SpriteKit Basics.
SpriteKit Basics: Sprites
SpriteKit Basics: Putting It All Together

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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á.

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.

circle body

Ahora agregue lo siguiente a Example2.swift.

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.

texture body

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.

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.

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.

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.

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.

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.

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:

  1. 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).
  2. 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 establece rectangle.physicsBody? .isDynamic = false.
  3. 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.

New File

Ingrese PhysicsCategories como el nombre y presione Create.

Physics Categories

Ingrese lo siguiente en el archivo que acaba de crear.

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.

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:

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.

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 (_:).

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!


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.