Added Glitch Room Source code to Gitty

This commit is contained in:
Jeff Luke committed 2026-10-04 11:31:17 -07:00
commit 1ac9eb5644
505 files changed
+28504

No files matched your search

+523
View File
@@ -0,0 +1,523 @@
@tool
extends ArcadeGame
const ACTOR = preload("res://games/galaga/actors/actor.gd")
const FIGHTER = preload("res://games/galaga/actors/Fighter.tscn")
const ENEMY = preload("res://games/galaga/actors/Enemy.tscn")
const PROJECTILE = preload("res://games/galaga/actors/Projectile.tscn")
const EXPLOSION = preload("res://games/galaga/actors/Explosion.tscn")
@export_group("Room Objective")
@export_range(0,100,1) var stages_to_win := 1
@export_range(0,1000000,100) var score_to_win := 0
@export_range(1,9,1) var starting_lives := 3
@export var extra_life_score := 20000
@export var second_extra_life_score := 70000
@export var repeat_extra_life_interval := 70000
var next_extra_life := 20000
var stage := 1
var score := 0
var lives := 3
var shots_fired := 0
var hits := 0
var state := "idle"
var timer := 0.0
var clock := 0.0
var cooldown := 0.0
var dive_timer := 2.0
var invulnerable := 0.0
var awarded := false
var player: Node2D
var enemies: Array[Dictionary] = []
var bullets: Array[Dictionary] = []
var effects: Array[Dictionary] = []
var voices: Array[AudioStreamPlayer] = []
var streams := {}
var field: Node2D
var hud: Node2D
var rng := RandomNumberGenerator.new()
@export_group("Opening Timing")
@export var intro_seconds := 6.0
@export var stage_card_seconds := 1.0
@export var player_card_seconds := 1.0
@export_group("Cabinet")
@export var credits := 1
var wingman: Node2D
var captive: Node2D
var captor: Dictionary = {}
var dual := false
var capture_origin := Vector2.ZERO
var capture_cooldown := 8.0
var beam: Dictionary = {}
func _ready() -> void:
pass
field = preload("res://games/galaga/galaga_view.gd").new()
add_child(field)
player = FIGHTER.instantiate()
field.add_child(player)
player.position = Vector2(112,256)
wingman = FIGHTER.instantiate()
field.add_child(wingman)
wingman.visible = false
captive = FIGHTER.instantiate()
field.add_child(captive)
captive.visible = false
captive.spinning = false
captive.modulate = Color(1,0.4,0.4)
_spawn_wave(true)
if not Engine.is_editor_hint():
for i in range(8):
var voice := AudioStreamPlayer.new()
add_child(voice)
voices.append(voice)
for name in ["start","fire","dive","explosion","boss_damage","boss_destroy","goei_destroy","zako_destroy","extend","stage_count","tractor_beam","capturing","fighter_captured","fighter_rescued","captured_ship_destroy"]:
streams[name] = load("res://games/galaga/audio/%s.wav" % name)
func start_game() -> bool:
if is_running or Engine.is_editor_hint(): return false
_clear_actors()
score = 0
stage = 1
lives = clampi(int(run_config.get("starting_lives",starting_lives)),1,9)
shots_fired = 0
hits = 0
awarded = false
next_extra_life = extra_life_score
clock = 0
credits = maxi(0,credits-1)
cooldown = 0
invulnerable = 0
player.position = Vector2(112,256)
player.visible = false
state = "intro"
timer = intro_seconds
dual = false
wingman.visible = false
captive.visible = false
captive.spinning = false
captor = {}
beam = {}
capture_cooldown = 8.0
_spawn_wave()
_sound("start")
return super.start_game()
func _exit_tree() -> void:
for voice in voices:
voice.stop()
voice.stream = null
func reset_game() -> void:
_clear_actors()
for voice in voices: voice.stop()
state = "idle"
dual = false
beam = {}
captor = {}
captive.visible = false
wingman.visible = false
player.visible = true
player.position = Vector2(112,256)
_spawn_wave(true)
super.reset_game()
func win_game() -> void:
if not is_running or state=="victory": return
for voice in voices: voice.stop()
state = "victory"
timer = 1.5
beam = {}
func lose_game() -> void:
if not is_running: return
for voice in voices: voice.stop()
state = "lost"
super.lose_game()
func _clear_actors() -> void:
for collection in [enemies,bullets,effects]:
for item in collection: item.node.queue_free()
collection.clear()
func _spawn_wave(preview := false) -> void:
dive_timer = 2.0
for row in range(5):
var count := 4 if row == 0 else (8 if row < 3 else 10)
for col in range(count):
var node = ENEMY.instantiate()
node.kind = "boss" if row == 0 else ("butterfly" if row < 3 else "bee")
field.add_child(node)
var home := Vector2(112 + (col - (count-1)/2.0)*18,48+row*18)
node.position = home if preview else Vector2(-24,-24)
node.visible = preview
var index := enemies.size()
enemies.append({"node":node,"home":home,"mode":"formation" if preview else "enter","time":-(index/8)*1.1-(index%8)*0.075,"side":-1.0 if (index/8)%2==0 else 1.0,"hp":2 if row==0 else 1,"origin":Vector2.ZERO,"target":112.0,"fired":false})
func _input(event: InputEvent) -> void:
if Engine.is_editor_hint(): return
if event.is_action_pressed("arcade_start"):
start_game()
func _held(action: String) -> bool:
return Input.is_action_pressed(action)
func _physics_process(dt: float) -> void:
if Engine.is_editor_hint(): return
if state=="won":
clock += dt
return
if not is_running: return
_tick(dt)
func _tick(dt: float) -> void:
clock += dt
for i in range(effects.size()-1,-1,-1):
var effect: Dictionary = effects[i]
effect.node.animation_time += dt
if effect.node.animation_time >= 0.34:
effect.node.queue_free()
effects.remove_at(i)
if state != "playing":
timer -= dt
if state=="victory":
_update_peaceful_formation()
for enemy in enemies:
if enemy.mode!="formation" and enemy.node.visible:
enemy.node.position += enemy.node.heading*70*dt
for i in range(bullets.size()-1,-1,-1):
var bullet: Dictionary = bullets[i]
bullet.node.position += bullet.velocity*dt
if not Rect2(-16,-16,256,320).has_point(bullet.node.position):
bullet.node.queue_free()
bullets.remove_at(i)
if timer<=0:
_clear_bullets()
state="won"
super.win_game()
return
if state == "capturing":
var t := clampf(1-timer/1.5,0,1)
player.position = capture_origin.lerp(captor.node.position-Vector2(0,16),t)
if timer<=0: _finish_capture()
return
if state in ["captured_return", "capture_ready", "rescue_spin", "rescue_center", "rescue_align", "rescue_descend"]:
_update_peaceful_formation()
match state:
"captured_return":
captor.node.position = captor.node.position.move_toward(_formation_home(captor),100*dt)
captive.position = captor.node.position-Vector2(0,16)
if timer<=0 and captor.node.position.distance_to(_formation_home(captor))<1:
captor.mode="formation"
if lives<=0:
lose_game()
else:
player.position=Vector2(112,256)
player.visible=true
state="capture_ready"
timer=1.5
"capture_ready":
if timer<=0: _resume_action()
"rescue_spin":
captive.spin_time += dt
if timer<=0:
captive.spinning=false
state="rescue_center"
"rescue_center":
player.position = player.position.move_toward(Vector2(104,256),100*dt)
if player.position.distance_to(Vector2(104,256))<0.1: state="rescue_align"
"rescue_align":
captive.position.x = move_toward(captive.position.x,120,100*dt)
if absf(captive.position.x-120)<0.1: state="rescue_descend"
"rescue_descend":
captive.position = captive.position.move_toward(Vector2(120,256),110*dt)
if captive.position.distance_to(Vector2(120,256))<0.1:
dual=true
captive.visible=false
wingman.position=Vector2(120,256)
wingman.visible=true
_resume_action()
return
if timer <= 0:
if state == "intro":
state = "stage_card"
timer = stage_card_seconds
_sound("stage_count")
elif state == "stage_card":
state = "ready"
timer = player_card_seconds
player.visible = true
elif state == "ready": state = "playing"
elif state == "death":
if lives <= 0: lose_game()
else:
player.position = Vector2(112,256)
player.visible = true
invulnerable = 2.5
state = "playing"
elif state == "clear":
stage += 1
_spawn_wave()
state = "stage_card"
player.visible = false
wingman.visible = false
timer = stage_card_seconds
_sound("stage_count")
return
cooldown = maxf(0,cooldown-dt)
invulnerable = maxf(0,invulnerable-dt)
player.visible = invulnerable == 0 or int(clock*12)%2==0
var movement := float(_held("arcade_right"))-float(_held("arcade_left"))
player.position.x = clampf(player.position.x+movement*110*dt,8,216)
if dual: player.position.x = minf(player.position.x,200)
wingman.position = player.position+Vector2(16,0)
wingman.visible = dual and player.visible
if _held("arcade_action_1"): _fire()
_update_enemies(dt)
if state != "playing": return
_update_bullets(dt)
if state != "playing": return
for enemy in enemies:
if (enemy.node.position.distance_to(player.position) < 11 or (dual and enemy.node.position.distance_to(wingman.position)<11)) and invulnerable <= 0:
_die()
return
if enemies.is_empty():
var target := int(run_config.get("stages_to_win", stages_to_win))
if target > 0 and stage >= target: win_game()
else:
state = "clear"
timer = 2
_clear_bullets()
func _update_enemies(dt: float) -> void:
dive_timer -= dt
capture_cooldown -= dt
var entering := false
for e in enemies:
if e.mode=="enter": entering=true
for enemy in enemies:
var previous_position: Vector2 = enemy.node.position
enemy.time += dt
var expansion := 1.0+0.10*(0.5+0.5*sin(clock*1.7))
var home: Vector2 = _formation_home(enemy)
match enemy.mode:
"enter":
if enemy.time < 0:
enemy.node.visible = false
continue
enemy.node.visible = true
var t := clampf(enemy.time/2.0,0,1)
var start := Vector2(112+enemy.side*140,160)
if t<0.8:
# Every member samples one shared route; its slot affects only docking.
var u := t/0.8
enemy.node.position = start.lerp(Vector2(112,90),u) + Vector2(-enemy.side*sin(u*TAU)*72,-sin(u*PI)*105)
else:
enemy.node.position = Vector2(112,90).lerp(home,(t-0.8)/0.2)
if t>=1: enemy.mode = "formation"
"formation":
enemy.node.position = home
if not entering and capture_cooldown<=0 and enemy.node.kind=="boss" and captor.is_empty() and beam.is_empty() and not dual:
enemy.mode="beam_approach"
enemy.origin=home
enemy.target=player.position.x
enemy.time=0
beam=enemy
capture_cooldown=15
elif dive_timer <= 0 and not entering:
enemy.mode = "dive"
enemy.time = 0
enemy.origin = home
enemy.target = player.position.x
enemy.fired = false
dive_timer = maxf(0.45,2.0-stage*0.12)
_sound("dive")
"dive":
var t: float = enemy.time/(3.0/maxf(1,1+stage*0.04))
enemy.node.position = Vector2(lerpf(enemy.origin.x,enemy.target,minf(t,1))+sin(t*TAU)*25,enemy.origin.y+t*250)
if t>0.35 and not enemy.fired:
enemy.fired = true
_bullet(enemy.node.position,(player.position-enemy.node.position).normalized()*95,false)
if enemy.node.position.y>310:
enemy.mode = "return"
enemy.time = 0
"return":
var t := minf(1,enemy.time/1.0)
enemy.node.position = Vector2(home.x,lerpf(-20,home.y,t))
if t>=1: enemy.mode = "formation"
"beam_approach":
var t := minf(enemy.time/1.3,1)
enemy.node.position=enemy.origin.lerp(Vector2(enemy.target,172),t)
if t>=1:
enemy.mode="beam"
enemy.time=0
_sound("tractor_beam")
"beam":
if invulnerable<=0 and absf(player.position.x-enemy.node.position.x)<18:
captor=enemy
capture_origin=player.position
state="capturing"
timer=1.5
_clear_bullets()
_stop_sounds()
_sound("capturing")
return
if enemy.time>=3:
enemy.mode="return"
enemy.time=0
beam={}
enemy.node.animation_time += dt
if enemy.mode in ["dive","enter","return"]:
var travel: Vector2 = enemy.node.position-previous_position
if travel.length_squared()>0.01: enemy.node.heading = travel.normalized()
else: enemy.node.heading = Vector2.DOWN
if not captor.is_empty() and captive.visible:
captive.position=captor.node.position-Vector2(0,16)
func _fire() -> void:
if cooldown>0: return
var count := 0
for bullet in bullets:
if bullet.friendly: count += 1
if count>=(4 if dual else 2): return
cooldown = 0.18
shots_fired += 1
_bullet(player.position-Vector2(0,9),Vector2(0,-230),true)
if dual: _bullet(wingman.position-Vector2(0,9),Vector2(0,-230),true)
_sound("fire")
func _bullet(at: Vector2, velocity: Vector2, friendly: bool) -> void:
var node = PROJECTILE.instantiate()
node.kind = "shot" if friendly else "enemy_shot"
node.position = at
field.add_child(node)
bullets.append({"node":node,"velocity":velocity,"friendly":friendly})
func _update_bullets(dt: float) -> void:
for i in range(bullets.size()-1,-1,-1):
var bullet: Dictionary = bullets[i]
bullet.node.position += bullet.velocity*dt
var remove: bool = bullet.node.position.y < -8 or bullet.node.position.y>296
if bullet.friendly:
if captive.visible and state=="playing" and bullet.node.position.distance_to(captive.position)<8:
captive.visible=false
captor={}
_sound("captured_ship_destroy")
bullet.node.queue_free()
bullets.remove_at(i)
continue
for j in range(enemies.size()-1,-1,-1):
var enemy: Dictionary = enemies[j]
if bullet.node.position.distance_to(enemy.node.position)>9: continue
hits += 1
enemy.hp -= 1
remove = true
if enemy.hp <= 0:
var kind: String = enemy.node.kind
var points: int = {"boss":150,"butterfly":80,"bee":50}[kind]
if enemy.mode == "dive": points *= 2
_explode(enemy.node.position,true)
if enemy==beam: beam={}
if enemy==captor:
captor={}
state="rescue_spin"
timer=1.5
captive.spinning=true
captive.spin_time=0
_stop_sounds()
_sound("fighter_rescued")
enemy.node.queue_free()
enemies.remove_at(j)
_sound({"boss":"boss_destroy","butterfly":"goei_destroy","bee":"zako_destroy"}[kind])
_add_score(points)
else:
enemy.node.damaged = true
_sound("boss_damage")
break
elif invulnerable<=0 and (bullet.node.position.distance_to(player.position)<8 or (dual and bullet.node.position.distance_to(wingman.position)<8)):
_die()
return
if remove:
bullet.node.queue_free()
bullets.remove_at(i)
if state != "playing": return
func _add_score(points: int) -> void:
score += points
while next_extra_life>0 and score>=next_extra_life:
var first := not awarded
awarded = true
lives += 1
_sound("extend")
if first and second_extra_life_score>next_extra_life:
next_extra_life = second_extra_life_score
elif repeat_extra_life_interval>0:
next_extra_life += repeat_extra_life_interval
else: next_extra_life = 0
var target := int(run_config.get("score_to_win",score_to_win))
if target>0 and score>=target: win_game()
func _clear_bullets() -> void:
for bullet in bullets: bullet.node.queue_free()
bullets.clear()
func _die() -> void:
if dual:
dual=false
_explode(wingman.position)
wingman.visible=false
invulnerable=2.5
_sound("explosion")
return
_explode(player.position)
player.visible = false
lives -= 1
state = "death"
timer = 1.5
_clear_bullets()
_sound("explosion")
func _explode(at: Vector2, enemy := false) -> void:
var node = EXPLOSION.instantiate()
if enemy: node.kind="enemy_explosion"
node.position = at
field.add_child(node)
effects.append({"node":node})
func _finish_capture() -> void:
beam={}
lives-=1
captive.visible=true
captive.position=player.position
player.visible=false
captor.mode="captured_return"
captor.time=0
_stop_sounds()
state="captured_return"
_sound("fighter_captured")
timer=1.5
func _sound(name: String) -> void:
if state=="capturing" and name!="capturing": return
if state.begins_with("rescue_") and name!="fighter_rescued": return
if not streams.has(name): return
for voice in voices:
if not voice.playing:
voice.stream = streams[name]
voice.play()
return
func _stop_sounds() -> void:
for voice in voices: voice.stop()
func _formation_home(enemy: Dictionary) -> Vector2:
var expansion := 1.0+0.10*(0.5+0.5*sin(clock*1.7))
return Vector2(112,48)+(enemy.home-Vector2(112,48))*expansion
func _update_peaceful_formation() -> void:
for enemy in enemies:
if enemy.mode=="formation": enemy.node.position=_formation_home(enemy)
func _resume_action() -> void:
state="playing"
invulnerable=2.5
dive_timer=2.0
_clear_bullets()