Veremos una manera sencilla de crear movimientos realistas en nuestros juegos, logrando efectos como aceleración, simulando elasticidad y rebotes.
Vamos a utilizar las bibliotecas Tweener y pygame.
Por último analizaremos en pocas palabras otras estrategias para lograr los mismos efectos usando bibliotecas como rabbyt y cocos.
Este artículo incluye varios ejemplos, te recomiendo que los descargues y los tengas a mano para seguir la lectura:
En los videojuegos cuando queremos representar movimientos, tendemos que simularlos, porque desde el primer momento las posiciones de los objetos dependen de una simple asignación de valor a una variable.
Por ejemplo, un automóvil puede estar situado en una posición determinada del escenario:
auto.x = 100
Si quisiéramos moverlo hacia la derecha, digamos, a la posición 500, el resultado en pantalla sería bastante brusco:
auto.x = 500
Por ese motivo, para simular algo mas parecido a la realidad, el automóvil tendría que moverse paso a paso para llegar de la posición 100 a la 500, por ejemplo dando saltos de unos 10 pixeles:
personaje.x = 110 personaje.x = 120 personaje.x = 130 personaje.x = 140 [...] personaje.x = 480 personaje.x = 490 personaje.x = 500
Ahora bien, esta solución es la que generalmente se suele aplicar en
muchos juegos. Porque resulta muy simple tomar una variable e incrementar su
valor con sentencias como posicion_x += 10
o similares.
Pero puede que tu videojuego requiera algo mas complicado que lo anterior, imagina que tu automóvil puede frenar poco a poco, o acelerar de manera brusca para ganar una carrera donde hay muchos otros objetos moviéndose…
Entonces, en un escenario diferente la solución inicial y mas sencilla puede que te resulte difícil de controlar, reutilizar y mejorar.
Las interpolaciones son funciones matemáticas que tienen como objetivo obtener todos los valores intermedios entre dos números.
En los videojuegos las posiciones de los personajes, así como su rotación, color o tamaño se representan mediante números, aquí es donde le daremos utilidad a las interpolaciones.
La propuesta de este artículo es que te puedas despreocupar por manipular una a una las interpolaciones. Y en su lugar, buscaremos una forma distinta de logar movimientos desde el código.
Este es un ejemplo de código que esperaríamos ver mas adelante:
auto.mover(interpolacion(desde=0, hasta=500, tiempo=2))
Si, con una sola linea de código podremos iniciar un movimiento y despreocuparnos por detalles…
La solución que aquí veremos consiste en la siguiente idea: en lugar
asignarle un valor fijo a nuestras coordenadas, usaremos una biblioteca
para solicitarle que asigne un valor de manera progresiva, por ejemplo
desde x=100
hasta x=500
pasando por todos los valores intermedios.
En general, no es buena idea escribir una y otra vez las mismas rutinas de código para tu juegos. Hoy existen porciones de código muy bien diseñado que te conviene re-utilizar, como la biblioteca que utilizaremos aquí: Tweener.
Tweener es una biblioteca que nos permite realizar interpolaciones de manera sencilla para aplicar a casi cualquier parte de un juego.
Es muy popular entre los desarrolladores de ActionScript y Flash. Principalmente porque les permite lograr transformaciones de objetos de manera sencilla, despreocupándose por detalles y gestión de tiempo.
Por suerte Tweener no es exclusiva del mundo Flash, dado que Benjamin Harling ha realizado una versión alternativa de esta biblioteca para python, llamada PyTweener.
PyTweener es un módulo de python que se puede obtener de la siguiente web:
Lo único que necesitas es descargar el archivo .py que contiene PyTweener y copiarlo al mismo directorio de tu programa.
Una vez copiado el archivo a tu directorio de aplicación podrás escribir una sentencia como la siguiente:
import pytweener
y podrás acceder a la funcionalidad de Tweener.
En tu juego seguramente tienes una porción de código que se ejecuta el principio del programa y otra sección destinada al bucle de juego.
Para utilizar pytweener tienes que modificar ligeramente esas dos partes del juego.
Estos son los pasos a seguir para integrar Tweener:
Veamos el primer paso, cuando tu juego comience tienes que iniciar el objeto Tweener:
import pytweener tweener = pytweener.Tweener()
Ahora, la instancia del objeto llamada tweener
contendrá todas
las interpolaciones. Este objeto tiene que ser accesible desde
cualquier parte del programa que quiera calcular interpolaciones, por
lo tanto es común que se utilice una variable global para él.
Para crear una interpolación le tienes que indicar al objeto Tweener el tipo de interpolación quieres y sobre que otro objeto debe aplicarla.
El método que te permite crear la interpolación es addTween
, observa
el siguiente ejemplo:
auto.x = 0 tweener.addTween(auto, x=100, tweenTime=5)
Mediante la última sentencia Tweener va a cambiar el valor de x
(la
posición del automóvil) progresivamente desde 0 a 100 en el transcurso
de 5 segundos.
El primer argumento del método indica el objeto que buscamos manipular, el segundo es el atributo que modificaremos y el último es la cantidad de tiempo que durará el movimiento.
Este primer ejemplo es muy simple, pero mas adelante veremos que este mismo método se puede utilizar para interpolaciones mucho mas complejas.
Lo último que necesitamos para que Tweener pueda mover nuestros objetos es integrarlo al bucle de juego.
En una parte interna al bucle de juego tienes que llamar al método
update
e indicarle el tiempo transcurrido desde la última
actualización, por ejemplo:
while True: dt = clock.tick(100) tweener.update(dt / 1000.0) [...]
En el código anterior tienes algo parecido a un bucle de
juego en pygame, la sentencia clock.tick(100)
le indica a pygame
que ejecute el juego como máximo a 100 cuadros por segundo. Y la linea
que sigue le permite a Tweener actualizar la posición de todos
los objetos.
El argumento que se utiliza en update
se divide por 1000.0 para
que siempre que especifiques una interpolación, la unidad de tiempo
se exprese en segundos. Por ejemplo, si solicitas una interpolación con
tweenTime=20
, el movimiento durará 20 segundos.
Veamos un ejemplo sencillo utilizando la biblioteca pygame; te recomiendo
que descargues el ejemplo y ejecutes el programa test_pygame.py
:
python test_con_sprite.py
El resultado en pantalla es un escenario con tres automóviles, que se desplazan de la parte izquierda a la derecha, cada uno de estos automóviles tardará 5 segundos en llegar de un lado a otro:
Otro programa sencillo que se incluye en los ejemplos te permite desplazar una bomba por la pantalla realizando clicks con el mouse.
Ten en cuenta que en realidad Tweener solamente conoce de interpolaciones sobre atributos o funciones. Pero en realidad, para nuestros juegos esto no solo representa movimientos. Las variables pueden indicar cualquier cosa dentro de un juego.
Por ejemplo, mediante una variable podríamos indicar el tamaño de nuestro personaje o logotipo. Si le decimos a Tweener que modifique esa variable obtendríamos un efecto de aumento de tamaño.
Cito un ejemplo, si descargas los programas adjuntos observarás uno llamado
bomba_animada.py
. Este programa muestra una bomba de caricatura que
aumenta de tamaño en pocos segundos:
python bomba_animada.py
Inicialmente vimos el método addTween
. Este método es un poco especial,
porque podemos llamarlo utilizando una cantidad variable de argumentos.
Para nuestro primer ejemplo alcanzaba con utilizar 3 argumentos: el objeto a modificar, el atributo y el tiempo para realizar el movimiento.
Para sacarle el mayor provecho a Tweener existen muchos argumentos opcionales para utilizar.
Veamos la declaración del método:
tweener.addTween(object, atributo_a_modificar=valor_final, [tweenTime, tweenType, onCompleteFunction, onUpdateFunction, tweenDelay]
El primer argumento y el segundo son obligatorios, el resto son opcionales y si no los especificamos tomarán valores por defecto:
El argumento tweenDelay
resulta útil para encadenar movimientos. Por
ejemplo, si queremos que un objeto se mueva a la derecha y luego hacia arriba
escribiríamos:
tweener.addTween(object, derecha=400, tweenTime=10) tweener.addTween(object, arriba=100, tweenDelay=10, tweenTime=5)
Es decir, el movimiento hacia arriba se iniciará luego de que transcurran 10 segundos (lo que se necesita esperar a que llegue a la derecha).
Pero a mi entender, uno de los mas interesantes es el argumento tweenType
,
porque te permite alterar la forma en que Tweener alcanza el valor
solicitado.
Existen diferentes funciones de interpolación, la mas común es la interpolación lineal, que avanza paso a paso a la misma velocidad, pero no es la única, hay muchas funciones interesantes para probar.
Esta es una lista de las interpolaciones mas destacadas:
y para indicarle a Tweener un tipo de interpolación diferente tienes
que utilizar el argumento tweenType
como se ve a continuación:
tweener.addTween(sprite, x=200, tweenType=pytweener.Easing.Elastic.easeOut) tweener.addTween(sprite, y=200, tweenType=pytweener.Easing.Bounce.easeInOut) tweener.addTween(sprite, alpha=255, tweenType=pytweener.Easing.Lineal.easeNone)
Si quieres experimentar y comparar los diferentes tipos de interpolaciones puedes visitar la siguiente web:
Las interpolaciones se pueden lograr de muchas maneras, aquí hemos visto una breve reseña de tweener, pero hay alternativas…
Actualmente hay bibliotecas y motores de juego que comienzan a incorporar las interpolaciones para los programadores de juegos logremos mejores resultados en menor tiempo.
Veamos alguna de estas alternativas.
Existe una biblioteca llamada rabbyt, que se puede utilizar junto al lenguaje de programación python y alguna biblioteca multimedia como pygame o pyglet.
En Rabbyt, si queremos hacer que un objeto llamado ruleta
realice
un movimiento tendríamos que escribir:
ruleta.rot = rabbyt.lerp(0.0, 360.0, dt=10.0)
la función lerp
viene de lineal interpolation
, por lo tanto
logrará que el objeto de un giro completo de 360 grados en unos 10
segundos pero siempre a la misma velocidad.
Lo interesante de rabbyt, es que permite hacer animaciones rápidamente, no solo por la simplicidad de la sintaxis, sino también porque todos sus cálculos internos están codificados en el lenguaje de programación C, lo que la hace muy rápida.
Otro ejemplo es la biblioteca Cocos2D, una excelente biblioteca que se ha construido sobre pyglet para añadirle mucha mas funcionalidad.
Cocos2D te permite realizar interpolaciones, pero no directamente, sino a través de acciones. Las acciones son cambios de estado que hacen los sprites (o actores) en el juego.
Veamos un ejemplo: imagina que quieres hacer que un personaje dé una vuelta completa de 360 grados, luego se desplace a la derecha unos 200 pixeles y luego de una vuelta completa pero hacia el otro lado.
En cocos2D tendrías que escribir algo como lo siguiente:
girar = actions.RotateBy(360, 3) # en 3 segundos moverse = actions.MoveBy(200, 5) # en 5 segundos girar_al_reves = - girar sprite.do(girar + moverse + girar_al_reves)
Si, como puedes ver, cocos2D tiene una estrategia muy prolija y novedosa. Cada acción es un objeto, y es independiente del personaje que puede realizarlas.
Si queremos que un personaje haga algo, simplemente tenemos que llamar a su método “do” indicándole las acciones a realizar. Si le enviamos muchas acciones unidas con ”+” entonces las acciones se encadenan en el tiempo (primero + luego + por_ultimo).
En cambio, si queremos hacer dos cosas al mismo tiempo, como girar un objeto mientras se mueve, podríamos usar el caracter “pipe”:
sprite.do(girar | moverse) # dos acciones simultaneas
Las interpolaciones aplicadas a los juegos son una buena idea, produce resultados muy realistas y es divertido codificarlas conociendo algunas de las herramientas comentadas.
Espero que este artículo te resulte de utilidad para el desarrollo de tus juegos y que te anime a sugerir ideas a la comunidad.