Saltar al contingut principal
Tornar enrere

Godot #1: Vectors i Moviment

#godot #gdscript #math #vectors

Entenent les matemàtiques darrere del moviment. Per què normalitzem? Què és Delta? Explicat per a humans.

La Nau i les Matemàtiques

En aquest capítol crearem la nostra nau i farem que es mogui. Però no copiarem un script. Entendrem per què es mou. I per això, hem de parlar de la nostra amiga (o enemiga): La Fletxa (El Vector).

Necessites gràfics?

Per seguir aquest curs, necessitaràs una nau. Si no en tens una a mà, et recomanem encaridament el pack gratuït de Kenney Space Shooter Extension. És net, bonic i perfecte per aprendre.

Necessites editar les imatges? No cal piratejar res. Tens la suite Affinity Studio disponible de forma gratuïta a la web.

1. L’Escenari (Pla Cartesià)

El teu monitor és una quadrícula de píxels.

  • X (Horitzontal): Creix cap a la dreta.
  • Y (Vertical): Creix cap a BAIX.
Compte amb la Y!

A les matemàtiques de l’escola, la Y creix cap amunt. En Game Dev, la Y creix cap avall (perquè comencem a dibuixar des de la cantonada superior esquerra). No ho oblidis o la teva nau volarà cap per avall.

2. L’Elecció del Node (La Teva Nau)

A la intro vam dir que “Tot és una Escena”. Cert, però… de què està feta una escena? De Nodes. Si l’Escena és la casa, els Nodes són els maons. I hi ha molts tipus de maons:

  1. Node2D: És la base per al món 2D. Té posició (x, y), rotació i escala. (Si estiguéssim en 3D seria Node3D, i per a interfícies Control, però aquí vivim al pla).
  2. RigidBody2D: Té física real (gravetat, rebots). Si li dones una puntada de peu, surt volant. Difícil de controlar per a un personatge principal.
  3. CharacterBody2D: El terme mig perfecte. Té posició (com Node2D) i sap xocar (com RigidBody), però no es mou sol. TU ets el motor. És l’estàndard per a personatges en jocs 2D.

Prepara els teus Assets

Abans de començar, necessites una nau. Pots dibuixar-ne una o utilitzar aquesta de prova:

Nau del Jugador

Clic dret -> Desa la imatge.
Crèdits: PSX Plane per Puck.

  1. Crea una carpeta anomenada sprites al teu FileSystem.
  2. Arrossega aquesta imatge a dins.

Passos:

  1. A l’escena buida que apareix per defecte:
  2. Al panell de “Create Root Node”, selecciona Other Node.
  3. Escriu CharacterBody2D al cercador i fes-li doble clic. Això crearà una nova escena amb aquest node com a arrel. Anomena’l Player.
  4. El node es queixarà amb una alerta ⚠️. T’està dient: “Ei, tinc cos físic però no tinc forma! Sóc un cercle? Un quadrat?”.
  5. Afegeix com a fill de CharacterBody2D un node anomenat CollisionShape2D.
    • A l’Inspector, on diu Shape, selecciona New CircleShape2D.
    • Important: Fes clic sobre el requadre que posa “CircleShape2D” (s’expandirà).
    • A Radius, escriu 40 px. Això ajustarà la mida exacta sense escalar el node.
  6. Afegeix un altre node fill Sprite2D perquè es vegi bonic (arrossega la teva imatge de la nau a la propietat Texture).
  7. Crea una carpeta anomenada scenes a l’arrel del projecte i desa-hi l’escena com a player.tscn.
Regla d'or: Hitbox vs Sprite

En Game Design, hi ha una regla no escrita: “Més val que en sobri que no que en falti”.

  • Si la teva col·lisió és més gran que la nau, el jugador cridarà “Però si no m’ha tocat!” quan mori per l’aire. Això se sent injust.
  • Si la teva col·lisió és una mica més petita, permets que les bales “freguin” la nau sense matar-la. Això genera moments de “Ufff, gairebé!” que se senten èpics.

Dona-li sempre una mica d’avantatge al jugador. Fes el CollisionShape un 10-20% més petit que el dibuix visible.

3. Conceptes Bàsics de GDScript

Abans d’escriure codi, necessites saber llegir el mapa. Si no has programat mai, això és tot el que necessites saber per ara:

  1. extends (Herència): És la primera línia. Li diu a Godot: “Sóc una extensió de…”.

    • extends CharacterBody2D: Si estens de com en aquest cas CharacterBody2D significarà que ara l’script té tots els mètodes i propietats d’un CharacterBody2D, més el que afegeixis.

    Això es diu Herència de Classes. És un concepte fonamental de programació orientada a objectes. El teu script no comença de zero: hereta un “vehicle” ja funcional amb rodes, motor i volant (CharacterBody2D), i tu només afegeixes la personalització (la teva lògica de moviment).

  2. func (Acció/Verb): És una ordre o tasca. Agrupa una llista de passos.

    • func _physics_process(delta): És una funció especial que Godot crida a un ritme fix (per defecte 60 vegades per segon). Tot el que escriguis a dins, es repetirà en bucle per moure coses.
  3. var (Variable): És un lloc on guardem informació a la memòria RAM de l’ordinador.

    • var speed = 300: Hem reservat un lloc a la memòria, li hem posat l’etiqueta “speed” i hem guardat el número 300 a dins.
Perill: Tabs vs Espais

GODOT NO PERDONA. La “sagnia” (aquests espais a l’esquerra) defineix quin codi està dins de quina funció.

  • Utilitza sempre Tabulacions (Tecla Tab ↹), no espais.
  • Mai barregis tabs i espais al mateix fitxer o tindràs errors invisibles.
  • Nota: El botó de copiar d’aquesta web intenta convertir espais a Tabs automàticament, però si veus errors vermells, revisa això primer.

4. Creant l’Script

La nostra nau és bonica, però no fa res. Necessitem un Script.

  1. Crea una carpeta a l’arrel anomenada scripts.
  2. Selecciona el node arrel Player.
  3. Fes clic a la icona del Pergamí amb un + verd (a sobre del panell d’escena). O fes clic dret -> Attach Script.
  4. Apareixerà una finestra.
  5. Template: Deixa-ho en CharacterBody2D: Basic Movement si vols veure molt codi complex, o posa Empty per començar net. Nosaltres ho esborrarem tot, així que és igual.
  6. Path: Aquí està la clau. No el llencis a l’arrel. Escriu res://scripts/player.gd.
  7. Dona-li a Create.

Ara veuràs un editor de text. Benvingut al teu primer programa!

5. El Problema de “Moure’s”

Per moure la nau, necessitem dir-li cap a on anar. Aquí entren els Vectors.

Un vector no és més que una fletxa. En videojocs, quan aquesta fletxa ens indica “cap a on”, l’anomenem Vector de Direcció.

  • Si toques Dreta, el vector apunta a (1, 0).
  • Si toques Avall, el vector apunta a (0, 1).

En Godot, això es diu Input.get_vector().

Però obtenir el vector no mou la nau. Només ens diu “cap a on vol anar el jugador”. Per moure-la, necessitem dues coses màgiques que viuen dins de CharacterBody2D:

  1. velocity (Velocitat): És una variable interna. Imagina-la com el velocímetre de la nau. Tu li dius “Posa’t a 300 km/h seguint aquest vector”.
  2. move_and_slide(): Aquesta funció és el motor. Li dius “Arrenca!”. I ella sola busca la variable velocity i mou la nau aquesta quantitat, xocant si cal.

No has de passar-li velocity a move_and_slide(). Ho fa sol.

Aquí tens el codi complet. Et recomano escriure’l a mà per memoritzar les comandes, però si tens pressa, pots copiar-lo.

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()

Desglossament línia per línia:

1. extends CharacterBody2D

Ja sabem que això és herència. Aquí ho usem concretament per desbloquejar la variable velocity. Si estenguéssim d’un simple Node2D, aquesta variable no existiria i hauríem de calcular la física manualment. En heretar de CharacterBody2D, guanyem tot el sistema de col·lisions sense haver de reescriure a mà tota una lògica que ja ens dona CharacterBody2D.

2. func _physics_process(delta):

Usem el bucle de físiques (i no el _process normal) perquè move_and_slide() necessita una estabilitat perfecta per calcular xocs. Si uséssim el loop visual, la nau podria travessar parets si el joc va lent. Aquí ens assegurem que el moviment sigui sòlid com una roca.

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

Tecla/es premuda/esValor de direction
Cap(0, 0)
Dreta(1, 0)
Avall(0, 1)
Dreta + Avall(0.707, 0.707) ← Normalitzat!

Input.get_vector() llegeix les tecles i construeix un vector de direcció. Ja ve normalitzat (magnitud 1), així que moure’s en diagonal no és més ràpid que moure’s recte.

Però què és “move_right”? Si intentes executar el codi ara, no funcionarà. Godot no sap quines tecles són aquestes. Has de configurar-les tu per definir què significa “moure a la dreta” (pot ser la tecla D, la Fletxa Dreta o el Joystick).

Configura-ho ara:

  1. Ves a Project > Project Settings > Input Map.
  2. Escriu move_right a la barra “Add New Action” i prem Add.
  3. Busca la nova acció a la llista de sota i prem el botó + a la seva dreta.
  4. Prem la tecla D (i opcionalment la tecla Fletxa Dreta) i dona-li a OK.
  5. Repeteix el procés per a la resta:
    • move_left: Tecla A.
    • move_up: Tecla W.
    • move_down: Tecla S.

Si vols, pots addicionalment en cada acció afegir per exemple el joystick (stick esquerre) i al creueta (d-pad).

4. velocity = direction * 300

La direcció és un vector de magnitud 1. En multiplicar per 300, obtenim “300 píxels per segon en aquesta direcció”. És com dir: “Apunta cap allà i ve a 300 de velocitat”.

5. move_and_slide()

Aquesta funció llegeix velocity automàticament (no cal passar-li) i mou el node. A més, gestiona col·lisions: si hi ha una paret, “llisca” en lloc de quedar-se enganxat. Per això es diu “move and slide”.

4. Per què corres més en diagonal? (Pitàgores)

Si prems Dreta (1, 0) i Avall (0, 1) alhora, Godot suma els vectors. El resultat és (1, 1).

Quant mesura aquest vector (1, 1)? Aquí és on entra el nostre amic grec.

Imagina que ets en una ciutat quadriculada (com l’Eixample de Barcelona o Manhattan):

  • Caminar 1 carrer a l’Est costa 1 minut.
  • Caminar 1 carrer al Sud costa 1 minut.
  • Si camines Est i després Sud, trigues 2 minuts en total.

Però, i si travesses el carrer en diagonal? Aquí entra el senyor Pitàgores amb el seu famós Teorema. No t’espantis, anem a disseccionar-lo peça a peça:

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

Què significa cada lletra?

  • hh (Hipotenusa): És la distància llarga, la longitud (magnitud) del vector.
  • aa i bb (Catets): Són els costats rectes. En el nostre cas, el moviment en X (1 pas) i en Y (1 pas).

Treu la calculadora! De debò, fes-ho. Anem a substituir les lletres per els nostres números (1 dreta, 1 avall):

  1. Elevem al quadrat: 121^2 significa 1×11 \times 1. Segueix sent 11.

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

  2. Sumem: 1+1=21 + 1 = 2.

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

  3. Arrel quadrada: Busca el botó \sqrt{} a la teva calculadora del mòbil i posa 22.

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

Diccionari Gamer 🤓

En videojocs i matemàtiques, a aquesta longitud hh li diem Magnitud o Mòdul del vector. També és coneguda com Distància Euclidiana (la línia recta entre dos punts).

Quan fem input_vector.length() en Godot, el motor fa aquest càlcul de Pitàgores per tu.

Aquí ho tens! Si no fem res, la teva direcció té una força de 1.41. Comparat amb la força de 1.0 quan vas recte, estàs corrent un 41% més ràpid!

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

Simulador

Input Raw(0, 0)
Magnitud0.000

💤 Nave parada.

Això és un problema clàssic (“Speedrunning hack”). En un joc competitiu, tothom aniria en diagonal per arribar abans.

La Solució: Normalitzar

Normalitzar un vector significa: “No m’importa com de llarg sigui el vector (la fletxa), talla’l perquè mesuri exactament 1, però mantingues la direcció”.

Godot és llest. Input.get_vector() JA normalitza el resultat per defecte. És important saber-ho perquè en matemàtiques de shaders o IA, hauràs d’utilitzar .normalized() manualment moltes vegades quan només t’importi la direcció (v^\hat{v}) i no la força.

5. Què dimonis és delta?

Veuràs delta en tots els tutorials.

gdscript
position += velocity * delta

Imagina que el teu joc va a 60 FPS (imatges per segon). El teu codi s’executa 60 vegades per segon. Ara imagina que al teu amic li va a 144 FPS. El seu codi s’executa 144 vegades per segon.

Si sumes position += 1 a cada frame:

  • Tu avances 60 píxels cada segon.
  • El teu amic avança 144 píxels cada segon.

El teu amic és més ràpid només per tenir millor PC! Això és injust (i trenca la física).

Delta és el temps que ha passat des de l’últim frame.

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

Si multipliques per delta:

  • Tu: 1 * 60 vegades * 0.016 = 1 metre per segon.
  • Amic: 1 * 144 vegades * 0.006 = 1 metre per segon.

Conclusió: Multiplicar per Delta converteix “Píxels per Frame” en “Píxels per Segon”. Fa que el joc vagi igual en una patata que en un PC de la NASA.

Nota de Godot

move_and_slide() utilitza delta automàticament a dins, per això al codi de dalt no el veus multiplicant. Però si mous coses manualment canviant position, SEMPRE utilitza delta.

6. Script Final (Per ara)

Abans de copiar, mira dos detalls nous:

  1. @export: Aquesta paraula màgica fa que la variable speed aparegui a l’Inspector de Godot. Així pots ajustar la velocitat escrivint un número a la capseta sense tornar a obrir l’script.
  2. direction * speed: Recorda que direction val 1 (és un Vector Unitari). És només la direcció pura. En multiplicar-lo per 400, convertim aquest 1 en 400 píxels per segon.
    • Això significa: “Mou-te 400 píxels per segon en aquesta direcció”.
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()

Felicitats! Tens una nau que es mou consistentment en totes direccions.

7. Provant-ho tot: El Teu Primer Nivell (Sandbox)

Fins ara hem treballat al “taller” (l’escena del Player). Però una nau necessita un univers. Si li dones al botó de Play (F5) ara, et demanarà una escena principal. Anem a crear-la:

  1. Crea una Nova Escena.
  2. Tria 2D Scene (Node2D). Aquesta vegada no volem físiques, només un contenidor per al món. Anomena’l Level (PascalCase, perquè és un Node).
  3. Desa-la a scenes/level.tscn (snake_case, perquè és un fitxer).

Aquí passa la màgia de Godot. No “copiarem i enganxarem” al jugador. El referenciarem.

  1. Busca el teu fitxer player.tscn a la carpeta scenes (a baix a l’esquerra).
  2. Arrossega’l des de l’explorador de fitxers i deixa’l anar dins del panell de Scene (a dalt a l’esquerra, sota on diu Level). Això el col·locarà automàticament a la posició (0, 0), perfectament centrat.
  3. Veuràs que apareix amb una icona de claqueta de cinema 🎬 al costat. Això significa que és una Instància. Si canvies el player.tscn original (ex: el pintes de vermell), aquest també canviarà!

Llums, Càmera… Coordenades!

Per facilitar-nos les matemàtiques (on 0 és a dalt i el màxim és a baix):

  1. A l’escena Level, afegeix un node fill Camera2D.
  2. A l’Inspector, busca Anchor Mode i canvia’l a Fixed Top Left.
    • Això fa que la cantonada superior esquerra sigui el (0,0), com a les pantalles de píxels clàssiques.

Centrant la Nau

Ara que el (0,0) és la cantonada, la teva nau (que està a 0,0) es veurà tallada a dalt a l’esquerra. Anem a col·locar-la al centre exacte usant lògica:

  1. La resolució per defecte de Godot és 1152 x 648 (massa ampla per al nostre joc retro). Anem a canviar-la a 800 x 600:
    • Ves a Project > Project Settings > Display > Window.
    • Canvia Viewport Width a 800.
    • Canvia Viewport Height a 600.
  2. El centre ara és: 800 / 2 = 400 (X) i 600 / 2 = 300 (Y).
  3. Selecciona el node player (la instància).
  4. A Inspector > Transform > Position, escriu:
    • X: 800/2
    • Y: 600/2
Truc: Godot sap matemàtiques!

Pots escriure literalment 800/2 a la casella i ell farà el compte. O també simplement pots moure el Player amb el ratolí fins on vulguis a la vista 2D.

A Jugar!

  1. Prem F5 (o el botó de Play a dalt a la dreta).
  2. Et preguntarà: “No Main Scene defined. Select one?”. Digues-li que Select Current.
  3. Aquí ho tens! La teva nau movent-se al teu nou món.
La meva nau marxa de la pantalla?

És normal! Encara no hem posat límits (parets). La teva nau viatja feliçment cap a l’infinit de coordenades matemàtiques. En propers capítols posarem murs a aquest univers.