bug-catcher-game: Bug Catcher Gnome: The Tiny Godot Game That Cheats Depth and Multiplies Players

A compact arcade project that uses authoritative multiplayer, browser-aware RPC paths, and a convincing 2.5D illusion to make a small world feel much bigger.

7 min read • View on GitHub • More from slavingia

A wide insect meadow where gnomes swing nets at bugs at several apparent depths. Layered trees, fence posts, and scattered flowers make the flat world read like a space with real distance, which is the visual trick the article explains.
Flat sprites, staged with enough perspective and shadow discipline to feel like a deeper world.
Key Takeaways

A 2D World That Pretends to Be Deep

The first trick in Bug Catcher Gnome is visual, not networked. The meadow is built from flat assets, but DepthManager, Y-position scaling, and pinned shadows make the arena feel stacked in space.

Flying bugs get a small altitude illusion on top of their 2D coordinates, so the eye reads them as hovering instead of sliding on a plane. That buys the game a lot of staging depth for very little engine cost.

Drag the bug up and down to see how vertical position becomes scale, shadow alpha, and the illusion of altitude.

The Multiplayer Trick Behind the Curtain

The networking code makes the same refusal: do not pretend one transport fits every target. The host creates the ENet server, clients join through the same manager, and spawned players are assigned authority so input stays with the right peer.

The browser path is the subtle part. if OS.has_feature("web"): tells the code to branch before desktop assumptions leak into the export, which is why the project reads like a small multiplayer lesson instead of a fragile demo.

var peer := ENetMultiplayerPeer.new()

func host_game() -> void:
    peer.create_server(PORT)
    multiplayer.multiplayer_peer = peer
    add_player(multiplayer.get_unique_id())

func join_game(address: String) -> void:
    peer.create_client(address, PORT)
    multiplayer.multiplayer_peer = peer

@rpc("any_peer", "call_local")
func add_player(id: int) -> void:
    var player := PLAYER_SCENE.instantiate()
    player.name = str(id)
    player.set_multiplayer_authority(id)
    add_child(player)

func _ready() -> void:
    if OS.has_feature("web"):
        # Browser-safe path. Avoid desktop-only assumptions.
        pass

Bugs, Behaviors, and the Spear Economy

Once the netting loop starts, the bug AI stops being background decoration. BugBehavior switches between wandering, fleeing, and chase-like responses, and the flee logic uses interpolation so movement looks organic instead of robotic.

The smartest design move is the bee-to-spear reward loop. Catch the right bug, and the game changes your verb from collecting to attacking, which lets one pickup reshape the entire tempo of a round.

enum BugBehavior { WANDER, FLEE, CHASE }

func _behavior_flee(delta: float) -> void:
    var away := (global_position - nearest_player.global_position).normalized()
    direction = direction.lerp(away, 6.0 * delta)

var bugs_hit: Array = []

func _on_spear_body_entered(body: Node) -> void:
    if body in bugs_hit:
        return
    bugs_hit.append(body)
    if bugs_hit.size() > 1:
        _trigger_multikill_effect()
A close, kinetic scene where a spear cuts through a cluster of ants while a bee-shaped power-up bursts nearby. The image explains how one reward can change the player’s verb from catching to attacking, and how the swarm system raises the stakes.
A single power-up changes the tempo, turning the game from collection into a short burst of crowd control.

Why the Code Reads Like a Template

This is the part that makes the repository useful even if you never ship the game. Main.gd stays out of the way because managers own music, sound, and multiplayer, while scene scripts handle local behavior and procedural spawning fills the arena at runtime.

That separation matters in Godot. The project keeps the art crisp with SVGs and a compatibility-minded render path, so the same structure can serve browser targets and low-end desktop hardware without forcing the whole codebase through one monolithic script.

What It Sits Beside

AxisBug Catcher GnomeTypical Godot arcade jamTypical Godot multiplayer starter
Multiplayer modelHost-as-server ENet with authority assignment.Often local only or loosely synced.Usually network-first, with little game feel.
Web exportBranches around browser constraints instead of ignoring them.Frequently left untested until it breaks.Often missing or brittle.
Depth illusion2D Y-scaling, pinned shadows, and sprite discipline.Flat top-down staging or a jump to real 3D.Usually unrelated to the core loop.
Code organizationManager pattern plus scene-script separation.One oversized main scene script.Engine demo code without much cohesion.
Reuse valuePlayable template and architecture reference.A one-off slice of gameplay.Transport example without a game loop.

That middle ground is the point. It is charming enough to read as a game, but disciplined enough to reuse as a pattern for Godot multiplayer and 2.5D staging.