Diseñando la escena de los enemigos

En esta parte, vas a programar los monstruos, a los que llamaremos "mobs". En la próxima lección, los generaremos aleatoriamente alrededor del área jugable.

Vamos a diseñar a los propios monstruos en una nueva escena. La estructura de nodos será similar a la escena del Jugador.

Crea una escena con, una vez más, un nodo KinematicBody como raíz. Nómbralo Mob. Agrega un nodo Spatial como hijo, nómbralo Pivot. Luego, arrastra y suelta el archivo mob.glb desde el panel Sistema de Archivos hacia el Pivot para agregar el modelo 3D del monstruo a la escena. Puedes cambiar el nombre del nodo mob recién creado a Character.

image0

Necesitamos una forma de colisión para que nuestro cuerpo funcione. Haz clic derecho en el nodo Mob, la raíz de la escena, y selecciona Añadir Nodo Hijo.

|image1|

Agrega una CollisionShape.

image2

En el Inspector, asigna un BoxShape a la propiedad Shape.

|image3|

Debemos cambiar el tamaño para que ocupe el modelo 3D mejor. Puedes hacer esto de forma interactiva dándole clic y arrastrando sobre los puntos naranjas.

La caja deberá tocar es suelo y ser un poco mas delgada que el modelo. El motor de física funciona de forma que si el jugador toca tan solo el borde de la forma de colisión, esta sera registrada. Si la forma de colisión algo demasiado grande comparada con el modelo 3D, morirás a distancia del monstruo, haciendo que el juego parezca injusto para el jugador.

image4

Fíjate en que mi caja es más alta que el monstruo. Está bien en este juego porque estamos mirando la escena desde arriba y usando una perspectiva fija. Las formas de colisión no tienen por qué coincidir exactamente con el modelo. Es la sensación que da el juego cuando lo pruebas la que debe dictar su forma y tamaño.

Eliminando los monstruos fuera de la pantalla

Vamos a generar monstruos en intervalos regulares en este nivel. Si no lo hacemos de forma controlada, el numero de monstruos podría incrementar infinitamente. Todos los monstruos generados tienen un coste de memoria y procesado, y no queremos gastarlo en monstruos que están fuera de nuestra pantalla.

Cuando un monstruo sale de la escena, ya no lo necesitemos, y lo podamos quitar. Godot tiene un nodo que detecta cuando un objeto sale de la escena, VisibilityNotifier, y vamos a usar este nodo para eliminar los monstruos.

Nota

Cuando generas muchos objectos en juegos, hay una técnica que puedes usar para evitar el coste de crearlos y destruirlos todo el rato, esta se llama pooling. Consiste en crear una lista de objetos y reciclarlos una y otra vez.

Cuando trabajas con GDScript, no necesitas preocuparte por esto. La principal razón para usar "pools" es evitar bloqueos en lenguajes con recolección de basura como C# o Lua. GDScript utiliza una técnica diferente para gestionar la memoria, el recuento de referencias, que no tiene esa limitación. Puedes obtener más información al respecto aquí: Gestión de la memoria.

Selecciona el nodo Mob y agrega un VisibilityNotifier como hijo de este. Aparecerá otro recuadro, esta vez de color rosa. Cuando este recuadro salga completamente de la pantalla, el nodo emitirá una señal.

image5

Cambia su tamaño con los puntos naranjas hasta que cubra todo el modelo 3D.

image6

Programando el movimiento del monstruo

Vamos a implementar el movimiento del monstruo. Lo haremos en dos pasos. Primero, escribiremos un script en el nodo Mob que defina una función para inicializar al monstruo. Luego, codificaremos el mecanismo de generación aleatoria en la escena principal (Main) y llamaremos a la función desde allí.

Adjunte el script al Mob.

image7

Aquí tienes el código de movimiento para comenzar. Definimos dos propiedades, min_speed y max_speed, para establecer un rango de velocidad aleatorio. Luego definimos e inicializamos la variable velocity.

extends KinematicBody

# Minimum speed of the mob in meters per second.
export var min_speed = 10
# Maximum speed of the mob in meters per second.
export var max_speed = 18

var velocity = Vector3.ZERO


func _physics_process(_delta):
    move_and_slide(velocity)

De manera similar al jugador, movemos al monstruo en cada cuadro de animación llamando al método move_and_slide() del nodo KinematicBody. Esta vez, no actualizamos la variable velocity en cada cuadro: queremos que el monstruo se mueva a una velocidad constante y salga de la pantalla, incluso si chocara con un obstáculo.

Es posible que veas una advertencia en GDScript de que no se utiliza el valor de retorno de move_and_slide(). Esto es normal. Simplemente puedes ignorar la advertencia o, si deseas ocultarla por completo, agrega el comentario # warning-ignore:return_value_discarded justo encima de la línea move_and_slide(velocity). Para obtener más información sobre el sistema de advertencias en GDScript, consulta Sistema de advertencias de GDScript.

Necesitamos definir otra función para calcular la velocidad inicial. Esta función girará el monstruo hacia el jugador y aleatorizará tanto su ángulo de movimiento como su velocidad.

La función tomará como argumentos una start_position, que es la posición de aparición del monstruo, y la player_position, que es la posición del jugador.

Posicionamos al monstruo en start_position y lo orientamos hacia el jugador utilizando el método look_at_from_position(), y aleatorizamos el ángulo rotando una cantidad aleatoria alrededor del eje Y. A continuación, rand_range() genera un valor aleatorio entre -PI / 4 radianes y PI / 4 radianes.

# We will call this function from the Main scene.
func initialize(start_position, player_position):
    # We position the mob and turn it so that it looks at the player.
    look_at_from_position(start_position, player_position, Vector3.UP)
    # And rotate it randomly so it doesn't move exactly toward the player.
    rotate_y(rand_range(-PI / 4, PI / 4))

Luego calculamos una velocidad aleatoria utilizando nuevamente rand_range() y la utilizamos para calcular la velocidad.

Comenzamos creando un vector 3D que apunta hacia adelante, lo multiplicamos por nuestra random_speed y, finalmente, lo rotamos utilizando el método rotated() de la clase Vector3.

func initialize(start_position, player_position):
    # ...

    # We calculate a random speed.
    var random_speed = rand_range(min_speed, max_speed)
    # We calculate a forward velocity that represents the speed.
    velocity = Vector3.FORWARD * random_speed
    # We then rotate the vector based on the mob's Y rotation to move in the direction it's looking.
    velocity = velocity.rotated(Vector3.UP, rotation.y)

Saliendo de la escena

Todavía tenemos que destruir a los monstruos cuando salen de la pantalla. Para hacerlo, conectaremos la señal screen_exited del nodo VisibilityNotifier al nodo Mob.

Regresa a la vista en 3D haciendo clic en la etiqueta 3D en la parte superior del editor. También puedes presionar Ctrl + F2 (Alt + 2 en macOS).

image8

Selecciona el nodo VisibilityNotifier y en el lado derecho de la interfaz, navega hasta el panel Node. Haz doble clic en la señal screen_exited().

image9

Conecta la señal al Mob.

image10

Esto te llevará de vuelta al editor de scripts y agregará una nueva función para ti, _on_VisibilityNotifier_screen_exited(). Desde allí, llama al método queue_free(). Esto destruirá la instancia del monstruo cuando el recuadro del VisibilityNotifier salga de la pantalla.

func _on_VisibilityNotifier_screen_exited():
    queue_free()

¡Nuestro monstruo está listo para entrar en el juego! En la siguente parte, generarás monstruos en el nivel del juego.

Aqui está el script completo Mob.gd para referencia.

extends KinematicBody

# Minimum speed of the mob in meters per second.
export var min_speed = 10
# Maximum speed of the mob in meters per second.
export var max_speed = 18

var velocity = Vector3.ZERO


func _physics_process(_delta):
    move_and_slide(velocity)

func initialize(start_position, player_position):
    look_at_from_position(start_position, player_position, Vector3.UP)
    rotate_y(rand_range(-PI / 4, PI / 4))

    var random_speed = rand_range(min_speed, max_speed)
    velocity = Vector3.FORWARD * random_speed
    velocity = velocity.rotated(Vector3.UP, rotation.y)


func _on_VisibilityNotifier_screen_exited():
    queue_free()