Asteroids Destroyer Dev Log – 14 April 2026

Asteroids Destroyer โ€” Development Journal

Dev Log โ€” 14 April 2026

๐Ÿ“… Session 3
๐ŸŽฎ Godot 4.6
๐Ÿ–ฅ๏ธ UI Build-Out
๐Ÿ› Bug Fixes
๐Ÿ”— Continued from 13 April
๐Ÿ“‹
Session Overview
Continuation of the 13 April menu build; context ran out mid-session, resumed here

I picked up directly from where the 13 April session ended. I’d written all 7 menu GDScript files but hadn’t created any of the .tscn scene files yet. This session I covered:

  1. Creating all 7 menu scene files (.tscn)
  2. Updating GameManager, GameScene, HUD with new settings fields and wiring
  3. A series of bug fixes after first in-editor test
  4. UI polish and layout improvements based on user feedback
  5. Level Select redesign (5ร—5 grid, game-kit level icon images)
  6. Main Menu container freedom (removed VBoxContainer constraint)
๐Ÿ—‚๏ธ
Scene Files Created
All 7 menu .tscn files built from scratch
Scene Type Notes
scenes/menus/Splash.tscn Node2D Starfield + planet decorations + logo + “TAP TO PLAY”
scenes/menus/MainMenu.tscn Node2D + CanvasLayer Background, planets, play/endless/settings/shop buttons
scenes/menus/LevelSelect.tscn Node2D + CanvasLayer Panel frame, grid, prev/next pagination
scenes/menus/CharacterShop.tscn Node2D + CanvasLayer 3ร—3 ship grid, coins row, play button
scenes/menus/Settings.tscn CanvasLayer (overlay) Music/SFX/Fullscreen toggles, close button
scenes/menus/PauseMenu.tscn CanvasLayer (overlay) Resume/Settings/Main Menu, layer=10, process_mode=Always
scenes/menus/LevelComplete.tscn CanvasLayer (overlay) Stars display, score, retry/next buttons, layer=10
All scenes use Neuropol font (Assests/Fonts/neuropol/neuropol.ttf) loaded as an ExtResource and applied per-label via theme_override_fonts/font.
The teal panel frame asset (Assests/GUI/PNG/window_whole_alpha.png) is wired into LevelSelect, CharacterShop, PauseMenu, Settings, and LevelComplete as a PanelFrame TextureRect, with size and position to be refined in the Godot editor.
โš™๏ธ
Script Updates
GameManager, GameScene, HUD, MainMenu, LevelSelect

GameManager.gd: Settings persistence

NEW Added three fields to persistent save data:

var music_enabled: bool = true var sfx_enabled: bool = true var fullscreen: bool = false

Saved under a [settings] section in user://save.cfg alongside existing player data.

GameScene.gd: Pause input + Level Complete handoff

NEW Added _unhandled_input() to handle the action_pause action (Escape / P). Instantiates PauseMenu as a CanvasLayer overlay on the current scene.

CHANGED_level_complete() no longer calls hud.show_level_complete(). Instead, instantiates LevelComplete.tscn and calls setup(stars, score) on it.

HUD.gd: Rank badge, pause button, simplified

NEW Rank badge display: loads Sci Fi Ranks/Sliver Ranks/Rank Silver N.png based on current_rank. Falls back to Rank Silver 12 if rank 13 (missing from assets) is requested.

NEW Pause button wired: PauseButton.pressed โ†’ instantiates PauseMenu and adds it to get_tree().current_scene.

CHANGED Removed show_level_complete() method; responsibility moved to GameScene. Kept show_game_over() in HUD.

CHANGED Node hierarchy: LeftWidget Control houses points_powerup_lifes.png background, RankBadge TextureRect (70ร—70 on the circle), ScoreLabel, and HealthDisplay.

MainMenu.gd: Live coin count

NEWBtnCoins text is populated in _ready() from gm.coins and updates via coins_changed signal.

LevelSelect.gd: 5ร—5 grid, level icon images

CHANGEDLEVELS_PER_PAGE changed from 12 to 25. All 24 levels now fit on a single page, so the pagination buttons hide automatically.

CHANGED Level tile now uses Game Assests/UI/Levels/Level N.png as the tile frame image, with the level number as a Neuropol label overlaid in the centre. Locked tiles use Game Assests/UI/Level Locked.png.

CHANGED Tile styling: transparent Button StyleBox, with all visual style coming from the icon image.

๐Ÿ›
Bug Fixes
Issues found during first editor/runtime test and resolved
Bug Root Cause Fix
BUG Pause button invisible in HUD TextureButton used anchor-based layout (anchor_left=1.0) as a direct CanvasLayer child. CanvasLayer children don’t resolve anchors against viewport size reliably. Switched to layout_mode = 0 with absolute pixel coords: offset_left=1836, offset_right=1908 (72px button, 12px from right edge of 1920px canvas).
BUG Settings menu unresponsive when opened from Pause get_tree().paused = true is set by PauseMenu. Settings.tscn root node had no process_mode, so it defaulted to Inherit โ†’ paused state โ†’ no input processed. Added process_mode = 3 (PROCESS_MODE_ALWAYS) to the Settings CanvasLayer root. Also confirmed PauseMenu already has this set.
BUG Splash showed wrong background Scene used a solid ColorRect. Needed the game’s starfield background matching all other screens. Replaced ColorRect with Game Assests/Backgrounds/Background/background_01_parallax_01.png TextureRect + PTModelSprite_ID61659.png planet decorations top-left and bottom-right.
BUG Menu backgrounds not filling screen TextureRect stretch_mode = 5 (KEEP_ASPECT_CENTERED) letterboxes the image. expand_mode was set but stretch mode was wrong. Changed to stretch_mode = 0 (SCALE) on all menu background TextureRects. Image is 1920ร—1080 so distortion is not an issue.
BUG Rank badge too large / overflowing circle RankBadge TextureRect was sized at ~80ร—80 but positioned incorrectly relative to the circular portion of points_powerup_lifes.png. Resized to 70ร—70, positioned at offset (6, 6) โ†’ (76, 76) within the LeftWidget. Final fine-tuning done in editor.
๐ŸŽจ
Layout & Visual Changes
Structure changes to match the original game screenshots

Consistent background system across all screens

Every menu screen now uses the same background stack:

  1. background_01_parallax_01.png (starfield, stretch_mode=SCALE, full 1920ร—1080)
  2. PTModelSprite_ID9256.png or PTModelSprite_ID61669.png: planet top-left and bottom-right
  3. CanvasLayer UI above

Panel frame asset wired in

Assests/GUI/PNG/window_whole_alpha.png (teal border frame with transparent centre) is included as a PanelFrame TextureRect in all panel-style screens. Size and position are tunable in the Godot editor.

VBoxContainer removed from Main Menu

The Buttons node was a VBoxContainer, which forced child Controls to a fixed layout, preventing free movement in the editor. Changed to a plain Control node. BtnPlay and BtnEndless switched from layout_mode = 2 (container-driven) to layout_mode = 0 (absolute). Both buttons can now be freely moved and resized in the 2D editor.

Level Select: 5ร—5 grid

Changed from 6-column 2-page layout to a 5-column single-page layout. All 24 levels displayed at once (25th slot is empty). Tile spacing increased to h=16 / v=20. Pagination buttons hidden when total pages = 1.

๐Ÿ–ฅ๏ธ
Editor Guidance Provided
Things explained as “best done in the Godot editor” rather than in code

Button background colour

Inspector โ†’ Theme Overrides โ†’ Styles โ†’ Normal โ†’ New StyleBoxFlat โ†’ Bg Color. Repeat for Hover and Pressed states.

Button icons

Two options:

  • Button with icon: set the Icon property in Inspector. Tick Expand Icon to fill. Clear text for icon-only.
  • TextureButton: right-click node โ†’ Change Type โ†’ TextureButton. Assign texture_normal / texture_hover / texture_pressed. Assets available: Game Assests/UI/Settings Button.png, Shop Button.png, Restart.png.

Panel frame sizing

The PanelFrame TextureRect in each scene is wired with the teal frame image. Drag edges in the 2D canvas to match the content area. No code change required.

Planet decoration positions

Each menu scene has PlanetTopLeft and PlanetBottomRight TextureRects as Node2D children (behind CanvasLayer). Select and move in the 2D editor to taste.

๐Ÿ“Š
Project State After the Morning Session
What was complete before the afternoon continuation

Complete

  • All 7 menu scenes created and wired to their scripts
  • Game flow: Splash โ†’ MainMenu โ†’ LevelSelect โ†’ GameScene โ†’ PauseMenu / LevelComplete
  • Settings overlay works from both MainMenu and PauseMenu (with process_mode=Always)
  • HUD: rank badge (with missing-rank-13 fallback), score, health, pause button
  • GameManager persists music_enabled, sfx_enabled, fullscreen across sessions
  • project.godot main scene set to Splash.tscn
  • Level tiles use game-kit icon images + Neuropol number overlay
  • CharacterShop buy/select logic complete

Carried into afternoon session

  • Pause menu missing restart button
  • Asteroid spawn system: all spawning on-screen simultaneously, no edge drift-in
  • Level Select grid overflowing panel frame (5 rows too tall)
  • Level tile hover detection unreliable (child Controls intercepting mouse events)
  • Button hover box visible; I wanted scale-only feedback with no box
  • MainMenu PlayButton spin animation not yet added
๐Ÿ”„
Afternoon Continuation: Context Resumed
Session picked up after context window ran out mid-response

The previous context window ended just as the asteroid spawn rewrite was beginning. This continuation session picked up directly from that point, completing all outstanding requests from the prior message in sequence.

โ˜„๏ธ
Asteroid Spawn System Rewrite
scripts/game/GameScene.gd

NEW Replaced the old simultaneous-spawn system with a staggered queue driven from 8 fixed edge points.

8 Fixed edge spawn points

Positions sit 200px outside the screen boundary, one at each midpoint and one at each corner:

top-center (wร—0.5, -200) bottom-center (wร—0.5, h+200) left-center (-200, hร—0.5) right-center (w+200, hร—0.5) top-left (-200, -200) top-right (w+200, -200) bottom-left (-200, h+200) bottom-right (w+200, h+200)

Velocity toward screen center

Each edge-spawned asteroid is given a velocity aimed at the screen centre with a ยฑ25ยฐ spread so they drift in naturally rather than heading straight to the middle:

var base_angle = (screen_center – spawn_pos).angle() var final_angle = base_angle + randf_range(-0.44, 0.44) # ยฑ25ยฐ

Initial on-screen burst (~25%)

At wave start, ~25% of the wave’s asteroids spawn immediately at the four screen quadrant positions (with ยฑ80px random offset). The rest are queued for staggered edge entry.

Level-scaled spawn interval

The queue drains at an interval that shrinks with each level, making higher levels feel increasingly hectic:

func _spawn_interval() -> float: return maxf(0.35, 1.2 – (level – 1) * 0.04) # Level 1 โ†’ 1.20s between spawns # Level 10 โ†’ 0.84s # Level 24 โ†’ 0.35s (minimum)

Wave completion

CHANGED Wave complete now requires bothasteroids_alive <= 0and_spawn_queue.is_empty(). Previously the wave could complete while asteroids were still waiting to spawn.

Off-screen cleanup

NEW Every 3 seconds, any asteroid that has drifted more than 500px beyond the screen boundary is silently removed. This prevents accumulation of off-screen RigidBody2D nodes over long play sessions.

Bug fixed: double-count

FIX The old _start_wave() pre-set asteroids_alive to the total count AND spawn_asteroid() also incremented it, effectively doubling the count. The new system resets asteroids_alive = 0 at wave start and lets spawn_asteroid() be the sole incrementor.

โธ๏ธ
Pause Menu: Restart Button
scenes/menus/PauseMenu.tscn ยท scripts/menus/PauseMenu.gd

NEW Added a RESTART button between Settings and Main Menu in the pause VBox.

The handler unpauses the tree first, then reloads the current scene. The ordering is critical to avoid Godot’s paused-scene reload edge case:

func _restart() -> void: get_tree().paused = false get_tree().reload_current_scene()
๐Ÿ—‚๏ธ
Level Select: Grid Fix & Hover Fixes
scenes/menus/LevelSelect.tscn ยท scripts/menus/LevelSelect.gd

Grid overflow fixed: 6ร—4 layout

FIX The 5-column layout produced 5 rows of tiles. The grid’s available height was only 677px; 5 rows needed 955px, causing the last two rows to overflow the panel frame.

Fix: switched to 6 columns ร— 4 rows. 24 รท 6 = exactly 4 rows with no orphan tiles. Tile size reduced to 148ร—152 and separations tightened to h=12 / v=14 so everything fits cleanly:

Before (5 col) After (6 col)
Rows needed 5 rows โ†’ 955px (overflows) 4 rows โ†’ 650px (fits)
Orphan tiles 4th row incomplete None (24 รท 6 = exact)
Tile size 160 ร— 175 148 ร— 152

Hover detection fixed

FIX Mouse hover was unreliable because child Control nodes inside the Button tile were consuming mouse events before they reached the Button. The default mouse_filter for Control is MOUSE_FILTER_STOP.

Fix: set mouse_filter = MOUSE_FILTER_PASS on all non-interactive children inside each tile: vbox, icon_holder, icon, num_lbl, star_row, and each star Label.

Star row spacing tightened

CHANGED VBox separation reduced from 6 โ†’ 2. Stars now sit immediately below the icon rather than floating away from it.

Hover box removed, scale tween added

CHANGED All button states (normal, hover, pressed, focus, disabled) now use StyleBoxEmpty. No box appears on hover. Instead, mouse_entered and mouse_exited run a 0.1s Tween that scales the tile 1.0 โ†’ 1.1 and back, pivoted from the tile centre.

func _tile_hover_in(btn: Button) -> void: btn.create_tween().tween_property(btn, “scale”, Vector2(1.1, 1.1), 0.1) .set_trans(Tween.TRANS_SINE) func _tile_hover_out(btn: Button) -> void: btn.create_tween().tween_property(btn, “scale”, Vector2(1.0, 1.0), 0.1) .set_trans(Tween.TRANS_SINE)
โœจ
Button Animations: Spin & Hover Scale
scripts/menus/MainMenu.gd

PlayButton 360ยฐ spin

NEW The MainButton1/Playbutton Sprite2D spins a full 360ยฐ when the cursor enters the Play button area (desktop) or the button is pressed (mobile/touch). A guard flag _spin_active prevents re-triggering mid-spin.

func _spin_play_button() -> void: if _spin_active: return _spin_active = true var tw := play_sprite.create_tween() tw.tween_property(play_sprite, “rotation”, play_sprite.rotation + TAU, 0.5) .set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) tw.finished.connect(func() -> void: _spin_active = false)

TAU = 2ฯ€ = one full rotation. Duration is 0.5 seconds with sine ease-in-out for a snappy but smooth feel.

How to add scale-on-hover to any other button

Here’s the pattern to apply it to Endless or any other menu button:

  1. Set btn.pivot_offset = btn.size / 2 in _ready() so the button scales from its centre, not its top-left corner.
  2. Connect mouse_entered โ†’ scale to Vector2(1.1, 1.1) over 0.1s
  3. Connect mouse_exited โ†’ scale back to Vector2(1.0, 1.0) over 0.1s
  4. Connect button_down โ†’ same as mouse_entered (covers mobile touch)

How to remove the hover box on any button

Godot buttons draw a box on hover from the theme’s hover StyleBox. To remove it:

In the editor: Select the button โ†’ Inspector โ†’ Theme Overrides โ†’ Styles โ†’ set Normal, Hover, Pressed, and Focus each to New StyleBoxEmpty.

In code:

var empty := StyleBoxEmpty.new() for state in [“normal”, “hover”, “pressed”, “focus”, “disabled”]: btn.add_theme_stylebox_override(state, empty)
Both techniques produce identical results. The editor approach is faster for one-off buttons; the code approach is better when buttons are created at runtime (as in the Level Select tiles).
๐Ÿ“Š
Project State at the End of 14 April
Full-day summary

Complete

  • All 7 menu scenes wired and functional
  • Asteroid spawn system: staggered queue, 8 edge points, 25% on-screen burst, level-scaled interval, off-screen cleanup
  • Asteroids no longer bounce off screen edges; they drift freely and collide only with each other
  • Pause menu: Resume / Settings / Restart / Main Menu
  • Level Select: 6ร—4 grid (24 tiles exact), correct hover detection, scale tween, no hover box
  • MainMenu PlayButton 360ยฐ spin on hover and touch
  • Button hover/animation pattern documented for self-service application to remaining buttons

Still outstanding

  • Apply hover scale + StyleBoxEmpty to Endless Mode button and any remaining menu buttons
  • Panel frame sizing: visual adjustment in editor per screen
  • HUD final pixel alignment
  • Mobile joystick input not yet wired
  • Audio bus muting on session start (respect saved music_enabled / sfx_enabled)
  • Coin shop / IAP not implemented