Introducción al Desarrollo de Videojuegos con GameMaker · Módulo 3: Fundamentos de programación con GML Code.
Ámbito de variables y buenas prácticas
Clase 14 de 60 · 50 min
También disponible en: EN
Introducción al concepto de ámbito (Scope)
Cuando empezamos a programar videojuegos, es común cometer el error de pensar que cualquier variable creada puede ser leída y modificada desde cualquier rincón del proyecto. Sin embargo, a medida que un juego crece en complejidad y agregamos más mecánicas (movimiento del personaje, patrulla de enemigos, contadores de monedas, salud e interfaces), este desorden genera problemas conocidos como 'código espagueti' y colisiones de nombres donde una acción en un objeto altera de forma imprevista los datos de otro.
El ámbito (denominado en inglés 'scope') define el ciclo de vida y la visibilidad de una variable: determina con exactitud quién puede verla, quién puede modificarla y cuánto tiempo existirá en la memoria de la computadora antes de ser descartada. En GameMaker y su lenguaje GML Code existen tres niveles esenciales de ámbito que todo desarrollador debe dominar desde el primer día: las variables locales (o temporales), las variables de instancia y las variables globales. Aprender a utilizarlas correctamente no solo evitará errores sutiles y difíciles de rastrear, sino que optimizará el rendimiento y mantendrá el proyecto ordenado y profesional.

Variables de Instancia: La memoria individual de cada objeto
Una variable de instancia pertenece de forma exclusiva a una copia específica de un objeto colocada en la sala (Room). Si tienes tres monedas (`obj_moneda`) o dos enemigos (`obj_enemigo_patrulla`) presentes en el nivel, cada uno de ellos posee su propio conjunto independiente de variables de instancia, aunque compartan exactamente el mismo código base.
Las variables de instancia nacen habitualmente en el evento Create del objeto y permanecen vivas en la memoria durante toda la existencia de esa instancia concreta. Cuando la instancia se destruye (por ejemplo, con la función `instance_destroy()`) o cuando la sala cambia sin que el objeto esté configurado como persistente, sus variables de instancia se liberan automáticamente de la memoria.
Para declarar una variable de instancia, simplemente se escribe su identificador seguido del operador de asignación (`=`) y su valor inicial dentro del evento Create, sin necesidad de ninguna palabra clave adicional. Estas variables pueden ser leídas o actualizadas en cualquier otro evento de esa misma instancia, como en el evento Step o Draw.
// Recurso: Objetos -> obj_jugador
// Evento: Create
// Variables de instancia para las mecánicas del jugador
velocidad_caminar = 4;
velocidad_salto = -9.5;
fuerza_gravedad = 0.4;
puntos_salud = 3;
estado_actual = "reposo";
ha_tocado_suelo = false;En el código anterior, cada una de estas propiedades pertenece a ese `obj_jugador`. Si creáramos un modo cooperativo con un segundo jugador (`obj_jugador_dos`), sus variables `puntos_salud` o `ha_tocado_suelo` funcionarían de manera totalmente separada. Aunque una instancia puede acceder a las variables de otra utilizando la sintaxis de punto (por ejemplo, `obj_jugador.puntos_salud`), abusar de este acceso directo externo rompe la independencia de los objetos y debe manejarse con cuidado.
Variables Locales (var): Eficiencia para cálculos temporales
Una variable local (o temporal) es aquella que se declara anteponiendo la palabra clave `var`. A diferencia de las variables de instancia, las variables locales tienen un ciclo de vida sumamente corto: solo existen mientras se ejecuta el evento, función o bloque de código donde fueron declaradas. En el momento exacto en que GameMaker termina de procesar ese bloque, la variable se destruye y la memoria ocupada se libera de inmediato.
¿Por qué son fundamentales? Imagina que en cada fotograma del juego (en el evento Step) necesitas leer si el jugador está presionando las teclas de izquierda o derecha, calcular la dirección matemática resultante y aplicar esa fuerza al movimiento. Si declaras esa dirección como una variable de instancia, el objeto conservará ese valor en memoria durante todo el juego, aun cuando solo fue necesario durante un microsegundo para realizar la suma de posiciones. Multiplicar este mal hábito por cientos de objetos y cálculos por segundo satura la memoria y vuelve el código difícil de seguir.
// Recurso: Objetos -> obj_jugador
// Evento: Step
// Declaración de variables locales temporales para procesar la entrada de teclado
var tecla_derecha = keyboard_check(vk_right) || keyboard_check(ord("D"));
var tecla_izquierda = keyboard_check(vk_left) || keyboard_check(ord("A"));
// Calculamos el eje horizontal temporal: 1 (derecha), -1 (izquierda), o 0 (quieto)
var eje_horizontal = tecla_derecha - tecla_izquierda;
// Aplicamos el cálculo a la posición usando la variable de instancia velocidad_caminar
x = x + (eje_horizontal * velocidad_caminar);
// Al finalizar el evento Step, las variables 'tecla_derecha', 'tecla_izquierda'
// y 'eje_horizontal' desaparecen por completo.Las variables locales también son la herramienta estándar e indispensable para los bucles, como los bucles `for` que estudiamos en la lección anterior. Declarar el índice de iteración con `var i = 0` asegura que no contamines las propiedades del objeto con variables transitorias que solo tienen sentido dentro del ciclo.
💡 Regla de oro: Si un valor solo se necesita para resolver una operación matemática intermedia, una comprobación rápida o un bucle dentro de un mismo evento, decláralo siempre con 'var'. Si el dato debe recordarse para el siguiente fotograma (como la vida, el inventario o la velocidad), debe ser una variable de instancia.
Variables Globales: El estado general del videojuego
Existen datos que no pertenecen a ningún personaje, enemigo o bloque en particular, sino al videojuego en su totalidad. Ejemplos claros de esto son: la puntuación acumulada del jugador a lo largo de varias pantallas, el nivel actual que se está jugando, la cantidad total de vidas restantes, o un interruptor que indique si el juego está en pausa.
Para estos casos existen las variables globales. Una variable global no está atada a ninguna sala ni a ninguna instancia: una vez que se inicializa, existe permanentemente en la memoria durante toda la sesión del juego y puede ser leída o modificada desde cualquier objeto, script o evento del proyecto.
En el estándar moderno de GML, las variables globales se declaran y utilizan anteponiendo el prefijo `global.` seguido del nombre de la variable. Existe una sintaxis antigua basada en la palabra clave `globalvar`, pero hoy en día está obsoleta y desaconsejada porque provoca ambigüedades en el código; debes utilizar siempre `global.nombre_de_variable`.
// Declaración y modificación de variables globales modernas
global.puntuacion_total = 0;
global.vidas_jugador = 3;
global.juego_pausado = false;
global.monedas_recolectadas = 0;¿Dónde y cuándo deben crearse las variables globales? Esta es una de las dudas más frecuentes de los principiantes. Si inicializas una variable global en el evento Create de tu jugador (`obj_jugador`), y el jugador se destruye o reinicias la sala, la variable volverá a inicializarse desde cero, perdiendo la puntuación acumulada. Peor aún, si la sala se carga antes de que el objeto exista, otros objetos intentarán leer una variable global que todavía no ha sido creada, provocando un cierre inesperado del juego (crash).
La mejor práctica de la industria consiste en crear un objeto administrador general, como `obj_controlador_juego`, y ubicarlo en la primera sala de arranque del juego (por ejemplo, una pantalla de carga o menú de inicio, o marcar este objeto como Persistent en sus propiedades). En el evento Create de este controlador se definen todas las variables globales iniciales una sola vez.
Comparativa de ámbitos en GML
Para visualizar con claridad las diferencias operativas y técnicas entre los tres ámbitos principales, analiza la siguiente tabla comparativa:
| Tipo de Ámbito | Sintaxis de Declaración | Ciclo de Vida | Visibilidad | Uso Recomendado |
|---|---|---|---|---|
| Local / Temporal | var nombre = valor; | Solo durante la ejecución del evento o función actual. | Exclusiva del bloque de código donde se declaró. | Cálculos intermedios, lecturas rápidas de teclado, contadores en bucles (for, while). |
| De Instancia | nombre = valor; | Desde que la instancia se crea hasta que se destruye o sale de la sala. | Propia de la instancia. Accesible por otras mediante 'id.variable' o 'with()'. | Velocidad, gravedad, vida propia, temporizadores internos, estados de animación. |
| Global | global.nombre = valor; | Durante toda la ejecución del juego (permanece entre salas). | Total. Cualquier objeto, evento o script puede leerla y editarla. | Puntuación acumulada, configuración general, vidas globales, banderas de pausa. |
Buenas prácticas de programación y código limpio en GameMaker
Escribir código no consiste únicamente en lograr que el juego funcione en la pantalla; consiste en hacerlo de manera comprensible, escalable y mantenible. Cuando retomes tu proyecto semanas después de haberlo pausado, un código desordenado será incomprensible incluso para ti mismo. A continuación, repasamos las directrices fundamentales para mantener un estándar profesional en GameMaker.
1. Convenciones de nomenclatura claras y coherentes
En GameMaker es altamente recomendable utilizar prefijos de tres letras para los recursos del proyecto (Assets), lo que evita confundir un recurso gráfico con un objeto o un sonido:
• Sprites: `spr_personaje_caminar`, `spr_bloque_piso`, `spr_moneda`. • Objetos: `obj_jugador`, `obj_enemigo_patrulla`, `obj_bloque_solido`. • Sonidos: `snd_salto`, `snd_moneda`, `mus_nivel_1`. • Salas (Rooms): `rm_menu_inicio`, `rm_nivel_1`, `rm_pantalla_victoria`. • Scripts: `scr_calcular_danio`, `scr_guardar_partida`.
Para las variables, se recomienda el formato 'snake_case' (palabras en minúsculas separadas por guiones bajos) en español o inglés neutro, utilizando nombres descriptivos que expliquen su función sin necesidad de adivinanzas. Evita nombres genéricos de una sola letra como `a = 10;` o `temp = true;`. Utiliza en su lugar `velocidad_maxima = 10;` o `esta_invencible = true;`.
2. Evitar números mágicos mediante variables y macros
Un 'número mágico' es un valor numérico escrito directamente en medio de la lógica del juego sin explicación aparente. Por ejemplo, escribir en el evento Step `if (y > 720) { instance_destroy(); }`. Si más adelante decides cambiar la resolución de la pantalla o la altura del nivel, tendrás que buscar manualmente ese '720' repartido en decenas de objetos distintos.
Es preferible declarar una variable con nombre descriptivo o definir una macro global con la directiva `#macro`. Las macros sustituyen una palabra clave por un valor constante antes de compilar y no pueden modificarse en tiempo de ejecución:
// Declaración de macros en un script global (por ejemplo: scr_configuracion)
#macro LIMITE_INFERIOR_PANTALLA 720
#macro GRAVEDAD_BASE 0.4
#macro MAX_VIDAS 3
// Uso limpio en el evento Step de obj_jugador:
if (y > LIMITE_INFERIOR_PANTALLA) {
scr_reiniciar_posicion_jugador();
}3. Comentarios significativos y estructura visual
Los comentarios en el código deben explicar el 'por qué' de una decisión, no únicamente repetir lo que la sintaxis ya dice de forma obvia. GML admite comentarios de una sola línea mediante `//` y bloques multilínea mediante `/* ... */`.
// Comentario redundante (no aporta valor real):
// Sumamos 5 a la x
x = x + 5;
// Comentario limpio y descriptivo (explica la intención del diseño):
// Añadimos velocidad constante para simular el desplazamiento forzado de la cinta transportadora
x = x + velocidad_cinta;Ejemplo práctico: Refactorización limpia del Jugador y Controlador
Apliquemos todo lo aprendido en la arquitectura de nuestro juego de plataformas retro. Vamos a crear dos elementos coordinados de forma limpia: el objeto de control general `obj_controlador_juego` y la refactorización del movimiento básico de nuestro personaje `obj_jugador` utilizando variables de instancia, locales y globales.
Paso 1: Creamos el objeto `obj_controlador_juego`. En el Inspector de GameMaker, activamos la casilla de verificación 'Persistent'. En su evento Create inicializamos el estado global del proyecto:
// Recurso: Objetos -> obj_controlador_juego
// Evento: Create
// Inicialización de variables globales del juego
global.puntuacion = 0;
global.vidas = 3;
global.nivel_completado = false;
// Mensaje de confirmación en la consola de depuración de GameMaker
show_debug_message("Sistema de juego inicializado correctamente.");Paso 2: Ahora abrimos nuestro `obj_jugador`. En su evento Create, declaramos únicamente sus variables de instancia, evitando mezclar variables que solo se usan en un cálculo momentáneo:
// Recurso: Objetos -> obj_jugador
// Evento: Create
// Variables de instancia: velocidad y estado físico
velocidad_movimiento = 3.5;
velocidad_vertical = 0;
velocidad_horizontal = 0;
fuerza_salto = -8;
gravedad_personaje = 0.35;
estrellado_contra_suelo = true;Paso 3: En el evento Step de `obj_jugador`, utilizamos variables locales (`var`) para capturar las entradas de teclado y calcular las velocidades del fotograma. Observa cómo ninguna de estas variables intermedias satura la memoria permanente de la instancia:
// Recurso: Objetos -> obj_jugador
// Evento: Step
// 1. Lectura de controles con variables locales
var presiona_izquierda = keyboard_check(vk_left) || keyboard_check(ord("A"));
var presiona_derecha = keyboard_check(vk_right) || keyboard_check(ord("D"));
var presiona_salto = keyboard_check_pressed(vk_space) || keyboard_check_pressed(ord("W"));
// 2. Cálculo vectorial del eje horizontal con variable local
var direccion_horizontal = presiona_derecha - presiona_izquierda;
// 3. Asignación a variables de instancia para el movimiento
velocidad_horizontal = direccion_horizontal * velocidad_movimiento;
// Aplicación de la gravedad sobre la velocidad vertical de instancia
velocidad_vertical += gravedad_personaje;
// 4. Salto básico provisional si se detecta pulsación
if (presiona_salto && estrellado_contra_suelo) {
velocidad_vertical = fuerza_salto;
estrellado_contra_suelo = false;
}
// 5. Aplicación física a las coordenadas intrínsecas de la instancia
x += velocidad_horizontal;
y += velocidad_vertical;
// Límite provisional de suelo para pruebas (la colisión real se verá en el Módulo 5)
if (y >= 400) {
y = 400;
velocidad_vertical = 0;
estrellado_contra_suelo = true;
}Al separar con total claridad las variables que solo importan en este instante (`presiona_izquierda`, `direccion_horizontal`) de las que definen el estado sostenido de la entidad (`velocidad_vertical`, `estrellado_contra_suelo`) y de las globales (`global.vidas`), logramos una base técnica sólida, legible y preparada para incorporar sistemas complejos sin generar conflictos de memoria ni de nombres.
Ejercicios prácticos
Pon a prueba tu comprensión de los tres ámbitos de variables y las buenas prácticas de programación en GML resolviendo los siguientes tres ejercicios guiados:
Ejercicio 1: Refactorización de código con fugas de ámbito Analiza el siguiente fragmento problemático escrito en el evento Step de un proyectil disparado por el jugador (`obj_proyectil`). El desarrollador novato olvidó usar `var` en variables que solo sirven para un cálculo trigonométrico inmediato: ```gml // Código problemático actual: angulo_rad = degtorad(image_angle); componente_x = cos(angulo_rad) * 6; componente_y = -sin(angulo_rad) * 6; x += componente_x; y += componente_y; ``` Reescribe el fragmento corrigiendo el ámbito para que esas tres variables intermedias (`angulo_rad`, `componente_x`, `componente_y`) se conviertan estrictamente en variables locales temporales. Criterio de comprobación: Verifica que al anteponer la palabra clave `var` a esas variables, el proyectil siga moviéndose con total fluidez en pantalla pero ya no figuren como propiedades permanentes de la instancia en la pestaña 'Variables' del depurador.
Ejercicio 2: Creación del objeto recolectable de monedas con variable global Crea un nuevo objeto en tu proyecto llamado `obj_moneda`. Asógnale un sprite simple cuadrado o circular de color dorado (puedes dibujarlo en el editor interno de GameMaker con un tamaño de 16x16 píxeles). En el evento Collision de `obj_moneda` con `obj_jugador`, programa las siguientes acciones: 1. Suma 25 unidades a la variable global `global.puntuacion`. 2. Muestra un mensaje en consola usando `show_debug_message()` que indique el nuevo total de puntos. 3. Destruye la instancia de la moneda recolectada con `instance_destroy()`. Criterio de comprobación: Coloca tres monedas en tu sala `rm_nivel_1`. Inicia el juego, muévete hacia ellas y observa la ventana de salida 'Output' de GameMaker; el contador debe aumentar secuencialmente a 25, 50 y 75 a medida que las monedas desaparecen del escenario.
Ejercicio 3: Detección y corrección de colisión de nombres (Shadowing) Imagina que un programador escribió en el evento Create del jugador: `velocidad = 5;`. Luego, en el evento Step, escribió por error: `var velocidad = 10; x += velocidad;`. Explica por escrito qué sucederá con la variable de instancia original en ese fotograma y qué efecto visual producirá en el jugador. Modifica el código para que las variables tengan nombres descriptivos independientes y no se solapen. Criterio de comprobación: Comprueba que el jugador se mueva a la velocidad correspondiente de su configuración de instancia sin que una variable temporal opaque (haga 'shadowing') a la variable original del objeto.
Resumen
- Las variables de instancia pertenecen a una copia específica de un objeto, se declaran habitualmente en el evento Create y permanecen vivas hasta que la instancia se destruye.
- Las variables locales se definen con la palabra clave 'var', existen únicamente mientras se procesa el evento o script en el que nacieron y se liberan de inmediato, optimizando la memoria y evitando conflictos.
- Las variables globales se gestionan mediante el prefijo 'global.', son accesibles desde cualquier rincón del juego y persisten a través de los cambios de sala; son ideales para puntuaciones, vidas y ajustes generales.
- Las variables globales deben inicializarse preferentemente en un objeto controlador persistente (como obj_controlador_juego) en la primera sala del juego.
- Mantener buenas prácticas de nomenclatura (prefijos como obj_, spr_, snd_ y formato snake_case), evitar números mágicos con macros (#macro) y añadir comentarios explicativos garantiza un proyecto limpio, profesional y escalable.
Practica lo aprendido
Ejercicio 1 · 1 punto
Relaciona cada tipo de ámbito de variable en GameMaker con su respectiva característica y ciclo de vida:
- Variable local
- Variable de instancia
- Variable global
Ejercicio 2 · 1 punto
¿Por qué no se recomienda inicializar variables globales (como la puntuación o las vidas) dentro del evento Create del objeto del jugador (`obj_jugador`)?
Ejercicio 3 · 1 punto
Completa las siguientes afirmaciones sobre el ámbito de variables en GameMaker: 1. Para realizar un cálculo intermedio o temporal y liberar la memoria al finalizar el evento o bloque, se antepone la palabra clave al nombre de la variable. 2. En el estándar moderno de GML, para declarar y acceder a una variable que pertenezca a todo el juego en general, se debe utilizar el prefijo . 3. Una variable de se declara habitualmente en el evento Create sin palabras clave adicionales y almacena datos individuales e independientes para cada copia de un objeto.
Ingresa para guardar tu progreso — Ingresar