Introducción al Desarrollo de Videojuegos con GameMaker · Módulo 7: Enemigos, inteligencia artificial básica y mecánicas de juego.
Creación de coleccionables (monedas y gemas)
Clase 34 de 60 · 50 min
También disponible en: EN
Introducción al diseño de coleccionables
Los coleccionables son uno de los componentes más versátiles y fundamentales en el diseño de videojuegos de plataformas 2D. No constituyen únicamente una recompensa matemática para el jugador en forma de números ascendentes, sino que cumplen funciones críticas de diseño de niveles (level design): actúan como guías visuales (conocidas como 'migas de pan' o breadcrumbs) que enseñan al jugador hacia dónde debe avanzar, señalan el camino óptimo para realizar un salto difícil y recompensan la curiosidad al explorar rutas secretas o áreas de alto riesgo.
En esta clase ampliaremos el proyecto del juego de plataformas retro que venimos construyendo a lo largo del curso. Hasta este momento disponemos de un personaje con movimiento físico, detección de suelo, saltos ajustados, un enemigo patrullero y un sistema de salud con vidas. Ahora añadiremos dos tipos esenciales de coleccionables:
1. Monedas (Coins): Elementos comunes, distribuidos en secuencias o arcos a lo largo del nivel. Tienen un valor modesto (10 puntos) y su propósito es trazar trayectorias de movimiento e incentivar la fluidez mecánica del jugador. 2. Gemas (Gems): Elementos raros o especiales, colocados en zonas elevadas, escondites o protegidos por enemigos. Tienen un valor superior (50 o 100 puntos) y representan un logro o reto opcional para el jugador completista.
Para evitar repetir código y construir un proyecto desordenado, implementaremos una arquitectura orientada a objetos mediante la jerarquía de herencia (parenting) de GameMaker. Esto nos permitirá crear una plantilla base con todo el comportamiento común de recolección y luego ramificar fácilmente decenas de objetos distintos sin duplicar una sola línea de lógica.
Creación y configuración de recursos gráficos (Sprites)
Antes de programar la recolección, necesitamos configurar los recursos visuales en el Asset Browser. Crearemos dos sprites con estética de píxel retro: una moneda giratoria o brillante y una gema flotante.
Para crear el sprite de la moneda, haz clic derecho sobre la carpeta 'Sprites' en el Asset Browser y selecciona 'Create -> Sprite'. Nómbralo 'spr_coin'. Si diseñas un sprite en el editor interno de GameMaker, establece unas dimensiones de 16x16 píxeles (o 24x24 según la escala visual elegida en tu proyecto). Puedes dibujar una pequeña elipse dorada de 4 a 6 fotogramas simulando el giro horizontal sobre su propio eje. Si prefieres un elemento estático, un único fotograma con un brillo sutil también funcionará a la perfección.
Repite el procedimiento para la gema: crea un sprite denominado 'spr_gem', de 16x16 píxeles, con forma de rombo o cristal en tonos esmeralda, rubí o zafiro.
💡 Punto de origen crítico: A diferencia de los bloques de suelo (cuyo origen suele situarse en Top-Left para alinearse a la cuadrícula) o del jugador (cuyo origen suele situarse en Bottom-Centre para apoyarse correctamente sobre el suelo), para los coleccionables se recomienda configurar el origen en 'Middle Centre' (Centro Medio). Esto garantiza que cualquier efecto de rotación, escalado al recogerse o animación matemática de oscilación ocurra alrededor del centro geométrico del objeto sin desplazarse de su eje original.
Respecto a la máscara de colisión (Collision Mask), asegúrate de que sea cómoda para el jugador. En los juegos de plataformas resulta frustrante pasar extremadamente cerca de una moneda sin llegar a recogerla. En la sección 'Collision Mask' de ambos sprites, selecciona la forma 'Rectangle' o 'Ellipse' y expande ligeramente los bordes para que la recolección sea indulgente y satisfactoria.
Arquitectura de herencia: Creación del objeto padre
Imagina que en el futuro tu videojuego incorpora llaves, pociones de salud, vidas extra, diamantes de colores y pergaminos. Si programaras la colisión con el jugador de forma individual dentro de cada objeto, cualquier ajuste a la lógica requeriría modificar decenas de eventos idénticos. Aquí es donde brilla el sistema de herencia de GameMaker.
Crearemos un objeto base llamado 'obj_parent_collectible'. Este objeto actuará como plantilla abstracta: albergará toda la lógica común de detección, animación flotante y recolección. Posteriormente crearemos los objetos hijos 'obj_coin' y 'obj_gem', los cuales heredarán todas las propiedades del padre y solo redefinirán variables específicas como el sprite y el valor numérico de la puntuación.

Para crear este sistema en GameMaker, sigue estos pasos estructurados:
1. Haz clic derecho en el Asset Browser sobre la carpeta 'Objects' y selecciona 'Create -> Object'. Nómbralo 'obj_parent_collectible'. No le asignes ningún sprite, ya que servirá exclusivamente de contenedor lógico.
2. Crea un segundo objeto llamado 'obj_coin'. Asígnale el sprite 'spr_coin'. En el panel de propiedades del objeto, busca la sección 'Parent', haz clic sobre ella y selecciona 'obj_parent_collectible' como su objeto padre.
3. Crea un tercer objeto llamado 'obj_gem'. Asígnale el sprite 'spr_gem'. De igual manera, asígnale como 'Parent' a 'obj_parent_collectible'.
Programación del objeto base: Variables y animación sinusoidal
Un detalle visual característico de los videojuegos clásicos es que los coleccionables parecen levitar suavemente en el aire. En lugar de dibujar una animación cuadro por cuadro para lograr esto, podemos emplear una sencilla función trigonométrica en GML Code. El uso del seno matemático genera una oscilación armónica periódica entre -1 y 1, lo que produce un movimiento suave y continuo sin necesidad de físicas complejas.
Abre el objeto 'obj_parent_collectible' y añade un evento 'Create'. En este evento definiremos las variables que gobernarán el valor del objeto y los parámetros de su animación:
/// @description Inicialización del objeto recolectable base
// Valor en puntos que otorgará este coleccionable por defecto
valor_puntos = 10;
// Variables de animación de levitación (oscilación armónica)
y_base = y; // Almacena la posición Y original en la habitación
tiempo_oscilacion = 0; // Contador interno para el ángulo matemático
velocidad_flotacion = 0.05; // Velocidad del ciclo de oscilación
amplitud_flotacion = 4; // Cantidad máxima de píxeles que sube y bajaA continuación, añade el evento 'Step' a 'obj_parent_collectible'. En cada fotograma del juego, incrementaremos el contador de tiempo y recalcularemos la posición vertical del objeto utilizando la función trigonométrica 'sin()':
/// @description Animación continua y lógica de recolección
// 1. Animación suave de flotación
tiempo_oscilacion += velocidad_flotacion;
y = y_base + (sin(tiempo_oscilacion) * amplitud_flotacion);
// 2. Comprobación de colisión con el jugador
if (place_meeting(x, y, obj_player))
{
// Añadir el puntaje a la variable global correspondiente
global.puntos += valor_puntos;
// Mensaje de comprobación en la consola de depuración
show_debug_message("Item recogido! Valor: +" + string(valor_puntos) + " | Total: " + string(global.puntos));
// Destruir esta instancia tras ser recogida
instance_destroy();
}Analicemos con detalle lo que ocurre en este código. La función 'sin(tiempo_oscilacion)' devuelve un valor decimal entre -1.0 y 1.0. Al multiplicarlo por 'amplitud_flotacion' (4 en nuestro ejemplo), el resultado varía fluidamente entre -4 y +4 píxeles. Sumar este valor a 'y_base' produce una oscilación natural, independiente de si el sprite tiene o no fotogramas de animación interna.
Posteriormente, la instrucción 'place_meeting(x, y, obj_player)' verifica si la máscara de colisión de este coleccionable se superpone con la instancia activa de 'obj_player'. Si el jugador lo toca, se incrementa la puntuación global y se destruye la instancia del coleccionable con 'instance_destroy()', liberándola inmediatamente de la memoria del motor.
Personalización de los objetos hijos mediante event_inherited()
Ahora que la plantilla 'obj_parent_collectible' contiene toda la funcionalidad mecánica, los objetos hijos 'obj_coin' y 'obj_gem' ya funcionan automáticamente por herencia. Si colocaras 'obj_coin' en la sala sin escribir una sola línea de código adicional, el juego funcionaría y otorgaría 10 puntos.
Sin embargo, queremos que 'obj_gem' tenga un valor mucho más alto y una oscilación con diferente cadencia para distinguirse visualmente. ¿Cómo modificamos las variables de un hijo sin perder la lógica que definimos en el padre? La respuesta es la función fundamental 'event_inherited()'.
Abre el objeto 'obj_gem' y añade un evento 'Create'. Si añades un evento en un hijo, GameMaker por defecto sobrescribe por completo el evento del padre a menos que invoques explícitamente a su progenitor. Escribe el siguiente código:
/// @description Personalización de la Gema
// Ejecutar primero todo el código Create del padre (obj_parent_collectible)
event_inherited();
// Sobrescribir variables específicas para la gema
valor_puntos = 50;
velocidad_flotacion = 0.08; // Flota ligeramente más rápido
amplitud_flotacion = 6; // Rango de oscilación vertical más amplioAl llamar a 'event_inherited()', GameMaker inicializa primero 'y_base', 'tiempo_oscilacion' y el resto de variables esenciales. Inmediatamente después, redefinimos 'valor_puntos' asignándole 50 puntos y aumentamos la amplitud a 6 píxeles. De esta forma logramos un comportamiento único para la gema en apenas cuatro líneas de código, conservando intacta toda la lógica de colisión y destrucción.
💡 Regla de oro de la herencia: Siempre que definas un evento en un objeto hijo (como Create, Step o Destroy) y quieras conservar lo que hace el objeto padre, debes colocar 'event_inherited();' al inicio del bloque de código. Si lo omites, el código del padre no se ejecutará para ese evento específico.
Gestión del estado global de la puntuación
En el evento Step del objeto padre hicimos referencia a una variable llamada 'global.puntos'. En GameMaker, las variables que inician con el prefijo 'global.' no pertenecen a ninguna instancia concreta del juego; residen en un espacio de memoria accesible por cualquier objeto, script o evento en cualquier momento.
Para evitar errores de ejecución del tipo 'variable not set before reading', es indispensable inicializar 'global.puntos' antes de que cualquier coleccionable o jugador intente interactuar con ella. El lugar idóneo para esto es un objeto controlador general (Game Manager) que persista a lo largo de toda la experiencia.
Si ya creaste un objeto gestor como 'obj_game' o 'obj_control' en clases anteriores (donde gestionaste las vidas y la salud del jugador), abre su evento 'Create' y añade la declaración de puntuación. Si aún no cuentas con este objeto, créalo ahora:
/// @description Inicialización del estado global de la partida en obj_game
// Configuración de persistencia (opcional, para mantener el controlador entre salas)
persistent = true;
// Variables globales de progreso del jugador
global.puntos = 0;
global.monedas_recogidas = 0;
global.gemas_recogidas = 0;Con estas variables inicializadas, podemos refinar la lógica de recolección en 'obj_parent_collectible' o en los hijos para registrar de forma separada el total de monedas y gemas obtenidas, lo que resultará de gran utilidad cuando diseñemos la interfaz de usuario (HUD) en el próximo módulo.
Comparativa de patrones arquitectónicos para coleccionables
A la hora de programar colisiones en videojuegos, suele surgir la duda: ¿debe el coleccionable detectar al jugador, o debe ser el jugador quien busque los coleccionables a su alrededor? Ambas soluciones son técnicamente viables en GameMaker, pero poseen implicaciones arquitectónicas distintas:
| Criterio | Coleccionable detecta al Jugador (Elegido) | Jugador detecta Coleccionables |
|---|---|---|
| Ubicación del código | Dentro de 'obj_parent_collectible' (Step o Collision). | Dentro del evento Step de 'obj_player'. |
| Modularidad | Alta: agregar nuevos coleccionables no altera el código del personaje. | Media: el script del jugador debe conocer cada tipo o categoría de ítem. |
| Rendimiento | Óptimo en salas pequeñas y medianas. | Favorable si hay miles de ítems simultáneos estáticos en pantalla. |
| Mantenimiento | Cada objeto se autodestruye y gestiona sus efectos de manera autónoma. | Requiere manejar referencias de instancias devueltas por 'instance_place'. |
Elegir que el coleccionable gestione su propia detección respeta el principio de responsabilidad única: 'obj_player' solo debe preocuparse por sus controles, movimientos físicos y saltos, mientras que los objetos interactivos del entorno gestionan el impacto de ser consumidos.
Distribución y diseño de niveles en la Room
Una vez programados los objetos, llega el momento de colocarlos en el nivel del juego ('rm_nivel_1'). La ubicación de los coleccionables no debe ser aleatoria; debe responder a patrones claros de diseño:
1. Arcos de salto: Coloca una hilera de tres o cuatro monedas describiendo una parábola sobre un abismo. Esto le muestra al jugador el punto exacto donde debe presionar la tecla de salto y el arco físico que describirá su personaje en el aire.
2. Líneas de aceleración: Ubica monedas a ras de suelo en tramos largos para estimular al jugador a no detener su carrera.
3. Gemas de recompensa por riesgo: Sitúa gemas en plataformas pequeñas y móviles, o justo detrás de la patrulla del enemigo que programamos en la clase 7.1. Si el jugador se arriesga a calcular el patrón de patrullaje sin ser herido, es recompensado con un incremento sustancial en su puntaje.
💡 Capa correcta en el editor de salas: Asegúrate de arrastrar las instancias de 'obj_coin' y 'obj_gem' a la capa 'Instances' del editor de salas, la misma capa (o una superior) donde reside 'obj_player'. Si colocas los objetos en capas inferiores de fondos o mosaicos, podrían quedar ocultos detrás del escenario.
Ejercicios prácticos
Desarrolla las siguientes actividades para afianzar tus competencias sobre herencia, funciones matemáticas y detección de colisiones en GameMaker:
Ejercicio 1: Diseño de parábolas y sincronización visual Abre la sala de tu proyecto ('rm_nivel_1') y diseña un arco de recolección compuesto por 5 monedas sobre una plataforma elevada. Ejecuta el juego y observa la oscilación vertical de las monedas. ¿Qué ocurre? Notarás que todas las monedas suben y bajan exactamente al mismo tiempo de forma monótona. Modifica el evento Create de 'obj_parent_collectible' para que 'tiempo_oscilacion' comience con un desfase aleatorio usando la función 'random(pi * 2)'. Comprueba que ahora cada moneda colocada en el nivel oscila de manera orgánica e independiente.
Ejercicio 2: Mecánica de magnetismo o atracción hacia el jugador En muchos juegos modernos, los coleccionables se desplazan hacia el jugador si este pasa a poca distancia. En el evento Step de 'obj_parent_collectible', calcula la distancia hacia 'obj_player' usando la función 'point_distance(x, y, obj_player.x, obj_player.y)' (verificando antes que exista una instancia con 'instance_exists(obj_player)'). Si la distancia es menor a 48 píxeles, haz que el coleccionable se mueva hacia las coordenadas del jugador usando 'move_towards_point()' o alterando 'x' e 'y' con interpolación lineal ('lerp'). Criterio de comprobación: la moneda debe ignorar su animación de flotación y volar directamente hacia el jugador una vez que este se aproxime.
Ejercicio 3: Creación de un tercer tipo de coleccionable (Cáliz de Oro) Utilizando la arquitectura de herencia ya establecida, crea un nuevo sprite 'spr_chalice' y un nuevo objeto 'obj_chalice' cuyo padre sea 'obj_parent_collectible'. En su evento Create, mediante 'event_inherited()', asígnale un valor de 200 puntos, una oscilación lenta y majestuosa ('velocidad_flotacion = 0.02') y un rango amplio ('amplitud_flotacion = 8'). Colócalo en una zona secreta del nivel y verifica en la ventana de salida (Output) de GameMaker que se imprima la suma de 200 puntos al tocarlo.
Resumen
- Los coleccionables cumplen el rol de recompensar la exploración, enseñar trayectorias de salto y dinamizar el flujo del juego de plataformas.
- El origen de los sprites de coleccionables debe establecerse en Middle Centre para facilitar efectos visuales, rotaciones y desplazamientos sinusoidales concéntricos.
- La arquitectura basada en un objeto padre (obj_parent_collectible) permite centralizar la lógica de colisión, destrucción y movimiento sin duplicar código en monedas o gemas.
- La función trigonométrica 'sin()' genera una oscilación armónica suave que simula levitación continua sin consumir recursos computacionales significativos.
- La llamada a 'event_inherited()' es obligatoria en eventos de objetos hijos cuando se desea conservar y extender el comportamiento programado en el padre.
- El estado de los puntos se resguarda en variables de ámbito global ('global.puntos') declaradas desde un objeto controlador persistente al arrancar la sesión de juego.
Practica lo aprendido
Ejercicio 1 · 1 punto
¿Por qué se recomienda configurar el origen del sprite de los coleccionables en "Middle Centre" (Centro Medio) en lugar de "Top-Left" o "Bottom-Centre"?
Ejercicio 2 · 1 punto
Completa el siguiente fragmento del evento **Step** de `obj_parent_collectible` para calcular la oscilación vertical y eliminar la instancia cuando sea recogida por el jugador: ```gml tiempo_oscilacion += velocidad_flotacion; y = y_base + ((tiempo_oscilacion) * amplitud_flotacion); if (place_meeting(x, y, obj_player)) { global.puntos += valor_puntos; (); } ```
Ejercicio 3 · 2 puntos
En el evento **Create** del objeto hijo `obj_gem`, llama a la función que ejecuta la lógica de inicialización del padre (`obj_parent_collectible`) y luego sobrescribe la variable `valor_puntos` asignándole un valor de 50.
Tu código no se ejecuta: se comprueban requisitos concretos sobre el texto.
Ingresa para guardar tu progreso — Ingresar