hello world!

Blog personal de Rafael Alcalde Azpiazu: programación, cacharreo y sobre todo, pasándomelo bien 😺.

Mi aporte a la Indie Spain Jam 2023

Sábado, 14 de octubre de 2023

post-mortencastellano

Se que iba a hablar antes de otros proyectos, pero el mes pasado estuve inmerso en la Indie Spain Jam 2023, por lo que quería hablar esta vez de mi última experiencia, además de lo que aprendí en este evento, sobre todo porque, aunque no quedó entre los 25 primeros, pude terminar otro proyecto del que se me ocurren muchas cosas para hacer.

Para la gente que no lo sepa, una Game Jam es un evento en el que desarrolladores, diseñadores y demás personas del mundo de los videojuegos se reúnen para, en un tiempo determinado, sacar un videojuego, ya sea de forma individual o por equipos. La idea es usar estos eventos para improvisar, aprender y sobre todo experimentar (de ahí el término jam, que hace consonancia a las Jam Sessions, que son eventos musicales para improvisar, sobre todo en el jazz). En esta ocasión me presenté a la Indie Spain Jam, que es una game jam anual (esta es su segunda entrega) hecha dentro de la organización de la IndieDevDay, la feria de videojuegos más importante a nivel español. A pesar de que cualquiera puede montar su stand y ofrecer un punto de promoción para algún proyecto, esta feria hace hincapié en, como su nombre indica, los videojuegos indie. Es por eso que el año pasado, pensando en potenciar este espacio de promoción de "lo indie", organizaron su propia Game Jam.

Una de las cosas que distingue un evento de jam es que hay un tema central sobre el que gira el desarrollo. No es obligatorio, porque existen jams en las que el tema es libre, o que está fijado por el evento del que trata (si es una jam de juegos de gameboy, la idea es que tenga las limitaciones cercanas a las de un juego de gameboy, como puede ser la paleta de colores o el número de sonidos que se pueden reproducir juntos). Este año el tema de la Indie Spain Jam era "cae la noche", y a ver, te lo puedes tomar de forma literal o puedes empezar a armar una historia en donde la noche es la protagonista, al final el límite es contar con un scope que puedas realizar bien en una semana (a veces incluso menos, pero ya hablaré de eso).

Y yo me lo tomé literal. No es que caiga la noche, pero si parte de ella. Mi idea era hacer que la luna cayese contra la tierra, y, a ver, puede parecer un concepto sencillo: le pones físicas a la luna para que caiga a la tierra y listo... pero se me escapaba que tenía que enfrentarme a un problema. Mi idea era imitar un poco un Angry Birds, con la principal mecánica de hacer que pudieras impulsar la luna y está trazara una parábola como estudiamos en física del colegio. Pero claro, aquí no somos terraplanistas como la gente de Angry Birds. Mi tierra es redonda (es un juego 2D, se me olvidó comentarlo), por lo que aquí encontré el problema.

Normalmente en los videojuegos, cuando aplicamos físicas suponemos que existe una fuerza de la gravedad que tira los objetos hacia abajo de la pantalla. Esta gravedad no tiene que coincidir con la gravedad terrestre, ya que, salvo que tu juego tenga un tema realista, este valor normalmente se ajusta con la idea de que en pantalla se muestre algo que sea divertido, jugable o incluso directamente se scripta (por ejemplo cuando tenemos plataformas de salto que tienen que llevar a una zona concreta del nivel). Aquí podríamos incluso jugar con el propósito de game feel y añadir impulsos, momentos y demás para hacer que el juego se sienta natural a pesar de no tener una gravedad como en la tierra. Como resumen, una de las cosas más recurrentes en la literatura del diseño de videojuegos es realizar trampitas, pero que no se noten.

Pues con esta idea decidí mirar a ver como modificar las físicas de Godot para mostrar algo que me convenciera, o al menos hacer algo que resolviera el problema. Revisando cómo funciona el sistema de físicas, Godot tiene dos funciones principales que me podían servir para esto. La primera es _physics_process(), que es muy similar a la función FixedUpdate() de Unity. Este callback se llama en el mismo frame donde se actualiza el motor de físicas, porque a diferencia de lo que se puede pensar, normalmente los motores de videojuegos actualizan las físicas a una duración constante. Esto se realiza para que las colisiones se detecten siempre de forma determinista. En Godot viene configurado para que sea llamado a 0.016 segundos (60 veces por segundo). La cosa es que esta función, aunque está sincronizada con la actualización de físicas, no permite una actualización correcta del estado físico del objeto porque, al menos en Godot, el sistema de físicas se ejecuta paralelamente en otro proceso independiente. Para estas cosas, si tu nodo deriva de un RigidBody, existe la función _integrate_forces(), que nos permite modificar de forma segura el estado físico del nodo, aplicarle fuerzas, etc. Incluso con esta función podríamos hacer nuestras físicas custom en vez de usar la del motor de físicas si también ponemos a true el atributo de custom_integrator. En mi caso me interesa cambiar cómo funciona solo la gravedad, por lo que no voy a desactivarlo completamente.

Teniendo esto en cuenta la idea es establecer una fuerza radial que siempre apunte al centro del planeta. Podríamos usar la función de la gravedad para dos cuerpos para que el valor de esta fuerza tenga en cuenta la distancia y demás, pero como ya dije antes, estamos aquí para hacer trampitas, y que el jugador mande la luna a velocidad de escape de la tierra no es que sea divertido, por lo que no lo voy a tener en cuenta. Para que afecte un poco eso de que que a mayor distancia atrae menos, si que le aplico esto, pero hasta una distancia máxima, a partir de ahí siempre va a atraer a la tierra. Con todo esto, mi función quedó con el código siguiente:

27  func _integrate_forces(state):
28      var direction = earth.global_position - global_position
29      var force = min(max(direction.length(), 2048.0), 8192.0)
30      gravity = direction.normalized() * Constants.GRAVITY * force / 2048.0
31      state.apply_central_force(gravity)

Luego, para que siempre se aplique esta función, puse que el objeto tenga siempre gravedad 0, que es un parámetro que se puede ajustar en el RigidBody. Además, lo bueno de poder saber cómo interactúa la gravedad en el objeto es que se puede también diseñar un sistema de predicción de donde va a acabar la luna, pero no lo voy a explicar en esta entrada porque está quedando bastante larga. Si queréis revisar más cosas de como hice este proyecto, tenéis todo el código disponible en mi GitLab.