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:
- Node2D: Es la base para el mundo 2D. Tiene posición
(x, y), rotación y escala. (Si estuviéramos en 3D seríaNode3D, y para interfacesControl, pero aquí vivimos en el plano). - RigidBody2D: Tiene física real (gravedad, rebotes). Si le das una patada, sale volando. Difícil de controlar para un personaje principal.
- 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:

Clic derecho -> Guardar imagen.
Créditos: PSX Plane por Puck.
- Crea una carpeta llamada
spritesen tu FileSystem. - Arrastra esta imagen dentro.
Pasos:
- En la escena vacía que aparece por defecto:
- En el panel de “Create Root Node”, selecciona Other Node.
- Escribe
CharacterBody2Den el buscador y dale doble clic. Esto creará una nueva escena con este nodo como raíz. LlámaloPlayer. - 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?”.
- Añade como hijo de
CharacterBody2Dun nodo llamadoCollisionShape2D.- 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.
- En el Inspector, donde dice Shape, selecciona
- Añade otro nodo hijo
Sprite2Dpara que se vea bonito (arrastra tu imagen de la nave a la propiedad Texture). - Crea una carpeta llamada
scenesen la raíz del proyecto y guarda dentro la escena comoplayer.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:
-
extends(Herencia): Es la primera línea. Le dice a Godot: “Soy una extensión de…”.extends CharacterBody2D: Si extiendes de como en este casoCharacterBody2Dsignificará 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). -
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.
-
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.
- Crea una carpeta en la raíz llamada
scripts. - Selecciona el nodo raíz
Player. - Haz clic en el icono del Pergamino con un
+verde (arriba del panel de escena). O dale click derecho -> Attach Script. - Aparecerá una ventana.
- Template: Déjalo en
CharacterBody2D: Basic Movementsi quieres ver mucho código complejo, o ponEmptypara empezar limpio. Nosotros borraremos todo, así que da igual. - Path: Aquí está la clave. No lo tires en la raíz. Escribe
res://scripts/player.gd. - 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:
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”.move_and_slide(): Esta función es el motor. Le dices “¡Arranca!”. Y ella sola busca la variablevelocityy 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.
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()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:
- Ve a Project > Project Settings > Input Map.
- Escribe
move_righten la barra “Add New Action” y pulsa Add. - Busca la nueva acción en la lista inferior y pulsa el botón + a su derecha.
- Pulsa la tecla D (y opcionalmente la tecla Flecha Derecha) y dale a OK.
- 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:
¿Qué significa cada letra?
- (Hipotenusa): Es la distancia larga, la longitud (magnitud) del vector.
- y (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):
-
Elevamos al cuadrado: significa . Sigue siendo .
Resultado:
-
Sumamos: .
Resultado:
-
Raíz cuadrada: Busca el botón en tu calculadora del móvil y pon .
Resultado:
Diccionario Gamer 🤓
En videojuegos y matemáticas, a esta longitud 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!
Simulador
(0, 0)0.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 () y no la fuerza.
5. ¿Qué demonios es delta?
Verás delta en todos los tutoriales.
position += velocity * deltaposition += velocity * deltaImagina 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 = 1metro por segundo. - Amigo:
1 * 144 veces * 0.006 = 1metro 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:
@export: Esta palabra mágica hace que la variablespeedaparezca en el Inspector de Godot. Así puedes ajustar la velocidad escribiendo un número en la cajita sin volver a abrir el script.direction * speed: Recuerda quedirectionvale 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”.
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()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:
- Crea una Nueva Escena.
- Elige 2D Scene (Node2D). Esta vez no queremos físicas, solo un contenedor para el mundo. Llámalo
Level(PascalCase, porque es un Nodo). - Guárdala en
scenes/level.tscn(snake_case, porque es un archivo).
Instanciando al Jugador (El “Link”)
Aquí ocurre la magia de Godot. No vamos a “copiar y pegar” al jugador. Vamos a referenciarlo.
- Busca tu archivo
player.tscnen la carpetascenes(abajo a la izquierda). - 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. - Verás que aparece con un icono de claqueta de cine 🎬 al lado. Eso significa que es una Instancia. Si cambias el
player.tscnoriginal (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):
- En la escena
Level, añade un nodo hijoCamera2D. - 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:
- 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 Widtha 800. - Cambia
Viewport Heighta 600.
- El centro ahora es:
800 / 2 = 400(X) y600 / 2 = 300(Y). - Selecciona el nodo
player(la instancia). - En Inspector > Transform > Position, escribe:
- X:
800/2 - Y:
600/2
- X:
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!
- Pulsa F5 (o el botón de Play arriba a la derecha).
- Te preguntará: “No Main Scene defined. Select one?”. Dile que Select Current.
- ¡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.