Skip to main content

Grouping Laser Levels

·625 words·3 mins

Okay, bit of a refactor today.

In the old code, lasers inherit from RayCast2D which does collision tests every frame. Not only is that unnecessary processing but it also makes it difficult to detect the level complete condition. Each frame, I’m resetting a counter (of sorts) on the sensor and then waiting for the hits to be registered. This model assumes that the _physics_process() for the sensors and the lasers always happen in the same order, which is not guaranteed.

This change will also make debugging easier because it’ll reduce the boatloads of output when printing troubleshooting messages.

I think a top-down approach would be more robust. Something that orchestrates all this. First, tell the sensors to reset their counters, then tell the emitters to fire their lasers, then check in with the sensors to see if they got hit.

Anyway. The reason I’m here is to think this through before diving in.

Laser extends RayCast2D

It’s currently using:

force_raycast_update()
get_collision_point()
get_collision_normal()
get_collider()
target_position
clear_exceptions()
collide_with_areas = true
set_collision_mask_value(Constants.CollisionLayer.DEFAULT, false)
set_collision_mask_value(Constants.CollisionLayer.LASERS, true)

With that info, converting to use the physics ray query was easy.

I’m curious about the performance.

Before:

After:

Okay, a little faster. Not much but it’s there.

Even with lasers disabled the frame time is longer than physics frame time.

Hrm.

Okay, I clearly don’t know what’s going on because this is what an empty project looks like:

Those spikes are alt-tabbing away from the game btw. But. Why is the frame time still so high?

I’ll make a note to understand this better later on. For now I wanna get this done.

Well, script functions are taking up far less time now. I don’t know if I can see an improvement in performance here but certainly the script functions are faster. 0.36 ms to 0.09 so one quarter but that’s not where the bottleneck is anyway.

Maybe it’s the laser glow rendering or something.

A little anti-climactic unforch but still helpful and that was actually a pretty quick little refactor.

I’ve organized the levels into groups. My groups inherit from ColorRect so they have nice little handles to resize it so all the levels will fit.

The groups detect the levels they contain and automatically assign the next_level_num property of each level based on the order of the levels in the group.

So all I gotta do is drag the levels into the group and arrange them left to right and the next button in the corner will take the player to the correct level.

I also encountered an interesting issue where walls were being ignored when the level first starts up.

As soon as I click the mouse button the walls are respected but the first render of the level shows lasers going through the walls. Unacceptable.

By the way, this is a side effect of the refactor I did this morning. Previously it was updating every frame so this would’ve been a brief flash of unstoppable lasers at worst. Now that it’s only updating as needed the problem remains until the user interacts with it.

I tried to solve it by updating after TileMapLayer’s changed signal was emitted but no dice. I did some reading and found a few related issues: https://github.com/godotengine/godot/issues/75073 https://github.com/godotengine/godot/issues/67679 https://github.com/godotengine/godot/issues/76349

For me, awaiting two frames of physics updates does the trick.

func _ready() -> void:
    await get_tree().physics_frame
    await get_tree().physics_frame
    update()

I saw that some other people lost hours troubleshooting this. Lucky I only spent a pomodoro or so.

Pretty stoked how things are going. Next step is probably a level selection screen that the player will see. But for now I gotta change gears. Tomorrow is the Grow Up Game Jam and I need to get my idea posted and finish my game template.