Work in progress

The content of this page was not yet updated for Godot 4.2 and may be outdated. If you know how to improve this page or you can confirm that it's up to date, feel free to open a pull request.

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 monstruos en una nueva escena. La estructura de nodos será similar a la escena player.tscn.

Crea una escena con, una vez más, un nodo CharacterBody3D como raíz. Nómbralo Mob. Agrega un nodo Node3D como hijo, nómbralo Pivot. Arrastra el archivo mob.glb desde el panel Sistema de Archivos hacia el Pivot y suelta para agregar el modelo 3D del monstruo a la escena.

../../_images/drag_drop_mob.webp

Puedes renombrar el mob recientemente 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 un CollisionShape3D.

image2

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

../../_images/08.create_box_shape3D.jpg

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 y no queremos eso. Cada instancia de los monstruos generados tienen un costo de memoria y procesado, y no queremos pagar por ello cuando los monstruos salen de pantalla.

Cuando un monstruo sale de la escena, ya no lo necesitemos, y lo podamos eliminar. Godot tiene un nodo que detecta cuando un objeto sale de la pantalla, VisibleOnScreenNotifier3D, y vamos a usarlo para eliminar nuestros monstruos.

Nota

Para cuando instancias objetos continuamente hay una técnica que puedes usar para evitar el coste de crearlos y destruirlos todo el tiempo, esta se llama pooling. Consiste en crear una lista de objetos y reusarlos 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 nodo VisibleOnScreenNotifier3D 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, programaremos el mecanismo de generación aleatoria en la escena main.tscn y llamaremos a la función desde allí.

Adjunta 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 el cual usaremos después para asignar a CharacterBody3D.velocity.

extends CharacterBody3D

# 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


func _physics_process(_delta):
    move_and_slide()

De manera similar al jugador, movemos al monstruo en cada cuadro de animación llamando a la función CharacterBody3D.move_and_slide(). 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.

Necesitamos definir otra función para calcular CharacterBody3D.velocity. 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 y PI / 4 radianes.

# This function will be called from the Main scene.
func initialize(start_position, player_position):
    # We position the mob by placing it at start_position
    # and rotate it towards player_position, so it looks at the player.
    look_at_from_position(start_position, player_position, Vector3.UP)
    # Rotate this mob randomly within range of -45 and +45 degrees,
    # so that it doesn't move directly towards the player.
    rotate_y(randf_range(-PI / 4, PI / 4))

Tenemos una posición al azar, ahora necesitamos una random_speed. randi_range() será útil ya que nos da un valor de enteros al azar, y usaremos min_speed y max_speed. random_speed es sólo un entero y sólo lo usaremos para multiplicar nuestra CharacterBody3D.velocity. Después de que aplicamos random_speed, rotamos el Vector3 CharacterBody3D.velocity hacia el jugador.

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

    # We calculate a random speed (integer)
    var random_speed = randi_range(min_speed, max_speed)
    # We calculate a forward velocity that represents the speed.
    velocity = Vector3.FORWARD * random_speed
    # We then rotate the velocity vector based on the mob's Y rotation
    # in order to move in the direction the mob is 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 VisibleOnScreenNotifier3D 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 VisibleOnScreenNotifier3D 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 por ti, _on_visible_on_screen_notifier_3d_screen_exited(). Desde allí llama al método queue_free(). Esta función destruirá la instancia del monstruo que la llamó.

func _on_visible_on_screen_notifier_3d_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 CharacterBody3D

# 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

func _physics_process(_delta):
    move_and_slide()

# This function will be called from the Main scene.
func initialize(start_position, player_position):
    # We position the mob by placing it at start_position
    # and rotate it towards player_position, so it looks at the player.
    look_at_from_position(start_position, player_position, Vector3.UP)
    # Rotate this mob randomly within range of -90 and +90 degrees,
    # so that it doesn't move directly towards the player.
    rotate_y(randf_range(-PI / 4, PI / 4))

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

func _on_visible_on_screen_notifier_3d_screen_exited():
    queue_free()