Saltar al contenido principal
Volver atrás

Godot #1: Vectores y Movimiento

#godot #gdscript #math #vectors

Entendiendo las matemáticas detrás del movimiento. ¿Por qué normalizamos? ¿Qué es Delta? Explicado para humanos.

La Nave y las Matemáticas

En este capítulo vamos a crear nuestra nave y a hacer que se mueva. Pero no vamos a copiar un script. Vamos a entender por qué se mueve. Y para eso, tenemos que hablar de nuestra amiga (o enemiga): La Flecha (El Vector).

¿Necesitas gráficos?

Para seguir este curso, necesitarás una nave. Si no tienes una a mano, te recomendamos encarecidamente el pack gratuito de Kenney Space Shooter Extension. Es limpio, bonito y perfecto para aprender.

¿Necesitas editar las imágenes? No hace falta piratear nada. Tienes la suite Affinity Studio disponible de forma gratuita en la web.

1. El Escenario (Plano Cartesiano)

Tu monitor es una cuadrícula de píxeles.

  • X (Horizontal): Crece hacia la derecha.
  • Y (Vertical): Crece hacia ABAJO.
¡Cuidado con la Y!

En matemáticas del colegio, la Y crece hacia arriba. En Game Dev, la Y crece hacia abajo (porque empezamos a dibujar desde la esquina superior izquierda). No lo olvides o tu nave volará al revés.

2. La Elección del Nodo (Tu Nave)

En la intro dijimos que “Todo es una Escena”. Cierto, pero… ¿de qué está hecha una escena? De Nodos. Si la Escena es la casa, los Nodos son los ladrillos. Y hay muchos tipos de ladrillos:

  1. Node2D: Es la base para el mundo 2D. Tiene posición (x, y), rotación y escala. (Si estuviéramos en 3D sería Node3D, y para interfaces Control, pero aquí vivimos en el plano).
  2. RigidBody2D: Tiene física real (gravedad, rebotes). Si le das una patada, sale volando. Difícil de controlar para un personaje principal.
  3. CharacterBody2D: El término medio perfecto. Tiene posición (como Node2D) y sabe chocarse (como RigidBody), pero no se mueve solo. TÚ eres el motor. Es el estándar para personajes en juegos 2D.

Prepara tus Assets

Antes de empezar, necesitas una nave. Puedes dibujar una o usar esta de prueba:

Nave del Jugador

Clic derecho -> Guardar imagen.
Créditos: PSX Plane por Puck.

  1. Crea una carpeta llamada sprites en tu FileSystem.
  2. Arrastra esta imagen dentro.

Pasos:

  1. En la escena vacía que aparece por defecto:
  2. En el panel de “Create Root Node”, selecciona Other Node.
  3. Escribe CharacterBody2D en el buscador y dale doble clic. Esto creará una nueva escena con este nodo como raíz. Llámalo Player.
  4. El nodo se quejará con una alerta ⚠️. Te está diciendo: “¡Oye, tengo cuerpo físico pero no tengo forma! ¿Soy un círculo? ¿Un cuadrado?”.
  5. Añade como hijo de CharacterBody2D un nodo llamado CollisionShape2D.
    • En el Inspector, donde dice Shape, selecciona New CircleShape2D.
    • Importante: Haz clic sobre el recuadro que pone “CircleShape2D” (se expandirá).
    • En Radius, escribe 40 px. Esto ajustará el tamaño exacto sin escalar el nodo.
  6. Añade otro nodo hijo Sprite2D para que se vea bonito (arrastra tu imagen de la nave a la propiedad Texture).
  7. Crea una carpeta llamada scenes en la raíz del proyecto y guarda dentro la escena como player.tscn.
Regla de oro: Hitbox vs Sprite

En Game Design, hay una regla no escrita: “Mejor que sobre a que falte”.

  • Si tu colisión es más grande que la nave, el jugador gritará “¡Pero si no me ha tocado!” cuando muera por el aire. Eso se siente injusto.
  • Si tu colisión es un poco más pequeña, permites que las balas “rocen” la nave sin matarla. Eso genera momentos de “¡Uff, casi!” que se sienten épicos.

Dale siempre un poco de ventaja al jugador. Haz el CollisionShape un 10-20% más pequeño que el dibujo visible.

3. Conceptos Básicos de GDScript

Antes de escribir código, necesitas saber leer el mapa. Si nunca has programado, esto es todo lo que necesitas saber por ahora:

  1. extends (Herencia): Es la primera línea. Le dice a Godot: “Soy una extensión de…”.

    • extends CharacterBody2D: Si extiendes de como en este caso CharacterBody2D significará que ahora el script tiene todos los métodos y propiedades de un CharacterBody2D, más lo que añadas.

    Esto se llama Herencia de Clases. Es un concepto fundamental de programación orientada a objetos. Tu script no empieza de cero: hereda un “vehículo” ya funcional con ruedas, motor y volante (CharacterBody2D), y tú solo añades la personalización (tu lógica de movimiento).

  2. func (Acción/Verbo): Es una orden o tarea. Agrupa una lista de pasos.

    • func _physics_process(delta): Es una función especial que Godot llama a un ritmo fijo (por defecto 60 veces por segundo). Todo lo que escribas dentro, se repetirá en bucle para mover cosas.
  3. var (Variable): Es un lugar donde guardamos información en la memoria RAM del ordenador.

    • var velocidad = 300: Hemos reservado un hueco en la memoria, le hemos puesto la etiqueta “velocidad” y hemos guardado el número 300 dentro.
Peligro: Tabs vs Espacios

GODOT NO PERDONA. La “sangría” (esos espacios a la izquierda) define qué código está dentro de qué función.

  • Usa siempre Tabulaciones (Tecla Tab ↹), no espacios.
  • Nunca mezcles tabs y espacios en el mismo archivo o tendrás errores invisibles.
  • Nota: El botón de copiar de esta web intenta convertir espacios a Tabs automáticamente, pero si ves errores rojos, revisa esto primero.

4. Creando el Script

Nuestra nave es bonita, pero no hace nada. Necesitamos un Script.

  1. Crea una carpeta en la raíz llamada scripts.
  2. Selecciona el nodo raíz Player.
  3. Haz clic en el icono del Pergamino con un + verde (arriba del panel de escena). O dale click derecho -> Attach Script.
  4. Aparecerá una ventana.
  5. Template: Déjalo en CharacterBody2D: Basic Movement si quieres ver mucho código complejo, o pon Empty para empezar limpio. Nosotros borraremos todo, así que da igual.
  6. Path: Aquí está la clave. No lo tires en la raíz. Escribe res://scripts/player.gd.
  7. Dale a Create.

Ahora verás un editor de texto. ¡Bienvenido a tu primer programa!

5. El Problema de “Moverse”

Para mover la nave, necesitamos decirle hacia dónde ir. Aquí entran los Vectores.

Un vector no es más que una flecha. En videojuegos, cuando esa flecha nos indica “hacia dónde”, la llamamos Vector de Dirección.

  • Si tocas Derecha, el vector apunta a (1, 0).
  • Si tocas Abajo, el vector apunta a (0, 1).

En Godot, esto se llama Input.get_vector().

Pero obtener el vector no mueve la nave. Solo nos dice “hacia dónde quiere ir el jugador”. Para moverla, necesitamos dos cosas mágicas que viven dentro de CharacterBody2D:

  1. velocity (Velocidad): Es una variable interna. Imagínala como el velocímetro de la nave. Tú le dices “Ponte a 300 km/h siguiendo este vector”.
  2. move_and_slide(): Esta función es el motor. Le dices “¡Arranca!”. Y ella sola busca la variable velocity y mueve la nave esa cantidad, chocándose si hace falta.

No tienes que pasarle velocity a move_and_slide(). Lo hace solo.

Aquí tienes el código completo. Te recomiendo escribirlo a mano para memorizar los comandos, pero si tienes prisa, puedes copiarlo.

gdscript
extends CharacterBody2D

func _physics_process(delta):
  var direction = Input.get_vector("move_left", "move_right", "move_up", "move_down")
  
  velocity = direction * 400
  move_and_slide()

Desglose línea a línea:

1. extends CharacterBody2D

Ya sabemos que esto es herencia. Aquí lo usamos concretamente para desbloquear la variable velocity. Si extendiéramos de un simple Node2D, esta variable no existiría y tendríamos que calcular la física manualmente. Al heredar de CharacterBody2D, ganamos todo el sistema de colisiones sin tener que reescribir a mano toda una lógica que ya nos da CharacterBody2D.

2. func _physics_process(delta):

Usamos el bucle de físicas (y no el _process normal) porque move_and_slide() necesita una estabilidad perfecta para calcular choques. Si usáramos el loop visual, la nave podría atravesar paredes si el juego va lento. Aquí nos aseguramos de que el movimiento sea sólido como una roca.

3. var direction = Input.get_vector(...)

Tecla(s) presionada(s)Valor de direction
Ninguna(0, 0)
Derecha(1, 0)
Abajo(0, 1)
Derecha + Abajo(0.707, 0.707) ← ¡Normalizado!

Input.get_vector() lee las teclas y construye un vector de dirección. Ya viene normalizado (magnitud 1), así que moverse en diagonal no es más rápido que moverse recto.

¿Pero qué es “move_right”? Si intentas ejecutar el código ahora, no funcionará. Godot no sabe qué teclas son esas. Tienes que configurarlas tú para definir qué significa “mover a la derecha” (puede ser la tecla D, la Flecha Derecha o el Joystick).

Configúralo ahora:

  1. Ve a Project > Project Settings > Input Map.
  2. Escribe move_right en la barra “Add New Action” y pulsa Add.
  3. Busca la nueva acción en la lista inferior y pulsa el botón + a su derecha.
  4. Pulsa la tecla D (y opcionalmente la tecla Flecha Derecha) y dale a OK.
  5. Repite el proceso para el resto:
    • move_left: Tecla A.
    • move_up: Tecla W.
    • move_down: Tecla S.

Si quieres, puedes adicionalmente en cada acción añadir por ejemplo el joystick (stick izquierdo) y al cruceta (d-pad).

4. velocity = direction * 300

La dirección es un vector de magnitud 1. Al multiplicar por 300, obtenemos “300 píxeles por segundo en esa dirección”. Es como decir: “Apunta hacia allá y ve a 300 de velocidad”.

5. move_and_slide()

Esta función lee velocity automáticamente (no hace falta pasárselo) y mueve el nodo. Además, gestiona colisiones: si hay una pared, “desliza” en vez de quedarse pegado. Por eso se llama “move and slide”.

4. ¿Por qué corres más en diagonal? (Pitágoras)

Si presionas Derecha (1, 0) y Abajo (0, 1) a la vez, Godot suma los vectores. El resultado es (1, 1).

¿Cuánto mide ese vector (1, 1)? Aquí es donde entra nuestro amigo griego.

Imagina que estás en una ciudad cuadriculada (como el Eixample de Barcelona o Manhattan):

  • Caminar 1 calle al Este cuesta 1 minuto.
  • Caminar 1 calle al Sur cuesta 1 minuto.
  • Si caminas Este y luego Sur, tardas 2 minutos en total.

Pero, ¿y si atraviesas la calle en diagonal? Aquí entra el señor Pitágoras con su famoso Teorema. No te asustes, vamos a diseccionarlo pieza a pieza:

h=a2+b2h = \sqrt{a^2 + b^2}

¿Qué significa cada letra?

  • hh (Hipotenusa): Es la distancia larga, la longitud (magnitud) del vector.
  • aa y bb (Catetos): Son los lados rectos. En nuestro caso, el movimiento en X (1 paso) y en Y (1 paso).

¡Saca la calculadora! De verdad, hazlo. Vamos a sustituir las letras por nuestros números (1 derecha, 1 abajo):

  1. Elevamos al cuadrado: 121^2 significa 1×11 \times 1. Sigue siendo 11.

    Resultado: h=1+1h = \sqrt{1 + 1}

  2. Sumamos: 1+1=21 + 1 = 2.

    Resultado: h=2h = \sqrt{2}

  3. Raíz cuadrada: Busca el botón \sqrt{} en tu calculadora del móvil y pon 22.

    Resultado: h1.41421356...h \approx 1.41421356...

Diccionario Gamer 🤓

En videojuegos y matemáticas, a esta longitud hh la llamamos Magnitud o Módulo del vector. También es conocida como Distancia Euclidiana (la línea recta entre dos puntos).

Cuando hacemos input_vector.length() en Godot, el motor hace este cálculo de Pitágoras por ti.

¡Ahí lo tienes! Si no hacemos nada, tu dirección tiene una fuerza de 1.41. Comparado con la fuerza de 1.0 cuando vas recto, ¡estás corriendo un 41% más rápido!

(0.00, 0.00)
Y+ (Abajo)
Demo Automática

Simulador

Input Raw(0, 0)
Magnitud0.000

💤 Nave parada.

Esto es un problema clásico (“Speedrunning hack”). En un juego competitivo, todos irían en diagonal para llegar antes.

La Solución: Normalizar

Normalizar un vector significa: “No me importa lo largo que sea el vector (la flecha), córtalo para que mida exactamente 1, pero mantén la dirección”.

Godot es listo. Input.get_vector() YA normaliza el resultado por defecto. Es importante saberlo porque en matemáticas de shaders o IA, tendrás que usar .normalized() manualmente muchas veces cuando solo te importe la dirección (v^\hat{v}) y no la fuerza.

5. ¿Qué demonios es delta?

Verás delta en todos los tutoriales.

gdscript
position += velocity * delta

Imagina que tu juego va a 60 FPS (imágenes por segundo). Tu código se ejecuta 60 veces por segundo. Ahora imagina que a tu amigo le va a 144 FPS. Su código se ejecuta 144 veces por segundo.

Si sumas position += 1 en cada frame:

  • Tú avanzas 60 píxeles cada segundo.
  • Tu amigo avanza 144 píxeles cada segundo.

¡Tu amigo es más rápido solo por tener mejor PC! Eso es injusto (y rompe la física).

Delta es el tiempo que ha pasado desde el último frame.

  • Si vas a 60 FPS, delta es 1/60 (aprox 0.016s).
  • Si vas a 144 FPS, delta es 1/144 (aprox 0.006s).

Si multiplicas por delta:

  • Tú: 1 * 60 veces * 0.016 = 1 metro por segundo.
  • Amigo: 1 * 144 veces * 0.006 = 1 metro por segundo.

Conclusión: Multiplicar por Delta convierte “Píxeles por Frame” en “Píxeles por Segundo”. Hace que el juego vaya igual en un banano que en un PC de la NASA.

Nota de Godot

move_and_slide() usa delta automáticamente dentro, por eso en el código de arriba no lo ves multiplicando. Pero si mueves cosas manualmente cambiando position, SIEMPRE usa delta.

6. Script Final (Por ahora)

Antes de copiar, mira dos detalles nuevos:

  1. @export: Esta palabra mágica hace que la variable speed aparezca en el Inspector de Godot. Así puedes ajustar la velocidad escribiendo un número en la cajita sin volver a abrir el script.
  2. direction * speed: Recuerda que direction vale 1 (es un Vector Unitario). Es solo la dirección pura. Al multiplicarlo por 400, convertimos ese 1 en 400 píxeles por segundo.
    • Esto significa: “Muévete 400 píxeles por segundo en esa dirección”.
gdscript
extends CharacterBody2D
  
@export var speed = 400

func _physics_process(delta):
  var direction = Input.get_vector("move_left", "move_right", "move_up", "move_down")
  
  velocity = direction * speed
  move_and_slide()

¡Felicidades! Tienes una nave que se mueve consistentemente en todas direcciones.

7. Probando todo: Tu Primer Nivel (Sandbox)

Hasta ahora hemos trabajado en el “taller” (la escena del Player). Pero una nave necesita un universo. Si le das al botón de Play (F5) ahora, te pedirá una escena principal. Vamos a crearla:

  1. Crea una Nueva Escena.
  2. Elige 2D Scene (Node2D). Esta vez no queremos físicas, solo un contenedor para el mundo. Llámalo Level (PascalCase, porque es un Nodo).
  3. Guárdala en scenes/level.tscn (snake_case, porque es un archivo).

Aquí ocurre la magia de Godot. No vamos a “copiar y pegar” al jugador. Vamos a referenciarlo.

  1. Busca tu archivo player.tscn en la carpeta scenes (abajo a la izquierda).
  2. Arrástralo desde el explorador de archivos y suéltalo dentro del panel de Escena (arriba a la izquierda, bajo donde dice Level). Esto lo colocará automáticamente en la posición (0, 0), perfectamente centrado.
  3. Verás que aparece con un icono de claqueta de cine 🎬 al lado. Eso significa que es una Instancia. Si cambias el player.tscn original (ej: lo pintas de rojo), ¡este también cambiará!

Luz, Cámara… ¡Coordenadas!

Para facilitarnos las matemáticas (donde 0 es arriba y el máximo es abajo):

  1. En la escena Level, añade un nodo hijo Camera2D.
  2. En el Inspector, busca Anchor Mode y cámbialo a Fixed Top Left.
    • Esto hace que la esquina superior izquierda sea el (0,0), como en las pantallas de píxeles clásicas.

Centrando la Nave

Ahora que el (0,0) es la esquina, tu nave (que está en 0,0) se verá cortada arriba a la izquierda. Vamos a colocarla en el centro exacto usando lógica:

  1. La resolución por defecto de Godot es 1152 x 648 (muy ancha para nuestro juego retro). Vamos a cambiarla a 800 x 600:
    • Ve a Project > Project Settings > Display > Window.
    • Cambia Viewport Width a 800.
    • Cambia Viewport Height a 600.
  2. El centro ahora es: 800 / 2 = 400 (X) y 600 / 2 = 300 (Y).
  3. Selecciona el nodo player (la instancia).
  4. En Inspector > Transform > Position, escribe:
    • X: 800/2
    • Y: 600/2
Truco: ¡Godot sabe matemáticas!

Puedes escribir literalmente 800/2 en la casilla y él hará la cuenta. O también simplemente puedes mover el Player con el ratón hasta donde quieras en la vista 2D.

¡A Jugar!

  1. Pulsa F5 (o el botón de Play arriba a la derecha).
  2. Te preguntará: “No Main Scene defined. Select one?”. Dile que Select Current.
  3. ¡Ahí lo tienes! Tu nave moviéndose en tu nuevo mundo.
¿Mi nave se va de la pantalla?

¡Es normal! Aún no hemos puesto límites (paredes). Tu nave viaja felizmente hacia el infinito de coordenadas matemáticas. En próximos capítulos le pondremos muros a este universo.