Quick answer: Camera jitter when following the player is caused by event ordering (Step vs End Step), subpixel positions in pixel-art games, or a mismatch between the camera’s update rate and the physics step. Move the camera update to End Step, round to integer coordinates, and align the read to the same step where the player position was finalized.
Your player character moves smoothly. The tiles render crisply. But the moment the camera starts following, everything shimmers — the world vibrates by a single pixel as the player walks, and on direction changes the camera seems to lag, then overshoot. GameMaker’s camera system is straightforward, but it is very easy to introduce jitter in one of three specific ways.
Event Order and One-Frame Lag
Every GameMaker frame runs a fixed sequence: Begin Step → Step → collision handling → End Step → Draw. If you update the camera in the Step event of a manager object, it runs before the player object’s Step event (or after, depending on creation order — and that ordering is not guaranteed). Either way, you either get the player’s position from last frame or fight an unpredictable ordering bug between objects.
The fix is to update the camera in the End Step event. End Step runs after every object’s Step event has completed, so by the time your camera object reads obj_player.x, the player has already moved for this frame and the camera’s follow is always synchronized.
/// oCamera - End Step event
if (!instance_exists(obj_player)) exit;
// Target the player, centered on the view
var target_x = obj_player.x - view_w * 0.5;
var target_y = obj_player.y - view_h * 0.5;
// Smooth follow with a lerp
cam_x = lerp(cam_x, target_x, 0.15);
cam_y = lerp(cam_y, target_y, 0.15);
// Round BEFORE applying so the camera snaps to whole pixels
camera_set_view_pos(view_camera[0],
floor(cam_x), floor(cam_y));
Notice floor(cam_x) rather than passing the raw lerp value. That is the second of the three fixes.
Subpixel Camera Positions
When you use lerp, approach, or any kind of smoothing on the camera, the position becomes a fractional number. If you pass cam_x = 123.75 to camera_set_view_pos, GameMaker happily uses that — but the sprite renderer draws your tiles at a 0.75-pixel offset. With texture filtering on, you get a blurred image that shifts every frame. With texture filtering off (standard for pixel art), the rasterizer snaps alternately to 123 and 124 depending on rounding, producing visible one-pixel shimmer.
Always round. floor is the simplest choice and works for any direction of travel. For rarer cases where you want subpixel movement within a scaled render target, apply the floor to the outer (window-space) camera but let the inner surface advance smoothly.
If you are scaling a low-resolution surface up to the window, the better practice is to render to an application surface at native game resolution, then scale it to the window in the post-draw step. That way the camera moves in integer game pixels and the upscale handles the smoothness of window-space motion.
Physics Step Alignment
If your player uses the built-in Box2D physics (physics_world_create + phy_ variables), the story gets more complex. Physics bodies update on a fixed physics step that does not necessarily align with the game frame. Reading obj_player.x in End Step returns a linearly interpolated position between physics steps — which is usually what you want, but only if the camera update also interpolates consistently.
A common symptom is that the camera looks fine at 60 FPS but jitters at 144 FPS, because multiple frames read the same physics position between ticks. The fix is to record the previous and current physics-step positions of the player and have the camera interpolate between them based on delta_time:
/// oPlayer - Begin Step event (snapshot positions)
prev_phy_x = phy_position_x;
prev_phy_y = phy_position_y;
/// oCamera - End Step event (interpolated follow)
var alpha = clamp(1 - (physics_pause_tick / physics_step_dt),
0, 1);
var px = lerp(obj_player.prev_phy_x,
obj_player.phy_position_x, alpha);
var py = lerp(obj_player.prev_phy_y,
obj_player.phy_position_y, alpha);
camera_set_view_pos(view_camera[0],
floor(px - view_w * 0.5),
floor(py - view_h * 0.5));
This removes the discrete jump you otherwise see each time a physics tick completes.
Delta-Time and Room Speed
GameMaker’s delta_time is reported in microseconds per frame. If you use delta-based movement for the player but fixed-step movement for the camera (or vice versa), the two run at slightly different rates and the camera falls incrementally behind on some frames, catching up on others — visible as jitter. Pick a single model and apply it to both. For most 2D games, fixed-step at 60 FPS is the simplest correct choice.
Also avoid toggling fps_real in-game or running with vsync disabled during development. Window compositors and driver-level vsync can present frames at slightly irregular intervals that amplify any subpixel jitter your code introduces.
Integer-Only Smooth Follow
If you want silky-smooth camera motion on a pixel-art game without subpixel blurring, keep the camera position as a float internally but floor it only at the final camera_set_view_pos call. The camera advances by whole pixels visually while the underlying smoothing computation continues to operate on fractional values. This is the standard pattern in commercial GameMaker pixel-art games:
// Internal smoothing - float
cam_x += (target_x - cam_x) * 0.1;
cam_y += (target_y - cam_y) * 0.1;
// Apply to camera - integer
camera_set_view_pos(view_camera[0],
floor(cam_x), floor(cam_y));
“Smooth camera math with a floor at the very end. If you see jitter, you are either floating your view position or reading the wrong step.”
Related Issues
If the jitter only appears when the player is close to collision edges, see Sprite Index Wrong After State Change for the state-machine timing. If the camera jitter is only on the Y axis during jumps, check Path Following Overshooting Target — the same velocity-correction math applies.
End Step for the camera, floor for the view, lerp on floats internally. Jitter gone.