Pues hemos llegado a un punto en que la jugabilidad está lista. Nos quedan unas pocas cosas para acabar: un diálogo de final de juego, añadir música y sonidos, y meter controles para móvil. Con esto tendremos nuestra primera versión del juego.
Para esta parte del tutorial necesitaremos algunos nuevos recursos gráficos y de sonido:
resources/images/gui/gameover.png
resources/images/gui/home.png
resources/images/gui/reload.png
resources/images/gui/left.png
resources/images/gui/right.png
resources/images/gui/fire.png
resources/sound/b-roll.ogg
Diálogo de fin de juego
La partida puede acabar de dos formas. O bien el protagonista muere, o bien destruye todas las burbujas. En ambos casos vamos a presentarle un diálogo donde pueda elegir entre jugar de nuevo, o volver al menú principal.
En la escena Play
añade un nuevo nodo de tipo Node2D, y ponle como nombre GameOver. Para que siempre aparezca delante de todos los elementos, ponle la propiedad “Z Index” a 2000.
A continuación añade a este nodo un hijo de tipo TextureRect, llámalo “Background”, y elige como su textura la imagen “gameover.png”. Para centrarlo, pon en su propiedad “Rect/Position” x=590
e y=238
.
Ahora, añade a este nodo “Background” dos hijos de tipo TextureRect. Al primero llámalo “Home”, ponle como textura la imagen “home.png”, y en su propiedad “Rect/Position” x=170
e y=350
. Al segundo llámalo “Reload”, ponle como textura “reload.png”, y en su propiedad “Rect/Position” x=390
e y=350
.
Vamos a hacer una función que haga un poco de limpieza y muestre este diálogo. La llamaremos “show_gameover” y va a hacer lo siguiente:
- Eliminar la flecha que pueda haber en pantalla
- Eliminar todas las burbujas que pueda haber en pantalla
- Mostrar la pantalla de “GameOver”
1 | func show_gameover(): |
Hay dos momentos en los que queremos llamar a show_gameover
. Primero, cuando el personaje muere, tras un par de segundos para dar tiempo a ver la animación:
1 | func player_hit(): |
Y segundo, cuando no queden más burbujas, con medio segundo de retardo. Para esto hay que añadir a la función “arrow_hit” lo siguiente:
1 | if balls.size() == 0: |
Ahora nos toca darle funcionalidad a los botones. Vamos con el más fácil. Elige “Home”, y en la pestaña “Node” (al lado del inspector, por si no lo recuerdas) haz doble click en “gui_input” y acepta los valores por defecto. Esto te creará una nueva función llamada “_on_Home_gui_input”. Lo que queremos hacer aquí es que cuando el usuario haga click, volvamos al menú principal. Así que el código será:
1 | func _on_Home_gui_input(event): |
El otro botón tiene que hacer recomenzar la partida. Elige “Reset”, y en la pestaña “Node” haz doble click en “gui_input” y acepta los valores por defecto. El código del botón será muy simple:
1 | func _on_Reload_gui_input(event): |
El método reset aún no existe, pero debería hacer varias cosas:
Colocar al protagonista en su posición inicial. con la animación “idle”.
1
2$Player.position.x = 845
$Player.idle()- Esto además requiere que creemos en “Player.gd” la función “idle”:
1
2
3func idle():
paused = false
$AnimatedSprite.animation = "adventure_guy_idle"
- Esto además requiere que creemos en “Player.gd” la función “idle”:
Crear y colocar las burbujas en su posición inicial. Mejor aún, para que el juego sea más variado, crear de forma aleatoria de 1 a 3 burbujas (si son 3, de tamaño mediano), en una posición
x
entre 500 y 1700, y yendo hacia la derecha o hacia la izquierda.1
2
3
4
5
6
7
8
9
10var num_balls = randi()%3 + 1
var size = 1
if num_balls == 3:
size = 0.5
for ball in range(num_balls):
var x = randi()%1500 + 200
var dir = 1
if randi() % 2:
dir = -1
create_ball(Vector2(x,0), Vector2(5 * dir, 0), size)Ocultar la pantalla de “GameOver”
1
$Gameover.hide()
Actualizar la función “_ready” para usar “reset”
1
2
3
4
5func _ready():
randomize()
balls = []
$Player.connect("player_fire_signal", self, "player_fire")
reset()
Y con esto ya tendríamos una pantalla de juego conectada con la pantalla principal, en la que cada partida es diferente. Como pequeña mejora estética, si al nodo “Player” dentro de la escena “Play” le pones un “Z Index” 101, la animación cuando muere queda mejor, ya que se ve por delante del suelo.
Compartiendo información entre escenas: Música y sonido
Para que nuestro juego termine de molar de verdad nos falta añadirle música y sonido. Pero, si recuerdas, en el menú principal el usuario elige si va a querer tenerlos activados o no, y debemos mantener esa configuración.
Un script que se carga en todas las escenas
Vamos a hacer eso con un script de código, “Globals.gd” que se va a compartir entre todas las escenas. Para esto ve a la sección “Script”, y elige “File / New Script…”. En el diálogo que aparece elige como nombre “Globals.gd” y pulsa Create
.
En este fichero vamos a guardar variables (y si queremos funciones) que queremos que estén accesibles en todas las escenas. En nuestro caso, crea variables que indiquen si la música y los sonidos están activados:
1 | extends Node |
Ahora vamos a hacer que “Globals.gd” sea accesible desde todas las escenas. Elige “Project / Project Settings…”, ve a la pestaña “Autoload”, y en el campo “Path” elige el fichero “Globals.gd”. Como “Node Name” deja Globals, y pulsa el botón Add
.
Usando Globals desde MainMenu
Lo siguiente es configurar “MainMenu” para activar o desactivar la música según el valor de esas variables:
1 | func update_music(): |
Y vamos a modificar las funciones que se llaman al pulsar los botones para que cambien las variables de Globals, y llamen a nuestras nuevas funciones:
1 | func _on_MusicButton_gui_input(event): |
Además, queremos que se ejecuten estas funciones al cargar la escena, para que la configuración del usuario se mantenga cuando volvamos al menú principal desde la pantalla de juego:
1 | func _ready(): |
Como ahora el arranque de la música se va a hacer desde código, nos queda ir al árbol de nodos, elegir “AudioStreamPlayer” y en sus propiedades quitarle el Autoplay
. Así la música no comenzará al cargar la escena a menos que “music_enabled” esté a true.
Usando Globals desde Play
Ahora que ya tenemos una configuración de sonido compartida entre todas las escenas, vamos a meter sonidos a la escena “Play”. Añade dos nuevos nodos de tipo “AudioStreamPlayer”, y llámalos “AudioStreamMusic” y “AutoStreamPop”. Al primero ponle como Stream
el recurso “b-roll.ogg”, y al segundo “pop.wav”.
Ahora vamos a hacer que comience la música en la pantalla de juego, pero solo si está activada. Añade estas líneas a la función “_ready”:
1 | if Globals.music_enabled: |
De forma similar, cuando reventemos una burbuja haremos que suene “pop”, solo si los efectos de sonido están activados. Añade estas líneas a “arrow_hit”:
1 | func arrow_hit(ball): |
Y con esto el juego tiene música y efectos. ¿A que es impresionante la diferencia que supone?
Controles para movil
Nuestro juego ahora mismo solo es jugable desde un teclado. Ahora nos toca añadir botones en la pantalla para poder jugar desde dispositivos móviles.
Antes de nada, para poder probar el comportamiento, tenemos que configurar Godot para que trate el ratón como si fuese un dedo tocando una pantalla táctil. Abre “Project / Project Settings…”, ve a la sección “Input Devices / Pointing” y marca la casilla “Emulate touch from mouse”.
Ahora ya solo queda añadir los controles. Vamos a utilizar nodos de tipo “TouchScreenButton”, que son específicos para pantallas táctiles, y además tienen un atributo utilísimo: “Action”. Este atributo indica qué evento queremos que esté activo mientras el jugador esté tocando el botón. Así podemos lanzar los mismos eventos que ya lanzamos con el teclado, con lo que el manejo será inmediato.
En la escena “Play” añade tres nuevos nodos de tipo “TouchScreenButton”, y configuralos así:
- Nombre: “LeftButton”, “Action”: “ui_left”, “Transform / Position”: x=25, y=900, “Z Index”: 1001
- Nombre: “RightButton”, “Action”: “ui_right”, “Transform / Position”: x=250, y=900, “Z Index”: 1001
- Nombre: “FireButton”, “Action”: “ui_accept”, “Transform / Position”: x=1700, y=900, “Z Index”: 1001
El último fix
Antes de terminar, hay una última cosa que nos hemos dejado por hacer. El protagonista es capaz de moverse fuera de los límites de la pantalla. Para solucionarlo, vamos a añadir estas líneas al final del método “_physics_process” de “Player.gd”:
1 | if position.x < 0: |
Versión 1.0
Y con esto, damos por terminada la versión 1.0 de Patapang. Nos ha quedado bastante bien, ¿no crees?. En este punto ya tienes conocimientos suficientes de Godot para seguir por tu cuenta, así que no veremos ninguna funcionalidad más. Por supuesto, hay un montón de ideas que puedes implementar para seguir practicando:
- Meter niveles prediseñados, y que el jugador vaya avanzando
- Meter diferentes power-ups como disparos dobles, escudos…
- Añadir dificultades adicionales como pinchos o cajas que no dejen que el protagonista se mueva por todo el escenario
- Diferentes personajes, que se puedan “comprar” con monedas que ganas en los niveles
- ¿Se te ocurren algunas más?
Como inspiración, puedes ver el código de mi primera versión de Patapang (Y la tienes publicada para Android). Esa versión implementa muchas más funcionalidades, pero es mucho menos didáctica, con un código menos limpio, y con diferentes experimentos y aproximaciones a los problemas de los que hemos visto aquí. Puede ser interesante para que veas otras formas de abordar los problemas.
Ahora ya solo falta la última parte, como subir el juego a Google Play. La publicaremos después del 2 de noviembre, fecha en la que actualizan Google Play Console.
Tutorial:
- Primera parte: Patapang, un tutorial para crear tu primer videojuego con Godot
- Segunda parte: Primeros pasos con Godot
- Tercera parte: Construyendo nuestro menú principal del videojuego con Godot
- Cuarta parte: Empezamos a jugar
- Quinta parte: Animando al protagonista
- Sexta parte: Esquivando burbujas
- Séptima parte: Rompiendo burbujas