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,402 @@
|
||||
@tool
|
||||
extends ArcadeGame
|
||||
|
||||
@export_range(0, 99) var waves_to_win: int = 1
|
||||
@export_range(1, 6) var starting_lives: int = 3
|
||||
@export_range(0, 10000, 100) var bonus_life_score: int = 1500
|
||||
@export var color_overlay: bool = true
|
||||
@export var sound_enabled: bool = true
|
||||
|
||||
const SAUCER_SCORES := [100,50,50,100,150,100,100,50,300,100,100,100,50,150,100]
|
||||
const WAVE_HEIGHTS := [96,104,112,112,112,120,120,120]
|
||||
var phase := "attract"
|
||||
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 extra_awarded := false
|
||||
var player_x := 24.0
|
||||
var player_shot := Vector2(-100,-100)
|
||||
var shot_active := false
|
||||
var shot_count := 0
|
||||
var shot_delay := 0.0
|
||||
var aliens: Array[Dictionary] = []
|
||||
var bombs: Array[Dictionary] = []
|
||||
var effects: Array[Dictionary] = []
|
||||
var shields: Array[Dictionary] = []
|
||||
var fleet_direction := 1
|
||||
var fleet_cursor := 0
|
||||
var fleet_delta := Vector2(2,0)
|
||||
var fleet_ticks := 0.0
|
||||
var march_note := 0
|
||||
var march_sound_delay := 0.0
|
||||
var hit_pause := 0.0
|
||||
var bomb_timer := 1.0
|
||||
var bomb_kind := 0
|
||||
var bomb_column := 0
|
||||
var saucer_x := -32.0
|
||||
var saucer_direction := 1
|
||||
var saucer_active := false
|
||||
var saucer_timer := 20.0
|
||||
var demo_target := 112.0
|
||||
@onready var board: Node2D = $PixelScreen/Board
|
||||
|
||||
func _ready() -> void:
|
||||
new_wave()
|
||||
lives = starting_lives
|
||||
|
||||
func new_wave() -> void:
|
||||
aliens.clear()
|
||||
var top: int = 72 if wave == 1 else WAVE_HEIGHTS[(wave-2)%WAVE_HEIGHTS.size()]
|
||||
# Bottom-to-top scan order gives the original rippling fleet movement.
|
||||
for row in range(4,-1,-1):
|
||||
for column in range(11):
|
||||
aliens.append({"position":Vector2(24+column*16,top+row*16),"kind":0 if row==0 else (1 if row<3 else 2),"frame":0,"alive":true,"column":column})
|
||||
fleet_direction = 1
|
||||
fleet_cursor = 0
|
||||
fleet_ticks = 0
|
||||
march_sound_delay = 0
|
||||
march_note = 0
|
||||
fleet_delta = Vector2(2,0)
|
||||
bombs.clear()
|
||||
effects.clear()
|
||||
shot_active = false
|
||||
shot_delay = 0
|
||||
bomb_timer = 1
|
||||
saucer_active = false
|
||||
saucer_timer = 20
|
||||
hit_pause = 0
|
||||
player_x = 24
|
||||
build_shields()
|
||||
|
||||
func build_shields() -> void:
|
||||
shields.clear()
|
||||
var pattern: Image = board.masks["shield"]
|
||||
for x in [32,77,122,167]:
|
||||
var pixels := pattern.duplicate() as Image
|
||||
shields.append({"position":Vector2i(x,192),"image":pixels,"texture":ImageTexture.create_from_image(pixels),"dirty":false})
|
||||
|
||||
func start_game() -> bool:
|
||||
if is_running: return false
|
||||
demo = false
|
||||
$Audio.stop_all()
|
||||
score = 0
|
||||
lives = starting_lives
|
||||
wave = 1
|
||||
shot_count = 0
|
||||
extra_awarded = false
|
||||
clock = 0
|
||||
new_wave()
|
||||
phase = "ready"
|
||||
phase_time = 2
|
||||
return super.start_game()
|
||||
|
||||
func reset_game() -> void:
|
||||
demo = false
|
||||
$Audio.stop_all()
|
||||
score = 0
|
||||
last_score = 0
|
||||
wave = 1
|
||||
lives = starting_lives
|
||||
new_wave()
|
||||
phase = "attract"
|
||||
phase_time = 0
|
||||
clock = 0
|
||||
super.reset_game()
|
||||
|
||||
func begin_demo() -> void:
|
||||
demo = true
|
||||
demo_time = 0
|
||||
score = 0
|
||||
lives = starting_lives
|
||||
wave = 1
|
||||
shot_count = 0
|
||||
new_wave()
|
||||
phase = "play"
|
||||
|
||||
func end_demo() -> void:
|
||||
demo = false
|
||||
score = last_score
|
||||
phase = "attract"
|
||||
phase_time = 0
|
||||
$Audio.stop_all()
|
||||
|
||||
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 held(action: StringName) -> bool:
|
||||
return Input.is_action_pressed(action)
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
clock += delta
|
||||
if Engine.is_editor_hint():
|
||||
phase_time = fmod(clock,22)
|
||||
board.queue_redraw()
|
||||
return
|
||||
if phase == "attract":
|
||||
phase_time += delta
|
||||
if phase_time >= 28: begin_demo()
|
||||
elif phase == "ready":
|
||||
phase_time -= delta
|
||||
if phase_time <= 0:
|
||||
phase = "play"
|
||||
if saucer_active: $Audio.ufo(true)
|
||||
elif phase == "lost":
|
||||
phase_time -= delta
|
||||
if phase_time <= 0:
|
||||
phase = "attract"
|
||||
phase_time = 0
|
||||
elif phase == "dead":
|
||||
phase_time -= delta
|
||||
if phase_time <= 0:
|
||||
if lives <= 0: finish(false)
|
||||
else:
|
||||
player_x = 24
|
||||
bombs.clear()
|
||||
shot_active = false
|
||||
phase = "ready"
|
||||
phase_time = 1.0
|
||||
elif phase == "clear":
|
||||
phase_time -= delta
|
||||
if phase_time <= 0:
|
||||
if demo: end_demo()
|
||||
elif waves_to_win > 0 and wave >= waves_to_win: finish(true)
|
||||
else:
|
||||
wave += 1
|
||||
new_wave()
|
||||
phase = "ready"
|
||||
phase_time = 1.5
|
||||
elif phase == "play":
|
||||
var move := 0.0
|
||||
if demo:
|
||||
demo_target = 112+sin(demo_time*.7)*88
|
||||
move = signf(demo_target-player_x) if absf(demo_target-player_x)>1 else 0.0
|
||||
else:
|
||||
move = float(held(&"arcade_right"))-float(held(&"arcade_left"))
|
||||
player_x = clampf(player_x+move*60*delta,16,208)
|
||||
shot_delay = maxf(0,shot_delay-delta)
|
||||
if demo or held(&"arcade_action_1"): fire()
|
||||
step_world(delta)
|
||||
if demo:
|
||||
demo_time += delta
|
||||
if demo_time >= 25: end_demo()
|
||||
for shield in shields:
|
||||
if shield.dirty:
|
||||
shield.texture.update(shield.image)
|
||||
shield.dirty = false
|
||||
board.queue_redraw()
|
||||
|
||||
func fire() -> bool:
|
||||
if phase != "play" or shot_active or shot_delay > 0: return false
|
||||
shot_active = true
|
||||
player_shot = Vector2(roundf(player_x),216)
|
||||
$Audio.effect("shot")
|
||||
return true
|
||||
|
||||
func remove_shot() -> void:
|
||||
if not shot_active: return
|
||||
shot_active = false
|
||||
shot_count += 1
|
||||
shot_delay = .08
|
||||
|
||||
func alive_count() -> int:
|
||||
var count := 0
|
||||
for alien in aliens:
|
||||
if alien.alive: count += 1
|
||||
return count
|
||||
|
||||
func march() -> void:
|
||||
var count := alive_count()
|
||||
if count == 0: return
|
||||
if fleet_cursor == 0:
|
||||
var edge := false
|
||||
for alien in aliens:
|
||||
if alien.alive and (alien.position.x+fleet_direction*2 > 208 or alien.position.x+fleet_direction*2 < 16): edge = true
|
||||
if edge:
|
||||
fleet_direction *= -1
|
||||
fleet_delta = Vector2(0,8)
|
||||
else: fleet_delta = Vector2(fleet_direction*(3 if count==1 else 2),0)
|
||||
if march_sound_delay <= 0:
|
||||
$Audio.effect("march%d"%march_note)
|
||||
march_note = (march_note+1)%4
|
||||
march_sound_delay = .08
|
||||
while fleet_cursor < aliens.size() and not aliens[fleet_cursor].alive: fleet_cursor += 1
|
||||
if fleet_cursor < aliens.size():
|
||||
var alien: Dictionary = aliens[fleet_cursor]
|
||||
alien.position += fleet_delta
|
||||
alien.frame = 1-alien.frame
|
||||
var bounds := Rect2(alien.position-Vector2(8,4),Vector2(16,8))
|
||||
for shield in shields:
|
||||
if bounds.intersects(Rect2(Vector2(shield.position),Vector2(shield.image.get_size()))):
|
||||
for y in range(shield.image.get_height()):
|
||||
for x in range(shield.image.get_width()):
|
||||
if bounds.has_point(Vector2(shield.position+Vector2i(x,y))): shield.image.set_pixel(x,y,Color.TRANSPARENT)
|
||||
shield.dirty = true
|
||||
if alien.position.y >= 216: finish(false)
|
||||
fleet_cursor += 1
|
||||
while fleet_cursor < aliens.size() and not aliens[fleet_cursor].alive: fleet_cursor += 1
|
||||
if fleet_cursor >= aliens.size(): fleet_cursor = 0
|
||||
|
||||
func launch_bomb() -> void:
|
||||
var kind := bomb_kind
|
||||
bomb_kind = (bomb_kind+1)%3
|
||||
for bomb in bombs:
|
||||
if bomb.kind == kind: return
|
||||
if kind == 1 and alive_count() == 1: return
|
||||
var candidates: Array[Dictionary] = []
|
||||
var columns: Dictionary = {}
|
||||
for alien in aliens:
|
||||
if alien.alive and not columns.has(alien.column):
|
||||
columns[alien.column] = true
|
||||
candidates.append(alien)
|
||||
if candidates.is_empty(): return
|
||||
var source: Dictionary = candidates[bomb_column%candidates.size()]
|
||||
bomb_column += 3
|
||||
if kind == 0:
|
||||
for alien in candidates:
|
||||
if absf(alien.position.x-player_x) < absf(source.position.x-player_x): source = alien
|
||||
bombs.append({"position":source.position+Vector2(0,8),"kind":kind})
|
||||
|
||||
func pixel_hit(name: String, origin: Vector2, point: Vector2) -> bool:
|
||||
var image: Image = board.masks[name]
|
||||
var p := Vector2i(point.round()-origin.round())
|
||||
return p.x>=0 and p.y>=0 and p.x<image.get_width() and p.y<image.get_height() and image.get_pixelv(p).a>.5
|
||||
|
||||
func shield_hit(point: Vector2, upward: bool) -> bool:
|
||||
for shield in shields:
|
||||
var p := Vector2i(point.round())-Vector2i(shield.position)
|
||||
var image: Image = shield.image
|
||||
if p.x<0 or p.y<0 or p.x>=image.get_width() or p.y>=image.get_height(): continue
|
||||
if image.get_pixelv(p).a < .5: continue
|
||||
for dy in range(-4,5):
|
||||
for dx in range(-3,4):
|
||||
var q := p+Vector2i(dx,dy)
|
||||
if q.x<0 or q.y<0 or q.x>=image.get_width() or q.y>=image.get_height(): continue
|
||||
var depth := -dy if upward else dy
|
||||
if dx*dx+dy*dy<=12 and depth>=-1 and (absi(dx)<2 or (q.x+q.y)%3!=0): image.set_pixelv(q,Color.TRANSPARENT)
|
||||
shield.dirty = true
|
||||
return true
|
||||
return false
|
||||
|
||||
func blast(position: Vector2, name: String = "burst", duration: float = .16, text: String = "") -> void:
|
||||
effects.append({"position":position,"name":name,"time":duration,"text":text})
|
||||
|
||||
func step_world(delta: float) -> void:
|
||||
march_sound_delay = maxf(0,march_sound_delay-delta)
|
||||
for i in range(effects.size()-1,-1,-1):
|
||||
effects[i].time -= delta
|
||||
if effects[i].time <= 0: effects.remove_at(i)
|
||||
hit_pause = maxf(0,hit_pause-delta)
|
||||
if hit_pause == 0:
|
||||
fleet_ticks += delta*60
|
||||
while fleet_ticks >= 1 and phase == "play":
|
||||
fleet_ticks -= 1
|
||||
march()
|
||||
if phase != "play": return
|
||||
bomb_timer -= delta
|
||||
if bomb_timer <= 0:
|
||||
launch_bomb()
|
||||
bomb_timer = maxf(.25,.75-score/20000.0)
|
||||
saucer_timer -= delta
|
||||
if saucer_timer <= 0 and not saucer_active:
|
||||
saucer_active = true
|
||||
saucer_direction = 1 if shot_count%2==0 else -1
|
||||
saucer_x = -12 if saucer_direction==1 else 236
|
||||
$Audio.ufo(true)
|
||||
if saucer_active:
|
||||
saucer_x += saucer_direction*30*delta
|
||||
if saucer_x < -16 or saucer_x > 240:
|
||||
saucer_active = false
|
||||
saucer_timer = 23
|
||||
$Audio.ufo(false)
|
||||
# One-pixel substeps prevent fast projectiles tunneling through shields/sprites.
|
||||
if shot_active:
|
||||
var travel := delta*240
|
||||
while travel > 0 and shot_active:
|
||||
var amount := minf(1,travel)
|
||||
player_shot.y -= amount
|
||||
travel -= amount
|
||||
if shield_hit(player_shot,true): remove_shot()
|
||||
elif player_shot.y <= 32:
|
||||
blast(player_shot,"shot_burst")
|
||||
remove_shot()
|
||||
elif saucer_active and pixel_hit("ufo",Vector2(saucer_x-8,40),player_shot):
|
||||
var value: int = SAUCER_SCORES[shot_count%15]
|
||||
add_score(value)
|
||||
blast(Vector2(saucer_x,44),"ufo_burst",.65,str(value))
|
||||
saucer_active = false
|
||||
saucer_timer = 23
|
||||
$Audio.ufo(false)
|
||||
$Audio.effect("ufo_hit")
|
||||
remove_shot()
|
||||
else:
|
||||
for alien in aliens:
|
||||
if alien.alive and pixel_hit("alien%d_%d"%[alien.kind,alien.frame],alien.position-Vector2(8,4),player_shot):
|
||||
alien.alive = false
|
||||
add_score([30,20,10][alien.kind])
|
||||
blast(alien.position)
|
||||
hit_pause = .10
|
||||
$Audio.effect("invader")
|
||||
remove_shot()
|
||||
break
|
||||
for i in range(bombs.size()-1,-1,-1):
|
||||
var bomb: Dictionary = bombs[i]
|
||||
var destroyed := false
|
||||
var travel := delta*(90 if alive_count()<9 else 60)
|
||||
while travel>0 and not destroyed:
|
||||
var amount := minf(1,travel)
|
||||
bomb.position.y += amount
|
||||
travel -= amount
|
||||
if shield_hit(bomb.position+Vector2(0,3),false): destroyed = true
|
||||
elif shot_active and absf(bomb.position.x-player_shot.x)<3 and absf(bomb.position.y-player_shot.y)<7:
|
||||
destroyed = true
|
||||
remove_shot()
|
||||
blast(bomb.position,"shot_burst")
|
||||
elif pixel_hit("player",Vector2(roundf(player_x)-8,216),bomb.position+Vector2(0,3)):
|
||||
die()
|
||||
destroyed = true
|
||||
elif bomb.position.y>=238:
|
||||
destroyed = true
|
||||
blast(Vector2(bomb.position.x,237),"shot_burst")
|
||||
if destroyed: bombs.remove_at(i)
|
||||
if phase != "play": return
|
||||
if alive_count()==0:
|
||||
phase = "clear"
|
||||
phase_time = 1.5
|
||||
$Audio.stop_all()
|
||||
|
||||
func add_score(value: int) -> void:
|
||||
score += value
|
||||
if demo: return
|
||||
high_score = maxi(high_score,score)
|
||||
if bonus_life_score>0 and score>=bonus_life_score and not extra_awarded:
|
||||
extra_awarded = true
|
||||
lives += 1
|
||||
$Audio.effect("bonus")
|
||||
|
||||
func die() -> void:
|
||||
if phase != "play": return
|
||||
lives -= 1
|
||||
phase = "dead"
|
||||
phase_time = 1.5
|
||||
$Audio.stop_all()
|
||||
$Audio.effect("death")
|
||||
|
||||
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()
|
||||
Reference in new issue
Block a user