Tutorial de Godot – Parte 05: Movimiento del jugador

En este tutorial, configuraremos nuestro nodo de jugador para usar el motor de física Godot para el movimiento y escribiremos un script simple para usar el teclado o el joypad para la entrada del jugador.

Para comenzar, abramos el proyecto SimpleRPG y retomemos donde lo dejamos en el último tutorial .

Reorganizando el proyecto

Antes de continuar, me gustaría reorganizar un poco la carpeta del proyecto.

En primer lugar, eliminemos dos archivos agregados automáticamente en la creación del proyecto pero que no necesitamos: mantenga presionada la tecla Mayús, seleccione los archivos icon.png y default_env.tres , luego haga clic con el botón derecho, elija Eliminar y confirme la eliminación.

Para mantener el proyecto ordenado, organizaremos los archivos del proyecto en carpetas; será útil más adelante porque vamos a agregar nuevos archivos que se mezclarán con los existentes. Haga clic con el botón derecho en res:// , elija Nueva carpeta… y cree una carpeta llamada Escenas . Luego arrastre la escena Main.tsch a esta carpeta.

Repite las operaciones anteriores y crea una carpeta llamada Entidades . Por ahora, la única entidad que tenemos es el jugador. Luego, en Entidades , cree una carpeta llamada Player y arrastre el archivo player.png a ella. El resultado final será este:

Otra cosa que podemos hacer es cambiar el nombre del nodo raíz a Root . Para ello, selecciónalo y luego vuelve a hacer clic en el nombre para entrar en el modo de edición, o haz clic derecho sobre él y pulsa Renombrar . Después de editar, nuestra escena tendrá esta estructura:

Pulsa Play para comprobar que todo está bien. El proyecto debería funcionar correctamente porque Godot actualizará automáticamente todas las referencias a los archivos que hemos movido.

Configuración del nodo de jugador para el movimiento

Para mover al jugador, usaremos el tipo de nodo KinematicBody2D . Los nodos KinematicBody2D son tipos especiales de cuerpos físicos que están destinados a ser controlados por el usuario . Son parte del motor de física de Godot, del cual hablaremos en profundidad en el próximo tutorial. Por ahora, lo único importante que debes saber es que KinematicBody2D tiene una API para mover objetos.

Primero, agregue KinematicBody2D como elemento secundario de Root :

Ahora, mueva Sprite a él y cambie el nombre de KinematicBody2D a Player :

Una cosa importante que debe saber es que la posición de un nodo siempre es relativa a su nodo padre . Ahora que hemos movido Sprite dentro de Player , su posición (160,90) es relativa a la del nodo Player . Dado que Player será el objeto que movemos (y no solo Sprite ), tendremos que establecer la posición de Sprite en (0,0), para que esté en la misma posición que el nodo Player . Ahora podemos mover al jugador al centro de la pantalla en la posición (160,90).

Si miramos el nodo Player , vemos que hay un triángulo amarillo a su lado. Esta advertencia nos dice que no hemos definido una forma de colisión para KinematicBody2D. Por ahora vamos a ignorarlo, lo retomaremos en el próximo tutorial cuando hablemos de colisiones.

Entrada del jugador

La forma más flexible de manejar la entrada del jugador es usando el mapeo de entrada de Godot . La asignación de entrada se utiliza creando acciones de entrada con nombre, a las que puede asignar cualquier número de eventos de entrada , como pulsaciones de teclas o clics del mouse. Un nuevo proyecto de Godot incluye algunas acciones predeterminadas ya definidas. Para verlos y agregar los suyos propios, abra Proyecto → Configuración del proyecto y seleccione la pestaña Mapa de entrada :

Las acciones ui_left , ui_right , ui_up y ui_down son las que usaremos para mover a nuestro personaje. De manera predeterminada, estas acciones están asociadas con las teclas de flecha del teclado y los eventos del pad direccional del joypad. También queremos agregar la capacidad de mover al personaje usando el joystick analógico del joypad. Para ello, para cada una de estas acciones añadiremos el evento relativo del eje del joypad.

Haga clic en el botón Más a la derecha de ui_left y elija Joy Axis . Se le mostrará una ventana para elegir el dispositivo y el eje que desea agregar: elija Dispositivo 0 y Eje 0 – (Palanca izquierda izquierda) y presione Agregar.

A la derecha de ui_left , verá un valor numérico, actualmente establecido en 0.5 : es la zona muerta. La zona muerta corresponde a cuánto se debe mover el joystick (o gatillo) del joypad antes de que se considere presionada la acción. Reduzca este valor a 0,1 para que pueda capturar incluso pequeños movimientos del pad analógico.

Repita estas operaciones para las demás acciones de entrada. El resultado final será este:

El guión del movimiento

Haga clic con el botón derecho en Player y seleccione Adjuntar secuencia de comandos . Asegúrese de que el idioma elegido sea GDScript y elija la carpeta Entities/Player que creamos anteriormente como Path:

Ahora estamos en el espacio de trabajo de Script. Reemplace el código predeterminado con este:

extends KinematicBody2D

# Player movement speed
export var speed = 75

func _physics_process(delta):
	# Get player input
	var direction: Vector2
	direction.x = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left")
	direction.y = Input.get_action_strength("ui_down") - Input.get_action_strength("ui_up")
	
	# If input is digital, normalize it for diagonal movement
	if abs(direction.x) == 1 and abs(direction.y) == 1:
		direction = direction.normalized()
	
	# Apply movement
	var movement = speed * direction * delta
	move_and_collide(movement)

Antes de analizar este script, si no está familiarizado con la programación (y en particular con la programación orientada a objetos), le daré algunas definiciones de términos que verá a menudo:

  • Variable : es un contenedor para almacenar datos.
  • Función : es un bloque de código que solo se ejecuta cuando se llama dentro de otro código. Puede pasar datos, conocidos como parámetros, a una función y, como resultado, puede devolver datos.
  • Clase : es un tipo de datos definido por el usuario que incluye variables (también llamadas propiedades) y funciones (también llamadas métodos). Una clase sirve como plantilla para crear objetos.
  • Instancia : es un objeto específico creado a partir de una clase particular.
  • Herencia : es el mecanismo por el cual una clase adquiere propiedades y métodos de otra clase y los extiende.
  • Anulación de métodos : es una función que permite que una clase secundaria proporcione una implementación específica de una función que ya proporciona su clase principal.

Como vimos en nuestro primer proyecto de Godot , en GDScript cada archivo de script representa una clase. Cuando adjunta un script a un nodo, está creando una instancia de esa clase.

Ahora echemos un vistazo al guión línea por línea. Si al leer la explicación tiene alguna duda, puede ir a la página de conceptos básicos de GDScript en el sitio web de Godot para obtener más información sobre las características del lenguaje.

extends KinematicBody2D

La palabra clave extends se usa para la herencia y define qué clase extender con la clase actual. Usando esta línea de código, en nuestra clase tendremos disponibles todas las propiedades y métodos de KinematicBody2D .

# Player movement speed

En GDScript, cualquier cosa, desde un # hasta el final de la línea, se ignora y se considera un comentario.

export var speed = 75

En esta línea, usamos la palabra clave var para crear una nueva variable, llamada velocidad , que almacenará la velocidad de movimiento del jugador (expresada en píxeles por segundo). Le estamos dando un valor predeterminado de 75 usando el operador de asignación (=).

La palabra clave export hace que la variable sea visible y modificable en el editor (lo verá en el Inspector). Si cambia la variable en el editor, su nuevo valor se guarda en el archivo de escena.

func _physics_process(delta):

La palabra clave func se utiliza para definir una función. La función _physics_process() se hereda de la clase Node . En nuestro script, lo reemplazamos para procesar el movimiento del jugador.

Esta función se llama durante el paso de procesamiento de física del ciclo principal. Cualquier código que necesite acceder a las propiedades de un cuerpo físico o usar el método de un cuerpo físico debe ejecutarse en esta función , que se llama antes de cada paso de física a una velocidad constante (60 veces por segundo de forma predeterminada). Dado que la tasa es constante, el parámetro delta de la función, que representa el tiempo transcurrido desde el último procesamiento físico, también debe ser constante.

GDScript usa sangría para indicar un bloque de código. Cada línea que sigue a la declaración de la función _physics_process() está sangrada, por lo que todas forman parte del bloque de código que se ejecuta cuando se llama a la función.

var direction: Vector2

Esta línea de código crea una nueva variable denominada dirección de tipo Vector2 . Vector2 es una estructura de 2 elementos utilizada para representar posiciones en el espacio 2D. Puede acceder a sus valores utilizando las propiedades x e y . Usaremos la variable de dirección para almacenar la dirección en la que el jugador quiere moverse.

direction.x = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left")
direction.y = Input.get_action_strength("ui_down") - Input.get_action_strength("ui_up")

Este código se usa para calcular la dirección del movimiento según la entrada del usuario. Mirando estas dos líneas, aprendemos algunas cosas:

  • el operador punto (.) se usa para acceder a las propiedades de una variable (por ejemplo, dirección.x )
  • hay un objeto central de Godot, llamado Input , que se ocupa de la entrada del teclado, el mouse y los joypads.
  • La entrada tiene un método llamado get_action_strength()
  • el operador de punto también se usa para llamar a métodos de objetos (por ejemplo, Input.get_action_strength(“ui_right”) )
  • los métodos se llaman pasando sus argumentos entre paréntesis (separados por comas)
  • el argumento de get_action_strength() es una cadena, es decir, una secuencia de caracteres. En GDScript, una cadena está entre comillas dobles.

¿Qué hace este código?

La función get_action_strength() devuelve un valor entre 0 y 1, que representa la intensidad de la acción de entrada pasada como argumento . En un joypad, por ejemplo, cuanto más lejos esté la palanca de la zona muerta, más cerca estará el valor de 1. Si la acción se asigna a un control digital como una tecla o un botón, el valor devuelto será 0 ( no presionado) o 1 (presionado).

Para el componente x de la variable de dirección , llamamos a esta función para las dos acciones ui_right y ui_left y calculamos la diferencia de los dos valores. El resultado que obtenemos es un rango que va de -1 (cuando el stick está a la izquierda) a 1 (cuando está a la derecha).

Hacemos lo mismo para las acciones ui_down e ui_up, obteniendo un rango que va desde -1 (cuando el stick está arriba) a 1 (cuando está abajo).

if abs(direction.x) == 1 and abs(direction.y) == 1:
	direction = direction.normalized()

Este código se usa para corregir un problema que ocurre cuando usamos el teclado o el D-Pad para movernos en diagonal.

Cuando presionamos una sola tecla, obtenemos un vector de dirección de longitud 1. Por ejemplo, para el movimiento hacia la derecha, la dirección tiene un valor de (1,0). Pero si presionamos Derecha y Arriba simultáneamente, el vector de dirección tendrá componentes (1,-1), y su longitud será de aproximadamente 1.4 (solo aplique el teorema de Pitágoras). Si usáramos esta variable directamente para calcular el movimiento, ¡las diagonales serían un 40% más rápidas! Entonces, deberíamos realizar la llamada operación de normalización , es decir, calcular un vector de longitud 1 que apunte en la misma dirección que el vector original.

Por el contrario, cuando usamos el stick analógico, este problema no se presenta. Así que tenemos que comprobar si tenemos un movimiento diagonal no normalizado y, en ese caso, normalizarlo.

La declaración if se usa para verificar si una condición es verdadera o no, y en base a eso, decidir si ejecutar un bloque de código. La condición que verificamos es si el valor absoluto (calculado con la función abs() ) de los componentes x e y de la dirección es igual a 1. Si es cierto, significa que la entrada del jugador es un movimiento diagonal no normalizado. . En este caso, la longitud del vector será diferente de 1, por lo que simplemente usaremos la función normalizada() de Vector2, que devolverá un vector normalizado con la misma dirección.

var movement = speed * direction * delta

Ahora que estamos seguros de que tenemos un vector de dirección válido, podemos calcular el movimiento real que debe realizar nuestro personaje. Simplemente lo calculamos como la velocidad multiplicada por la dirección (que nos da la velocidad) multiplicada por el delta de tiempo transcurrido . Guardaremos el resultado en una nueva variable llamada movimiento .

move_and_collide(movement)

Esta función mueve el personaje a lo largo del vector de movimiento . Veremos en el próximo tutorial que si hay otros nodos CollisionObject2D , el personaje se detendrá si choca con ellos.

probando el juego

Ejecuta el juego. Puedes mover al personaje presionando las teclas de flecha. Si tienes un joypad, puedes probar el movimiento tanto con el D-Pad como con el stick analógico.

Conclusiones

En este tutorial, hemos aprendido a obtener información del jugador y usarla para mover el personaje en la pantalla. Probablemente, podríamos haber logrado el mismo resultado más fácilmente si hubiéramos modificado directamente la propiedad Posición del jugador. Pero haciendo uso del «método correcto» de inmediato, hemos sentado las bases para comprender el motor de física y las colisiones de Godot, un tema que discutiremos en la próxima publicación.