Dev Log โ 13 April 2026
Session Overview
I focused this session on clearing the carry-over bugs from Session 1, adding the thruster VFX system, and writing the first dev log. The game is now properly playable. The player takes damage, bounces off asteroids, rockets explode on impact, and thrusters animate correctly per ship type.
Fixed Today
- โ Player losing lives on asteroid collision
- โ Player bounce-off instead of stuck
- โ Asteroid collision shapes fitting sprites
- โ Bullet explosion animation (frames 1โ9)
- โ Health HUD: single swap-image system
- โ Asset paths โ Game Assests folder
New Today
- โจ Thruster animation (7 frames, 16fps)
- โจ Per-ship thruster layout (center/sides/both)
- โจ Explosion VFX on bullet impact
- โจ Dev Log system started
- ๐ Dev_Log_120426.html (yesterday retrospective)
- ๐ Dev_Log_130426.html (this file)
Collision System Rework
Root Cause: One Size Fits None
The asteroid collision shape was a single CircleShape2D with radius = 210 in local space. After node scale is applied, this becomes:
MEDIUM and SMALL asteroids had invisible collision halos extending well beyond their sprite, which let the player “collide with empty space” and get trapped.
Dynamic Radius from Texture Size
_fit_collision_shape() is called in _ready() after textures load. It derives the collision radius from the actual loaded image dimensions:
func _fit_collision_shape() -> void:
var img_size: Vector2 = _textures[0].get_size()
var radius: float = min(img_size.x, img_size.y) * 0.42
var shape := CircleShape2D.new()
shape.radius = radius # local space โ scales with node
collision_shape.shape = shape
Player Bounce Fix
Switched from move_and_collide() to move_and_slide(). This handles multiple simultaneous collisions per frame (asteroid clusters), preventing the “stuck between two rocks” scenario. On asteroid hit, velocity is reflected off the collision normal with 0.6ร damping:
func _move() -> void:
move_and_slide() # handles multi-collision clusters
for i in get_slide_collision_count():
var c := get_slide_collision(i)
var collider := c.get_collider()
if collider and collider.is_in_group("asteroids"):
velocity = velocity.bounce(c.get_normal()) * 0.6 # bounce + dampen
take_damage()
break
Explosion VFX System
New scenes/effects/Explosion.tscn + scripts/effects/Explosion.gd. Cycles through 9 frames at ~18fps, then self-destructs. Spawned at bullet world position on impact.
The explosion is added to get_tree().current_scene rather than the bullet’s parent, so it persists for its full duration after the bullet is freed:
func _spawn_explosion() -> void:
var e: Node2D = _explosion_scene.instantiate()
get_tree().current_scene.add_child(e)
e.global_position = global_position # world position of impact
Thruster Animation System
Thrusters animate through 7 frames at 16fps when the thrust key is held. Each ship variant in a series has a different nozzle configuration. The pattern is consistent across all 3 ship series (CX16, DKO-api, WO84-wu):
X1: Center Only
Ships: CX16-X1, DKO-api-X1, WO84-wu-X1
Single center nozzle at bottom of fuselage. Position: (0, 53)
X2: Sides Only
Ships: CX16-X2, DKO-api-X2, WO84-wu-X2
Two wing pod nozzles. Positions: (-20, 40) and (20, 40)
X3: Center + Sides
Ships: CX16-X3, DKO-api-X3, WO84-wu-X3
All three nozzles active simultaneously.
Ship Variant Detection
The thruster config is derived from selected_ship % 3. This works because all 3 ship series follow the same X1/X2/X3 pattern:
var variant: int = gm.selected_ship % 3
# 0 โ center only (X1) 1 โ sides only (X2) 2 โ center + sides (X3)
_use_center = (variant == 0 or variant == 2)
_use_sides = (variant == 1 or variant == 2)
Thruster Nodes in Player.tscn
Animation Loop
func _animate_thrusters(delta: float) -> void:
if not is_thrusting or _thruster_textures.is_empty():
return
_thruster_timer += delta
if _thruster_timer >= THRUSTER_FRAME_DURATION: # 0.06s
_thruster_timer -= THRUSTER_FRAME_DURATION
_thruster_frame = (_thruster_frame + 1) % 7
var tex := _thruster_textures[_thruster_frame]
if _use_center: thruster_center.texture = tex
if _use_sides: thruster_left.texture = tex
thruster_right.texture = tex
Files Changed Today
| File | Change Type | Summary |
|---|---|---|
scripts/entities/Player.gd |
Rework | Thruster animation system, move_and_slide bounce, per-ship thruster config |
scenes/entities/Player.tscn |
Updated | Added ThrusterCenter/Left/Right Sprite2D nodes, removed GPUParticles2D |
scripts/entities/Asteroid.gd |
Fix | Dynamic collision radius from texture size, size-grouped variant system |
scripts/ui/HUD.gd |
Fix | Single health image swap, new asset paths, runtime GameManager ref |
scenes/ui/HUD.tscn |
Rework | 3 heart nodes โ single HealthDisplay TextureRect, new joystick asset paths |
scripts/effects/Explosion.gd |
New | 9-frame explosion animation, auto queue_free on completion |
scenes/effects/Explosion.tscn |
New | Explosion Node2D + Sprite2D |
scripts/entities/Bullet.gd |
Updated | Spawns Explosion on impact, explosion added to scene root |
Dev Log/Dev_Log_120426.html |
New | Session 1 retrospective |
Dev Log/Dev_Log_130426.html |
New | This file |
Asteroid Bounce Rework (later same day)
The initial bounce using velocity.bounce(collision_normal) felt wrong and was still causing sticking. The root cause: physics collision normals are unreliable when collision shapes don’t perfectly match the sprite. I replaced it with a centre-to-centre push that matches the wall-bounce behaviour exactly.
Why Collision Normals Fail Here
The Fix: Centre-to-Centre Push
func _bounce_off_body(body: Object) -> void:
# Direction from body centre โ player centre (always correct regardless of shape)
var push_dir := (global_position - body.global_position).normalized()
if push_dir == Vector2.ZERO:
push_dir = Vector2.RIGHT # exact overlap fallback
# Preserve speed; guarantee minimum 200 px/s exit velocity
var speed := maxf(velocity.length(), 200.0)
velocity = push_dir * speed
# Physical separation โ moves player out of collision zone before next frame
global_position += push_dir * 6.0
Also extended to enemies group, so the player now bounces off enemy ships the same way.
Open Items / Next Session
scenes/menus/LevelSelect.tscn which doesn’t exist yet