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.png

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.png

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 en la palabra "Mono" en la parte inferior de la ventana de edición para revelar el Panel Mono, y luego haciendo clic en el botón "Construir Proyecto".

../../_images/export_variable.png

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.png

Necesitamos asignar una clave a esta acción. Haga clic en el ícono "+" a la derecha, luego haga clic en la opción "Tecla" en el menú desplegable. Un cuadro de diálogo le pide que escriba la tecla deseada. Presione la flecha derecha en su teclado y haga clic en "Aceptar".

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

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.png

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
        $AnimatedSprite.play()
    else:
        $AnimatedSprite.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 AnimatedSprite.

Truco

$ is shorthand for get_node(). So in the code above, $AnimatedSprite.play() is the same as get_node("AnimatedSprite").play().

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

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.x = clamp(position.x, 0, screen_size.x)
position.y = clamp(position.y, 0, screen_size.y)

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 AnimatedSprite. Los nombres de nodo son sensible a mayúsculas y $NodeName debe coincidir con el nombre en el árbol de escenas.

Seleccionar animaciones

Now that the player can move, we need to change which animation the AnimatedSprite is playing based on its direction. We have the "walk" animation, which shows the player walking to the right. This animation should be flipped horizontally using the flip_h property for left movement. We also have the "up" animation, which should be flipped vertically with flip_v for downward movement. Let's place this code at the end of the _process() function:

if velocity.x != 0:
    $AnimatedSprite.animation = "walk"
    $AnimatedSprite.flip_v = false
    # See the note below about boolean assignment.
    $AnimatedSprite.flip_h = velocity.x < 0
elif velocity.y != 0:
    $AnimatedSprite.animation = "up"
    $AnimatedSprite.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:
    $AnimatedSprite.flip_h = true
else:
    $AnimatedSprite.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.

Añade lo siguiente en la parte superior del script, después de extends 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.png

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( Object body ). Esta señal se emitirá cuando un cuerpo entre en contacto con el player. Haz clic en "Conectar..." y luego en "Conectar" de nuevo en la ventana " Conectar señal ". No necesitamos cambiar ninguna de estas configuraciones. Godot entonces creará automáticamente una función en el script de Player.

../../_images/player_signal_connection.png

Nota el icono verde indicando que una señal es conectada a esta función. Agrega este código a la función:

func _on_Player_body_entered(body):
    hide() # Player disappears after being hit.
    emit_signal("hit")
    # 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.