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,22 @@
|
||||
[gd_scene format=3 uid="uid://y0xg0i8yacqd"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c375fg6kitvex" path="res://games/pacman/pacman.gd" id="1"]
|
||||
[ext_resource type="Script" uid="uid://c0kalctx5x8nf" path="res://games/pacman/pacman_editor_preview.gd" id="2"]
|
||||
|
||||
[node name="Pacman" type="Node2D" unique_id=1012196150]
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="Background" type="ColorRect" parent="." unique_id=1925270229]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
color = Color(0, 0, 0, 1)
|
||||
|
||||
[node name="EditorPreview" type="Node2D" parent="." unique_id=452219297]
|
||||
texture_filter = 1
|
||||
position = Vector2(624, 108)
|
||||
scale = Vector2(3, 3)
|
||||
script = ExtResource("2")
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 8.0 KiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://np8iifxlrof5"
|
||||
path="res://.godot/imported/sprites.png-6a0c66632a3c198b081d2b7b7452fab9.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/pacman/art/sprites.png"
|
||||
dest_files=["res://.godot/imported/sprites.png-6a0c66632a3c198b081d2b7b7452fab9.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: 3.0 KiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cbq03kf3nual3"
|
||||
path="res://.godot/imported/text.png-d0d1c131f1ae1bb662e9ed4b6ddfead5.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/pacman/art/text.png"
|
||||
dest_files=["res://.godot/imported/text.png-d0d1c131f1ae1bb662e9ed4b6ddfead5.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://b54r4c60urcai"
|
||||
path="res://.godot/imported/credit.wav-92f0d7ae9a7bc6bec91c661f1ee37d4b.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/pacman/audio/credit.wav"
|
||||
dest_files=["res://.godot/imported/credit.wav-92f0d7ae9a7bc6bec91c661f1ee37d4b.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://ovd85rcstsbj"
|
||||
path="res://.godot/imported/death_0.wav-9cac572d18aa62fc6445fd773e82e8fe.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/pacman/audio/death_0.wav"
|
||||
dest_files=["res://.godot/imported/death_0.wav-9cac572d18aa62fc6445fd773e82e8fe.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://c4htnok4dd2h2"
|
||||
path="res://.godot/imported/death_1.wav-3480eee71db390581fe6b7c8f5b1ab41.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/pacman/audio/death_1.wav"
|
||||
dest_files=["res://.godot/imported/death_1.wav-3480eee71db390581fe6b7c8f5b1ab41.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://bwtevq2kndkjy"
|
||||
path="res://.godot/imported/eat_dot_0.wav-e785d924a8ad68c8cad22f77721e8d3f.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/pacman/audio/eat_dot_0.wav"
|
||||
dest_files=["res://.godot/imported/eat_dot_0.wav-e785d924a8ad68c8cad22f77721e8d3f.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://bhai4c6nel4hr"
|
||||
path="res://.godot/imported/eat_dot_1.wav-589291afc9dc717182937ec5a490b116.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/pacman/audio/eat_dot_1.wav"
|
||||
dest_files=["res://.godot/imported/eat_dot_1.wav-589291afc9dc717182937ec5a490b116.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://bltx7wcc4rxas"
|
||||
path="res://.godot/imported/eat_fruit.wav-a61a6e480dfc9456d45ca90aab280a2d.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/pacman/audio/eat_fruit.wav"
|
||||
dest_files=["res://.godot/imported/eat_fruit.wav-a61a6e480dfc9456d45ca90aab280a2d.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://bi6eyjbt8nkbh"
|
||||
path="res://.godot/imported/eat_ghost.wav-6928c0d767e65ac3b8dc104f903ce44e.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/pacman/audio/eat_ghost.wav"
|
||||
dest_files=["res://.godot/imported/eat_ghost.wav-6928c0d767e65ac3b8dc104f903ce44e.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://cn4y8jnq27rkf"
|
||||
path="res://.godot/imported/extend.wav-22067b1829b78c441ada74f3450451a7.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/pacman/audio/extend.wav"
|
||||
dest_files=["res://.godot/imported/extend.wav-22067b1829b78c441ada74f3450451a7.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://cy433r2abm2qm"
|
||||
path="res://.godot/imported/eyes.wav-6339bb3cfd78ee9fb7c65c8d14fae1b4.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/pacman/audio/eyes.wav"
|
||||
dest_files=["res://.godot/imported/eyes.wav-6339bb3cfd78ee9fb7c65c8d14fae1b4.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://d4ljtyavowqbk"
|
||||
path="res://.godot/imported/eyes_firstloop.wav-135f00eb080c2f8bf64b2349457c055e.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/pacman/audio/eyes_firstloop.wav"
|
||||
dest_files=["res://.godot/imported/eyes_firstloop.wav-135f00eb080c2f8bf64b2349457c055e.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://cw8kuxiv30fh4"
|
||||
path="res://.godot/imported/fright.wav-0174c816078cec81baabdba1e8269e91.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/pacman/audio/fright.wav"
|
||||
dest_files=["res://.godot/imported/fright.wav-0174c816078cec81baabdba1e8269e91.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://drsvbhyuv3yp4"
|
||||
path="res://.godot/imported/fright_firstloop.wav-f882fcff985ed68cb9a48727df0588e7.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/pacman/audio/fright_firstloop.wav"
|
||||
dest_files=["res://.godot/imported/fright_firstloop.wav-f882fcff985ed68cb9a48727df0588e7.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://nqaa736y0aa2"
|
||||
path="res://.godot/imported/intermission.wav-9454a9c38b8a759357091a745dfdacc8.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/pacman/audio/intermission.wav"
|
||||
dest_files=["res://.godot/imported/intermission.wav-9454a9c38b8a759357091a745dfdacc8.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://ceg1xejgjr70f"
|
||||
path="res://.godot/imported/siren0.wav-c81bb85bf12b08da36d0bfd788c39b44.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/pacman/audio/siren0.wav"
|
||||
dest_files=["res://.godot/imported/siren0.wav-c81bb85bf12b08da36d0bfd788c39b44.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://dn36vo2x0ub13"
|
||||
path="res://.godot/imported/siren0_firstloop.wav-812fe5df64e7c2d782c1107cee0cb3ec.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/pacman/audio/siren0_firstloop.wav"
|
||||
dest_files=["res://.godot/imported/siren0_firstloop.wav-812fe5df64e7c2d782c1107cee0cb3ec.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://djv5wb36s67eb"
|
||||
path="res://.godot/imported/siren1.wav-cbe72c5a62b545c10c2aa59940f52d3d.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/pacman/audio/siren1.wav"
|
||||
dest_files=["res://.godot/imported/siren1.wav-cbe72c5a62b545c10c2aa59940f52d3d.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://c5oq5f1ly4ifq"
|
||||
path="res://.godot/imported/siren1_firstloop.wav-166981f88f13d9478d3107ed333295d3.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/pacman/audio/siren1_firstloop.wav"
|
||||
dest_files=["res://.godot/imported/siren1_firstloop.wav-166981f88f13d9478d3107ed333295d3.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://bdb6mkd0vbbe5"
|
||||
path="res://.godot/imported/siren2.wav-c006186d3fd86cbc8d3312d6a077819e.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/pacman/audio/siren2.wav"
|
||||
dest_files=["res://.godot/imported/siren2.wav-c006186d3fd86cbc8d3312d6a077819e.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://dx6gucejy14ii"
|
||||
path="res://.godot/imported/siren2_firstloop.wav-c5cd8e073ceeb2cf1d5f2a52b1591395.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/pacman/audio/siren2_firstloop.wav"
|
||||
dest_files=["res://.godot/imported/siren2_firstloop.wav-c5cd8e073ceeb2cf1d5f2a52b1591395.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://bk86t2emh1t0v"
|
||||
path="res://.godot/imported/siren3.wav-8a120be99cb0e0f3dfa8ad4aef35bd9c.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/pacman/audio/siren3.wav"
|
||||
dest_files=["res://.godot/imported/siren3.wav-8a120be99cb0e0f3dfa8ad4aef35bd9c.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://bksxvwormaye0"
|
||||
path="res://.godot/imported/siren3_firstloop.wav-78226539db9aee757f5df5e53e8e99e0.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/pacman/audio/siren3_firstloop.wav"
|
||||
dest_files=["res://.godot/imported/siren3_firstloop.wav-78226539db9aee757f5df5e53e8e99e0.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://08e4iamm6wor"
|
||||
path="res://.godot/imported/siren4.wav-c19a24ba9d0aa6a0dbdea17d8bc123e5.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/pacman/audio/siren4.wav"
|
||||
dest_files=["res://.godot/imported/siren4.wav-c19a24ba9d0aa6a0dbdea17d8bc123e5.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://cln07kvdnwb4e"
|
||||
path="res://.godot/imported/siren4_firstloop.wav-f3e6af1140d2a0e60d5bce993412e499.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/pacman/audio/siren4_firstloop.wav"
|
||||
dest_files=["res://.godot/imported/siren4_firstloop.wav-f3e6af1140d2a0e60d5bce993412e499.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://di0ql7bdioby4"
|
||||
path="res://.godot/imported/start.wav-03500820d755c9b2b3fd094e7dac60a6.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/pacman/audio/start.wav"
|
||||
dest_files=["res://.godot/imported/start.wav-03500820d755c9b2b3fd094e7dac60a6.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,82 @@
|
||||
extends ArcadeGame
|
||||
const Model = preload("res://games/pacman/pacman_model.gd")
|
||||
const View = preload("res://games/pacman/pacman_view.gd")
|
||||
const Sound = preload("res://games/pacman/pacman_audio.gd")
|
||||
|
||||
@export_group("Room Objective")
|
||||
## Zero means unlimited boards. Set a score target or use arcade mode.
|
||||
@export_range(0, 100, 1) var boards_to_win: int = 1
|
||||
@export_range(0, 1000000, 100) var score_to_win: int = 0
|
||||
@export_range(1, 9, 1) var starting_lives: int = 3
|
||||
@export var extra_life_score: int = 10000
|
||||
var model = Model.new()
|
||||
var view
|
||||
var audio
|
||||
var accumulator := 0.0
|
||||
|
||||
func _ready() -> void:
|
||||
view = View.new()
|
||||
view.model = model
|
||||
add_child(view)
|
||||
audio = Sound.new()
|
||||
add_child(audio)
|
||||
model.sound_requested.connect(audio.play_effect)
|
||||
model.finished.connect(_on_finished)
|
||||
_apply_config()
|
||||
|
||||
func configure(config: Dictionary = {}) -> void:
|
||||
super.configure(config)
|
||||
_apply_config()
|
||||
|
||||
func _apply_config() -> void:
|
||||
model.boards_to_win = maxi(0, int(run_config.get("boards_to_win", boards_to_win)))
|
||||
model.score_to_win = maxi(0, int(run_config.get("score_to_win", score_to_win)))
|
||||
model.initial_lives = clampi(int(run_config.get("starting_lives", starting_lives)), 1, 9)
|
||||
model.extra_life_score = maxi(0, int(run_config.get("extra_life_score", extra_life_score)))
|
||||
|
||||
func start_game() -> bool:
|
||||
if is_running: return false
|
||||
_apply_config()
|
||||
audio.stop_all()
|
||||
model.start()
|
||||
accumulator = 0.0
|
||||
return super.start_game()
|
||||
|
||||
func reset_game() -> void:
|
||||
audio.stop_all()
|
||||
model.reset()
|
||||
accumulator = 0.0
|
||||
super.reset_game()
|
||||
|
||||
func win_game() -> void:
|
||||
if not is_running: return
|
||||
model.state = "won"
|
||||
audio.stop_all()
|
||||
super.win_game()
|
||||
|
||||
func lose_game() -> void:
|
||||
if not is_running: return
|
||||
model.state = "lost"
|
||||
audio.stop_all()
|
||||
super.lose_game()
|
||||
|
||||
func _on_finished(won: bool) -> void:
|
||||
if won: win_game()
|
||||
else: lose_game()
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event.is_action_pressed("arcade_start"):
|
||||
start_game()
|
||||
get_viewport().set_input_as_handled()
|
||||
for pair in [["arcade_up", Vector2i.UP], ["arcade_left", Vector2i.LEFT], ["arcade_down", Vector2i.DOWN], ["arcade_right", Vector2i.RIGHT]]:
|
||||
if event.is_action_pressed(pair[0]): model.requested = pair[1]
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if not is_running:
|
||||
model.clock += delta
|
||||
if is_running:
|
||||
accumulator += minf(delta, 0.25)
|
||||
while accumulator >= 1.0 / 60.0 and is_running:
|
||||
model.update(1.0 / 60.0)
|
||||
accumulator -= 1.0 / 60.0
|
||||
if audio != null: audio.set_background(model.background_sound() if is_running else "")
|
||||
@@ -0,0 +1 @@
|
||||
uid://c375fg6kitvex
|
||||
@@ -0,0 +1,96 @@
|
||||
extends Node
|
||||
## Separate effects from ambience so pellet sounds never restart the siren.
|
||||
## Tune this to the end of the fourth wah in death_0.wav.
|
||||
const DEATH_INTRO_SECONDS := 1.35
|
||||
var death_cutoff: Timer
|
||||
var effects: Array[AudioStreamPlayer] = []
|
||||
var ambience: AudioStreamPlayer
|
||||
var streams := {}
|
||||
var background := ""
|
||||
var effect_cursor := 0
|
||||
var death_tail := false
|
||||
var intro_pending := false
|
||||
|
||||
func _ready() -> void:
|
||||
death_cutoff = Timer.new()
|
||||
death_cutoff.one_shot = true
|
||||
add_child(death_cutoff)
|
||||
death_cutoff.timeout.connect(_death_finished)
|
||||
for i in range(4):
|
||||
var voice := AudioStreamPlayer.new()
|
||||
voice.volume_db = -10.0
|
||||
add_child(voice)
|
||||
effects.append(voice)
|
||||
ambience = AudioStreamPlayer.new()
|
||||
ambience.volume_db = -15.0
|
||||
add_child(ambience)
|
||||
ambience.finished.connect(_ambient_finished)
|
||||
effects[3].finished.connect(_death_finished)
|
||||
|
||||
func stream(name: String) -> AudioStream:
|
||||
if not streams.has(name):
|
||||
var path := "res://games/pacman/audio/%s.wav" % name
|
||||
if not ResourceLoader.exists(path): return null
|
||||
streams[name] = load(path)
|
||||
return streams[name]
|
||||
|
||||
func play_effect(name: String) -> void:
|
||||
if name == "death":
|
||||
stop_all()
|
||||
death_tail = true
|
||||
effects[3].stream = stream("death_0")
|
||||
effects[3].play()
|
||||
death_cutoff.start(DEATH_INTRO_SECONDS)
|
||||
return
|
||||
var voice: AudioStreamPlayer = effects[0] if name.begins_with("eat_dot") else effects[1 + effect_cursor % 2]
|
||||
if not name.begins_with("eat_dot"): effect_cursor += 1
|
||||
voice.stream = stream(name)
|
||||
if voice.stream != null: voice.play()
|
||||
|
||||
func _death_finished() -> void:
|
||||
if not death_tail: return
|
||||
death_tail = false
|
||||
death_cutoff.stop()
|
||||
effects[3].stop()
|
||||
effects[3].stream = stream("death_1")
|
||||
effects[3].play()
|
||||
|
||||
func set_background(name: String) -> void:
|
||||
if name == background: return
|
||||
background = name
|
||||
ambience.stop()
|
||||
intro_pending = false
|
||||
if name.is_empty(): return
|
||||
var intro := stream(name + "_firstloop")
|
||||
# Join PCM samples before playback: no finished-signal gap at the intro seam.
|
||||
ambience.stream = _loop_stream(name, intro)
|
||||
if ambience.stream != null: ambience.play()
|
||||
|
||||
func _loop_stream(name: String, intro: AudioStream = null) -> AudioStream:
|
||||
var source := stream(name)
|
||||
if source is AudioStreamWAV:
|
||||
var loop: AudioStreamWAV = source.duplicate()
|
||||
loop.loop_mode = AudioStreamWAV.LOOP_FORWARD
|
||||
loop.loop_begin = 0
|
||||
loop.loop_end = roundi(loop.get_length() * loop.mix_rate)
|
||||
if intro is AudioStreamWAV and intro.format == loop.format and intro.mix_rate == loop.mix_rate and intro.stereo == loop.stereo:
|
||||
var intro_frames := roundi(intro.get_length() * intro.mix_rate)
|
||||
loop.data = intro.data + loop.data
|
||||
loop.loop_begin = intro_frames
|
||||
loop.loop_end += intro_frames
|
||||
return loop
|
||||
return source
|
||||
|
||||
func _ambient_finished() -> void:
|
||||
if background.is_empty(): return
|
||||
intro_pending = false
|
||||
ambience.stream = _loop_stream(background)
|
||||
if ambience.stream != null: ambience.play()
|
||||
|
||||
func stop_all() -> void:
|
||||
background = ""
|
||||
death_tail = false
|
||||
intro_pending = false
|
||||
if death_cutoff != null: death_cutoff.stop()
|
||||
if ambience != null: ambience.stop()
|
||||
for voice in effects: voice.stop()
|
||||
@@ -0,0 +1 @@
|
||||
uid://bs1fyngribtup
|
||||
@@ -0,0 +1,42 @@
|
||||
@tool
|
||||
extends "res://games/pacman/pacman_view.gd"
|
||||
## A static design-time board, using the same renderer as the running game.
|
||||
## No gameplay model, audio, input handlers, or room events are instantiated.
|
||||
|
||||
func _ready() -> void:
|
||||
super._ready()
|
||||
if not Engine.is_editor_hint():
|
||||
hide()
|
||||
set_process(false)
|
||||
return
|
||||
var preview_ghosts: Array[Dictionary] = []
|
||||
for i in range(4):
|
||||
var ghost := Maze.actor([Maze.DOOR, Vector2i(13, 14), Vector2i(11, 14), Vector2i(15, 14)][i], Vector2i.LEFT)
|
||||
ghost["status"] = "active" if i == 0 else "house"
|
||||
ghost["frightened"] = false
|
||||
preview_ghosts.append(ghost)
|
||||
model = {
|
||||
"state": "ready", "phase_time": 2.0, "clock": 0.0,
|
||||
"score": 0, "high_score": 0, "level": 1, "lives": 3,
|
||||
"dots": Maze.pellets(), "player": Maze.actor(Maze.PLAYER_START, Vector2i.LEFT),
|
||||
"ghosts": preview_ghosts, "fruit_time": 0.0, "fright_time": 0.0,
|
||||
"popup_time": 0.0,
|
||||
}
|
||||
_update_preview()
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if Engine.is_editor_hint(): _update_preview()
|
||||
|
||||
func _update_preview() -> void:
|
||||
# Match runtime placement inside the configured game viewport, independently
|
||||
# of the editor's current panel size, zoom, and pan.
|
||||
var screen_size := Vector2(
|
||||
ProjectSettings.get_setting("display/window/size/viewport_width", 1920),
|
||||
ProjectSettings.get_setting("display/window/size/viewport_height", 1080)
|
||||
)
|
||||
var factor := minf(screen_size.x / GAME_RECT.size.x, screen_size.y / GAME_RECT.size.y)
|
||||
if factor >= 1.0: factor = floorf(factor)
|
||||
scale = Vector2.ONE * factor
|
||||
position = ((screen_size - GAME_RECT.size * factor) * 0.5).floor()
|
||||
model.lives = int(get_parent().get("starting_lives"))
|
||||
queue_redraw()
|
||||
@@ -0,0 +1 @@
|
||||
uid://c0kalctx5x8nf
|
||||
@@ -0,0 +1,78 @@
|
||||
extends RefCounted
|
||||
## Logical map: artwork is never used as collision geometry.
|
||||
const ROWS := [
|
||||
"############################",
|
||||
"#............##............#",
|
||||
"#.####.#####.##.#####.####.#",
|
||||
"#o####.#####.##.#####.####o#",
|
||||
"#.####.#####.##.#####.####.#",
|
||||
"#..........................#",
|
||||
"#.####.##.########.##.####.#",
|
||||
"#.####.##.########.##.####.#",
|
||||
"#......##....##....##......#",
|
||||
"######.##### ## #####.######",
|
||||
" #.##### ## #####.# ",
|
||||
" #.## ##.# ",
|
||||
" #.## ###--### ##.# ",
|
||||
"######.## # # ##.######",
|
||||
" . # # . ",
|
||||
"######.## # # ##.######",
|
||||
" #.## ######## ##.# ",
|
||||
" #.## ##.# ",
|
||||
" #.## ######## ##.# ",
|
||||
"######.## ######## ##.######",
|
||||
"#............##............#",
|
||||
"#.####.#####.##.#####.####.#",
|
||||
"#.####.#####.##.#####.####.#",
|
||||
"#o..##....... .......##..o#",
|
||||
"###.##.##.########.##.##.###",
|
||||
"###.##.##.########.##.##.###",
|
||||
"#......##....##....##......#",
|
||||
"#.##########.##.##########.#",
|
||||
"#.##########.##.##########.#",
|
||||
"#..........................#",
|
||||
"############################",
|
||||
]
|
||||
const DIRS := [Vector2i.UP, Vector2i.LEFT, Vector2i.DOWN, Vector2i.RIGHT]
|
||||
const PLAYER_START := Vector2i(13, 23)
|
||||
const DOOR := Vector2i(13, 11)
|
||||
|
||||
static func neighbor(tile: Vector2i, direction: Vector2i) -> Vector2i:
|
||||
var result := tile + direction
|
||||
if result.y == 14:
|
||||
result.x = posmod(result.x, 28)
|
||||
return result
|
||||
|
||||
static func walkable(tile: Vector2i, eyes := false) -> bool:
|
||||
if tile.x < 0 or tile.x >= 28 or tile.y < 0 or tile.y >= 31:
|
||||
return false
|
||||
# Blank regions outside the maze are not corridors.
|
||||
if tile.y != 14 and tile.y >= 9 and tile.y <= 19 and (tile.x < 5 or tile.x > 22):
|
||||
return false
|
||||
var cell: String = ROWS[tile.y][tile.x]
|
||||
return cell != "#" and (cell != "-" or eyes)
|
||||
|
||||
static func pellets() -> Dictionary:
|
||||
var result := {}
|
||||
for y in range(31):
|
||||
for x in range(28):
|
||||
if ROWS[y][x] in [".", "o"]:
|
||||
result[Vector2i(x, y)] = ROWS[y][x] == "o"
|
||||
return result
|
||||
|
||||
static func actor(tile: Vector2i, direction: Vector2i) -> Dictionary:
|
||||
return {
|
||||
"tile": tile,
|
||||
"next": tile,
|
||||
"progress": 0.0,
|
||||
"dir": direction,
|
||||
"spawn_offset": Vector2.ZERO,
|
||||
}
|
||||
|
||||
static func position(actor_data: Dictionary) -> Vector2:
|
||||
var offset := Vector2(actor_data.next - actor_data.tile)
|
||||
if abs(offset.x) > 1.0:
|
||||
offset.x = float(actor_data.dir.x)
|
||||
# Actor centers use the logical maze lane. Rendering and collision share
|
||||
# this exact position so a sprite cannot be shifted into a neighboring wall.
|
||||
return Vector2(actor_data.tile) + actor_data.get("spawn_offset", Vector2.ZERO) + offset * actor_data.progress
|
||||
@@ -0,0 +1 @@
|
||||
uid://ccswn2ej4ufva
|
||||
@@ -0,0 +1,378 @@
|
||||
extends RefCounted
|
||||
const Maze = preload("res://games/pacman/pacman_maze.gd")
|
||||
const Rules = preload("res://games/pacman/pacman_rules.gd")
|
||||
signal sound_requested(name: String)
|
||||
signal finished(won: bool)
|
||||
|
||||
var state := "idle"
|
||||
var phase_time := 0.0
|
||||
var clock := 0.0
|
||||
var player_anim_time := 0.0
|
||||
var score := 0
|
||||
var high_score := 0
|
||||
var lives := 3
|
||||
var level := 1
|
||||
var boards_cleared := 0
|
||||
var boards_to_win := 1
|
||||
var score_to_win := 0
|
||||
var initial_lives := 3
|
||||
var extra_life_score := 10000
|
||||
var extra_awarded := false
|
||||
var dots := {}
|
||||
var player := {}
|
||||
var ghosts: Array[Dictionary] = []
|
||||
var requested := Vector2i.LEFT
|
||||
var fright_time := 0.0
|
||||
var ghost_chain := 0
|
||||
var mode_index := 0
|
||||
var mode_time := 0.0
|
||||
var pellet_pause := 0.0
|
||||
var release_idle := 0.0
|
||||
var release_counts := [0, 0, 0, 0]
|
||||
var after_death := false
|
||||
var global_dots := 0
|
||||
var fruit_time := 0.0
|
||||
var fruit_spawned := 0
|
||||
var popup_time := 0.0
|
||||
var popup_text := ""
|
||||
var popup_position := Vector2.ZERO
|
||||
var rng := RandomNumberGenerator.new()
|
||||
const DEATH_PAUSE_SECONDS := 1.5
|
||||
const DEATH_ANIMATION_SECONDS := 1.55
|
||||
const DEATH_POP_SECONDS := 0.24
|
||||
|
||||
func _init() -> void:
|
||||
reset()
|
||||
|
||||
func reset() -> void:
|
||||
state = "idle"
|
||||
player_anim_time = 0.0
|
||||
score = 0
|
||||
lives = initial_lives
|
||||
level = 1
|
||||
boards_cleared = 0
|
||||
extra_awarded = false
|
||||
dots = Maze.pellets()
|
||||
fruit_spawned = 0
|
||||
after_death = false
|
||||
_reset_actors()
|
||||
|
||||
func start() -> void:
|
||||
reset()
|
||||
state = "ready"
|
||||
phase_time = 4.25
|
||||
sound_requested.emit("start")
|
||||
|
||||
func _reset_actors() -> void:
|
||||
player = Maze.actor(Maze.PLAYER_START, Vector2i.LEFT)
|
||||
requested = Vector2i.LEFT
|
||||
ghosts.clear()
|
||||
for i in range(4):
|
||||
var g := Maze.actor([Maze.DOOR, Vector2i(13, 14), Vector2i(11, 14), Vector2i(15, 14)][i], Vector2i.LEFT)
|
||||
# Center the opening formation like the arcade cabinet. This is cleared
|
||||
# on the first gameplay movement so normal lane centers stay unchanged.
|
||||
g["spawn_offset"] = Vector2(0.5, 0.0)
|
||||
g["status"] = "active" if i == 0 else "house"
|
||||
g["frightened"] = false
|
||||
g["reverse"] = false
|
||||
ghosts.append(g)
|
||||
mode_index = 0
|
||||
mode_time = 0.0
|
||||
fright_time = 0.0
|
||||
ghost_chain = 0
|
||||
pellet_pause = 0.0
|
||||
release_idle = 0.0
|
||||
release_counts = [0, 0, 0, 0]
|
||||
global_dots = 0
|
||||
fruit_time = 0.0
|
||||
popup_time = 0.0
|
||||
rng.seed = 12345
|
||||
|
||||
func update(dt: float) -> void:
|
||||
# Keep every clock-driven visual frozen during the pre-death pause. The
|
||||
# phase timer still advances below, so the death animation starts on time.
|
||||
if state != "death_pause":
|
||||
clock += dt
|
||||
popup_time = maxf(0.0, popup_time - dt)
|
||||
if state in ["idle", "won", "lost"]:
|
||||
return
|
||||
if state != "playing":
|
||||
phase_time -= dt
|
||||
if phase_time <= 0.0:
|
||||
match state:
|
||||
"ready", "ghost_pause": state = "playing"
|
||||
"death_pause":
|
||||
state = "dying"
|
||||
phase_time = DEATH_ANIMATION_SECONDS
|
||||
sound_requested.emit("death")
|
||||
"intermission": _next_level()
|
||||
"dying":
|
||||
state = "death_pop"
|
||||
phase_time = DEATH_POP_SECONDS
|
||||
"death_pop":
|
||||
lives -= 1
|
||||
if lives <= 0:
|
||||
_end(false)
|
||||
else:
|
||||
after_death = true
|
||||
_reset_actors()
|
||||
state = "ready"
|
||||
phase_time = 2.0
|
||||
"clear":
|
||||
boards_cleared += 1
|
||||
if boards_to_win > 0 and boards_cleared >= boards_to_win:
|
||||
_end(true)
|
||||
elif level in [2, 5] or (level >= 9 and (level - 9) % 4 == 0):
|
||||
state = "intermission"
|
||||
phase_time = 5.28
|
||||
sound_requested.emit("intermission")
|
||||
else:
|
||||
_next_level()
|
||||
return
|
||||
if fright_time > 0:
|
||||
fright_time = maxf(0.0, fright_time - dt)
|
||||
if fright_time == 0.0:
|
||||
for g in ghosts: g.frightened = false
|
||||
else:
|
||||
mode_time += dt
|
||||
var schedule: Array = Rules.schedule(level)
|
||||
if mode_time >= schedule[mode_index]:
|
||||
mode_time = 0.0
|
||||
mode_index = mini(mode_index + 1, 7)
|
||||
for g in ghosts:
|
||||
if g.status == "active": g.reverse = true
|
||||
fruit_time = maxf(0.0, fruit_time - dt)
|
||||
release_idle += dt
|
||||
_release_ghosts()
|
||||
if pellet_pause > 0.0:
|
||||
pellet_pause -= dt
|
||||
else:
|
||||
_move_player(_player_speed() * dt)
|
||||
if state != "playing": return
|
||||
_check_collisions()
|
||||
if state != "playing": return
|
||||
# Animate Pac-Man only while he is actually travelling. This keeps the
|
||||
# mouth cadence steady when the global clock is affected by pauses or
|
||||
# unrelated game state changes.
|
||||
if pellet_pause <= 0.0 and player.tile != player.next:
|
||||
player_anim_time += dt
|
||||
elif player.tile == player.next:
|
||||
player_anim_time = 0.0
|
||||
for i in range(4):
|
||||
_move_ghost(i, _ghost_speed(i) * dt)
|
||||
_check_collisions()
|
||||
if state != "playing": return
|
||||
|
||||
func _player_speed() -> float:
|
||||
var ratio := 0.8 if level == 1 else (0.9 if level < 5 or level >= 21 else 1.0)
|
||||
if fright_time > 0.0: ratio = 0.9 if level == 1 else (0.95 if level < 5 else 1.0)
|
||||
return 9.46875 * ratio
|
||||
|
||||
func _ghost_speed(index: int) -> float:
|
||||
var g: Dictionary = ghosts[index]
|
||||
if g.status == "eyes": return 15.0
|
||||
if g.status in ["house", "leaving", "entering"]: return 4.0
|
||||
if g.tile.y == 14 and (g.tile.x < 6 or g.tile.x > 21):
|
||||
return 9.46875 * (0.4 if level == 1 else (0.45 if level < 5 else 0.5))
|
||||
if g.frightened: return 9.46875 * (0.5 if level == 1 else (0.55 if level < 5 else 0.6))
|
||||
var ratio := 0.75 if level == 1 else (0.85 if level < 5 else 0.95)
|
||||
if index == 0 and _elroy_active():
|
||||
ratio += 0.05 if dots.size() > Rules.elroy_threshold(level) / 2 else 0.1
|
||||
return 9.46875 * ratio
|
||||
|
||||
func _elroy_active() -> bool:
|
||||
return dots.size() <= Rules.elroy_threshold(level) and (not after_death or ghosts[3].status != "house")
|
||||
|
||||
func _move_player(distance: float) -> void:
|
||||
# Reversing is legal between tile centers. Other turns are buffered.
|
||||
if player.tile != player.next and requested == -player.dir:
|
||||
var previous: Vector2i = player.tile
|
||||
player.tile = player.next
|
||||
player.next = previous
|
||||
player.progress = 1.0 - player.progress
|
||||
player.dir = requested
|
||||
while distance > 0.000001:
|
||||
if player.tile == player.next:
|
||||
_consume()
|
||||
if state != "playing" or pellet_pause > 0.0: return
|
||||
if Maze.walkable(Maze.neighbor(player.tile, requested)):
|
||||
player.dir = requested
|
||||
var destination: Vector2i = Maze.neighbor(player.tile, player.dir)
|
||||
if not Maze.walkable(destination): return
|
||||
player.next = destination
|
||||
var step := minf(distance, 1.0 - player.progress)
|
||||
player.progress += step
|
||||
distance -= step
|
||||
if player.progress >= 0.999999:
|
||||
player.tile = player.next
|
||||
player.progress = 0.0
|
||||
_consume()
|
||||
if pellet_pause > 0.0 or state != "playing": return
|
||||
|
||||
func _consume() -> void:
|
||||
if dots.has(player.tile):
|
||||
var power: bool = dots[player.tile]
|
||||
dots.erase(player.tile)
|
||||
_add_score(50 if power else 10)
|
||||
if state != "playing": return
|
||||
pellet_pause = (3.0 if power else 1.0) / 60.0
|
||||
sound_requested.emit("eat_dot_%d" % (dots.size() % 2))
|
||||
release_idle = 0.0
|
||||
if after_death:
|
||||
global_dots += 1
|
||||
else:
|
||||
for i in range(1, 4):
|
||||
if ghosts[i].status == "house":
|
||||
release_counts[i] += 1
|
||||
break
|
||||
if power:
|
||||
fright_time = Rules.frightened_seconds(level)
|
||||
ghost_chain = 0
|
||||
for g in ghosts:
|
||||
if g.status not in ["eyes", "entering"]:
|
||||
g.frightened = fright_time > 0.0
|
||||
if g.status == "active": g.reverse = true
|
||||
var eaten := 244 - dots.size()
|
||||
if (eaten >= 70 and fruit_spawned == 0) or (eaten >= 170 and fruit_spawned == 1):
|
||||
fruit_spawned += 1
|
||||
fruit_time = rng.randf_range(9.0, 10.0)
|
||||
if dots.is_empty():
|
||||
state = "clear"
|
||||
phase_time = 2.5
|
||||
if fruit_time > 0 and player.tile in [Vector2i(13, 17), Vector2i(14, 17)]:
|
||||
fruit_time = 0.0
|
||||
var points: int = Rules.fruit_score(level)
|
||||
_add_score(points)
|
||||
_popup(str(points), Maze.position(player))
|
||||
sound_requested.emit("eat_fruit")
|
||||
|
||||
func _add_score(points: int) -> void:
|
||||
score += points
|
||||
high_score = maxi(high_score, score)
|
||||
if not extra_awarded and extra_life_score > 0 and score >= extra_life_score:
|
||||
extra_awarded = true
|
||||
lives += 1
|
||||
sound_requested.emit("extend")
|
||||
if score_to_win > 0 and score >= score_to_win: _end(true)
|
||||
|
||||
func _release_ghosts() -> void:
|
||||
# Release one ghost at a time; returning ghosts take the same door route.
|
||||
for g in ghosts:
|
||||
if g.status in ["leaving", "entering"]: return
|
||||
for i in range(1, 4):
|
||||
if ghosts[i].status != "house": continue
|
||||
var threshold: int = [0, 0, 30 if level == 1 else 0, 60 if level == 1 else (50 if level == 2 else 0)][i]
|
||||
var ready: bool = release_counts[i] >= threshold
|
||||
if after_death: ready = global_dots >= [0, 7, 17, 32][i]
|
||||
if ready or release_idle >= (4.0 if level < 5 else 3.0):
|
||||
ghosts[i].status = "leaving"
|
||||
release_idle = 0.0
|
||||
return
|
||||
|
||||
func _move_ghost(index: int, distance: float) -> void:
|
||||
var g: Dictionary = ghosts[index]
|
||||
g["spawn_offset"] = Vector2.ZERO
|
||||
if g.status == "house": return
|
||||
while distance > 0.000001:
|
||||
if g.tile == g.next:
|
||||
if g.status == "eyes" and g.tile == Maze.DOOR:
|
||||
g.status = "entering"
|
||||
if g.status == "entering" and g.tile == Vector2i(13, 14):
|
||||
g.status = "leaving"
|
||||
g.frightened = false
|
||||
if g.status == "leaving" and g.tile == Maze.DOOR:
|
||||
g.status = "active"
|
||||
g.dir = Vector2i.LEFT
|
||||
var direction := Vector2i.ZERO
|
||||
if g.status == "leaving":
|
||||
direction = Vector2i(signi(13 - g.tile.x), 0) if g.tile.x != 13 else Vector2i.UP
|
||||
elif g.status == "entering": direction = Vector2i.DOWN
|
||||
elif g.reverse:
|
||||
direction = -g.dir
|
||||
g.reverse = false
|
||||
else: direction = _ghost_direction(index)
|
||||
if direction == Vector2i.ZERO: return
|
||||
g.dir = direction
|
||||
g.next = Maze.neighbor(g.tile, direction)
|
||||
var step := minf(distance, 1.0 - g.progress)
|
||||
g.progress += step
|
||||
distance -= step
|
||||
if g.progress >= 0.999999:
|
||||
g.tile = g.next
|
||||
g.progress = 0.0
|
||||
|
||||
func _ghost_direction(index: int) -> Vector2i:
|
||||
var g: Dictionary = ghosts[index]
|
||||
var target: Vector2i = Rules.SCATTER[index]
|
||||
if g.status == "eyes": target = Maze.DOOR
|
||||
elif mode_index % 2 == 1 or (index == 0 and _elroy_active()):
|
||||
target = Rules.target(index, player.tile, player.dir, ghosts[0].tile, g.tile)
|
||||
var candidates: Array[Vector2i] = []
|
||||
for direction in Maze.DIRS:
|
||||
if direction == -g.dir: continue
|
||||
if not Maze.walkable(Maze.neighbor(g.tile, direction)): continue
|
||||
# Original maze disallows upward turns at these junctions in normal modes.
|
||||
if g.status == "active" and not g.frightened and direction == Vector2i.UP and g.tile in [Vector2i(12, 11), Vector2i(15, 11), Vector2i(12, 23), Vector2i(15, 23)]: continue
|
||||
candidates.append(direction)
|
||||
if candidates.is_empty():
|
||||
return -g.dir if Maze.walkable(Maze.neighbor(g.tile, -g.dir)) else Vector2i.ZERO
|
||||
if g.frightened and g.status == "active": return candidates[rng.randi_range(0, candidates.size() - 1)]
|
||||
var chosen: Vector2i = candidates[0]
|
||||
var best := INF
|
||||
for direction in candidates:
|
||||
var test: Vector2i = Maze.neighbor(g.tile, direction)
|
||||
var distance := Vector2(test).distance_squared_to(Vector2(target))
|
||||
if distance < best:
|
||||
best = distance
|
||||
chosen = direction
|
||||
return chosen
|
||||
|
||||
func _check_collisions() -> void:
|
||||
for g in ghosts:
|
||||
if g.status != "active": continue
|
||||
var difference: Vector2 = Maze.position(g) - Maze.position(player)
|
||||
if absf(difference.x) > 14.0: difference.x = 28.0 - absf(difference.x)
|
||||
if difference.length_squared() > 0.45 * 0.45: continue
|
||||
if g.frightened:
|
||||
g.status = "eyes"
|
||||
g.frightened = false
|
||||
g.reverse = false
|
||||
var points := 200 * (1 << mini(ghost_chain, 3))
|
||||
ghost_chain += 1
|
||||
_add_score(points)
|
||||
if state != "playing": return
|
||||
_popup(str(points), Maze.position(g))
|
||||
state = "ghost_pause"
|
||||
phase_time = 0.6
|
||||
sound_requested.emit("eat_ghost")
|
||||
else:
|
||||
state = "death_pause"
|
||||
phase_time = DEATH_PAUSE_SECONDS
|
||||
fruit_time = 0.0
|
||||
return
|
||||
|
||||
func _next_level() -> void:
|
||||
level += 1
|
||||
dots = Maze.pellets()
|
||||
fruit_spawned = 0
|
||||
after_death = false
|
||||
_reset_actors()
|
||||
state = "ready"
|
||||
phase_time = 2.0
|
||||
|
||||
func _popup(text: String, pos: Vector2) -> void:
|
||||
popup_text = text
|
||||
popup_position = pos
|
||||
popup_time = 1.0
|
||||
|
||||
func _end(won: bool) -> void:
|
||||
if state in ["won", "lost"]: return
|
||||
state = "won" if won else "lost"
|
||||
finished.emit(won)
|
||||
|
||||
func background_sound() -> String:
|
||||
if state != "playing": return ""
|
||||
for g in ghosts:
|
||||
if g.status in ["eyes", "entering"]: return "eyes"
|
||||
if fright_time > 0.0: return "fright"
|
||||
return "siren%d" % mini(4, int((244 - dots.size()) / 50.0))
|
||||
@@ -0,0 +1 @@
|
||||
uid://1wwerfcb54mj
|
||||
@@ -0,0 +1,37 @@
|
||||
extends RefCounted
|
||||
## Tables and target rules are based on https://pacman.holenet.info/.
|
||||
const SCATTER := [Vector2i(25, -3), Vector2i(2, -3), Vector2i(27, 32), Vector2i(0, 32)]
|
||||
const FRIGHT := [6.0, 5.0, 4.0, 3.0, 2.0, 5.0, 2.0, 2.0, 1.0, 5.0, 2.0, 1.0, 1.0, 3.0, 1.0, 1.0, 0.0, 1.0]
|
||||
|
||||
static func frightened_seconds(level: int) -> float:
|
||||
return FRIGHT[level - 1] if level <= FRIGHT.size() else 0.0
|
||||
|
||||
static func schedule(level: int) -> Array:
|
||||
if level == 1:
|
||||
return [7.0, 20.0, 7.0, 20.0, 5.0, 20.0, 5.0, INF]
|
||||
return [7.0 if level < 5 else 5.0, 20.0, 7.0 if level < 5 else 5.0, 20.0, 5.0, 1033.0 if level < 5 else 1037.0, 1.0 / 60.0, INF]
|
||||
|
||||
static func target(index: int, player_tile: Vector2i, direction: Vector2i, blinky: Vector2i, ghost_tile: Vector2i) -> Vector2i:
|
||||
match index:
|
||||
0: return player_tile
|
||||
1: return player_tile + direction * 4 + (Vector2i.LEFT * 4 if direction == Vector2i.UP else Vector2i.ZERO)
|
||||
2:
|
||||
var ahead := player_tile + direction * 2 + (Vector2i.LEFT * 2 if direction == Vector2i.UP else Vector2i.ZERO)
|
||||
return ahead * 2 - blinky
|
||||
_: return player_tile if Vector2(ghost_tile).distance_squared_to(Vector2(player_tile)) >= 64.0 else SCATTER[3]
|
||||
|
||||
static func elroy_threshold(level: int) -> int:
|
||||
if level == 1: return 20
|
||||
if level == 2: return 30
|
||||
if level <= 5: return 40
|
||||
if level <= 8: return 50
|
||||
if level <= 11: return 60
|
||||
if level <= 14: return 80
|
||||
if level <= 18: return 100
|
||||
return 120
|
||||
|
||||
static func fruit_index(level: int) -> int:
|
||||
return mini(7, int(level / 2.0))
|
||||
|
||||
static func fruit_score(level: int) -> int:
|
||||
return [100, 300, 500, 700, 1000, 2000, 3000, 5000][fruit_index(level)]
|
||||
@@ -0,0 +1 @@
|
||||
uid://c5qe5vdxboj22
|
||||
@@ -0,0 +1,147 @@
|
||||
@tool
|
||||
extends Node2D
|
||||
const Maze = preload("res://games/pacman/pacman_maze.gd")
|
||||
const Rules = preload("res://games/pacman/pacman_rules.gd")
|
||||
const SHEET = preload("res://games/pacman/art/sprites.png")
|
||||
const MAZE_RECT := Rect2(0, 24, 224, 248)
|
||||
const GAME_RECT := Rect2(0, 0, 224, 288)
|
||||
const FONT = preload("res://games/pacman/art/text.png")
|
||||
const DEATH_ANIMATION_SECONDS := 1.55
|
||||
const DEATH_POP_SECONDS := 0.24
|
||||
var model
|
||||
var debug_grid := false
|
||||
|
||||
func _ready() -> void:
|
||||
texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
||||
|
||||
func _process(_dt: float) -> void:
|
||||
if Engine.is_editor_hint(): return
|
||||
var available := get_viewport_rect().size
|
||||
var factor := minf(available.x / 224.0, available.y / 288.0)
|
||||
if factor >= 1.0: factor = floorf(factor)
|
||||
scale = Vector2.ONE * factor
|
||||
position = ((available - Vector2(224, 288) * factor) * 0.5).floor()
|
||||
queue_redraw()
|
||||
|
||||
func _draw() -> void:
|
||||
draw_rect(_viewport_bounds(), Color.BLACK)
|
||||
if model == null: return
|
||||
if model.state == "intermission":
|
||||
_intermission()
|
||||
_draw_edge_mask(GAME_RECT)
|
||||
return
|
||||
var flash: bool = model.state == "clear" and int(model.phase_time * 5) % 2 == 0
|
||||
draw_texture_rect_region(SHEET, MAZE_RECT, Rect2(228, 0, 224, 248), Color(0.7, 0.7, 1.0) if flash else Color.WHITE)
|
||||
for tile in model.dots:
|
||||
var center := _pixel(Vector2(tile))
|
||||
if model.dots[tile]:
|
||||
if int(model.clock * 4) % 2 == 0: draw_circle(center, 3.5, Color("ffb8ae"))
|
||||
else: draw_rect(Rect2(center - Vector2.ONE, Vector2(2, 2)), Color("ffb8ae"))
|
||||
if model.fruit_time > 0:
|
||||
_sprite(Rect2(504 + Rules.fruit_index(model.level) * 16, 48, 16, 16), _actor_pixel(Vector2(13.5, 17)))
|
||||
if model.state not in ["ready", "dying", "death_pop", "lost", "won", "clear"]:
|
||||
for i in range(4): _ghost(i)
|
||||
if model.state not in ["ready", "death_pop", "lost", "won", "clear"]: _player()
|
||||
if model.state == "death_pop":
|
||||
_sprite(Rect2(664, 0, 16, 16), _actor_pixel(Maze.position(model.player)))
|
||||
if model.popup_time > 0: _text(model.popup_text, _pixel(model.popup_position) - Vector2(12, 4), Color.CYAN)
|
||||
_draw_edge_mask(MAZE_RECT)
|
||||
_text("1UP", Vector2(24, 0))
|
||||
_text("HIGH SCORE", Vector2(80, 0))
|
||||
_text(str(model.score).pad_zeros(2), Vector2(8, 8))
|
||||
_text(str(model.high_score).pad_zeros(2), Vector2(88, 8))
|
||||
for i in range(mini(model.lives - 1, 5)):
|
||||
_sprite(Rect2(600, 16, 16, 16), Vector2(16 + i * 16, 280))
|
||||
_text("L%d" % model.level, Vector2(192, 276), Color("ffb852"))
|
||||
match model.state:
|
||||
"idle":
|
||||
_banner("PRESS START", Color.YELLOW)
|
||||
_text("ARROWS TO MOVE", Vector2(56, 256))
|
||||
"ready":
|
||||
_banner("READY!", Color.YELLOW)
|
||||
_text("PLAYER ONE", Vector2(72, 112), Color("33aaff"))
|
||||
"won": _banner("YOU WIN!", Color.CYAN)
|
||||
"lost": _banner("GAME OVER", Color.RED)
|
||||
if debug_grid:
|
||||
for y in range(31):
|
||||
for x in range(28):
|
||||
if Maze.walkable(Vector2i(x,y)):
|
||||
draw_rect(Rect2(x * 8, y * 8 + 24, 8, 8), Color(0, 1, 0, 0.25), false)
|
||||
|
||||
func _viewport_bounds() -> Rect2:
|
||||
if Engine.is_editor_hint(): return GAME_RECT
|
||||
return get_global_transform_with_canvas().affine_inverse() * get_viewport_rect()
|
||||
|
||||
func _draw_edge_mask(visible_area: Rect2) -> void:
|
||||
var outer := _viewport_bounds().merge(visible_area)
|
||||
draw_rect(Rect2(outer.position, Vector2(visible_area.position.x - outer.position.x, outer.size.y)), Color.BLACK)
|
||||
draw_rect(Rect2(Vector2(visible_area.end.x, outer.position.y), Vector2(outer.end.x - visible_area.end.x, outer.size.y)), Color.BLACK)
|
||||
draw_rect(Rect2(Vector2(visible_area.position.x, outer.position.y), Vector2(visible_area.size.x, visible_area.position.y - outer.position.y)), Color.BLACK)
|
||||
draw_rect(Rect2(Vector2(visible_area.position.x, visible_area.end.y), Vector2(visible_area.size.x, outer.end.y - visible_area.end.y)), Color.BLACK)
|
||||
|
||||
func _pixel(tile: Vector2) -> Vector2:
|
||||
return Vector2(4, 28) + tile * 8.0
|
||||
|
||||
func _actor_pixel(tile: Vector2) -> Vector2:
|
||||
return _pixel(tile)
|
||||
|
||||
func _intermission() -> void:
|
||||
var elapsed: float = 5.28 - model.phase_time
|
||||
var frame := int(model.clock * 12) % 2
|
||||
if elapsed < 2.64:
|
||||
var x := 240.0 - elapsed * 105.0
|
||||
_sprite(Rect2(456 + frame * 16, 16, 16, 16), Vector2(x, 144))
|
||||
_sprite(Rect2(488 + frame * 16, 64, 16, 16), Vector2(x + 40, 144))
|
||||
else:
|
||||
var x := -40.0 + (elapsed - 2.64) * 110.0
|
||||
_sprite(Rect2(584 + frame * 16, 64, 16, 16), Vector2(x + 55, 144))
|
||||
draw_texture_rect_region(SHEET, Rect2(x - 16, 128, 32, 32), Rect2(504 + frame * 32, 16, 32, 32))
|
||||
_text("LEVEL %d CLEAR" % model.level, Vector2(56, 200), Color.YELLOW)
|
||||
|
||||
func _sprite(region: Rect2, center: Vector2) -> void:
|
||||
draw_texture_rect_region(SHEET, Rect2(center.round() - Vector2(8, 8), Vector2(16, 16)), region)
|
||||
if center.x < 8: draw_texture_rect_region(SHEET, Rect2(center.round() + Vector2(216, -8), Vector2(16, 16)), region)
|
||||
elif center.x > 216: draw_texture_rect_region(SHEET, Rect2(center.round() - Vector2(232, 8), Vector2(16, 16)), region)
|
||||
|
||||
func _player() -> void:
|
||||
var p: Dictionary = model.player
|
||||
var region := Rect2(456, 0, 16, 16)
|
||||
if model.state == "dying":
|
||||
var elapsed: float = DEATH_ANIMATION_SECONDS - model.phase_time
|
||||
var frame := clampi(int(elapsed / DEATH_ANIMATION_SECONDS * 11.0), 0, 10)
|
||||
region = Rect2(488 + frame * 16, 0, 16, 16)
|
||||
else:
|
||||
var row: int = {Vector2i.RIGHT: 0, Vector2i.LEFT: 1, Vector2i.UP: 2, Vector2i.DOWN: 3}.get(p.dir, 0)
|
||||
var frame: int = [0, 1, 2, 1][int(model.player_anim_time * 12.0) % 4] if p.tile != p.next else 1
|
||||
region = Rect2(456 + frame * 16, row * 16, 16, 16)
|
||||
_sprite(region, _actor_pixel(Maze.position(p)))
|
||||
|
||||
func _ghost(index: int) -> void:
|
||||
var g: Dictionary = model.ghosts[index]
|
||||
var direction: int = {Vector2i.RIGHT: 0, Vector2i.LEFT: 1, Vector2i.UP: 2, Vector2i.DOWN: 3}.get(g.dir, 0)
|
||||
var frame := int(model.clock * 8) % 2
|
||||
var region := Rect2(456 + direction * 32 + frame * 16, 64 + index * 16, 16, 16)
|
||||
if g.status in ["eyes", "entering"]: region = Rect2(584 + direction * 16, 80, 16, 16)
|
||||
elif g.frightened:
|
||||
var white: bool = model.fright_time < 2.0 and int(model.fright_time * 6) % 2 == 0
|
||||
region = Rect2(584 + frame * 16 + (32 if white else 0), 64, 16, 16)
|
||||
var center := _actor_pixel(Maze.position(g))
|
||||
if g.status == "house": center.y += sin(model.clock * 6 + index) * 2
|
||||
_sprite(region, center)
|
||||
|
||||
func _banner(message: String, color: Color) -> void:
|
||||
var width := message.length() * 8
|
||||
draw_rect(Rect2(112 - width / 2 - 4, 160, width + 8, 12), Color.BLACK)
|
||||
_text(message, Vector2(112 - width / 2, 162), color)
|
||||
|
||||
func _text(message: String, at: Vector2, color := Color.WHITE) -> void:
|
||||
for i in range(message.length()):
|
||||
var c := message[i]
|
||||
var region := Rect2()
|
||||
var code := c.unicode_at(0)
|
||||
if code >= 65 and code <= 79: region = Rect2((code - 65) * 8, 0, 8, 8)
|
||||
elif code >= 80 and code <= 90: region = Rect2((code - 80) * 8, 8, 8, 8)
|
||||
elif code >= 48 and code <= 57: region = Rect2((code - 48) * 8, 16, 8, 8)
|
||||
elif c == "!": region = Rect2(88, 8, 8, 8)
|
||||
else: continue
|
||||
draw_texture_rect_region(FONT, Rect2(at + Vector2(i * 8, 0), Vector2(8, 8)), region, color)
|
||||
@@ -0,0 +1 @@
|
||||
uid://df6mgds15wbar
|
||||
Reference in new issue
Block a user