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

+429
View File
@@ -0,0 +1,429 @@
@tool
extends ArcadeGame
@export_range(0,99) var waves_to_win: int = 1
@export_range(1,9) var starting_lives: int = 3
@export_range(.5,2,.1) var difficulty: float = 1.0
@export_range(0,100000,5000) var extra_life_interval: int = 25000
@export var sound_enabled: bool = true
@export var crt_aspect: bool = true
@export var reduced_flashes: bool = false
const ARENA := Rect2(8,20,276,208)
const POINTS := {"grunt":100,"hulk":0,"spheroid":1000,"enforcer":150,"quark":1000,"tank":200,"brain":500,"prog":100,"electrode":0}
var phase := "title"
var phase_time := 0.0
var clock := 0.0
var demo := false
var demo_time := 0.0
var score := 0
var high_score := 0
var last_score := 0
var lives := 3
var wave := 1
var rescue_chain := 0
var next_extra := 25000
var player := Vector2(146,124)
var facing := Vector2.DOWN
var moving := false
var fire_delay := 0.0
var invulnerable := 0.0
var grunt_delay := 0.0
var entities: Array[Dictionary] = []
var shots: Array[Dictionary] = []
var enemy_shots: Array[Dictionary] = []
var effects: Array[Dictionary] = []
var rng := RandomNumberGenerator.new()
var next_id := 0
@onready var board: Node2D = $PixelScreen/Board
func _ready() -> void:
lives = starting_lives
rng.seed = 2084
setup_wave()
if not Engine.is_editor_hint():
$Audio.effect("startup")
func _unhandled_input(event: InputEvent) -> void:
if Engine.is_editor_hint() or event.is_echo(): return
if event.is_action_pressed("arcade_start"):
start_game()
func eight_way(vector: Vector2) -> Vector2:
if vector.length()<.25: return Vector2.ZERO
return Vector2.from_angle(roundf(vector.angle()/(PI/4))*(PI/4)).normalized()
func control_vector(prefix: String) -> Vector2:
return eight_way(Input.get_vector("robotron_"+prefix+"left","robotron_"+prefix+"right","robotron_"+prefix+"up","robotron_"+prefix+"down"))
func start_game() -> bool:
if is_running: return false
demo = false
score = 0
lives = starting_lives
wave = 1
next_extra = extra_life_interval
rng.randomize()
setup_wave()
phase = "ready"
phase_time = 2.0
$Audio.stop_all()
$Audio.effect("game-start")
return super.start_game()
func reset_game() -> void:
demo = false
score = 0
last_score = 0
wave = 1
lives = starting_lives
setup_wave()
phase = "title"
phase_time = 0
$Audio.stop_all()
super.reset_game()
func spawn(kind: String, position: Vector2) -> Dictionary:
next_id += 1
var entity := {"id":next_id,"kind":kind,"position":position,"velocity":Vector2.from_angle(rng.randf()*TAU),"timer":rng.randf_range(.6,2.0),"age":0.0,"frame":rng.randi_range(0,2),"alive":true,"spawns":0,"human_type":rng.randi_range(0,2),"hp":3 if kind=="brain" else 1}
entities.append(entity)
return entity
func spawn_point() -> Vector2:
for attempt in range(80):
var p := Vector2(rng.randf_range(15,277),rng.randf_range(28,220))
if p.distance_to(player)<=48: continue
var occupied := false
for e in entities:
if e.alive and p.distance_squared_to(e.position)<100:
occupied = true
break
if not occupied: return p
return Vector2(20,30)
func setup_wave() -> void:
entities.clear()
shots.clear()
enemy_shots.clear()
effects.clear()
player = Vector2(146,124)
facing = Vector2.DOWN
rescue_chain = 0
fire_delay = 0
invulnerable = 1.5
var brain_wave := wave%5==0
var tank_wave := wave>=7 and wave%5==2
for i in range(mini(15+wave*3,65)): spawn("grunt",spawn_point())
for i in range(5+mini(wave/3,5)): spawn("hulk",spawn_point())
for i in range(15 if not brain_wave else 5): spawn("electrode",spawn_point())
for i in range(20 if brain_wave else 3+mini(wave,5)): spawn("human",spawn_point())
if brain_wave:
for i in range(8+mini(wave,12)): spawn("brain",spawn_point())
elif tank_wave:
for i in range(5+mini(wave/5,5)): spawn("quark",spawn_point())
elif wave>=2:
for i in range(mini(wave,8)): spawn("spheroid",spawn_point())
func _physics_process(delta: float) -> void:
clock += delta
if Engine.is_editor_hint():
phase_time = fmod(clock,12)
board.queue_redraw()
return
if phase in ["title","story","roster","scores"]:
phase_time += delta
if phase_time>12:
phase_time = 0
match phase:
"title": phase = "story"
"story": phase = "roster"
"roster": begin_demo()
"scores": phase = "title"
elif phase in ["ready","dead","transition","lost"]:
phase_time -= delta
if phase_time<=0:
match phase:
"ready": phase = "play"
"dead":
if lives<=0: finish(false)
else: respawn()
"transition":
if not demo and waves_to_win>0 and wave>=waves_to_win: finish(true)
else:
wave += 1
setup_wave()
phase = "ready"
phase_time = 1.5
if wave%5==0: $Audio.effect("brain-wave-start")
"lost":
phase = "scores"
phase_time = 0
elif phase == "play":
var movement := Vector2.ZERO
var firing := Vector2.ZERO
if demo:
var target := Vector2(146+cos(demo_time*.8)*88,124+sin(demo_time*.8)*70)
movement = eight_way(target-player)
var enemy := nearest(player,false)
if not enemy.is_empty(): firing = eight_way(enemy.position-player)
else:
movement = control_vector("move_")
firing = control_vector("fire_")
step_world(delta,movement,firing)
if demo:
demo_time += delta
if demo_time>25: end_demo()
board.queue_redraw()
func begin_demo() -> void:
demo = true
demo_time = 0
score = 0
lives = 3
wave = 3
rng.seed = 2084
setup_wave()
phase = "play"
$Audio.stop_all()
func end_demo() -> void:
demo = false
score = last_score
phase = "scores"
phase_time = 0
$Audio.stop_all()
func clamp_arena(p: Vector2, margin: float = 4) -> Vector2:
return p.clamp(ARENA.position+Vector2.ONE*margin,ARENA.end-Vector2.ONE*margin)
func nearest(p: Vector2, human: bool) -> Dictionary:
var best: Dictionary = {}
var distance := INF
for e in entities:
if not e.alive or (e.kind=="human")!=human or e.kind in ["electrode","hulk"]: continue
var d: float = p.distance_squared_to(e.position)
if d<distance:
distance = d
best = e
return best
func fire(direction: Vector2) -> bool:
if phase!="play" or direction==Vector2.ZERO or fire_delay>0 or shots.size()>=4: return false
shots.append({"position":player+direction*5,"velocity":direction*260})
fire_delay = .085
$Audio.effect("player-lazer")
return true
func radius(kind: String) -> float:
return 7.0 if kind in ["brain","hulk","tank"] else (5.0 if kind in ["spheroid","quark"] else 3.5)
func projectile(kind: String, origin: Vector2, target: Vector2) -> void:
if enemy_shots.size()>=70: return
var velocity := (target-origin).normalized()*(90 if kind=="shell" else 65)
enemy_shots.append({"kind":kind,"position":origin,"velocity":velocity,"life":9.0,"bounces":0})
$Audio.effect("tank-fire" if kind=="shell" else "enforcer-fire")
func step_world(delta: float, movement: Vector2, firing: Vector2) -> void:
moving = movement!=Vector2.ZERO
if moving: facing = movement
player = clamp_arena(player+movement*86*delta)
invulnerable = maxf(0,invulnerable-delta)
fire_delay = maxf(0,fire_delay-delta)
grunt_delay -= delta
if grunt_delay<=0:
grunt_delay = .6
if entities.any(func(e): return e.alive and e.kind=="grunt"): $Audio.effect("grunt-walk")
fire(firing)
for i in range(effects.size()-1,-1,-1):
effects[i].life -= delta
if effects[i].life<=0: effects.remove_at(i)
# Spawners append enemies; the snapshot prevents updating new enemies twice.
for e in entities.duplicate():
if not e.alive: continue
e.age += delta
e.timer -= delta
var direction: Vector2 = (player-e.position).normalized()
match e.kind:
"human":
if e.timer<=0:
e.velocity = eight_way(Vector2(rng.randf_range(-1,1),rng.randf_range(-1,1)))
e.timer = rng.randf_range(.4,1.4)
e.position = clamp_arena(e.position+e.velocity*12*delta)
if player.distance_to(e.position)<7: save_human(e)
"grunt","prog":
e.position += eight_way(direction)*(32 if e.kind=="grunt" else 58)*difficulty*delta
if e.kind=="grunt":
for obstacle in entities:
if obstacle.alive and obstacle.kind=="electrode" and e.position.distance_to(obstacle.position)<6:
e.alive = false
obstacle.alive = false
burst(e.position)
break
"hulk","brain":
var human := nearest(e.position,true)
var destination: Vector2 = human.position if not human.is_empty() else player
if e.timer<=0:
e.velocity = eight_way(destination-e.position)
e.timer = .6 if e.kind=="hulk" else .2
e.position = clamp_arena(e.position+e.velocity*(17 if e.kind=="hulk" else 22)*difficulty*delta)
if not human.is_empty() and e.position.distance_to(human.position)<8:
human.alive = false
if e.kind=="brain":
var prog := spawn("converting",human.position)
prog.timer = .7
$Audio.effect("human-to-prog")
else:
burst(human.position)
$Audio.effect("human-die")
if e.kind=="brain" and int(e.age/2.2)!=int((e.age-delta)/2.2): projectile("cruise",e.position,player)
"converting":
if e.timer<=0:
e.kind = "prog"
$Audio.effect("prog-transformation")
"spheroid","quark":
var next: Vector2 = e.position+e.velocity*(45 if e.kind=="spheroid" else 28)*delta
if next.x<14 or next.x>278: e.velocity.x *= -1
if next.y<26 or next.y>222: e.velocity.y *= -1
e.position = clamp_arena(e.position+e.velocity*40*delta)
if e.timer<=0:
spawn("enforcer" if e.kind=="spheroid" else "tank",e.position)
e.spawns += 1
e.timer = 1.4
$Audio.effect("spheroid-spawn" if e.kind=="spheroid" else "quark-spawn")
if e.spawns>=4:
e.alive = false
burst(e.position)
"enforcer","tank":
if e.timer<=0:
e.velocity = Vector2.from_angle(rng.randf()*TAU)
e.timer = rng.randf_range(.8,1.7)/difficulty
projectile("spark" if e.kind=="enforcer" else "shell",e.position,player)
e.position = clamp_arena(e.position+e.velocity*(30 if e.kind=="enforcer" else 18)*delta)
if e.alive and e.kind!="human" and e.kind!="converting" and player.distance_to(e.position)<radius(e.kind)+2 and invulnerable<=0:
die()
return
update_shots(delta)
update_enemy_shots(delta)
entities = entities.filter(func(e): return e.alive)
if phase=="play" and remaining_enemies()==0:
phase = "transition"
phase_time = 1.8
enemy_shots.clear()
shots.clear()
$Audio.stop_all()
$Audio.effect("level-transition")
func update_shots(delta: float) -> void:
for i in range(shots.size()-1,-1,-1):
var shot: Dictionary = shots[i]
var distance: float = shot.velocity.length()*delta
var direction: Vector2 = shot.velocity.normalized()
var hit := false
while distance>0 and not hit:
var step := minf(2,distance)
shot.position += direction*step
distance -= step
if not ARENA.has_point(shot.position): hit = true
for enemy in entities:
if hit: break
if not enemy.alive or enemy.kind in ["human","converting"]: continue
if shot.position.distance_to(enemy.position)<radius(enemy.kind):
hit = true
if enemy.kind=="hulk":
enemy.position = clamp_arena(enemy.position+direction*4)
enemy.velocity = direction
enemy.timer = .3
else: damage(enemy)
for bullet in enemy_shots:
if not hit and bullet.life>0 and shot.position.distance_to(bullet.position)<4:
bullet.life = 0
hit = true
add_score(50 if bullet.kind=="shell" else (75 if bullet.kind=="cruise" else 25))
if hit: shots.remove_at(i)
func update_enemy_shots(delta: float) -> void:
for i in range(enemy_shots.size()-1,-1,-1):
var b: Dictionary = enemy_shots[i]
b.life -= delta
if b.kind=="cruise": b.velocity = b.velocity.move_toward((player-b.position).normalized()*78,delta*45)
b.position += b.velocity*delta
if not ARENA.has_point(b.position):
if b.kind=="shell" and b.bounces<4:
if b.position.x<ARENA.position.x or b.position.x>ARENA.end.x: b.velocity.x *= -1
if b.position.y<ARENA.position.y or b.position.y>ARENA.end.y: b.velocity.y *= -1
b.position = clamp_arena(b.position,1)
b.bounces += 1
$Audio.effect("tank-projectile-bounce")
else: b.life = 0
if b.life>0 and b.position.distance_to(player)<5 and invulnerable<=0:
die()
return
if b.life<=0: enemy_shots.remove_at(i)
func damage(enemy: Dictionary) -> void:
enemy.hp -= 1
if enemy.hp>0: return
enemy.alive = false
add_score(POINTS.get(enemy.kind,0))
burst(enemy.position)
var sounds := {"brain":"brain-explode","tank":"tank-explode","electrode":"electrode-explode","spheroid":"spheroid-pop"}
$Audio.effect(sounds.get(enemy.kind,"robot-explode"))
func save_human(human: Dictionary) -> void:
if not human.alive: return
human.alive = false
rescue_chain = mini(5,rescue_chain+1)
add_score(rescue_chain*1000)
burst(human.position,str(rescue_chain*1000))
$Audio.effect("human-save")
func add_score(value: int) -> void:
score += value
if demo: return
high_score = maxi(high_score,score)
while extra_life_interval>0 and score>=next_extra:
lives += 1
next_extra += extra_life_interval
$Audio.effect("extra-life")
func burst(position: Vector2, label: String = "") -> void:
effects.append({"position":position,"life":.55 if label!="" else .25,"label":label})
func remaining_enemies() -> int:
var count := 0
for e in entities:
if e.alive and e.kind not in ["human","hulk","electrode"]: count += 1
return count
func die() -> void:
if phase!="play": return
lives -= 1
rescue_chain = 0
phase = "dead"
phase_time = 1.2
$Audio.stop_all()
$Audio.effect("player-death")
func respawn() -> void:
player = Vector2(146,124)
shots.clear()
enemy_shots.clear()
invulnerable = 2
for e in entities:
if e.alive and e.kind!="human" and e.position.distance_to(player)<48: e.position = spawn_point()
phase = "ready"
phase_time = 1
func finish(won: bool) -> void:
if demo:
end_demo()
return
if not is_running: return
last_score = score
phase = "won" if won else "lost"
phase_time = 5
$Audio.stop_all()
if won: win_game()
else:
lose_game()
if score>0 and score>=high_score: $Audio.effect("high-score-top")