Introducción al Desarrollo de Videojuegos con GameMaker · Module 7: Enemies, Basic AI, and Gameplay Mechanics
Creating Collectibles (Coins and Gems)
Lesson 34 of 60 · 50 min
Also available in: ES
Introduction to Collectible Design
Collectibles are one of the most versatile and fundamental components in 2D platformer design. They are not merely mathematical rewards for the player in the form of increasing numbers; they serve critical level design functions: they act as visual guides (breadcrumbs) that show the player where to advance, indicate optimal paths for tricky jumps, and reward curiosity when exploring secret routes or high-risk areas.
In this lesson, we will expand the retro platformer project we have been building throughout the course. So far, we have a character with physics-based movement, floor collision checks, tight jumping, a patrolling enemy, and a health/lives system. Now we will add two essential types of collectibles:
1. Coins: Common items distributed in sequences or arcs across the level. They have a modest value (10 points), and their purpose is to trace movement paths and encourage mechanical flow. 2. Gems: Rare or special items placed on high platforms, hidden alcoves, or guarded by enemies. They have a higher value (50 or 100 points) and represent an optional achievement or challenge for completionists.
To avoid repetitive code and keep the project clean, we will implement an object-oriented architecture using GameMaker's parent-child inheritance system. This allows us to build a base template with all shared collection behavior and easily branch out into dozens of distinct items without duplicating a single line of logic.
Creating and Configuring Sprite Assets
Before coding collection logic, we need to set up our visual assets in the Asset Browser. We will create two sprites with a retro pixel-art aesthetic: a spinning or gleaming coin and a floating gem.
To create the coin sprite, right-click the 'Sprites' folder in the Asset Browser and select 'Create -> Sprite'. Name it 'spr_coin'. If you design the sprite in GameMaker's built-in image editor, set the canvas size to 16x16 pixels (or 24x24 depending on your project's visual scale). You can draw a small gold ellipse with 4 to 6 frames simulating a horizontal spin on its axis. If you prefer a static item, a single frame with a subtle glint will work just as well.
Repeat this process for the gem: create a sprite named 'spr_gem', 16x16 pixels, diamond- or crystal-shaped in emerald, ruby, or sapphire tones.
💡 Critical origin point: Unlike ground blocks (whose origin is usually Top-Left to align with the grid) or the player (whose origin is typically Bottom-Centre to stand accurately on surfaces), collectibles should have their origin set to 'Middle Centre'. This ensures that any rotation, pickup scaling effects, or mathematical hovering oscillations occur around the geometric center of the object without shifting off its intended anchor.
Regarding the Collision Mask, ensure it feels forgiving to the player. In platformers, few things are more frustrating than brushing past a coin without collecting it. In the 'Collision Mask' properties of both sprites, set the shape to 'Rectangle' or 'Ellipse' and slightly expand the bounding box edges so collection feels generous and responsive.
Inheritance Architecture: Creating the Parent Object
Imagine that later on your game adds keys, health potions, extra lives, colored diamonds, and scrolls. If you programmed collision with the player individually inside every single item, modifying that logic would require updating dozens of identical events. This is where GameMaker's inheritance system shines.
We will create a base object called 'obj_parent_collectible'. This object serves as an abstract blueprint containing all shared detection, floating animation, and collection logic. Later, we will create child objects 'obj_coin' and 'obj_gem', which inherit all properties from the parent and only override specific variables such as sprite assignment and point value.

To set up this system in GameMaker, follow these structured steps:
1. Right-click the 'Objects' folder in the Asset Browser and select 'Create -> Object'. Name it 'obj_parent_collectible'. Do not assign a sprite, as it will serve strictly as a logical container.
2. Create a second object named 'obj_coin'. Assign it the 'spr_coin' sprite. In the object properties panel, click the 'Parent' section and select 'obj_parent_collectible' as its parent.
3. Create a third object named 'obj_gem'. Assign it the 'spr_gem' sprite. Likewise, set its 'Parent' to 'obj_parent_collectible'.
Programming the Base Object: Variables and Sine Animation
A classic hallmark of retro games is collectibles gently bobbing in mid-air. Instead of drawing frame-by-frame animations for this, we can use a simple trigonometric function in GML Code. Using the sine function generates smooth, periodic harmonic oscillation between -1 and 1, producing natural floating motion without any complex physics overhead.
Open 'obj_parent_collectible' and add a 'Create' event. Here we define the variables that govern the item's point value and its floating animation parameters:
/// @description Initialize base collectible object
// Point value awarded by this collectible by default
valor_puntos = 10;
// Levitating animation variables (harmonic oscillation)
y_base = y; // Store original Y position in the room
tiempo_oscilacion = 0; // Internal counter for mathematical angle
velocidad_flotacion = 0.05; // Oscillation cycle speed
amplitud_flotacion = 4; // Maximum pixels to move up and downNext, add a 'Step' event to 'obj_parent_collectible'. Every game frame, we will advance the time counter and recalculate the object's vertical position using the 'sin()' trigonometric function:
/// @description Continuous animation and collection logic
// 1. Smooth floating animation
tiempo_oscilacion += velocidad_flotacion;
y = y_base + (sin(tiempo_oscilacion) * amplitud_flotacion);
// 2. Player collision check
if (place_meeting(x, y, obj_player))
{
// Add score to the corresponding global variable
global.puntos += valor_puntos;
// Debug message in output console
show_debug_message("Item collected! Value: +" + string(valor_puntos) + " | Total: " + string(global.puntos));
// Destroy this instance once collected
instance_destroy();
}Let's break down what happens in this script. The 'sin(tiempo_oscilacion)' function returns a decimal value between -1.0 and 1.0. Multiplying it by 'amplitud_flotacion' (4 in our example) causes the result to vary smoothly between -4 and +4 pixels. Adding this offset to 'y_base' creates an organic bobbing effect, whether or not the sprite contains sub-image frames.
Next, 'place_meeting(x, y, obj_player)' checks if this collectible's collision mask overlaps with an active instance of 'obj_player'. If the player touches it, the global score increases and the collectible instance is removed with 'instance_destroy()', freeing it from memory immediately.
Customizing Child Objects with event_inherited()
Now that the 'obj_parent_collectible' template contains all mechanical logic, child objects 'obj_coin' and 'obj_gem' work automatically through inheritance. If you placed 'obj_coin' in a room without writing any extra code, it would already function and award 10 points.
However, we want 'obj_gem' to grant a much higher score and bob at a different rate to distinguish itself visually. How do we override variables in a child without losing the logic defined in the parent? The solution is the essential 'event_inherited()' function.
Open 'obj_gem' and add a 'Create' event. By default, adding an event in a child object completely overrides the parent's event unless you explicitly call its ancestor. Add the following code:
/// @description Customize Gem child object
// Execute all parent Create code first (obj_parent_collectible)
event_inherited();
// Override specific variables for the gem
valor_puntos = 50;
velocidad_flotacion = 0.08; // Floats slightly faster
amplitud_flotacion = 6; // Wider vertical oscillation rangeBy calling 'event_inherited()', GameMaker first initializes 'y_base', 'tiempo_oscilacion', and the rest of the core variables. Immediately afterward, we reassign 'valor_puntos' to 50 points and increase the amplitude to 6 pixels. This gives the gem unique behavior in just four lines of code while keeping all collision and destruction logic intact.
💡 Golden rule of inheritance: Whenever you define an event in a child object (such as Create, Step, or Destroy) and want to keep what the parent does, you must place 'event_inherited();' at the start of that code block. If you omit it, the parent's code for that specific event will not execute.
Managing Global Score State
In the parent object's Step event, we referenced a variable called 'global.puntos'. In GameMaker, variables prefixed with 'global.' do not belong to any single instance; they exist in a shared memory space accessible by any object, script, or event at any time.
To prevent 'variable not set before reading' runtime errors, you must initialize 'global.puntos' before any collectible or player instance attempts to access it. The ideal place for this is a persistent Game Manager controller object that lives across the entire session.
If you already created a controller object such as 'obj_game' or 'obj_control' in earlier lessons (where you managed player health and lives), open its 'Create' event and declare the score variables. If you haven't created it yet, set it up now:
/// @description Initialize global game state in obj_game
// Persistence configuration (keeps controller active between rooms)
persistent = true;
// Global player progress variables
global.puntos = 0;
global.monedas_recogidas = 0;
global.gemas_recogidas = 0;With these variables initialized, we can refine the collection logic in 'obj_parent_collectible' or in child objects to track collected coins and gems separately. This will prove very useful when designing the heads-up display (HUD) in the upcoming module.
Comparing Architectural Patterns for Collectibles
When programming collisions in video games, a common question arises: should the collectible detect the player, or should the player look for nearby collectibles? Both approaches work in GameMaker, but they carry distinct architectural trade-offs:
| Criterion | Collectible Detects Player (Chosen) | Player Detects Collectibles |
|---|---|---|
| Code location | Inside 'obj_parent_collectible' (Step or Collision event). | Inside 'obj_player' Step event. |
| Modularity | High: adding new collectibles does not modify the player's script. | Medium: player script must know about every item type or category. |
| Performance | Optimal for small- to medium-sized rooms. | Favorable when thousands of static items exist simultaneously on screen. |
| Maintenance | Each object manages its own effects and destroys itself autonomously. | Requires handling instance IDs returned by 'instance_place'. |
Having the collectible handle its own detection adheres to the Single Responsibility Principle: 'obj_player' focuses exclusively on input, physics, and jumping, while interactive world items manage the consequences of being picked up.
Level Design and Room Placement
Once the objects are programmed, it is time to place them in your level ('rm_nivel_1'). Collectible placement should never be arbitrary; it should follow deliberate level design principles:
1. Jump arcs: Place an arc of three or four coins along a parabola over a pit or obstacle. This teaches the player the exact takeoff point and illustrates the airborne trajectory their character will follow.
2. Speed lines: Place coins at ground level across long, flat stretches to encourage uninterrupted sprinting and fluid movement.
3. Risk-reward gems: Position gems on narrow moving platforms or right behind the patrol route of the enemy built in lesson 7.1. Rewarding players who time enemy patterns cleanly reinforces mastery through substantial point gains.
💡 Correct Room Editor layer: Make sure to drag instances of 'obj_coin' and 'obj_gem' onto the 'Instances' layer in the Room Editor—the same layer (or higher) where 'obj_player' resides. If you drop them onto lower background or tile layers, they may render behind room geometry.
Hands-On Exercises
Complete the following exercises to reinforce your understanding of inheritance, math functions, and collision detection in GameMaker:
Exercise 1: Arc Placement and Visual Desynchronization Open your project room ('rm_nivel_1') and build a 5-coin arc over an elevated platform. Run the game and observe the vertical bobbing. Notice how every coin moves up and down in identical sync. Modify the Create event of 'obj_parent_collectible' so that 'tiempo_oscilacion' starts with a random phase offset using 'random(pi * 2)'. Verify that every coin in the room now floats independently and organically.
Exercise 2: Coin Magnetism / Attraction Mechanic In many modern games, collectibles fly toward the player when nearby. In the Step event of 'obj_parent_collectible', calculate the distance to 'obj_player' using 'point_distance(x, y, obj_player.x, obj_player.y)' (first checking that an instance exists with 'instance_exists(obj_player)'). If the distance is under 48 pixels, make the collectible move toward player coordinates using 'move_towards_point()' or by interpolating 'x' and 'y' with 'lerp'. Verification: the coin should interrupt its hover animation and home in on the player once they approach.
Exercise 3: Creating a Third Collectible Type (Golden Chalice) Using your inheritance architecture, create a new sprite 'spr_chalice' and a new object 'obj_chalice' with 'obj_parent_collectible' as its parent. In its Create event, call 'event_inherited()' and set its value to 200 points, give it a slow, stately bob ('velocidad_flotacion = 0.02'), and a wider float range ('amplitud_flotacion = 8'). Place it in a secret alcove and verify in GameMaker's Output console that collecting it prints a 200-point increase.
Summary
- Collectibles reward exploration, signpost jumping trajectories, and maintain dynamic player flow in platformers.
- Setting collectible sprite origins to Middle Centre ensures concentric visual scaling, rotation, and smooth sine-wave oscillation.
- A parent object architecture (obj_parent_collectible) centralizes collision, destruction, and movement logic without duplicating code across coins or gems.
- The 'sin()' trigonometric function produces smooth harmonic oscillation that simulates continuous levitation without heavy computational overhead.
- Calling 'event_inherited()' is mandatory when child objects need to extend rather than overwrite parent event behavior.
- Global score state is maintained through globally scoped variables ('global.puntos') initialized in a persistent controller object when the game starts.
Practise what you learned
Exercise 1 · 1 point
Why is it recommended to set a collectible sprite's origin to "Middle Centre" instead of "Top-Left" or "Bottom-Centre"?
Exercise 2 · 1 point
Complete the following code snippet from the **Step** event of `obj_parent_collectible` to calculate vertical oscillation and remove the instance when collected by the player: ```gml tiempo_oscilacion += velocidad_flotacion; y = y_base + ((tiempo_oscilacion) * amplitud_flotacion); if (place_meeting(x, y, obj_player)) { global.puntos += valor_puntos; (); } ```
Exercise 3 · 2 points
In the **Create** event of the child object `obj_gem`, call the function that executes the parent object's (`obj_parent_collectible`) initialization logic, and then override the `valor_puntos` variable with a value of 50.
Your code is not executed: specific requirements are checked on the text.
Sign in to save your progress — Sign in