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

+6
View File
@@ -0,0 +1,6 @@
[gd_scene format=3 uid="uid://byorfa7yxhhrv"]
[ext_resource type="Script" uid="uid://b6ui86lx5kkpa" path="res://games/galaga/galaga.gd" id="1"]
[node name="Galaga" type="Node2D" unique_id=633520072]
script = ExtResource("1")
+6
View File
@@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3]
[ext_resource type="Script" path="res://games/galaga/actors/actor.gd" id="1"]
[node name="Enemy" type="Node2D"]
script = ExtResource("1")
kind = "boss"
radius = 6.0
+6
View File
@@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3]
[ext_resource type="Script" path="res://games/galaga/actors/actor.gd" id="1"]
[node name="Explosion" type="Node2D"]
script = ExtResource("1")
kind = "explosion"
radius = 0.0
+6
View File
@@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3]
[ext_resource type="Script" path="res://games/galaga/actors/actor.gd" id="1"]
[node name="Fighter" type="Node2D"]
script = ExtResource("1")
kind = "fighter"
radius = 6.0
+6
View File
@@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3]
[ext_resource type="Script" path="res://games/galaga/actors/actor.gd" id="1"]
[node name="Projectile" type="Node2D"]
script = ExtResource("1")
kind = "shot"
radius = 2.0
+61
View File
@@ -0,0 +1,61 @@
@tool
extends Node2D
## Visual and collision bounds share the node origin, in native game pixels.
@export_enum("fighter", "boss", "butterfly", "bee", "shot", "enemy_shot", "explosion", "enemy_explosion") var kind := "fighter":
set(value):
kind = value
queue_redraw()
@export var damaged := false
@export var radius := 6.0
var animation_time := 0.0
var spinning := false
var spin_time := 0.0
@export var heading := Vector2.DOWN
const SHEET = preload("res://games/galaga/art/spr_galaga.png")
const DIRECTION_ROWS := {"boss":36, "boss_damaged":54, "butterfly":72, "bee":108}
func _ready() -> void:
texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
if kind not in ["shot", "enemy_shot"]:
var sprite_material := ShaderMaterial.new()
sprite_material.shader = preload("res://games/galaga/actors/sprite.gdshader")
material = sprite_material
func _process(_delta: float) -> void:
queue_redraw()
func _draw() -> void:
# Quantize the rendered center in native 224x288 coordinates, before the
# view's integer enlargement. Keep simulation positions precise.
draw_set_transform(position.round()-position)
if kind in ["shot", "enemy_shot"]:
draw_rect(Rect2(-1, -3, 2, 6), Color.CYAN if kind == "shot" else Color.SALMON)
return
if kind == "explosion":
var frame := mini(3, int(animation_time * 12))
draw_texture_rect_region(SHEET, Rect2(-16,-16,32,32), Rect2(145 + frame * 34, 1, 32,32))
return
if kind == "enemy_explosion":
var frame := mini(4, int(animation_time * 15))
draw_texture_rect_region(SHEET, Rect2(-16,-16,32,32), Rect2(289+frame*34,1,32,32))
return
var source := Vector2(108,0)
var destination := Rect2(-8,-8,16,16)
if kind=="fighter" and spinning:
var facing := Vector2.UP.rotated(spin_time*TAU*2)
var column := 6-clampi(roundi(atan2(absf(facing.x),absf(facing.y))/(PI/12)),0,6)
source=Vector2(column*18,0)
if facing.x>0: destination.size.x=-16
if facing.y>0: destination.size.y=-16
if kind in ["boss", "butterfly", "bee"]:
# Seven authored angles from right to down, at 15-degree intervals.
# Mirror these pixels into the other quadrants; never rotate the texture.
var column := 6 - clampi(roundi(atan2(absf(heading.x),absf(heading.y))/(PI/12)),0,6)
# Adjacent rows are different sprites, not interchangeable wing frames.
var row: int = DIRECTION_ROWS["boss_damaged" if kind == "boss" and damaged else kind]
source = Vector2(column*18,row)
if heading.x < 0: destination.size.x = -16
if heading.y < 0: destination.size.y = -16
draw_texture_rect_region(SHEET, destination, Rect2(source+Vector2.ONE,Vector2(16,16)))
if Engine.is_editor_hint():
draw_arc(Vector2.ZERO, radius, 0, TAU, 24, Color(0,1,0,0.5))
+1
View File
@@ -0,0 +1 @@
uid://c8jf7ijduugag
+7
View File
@@ -0,0 +1,7 @@
shader_type canvas_item;
render_mode unshaded;
void fragment() {
vec4 pixel = texture(TEXTURE, UV);
if (max(pixel.r, max(pixel.g, pixel.b)) < 0.02) { discard; }
COLOR *= pixel;
}
+1
View File
@@ -0,0 +1 @@
uid://m8sm8co7m5hs
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

+40
View File
@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c8tq40qfjbj0j"
path="res://.godot/imported/spr_galaga.png-14ddb3514e589ecbada54d6f3d3d9c6a.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://games/galaga/art/spr_galaga.png"
dest_files=["res://.godot/imported/spr_galaga.png-14ddb3514e589ecbada54d6f3d3d9c6a.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b73fvvkpj1n0m"
path="res://.godot/imported/spr_galaga_screens_fonts.png-903d74438a2f25937f64b812fa39ec5f.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://games/galaga/art/spr_galaga_screens_fonts.png"
dest_files=["res://.godot/imported/spr_galaga_screens_fonts.png-903d74438a2f25937f64b812fa39ec5f.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
Binary file not shown.
+24
View File
@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://dncbkjieopw2j"
path="res://.godot/imported/boss_damage.wav-78e99d1a33486a6035ee91fa662ba934.sample"
[deps]
source_file="res://games/galaga/audio/boss_damage.wav"
dest_files=["res://.godot/imported/boss_damage.wav-78e99d1a33486a6035ee91fa662ba934.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2
Binary file not shown.
@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://daxutfef332d0"
path="res://.godot/imported/boss_destroy.wav-e354d8227cc7537a700b34f5fe73f561.sample"
[deps]
source_file="res://games/galaga/audio/boss_destroy.wav"
dest_files=["res://.godot/imported/boss_destroy.wav-e354d8227cc7537a700b34f5fe73f561.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2
Binary file not shown.
@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://cx3ne7dset50h"
path="res://.godot/imported/captured_ship_destroy.wav-00ca325f65364f942d197e896286ff9e.sample"
[deps]
source_file="res://games/galaga/audio/captured_ship_destroy.wav"
dest_files=["res://.godot/imported/captured_ship_destroy.wav-00ca325f65364f942d197e896286ff9e.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2
Binary file not shown.
+24
View File
@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://dj5w28f8vfe11"
path="res://.godot/imported/capturing.wav-31e55e3bb216de5d977b1eb2a70fa55e.sample"
[deps]
source_file="res://games/galaga/audio/capturing.wav"
dest_files=["res://.godot/imported/capturing.wav-31e55e3bb216de5d977b1eb2a70fa55e.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2
Binary file not shown.
@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://hkxrmuwhnjm8"
path="res://.godot/imported/challenging_stage_perfect.wav-108af1f9129534a9994485cfa520c75c.sample"
[deps]
source_file="res://games/galaga/audio/challenging_stage_perfect.wav"
dest_files=["res://.godot/imported/challenging_stage_perfect.wav-108af1f9129534a9994485cfa520c75c.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2
Binary file not shown.
@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://14q74uylektm"
path="res://.godot/imported/challenging_stage_results.wav-dab64a66c50b7f1993c4c2c3acd961b7.sample"
[deps]
source_file="res://games/galaga/audio/challenging_stage_results.wav"
dest_files=["res://.godot/imported/challenging_stage_results.wav-dab64a66c50b7f1993c4c2c3acd961b7.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2
Binary file not shown.
@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://bdhprhh6occ5v"
path="res://.godot/imported/challenging_stage_start.wav-73d928e3ae25ec568dc7063663edd538.sample"
[deps]
source_file="res://games/galaga/audio/challenging_stage_start.wav"
dest_files=["res://.godot/imported/challenging_stage_start.wav-73d928e3ae25ec568dc7063663edd538.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2
Binary file not shown.
+24
View File
@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://dxrhspw3c16qk"
path="res://.godot/imported/credit.wav-700cc7936bb34b0b15042241f97b45ed.sample"
[deps]
source_file="res://games/galaga/audio/credit.wav"
dest_files=["res://.godot/imported/credit.wav-700cc7936bb34b0b15042241f97b45ed.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2
Binary file not shown.
+24
View File
@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://cs3pmp26xbwhk"
path="res://.godot/imported/dive.wav-5a74a6bfe1592a6aaea72d344d9ca9e3.sample"
[deps]
source_file="res://games/galaga/audio/dive.wav"
dest_files=["res://.godot/imported/dive.wav-5a74a6bfe1592a6aaea72d344d9ca9e3.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2
Binary file not shown.
+24
View File
@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://cpyt1s7qanl8q"
path="res://.godot/imported/explosion.wav-6c4c21ff43ef8c0e71bcf43356fc5b5a.sample"
[deps]
source_file="res://games/galaga/audio/explosion.wav"
dest_files=["res://.godot/imported/explosion.wav-6c4c21ff43ef8c0e71bcf43356fc5b5a.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2
Binary file not shown.
+24
View File
@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://bvquw0oosnxqh"
path="res://.godot/imported/extend.wav-6f07b0f7aff14e3922701a3466431816.sample"
[deps]
source_file="res://games/galaga/audio/extend.wav"
dest_files=["res://.godot/imported/extend.wav-6f07b0f7aff14e3922701a3466431816.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2
Binary file not shown.
@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://ddwn4qvfxdksy"
path="res://.godot/imported/fighter_captured.wav-de09c8e5ce22fdac89b516e365c9b080.sample"
[deps]
source_file="res://games/galaga/audio/fighter_captured.wav"
dest_files=["res://.godot/imported/fighter_captured.wav-de09c8e5ce22fdac89b516e365c9b080.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2
Binary file not shown.
@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://33yiax8gm27x"
path="res://.godot/imported/fighter_rescued.wav-104f1a0d3458b6b0c5fe2b4988a05239.sample"
[deps]
source_file="res://games/galaga/audio/fighter_rescued.wav"
dest_files=["res://.godot/imported/fighter_rescued.wav-104f1a0d3458b6b0c5fe2b4988a05239.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2
Binary file not shown.
+24
View File
@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://be1e74l1loh7n"
path="res://.godot/imported/fire.wav-5f3244d6c331a676322539de15b7dd8a.sample"
[deps]
source_file="res://games/galaga/audio/fire.wav"
dest_files=["res://.godot/imported/fire.wav-5f3244d6c331a676322539de15b7dd8a.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2
Binary file not shown.
@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://btk1xrnbeclk5"
path="res://.godot/imported/goei_destroy.wav-8823f818c7ebeab88cbc5bbce5e497a6.sample"
[deps]
source_file="res://games/galaga/audio/goei_destroy.wav"
dest_files=["res://.godot/imported/goei_destroy.wav-8823f818c7ebeab88cbc5bbce5e497a6.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2
Binary file not shown.
+24
View File
@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://caafa28sxwha2"
path="res://.godot/imported/name_entry.wav-ac9a4ac5de170d13f1cf215b46ecf4be.sample"
[deps]
source_file="res://games/galaga/audio/name_entry.wav"
dest_files=["res://.godot/imported/name_entry.wav-ac9a4ac5de170d13f1cf215b46ecf4be.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2
Binary file not shown.
@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://bwveio4bx3bcg"
path="res://.godot/imported/name_entry_top_end.wav-746289f541bb0b67aa212f88d14d1e29.sample"
[deps]
source_file="res://games/galaga/audio/name_entry_top_end.wav"
dest_files=["res://.godot/imported/name_entry_top_end.wav-746289f541bb0b67aa212f88d14d1e29.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2
Binary file not shown.
@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://b2pbd5dqivcjh"
path="res://.godot/imported/name_entry_top_firstloop.wav-9dd53a38e65854032fde87dfd496ebf4.sample"
[deps]
source_file="res://games/galaga/audio/name_entry_top_firstloop.wav"
dest_files=["res://.godot/imported/name_entry_top_firstloop.wav-9dd53a38e65854032fde87dfd496ebf4.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2
Binary file not shown.
@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://7vox07aqyd22"
path="res://.godot/imported/name_entry_top_loop.wav-ec1fa871accf7a1b3d2bb99e5301f70a.sample"
[deps]
source_file="res://games/galaga/audio/name_entry_top_loop.wav"
dest_files=["res://.godot/imported/name_entry_top_loop.wav-ec1fa871accf7a1b3d2bb99e5301f70a.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2
Binary file not shown.
+24
View File
@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://8tpl8twpsre2"
path="res://.godot/imported/stage_count.wav-5dd59d6a7f3b9370e4221a44921f54ff.sample"
[deps]
source_file="res://games/galaga/audio/stage_count.wav"
dest_files=["res://.godot/imported/stage_count.wav-5dd59d6a7f3b9370e4221a44921f54ff.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2
Binary file not shown.
+24
View File
@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://dhwcq3grnk6ex"
path="res://.godot/imported/start.wav-b157900079964c24f391d1f6cfcfba00.sample"
[deps]
source_file="res://games/galaga/audio/start.wav"
dest_files=["res://.godot/imported/start.wav-b157900079964c24f391d1f6cfcfba00.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2
Binary file not shown.
@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://7iue027ylkmd"
path="res://.godot/imported/tractor_beam.wav-81aae0e2d4adf83ca0a76ed52762592a.sample"
[deps]
source_file="res://games/galaga/audio/tractor_beam.wav"
dest_files=["res://.godot/imported/tractor_beam.wav-81aae0e2d4adf83ca0a76ed52762592a.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2
Binary file not shown.
+24
View File
@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://bvh5e81wnestw"
path="res://.godot/imported/transform.wav-20248f23471e9e0a259acc1e86aba9be.sample"
[deps]
source_file="res://games/galaga/audio/transform.wav"
dest_files=["res://.godot/imported/transform.wav-20248f23471e9e0a259acc1e86aba9be.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2
Binary file not shown.
@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://c8lhi71x0bxk"
path="res://.godot/imported/zako_destroy.wav-d927ad0cf990543c87414951c2478a97.sample"
[deps]
source_file="res://games/galaga/audio/zako_destroy.wav"
dest_files=["res://.godot/imported/zako_destroy.wav-d927ad0cf990543c87414951c2478a97.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2
+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()
+1
View File
@@ -0,0 +1 @@
uid://b6ui86lx5kkpa
+14
View File
@@ -0,0 +1,14 @@
@tool
extends Node2D
## Draw above actors in the same native-pixel coordinate system.
const FRAME := Rect2(0, 0, 224, 288)
func _process(_delta: float) -> void:
queue_redraw()
func _draw() -> void:
var outer := (get_global_transform_with_canvas().affine_inverse() * get_viewport_rect()).merge(FRAME)
draw_rect(Rect2(outer.position, Vector2(FRAME.position.x-outer.position.x, outer.size.y)), Color.BLACK)
draw_rect(Rect2(Vector2(FRAME.end.x,outer.position.y),Vector2(outer.end.x-FRAME.end.x,outer.size.y)), Color.BLACK)
draw_rect(Rect2(Vector2(0,outer.position.y),Vector2(FRAME.size.x,-outer.position.y)), Color.BLACK)
draw_rect(Rect2(Vector2(0,FRAME.end.y),Vector2(FRAME.size.x,outer.end.y-FRAME.end.y)), Color.BLACK)
+1
View File
@@ -0,0 +1 @@
uid://clnm0uio7etas
+91
View File
@@ -0,0 +1,91 @@
@tool
extends Node2D
const FONT = preload("res://games/galaga/art/spr_galaga_screens_fonts.png")
const SHEET = preload("res://games/galaga/art/spr_galaga.png")
var attract_time := 0.0
var previous_state := ""
var high_score := 20000
var edge_mask: Node2D
func _ready() -> void:
texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
edge_mask = preload("res://games/galaga/galaga_edge_mask.gd").new()
edge_mask.z_index = 100
add_child(edge_mask)
func _process(delta: float) -> void:
var game = get_parent()
if game.state == "idle":
if previous_state != "idle": attract_time = 0.0
attract_time += delta
for actor in get_children():
if actor == edge_mask: continue
if game.state == "idle" and not Engine.is_editor_hint(): actor.visible = false
previous_state = game.state
high_score = maxi(high_score,game.score)
var size := get_viewport_rect().size
var factor := maxf(0.1,minf(size.x/224.0,size.y/288.0))
if factor >= 1: factor = floorf(factor)
scale = Vector2.ONE*factor
position = ((size-Vector2(224,288)*factor)/2).floor()
queue_redraw()
func _draw() -> void:
draw_rect(Rect2(-1000,-1000,2224,2288),Color.BLACK)
var star_clock: float = attract_time if get_parent().state=="idle" else get_parent().clock
for i in range(90):
var x := fposmod(i*73.0,224)
var y := fposmod(i*113.0+star_clock*(12+i%3*8),288)
if (int(star_clock*2)+i)%7!=0:
draw_rect(Rect2(Vector2(x,y).round(),Vector2.ONE),[Color.CYAN,Color.RED,Color.YELLOW,Color.BLUE,Color.MAGENTA,Color.WHITE][i%6]*0.7)
if get_parent().state == "idle" and not Engine.is_editor_hint():
_attract()
return
_text("1UP %06d" % get_parent().score,Vector2(8,4))
if get_parent().state != "intro":
for i in range(mini(5,maxi(0,get_parent().lives-1))):
draw_texture_rect_region(SHEET,Rect2(8+i*16,270,16,16),Rect2(109,1,16,16))
_text("STAGE %d" % get_parent().stage,Vector2(144,278))
var text: String = {"intro":"PLAYER 1","stage_card":"STAGE %d" % get_parent().stage,"ready":"PLAYER 1 STAGE %d" % get_parent().stage,"won":"YOU WIN!","lost":"GAME OVER","clear":"STAGE CLEAR"}.get(get_parent().state,"")
if text != "": _text(text,Vector2((224-text.length()*8)/2,180),Color.CYAN)
if get_parent().state=="captured_return": _center("FIGHTER CAPTURED",180,Color.CYAN)
elif get_parent().state=="capture_ready": _center("READY",180,Color.CYAN)
var game = get_parent()
if not game.beam.is_empty() and game.beam.mode=="beam":
var source_x := 289+int(game.clock*8)%3*50
draw_texture_rect_region(SHEET,Rect2(game.beam.node.position.round()+Vector2(-24,8),Vector2(48,79)),Rect2(source_x,36,48,79))
func _text(message: String, at: Vector2, tint := Color.WHITE) -> void:
var chars := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
for i in range(message.length()):
var index := chars.find(message[i])
if index<0: continue
var row := 0 if index<25 else 1
var col := index if row==0 else index-25
draw_texture_rect_region(FONT,Rect2(at+Vector2(i*8,0),Vector2(7,7)),Rect2(454+col*9,443+row*9,7,7),tint)
func _center(message: String, y: float, tint := Color.WHITE) -> void:
_text(message,Vector2((224-message.length()*8)/2,y),tint)
func _attract() -> void:
_text("1UP",Vector2(16,0),Color.RED)
_text("HIGH SCORE",Vector2(72,0),Color.RED)
_text("2UP",Vector2(184,0),Color.RED)
_center(str(high_score).pad_zeros(6),10)
if int(attract_time / 8.0) % 2 == 0:
_center("PUSH START BUTTON",104,Color.CYAN)
var bonus: int = get_parent().extra_life_score
var messages := ["1ST BONUS FOR %d PTS" % bonus,"2ND BONUS FOR %d PTS" % get_parent().second_extra_life_score,"AND FOR EVERY %d PTS" % get_parent().repeat_extra_life_interval]
for i in range(3):
_text(messages[i],Vector2(24,136+i*24),Color.YELLOW)
draw_texture_rect_region(SHEET,Rect2(8,132+i*24,16,16),Rect2(109,1,16,16))
else:
_center("THE GALACTIC HEROES",56,Color.BLUE)
_center("BEST 5",112,Color.RED)
_text("SCORE NAME",Vector2(64,152),Color.CYAN)
for i in range(5):
_text(["1ST","2ND","3RD","4TH","5TH"][i],Vector2(24,176+i*16),Color.CYAN)
_text(str(high_score if i==0 else (5-i)*1000).pad_zeros(6),Vector2(64,176+i*16),Color.CYAN)
_text(["AAA","BBB","CCC","DDD","EEE"][i],Vector2(144,176+i*16),Color.CYAN)
_text("CREDIT %d" % get_parent().credits,Vector2(8,278))
+1
View File
@@ -0,0 +1 @@
uid://yjuxbruqjkbf