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,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")
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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))
|
||||
@@ -0,0 +1 @@
|
||||
uid://c8jf7ijduugag
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://m8sm8co7m5hs
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
@@ -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.
@@ -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.
@@ -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.
@@ -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.
@@ -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.
@@ -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.
@@ -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.
@@ -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.
@@ -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.
@@ -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.
@@ -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.
@@ -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
|
||||
@@ -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()
|
||||
@@ -0,0 +1 @@
|
||||
uid://b6ui86lx5kkpa
|
||||
@@ -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)
|
||||
@@ -0,0 +1 @@
|
||||
uid://clnm0uio7etas
|
||||
@@ -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))
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://yjuxbruqjkbf
|
||||
Reference in new issue
Block a user