Dev Log โ 2 May 2026
Session Overview
Big session: enemy ships, endless difficulty scaling, controller support, and several fixes. The full enemy ship system was built from scratch, starting with enemy units that spawn from level 4, a mothership from level 21, animated bullets for both, saucer-style spin, and sine-wave drift movement. Endless mode difficulty scaling was wired up with a virtual level system and sector banners, full controller support added with analog stick rotation, and a handful of smaller issues fixed including the endless mode blank background and enemy ship sizes.
- New Enemy unit ship: spawns from level 4, saucer spin, sine-wave movement, fires animated bullets
- New Enemy mothership: spawns from level 21, hovers and orbits, fires big bullets, spawns units
- New Animated enemy bullets: 6-frame small and big variants
- New Endless mode difficulty scaling: virtual level, score multiplier, sector banner
- New Controller support: left stick rotate, right stick thrust, triggers fire, Y nuke, Start pause
- Fix Endless mode blank background: random background chosen on each run
- Fix Endless mode negative asteroid bonus at level 0
- Change Enemy ship sizes increased to match player ship scale
- Change Enemy bullet sizes increased: small 0.5โ1.0, big 0.7โ1.2
- Fix Game Over screen: WindowWhole background panel added behind labels and buttons
Enemy Ship System: 6 New Files
Architecture
All enemies are Area2D nodes, which gives full position control in _process without physics engine interference.
| Node | collision_layer | collision_mask | Detects |
|---|---|---|---|
| Player bullet | 4 | 10 | Asteroids (2) + Enemies (8) |
| Enemy unit / mothership | 8 | 5 | Player body (1) + Player bullets (4) |
| Enemy bullet | 16 | 1 | Player body (1) |
Enemy Unit
Spawns from level 4. The sprite child rotates independently at 40ยฐ/s (saucer effect) without affecting the root node’s movement direction. Movement is sine-wave weave: a perpendicular oscillation added to the base direction vector gives a spaceship-feel drift rather than straight asteroid-style movement. Two states: Patrol (random screen targets) and Attack (faster, higher fire rate when player within 520px).
- HP: 1, Points: 100
- Fire interval:
max(2.0, 4.0 โ (level โ 4) ร 0.06), gets faster each level - Count per wave:
clamp(1 + floor((levelโ4)/4) + wave_index, 0, 8) - Rendered size: 79 px, matching the player ship (116 px asset at scale 0.68)
Enemy Mothership
Spawns from level 21, final wave only. Slower spin (15ยฐ/s). Approaches a fixed hover point in the upper quarter of the screen, then enters a small circular orbit (45 px radius). Fires big animated bullets at the player and periodically deploys new enemy units via a unit_spawn_requested signal connected to GameScene. GameScene increments enemies_alive for each spawned unit so the wave won’t end until they’re all cleared.
- HP: 3, Points: 300, Hit flash: red modulate tween
- 2 motherships from level 41+
- Rendered size: 150 px, clearly larger than the player (230 px asset at scale 0.65)
Animated Enemy Bullets
6-frame manual animation at 12 fps, same pattern as player thrusters. Small bullets for units, big bullets for mothership. Direction set via a launch(direction) method called after add_child, sets velocity and orients the sprite toward the direction of travel.
Wave Completion
The wave completion check now requires both asteroids_alive <= 0 AND enemies_alive <= 0. This applies in _on_asteroid_destroyed, _on_enemy_destroyed, and _remove_offscreen_asteroids.
Bullet.tscncollision_mask was widened from 2 to 10 (adds enemy layer 8) so player bullets detect enemy Area2D nodes via _on_area_entered. The existing _on_body_entered enemies check remains for any future physics-body enemies. Endless Mode Difficulty Scaling: Virtual Level System
The Problem
Endless mode set current_level = 0 and looped waves forever at the same difficulty. Worse, level = 0 caused a negative asteroid bonus (floori((0โ1)/3) = โ1), so endless mode actually had fewer asteroids than level 1.
Virtual Level System
An _endless_level integer starts at 1 and increments every 3 waves. A single helper function routes all difficulty math through it:
func _effective_level() -> int:
return _endless_level if _gm.current_level == 0 else _gm.current_level
This is swapped into _get_wave_composition(), _spawn_interval(), and _spawn_enemies_for_wave(), so all existing level-based scaling applies automatically in endless mode with no duplicated logic.
| Waves Completed | _endless_level | What Changes |
|---|---|---|
| 1โ3 | 1 | Normal level-1 difficulty |
| 4โ6 | 2 | More asteroids, faster spawns |
| 10โ12 | 4 | Enemy units begin spawning |
| 61โ63 | 21 | Mothership appears |
| 121โ123 | 41 | Two motherships per wave |
Score Multiplier
Each time _endless_level increases, the score multiplier steps up by 0.1ร (capped at 3.0ร). All kill points are multiplied before being passed to GameManager.add_score(). In normal levelled mode the multiplier stays at 1.0ร, so it has no effect outside endless.
Sector Banner
When the virtual level increases, a purple banner flashes on screen showing the new sector number and current score multiplier. It holds for 1.2 s, fades out, then hands off to the existing wave warning. The purple colour is intentionally distinct from the red wave warning so the player can tell the difference at a glance.
Controller Support: Analog Rotation
Controller bindings were added to all input actions in project.godot. The layout uses both sticks for movement so thumbs never leave the sticks during play, and triggers handle firing with no conflict.
| Action | Controller Binding | Fallback |
|---|---|---|
| Rotate left | Left stick โ | D-pad โ |
| Rotate right | Left stick โ | D-pad โ |
| Thrust | Right stick โ | D-pad โ |
| Fire | Left trigger | Right trigger |
| Nuke | Y / Triangle | โ |
| Pause | Start / Menu | โ |
Analog Rotation
_handle_rotation() was updated to use Input.get_axis() instead of two is_action_pressed() checks. This returns a float from โ1.0 to +1.0 so gently pushing the stick gives slow rotation and fully pushing gives full speed. Keyboard behaviour is unchanged; it still snaps to โ1/0/1.
# Before โ digital only, no analog feel
var dir: float = 0.0
if Input.is_action_pressed("rotate_left"): dir -= 1.0
if Input.is_action_pressed("rotate_right"): dir += 1.0
# After โ smooth proportional rotation from analog stick
var dir: float = Input.get_axis("rotate_left", "rotate_right")
Endless Mode: No Background Fix
Bug Endless mode sets current_level = 0. The background loader had an early-return guard at level < 1, so endless mode always loaded a blank screen. A _BG_PATHS constant (all 13 backgrounds) and a level-0 branch that picks one at random on each run was added. Every endless session now starts with a different background including planet float animation.
# Before: level = 0 โ guard fires โ blank screen
# After: level = 0 โ randi() % 13 โ random BG_01โBG_13
Files Changed on 2 May 2026
| File | Change |
|---|---|
| scripts/entities/EnemyBullet.gd | New 6-frame animated bullet, launch(direction) method, body_entered โ player.take_damage() |
| scenes/entities/EnemyBullet.tscn | New Area2D, collision_layer=16, collision_mask=1 |
| scripts/entities/EnemyUnit.gd | New Saucer spin, sine-wave weave, patrol/attack states, 1 HP, 100 pts |
| scenes/entities/EnemyUnit.tscn | New Area2D, collision_layer=8, collision_mask=5 |
| scripts/entities/EnemyMothership.gd | New Hover orbit, unit spawning, big bullets, 3 HP, 300 pts |
| scenes/entities/EnemyMothership.tscn | New Area2D, collision_layer=8, collision_mask=5 |
| scripts/entities/Bullet.gd | Change _on_area_entered now handles enemies group |
| scenes/entities/Bullet.tscn | Change collision_mask widened from 2 to 10 to detect enemy layer |
| scripts/entities/Player.gd | Change _handle_rotation uses get_axis() for analog stick support |
| scripts/game/GameScene.gd | Change Enemy system, endless scaling, _effective_level(), sector banner, enemies_alive tracking, _BG_PATHS random pick for endless |
| project.godot | New Controller bindings added to all 5 input actions |
| scripts/entities/EnemyUnit.gd | Change Sprite scale 0.24โ0.68 (79 px rendered) |
| scripts/entities/EnemyMothership.gd | Change Sprite scale 0.36โ0.65 (150 px rendered) |
| scripts/entities/EnemyBullet.gd | Change Bullet scale increased: small 0.5โ1.0, big 0.7โ1.2 |
| scenes/ui/HUD.tscn | Fix WindowWhole TextureRect added as first child of GameOverPanel, so the background now shows behind all labels and buttons |
Open Items
| Item | Priority | Notes |
|---|---|---|
| Mobile joystick wiring | Medium | Not connected to player movement |
| Add “Music” and “SFX” audio buses | Medium | Required to activate Settings mute toggles |
| Planet positions BG_02โBG_13 | Medium | Editor adjustment needed per background scene |
| Level Complete visual polish | Medium | Polish pass still needed |
| Wave warning text | Low | Still says “Asteroids Incoming” on enemy-only waves |
| Enemy ship system playtest | Low | Untested in-engine; needs a full playtest |
| Coin shop / IAP | Low | Not started |
| AdMob, Vungle, AppLovin | Low | SDK wiring pending |
| GDPR consent | Low | Required before store submission |
| Google Play Games leaderboards | Low | Not started |
| Android / iOS export signing | Low | Export templates not configured |