Introducción al Desarrollo de Videojuegos con GameMaker · Module 5: Collisions, Gravity, and Platforms
Applying Constant Gravity
Lesson 21 of 60 · 50 min
Also available in: ES
Introduction to Physics Simulation in 2D Video Games
In 2D platformer development, gravity is not merely a planetary force of attraction as described by Newtonian physics; it is, first and foremost, a game feel tool. The way a character falls, the rate at which they accelerate downward, and how quickly they respond to player inputs directly define the game feel. If gravity is too low, the character feels floaty, imprecise, and difficult to control—as if walking on the Moon. Conversely, if downward acceleration is excessive, jumps feel stiff, heavy, and unpredictable.
To build this mechanic on a rock-solid technical foundation, you must understand how GameMaker handles spatial coordinates and execution timing. Unlike a continuous scientific simulation, a game engine updates the world state at discrete intervals called frames or 'steps.' In each of these cycles, our code must deterministically compute how much vertical velocity increases and how that change affects the object's vertical position. In this lesson, we will implement this system step by step using native GML Code, intentionally bypassing the engine's built-in automatic systems to give you complete control over movement and future collisions.
GameMaker's Coordinate System and the Inverted Y-Axis
One of the first conceptual hurdles beginners face in game programming is the orientation of Cartesian axes. In traditional school mathematics, the origin point (0, 0) typically sits at the center or bottom-left corner of a Cartesian plane, where X increases to the right and Y increases upward. However, in computer graphics and in GameMaker specifically, the standard originates from how cathode-ray tube (CRT) monitors scanned lines across the screen: left to right, top to bottom.
Consequently, in a GameMaker Room, the origin (x = 0, y = 0) is located at the top-left corner of the room. This has two immediate and essential consequences for movement programming:
1. The horizontal axis (x) works intuitively: increasing x moves the object right, while decreasing x moves it left. 2. The vertical axis (y) is inverted compared to standard math: increasing y moves the object downward (toward the bottom of the screen), while subtracting or decreasing y moves the object upward (toward the ceiling of the room).
Understanding this inverted Y-axis is an absolute prerequisite for programming gravity. If gravity is an acceleration pushing bodies toward the floor, in GameMaker that acceleration must be a positive value added progressively to the character's vertical position on every elapsed frame.

Built-in Variables vs. Custom Variables in GameMaker
Anyone browsing basic GameMaker documentation will quickly discover that the engine provides built-in variables like 'vspeed', 'hspeed', 'speed', 'direction', and 'gravity'. If you assign a value to 'gravity' (for example, gravity = 0.5), GameMaker automatically applies downward acceleration to the instance without requiring a single additional line of math. However, in professional and indie 2D platformer development, using these built-in variables is strongly discouraged.
The reason lies in the engine's internal event execution order. When you use 'vspeed' and 'gravity', GameMaker automatically adds velocity to the 'y' position between the Step event and the End Step event, before you have a chance to intervene and check whether the character is about to pass through a platform, a hazard, or a solid wall. This leads to erratic behavior such as characters sinking into floors, visual jitter, and game-breaking collision errors.
The standard, robust solution is to manage speed and acceleration using custom instance variables such as 'vspd' (vertical speed) and 'grav' (gravitational acceleration). By calculating velocity accumulation and position updates ourselves inside the Step event, we retain full control over every pixel traveled before the engine renders the frame.
💡 Golden Rule: In high-precision 2D platformers, never mix built-in variables (vspeed, hspeed, gravity) with custom movement variables (vspd, hspd, grav). Keep full control inside your own scripts and events.
Defining Variables in obj_player's Create Event
The first technical step is initializing the variables that define our main character's physical behavior, which we will call 'obj_player'. Remember that the Create event runs exactly once at the moment the object instance is instantiated in the Room. It is the ideal place to declare movement properties, parameters, and initial values.
Let's open 'obj_player' in the GameMaker workspace, select the Create event, and add the following GML Code block:
// obj_player Create Event
// Movement speeds
hspd = 0; // Current horizontal speed in pixels per frame
vspd = 0; // Current vertical speed in pixels per frame
// Acceleration parameters and physical limits
grav = 0.3; // Gravity constant (acceleration per frame)
vspd_max = 9.0; // Terminal velocity or maximum falling speed
// Horizontal control and jump parameters (declared in Module 4)
walk_spd = 3.5; // Maximum walking speed
jump_spd = -7.5; // Initial jump impulse (negative to move upward)Let's examine the role of each variable introduced for gravity:
- 'vspd': Stores the number of pixels the instance should move vertically on the current frame. A value of 0 indicates rest; a negative value (for example, -7.5) means the character is rising; a positive value (for example, 4.2) means they are descending. - 'grav': Represents gravitational acceleration. On every frame, we add this amount to 'vspd'. A value of 0.3 pixels/frame² delivers a responsive, snappy feel typical of classic 16-bit platformers. - 'vspd_max': Represents terminal velocity. In the physical world, air drag prevents falling bodies from accelerating infinitely. In a video game, a terminal velocity is critical not just for aesthetics, but to prevent characters from falling so fast that they tunnel right through thin platforms in a single frame (a phenomenon known as 'tunneling').
Implementing Gradual Acceleration in the Step Event
The Step event runs constantly and periodically: exactly once per game frame. If your project runs at 60 frames per second (fps), the code inside the Step event is evaluated and executed 60 times every second. This is where we turn mathematical acceleration into visible movement.
The fundamental kinematic formula for this process consists of two sequential steps:
1. Update velocity with acceleration: new_velocity = current_velocity + acceleration 2. Update position with velocity: new_position = current_position + new_velocity
Here is how that translates into clean, efficient code within the Step event of 'obj_player':
// obj_player Step Event
// 1. Gradually apply gravity
vspd += grav;
// 2. Clamp to terminal velocity
if (vspd > vspd_max) {
vspd = vspd_max;
}
// 3. Apply vertical movement to spatial coordinate
y += vspd;The '+=' operator is shorthand for 'vspd = vspd + grav;'. In every 1/60th of a second cycle, 'vspd' increases by 0.3 units. When this value exceeds the threshold set in 'vspd_max' (9.0 pixels per frame), the 'if' condition steps in and caps it, keeping the speed locked at its maximum ceiling.
Using Native Functions to Bound Velocity: clamp() and min()
While the 'if' statement is completely valid and intuitive for beginners, GameMaker provides built-in math functions that let you write more concise, expressive code. Among the most useful here are 'min()' and 'clamp()'.
The 'min(val1, val2, ...)' function returns the smallest value among all supplied arguments. Consequently, we can combine the gravity increment and the terminal velocity cap into a single line:
// Clean alternative using min()
vspd = min(vspd + grav, vspd_max);If 'vspd + grav' evaluates to 4.5 and 'vspd_max' is 9.0, the function chooses 4.5. However, once 'vspd + grav' reaches 9.3, the function discards that value and returns 9.0, ensuring the character never exceeds the allowed maximum falling speed.
Meanwhile, 'clamp(val, min, max)' constrains a numerical value between a lower and an upper bound. This is particularly helpful when you also want to ensure the character doesn't launch upward at an uncontrolled speed during powered jumps or bounce pads:
// Bidirectional alternative using clamp()
vspd += grav;
vspd = clamp(vspd, -12.0, vspd_max);Step-by-Step Breakdown of Free Fall Behavior
To see exactly what happens in engine memory and on screen coordinates, let's trace an 'obj_player' instance starting from rest (vspd = 0) at vertical position y = 100, with gravity grav = 0.5 and terminal velocity vspd_max = 2.0 across the first 6 frames:
| Frame | Initial vspd | Operation (+ grav) | Final vspd | Previous y | New y (y + vspd) |
|---|---|---|---|---|---|
| Frame 0 | 0.0 | 0.0 + 0.5 | 0.5 | 100.0 | 100.5 |
| Frame 1 | 0.5 | 0.5 + 0.5 | 1.0 | 100.5 | 101.5 |
| Frame 2 | 1.0 | 1.0 + 0.5 | 1.5 | 101.5 | 103.0 |
| Frame 3 | 1.5 | 1.5 + 0.5 | 2.0 | 103.0 | 105.0 |
| Frame 4 | 2.0 | 2.0 + 0.5 -> min(2.5, 2.0) | 2.0 | 105.0 | 107.0 |
| Frame 5 | 2.0 | 2.0 + 0.5 -> min(2.5, 2.0) | 2.0 | 107.0 | 109.0 |
Notice the distance traveled across consecutive frames: on Frame 0 it drops just 0.5 pixels; on Frame 1 it drops 1.0 pixel; on Frame 2 it drops 1.5 pixels; and starting at Frame 3, having hit the terminal velocity of 2.0 pixels per frame, descent becomes uniform, advancing exactly 2.0 pixels every clock cycle. This smooth progression from rest through parabolic acceleration to constant speed tricks the human eye into perceiving weight and tangible mass on screen.
Integrating Gravity with the Jump from Module 4
In the previous module, you learned how to read player input and apply negative vertical velocity when pressing the spacebar or up arrow key. By combining jumping with constant gravity, the underlying math naturally produces a parabolic arc without requiring any trigonometric functions like sine or cosine.
Here is how both mechanics work together inside the complete Step event for 'obj_player':
// obj_player Step Event
// 1. Capture player input
var _key_jump = keyboard_check_pressed(vk_space);
// 2. Apply jump impulse (if the key is pressed)
// Note: Later in this module we will restrict this to when grounded
if (_key_jump) {
vspd = jump_spd;
}
// 3. Apply constant gravity
vspd += grav;
// 4. Cap at terminal velocity
if (vspd > vspd_max) {
vspd = vspd_max;
}
// 5. Apply displacement to the Y-axis
y += vspd;When the player taps the spacebar, 'vspd' immediately takes on a large negative value, such as -7.5. On the first frame after jumping, 'y' decreases by 7.5 pixels, sending the character upward. Over subsequent frames, gravity (+0.3) gradually chips away at that negative momentum:
- Frame 1: vspd = -7.2 (the character rises quickly) - Frame 10: vspd = -4.5 (the character continues rising, but decelerates) - Frame 25: vspd = 0.0 (apex of the jump; vertical speed hits zero for an instant) - Frame 26: vspd = +0.3 (the character begins falling downward) - Frame 40: vspd = +4.5 (the character falls with increasing speed)
This kinematic curve matches the trajectory of a real projectile in elementary physics, generated entirely through basic addition and logic running 60 times per second.
The Infinite Fall Problem and Preparing for Collisions
If you run the game right now with this code in place, you will immediately spot an obvious problem: the character falls smoothly, accelerates, reaches terminal velocity, and keeps falling forever—slipping past the bottom edge of the window into the void. This is completely normal and expected at this stage.
Computers have no intrinsic concept of 'floors' unless we write the mathematical checks that prevent one sprite bounding box from overlapping another. GameMaker draws sprites exactly where coordinates dictate; if we increment the 'y' coordinate every frame without checking for obstacles, the instance will simply keep falling toward positive infinity.
In the upcoming lessons of this module, we will implement 'place_meeting()' and solid obstacle management ('obj_solid' or 'obj_wall') to zero out 'vspd' the exact pixel the player's feet touch a platform. Isolating and perfecting gravity first ensures our physics pipeline remains modular, clean, and straightforward to debug once horizontal and vertical collisions enter the picture.
Best Practices and Common Pitfalls with Gravity
When implementing basic physics in GameMaker, beginners often run into recurring bugs that destabilize character controllers. Keep these core principles in mind:
1. **Omitting terminal velocity:** Letting gravity add to 'vspd' unchecked can push vertical speeds to 50 or 100 pixels per frame during long drops. This causes the character to skip past thin platforms entirely without triggering collisions between frames. 2. **Mixing up directional signs:** Remember that falling means adding downward speed (vspd += grav), while jumping or rising requires negative speed (jump_spd = -8). Flipping these signs will send your character rocketing toward the ceiling. 3. **Declaring variables in the wrong event:** Initializing 'vspd = 0;' inside the Step event instead of the Create event resets vertical speed to zero every single frame. The character will never accelerate because the assignment continuously wipes out prior calculations. 4. **Embracing floating-point precision:** GameMaker handles 64-bit floating-point numbers by default. Using small decimal values such as 0.25, 0.3, or 0.45 for gravity is completely standard and recommended. Do not restrict your movement variables to integers if you want smooth, responsive handling.
💡 Debugging Tip: You can monitor 'vspd' and 'y' in real time by drawing them on screen using 'draw_text(x, y - 20, string(vspd));' inside the Draw event, or by opening the Debugger window in GameMaker while testing.
Hands-On Practice Exercises
Test your understanding and reinforce the acceleration logic by working through these exercises:
**Exercise 1: Manual Trajectory and Acceleration Calculation** A character starts from rest at vertical coordinate y = 250. It uses gravity grav = 0.4 and terminal velocity vspd_max = 1.0. Without running the engine, manually calculate on paper the exact values of 'vspd' and the 'y' coordinate for frames 0, 1, 2, 3, and 4. Identify the exact frame where velocity stops increasing and locks at terminal velocity.
**Exercise 2: Asymmetric Gravity (Fast-Falling Jump)** In many classic platformers (such as Super Mario Bros. or Hollow Knight), characters fall faster than they rise, producing a satisfying sense of weight. Modify the Step event of 'obj_player' to check a condition: if 'vspd < 0' (rising), apply lower gravity (e.g., grav_rise = 0.25). If 'vspd >= 0' (at apex or falling), apply higher gravity (e.g., grav_fall = 0.55). Test in GameMaker to see how this changes the jump's responsiveness.
**Exercise 3: Catching and Fixing an Initialization Bug** A student tries to implement gravity, but their character drops at a locked, constant speed without ever accelerating, moving downward at exactly 3 pixels per frame. Inspecting their code reveals this inside the Step event: ```gml vspd = 0; vspd += 3; y += vspd; ``` Identify the conceptual mistake, explain why acceleration fails to occur, and rewrite the code properly split between the Create event and the Step event.
Summary
- In GameMaker, the Y-axis is inverted: the origin (0,0) sits in the top-left corner, meaning positive Y values move downward.
- For precise control over collisions and movement, use custom instance variables (hspd, vspd, grav, vspd_max) instead of GameMaker's built-in variables (speed, vspeed, gravity).
- Speed and acceleration variables must be initialized in the Create event to avoid resetting their values every frame.
- Gravity is simulated by continuously adding acceleration to vertical speed (vspd += grav) every frame inside the Step event.
- Terminal velocity (vspd_max) is essential to prevent extreme speeds that cause tunneling through thin floors or platforms.
- A natural parabolic jump arc emerges automatically when combining an initial negative vertical impulse with continuous positive gravitational acceleration.
Practise what you learned
Exercise 1 · 1 point
Why does adding a positive value to an object's vertical position in GameMaker move it downward (toward the ground)?
Exercise 2 · 1 point
Arrange the fundamental steps executed in obj_player's Step event in chronological order to apply gravity and move the character in a controlled manner:
- 1Update the vertical spatial coordinate by adding the final speed (y += vspd)
- 2Check if vertical speed exceeds the allowed limit and clamp it to vspd_max
- 3Add gravitational acceleration to vertical speed (vspd += grav)
Exercise 3 · 1 point
Complete the Step event code with the appropriate variable names covered in the lesson: // 1. Gradually apply gravity vspd += ; // 2. Clamp to terminal velocity if (vspd > ) { vspd = ; } // 3. Apply movement to vertical position += vspd;
Sign in to save your progress — Sign in