Hola, a todos sean bienvenidos a mi nuevo post. Despues de nuestro primer juego en el post anterior, hoy haremos nuestro segundo juego, en este veremos las nuevas modalidades para las colisiones y como trabajar con animaciones, dejemonos de palabras y empecemos. Ante todo, necesitas un minimo conocimiento de Escenas y Nodos por esto te recomiendo este post. Empezaremos creando nuestro proyecto, yo lo voy a bautizar como “Esquivar Criaturas”. Una vez creado, utilizaremos el entorno 2D, nos conviene aprender todas estas tecnicas en 2D porque son mas sencillas cuando tengan mas conocimientos ahi pueden intentar con el 3D pero por ahora sigamos con este proyecto. Ahora modificaremos el tamaño de la pantalla, para esto vayan a Proyectos -> Ajustes del Proyecto -> Display, y a su derecha modifiquen width a 480 y height a 720. Luego descarguen este archivo dodge_assets, una vez descargado lo extraen en la carpeta creada para el proyecto y deberia quedar algo asi

godot63

Una vez agregados nuestros assets, es decir contenido del juego, procederemos a crear nuestras escenas, estas van a ser cuatro:

  • Jugador
  • Enemigo
  • HUD
  • Main (Este va a ser el contenedor de todos los anteriores)

Primero vamos a ir a Escena y elegir nueva Escena, donde nos deberia quedar algo asi (por lo menos por ahora)

godot64

Ahora en la solapa Escena agregamos con el boton mas (+) un nuevo nodo, en este caso Area2D, una vez agregado renombrelo a Jugador (para saber cual es) y nos va a quedar con un mensaje de error pero por ahora no le den importancia. Una vez creado deben hacer la siguiente accion

godot65

Deben hacer click en donde esta el circulo rojo, esta accion permitira evitar mover o cambiar el tamaño de los herederos de esta escena por accidente. Una vez hecho tambien les aparecera en la solapa escena, remarcado por el circulo azul. Ahora solamente debemos grabar la escena y listo. Ahora procederemos a la animacion de nuestro personaje, para esto vamos a crear un nodo nuevo, agregan con el mas y seleccionan AnimatedSprite, este quedara con un notificacion de error para solucionarlo deben ir a Frames y seleccionar Nuevo SpriteFrames

godot66

Una vez seleccionado vuelven a hacer click en la misma opcion y deberia aparecer el seleccionador de Frames

godot67.png

Observen esto, ya hay una animacion llamada default. En nuestro caso a esta la llamaremos right y con el boton de mas arriba de default agregaremos una nueva a la cual llamaremos up. Despues hay dos opciones, o a traves del panel de la izquierda van a dodge_assets, y luego a art para seleccionar los graficos correspondientes para cada animacion, playerGrey_up1 y playerGrey_up2 para la animacion up y playerGrey_walk1 y playerGrey_walk1 para la animacion right,  o pueden ver como lo hago a traves del video de aca abajo

Una vez terminado podemos ver como la figura quedo demasiado grande con respecto a la pantalla para esto vamos de nuevo a la solapa escena, elegimos el nodo AnimatedSprite y dentro de inspector vamos a transform, luego a scale y lo reducimos a la mitad poniendo en x e y los valores 0.5

godot69.png

Para ir finalizando la escena del jugador, por lo menos a lo grafico, vamos a agregar un  nuevo nodo para el area de colision, usamos otra vez el boton mas en la solapa escena y elegimos CollisionShape2D, recuerden tener seleccionado a Jugador para quedar como child de este sino quedara como child de AnimatedSprite., observen como desaparecio la notificacion de error del nodo Jugador y pasaremos a setear los parametros del nuevo child creado pero antes les muestro como deberia quedar la tabla de nodos y childs

godot70.png

Ahora deberian ir a shape, dentro de la solapa Inspector, y seleccionar CapsuleShape2D, van a tener varias opciones pero para nuestro caso esta a va a ser la mejor forma, una vez seleccionado, lo vuelven a seleccionar y ahora les deberia aparecer la opcion de poder seleccionar el area de colision, es probable que se vea un poco pequeño les recomiendo hacer un poco de zoom y vean como lo hago con el siguiente video

Una vez realizado todo esto, deberian haber desaparecido todas las notificaciones de error de nuestra solapa escena, lo grabamos y procedemos con la parte de scripting de nuestra escena. Si necesitan saber sobre esto, les recomiendo este post donde explique todo lo basico, ahora simplemente elegimos el nodo Jugador y cliqueamos la opcion de agregar script, una vez creado usaremos el siguiente codigo fuente:

jugador.gd:

extends Area2D

signal hit

export (int) var speed
var screensize

func _ready():
screensize = get_viewport_rect().size

func _process(delta):
var velocity = Vector2()
if (Input.is_action_pressed(“ui_right”)):
velocity.x +=1
if (Input.is_action_pressed(“ui_left”)):
velocity.x -= 1
if (Input.is_action_pressed(“ui_up”)):
velocity.y -= 1
if (Input.is_action_pressed(“ui_down”)):
velocity.y += 1
if (velocity.length() > 0):
velocity = velocity.normalized() * speed
$AnimatedSprite.play()
else:
$AnimatedSprite.stop()

position += velocity * delta
position.x = clamp(position.x,0,screensize.x)
position.y = clamp(position.y,0,screensize.y)

if (velocity.x != 0):
$AnimatedSprite.animation = “right”
$AnimatedSprite.flip_v = false
$AnimatedSprite.flip_h = velocity.x < 0
elif (velocity.y != 0):
$AnimatedSprite.animation = “up”
$AnimatedSprite.flip_v = velocity.y > 0

func _on_Jugador_body_entered(body):
hide()
emit_signal(“hit”)
$CollisionShape2D.disabled = true

func start(pos):
position = pos
show()
$CollisionShape2D.disabled = false

En este caso vamos a tener las funciones basicas de siempre, las cuales son ready y process. En el post de scripting y de nuestro primer juego explico estas dos funciones pero basicamente son: ready para cuando el programa entra en estado de listo o preparado y el otro el tiempo transcurrido entre evento y evento. La linea signat hit va a ser la encargada de monitorear las colisiones, luego vamos a crear una variable global a traves de EXPORT para todo el juego la cual va a ser speed, la cual va a definir la velocidad en el juego, y vamos a crear una variable para almacenar el tamaño de la pantalla (screensize), la variable global creada aparecera entre los parametros de la solapa Inspector dentro de Script Variables

godot72

Luego en la funcion _ready() le definiremos el tamaño de la pantalla a screensize para futuras aplicaciones dentro de nuestro script. En la siguiente funcion (_process()), primero definimos una variable llamada velocity la cual va a guardar una posicion en base a Vector2, luego vamos a definir una serie de condicionales if donde evaluara si una tecla fue apretada o no, esto es a traves de Input.is_action_pressed() donde Input es la parte encargada de esperar un metodo de entrada e is_action_pressed() verifica si fue apretada la tecla asignada entre parentesis (p.e. ui_up donde equivale al cursor arriba), las teclas utilizadas en este juego son las de los cursores del teclado por defecto, si desean modificarlo les recomiendo ver el post anterior donde explico en cual lugar pueden modificar y/o agregar nuevos metodos de entradas. En estos cuatro if vamos a modificar la direccion de x e y de velocity, el cual modificara los valores de x e y obtenidos por Vector2. Luego tendremos un condicional if donde chequearemos si la longitud de velocity es mayor a cero en caso de ser afirmativo procederemos a setear velocity igual al producto de velocity con la propiedad normailzed() multiplicada por la velocidad que seteamos entre los parametros antes mencionados (speed) y luego utiliza el metodo play para ejecutar la animacion de AnimatedSprite. El simbolo de pesos ($) es una forma equivalente de get_node(), en el post anterior hemos visto a esta funcion. En caso contrario, detendra la animacion. La variable position sera recalculada en base a la velocidad multiplicada por delta, despues limitaremos a nuestro personaje con el metodo clamp, este metodo primero se le define una propiedad a limitar (p.e. el eje x de position), luego pondremos un valor inicial (en este caso cero) y por ultimo el valor maximo (en este caso el eje x de screensize), en una linea limitamos a x y en la siguiente limitamos a y. La siguiente condicion if es para establecer el movimiento, primero verificamos si el eje x de velocity es distinto de cero, en caso afirmativo asigna la animacion right a AnimatedSprite, luego setea al metodo flip_v como false y por ultimo utiliza el flip_h (dar vuelta de forma horizontal el grafico) en caso del eje x de velocity sea menor a cero, esta linea se encargara basicamente de invertir la imagen cuando vayamos a la izquierda. Despues tenemos en caso contrario donde el eje y de velocity es distinto de cero, asigna la animacion up a AnimatedSprite y como en el caso anterior la ultima linea invierte la imagen en caso de ir hacia abajo. Hasta aqui todas las funciones pertinentes a la funcion _process() y todo lo relacionado al movimiento de nuestro personaje, ahora pasaremos a la parte del impacto. Para esto, utilizaremos una señal (pueden verlo en este post) iremos a nuestro Node2D (en este caso Jugador), elegimos la solapa Nodo y Señales. De estas acciones, elegiremos body_entered(), elegimos conectar, lo dejan tal cual como aparece y conectar de nuevo, si todo sale bien nos deberia haber creado una nueva funcion: func _on_Jugador_body_entered(body). En esta funcion agregaremos una funcion para ocultar el sprite, hide(), emita una señal de contacto y por ultimo desactiva la forma de colision. En la siguiente funcion simplemente restableceremos la posicion, volveremos a mostrar el sprite y por ultimo reactivamos el area de colision. Hasta aca vimos todo lo relacionado con el jugador, ahora procederemos con la escena Enemigos. Volvemos a crear una nueva escena pero en este caso vamos a agregar un nodo RigidBody2D la cual renombraremos a Enemigo, con sus respectivos childs:

  • AnimatedSprite
  • CollisionShape2D
  • VisibilityNotifier2D (renombrar a visibility)

Una vez creado nos deberia quedar algo asi

godot77.png

En esto haremos muy pocas modificaciones, unas seran en el nodo enemigo donde modificaremos la propiedad gravity scale dentro de la solapa Inspector, la setearemos en cero para evitar que nuestro “enemigos” se caigan al piso, tambien deben destildar la unica opcion tildada de PhysicsBody2D, Mask para evitar que los enemigos colisionen entre si, les muestro la opcion a destildar

godot74
Esto es para evitar la colision de enemigos
godot73
Y este para evitar que se caigan al piso

Luego pasaremos a la parte de AnimatedSprite donde cargaremos las imagenes de los enemigos para su animacion, les muestro un video con la forma alternativa de cargarlos a diferencia del video anterior

Una vez finalizado, observen las tres animaciones creadas, fly, swim y walk, tambien le bajaremos la escala como en el caso de jugador pero ahora a 0.75 en ambos ejes, y por ultimos cambiaremos los frames per second (fps) de los enemigos, para fly lo pondremos en tres y para swim y walk en cuatro

godot76godot75

Ahora para poder ver una completa finalizacion de las animaciones debemos tildar la opcion de Playing, como se ve en la siguiente figura

godot79.png
Esto tambien deben hacerlo con la escena de Jugador.

Luego iremos a CollisionShape2D donde definiremos nuevamente el area de colision para nuestro AnimatedSprite pero la unica diferencia con respecto al anterior va a ser una modificacion en la solapa Inspector, opcion Node2D, Rotation degree y poner a 90º. Una vez hecho todo esto pasemos al script de enemigo

enemigo.gd

extends RigidBody2D

export (int) var min_speed
export (int) var max_speed
var mob_types = [“walk”, “swim”, “fly”]

func _ready():
$AnimatedSprite.animation = mob_types[randi() % mob_types.size()]

func _on_visibility_screen_exited():
queue_free()

Como pueden ver, este es mucho mas simple al principio creamos dos variables con el metodo export y luego vamos a crear para los distintos tipos de enemigos (mob_types), en la funcion _ready() vamos a hacer una asignar a animacion de Animatedsprite un tipo de enemigo por medio de la funcion randi() y va a estar definida por el tamaño de la mob_types y por ultimo vamos a elegir el child visibility le asignaremos la señal screen_exited() lo conectaremos como hicimos en Jugador, y una vez creada la funcion le pondremos la funcion queue_free() para liberar la escena. Ahora pasemos a la escena principal a la cual llamaremos Main. En main vamos a crear un nodo, en la solapa escena le dan al mas y buscan nodo (o node) una vez seleccionado, le damos a al boton de al lado para crear una instancia, en este traeremos a Jugador para mas informacion vean este post, luego crearemos unos childs, 3 timers y Position2D, para nuesto Nodo:

  • Timer / Renombrado: timermalos / Setear wait time a 0.5
  • Timer / Renombrado: timerpuntos / Setear wait time a 1
  • Timer / Renombrado: timerinicial / Setear wait time a 2 / setear one shot a “On”
  • Position2D / Renombrado: posicioninicial / setear position en x: 240 e y: 450

Todo nos deberia quedar algo asi y observen donde se encuentra one shot

godot78

Una vez configurado todo procederemos a crear los puntos para generar los enemigos de forma aleatoria. Vamos a crear un nuevo nodo llamado Path2D, como child del Nodo principal, y a este lo llamaremos caminomalos, cuando lo selecionemos nos apareceran una serie de botones, aca les paso un video donde pueden ver como asignamos los puntos de salida de los enemigos

Respeten el orden como se ve en el video porque de lo contrario los enemigos en vez de ingresar lo haran hacia afuera, como se ve despues del cuarto punto cliqueamos un boton para dibujar la linea para unir los cuatro puntos. Luego le agregaremos un child del tipo Pathfollow2D y la llamaremos puntodesalida el cual se encargara de dar el verdadero efecto de salida a la azar de los enemigos. Ahora pasaremos al script de Main

main.gd

extends Node

export (PackedScene) var Mob
var score

func _ready():
randomize()

func game_over():
$timerpuntos.stop()
$timermalos.stop()

func new_game():
score = 0
$Jugador.start($posicioninicial.position)
$timerinicial.start()

func _on_timerinicial_timeout():
$timermalos.start()
$timerpuntos.start()

func _on_timerpuntos_timeout():
score +=  1

func _on_timermalos_timeout():
$caminomalos/puntodesalida.set_offset(randi())
var enemigos=Mob.instance()
add_child(enemigos)
var direction = $caminomalos/puntodesalida.rotation + PI/2
enemigos.position = $caminomalos/puntodesalida.position
enemigos.rotation = direction
enemigos.set_linear_velocity(Vector2(rand_range(enemigos.min_speed, enemigos.max_speed),0). rotated(direction))

En este script tambien vamos a utilizar el export para crear instancia con una escena, en este caso enemigo, aca les muestro un video de como se hace para dropear la escena dentro de la propiedad creada atraves del export

Ahora elegimos la instancia creada de Jugador vamos a señales y elegimos hit(), esto lo vamos a vincular para crear nuestra funcion de game_over(), para esto elegimos hit(), luego le damos a conectar y si se fijan en Method in node, borran lo escrito y lo nombran game_over, le dan conectar y si todo sale bien, tendremos una funcion llamada game_over() a ella le diremos que detenga el timerpuntos y el timermalos. Luego crearemos una nueva funcion llamada new_game(), en esta setearemos en cero el puntaje (score), resetearemos la posicion del jugador e inciaremos el timer inicial del jugador, luego uniremos dos timer a la señal timeout(), una va a ser timerinicial y la otra timerpuntos, luego vemos como en el timeout de timerinicial vamos a iniciar a timerpuntos y timermalos, despues asignamos la señal timeout en timerpuntos, una vez conectados y creada la funcion la utilizaremos para incrementar el score y por ultimo conectaremos la señal timeout a timermalos, y en esta funcion nos encargaremos de hacer los seteos para la direccion, el angulo y la velocidad de los enemigos. Hasta aqui tenemos creados una escena main, una escena del jugador y una de enemigos, ahora procederemos con el HUD y practicamente terminaremos el juego.
Crean una nueva escena, le crean un nodo del tipo CanvasLayer, en esta usaremos 4 childs, les paso el listado:

  • Label (Renombrar: mensaje )
  • Label (Renombrar: puntaje)
  • Button (Renombrar: inicio)
  • Timer (Renombrar: timermensaje)

Estos se pueden ubicar de la forma que uno quiera, los textos recomendados pueden ser estos pero no son obligatorios:

  • mensaje: Esquiva las criaturas!
  • puntaje: 0
  • inicio: Dale, Gas!!

Yo lo deje algo asi

godot80

Ahora les explicare como modificar las fuentes, elijan por ejemplo puntaje, en ella iremos a custom font

godot81

Ahi lo cambiaremos a New dynamic font

godot82

Luego lo volveremos a elegir y ahi elegiremos font, font data y la opcion cargar

godot83

Ahi se nos abrira una ventana para elegir la fuente, aqui vamos hasta res://dodge_assets/fonts

godot84

Elegimos la unica fuente y listo, ahora nos falta unicamente cambiar el tamaño de la fuente

godot85

Asi como se ve en esta imagen, esto deberian repetirlo en cada uno de las etiquetas (Labels) y el boton. Ahora pasaremos al script:

hud.gd

extends CanvasLayer

signal start_game

func show_message(text):
$mensaje.text = text
$mensaje.show()
$timermensaje.start()

func show_game_over():
show_message(“Game over,\n Man!!!”)
yield($timermensaje,”timeout”)
$inicio.show()
$mensaje.text=”Esquiva las\nCriaturas!”
$mensaje.show()

func update_score(score):
$puntaje.text = str(score)

func _on_inicio_pressed():
$inicio.hide()
emit_signal(“start_game”)

func _on_timermensaje_timeout():
$mensaje.hide()

La primera linea creara una señal llamada start_game, y esta le dira al programa que el boton inicio fue apretado, luego tendremos una funcion encargada de mostrar un mensaje y de iniciar el timermensaje. Me olvide un detalle, entre las propiedades del timermensaje deben setear el wait time en 2 y la propiedad one shot en On. Luego tendremos una hermosa funcion, la encargada del game over(), En esta haremos desplegar un mensaje, con el yield la haremos desaparecer y luego volveremos a poner todos los mensajes del inicio para comenzar otro juego y por ultimo tres funciones, la primera sera encargada de actualizar el puntaje (score) en pantalla, la segunda es la creacion de una señal de pressed para el boton llamado inicio, para esto deben crear esta señal y una vez creada la funcion, le dicen de esconder el boton inicio y luego emiten la señal start_game() para comenzar, y por ultimo otra señal de timeout para timermensaje y este cuando se cumpla esconde la etiqueta mensaje. Hasta aqui el script y la escena HUD, ahora procederemos a incluirla en Main para concluir el juego.  Primero deberemos crear como instancia de main a hud, atraves del simbolo de eslabon al lado del boton mas en la solapa escena, una vez hecho nos deberia quedar algo asi

godot86

Ahora deberiamos vincular la señal de HUD start_game con la funcion new_game

godot87

y por ultimo, debemos definirlo de esta manera

godot88

Con esto ya tenemos la señal de start_game de la instancia hud vinculada a la funcion new_game() de la escena main. Ahora deberemos modificar un par de funciones en el main para dejar funcional nuestro, aca les paso las nuevas lineas resaltadas en sus respectivas funciones

main.gd

extends Node

export (PackedScene) var Mob
var score

func _ready():
 randomize()

func game_over():
 $hud.show_game_over()
 $timerpuntos.stop()
 $timermalos.stop()

func new_game():
 score = 0
 $hud.update_score(score)
 $hud.show_message(“Preparate”)
 $Jugador.start($posicioninicial.position) $timerinicial.start()

func _on_timerinicial_timeout():
 $timermalos.start()
 $timerpuntos.start()

func _on_timerpuntos_timeout():
 score +=  1
 $hud.update_score(score)

func _on_timermalos_timeout():
 $caminomalos/puntodesalida.set_offset(randi())
 var enemigos = Mob.instance()
 add_child(enemigos)
 var direction = $caminomalos/puntodesalida.rotation + PI/2
 enemigos.position = $caminomalos/puntodesalida.position
 enemigos.set_linear_velocity(Vector2(rand_range(enemigos.min_speed, enemigos.max_speed),0).rotated(direction))

Estas son las ultimas modificaciones para realizar en main.gd, una breve explicacion aunque es bastante intuitiva, la primera modificacion la sufre la funcion game_over() donde ahora invocaremos a la funcion del hud llamada show_game_over() la cual se encarga de mostrar el mensaje de final y este hace aparecer nuevamente el mensaje para iniciar un nuevo juego, la segunda modificacion va a ser en new_game(), ahora nos actualizara el marcador y nos mostrara un mensaje para prepararnos antes de empezar el juego y por ultimo modificamos la señal de timeout de timerpuntos donde solamente se incrementaba, ahora nos va a mostrar la incrementacion, si todo salio bien solo falta designar nuestra escena principal, esto lo pueden hacer via ejecutar el juego y se les va a preguntar o sino por proyecto, ajustes del proyecto, la opcion Run y Main Scene. Como se ve en la imagen

godot91

Una vez finalizado deberian tener algo asi

Si lograron esto, oficialmente tenemos nuestro segundo juego logrado!!!

Como pueden ver este fue un poco mas complejo al ejemplo anterior, por eso postee el otro primero, pero este explica mejor el tema de las colisiones y como trabajar con varias instancias invocadas desde el script como vinculadas via propiedades. Ahora procederemos a hacer el retoque final porque si bien nuestro juego esta 100% terminado y funcional, nos falto el sonido y agregar algun detalle “monono” para tener una mejor puntuacion en los reviews xD. Primero veamos como agregar un color de fondo, en este caso usaremos un child del nodo principal de main, el elemento utilizado sera ColorRect

godot92

Una vez creada, procederemos a cambiar el color, elegin el child y luego lo eligen en la propiedad Color como se ve en la siguiente fiigura

godot93

Una vez elegido el color, procederemos a definir el tamaño, deben ir a la propiedad Rect y luego size, lo eligen y ponen en x: 480, y: 750 como ven en la imagen inferior

godot94

Con esto, ya le hemos hecho un color de fondo pero si tienen alguna imagen este nodo puede ser reemplazado por un Sprite y utilizar esa imagen, el ultimo detalle debemos colocar este rectangulo justo debajo de main sino nos tapara al resto, les muestro una imagen

godot96

Ahora pasaremos al sonido, en este caso agregaremos dos AudioStreamPlayer, uno lo vamos a llamar musica y otro gameover, en gameover vamos a cargarle el sonido para cuando perdamos y en musica la cancion de fondo mientras jugamos, para asignarle el archivo de sonido deben usar esta opcion

godot95

Por ejemplo aca deben ir a la carpeta dodge_assets, art y ahi elegin el sonido gameover.wav para musica hacen exactamente lo mismo pero eligen el sonido House In a Forest Loop.ogg, una vez realizado esto debemos hacer un par de modificaciones en el script main

func game_over():func game_over():
 $hud.show_game_over()
 $timerpuntos.stop()
 $timermalos.stop()
 $musica.stop()
 $gameover.play()

func new_game():
 score = 0
 $musica.play()
 $hud.update_score(score)
 $hud.show_message(“Preparate”)
 $Jugador.start($posicioninicial.position)
 $timerinicial.start()

Como pueden ver, solamente se modifican dos funciones ya creadas la de game_over() y la de new_game(), en el caso de game_over() agregaremos una linea para detener la musica de fondo y le decimos activar el sonido de derrota y para la funcion new_game() agregamos una nueva donde activa la musica de fondo. Les dejo un video de como seria el juego terminado, aca les dejo el juego exportado creep y el codigo fuente final EsquivarCriaturas.

Hasta aqui todo lo relacionado con el juego, hemos utilizado las instancias, hemos aprendido sobre colisiones y hemos visto como agregar sonido y musica a nuestro nuevo juego, como pudieron ver no quise usarlo como primer juego porque realmente era mas complejo al primer ejemplo usado, en este post, y si hicieron el anterior en este se habran facilitado algunos procedimientos, espero les haya gustado no dejen de seguirme y nos vemos en el proximo post.

Anuncios