Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

Programando el jugador

En esta lección, agregaremos el movimiento del jugador, la animación y lo configuraremos para detectar colisiones.

Para hacerlo, debemos agregar alguna funcionalidad que no podemos obtener de un nodo integrado, por lo que agregaremos un script. Haga clic en el nodo Player y haga clic en el botón "Añadir script":

../../_images/add_script_button.webp

En la ventana de configuración del script, puedes dejar los ajustes por defecto. Simplemente haz clic en "Crear":

Nota

Si quieres crear un script C# u otro lenguaje, selecciona el lenguaje en el menú desplegable "Lenguaje " antes de pulsar crear.

../../_images/attach_node_window.webp

Nota

Si es la primera vez que se encuentra con GDScript, lea Lenguaje de Scripting antes de continuar.

Comienza declarando las variables miembro que este objeto necesitará:

extends Area2D

@export var speed = 400 # How fast the player will move (pixels/sec).
var screen_size # Size of the game window.

Utilizar la palabra clave export en la primera variable speed nos permite asignar su valor desde el Inspector. Esto puede ser útil para valores que quieres ajustar como ajustas las propiedades de los nodos comunes. Haz clic en el nodo Player y verás que en la sección "Script Variables" del Inspector aparece la nueva propiedad. Recuerda que si cambias el valor aquí, sobrescribirá el valor asignado en el script al ejecutar el juego.

Advertencia

Si estás usando C#, necesitas (re)construir los archivos assembly del proyecto cada vez que quieras tener visibles las variables exportadas o las señales. Esta construcción puede ser lanzada manualmente pulsando el botón "Build" en la parte superior derecha del editor.

../../_images/build_dotnet.webp
../../_images/export_variable.webp

Tu script player.gd debería ya contener una función _ready() y _process(). Si no seleccionaste el script por defecto mostrado anteriormente, crea esas funciones mientras sigues la lección.

La función _ready() se llama cuando un nodo entra en la escena, este es un buen momento para averiguar el tamaño de la ventana de juego:

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

Ahora podemos utilizar la función _process() para definir lo que hará el jugador. _process() se llama en cada frame, así que la usaremos para actualizar elementos del juego que esperamos que cambien a menudo. Para el jugador haremos lo siguiente:

  • Comprobar entradas.

  • Moverse en la dirección dada.

  • Reproducir la animación apropiada.

Primero, necesitamos comprobar las entradas - ¿está el jugador presionando una tecla? Para este juego, tenemos 4 entradas direccionales para comprobar. Las acciones de entrada (Input actions) se definen en Configuración del Proyecto, dentro de la pestaña Mapa de Entrada. Puedes definir eventos personalizados y asignarlos a diferentes claves, eventos del ratón u otras entradas. Para este juego, mapearemos las teclas direccionales con las cuatro direcciones.

Haga clic en Proyecto -> Configuración del proyecto para abrir la ventana de configuración del proyecto y haga clic en la pestaña Mapa de Entrada en la parte superior. Escriba "move_right" en la barra superior y haga clic en el botón "Añadir" para agregar la acción move_right.

../../_images/input-mapping-add-action.webp

Necesitamos asignar una tecla a la acción. Haz clic en el icono "+" a la derecha para abrir la ventana de configuración de eventos.

../../_images/input-mapping-add-key.webp

El campo "Esperando Entrada..." estará seleccionado automaticamente. Presiona la tecla "derecha" en tu teclado y el menú ahora debería verse así.

../../_images/input-mapping-event-configuration.webp

Selecciona el botón "ok". La tecla "derecha" está ahora asociada a la acción move_right.

Repita estos pasos para agregar tres mapeos más:

  1. move_left mapeado con la tecla direccional hacia la izquierda.

  2. move_up mapeado con la tecla direccional hacia arriba.

  3. Y move_down mapeado con la tecla direccional hacia abajo.

Tu mapa de entradas debería verse así:

../../_images/input-mapping-completed.webp

Haga clic en el botón "Cerrar" para cerrar la configuración del proyecto.

Nota

Solo asignamos una tecla a cada acción de entrada, pero puede asignar varias teclas, botones del mando o botones del ratón a la misma acción de entrada.

Puedes detectar si una tecla se está presionando usando Input.is_action_pressed(), lo que devuelve true si está presionada o false en caso contrario.

func _process(delta):
    var velocity = Vector2.ZERO # The player's movement vector.
    if Input.is_action_pressed("move_right"):
        velocity.x += 1
    if Input.is_action_pressed("move_left"):
        velocity.x -= 1
    if Input.is_action_pressed("move_down"):
        velocity.y += 1
    if Input.is_action_pressed("move_up"):
        velocity.y -= 1

    if velocity.length() > 0:
        velocity = velocity.normalized() * speed
        $AnimatedSprite2D.play()
    else:
        $AnimatedSprite2D.stop()

Comenzaremos colocando velocity en (0,0) - por defecto, el jugador no se debería estar moviendo. Luego revisamos cada entrada y sumamos o restamos de velocity para obtener la dirección. Por ejemplo, si mantienes right (derecha) y down (abajo) al mismo tiempo, el resultado del vector velocity será (1,1). En este caso, como estamos agregando un movimiento horizontal y vertical, el jugador podrá moverse más rápido que si solo se estuviese moviendo horizontalmente.

Podemos prevenir eso normalizando velocity, lo que significa que su longitud será 1, y multiplicando por la velocidad total deseada. Esto hará que el movimiento diagonal no sea más rápido.

Truco

Si no has usado antes cálculo vectorial, o necesitas un repaso, puedes ver una explicación de la utilización de vectores en Godot en Matemáticas vectoriales. Es bueno saberlo pero no será necesario para el resto de este tutorial.

También verificamos si el jugador se está moviendo para poder llamar a play() o stop() en el AnimatedSprite2D.

Truco

$ es la abreviatura de get_node(). Así que, en el código anterior, $AnimatedSprite2D.play() es lo mismo que get_node("AnimatedSprite2D").play().

En GDScript, $ retorna el nodo en la ruta relativa del nodo actual, o retorna null si el nodo no se encuentra. Como AnimatedSprite2D es hijo del nodo actual, podemos utilizar $AnimatedSprite2D.

Ahora que tenemos la dirección del movimiento, podemos actualizar la posición de Player. También podemos usar clamp() para prevenir que abandone la pantalla. Aplicar clamp quiere decir que vamos a restringir un valor a un determinado rango. Agrega lo siguiente al final de la función _process (asegúrate de que no esté indentada dentro del else):

position += velocity * delta
position = position.clamp(Vector2.ZERO, screen_size)

Truco

El parámetro delta en la función _process() se refiere al frame length -la cantidad de tiempo que le tomo al frame anterior para completarse. Usando este valor aseguras que tu movimiento sera consistente incluso si el frame rate cambia .

Haz clic en "Reproducir Escena" (F6, Cmd + R on macOS) y confirme que puede mover el player por la pantalla en todas las direcciones.

Advertencia

Si tu obtienes un error en el panel "Debugger" que dice

`` Intento de llamar a la función 'play' en la base 'null instance' en una instancia nula ''

esto puede significar que escribiste mal el nombre del nodo AnimatedSprite2D. Los nombres de nodo son sensible a mayúsculas y $NombreNodo debe coincidir con el nombre en el árbol de escenas.

Seleccionar animaciones

Ahora que el jugador puede moverse, necesitamos cambiar qué animación está reproduciendo el AnimatedSprite2D en función de su dirección. Tenemos la animación "walk", que muestra al jugador caminando hacia la derecha. Esta animación debe ser volteada horizontalmente usando la propiedad flip_h para el movimiento hacia la izquierda. También tenemos la animación "arriba", que debe ser volteada verticalmente con flip_v para el movimiento hacia abajo. Coloquemos este código al final de la función _process():

if velocity.x != 0:
    $AnimatedSprite2D.animation = "walk"
    $AnimatedSprite2D.flip_v = false
    # See the note below about boolean assignment.
    $AnimatedSprite2D.flip_h = velocity.x < 0
elif velocity.y != 0:
    $AnimatedSprite2D.animation = "up"
    $AnimatedSprite2D.flip_v = velocity.y > 0

Nota

Las asignaciones booleanas en el código de arriba son una abreviatura para los programadores. Como estamos haciendo una comparación (booleana) y al mismo tiempo asignando un valor booleano, podemos hacer las dos cosas a la vez. Considere este código versus la asignación booleana abreviada de arriba:

if velocity.x < 0:
    $AnimatedSprite2D.flip_h = true
else:
    $AnimatedSprite2D.flip_h = false

Reproduce la escena de nuevo y revisa si las animaciones son correctas en cada una de las direcciones.

Truco

Un error común aquí es escribir los nombres de las animaciones mal. Los nombres de las animaciones en el SpriteFrames panel deberán coincidir con lo que tu escribes en tu código. si tu llamas la animación "Caminar", tu también deberás usar la mayúscula "C" en el código.

Cuando estés seguro de que el movimiento funciona correctamente, agrega esta línea a _ready() para que el jugador esté oculto cuando comience el juego:

hide()

Preparacion para las colisiones

Queremos detectar cuando un enemigo alcanza a Player, pero no hemos creado ningún enemigo todavía. Eso está bien, porque vamos a utilizar la funcionalidad de señales (signal) de Godot para hacer que funcione.

Agrega lo siguiente a la parte superior del script. Si estás usando GDScript agrégalo después de extends Area2D. Si estás usando C#, agrégalo después de public partial class Player : Area2D:

signal hit

Esto define una señal personalizada llamada "hit" que nuestro player emitirá (enviará) cuando colisione con un enemigo. Usaremos Area2D para detectar la colisión. Selecciona el nodo Player y haz clic en la pestaña "Nodos" junto a la pestaña Inspector para ver la lista de señales que puede emitir el player:

../../_images/player_signals.webp

Observa que nuestra señal personalizada de "hit" también está ahí. Como nuestros enemigos van a ser nodos RigidBody2D, necesitamos la señal body_entered(body: Node2D). Esta señal se emitirá cuando un cuerpo entre en contacto con el player. Haz clic en "Conectar..." y aparecerá la ventana " Conectar señal ".

Godot creará una función con ese nombre exacto directamente en el script para usted. Usted no necesita cambiar la configuración por defecto en este momento.

Advertencia

Si estas usando un editor de texto externo (por ejemplo, Visual Studio Code), un error actualmente impide que Godot lo haga. Serás enviado a tu editor externo, pero la nueva función no estará allí.

En este caso, tendrá que escribir la función usted mismo en el archivo de script del reproductor.

../../_images/player_signal_connection.webp

Fíjate en el icono verde que indica que una señal está conectada a esta función; esto no significa que la función exista, sólo que la señal intentará conectarse a una función con ese nombre, ¡así que comprueba que la ortografía de la función coincide exactamente!

A continuación, añade este código a la función:

func _on_body_entered(body):
    hide() # Player disappears after being hit.
    hit.emit()
    # Must be deferred as we can't change physics properties on a physics callback.
    $CollisionShape2D.set_deferred("disabled", true)

Cada vez que un enemigo golpea al jugador, la señal será emitida. Necesitamos deshabilitar la colisión del jugador para que no activemos la señal de hit más de una vez.

Nota

Desactivar la forma de colisión del nodo Area puede causar un error si ocurre en medio del procesado de colisiones del motor. El uso de set_deferred() nos permite hacer que Godot espere para desactivar la forma hasta que sea seguro hacerlo.

Lo último será agregar una función que llamaremos para reiniciarlo cuando comenzamos un juego nuevo.

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

Con el jugador funcionando, trabajaremos con el enemigo en la próxima lección.