Architecture for Lasers and Things That Get Hit by Lasers #
Cool. I moved to composition for laser collisions. It wasn’t using inheritance but it was definitely janky.
Okay, how do I describe this.
The level scene has children, including Laser and Rect Prism. Lasers have child lasers for each reflection and refraction.
The problem is that when a laser hits something, Godot gives me a reference to that object—the collider—and I need to decide whether the ray is absorbed, reflected, or refracted. The response to this laser collision will get more complicated as I add more mechanics.
To make matters worse, the Rect Prism is made up of two static bodies: the mount, which collides with walls but not lasers, and the fixture, which collides with both. When a Laser tests for a collision it gets the Fixture.
Currently the Laser code checks whether the collider’s parent is a Rect Prism and then decides to refract. Pretty janky. I’m depending on a particular scene tree structure inside the prism.
So instead, a Laser Collidable is used in the Godot editor to specify two things:
- How to respond to the laser collision: absorb the ray, reflect it, or refract it.
- A reference to the collision object (in this case the Fixture)
So that’s the Laser Collidable component.
The second part of the puzzle is a Laser Collider, a sort of laser collision management class. When Laser hits something, Godot gives it the collider, the object the laser hit. So Laser Collider provides a way to exchange that collider object for a response, i.e., absorb/reflect/refract.
When the level loads it creates a Laser Collider instance then traverses the scene tree giving that instance to all Lasers and Laser Collidables.
When a Laser hits something it uses the Laser Collider to determine whether to absorb, reflect, or refract the ray, depending on what it hit.
In a bigger level it might look something like this.
So right now all the logic is still in the Laser. If I go full ECS then the logic should be in the Laser Collider which is the “System” in Entity-Component-System. Right now the logic is just doing stuff to a child laser so it seems reasonable to keep that in the Laser class for now but I’m keeping it in mind.
…
Huh. I wonder if I could’ve used four SegmentShape2D (line segments) instead of one RectShape2D to detect collisions for both entering and exiting the rectangle.
Farewell to the Ghost #
The Laser Collidable has a problem. When dragging a mirror, say, I have a drag ghost. It’s a clone of the original and it’s responsive to laser collisions. At least, it’s supposed to be. Unforch right now when I create the clone, there’s no way of Laser Collider knowing that it exists. Laser Collider registers all the Laser Collidables on _ready()
. I’d need to manually register the Collidable when it’s cloned. But I don’t have a reference to the Laser Collider… though, I guess I could. When I register the Collidable I could give a reference to the Collider.
Ugh. I hate these class names. Really need to figure something out.
Well, I have an item on my to-do list to remove the ghost drag and drop. Player feedback is that the mounts aren’t so clever so much as they are confusing. I think I’ll let the player drag and rotate mirrors wherever they want, just the mirror will be disabled if it’s in an invalid position.
That would solve my ghost clone problem.
…
Getting rid of the mount/fixture drag and drop and rotate should reduce the complexity quite a bit. Just out of curiosity, what’s the line count right now?
$find . -type f -name '*.gd' | xargs wc -l
6 ./components/laser_collidable.gd
48 ./components/laser_collider.gd
4 ./scripts/global_state.gd
8 ./scripts/collidable.gd
287 ./scripts/interactible.gd
33 ./scripts/cursor_manager.gd
18 ./scripts/constants.gd
30 ./scripts/level_file_manager.gd
113 ./scripts/drag_and_drop.gd
43 ./scenes/level.gd
18 ./scenes/mirror.gd
29 ./scenes/level_change_ui.gd
236 ./scenes/laser.gd
18 ./scenes/prism_triangle.gd
65 ./scenes/sensor.gd
2 ./scenes/game_complete_popup.gd
18 ./scenes/prism_rectangle.gd
66 ./scenes/main.gd
14 ./scenes/level_complete_popup.gd
33 ./editor/create_wall_outlines.gd
1089 total
Interactible, Laser, and DragAndDrop are all pretty big. Some of these shouldn’t even have files at all, e.g., GameCompletePopup. I guess I can get rid of CreateWallOutlines, too.
That brings the total to 1054 total
.
/me cracks knuckles
Here we go…
…
A gotcha that hit me a couple times. I’m using Area2Ds now instead of StaticBody2Ds. My raycasts need to have collide_with_areas enabled.
…
Alright. I’ve got drag and drop rotation and translation working. Lasers still work. The game is beatable… but it’s very easy because there is nothing preventing the mirrors and prisms from occupying invalid positions. They don’t even turn red.
So.
That’ll be my task for tomorrow. Conjure a new component that detects when a mirror/prism is in an invalid position, change its colour to a ghostly red, and make the laser go straight through it.
For now, I’m heaps excited about how the project is looking with the switch to composition. I can already see how powerful and scalable this is.