Asteroids Destroyer Dev Log – 13 June 2026

Asteroids Destroyer — Development Journal

Dev Log — 13 June 2026

📅 Session 10
🎮 Godot 4.6
💥 Hit & Death Effects
🛡️ Shield Fix
🛸 Enemy Colour Variants
🚀 BGS Capital Ship Destruction
🚀

Session Overview

Long session covering five distinct areas: a long-standing collision shape warning, a shield always-visible bug, impact and death visual effects, random colour enemy units, and a fully separate death sequence for BGS capital ships.

  • Fixed: “This node has no shape” collision warning on Player.tscn; the root cause was a Node2D intermediate parent breaking Godot 4’s physics engine detection
  • Fixed: Shield sprite visible during gameplay at all times
  • New: Small explosion animation at point of impact when player is hit
  • New: Random colour variants for enemy units (matching existing mothership approach)
  • New: Full ship death effect: debris pieces flying outward + seam explosions for fighters; 13-blast hull-covering grid for BGS capital ships
🐛

The “This node has no shape” Collision Warning

Fixscenes/entities/Player.tscn was logging a warning for every CollisionPolygon2D in the scene, even though all 18 polygons had valid geometry assigned.

Root Cause

All 18 CollisionPolygon2D nodes were children of an intermediate Node2D called CollisionScaler, which was itself a child of the CharacterBody2D root. Godot 4’s physics engine only registers collision shapes that are direct children of a physics body; grandchildren through a plain Node2D are silently ignored and produce the warning.

Fix

Removed the CollisionScaler node entirely and re-parented all 18 CollisionPolygon2D nodes directly under the CharacterBody2D root. All references in Player.gd to CollisionScaler were removed and the enable/disable logic now targets the polygons directly by name.

Godot 4 rule: CollisionShape2D / CollisionPolygon2D nodes must be direct children of a physics body (CharacterBody2D, RigidBody2D, StaticBody2D, Area2D). Any intermediate Node2D breaks detection entirely.

Code Changes

_editor_update_preview() and _apply_ship_setup() both now iterate the collision name list and set disabled = true on all, then disabled = false on only the active one, with no CollisionScaler involved.

🐛

Shield Always Visible in Game

Fix The ShieldPreviewSprite2D was left visible in the scene file for editor tuning purposes and was never hidden at runtime.

Fix

Added two lines at the top of _ready() in Player.gd to hide the node at runtime:

var sp_prev := get_node_or_null("ShieldPreview") as Sprite2D
if sp_prev:
    sp_prev.visible = false

The ShieldPreview node remains visible in the editor (for per-ship tuning) but is hidden the moment the game starts.

Impact Explosion at the Point of Hit

New When the player takes damage the ship previously only flashed. A small explosion animation now spawns at the exact point of contact.

Impact Position Propagation

take_damage() was extended to accept an optional impact_pos: Vector2 parameter (defaults to global_position for backwards compatibility). All four call sites were updated:

  • EnemyBullet.gd passes global_position of the bullet
  • EnemyUnit.gd passes global_position of the unit
  • EnemyMothership.gd passes global_position of the mothership
  • Player-asteroid collision passes KinematicCollision2D.get_position() (exact contact point)

Animation

_spawn_hit_effect(impact_pos) loads the 9-frame Explosion1.pngExplosion9.png sequence, spawns a temporary Sprite2D at the impact position, and drives it with a Timer at 25 fps. Display size is 30 px.

GDScript Lambda Closure Bug: Fixed

The first implementation used var frame_idx: int captured by the timer lambda. The animation never advanced because GDScript captures int as a value copy, so mutations inside the lambda don’t persist between calls.

Fix: array reference trick: Wrap the counter in a single-element array: var frame: Array[int] = [1]. Arrays are reference types so frame[0] += 1 inside the lambda mutates the same object and the frame counter advances correctly.

This pattern is now the project standard for timer-driven animation state inside lambdas.

Random Colour Enemy Units

New Enemy units previously always used the single default grey texture. They now pick a random colour variant on spawn, matching the mothership behaviour introduced in an earlier session.

Implementation

In EnemyUnit.gd, the single TEXTURE_PATH constant was replaced with a TEXTURE_PATHS: Array[String] containing 9 variants:

const TEXTURE_PATHS: Array[String] = [
    "res://Game Assets/Enemys/enemy_unit.png",
    "res://Game Assets/Enemys/Enemy Unit Green.png",
    "res://Game Assets/Enemys/Enemy Unit Light Blue.png",
    "res://Game Assets/Enemys/Enemy Unit Light Green.png",
    "res://Game Assets/Enemys/Enemy Unit Light Orange.png",
    "res://Game Assets/Enemys/Enemy Unit Light Purple.png",
    "res://Game Assets/Enemys/Enemy Unit Light Red (1).png",
    "res://Game Assets/Enemys/Enemy Unit Light White.png",
    "res://Game Assets/Enemys/Enemy Unit Light Yellow.png",
]

_ready() picks one at random and scales it to the 55 px target display size, identical to how the mothership handles its 15 variants.

Fighter Ship Death Effect

New When a fighter-series player ship is destroyed it now produces a full multi-part explosion rather than just disappearing.

Debris Pieces

Five Sprite2D nodes are spawned, each showing a cropped region of the ship’s own texture using region_enabled = true and region_rect. This avoids needing separate artwork for every ship variant.

  • Nose section: flies upward
  • Left wing: flies left and slightly down
  • Right wing: flies right and slightly down
  • Engine section: flies downward
  • Centre core: flies in a random direction

Each piece launches at 90–160 px/s with ±15° rotation jitter on the initial heading. Velocity and offset are rotated by the ship’s global_rotation at the time of death so pieces fly in the correct world-space directions regardless of heading. A Tween fades each piece to transparent over 0.8 s then frees it.

Seam Explosions

The crop edges between pieces are straight lines and were clearly visible. Small explosions are spawned in a zig-zag pattern along each of the four cut lines to disguise them.

  • 4 cut lines defined (nose bottom, engine top, left wing right edge, right wing left edge)
  • Each cut sampled at n evenly spaced points (6–16 depending on ship size)
  • Alternating ±zz px perpendicular offset creates a jagged break appearance
  • Explosion size and point count scale with ship display size using sqrt sub-linear scaling so they don’t become enormous on large ships

Final Blast Chain

6 staggered explosions fire outward from the ship centre over 0.34 s to complete the destruction sequence.

🚀

BGS Capital Ship Death Effect

New BGS ships (series 6 & 7) are 392×1057 px portrait textures displayed at ~495 px tall, far larger than fighter ships. The region-crop debris approach produced massively overlapping pieces and the cut seams were very obvious. A dedicated capital ship destruction sequence was implemented instead.

Approach

BGS ships skip the debris pieces and seam explosions entirely (if not _is_bgs guard). Instead they receive a grid of 13 precisely positioned blasts that march down the length of the hull over 0.8 seconds, covering nose to engines.

Blast Grid

Each blast is defined as [local_x_frac, local_y_frac, delay_sec, size_px]. The fractions are multiplied by the ship’s on-screen half-width and half-height, then rotated by global_rotation to match the ship’s actual heading:

# [local_x_frac, local_y_frac, delay_sec, explosion_size_px]
[ 0.0,  -0.85, 0.00, 100.0],  # nose tip
[-0.3,  -0.60, 0.08,  80.0],
[ 0.3,  -0.50, 0.13,  85.0],
[ 0.0,  -0.30, 0.19, 110.0],
[-0.4,  -0.06, 0.26,  80.0],
[ 0.4,   0.04, 0.32,  85.0],
[ 0.0,   0.16, 0.37, 130.0],  # centre — biggest
[-0.3,   0.36, 0.45,  80.0],
[ 0.3,   0.44, 0.51,  85.0],
[ 0.0,   0.56, 0.57, 100.0],
[-0.2,   0.72, 0.64,  80.0],
[ 0.2,   0.80, 0.70,  85.0],
[ 0.0,   0.0,  0.80, 150.0],  # final massive central flare

Each delayed blast uses the same Timer + one-shot lambda pattern established by the hit effect. The final blast at 0.80 s is a 150 px flare centred on the ship to cap the sequence.

Branching Logic

if not _is_bgs and ship_tex != null:
    # ... debris pieces ...

if not _is_bgs and ship_tex != null:
    # ... seam explosions along cut lines ...

if _is_bgs and ship_tex != null:
    # ... 13-blast capital ship grid ...
else:
    # ... 6-blast fighter chain ...
📁

Files Changed

File Change Status
scenes/entities/Player.tscn Removed CollisionScaler Node2D; re-parented all 18 CollisionPolygon2D nodes directly under CharacterBody2D Done
scripts/entities/Player.gd ShieldPreview hidden at runtime; take_damage() accepts impact_pos; _spawn_hit_effect(); _spawn_death_effect() with fighter debris + seam explosions + BGS blast grid Done
scripts/entities/EnemyUnit.gd Single texture replaced with 9-variant random colour array; display size scaling added; take_damage() call passes impact position Done
scripts/entities/EnemyBullet.gd take_damage() call updated to pass bullet global_position as impact point Done
scripts/entities/EnemyMothership.gd take_damage() call updated to pass mothership global_position as impact point Done

Late Session: Game Over Screen Navigation & Enemy Size

Game Over: Main Menu & Ships Buttons

New Two navigation buttons added to the game over panel matching the exact visual style used elsewhere in the game (transparent StyleBoxEmpty background, icon on top, Neuropol text below):

  • Main Menu uses game_logo_small.png icon, same asset as the main menu button in the pause menu. Navigates to MainMenu.tscn.
  • Ships uses Shop Button.png icon, same asset as the Ships button on the main menu. Navigates to CharacterShop.tscn.

Both buttons unset get_tree().paused before navigating so the scene transition is clean. Wired in HUD.gd as _on_go_main_menu_pressed() and _on_go_ships_pressed().

Final positions set manually in the Godot editor after initial coordinate calculations placed them partially off-screen (the GameOverPanel extends to x=2090 on a 1920px screen, so only local x ≤ 1280 is visible). Editor-confirmed positions: top-right and bottom-right of the visible panel area.

Enemy Ship Size Increase

Change Both enemy types were slightly too small in-game. DISPLAY_SIZE constants bumped:

Entity Before After
EnemyUnit.gd 55 px 75 px
EnemyMothership.gd 150 px 200 px
🔊

Sound Effects Audit

Full audit of audio assets on disk vs. events in code. Only one sound (thruster loop) is currently wired. All others are missing.

Assets on Disk: res://Sounds/

File Likely Purpose Wired?
Main Menu.mp3 Main menu background music Not wired
Spaceshuttle_Rocket_Steady_LV.mp3 Thruster loop Wired ✓
Rocket Fire.mp3 Player shooting / rocket fire Not wired
Asteroid Hit.mp3 Asteroid takes damage Not wired
Nuke Exp.mp3 Nuke explosion Not wired
science_fiction_spaceship_or_rocket_fly_by_fast_001.mp3 Unknown / fly-by Not wired
PTModelSound_ID116707.mp3 Unknown Not wired
PTModelSound_ID193943.mp3 Unknown Not wired
PTModelSound_ID195419.mp3 Unknown Not wired
PTModelSound_ID195429.mp3 Unknown Not wired
PTModelSound_ID26529.mp3 Unknown Not wired
PTModelSound_ID70321.mp3 Unknown Not wired

Missing Sound Events: Priority Order

Sound Needed Triggered By Script / Line
Player shoot Player fires bullet (X1/X2/X3 and BGS variants) Player.gd · _handle_fire()
Enemy fire EnemyUnit or Mothership fires a bullet EnemyUnit.gd / EnemyMothership.gd · _shoot_at()
Asteroid hit Asteroid takes a hit, may split Asteroid.gd · hit()
Asteroid explosion Asteroid fully destroyed Asteroid.gd · _destroy()
Enemy explosion EnemyUnit or Mothership destroyed EnemyUnit.gd / EnemyMothership.gd · hit()
Player hit / damage Player takes damage from enemy/asteroid Player.gd · take_damage()
Player death Player health reaches 0 Player.gd · _die()
Crystal collect Player collects a crystal Crystal.gd · marked SOUND NEEDED
Power-up collect Health / shield / nuke pickup PowerUp.gd · _apply()
Nuke explosion Nuke detonates, destroys asteroids in radius NukeExplosion.gd
Ship level-up Ship crosses X1→X2 or X2→X3 threshold GameManager.gd / Player.gd · marked SOUND NEEDED
Rank promotion fanfare Player promoted to new rank HUD.gd · marked SOUND NEEDED
Shield activate Shield power-up collected and activated Player.gd · activate_shield()
Ship purchase success Successful ship buy in CharacterShop CharacterShop.gd · marked SOUND NEEDED
Insufficient funds Failed ship purchase attempt CharacterShop.gd · marked SOUND NEEDED
Main menu music Main menu scene loads MainMenu.gd · asset exists, not wired
Button click (UI) Any menu button press All menu scripts
Note: 6 of the 17 missing events already have SOUND NEEDED comments in the code with suggested filenames. The unidentified PTModelSound_ID*.mp3 files need to be auditioned to assign them to events before wiring begins.
📋

Open Items

Item Notes Status
Shield scale per ship Tuning only; Shield Scale inspector group already wired Tuning
Collision polygons 18 polygons need geometry authored to match each ship hull Pending
Thruster / gun barrel positions Per-ship offset tuning in editor Pending
BGS rocket mechanic / Viper level-up benefit Gameplay feature, not yet designed Pending
Mobile joystick wiring Input layer not yet implemented Pending
Sound effects: 17 events unwired Audit complete. 6 PTModelSound files need auditioning before assignment. Main menu music asset exists unwired. Pending
Enemy movement to _physics_process Currently in _process; should move for consistent collision Pending
Coin shop / IAP / AdMob / GDPR / leaderboards Monetisation layer Pending
DEBUG_SHIP_SCALE = false Must be set before shipping Before ship
Remove DEBUG_AddCoins button Must be removed before shipping Before ship