Quick answer: Collision Events are evaluated once per step at End Step. An instance created the same step as a collision has not completed a full step cycle, so the collision pass either misses it or evaluates it before its mask is set. Set sprite_index inside Create, use place_meeting in End Step for inline checks, or defer creation with alarm[0] = 1.
Here is how to fix GameMaker collision events that fail to fire when an instance is created the same step. You spawn a hitbox, position it on top of the enemy, and expect the enemy’s Collision event with the hitbox to trigger. It does not. The hitbox exists, the enemy exists, the bounding boxes overlap on the next frame, but the collision event never fires for the frame they were created. Two frames later they are clearly intersecting and still nothing. The collision system is doing exactly what it was designed to do — you just hit it during the one window where new instances are not yet visible to it.
The Symptom
An instance created via instance_create_layer or instance_create_depth overlaps an existing instance on the same step, but the existing instance’s Collision Event with the new instance’s object never fires. Different patterns produce slightly different failures.
Spawn-and-hit hitboxes. A weapon swing creates a hitbox at the player’s position, the hitbox overlaps an enemy, and the enemy’s Collision event with obj_hitbox never runs. The hitbox is destroyed at the end of the swing without ever registering damage.
Projectile spawn inside an enemy. A player fires a projectile from inside an enemy’s bounding box (e.g., a melee gun blast). The projectile’s Collision event should trigger immediately but does not. The projectile travels through the enemy and only registers a hit if it leaves and re-enters the bounding box.
Powerups dropped at enemy death. When an enemy dies it spawns a powerup at its own position. The player, who is overlapping the dying enemy, should immediately collide with the powerup. The collision event between obj_player and obj_powerup does not fire that step. The powerup is collected only after the player moves and re-enters its bounding box.
Random first-frame inconsistency. Sometimes the collision fires the same frame, sometimes it does not. The behaviour appears to depend on object instance order, sprite assignment timing, or whether the new instance had a sprite at the moment of creation.
What Causes This
Collision Events evaluate once per step. GameMaker runs Collision Events during the End Step phase, after Begin Step, the user-defined Step event, and any movement. The pass iterates instances and checks intersections with each registered Collision target. An instance created mid-step misses the start of this pass — whether it is included depends on internal iteration order, which is not guaranteed.
The new instance has no mask yet. An instance created without an explicit sprite_index assignment has spr_undefined as its mask. Collision checks against spr_undefined always return false because there is no shape to test. If you assign sprite_index on the line after instance_create_layer, the assignment lands too late for the same-frame collision pass.
Begin Step has not run on the new instance. Many objects set image_speed, mask offsets, or position adjustments in their Begin Step event. The first time Begin Step runs on a freshly created instance is the step after creation, so any mask configuration done in Begin Step is missing on the creation step.
Persistence does not help. Persistent objects participate in the same per-step collision pipeline. The persistence flag controls room transitions, not collision timing. A persistent instance created mid-step still misses the same-frame collision pass.
The Fix
Step 1: Set sprite_index inside the Create event. The single most common cause is missing or late mask assignment. Make every projectile, hitbox, and powerup set its sprite (and therefore its mask) in its own Create event, not in the spawning instance’s code. This guarantees the mask is valid the moment the instance exists.
// obj_hitbox Create event — set everything immediately
sprite_index = spr_hitbox_swing;
image_speed = 0;
image_index = 0;
mask_index = spr_hitbox_swing;
damage = 10;
lifetime = 8;
// Defensive: scan for instant-overlap targets right now
var _hits = ds_list_create();
var _count = instance_place_list(x, y, obj_enemy, _hits, false);
for (var i = 0; i < _count; ++i) {
var _enemy = _hits[| i];
with (_enemy) {
hp -= other.damage;
if (hp <= 0) instance_destroy();
}
}
ds_list_destroy(_hits);
The instance_place_list call is the workaround for the same-step collision miss. It runs synchronously inside Create, finds every overlapping target right now, and applies your collision logic before any End Step pass would have run. The collision event itself is no longer needed for the spawn frame because you handled it manually.
Step 2: Use place_meeting in End Step for ongoing checks. If your hitbox needs to keep checking for overlaps every step (it lingers, it grows, it moves), put the check in End Step rather than relying on the Collision event. End Step runs after movement and gives you a deterministic point to test for overlaps with up-to-date positions.
// obj_lingering_hitbox End Step event
if (place_meeting(x, y, obj_enemy)) {
with (obj_enemy) {
if (place_meeting(x, y, other)) {
// Avoid double-hits using a tracking list
if (!ds_list_find_index(other.hit_targets, id)) {
ds_list_add(other.hit_targets, id);
hp -= other.damage;
if (hp <= 0) instance_destroy();
}
}
}
}
// Decrement lifetime regardless of hits
lifetime -= 1;
if (lifetime <= 0) instance_destroy();
The hit_targets list (created in the Create event as hit_targets = ds_list_create(), destroyed in Cleanup) prevents the same enemy from being damaged on every frame the hitbox lingers. Without it, a hitbox that lasts 8 frames would deal 8x damage to anything it touches.
When to Defer With alarm[0]
If your code needs the spawned instance to participate in the next Collision Event pass naturally (rather than handling collisions manually in Create), defer the actual creation by one frame using an alarm. Set alarm[0] = 1 on a controller object and put the instance_create_layer call in the Alarm 0 event. The new instance has a full step before any collision pass evaluates it, and its mask is fully assigned by the time anything checks.
The deferred pattern is a one-frame delay, which is invisible to players in most cases. Avoid it for tight reaction-window mechanics like parries where one frame matters. For those, the inline instance_place_list approach in Create is the right tool.
Diagnostic: confirming the cause
Before applying any fix, confirm the symptom by logging the new instance’s mask. Add show_debug_message($"mask={mask_index}") to the spawning code right after instance_create_layer. If it prints -4 (which is spr_undefined), your mask is missing on the spawn frame and step 1 of the fix will resolve it. If it prints a valid sprite ID and the collision still misses, you are hitting the per-step ordering issue and need step 2 or the alarm deferral.
“The Collision Event is a snapshot of the world at End Step. If your instance does not exist with a valid mask when the snapshot is taken, the snapshot does not include it. Take the snapshot yourself if you need it sooner.”
Related Issues
If your collision events fire but report wrong positions, see Collision Position Offset by One Frame for movement-order fixes. If destroying instances inside a Collision event causes crashes, check Instance Destroy During Collision Crash for safe deferred-destroy patterns.
Always set sprite_index in Create, never after — the same-frame mask is the cheapest fix.