Added Glitch Room Source code to Gitty
This commit is contained in:
commit
1ac9eb5644
505 files changed
+28504
No files matched your search
@@ -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")
|
||||
Reference in new issue
Block a user