← Volver al listado de tecnologías

Capítulo 4: GDScript Básico - Fundamentos del Lenguaje

Por: Artiko
godotgdscriptprogramaciónbásicofundamentossintaxis

Capítulo 4: GDScript Básico - Fundamentos del Lenguaje

GDScript es el corazón de Godot. En este capítulo aprenderás desde cero todos los fundamentos necesarios para empezar a programar tus juegos. No necesitas experiencia previa en programación - empezaremos desde lo más básico.

¿Qué es GDScript?

GDScript es un lenguaje de programación creado específicamente para Godot. Si has programado en Python, te sentirás como en casa. Si nunca has programado, no te preocupes - GDScript fue diseñado para ser fácil de aprender.

Características de GDScript

Tu Primer Script

Crea un nuevo script y verás esto:

extends Node

# Called when the node enters the scene tree for the first time.
func _ready():
    pass # Replace with function body.

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
    pass

Vamos a entender cada línea:

extends Node  # Este script hereda de la clase Node

# Esto es un comentario - no se ejecuta
# Los comentarios explican el código

func _ready():  # Define una función llamada _ready
    pass  # pass significa "no hacer nada aún"

Comentarios y Documentación

Los comentarios son notas para ti y otros programadores:

# Comentario de una línea

# Puedes tener
# múltiples líneas
# de comentarios

"""
Este es un comentario
de múltiples líneas
usando triple comillas
"""

## Comentarios de documentación (para generar docs)
## @tutorial: https://docs.godotengine.org
## @deprecated: Esta función será eliminada

func mi_funcion():
    # Los comentarios dentro de funciones explican la lógica
    var vida = 100  # También puedes comentar al final de líneas

Variables: Almacenando Información

Las variables son como cajas donde guardas datos:

Declaración Básica

# Usar 'var' para crear variables
var mi_numero = 42
var mi_texto = "Hola Godot"
var mi_decimal = 3.14
var es_verdad = true

# Puedes cambiar el valor
mi_numero = 100
mi_texto = "Adiós"

# Variables sin valor inicial
var sin_valor  # Vale null por defecto
var otra_variable = null  # Explícitamente null

Tipos de Datos Básicos

# Enteros (int) - Números sin decimales
var edad = 25
var puntos = 0
var vidas = 3
var numero_negativo = -10

# Decimales (float) - Números con punto decimal
var velocidad = 5.5
var gravedad = 9.81
var pi = 3.14159
var porcentaje = 0.75

# Texto (String) - Cadenas de caracteres
var nombre = "Mario"
var mensaje = "Bienvenido al juego"
var vacio = ""  # String vacío
var con_comillas = 'También con comillas simples'

# Booleanos (bool) - Verdadero o Falso
var esta_vivo = true
var juego_pausado = false
var puede_saltar = true

Constantes

Las constantes no pueden cambiar después de definirse:

# Constantes con MAYÚSCULAS (convención)
const MAX_VIDAS = 3
const VELOCIDAD_JUGADOR = 200.0
const NOMBRE_JUEGO = "Mi Súper Juego"
const GRAVEDAD = 9.8

# Esto daría error:
# MAX_VIDAS = 5  # ¡No puedes cambiar una constante!

# Úsalas para valores que nunca cambian
const ANCHO_PANTALLA = 1280
const ALTO_PANTALLA = 720
const VERSION = "1.0.0"

Conversión de Tipos

A veces necesitas convertir entre tipos:

# String a número
var texto_numero = "42"
var numero = int(texto_numero)  # Convierte a 42
var decimal = float("3.14")     # Convierte a 3.14

# Número a String
var puntos = 100
var texto_puntos = str(puntos)  # "100"
var mensaje = "Tienes " + str(puntos) + " puntos"

# Boolean
var verdadero = bool(1)   # true (cualquier número != 0)
var falso = bool(0)       # false
var texto_bool = bool("") # false (string vacío)
var texto_lleno = bool("algo") # true

Operadores Matemáticos

Operaciones Básicas

# Suma
var resultado = 5 + 3      # 8
var vida = vida + 10       # Aumenta vida en 10
vida += 10                 # Forma corta de lo anterior

# Resta
var diferencia = 10 - 3    # 7
var vida = vida - 20       # Reduce vida en 20
vida -= 20                 # Forma corta

# Multiplicación
var producto = 4 * 5       # 20
var doble = puntos * 2
puntos *= 2                # Duplica puntos

# División
var division = 10 / 2      # 5.0 (siempre retorna float)
var mitad = vida / 2.0
vida /= 2                  # Divide vida a la mitad

# División entera
var division_entera = 10 // 3  # 3 (sin decimales)

# Módulo (resto de división)
var resto = 10 % 3         # 1
var es_par = numero % 2 == 0  # true si es par

# Potencia
var cuadrado = 3 ** 2      # 9
var cubo = 2 ** 3          # 8

Operaciones con Strings

# Concatenación (unir textos)
var nombre = "Juan"
var apellido = "Pérez"
var completo = nombre + " " + apellido  # "Juan Pérez"

# Repetición
var linea = "-" * 10  # "----------"
var espacios = " " * 5  # "     "

# Interpolación (formato)
var vidas = 3
var puntos = 100
var mensaje = "Vidas: %d, Puntos: %d" % [vidas, puntos]
# Resultado: "Vidas: 3, Puntos: 100"

# Diferentes formatos
var pi = 3.14159
var formato = "Pi es %.2f" % pi  # "Pi es 3.14"
var nombre = "Mario"
var saludo = "Hola %s" % nombre  # "Hola Mario"

Operadores de Comparación

Para comparar valores:

var a = 5
var b = 10

# Igual
var son_iguales = (a == b)        # false
var mismo_numero = (5 == 5)       # true

# Diferente
var son_diferentes = (a != b)     # true
var no_es_cero = (puntos != 0)    # depende de puntos

# Mayor que
var es_mayor = (b > a)            # true
var tiene_vida = (vida > 0)       # para verificar si está vivo

# Menor que
var es_menor = (a < b)            # true
var sin_municion = (balas < 1)    # true si no hay balas

# Mayor o igual
var mayor_igual = (10 >= 10)      # true
var nivel_maximo = (nivel >= 99)  # verificar nivel máximo

# Menor o igual
var menor_igual = (5 <= 10)       # true
var vida_critica = (vida <= 25)   # verificar vida baja

Operadores Lógicos

Para combinar condiciones:

# AND (y) - Ambas deben ser verdaderas
var puede_atacar = esta_vivo and tiene_municion
var puede_comprar = tiene_dinero and tienda_abierta

# OR (o) - Al menos una debe ser verdadera
var game_over = sin_vidas or tiempo_agotado
var puede_abrir = tiene_llave or es_admin

# NOT (no) - Invierte el valor
var esta_muerto = not esta_vivo
var no_puede_saltar = not en_el_suelo

# Combinaciones
var puede_super_salto = (
    esta_vivo and 
    en_el_suelo and 
    (tiene_power_up or modo_debug)
)

Estructuras de Control: if/else

El if toma decisiones en tu código:

if Básico

var vida = 75

if vida > 0:
    print("Estás vivo")

# Con múltiples líneas
if vida > 0:
    print("Estás vivo")
    puede_mover = true
    puede_atacar = true

if/else

if vida > 0:
    print("Estás vivo")
else:
    print("Estás muerto")
    reiniciar_nivel()

# Operador ternario (if en una línea)
var estado = "vivo" if vida > 0 else "muerto"

if/elif/else

if vida >= 100:
    print("Vida completa")
    color_barra = Color.GREEN
elif vida >= 50:
    print("Vida buena")
    color_barra = Color.YELLOW
elif vida >= 25:
    print("Vida baja")
    color_barra = Color.ORANGE
else:
    print("¡Crítico!")
    color_barra = Color.RED
    reproducir_alerta()

# Condiciones múltiples
if esta_vivo and tiene_llave:
    abrir_puerta()
elif esta_vivo and not tiene_llave:
    mostrar_mensaje("Necesitas una llave")
else:
    mostrar_mensaje("Game Over")

Bucles: Repitiendo Código

Bucle for

Para repetir un número específico de veces:

# Repetir 5 veces
for i in 5:
    print("Repetición ", i)  # 0, 1, 2, 3, 4

# Con range para más control
for i in range(5):
    print(i)  # 0, 1, 2, 3, 4

for i in range(1, 6):
    print(i)  # 1, 2, 3, 4, 5

for i in range(10, 0, -1):
    print(i)  # Cuenta regresiva: 10, 9, 8...1

# Recorrer arrays
var enemigos = ["Goblin", "Orco", "Dragón"]
for enemigo in enemigos:
    print("Luchando contra ", enemigo)

# Con índice
for i in range(enemigos.size()):
    print("Enemigo ", i, ": ", enemigos[i])

Bucle while

Para repetir mientras una condición sea verdadera:

var contador = 0
while contador < 5:
    print("Contador: ", contador)
    contador += 1

# Bucle de juego típico
var jugando = true
while jugando:
    actualizar_juego()
    if vida <= 0:
        jugando = false

# Cuidado con bucles infinitos
var x = 0
while x < 10:
    print(x)
    x += 1  # Sin esto, sería infinito!

Control de Bucles

# break - Sale del bucle
for i in range(10):
    if i == 5:
        break  # Sale cuando i es 5
    print(i)  # Imprime 0, 1, 2, 3, 4

# continue - Salta a la siguiente iteración
for i in range(10):
    if i % 2 == 0:
        continue  # Salta números pares
    print(i)  # Imprime 1, 3, 5, 7, 9

# Ejemplo práctico
var items = ["Poción", "", "Espada", "", "Escudo"]
for item in items:
    if item == "":
        continue  # Ignora espacios vacíos
    print("Tienes: ", item)

Funciones: Código Reutilizable

Las funciones son bloques de código que puedes usar múltiples veces:

Funciones Básicas

# Función simple
func saludar():
    print("¡Hola!")
    print("¿Cómo estás?")

# Llamar la función
saludar()  # Ejecuta el código de la función

# Función con parámetros
func saludar_a(nombre):
    print("¡Hola, ", nombre, "!")

# Llamar con argumentos
saludar_a("Mario")  # ¡Hola, Mario!
saludar_a("Luigi")  # ¡Hola, Luigi!

Funciones con Return

# Función que devuelve un valor
func sumar(a, b):
    return a + b

# Usar el valor retornado
var resultado = sumar(5, 3)  # resultado = 8
print("5 + 3 = ", resultado)

# Función con múltiples returns
func obtener_estado_vida(vida):
    if vida >= 100:
        return "Perfecto"
    elif vida >= 50:
        return "Bueno"
    elif vida > 0:
        return "Crítico"
    else:
        return "Muerto"

var estado = obtener_estado_vida(75)  # "Bueno"

Parámetros Opcionales

# Parámetros con valores por defecto
func crear_enemigo(tipo = "Goblin", vida = 100):
    print("Creando ", tipo, " con ", vida, " de vida")

# Diferentes formas de llamarla
crear_enemigo()                    # Goblin con 100
crear_enemigo("Orco")              # Orco con 100
crear_enemigo("Dragón", 500)       # Dragón con 500

# Función más compleja
func aplicar_dano(cantidad, es_critico = false, elemento = "normal"):
    var dano_total = cantidad
    if es_critico:
        dano_total *= 2
    if elemento == "fuego":
        dano_total *= 1.5
    return dano_total

var dano = aplicar_dano(10)  # 10 de daño normal
var dano_critico = aplicar_dano(10, true)  # 20 de daño
var dano_fuego = aplicar_dano(10, false, "fuego")  # 15 de daño

Arrays: Listas de Elementos

Los arrays almacenan múltiples valores:

Arrays Básicos

# Crear arrays
var vacio = []
var numeros = [1, 2, 3, 4, 5]
var nombres = ["Ana", "Luis", "María"]
var mixto = [1, "texto", 3.14, true]  # Diferentes tipos

# Acceder a elementos (índice empieza en 0)
var primero = numeros[0]  # 1
var segundo = numeros[1]  # 2
var ultimo = numeros[4]   # 5

# Modificar elementos
numeros[0] = 10  # Ahora es [10, 2, 3, 4, 5]
nombres[1] = "Carlos"  # ["Ana", "Carlos", "María"]

# Tamaño del array
var cantidad = numeros.size()  # 5
var hay_elementos = nombres.size() > 0  # true

Operaciones con Arrays

var inventario = []

# Añadir elementos
inventario.append("Espada")       # Añade al final
inventario.push_back("Escudo")    # Igual que append
inventario.push_front("Poción")   # Añade al inicio
# inventario = ["Poción", "Espada", "Escudo"]

# Insertar en posición específica
inventario.insert(1, "Arco")
# inventario = ["Poción", "Arco", "Espada", "Escudo"]

# Eliminar elementos
inventario.pop_back()   # Quita el último
inventario.pop_front()  # Quita el primero
inventario.remove_at(0) # Quita en posición específica
inventario.erase("Espada") # Quita primera ocurrencia

# Buscar elementos
var tiene_espada = "Espada" in inventario
var posicion = inventario.find("Escudo")  # Índice o -1

# Limpiar array
inventario.clear()  # Vacía el array

Arrays Útiles

# Array de enemigos en el nivel
var enemigos = []
func agregar_enemigo(enemigo):
    enemigos.append(enemigo)

func eliminar_enemigo(enemigo):
    enemigos.erase(enemigo)

# Array de puntuaciones
var puntuaciones = [100, 250, 50, 300, 150]
puntuaciones.sort()  # Ordena: [50, 100, 150, 250, 300]
var mejor = puntuaciones[-1]  # Último elemento: 300

# Array para histórico
var historico_vida = []
func actualizar_vida(nueva_vida):
    historico_vida.append(nueva_vida)
    if historico_vida.size() > 10:
        historico_vida.pop_front()  # Mantén solo últimos 10

Diccionarios: Pares Clave-Valor

Los diccionarios almacenan datos con etiquetas:

Diccionarios Básicos

# Crear diccionarios
var jugador = {
    "nombre": "Mario",
    "nivel": 5,
    "vida": 100,
    "mana": 50
}

# Acceder a valores
var nombre = jugador["nombre"]  # "Mario"
var nivel = jugador.nivel       # 5 (notación de punto)

# Modificar valores
jugador["nivel"] = 6
jugador.vida = 80

# Añadir nuevas claves
jugador["experiencia"] = 1500
jugador["clase"] = "Guerrero"

# Verificar si existe una clave
if "mana" in jugador:
    print("El jugador tiene mana")

if jugador.has("inventario"):
    print("Tiene inventario")

Operaciones con Diccionarios

var stats = {
    "fuerza": 10,
    "agilidad": 15,
    "inteligencia": 8
}

# Obtener con valor por defecto
var carisma = stats.get("carisma", 5)  # 5 si no existe

# Obtener todas las claves
var atributos = stats.keys()  # ["fuerza", "agilidad", "inteligencia"]

# Obtener todos los valores
var valores = stats.values()  # [10, 15, 8]

# Eliminar claves
stats.erase("inteligencia")

# Limpiar diccionario
stats.clear()

# Combinar diccionarios
var base_stats = {"fuerza": 10, "vida": 100}
var bonus_stats = {"fuerza": 5, "mana": 50}
base_stats.merge(bonus_stats)
# Resultado: {"fuerza": 5, "vida": 100, "mana": 50}

Usos Prácticos

# Sistema de inventario
var inventario = {
    "pociones": 5,
    "llaves": 2,
    "oro": 100
}

func usar_item(item):
    if item in inventario and inventario[item] > 0:
        inventario[item] -= 1
        return true
    return false

# Configuración del juego
var config = {
    "volumen_musica": 0.8,
    "volumen_fx": 1.0,
    "dificultad": "normal",
    "pantalla_completa": false
}

# Base de datos de enemigos
var enemigos_db = {
    "goblin": {"vida": 30, "ataque": 5, "exp": 10},
    "orco": {"vida": 50, "ataque": 10, "exp": 25},
    "dragon": {"vida": 200, "ataque": 30, "exp": 100}
}

func crear_enemigo(tipo):
    if tipo in enemigos_db:
        return enemigos_db[tipo].duplicate()
    return null

Tipos Especiales de Godot

Vector2: Posiciones y Direcciones 2D

# Crear vectores
var posicion = Vector2(100, 200)  # x=100, y=200
var direccion = Vector2(1, 0)     # Derecha
var cero = Vector2.ZERO           # (0, 0)
var arriba = Vector2.UP           # (0, -1)
var abajo = Vector2.DOWN          # (0, 1)
var izquierda = Vector2.LEFT      # (-1, 0)
var derecha = Vector2.RIGHT       # (1, 0)

# Operaciones
var suma = posicion + Vector2(50, 50)
var velocidad = direccion * 100  # Escalar
var distancia = posicion.distance_to(Vector2.ZERO)
var normalizado = direccion.normalized()  # Longitud 1

# Uso común
position += velocity * delta  # Mover personaje

Color: Manejo de Colores

# Crear colores
var rojo = Color.RED
var azul = Color(0, 0, 1)  # RGB
var transparente = Color(1, 1, 1, 0.5)  # RGBA
var desde_hex = Color("#FF5733")

# Colores predefinidos
var verde = Color.GREEN
var blanco = Color.WHITE
var negro = Color.BLACK

# Modificar sprite
$Sprite2D.modulate = Color.RED  # Tinte rojo

NodePath: Referencias a Nodos

# Rutas de nodos
var ruta = NodePath("Player/Sprite2D")
var ruta_absoluta = NodePath("/root/Main/Player")

# Obtener nodos
var sprite = get_node("Sprite2D")  # Hijo directo
var player = get_node("/root/Main/Player")  # Ruta absoluta
var padre = get_node("..")  # Nodo padre

# Notación $
var animation = $AnimationPlayer  # Igual que get_node("AnimationPlayer")
var nested = $Container/Label     # Nodo anidado

Sistema de Nodos y Señales

Ciclo de Vida del Nodo

extends Node

# Se llama cuando el nodo entra al árbol
func _ready():
    print("Nodo listo")
    configurar_inicial()

# Se llama cada frame
func _process(delta):
    # delta es el tiempo desde el último frame
    actualizar_logica(delta)

# Se llama para física (60 fps fijo)
func _physics_process(delta):
    mover_personaje(delta)

# Se llama al salir del árbol
func _exit_tree():
    guardar_datos()
    print("Adiós")

Señales Básicas

# Definir señal
signal vida_cambio(nueva_vida)
signal muerto()
signal item_recogido(nombre_item)

# Emitir señal
func recibir_dano(cantidad):
    vida -= cantidad
    vida_cambio.emit(vida)
    if vida <= 0:
        muerto.emit()

# Conectar señal
func _ready():
    # Conectar a función
    player.muerto.connect(_on_player_muerto)
    
    # Conectar con parámetros extra
    button.pressed.connect(_on_button_pressed.bind("extra_data"))

func _on_player_muerto():
    print("Game Over")
    get_tree().reload_current_scene()

Input: Detectando Controles

Input Básico

func _process(delta):
    # Teclas
    if Input.is_action_pressed("ui_right"):
        position.x += speed * delta
    
    if Input.is_action_just_pressed("jump"):
        if is_on_floor():
            velocity.y = -jump_force
    
    # Mouse
    if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
        disparar()
    
    var mouse_pos = get_global_mouse_position()
    look_at(mouse_pos)

func _input(event):
    # Eventos específicos
    if event is InputEventKey:
        if event.pressed and event.keycode == KEY_SPACE:
            print("Espacio presionado")
    
    if event is InputEventMouseButton:
        if event.button_index == MOUSE_BUTTON_LEFT:
            if event.pressed:
                print("Click en: ", event.position)

Gestión de Recursos de Godot

Godot Asset Library

Godot tiene su propia “tienda” de assets y plugins gratuitos:

  1. En el Editor: AssetLib (pestaña junto a 2D, 3D, Script)
  2. Online: godotengine.org/asset-library

Tipos de Assets Disponibles

# Plugins populares en Asset Library:
# - Dialogic (sistema de diálogos)
# - Godot-Firebase (integración Firebase)  
# - ProtonScatter (herramienta de scatter)
# - SmartShape2D (formas 2D inteligentes)

# Para instalar desde AssetLib:
# 1. Buscar el asset
# 2. Click en Download
# 3. Install
# 4. Activar en Project Settings > Plugins

Gestión de Dependencias

# project.godot - archivo de configuración
# No hay npm, pero puedes:

# 1. Usar git submodules para librerías
# 2. Copiar addons/ en tu proyecto
# 3. Usar el Asset Library integrado

# Estructura típica:
# res://
# ├── addons/          # Plugins y librerías
# │   ├── dialogic/
# │   └── firebase/
# ├── scenes/
# └── scripts/

Recursos y Cargas

# Cargar recursos
var textura = load("res://icon.png")  # Carga inmediata
var escena = preload("res://player.tscn")  # Carga en compilación

# Instanciar escenas
var player_instance = escena.instantiate()
add_child(player_instance)

# Recursos dinámicos
var ruta = "res://sprites/" + nombre_sprite + ".png"
var sprite_texture = load(ruta)

Debugging y Errores Comunes

# Diferentes tipos de print
print("Mensaje normal")
print_debug("Solo en modo debug")
printerr("Mensaje de error")
push_error("Error que aparece en el debugger")
push_warning("Advertencia en el debugger")

# Print con múltiples valores
var vida = 100
var nombre = "Mario"
print("Jugador: ", nombre, " - Vida: ", vida)

# Print formateado
print("Vida: %d/%d" % [vida_actual, vida_maxima])

Errores Comunes y Soluciones

# Error: Invalid get index 'x' (on base: 'null')
# Solución: Verificar que el nodo existe
if player != null:
    player.position.x = 100

# Error: Invalid call. Nonexistent function
# Solución: Verificar el nombre de la función
if target.has_method("recibir_dano"):
    target.recibir_dano(10)

# Error: Division by zero
# Solución: Verificar antes de dividir
if divisor != 0:
    resultado = dividendo / divisor
else:
    resultado = 0

# Error: Index out of bounds
# Solución: Verificar tamaño del array
if index < array.size():
    var elemento = array[index]

Ejercicios Prácticos

Ejercicio 1: Calculadora Simple

# Crea funciones para operaciones básicas
func sumar(a, b):
    return a + b

func restar(a, b):
    return a - b

func multiplicar(a, b):
    return a * b

func dividir(a, b):
    if b != 0:
        return a / b
    else:
        push_error("División por cero")
        return 0

# Prueba
func _ready():
    print("2 + 3 = ", sumar(2, 3))
    print("10 - 4 = ", restar(10, 4))
    print("5 * 6 = ", multiplicar(5, 6))
    print("20 / 4 = ", dividir(20, 4))

Ejercicio 2: Sistema de Inventario

var inventario = []
var capacidad_maxima = 10

func agregar_item(item):
    if inventario.size() < capacidad_maxima:
        inventario.append(item)
        print("Añadido: ", item)
        return true
    else:
        print("Inventario lleno")
        return false

func usar_item(indice):
    if indice < inventario.size():
        var item = inventario[indice]
        inventario.remove_at(indice)
        print("Usado: ", item)
        return item
    return null

func listar_inventario():
    print("=== Inventario ===")
    for i in range(inventario.size()):
        print(i, ": ", inventario[i])

Ejercicio 3: Control de Personaje

extends CharacterBody2D

const SPEED = 300.0
var vida = 100
var puede_moverse = true

func _ready():
    print("Personaje listo")

func _physics_process(delta):
    if not puede_moverse:
        return
    
    var direccion = Vector2.ZERO
    
    if Input.is_action_pressed("ui_right"):
        direccion.x += 1
    if Input.is_action_pressed("ui_left"):
        direccion.x -= 1
    if Input.is_action_pressed("ui_down"):
        direccion.y += 1
    if Input.is_action_pressed("ui_up"):
        direccion.y -= 1
    
    if direccion != Vector2.ZERO:
        direccion = direccion.normalized()
    
    velocity = direccion * SPEED
    move_and_slide()

func recibir_dano(cantidad):
    vida -= cantidad
    print("Vida: ", vida)
    if vida <= 0:
        morir()

func morir():
    print("Game Over")
    puede_moverse = false
    hide()

Resumen del Capítulo

Has aprendido los fundamentos de GDScript:

Próximo Capítulo

En el siguiente capítulo exploraremos conceptos intermedios de GDScript:

→ Capítulo 5: GDScript Intermedio


Reto: Crea un mini-juego donde el jugador debe recolectar items antes de que se acabe el tiempo. Usa todo lo aprendido: variables, arrays, funciones, input y señales.