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,58 @@
|
||||
# Robotron 2084 setup and fidelity notes
|
||||
|
||||
Robotron is installed in the project and registered in the Games database. Open Robotron.tscn and run the current scene to play. The game uses the framework's ArcadeGame start, win, loss, and reset signals. Existing cabinet assignments remain unchanged.
|
||||
|
||||
## Controls
|
||||
|
||||
- Enter, arcade_start, or gamepad Start begins a fresh game, including during a demonstration.
|
||||
- WASD moves; arrow keys fire independently in eight directions. Diagonal speed is normalized.
|
||||
- On a gamepad, the left stick moves and the right stick fires. Releasing the firing stick stops shooting.
|
||||
- The cabinet requires dual_joystick_8way and start_button capabilities. Add these to a cabinet profile only when that cabinet provides the matching controls.
|
||||
|
||||
The game installs default input actions at runtime only when they do not already exist. For a cabinet encoder, define robotron_move_left/right/up/down and robotron_fire_left/right/up/down in Project Settings, Input Map, with the desired bindings. Existing definitions take precedence. These use their own names so Robotron's arrow-key firing does not change the other games' movement actions. Physical cabinet/gamepad testing remains necessary.
|
||||
|
||||
## Scene settings
|
||||
|
||||
Select the Robotron root to change Waves To Win (default 1; set 0 for endless), Starting Lives (3), Difficulty (1), Extra Life Interval (25000; 0 disables), Sound Enabled, CRT Aspect, and Reduced Flashes. Save the scene before running through a cabinet. Settings are scene exports, not run_config overrides.
|
||||
|
||||
Waves To Win defaults to one for escape-room completion. Set it to 0 to keep playing through brain waves starting at 5 and tank waves starting at 7. Completing the configured wave emits game_won and holds the victory screen. Losing all lives emits game_lost, then returns to the idle sequence. Reset preserves only the session high score.
|
||||
|
||||
## Included gameplay
|
||||
|
||||
Independent eight-way movement and rapid directional fire, four simultaneous player shots, human rescue chains worth 1000 through 5000, repeat extra-life thresholds, electrodes, and increasingly crowded waves are implemented. Shooting does not hurt humans. Player respawn preserves surviving entities and grants a short protective interval.
|
||||
|
||||
Grunts chase the player and can destroy themselves on electrodes. Hulks survive shots, can be pushed back, and kill humans. Spheroids spawn enforcers; enforcers roam and fire. Quarks spawn tanks whose shells bounce off the arena boundaries. Brains take three hits, seek humans, convert them into hostile Progs, and launch homing cruise missiles. Enemy projectiles can be shot. Remaining hulks, electrodes, and humans do not prevent a wave from ending.
|
||||
|
||||
The idle loop has an animated title, instruction screen, enemy roster, silent gameplay demonstration, and session high-score screen. Demonstration scores cannot change the real high score, and the demo never emits room start/win/loss events. Start interrupts any idle phase and initializes a fresh run.
|
||||
|
||||
## Art and display
|
||||
|
||||
All four supplied PNGs are included unchanged. Runtime sprite crops preserve existing transparency and opaque white details. The title uses the supplied logo/credit artwork, with extracted Williams emblems moving around its perimeter and a cycling logo palette. The screenshot's fixed credit count is replaced with FREE PLAY. HUD and instruction lettering use an approximate pixel font because no complete font sheet was supplied.
|
||||
|
||||
The logical viewport is 292 by 240 with nearest-neighbor presentation and black margins. CRT Aspect presents it at 4:3; disable it for square source pixels. The renderer also provides an editor preview. Reduced Flashes stops the title movement/palette cycling and uses a steady accent color. Transition geometry still animates.
|
||||
|
||||
## Sound mapping
|
||||
|
||||
All 23 supplied WAVs are copied unchanged and loaded by the audio controller. Startup plays once when the runtime scene opens; game-start plays for a new run. Player-lazer accompanies firing, grunt-walk follows active grunts, and the human-save/human-die clips accompany rescue or loss. Robot-explode is the generic enemy destruction sound, with dedicated electrode, tank, brain, and spheroid destruction clips. Spheroid-spawn and quark-spawn accompany spawning. Enforcer-fire and tank-fire accompany projectiles, with tank-projectile-bounce on reflection. Human-to-prog and prog-transformation mark the two conversion stages. Player-death, extra-life, level-transition, and brain-wave-start follow their corresponding events. High-score-top plays when a completed losing run meets the session high score.
|
||||
|
||||
Sound effects are muted during demo play. A single audio channel plays effects: each accepted new sound stops the previous sound before starting, preventing overlapping echoes. Repeated effects remain rate limited to avoid rapid retriggering in crowded waves. Audio timing approximates the original sound hardware.
|
||||
|
||||
## Fidelity boundaries
|
||||
|
||||
This is a playable recreation, not ROM emulation. Wave populations and placements, spawn quotas, shot limits, movement speeds, collision radii, projectile paths, respawn protection, and timing are approximations that still need side-by-side tuning. In particular, the original Brain target-human bug and exact Enforcer firing behavior are not reproduced. Sprite variants are drawn from the supplied sheets but not every original palette/animation state is implemented. The story, roster, transition effects, and demo are recreations rather than exact playback of the original sequences. Play is one-player free play; coin accounting, alternating two-player games, initials entry, and persistent high scores are not implemented.
|
||||
|
||||
Visual reference: [supplied arcade footage](https://www.youtube.com/watch?v=l800GL6NQPY&t=28s). The native display dimensions and 4:3 presentation are corroborated by the [FinalBurn Neo machine definition](https://wiki.neo-source.com/database/view/arcade/robotron). Original manual references are catalogued in the [Robotron document archive](https://www.robotron-2084.co.uk/williams/documents/robotron).
|
||||
|
||||
## Validation
|
||||
|
||||
Automated checks pass for start guards, independent input actions, diagonal movement, firing release, rescue chains, extra-life thresholds, hulk knockback, human bullet immunity, electrode destruction, hulk kills, brain conversion, three-hit brains, spawners, shell bounces, brain/tank waves, wave completion, endless progression, room events, reset, and demo isolation. Tests ran in Godot 4.6.1 Compatibility; the project remains configured for 4.7. Title, roster, and gameplay captures were visually inspected. A desktop dense-wave render check reached 120 entities without a game-script failure; Raspberry Pi performance is unverified. Sandbox certificate-store and shader-cache warnings occurred, and desktop test shutdown reported a retained resource.
|
||||
|
||||
Run tests/robotron/check_robotron.gd with Godot --headless --path . --script. The same directory includes render_preview.gd and benchmark.gd.
|
||||
|
||||
Play checklist:
|
||||
- Move diagonally while firing in another direction, then release the firing control.
|
||||
- Rescue several humans and check increasing awards; lose a life and verify the chain resets.
|
||||
- Shoot a hulk and an electrode; confirm the hulk survives and the electrode disappears.
|
||||
- In endless mode, check brain conversion on wave 5 and tank shells on wave 7.
|
||||
- Start during the demo; confirm clean score/lives and no demo-triggered room events.
|
||||
- Verify cabinet input, sound levels, room completion, reset, and display scaling on the target machine.
|
||||
@@ -0,0 +1,33 @@
|
||||
[gd_scene format=3 uid="uid://bbe38558vhtp1"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dllelyq8hgx07" path="res://games/robotron/robotron.gd" id="1"]
|
||||
[ext_resource type="Script" uid="uid://dhxkitgihoy3d" path="res://games/robotron/board.gd" id="2"]
|
||||
[ext_resource type="Script" uid="uid://dxycwontqumst" path="res://games/robotron/display.gd" id="3"]
|
||||
[ext_resource type="Script" uid="uid://c1xbioehp2ccr" path="res://games/robotron/audio.gd" id="4"]
|
||||
[ext_resource type="Shader" uid="uid://bowtwv6ih8lj" path="res://games/robotron/title_palette.gdshader" id="5"]
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="TitlePalette"]
|
||||
shader = ExtResource("5")
|
||||
shader_parameter/animate_title = true
|
||||
shader_parameter/palette_step = 0
|
||||
|
||||
[node name="Robotron" type="Node" unique_id=593921370]
|
||||
script = ExtResource("1")
|
||||
waves_to_win = 0
|
||||
|
||||
[node name="PixelScreen" type="SubViewport" parent="." unique_id=230356224]
|
||||
disable_3d = true
|
||||
size = Vector2i(292, 240)
|
||||
render_target_update_mode = 4
|
||||
|
||||
[node name="Board" type="Node2D" parent="PixelScreen" unique_id=1006156591]
|
||||
texture_filter = 1
|
||||
script = ExtResource("2")
|
||||
|
||||
[node name="Display" type="Node2D" parent="." unique_id=97971978]
|
||||
texture_filter = 1
|
||||
material = SubResource("TitlePalette")
|
||||
script = ExtResource("3")
|
||||
|
||||
[node name="Audio" type="Node" parent="." unique_id=342326582]
|
||||
script = ExtResource("4")
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.0 KiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://h72sgu6d22pv"
|
||||
path="res://.godot/imported/humans.png-17e546e10e75756c93aca223d85bac7b.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/robotron/art/humans.png"
|
||||
dest_files=["res://.godot/imported/humans.png-17e546e10e75756c93aca223d85bac7b.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.1 KiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bgd3y37w5ea5q"
|
||||
path="res://.godot/imported/player.png-7bd78229e4ea71b047460c0ee57e74cb.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/robotron/art/player.png"
|
||||
dest_files=["res://.godot/imported/player.png-7bd78229e4ea71b047460c0ee57e74cb.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: 6.0 KiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cpd7qsb1dffdp"
|
||||
path="res://.godot/imported/robotrons.png-2115096d401f70a24af3c3b7502e23bf.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/robotron/art/robotrons.png"
|
||||
dest_files=["res://.godot/imported/robotrons.png-2115096d401f70a24af3c3b7502e23bf.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: 5.1 KiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://vnrsif2q6d5r"
|
||||
path="res://.godot/imported/titlescreen_example.png-60f0e70647e7cef015516ffef23c2f0f.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/robotron/art/titlescreen_example.png"
|
||||
dest_files=["res://.godot/imported/titlescreen_example.png-60f0e70647e7cef015516ffef23c2f0f.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
|
||||
@@ -0,0 +1,21 @@
|
||||
extends Node
|
||||
var tracks: Dictionary = {}
|
||||
var voice := AudioStreamPlayer.new()
|
||||
var gates: Dictionary = {}
|
||||
func _ready() -> void:
|
||||
for name in ["human-die","startup","electrode-explode","level-transition","spheroid-spawn","enforcer-fire","player-lazer","grunt-walk","human-save","robot-explode","player-death","game-start","tank-explode","tank-projectile-bounce","tank-fire","quark-spawn","brain-explode","prog-transformation","human-to-prog","extra-life","high-score-top","brain-wave-start","spheroid-pop"]:
|
||||
tracks[name] = load("res://games/robotron/audio/"+name+".wav")
|
||||
voice.volume_db = -15
|
||||
add_child(voice)
|
||||
func effect(name: String) -> void:
|
||||
if get_parent().demo or not get_parent().sound_enabled or not tracks.has(name): return
|
||||
var now := Time.get_ticks_msec()
|
||||
if now<int(gates.get(name,0)): return
|
||||
# Limit repeated triggers while allowing each accepted sound to replace the last.
|
||||
gates[name] = now+(90 if name=="player-lazer" else 180)
|
||||
voice.stop()
|
||||
voice.stream = tracks[name]
|
||||
voice.play()
|
||||
func stop_all() -> void:
|
||||
voice.stop()
|
||||
gates.clear()
|
||||
@@ -0,0 +1 @@
|
||||
uid://c1xbioehp2ccr
|
||||
Binary file not shown.
@@ -0,0 +1,24 @@
|
||||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://d1f8hmol234b7"
|
||||
path="res://.godot/imported/brain-explode.wav-eb257aa4582e409968f11cff0dfaecb2.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/robotron/audio/brain-explode.wav"
|
||||
dest_files=["res://.godot/imported/brain-explode.wav-eb257aa4582e409968f11cff0dfaecb2.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://dev6i7giud6ks"
|
||||
path="res://.godot/imported/brain-wave-start.wav-d5d31728a09fe9adc41f02c9be62d889.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/robotron/audio/brain-wave-start.wav"
|
||||
dest_files=["res://.godot/imported/brain-wave-start.wav-d5d31728a09fe9adc41f02c9be62d889.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://c510aq3wmxfr8"
|
||||
path="res://.godot/imported/electrode-explode.wav-94deded81f87387aa01d2cf8aaf59b0b.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/robotron/audio/electrode-explode.wav"
|
||||
dest_files=["res://.godot/imported/electrode-explode.wav-94deded81f87387aa01d2cf8aaf59b0b.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://fnwq6lxfkd64"
|
||||
path="res://.godot/imported/enforcer-fire.wav-fbdf919096441f6fdd3dc412c82b809f.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/robotron/audio/enforcer-fire.wav"
|
||||
dest_files=["res://.godot/imported/enforcer-fire.wav-fbdf919096441f6fdd3dc412c82b809f.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://c2lbp0mfeodwk"
|
||||
path="res://.godot/imported/extra-life.wav-19a70d7388e345af9f7f730bc4b90652.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/robotron/audio/extra-life.wav"
|
||||
dest_files=["res://.godot/imported/extra-life.wav-19a70d7388e345af9f7f730bc4b90652.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://c6typpwkisj8m"
|
||||
path="res://.godot/imported/game-start.wav-4b865d843701ab928493a76855499efb.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/robotron/audio/game-start.wav"
|
||||
dest_files=["res://.godot/imported/game-start.wav-4b865d843701ab928493a76855499efb.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://bcehmbcjwptsx"
|
||||
path="res://.godot/imported/grunt-walk.wav-faf58376d15b68b3a815bb279e447006.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/robotron/audio/grunt-walk.wav"
|
||||
dest_files=["res://.godot/imported/grunt-walk.wav-faf58376d15b68b3a815bb279e447006.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://bwn6jkq0xpuu0"
|
||||
path="res://.godot/imported/high-score-top.wav-8e6de3f4bbf1fece31c5fa6282adf209.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/robotron/audio/high-score-top.wav"
|
||||
dest_files=["res://.godot/imported/high-score-top.wav-8e6de3f4bbf1fece31c5fa6282adf209.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://cn2fw55nvynf5"
|
||||
path="res://.godot/imported/human-die.wav-99fd3bed7e7e5c07dd59719c2228724a.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/robotron/audio/human-die.wav"
|
||||
dest_files=["res://.godot/imported/human-die.wav-99fd3bed7e7e5c07dd59719c2228724a.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://b3ww2vonrhhkd"
|
||||
path="res://.godot/imported/human-save.wav-dee20155e9e26ff4bd95b65e30c1d026.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/robotron/audio/human-save.wav"
|
||||
dest_files=["res://.godot/imported/human-save.wav-dee20155e9e26ff4bd95b65e30c1d026.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://qx72ejxfca08"
|
||||
path="res://.godot/imported/human-to-prog.wav-7089b66bf4dcb3d404cb16fcc2dd6529.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/robotron/audio/human-to-prog.wav"
|
||||
dest_files=["res://.godot/imported/human-to-prog.wav-7089b66bf4dcb3d404cb16fcc2dd6529.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://dimmu61s3m5ew"
|
||||
path="res://.godot/imported/level-transition.wav-6fc33834b681710f4c9cbb77b6f5f3cb.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/robotron/audio/level-transition.wav"
|
||||
dest_files=["res://.godot/imported/level-transition.wav-6fc33834b681710f4c9cbb77b6f5f3cb.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://beqa8vnny763m"
|
||||
path="res://.godot/imported/player-death.wav-55b9d5daa99d827e04bb3f544c18dada.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/robotron/audio/player-death.wav"
|
||||
dest_files=["res://.godot/imported/player-death.wav-55b9d5daa99d827e04bb3f544c18dada.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://dr78jauiuuhnl"
|
||||
path="res://.godot/imported/player-lazer.wav-05da460a1294a0fe996d94cf88c81bf2.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/robotron/audio/player-lazer.wav"
|
||||
dest_files=["res://.godot/imported/player-lazer.wav-05da460a1294a0fe996d94cf88c81bf2.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://qfgui3ep0118"
|
||||
path="res://.godot/imported/prog-transformation.wav-bb6c2bd0e6e8c190e9882d119dde61c2.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/robotron/audio/prog-transformation.wav"
|
||||
dest_files=["res://.godot/imported/prog-transformation.wav-bb6c2bd0e6e8c190e9882d119dde61c2.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://cr35y8iwvy7ck"
|
||||
path="res://.godot/imported/quark-spawn.wav-9c0f31ab2f22bfe191e0be8f03b0b11b.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/robotron/audio/quark-spawn.wav"
|
||||
dest_files=["res://.godot/imported/quark-spawn.wav-9c0f31ab2f22bfe191e0be8f03b0b11b.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://cmlyl3e33eh4l"
|
||||
path="res://.godot/imported/robot-explode.wav-8eea8ea0a6c79e087ab9c54c1b6454dd.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/robotron/audio/robot-explode.wav"
|
||||
dest_files=["res://.godot/imported/robot-explode.wav-8eea8ea0a6c79e087ab9c54c1b6454dd.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://bh3mjr3r6nnft"
|
||||
path="res://.godot/imported/spheroid-pop.wav-77f9dcf5365d5202df99a46f1492107a.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/robotron/audio/spheroid-pop.wav"
|
||||
dest_files=["res://.godot/imported/spheroid-pop.wav-77f9dcf5365d5202df99a46f1492107a.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://6hq1t3mexcyw"
|
||||
path="res://.godot/imported/spheroid-spawn.wav-7681b106e9d24fa45a1a55c0eddb90c5.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/robotron/audio/spheroid-spawn.wav"
|
||||
dest_files=["res://.godot/imported/spheroid-spawn.wav-7681b106e9d24fa45a1a55c0eddb90c5.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://c33524qlyohrh"
|
||||
path="res://.godot/imported/startup.wav-09f3a543c3cc1641263246bb8a2e314b.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/robotron/audio/startup.wav"
|
||||
dest_files=["res://.godot/imported/startup.wav-09f3a543c3cc1641263246bb8a2e314b.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://fh5303842hua"
|
||||
path="res://.godot/imported/tank-explode.wav-06ef0590bf491e2fa992dd03e3e11cb9.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/robotron/audio/tank-explode.wav"
|
||||
dest_files=["res://.godot/imported/tank-explode.wav-06ef0590bf491e2fa992dd03e3e11cb9.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://6xpa0q7yeb7y"
|
||||
path="res://.godot/imported/tank-fire.wav-aa436e73a9bdf6b01d0c6684a52ffd1a.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/robotron/audio/tank-fire.wav"
|
||||
dest_files=["res://.godot/imported/tank-fire.wav-aa436e73a9bdf6b01d0c6684a52ffd1a.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://dgucnurwckx8t"
|
||||
path="res://.godot/imported/tank-projectile-bounce.wav-8189827f0f7c9a2573cc9caad22d14e6.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://games/robotron/audio/tank-projectile-bounce.wav"
|
||||
dest_files=["res://.godot/imported/tank-projectile-bounce.wav-8189827f0f7c9a2573cc9caad22d14e6.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,188 @@
|
||||
@tool
|
||||
extends Node2D
|
||||
const PLAYER = preload("res://games/robotron/art/player.png")
|
||||
const HUMANS = preload("res://games/robotron/art/humans.png")
|
||||
const ROBOTS = preload("res://games/robotron/art/robotrons.png")
|
||||
const TITLE = preload("res://games/robotron/art/titlescreen_example.png")
|
||||
const COLORS := [Color.WHITE,Color.YELLOW,Color("ff8000"),Color.MAGENTA,Color.GREEN,Color.RED,Color.BLUE]
|
||||
# Small pixel font for HUD and instructions; artwork remains in supplied sheets.
|
||||
const FONT := {
|
||||
"A":"01110100011000111111100011000110001","B":"11110100011000111110100011000111110","C":"01111100001000010000100001000001111","D":"11110100011000110001100011000111110",
|
||||
"E":"11111100001000011110100001000011111","F":"11111100001000011110100001000010000","G":"01111100001000010111100011000101111","H":"10001100011000111111100011000110001",
|
||||
"I":"11111001000010000100001000010011111","J":"00111000100001000010000101001001100","K":"10001100101010011000101001001010001","L":"10000100001000010000100001000011111",
|
||||
"M":"10001110111010110101100011000110001","N":"10001110011010110011100011000110001","O":"01110100011000110001100011000101110","P":"11110100011000111110100001000010000",
|
||||
"Q":"01110100011000110001101011001001101","R":"11110100011000111110101001001010001","S":"01111100001000001110000010000111110","T":"11111001000010000100001000010000100",
|
||||
"U":"10001100011000110001100011000101110","V":"10001100011000110001100010101000100","W":"10001100011000110101101011101110001","X":"10001100010101000100010101000110001",
|
||||
"Y":"10001100010101000100001000010000100","Z":"11111000010001000100010001000011111","0":"01110100011001110101110011000101110","1":"00100011000010000100001000010001110",
|
||||
"2":"01110100010000100010001000100011111","3":"11110000010000101110000010000111110","4":"00010001100101010010111110001000010","5":"11111100001000011110000010000111110",
|
||||
"6":"01110100001000011110100011000101110","7":"11111000010001000100010000100001000","8":"01110100011000101110100011000101110","9":"01110100011000101111000010000101110",
|
||||
"-":"00000000000000011111000000000000000",":":"00000001000010000000001000010000000","!":"00100001000010000100001000000000100",".":"00000000000000000000000000010000100"}
|
||||
var sprites: Dictionary = {}
|
||||
var game: Node
|
||||
var badge: ImageTexture
|
||||
|
||||
func _ready() -> void:
|
||||
game = get_parent().get_parent()
|
||||
var player := PLAYER.get_image()
|
||||
for f in range(3):
|
||||
for d in range(4):
|
||||
register("player%d_%d"%[d,f],player,Rect2i([0,7,14,23][d],[0,12,25][f],[5,5,7,7][d],[11,12,10][f]),Vector2i(8,12))
|
||||
var human := HUMANS.get_image()
|
||||
for f in range(3):
|
||||
for d in range(4):
|
||||
register("human0_%d_%d"%[d,f],human,Rect2i([0,9,19,30][d],[0,13,27][f],[8,8,9,8][d],12),Vector2i(10,14))
|
||||
register("human1_%d_%d"%[d,f],human,Rect2i([0,9,18,28][d],[41,54,70][f],[8,8,10,10][d],12 if f==0 else 14),Vector2i(10,14))
|
||||
register("human2_%d_%d"%[d,f],human,Rect2i([1,7,12,18][d],[85,96,108][f],[4,4,5,4][d],10 if f==0 else 11),Vector2i(6,12))
|
||||
register("prog",human,Rect2i(0,120,11,11))
|
||||
var robots := ROBOTS.get_image()
|
||||
for f in range(3):
|
||||
register("grunt%d"%f,robots,Rect2i(f*12,0,9,13),Vector2i(10,14))
|
||||
register("hulk%d"%f,robots,Rect2i(14,[14,30,46][f],11,15),Vector2i(14,16))
|
||||
register("brain%d"%f,robots,Rect2i(1,[141,157,174][f],12,15),Vector2i(14,16))
|
||||
register("spheroid",robots,Rect2i(45,65,11,11))
|
||||
register("enforcer",robots,Rect2i(39,79,11,11))
|
||||
register("quark",robots,Rect2i(27,103,9,9))
|
||||
register("tank0",robots,Rect2i(29,116,14,17))
|
||||
register("tank1",robots,Rect2i(44,116,14,17))
|
||||
register("tank2",robots,Rect2i(59,116,14,17))
|
||||
register("shell",robots,Rect2i(0,134,7,7))
|
||||
for i in range(6):
|
||||
var key := "electrode%d"%i
|
||||
register(key,robots,[Rect2i(1,220,7,7),Rect2i(11,220,7,7),Rect2i(21,220,7,7),Rect2i(32,220,7,7),Rect2i(49,220,7,7),Rect2i(84,220,7,7)][i])
|
||||
var mask: Image = sprites[key].get_image()
|
||||
for y in range(mask.get_height()):
|
||||
for x in range(mask.get_width()):
|
||||
var alpha := mask.get_pixel(x,y).a
|
||||
mask.set_pixel(x,y,Color(1,1,1,alpha))
|
||||
sprites[key] = ImageTexture.create_from_image(mask)
|
||||
# Reuse the supplied title's exact Williams emblem as a tintable mask.
|
||||
var emblem := TITLE.get_image().get_region(Rect2i(48,16,56,54))
|
||||
emblem.convert(Image.FORMAT_RGBA8)
|
||||
for y in range(emblem.get_height()):
|
||||
for x in range(emblem.get_width()):
|
||||
var c := emblem.get_pixel(x,y)
|
||||
emblem.set_pixel(x,y,Color.WHITE if c.r>.8 and c.g>.8 and c.b>.8 else Color.TRANSPARENT)
|
||||
badge = ImageTexture.create_from_image(emblem)
|
||||
|
||||
func register(key: String, image: Image, rect: Rect2i, canvas: Vector2i = Vector2i.ZERO) -> void:
|
||||
var piece := image.get_region(rect)
|
||||
if canvas!=Vector2i.ZERO:
|
||||
var padded := Image.create(canvas.x,canvas.y,false,Image.FORMAT_RGBA8)
|
||||
piece.convert(Image.FORMAT_RGBA8)
|
||||
padded.blit_rect(piece,Rect2i(Vector2i.ZERO,piece.get_size()),Vector2i((canvas.x-piece.get_width())/2,canvas.y-piece.get_height()))
|
||||
piece = padded
|
||||
sprites[key] = ImageTexture.create_from_image(piece)
|
||||
|
||||
func sprite(key: String, point: Vector2, tint: Color = Color.WHITE) -> void:
|
||||
var texture: Texture2D = sprites[key]
|
||||
draw_texture(texture,(point-texture.get_size()*.5).round(),tint)
|
||||
|
||||
func text(value: String, point: Vector2, color: Color = Color.WHITE) -> void:
|
||||
for i in range(value.length()):
|
||||
if not FONT.has(value[i]): continue
|
||||
var bits: String = FONT[value[i]]
|
||||
for pixel in range(35):
|
||||
if bits[pixel]=="1": draw_rect(Rect2(point+Vector2(i*6+pixel%5,pixel/5),Vector2.ONE),color)
|
||||
|
||||
func center(value: String,y: float,color: Color = Color.WHITE) -> void:
|
||||
text(value,Vector2(roundf((292-value.length()*6)/2.0),y),color)
|
||||
|
||||
func direction_index(v: Vector2) -> int:
|
||||
if absf(v.x)>absf(v.y): return 0 if v.x<0 else 1
|
||||
return 3 if v.y<0 else 2
|
||||
|
||||
func draw_title() -> void:
|
||||
draw_texture_rect(TITLE,Rect2(0,0,292,240),false)
|
||||
# Animated emblems move around the perimeter, rather than freezing the screenshot.
|
||||
draw_rect(Rect2(0,0,292,44),Color.BLACK)
|
||||
draw_rect(Rect2(0,200,292,40),Color.BLACK)
|
||||
draw_rect(Rect2(0,44,36,164),Color.BLACK)
|
||||
draw_rect(Rect2(256,44,36,164),Color.BLACK)
|
||||
var perimeter := 2.0*(252+200)
|
||||
var shift: float = 0.0 if game.reduced_flashes else game.phase_time*24
|
||||
for i in range(28):
|
||||
var distance := fposmod(i*perimeter/28+shift,perimeter)
|
||||
var p: Vector2
|
||||
if distance<252: p=Vector2(20+distance,20)
|
||||
elif distance<452: p=Vector2(272,20+distance-252)
|
||||
elif distance<704: p=Vector2(272-(distance-452),220)
|
||||
else: p=Vector2(20,220-(distance-704))
|
||||
draw_texture_rect(badge,Rect2((p-Vector2(13,13)).round(),Vector2(26,26)),false,COLORS[(i+int(game.phase_time*2))%COLORS.size()] if not game.reduced_flashes else COLORS[i%COLORS.size()])
|
||||
draw_rect(Rect2(108,158,76,10),Color.BLACK)
|
||||
center("FREE PLAY",160,Color.BLUE)
|
||||
|
||||
func _draw() -> void:
|
||||
if game==null: return
|
||||
draw_rect(Rect2(0,0,292,240),Color.BLACK)
|
||||
if game.phase=="title":
|
||||
draw_title()
|
||||
return
|
||||
var accent: Color = COLORS[int(game.clock*3)%COLORS.size()] if not game.reduced_flashes else Color.RED
|
||||
draw_rect(Rect2(4,16,284,216),Color.BLUE if game.phase in ["story","roster","scores"] else Color.RED,false,1)
|
||||
text(str(game.last_score if game.demo else game.score),Vector2(52,4))
|
||||
if game.phase in ["story","roster","scores"]:
|
||||
center("FREE PLAY",233,Color.GREEN)
|
||||
else:
|
||||
for i in range(mini(maxi(0,game.lives-1),7)): sprite("player2_0",Vector2(134+i*10,8))
|
||||
center("%d WAVE"%game.wave,233,Color.BLUE)
|
||||
if game.phase=="story":
|
||||
center("ROBOTRON: 2084",30,accent)
|
||||
var lines := ["YOU ARE THE LAST HOPE OF MANKIND.","SAVE THE LAST HUMAN FAMILY!","MOVE AND FIRE IN EIGHT DIRECTIONS.","WASD MOVES ARROWS FIRE","RESCUE HUMANS FOR 1000 TO 5000","EXTRA LIFE EVERY %d POINTS"%game.extra_life_interval if game.extra_life_interval>0 else "BONUS LIVES DISABLED"]
|
||||
for i in range(lines.size()):
|
||||
if game.phase_time>i*.8: center(lines[i],56+i*22,COLORS[(i+2)%7])
|
||||
sprite("player2_0",Vector2(114,205))
|
||||
sprite("human0_2_0",Vector2(146,205))
|
||||
return
|
||||
if game.phase=="roster":
|
||||
center("THE ROBOTRONS",30,accent)
|
||||
var names := ["GRUNT","HULK","SPHEROID","ENFORCER","BRAIN","QUARK","TANK","PROG"]
|
||||
var keys := ["grunt0","hulk0","spheroid","enforcer","brain0","quark","tank0","prog"]
|
||||
for i in range(8):
|
||||
sprite(keys[i],Vector2(48,54+i*20))
|
||||
text(names[i],Vector2(76,50+i*20),COLORS[i%7])
|
||||
return
|
||||
if game.phase=="scores":
|
||||
center("ROBOTRON HERO",60,accent)
|
||||
center("HIGH SCORE",92)
|
||||
center(str(game.high_score),110,Color.YELLOW)
|
||||
center("PUSH START",176,accent)
|
||||
return
|
||||
for entity in game.entities:
|
||||
if not entity.alive: continue
|
||||
var frame := int(game.clock*8+entity.frame)%3
|
||||
var key: String = entity.kind
|
||||
match key:
|
||||
"human": key="human%d_%d_%d"%[entity.human_type,direction_index(entity.velocity),frame]
|
||||
"grunt","hulk","brain","tank": key+=str(frame)
|
||||
"electrode": key+=str((game.wave+entity.id)%6)
|
||||
"converting": key="prog"
|
||||
var tint := Color.WHITE
|
||||
if entity.kind=="converting": tint = accent
|
||||
elif entity.kind=="electrode": tint = [Color.GREEN,Color.RED,Color.BLUE,Color.YELLOW,Color.MAGENTA,Color.CYAN][(game.wave-1)%6]
|
||||
sprite(key,entity.position,tint)
|
||||
if game.phase=="dead":
|
||||
for i in range(8):
|
||||
var offset: Vector2 = Vector2.from_angle(i*TAU/8)*(1.2-game.phase_time)*30
|
||||
sprite("player%d_%d"%[direction_index(game.facing),i%3],game.player+offset,Color(1,1,1,clampf(game.phase_time,0,1)))
|
||||
elif game.phase not in ["won","lost","transition"]:
|
||||
if game.invulnerable<=0 or int(game.clock*12)%2==0:
|
||||
sprite("player%d_%d"%[direction_index(game.facing),int(game.clock*10)%3 if game.moving else 0],game.player)
|
||||
for shot in game.shots:
|
||||
var tail: Vector2 = shot.position-shot.velocity.normalized()*5
|
||||
draw_line(tail.round(),shot.position.round(),Color.WHITE,1)
|
||||
for shot in game.enemy_shots:
|
||||
if shot.kind=="shell": sprite("shell",shot.position)
|
||||
else: draw_rect(Rect2(shot.position.round()-Vector2(1,1),Vector2(3,3)),accent)
|
||||
for effect in game.effects:
|
||||
if effect.label!="": text(effect.label,effect.position.round()-Vector2(10,8),accent)
|
||||
else:
|
||||
for i in range(8):
|
||||
var p: Vector2 = effect.position+Vector2.from_angle(i*TAU/8)*(1-effect.life/.25)*12
|
||||
draw_rect(Rect2(p.round(),Vector2(2,2)),accent)
|
||||
if game.phase=="ready": center("GET READY",110,accent)
|
||||
if game.phase=="transition":
|
||||
for i in range(8):
|
||||
var expansion: float = fposmod((1.8-game.phase_time)*150+i*18,150)
|
||||
draw_rect(Rect2(Vector2(146,124)-Vector2(expansion,expansion*.7),Vector2(expansion*2,expansion*1.4)),accent,false,1)
|
||||
if game.phase=="won": center("YOU WIN!",114,accent)
|
||||
if game.phase=="lost" or game.demo: center("GAME OVER",114,accent)
|
||||
@@ -0,0 +1 @@
|
||||
uid://dhxkitgihoy3d
|
||||
@@ -0,0 +1,12 @@
|
||||
@tool
|
||||
extends Node2D
|
||||
func _process(_delta: float) -> void:
|
||||
material.set_shader_parameter("animate_title",get_parent().phase=="title" and not get_parent().reduced_flashes)
|
||||
material.set_shader_parameter("palette_step",int(get_parent().phase_time*2)%7)
|
||||
queue_redraw()
|
||||
func _draw() -> void:
|
||||
var available := get_viewport_rect().size if not Engine.is_editor_hint() else Vector2(960,720)
|
||||
var aspect := Vector2(4,3) if get_parent().crt_aspect else Vector2(292,240)
|
||||
var size := aspect*minf(available.x/aspect.x,available.y/aspect.y)
|
||||
draw_rect(Rect2(Vector2.ZERO,available),Color.BLACK)
|
||||
draw_texture_rect($"../PixelScreen".get_texture(),Rect2(((available-size)*.5).floor(),size),false)
|
||||
@@ -0,0 +1 @@
|
||||
uid://dxycwontqumst
|
||||
@@ -0,0 +1,429 @@
|
||||
@tool
|
||||
extends ArcadeGame
|
||||
|
||||
@export_range(0,99) var waves_to_win: int = 1
|
||||
@export_range(1,9) var starting_lives: int = 3
|
||||
@export_range(.5,2,.1) var difficulty: float = 1.0
|
||||
@export_range(0,100000,5000) var extra_life_interval: int = 25000
|
||||
@export var sound_enabled: bool = true
|
||||
@export var crt_aspect: bool = true
|
||||
@export var reduced_flashes: bool = false
|
||||
|
||||
const ARENA := Rect2(8,20,276,208)
|
||||
const POINTS := {"grunt":100,"hulk":0,"spheroid":1000,"enforcer":150,"quark":1000,"tank":200,"brain":500,"prog":100,"electrode":0}
|
||||
var phase := "title"
|
||||
var phase_time := 0.0
|
||||
var clock := 0.0
|
||||
var demo := false
|
||||
var demo_time := 0.0
|
||||
var score := 0
|
||||
var high_score := 0
|
||||
var last_score := 0
|
||||
var lives := 3
|
||||
var wave := 1
|
||||
var rescue_chain := 0
|
||||
var next_extra := 25000
|
||||
var player := Vector2(146,124)
|
||||
var facing := Vector2.DOWN
|
||||
var moving := false
|
||||
var fire_delay := 0.0
|
||||
var invulnerable := 0.0
|
||||
var grunt_delay := 0.0
|
||||
var entities: Array[Dictionary] = []
|
||||
var shots: Array[Dictionary] = []
|
||||
var enemy_shots: Array[Dictionary] = []
|
||||
var effects: Array[Dictionary] = []
|
||||
var rng := RandomNumberGenerator.new()
|
||||
var next_id := 0
|
||||
@onready var board: Node2D = $PixelScreen/Board
|
||||
|
||||
func _ready() -> void:
|
||||
lives = starting_lives
|
||||
rng.seed = 2084
|
||||
setup_wave()
|
||||
if not Engine.is_editor_hint():
|
||||
$Audio.effect("startup")
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if Engine.is_editor_hint() or event.is_echo(): return
|
||||
if event.is_action_pressed("arcade_start"):
|
||||
start_game()
|
||||
|
||||
func eight_way(vector: Vector2) -> Vector2:
|
||||
if vector.length()<.25: return Vector2.ZERO
|
||||
return Vector2.from_angle(roundf(vector.angle()/(PI/4))*(PI/4)).normalized()
|
||||
|
||||
func control_vector(prefix: String) -> Vector2:
|
||||
return eight_way(Input.get_vector("robotron_"+prefix+"left","robotron_"+prefix+"right","robotron_"+prefix+"up","robotron_"+prefix+"down"))
|
||||
|
||||
func start_game() -> bool:
|
||||
if is_running: return false
|
||||
demo = false
|
||||
score = 0
|
||||
lives = starting_lives
|
||||
wave = 1
|
||||
next_extra = extra_life_interval
|
||||
rng.randomize()
|
||||
setup_wave()
|
||||
phase = "ready"
|
||||
phase_time = 2.0
|
||||
$Audio.stop_all()
|
||||
$Audio.effect("game-start")
|
||||
return super.start_game()
|
||||
|
||||
func reset_game() -> void:
|
||||
demo = false
|
||||
score = 0
|
||||
last_score = 0
|
||||
wave = 1
|
||||
lives = starting_lives
|
||||
setup_wave()
|
||||
phase = "title"
|
||||
phase_time = 0
|
||||
$Audio.stop_all()
|
||||
super.reset_game()
|
||||
|
||||
func spawn(kind: String, position: Vector2) -> Dictionary:
|
||||
next_id += 1
|
||||
var entity := {"id":next_id,"kind":kind,"position":position,"velocity":Vector2.from_angle(rng.randf()*TAU),"timer":rng.randf_range(.6,2.0),"age":0.0,"frame":rng.randi_range(0,2),"alive":true,"spawns":0,"human_type":rng.randi_range(0,2),"hp":3 if kind=="brain" else 1}
|
||||
entities.append(entity)
|
||||
return entity
|
||||
|
||||
func spawn_point() -> Vector2:
|
||||
for attempt in range(80):
|
||||
var p := Vector2(rng.randf_range(15,277),rng.randf_range(28,220))
|
||||
if p.distance_to(player)<=48: continue
|
||||
var occupied := false
|
||||
for e in entities:
|
||||
if e.alive and p.distance_squared_to(e.position)<100:
|
||||
occupied = true
|
||||
break
|
||||
if not occupied: return p
|
||||
return Vector2(20,30)
|
||||
|
||||
func setup_wave() -> void:
|
||||
entities.clear()
|
||||
shots.clear()
|
||||
enemy_shots.clear()
|
||||
effects.clear()
|
||||
player = Vector2(146,124)
|
||||
facing = Vector2.DOWN
|
||||
rescue_chain = 0
|
||||
fire_delay = 0
|
||||
invulnerable = 1.5
|
||||
var brain_wave := wave%5==0
|
||||
var tank_wave := wave>=7 and wave%5==2
|
||||
for i in range(mini(15+wave*3,65)): spawn("grunt",spawn_point())
|
||||
for i in range(5+mini(wave/3,5)): spawn("hulk",spawn_point())
|
||||
for i in range(15 if not brain_wave else 5): spawn("electrode",spawn_point())
|
||||
for i in range(20 if brain_wave else 3+mini(wave,5)): spawn("human",spawn_point())
|
||||
if brain_wave:
|
||||
for i in range(8+mini(wave,12)): spawn("brain",spawn_point())
|
||||
elif tank_wave:
|
||||
for i in range(5+mini(wave/5,5)): spawn("quark",spawn_point())
|
||||
elif wave>=2:
|
||||
for i in range(mini(wave,8)): spawn("spheroid",spawn_point())
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
clock += delta
|
||||
if Engine.is_editor_hint():
|
||||
phase_time = fmod(clock,12)
|
||||
board.queue_redraw()
|
||||
return
|
||||
if phase in ["title","story","roster","scores"]:
|
||||
phase_time += delta
|
||||
if phase_time>12:
|
||||
phase_time = 0
|
||||
match phase:
|
||||
"title": phase = "story"
|
||||
"story": phase = "roster"
|
||||
"roster": begin_demo()
|
||||
"scores": phase = "title"
|
||||
elif phase in ["ready","dead","transition","lost"]:
|
||||
phase_time -= delta
|
||||
if phase_time<=0:
|
||||
match phase:
|
||||
"ready": phase = "play"
|
||||
"dead":
|
||||
if lives<=0: finish(false)
|
||||
else: respawn()
|
||||
"transition":
|
||||
if not demo and waves_to_win>0 and wave>=waves_to_win: finish(true)
|
||||
else:
|
||||
wave += 1
|
||||
setup_wave()
|
||||
phase = "ready"
|
||||
phase_time = 1.5
|
||||
if wave%5==0: $Audio.effect("brain-wave-start")
|
||||
"lost":
|
||||
phase = "scores"
|
||||
phase_time = 0
|
||||
elif phase == "play":
|
||||
var movement := Vector2.ZERO
|
||||
var firing := Vector2.ZERO
|
||||
if demo:
|
||||
var target := Vector2(146+cos(demo_time*.8)*88,124+sin(demo_time*.8)*70)
|
||||
movement = eight_way(target-player)
|
||||
var enemy := nearest(player,false)
|
||||
if not enemy.is_empty(): firing = eight_way(enemy.position-player)
|
||||
else:
|
||||
movement = control_vector("move_")
|
||||
firing = control_vector("fire_")
|
||||
step_world(delta,movement,firing)
|
||||
if demo:
|
||||
demo_time += delta
|
||||
if demo_time>25: end_demo()
|
||||
board.queue_redraw()
|
||||
|
||||
func begin_demo() -> void:
|
||||
demo = true
|
||||
demo_time = 0
|
||||
score = 0
|
||||
lives = 3
|
||||
wave = 3
|
||||
rng.seed = 2084
|
||||
setup_wave()
|
||||
phase = "play"
|
||||
$Audio.stop_all()
|
||||
|
||||
func end_demo() -> void:
|
||||
demo = false
|
||||
score = last_score
|
||||
phase = "scores"
|
||||
phase_time = 0
|
||||
$Audio.stop_all()
|
||||
|
||||
func clamp_arena(p: Vector2, margin: float = 4) -> Vector2:
|
||||
return p.clamp(ARENA.position+Vector2.ONE*margin,ARENA.end-Vector2.ONE*margin)
|
||||
|
||||
func nearest(p: Vector2, human: bool) -> Dictionary:
|
||||
var best: Dictionary = {}
|
||||
var distance := INF
|
||||
for e in entities:
|
||||
if not e.alive or (e.kind=="human")!=human or e.kind in ["electrode","hulk"]: continue
|
||||
var d: float = p.distance_squared_to(e.position)
|
||||
if d<distance:
|
||||
distance = d
|
||||
best = e
|
||||
return best
|
||||
|
||||
func fire(direction: Vector2) -> bool:
|
||||
if phase!="play" or direction==Vector2.ZERO or fire_delay>0 or shots.size()>=4: return false
|
||||
shots.append({"position":player+direction*5,"velocity":direction*260})
|
||||
fire_delay = .085
|
||||
$Audio.effect("player-lazer")
|
||||
return true
|
||||
|
||||
func radius(kind: String) -> float:
|
||||
return 7.0 if kind in ["brain","hulk","tank"] else (5.0 if kind in ["spheroid","quark"] else 3.5)
|
||||
|
||||
func projectile(kind: String, origin: Vector2, target: Vector2) -> void:
|
||||
if enemy_shots.size()>=70: return
|
||||
var velocity := (target-origin).normalized()*(90 if kind=="shell" else 65)
|
||||
enemy_shots.append({"kind":kind,"position":origin,"velocity":velocity,"life":9.0,"bounces":0})
|
||||
$Audio.effect("tank-fire" if kind=="shell" else "enforcer-fire")
|
||||
|
||||
func step_world(delta: float, movement: Vector2, firing: Vector2) -> void:
|
||||
moving = movement!=Vector2.ZERO
|
||||
if moving: facing = movement
|
||||
player = clamp_arena(player+movement*86*delta)
|
||||
invulnerable = maxf(0,invulnerable-delta)
|
||||
fire_delay = maxf(0,fire_delay-delta)
|
||||
grunt_delay -= delta
|
||||
if grunt_delay<=0:
|
||||
grunt_delay = .6
|
||||
if entities.any(func(e): return e.alive and e.kind=="grunt"): $Audio.effect("grunt-walk")
|
||||
fire(firing)
|
||||
for i in range(effects.size()-1,-1,-1):
|
||||
effects[i].life -= delta
|
||||
if effects[i].life<=0: effects.remove_at(i)
|
||||
# Spawners append enemies; the snapshot prevents updating new enemies twice.
|
||||
for e in entities.duplicate():
|
||||
if not e.alive: continue
|
||||
e.age += delta
|
||||
e.timer -= delta
|
||||
var direction: Vector2 = (player-e.position).normalized()
|
||||
match e.kind:
|
||||
"human":
|
||||
if e.timer<=0:
|
||||
e.velocity = eight_way(Vector2(rng.randf_range(-1,1),rng.randf_range(-1,1)))
|
||||
e.timer = rng.randf_range(.4,1.4)
|
||||
e.position = clamp_arena(e.position+e.velocity*12*delta)
|
||||
if player.distance_to(e.position)<7: save_human(e)
|
||||
"grunt","prog":
|
||||
e.position += eight_way(direction)*(32 if e.kind=="grunt" else 58)*difficulty*delta
|
||||
if e.kind=="grunt":
|
||||
for obstacle in entities:
|
||||
if obstacle.alive and obstacle.kind=="electrode" and e.position.distance_to(obstacle.position)<6:
|
||||
e.alive = false
|
||||
obstacle.alive = false
|
||||
burst(e.position)
|
||||
break
|
||||
"hulk","brain":
|
||||
var human := nearest(e.position,true)
|
||||
var destination: Vector2 = human.position if not human.is_empty() else player
|
||||
if e.timer<=0:
|
||||
e.velocity = eight_way(destination-e.position)
|
||||
e.timer = .6 if e.kind=="hulk" else .2
|
||||
e.position = clamp_arena(e.position+e.velocity*(17 if e.kind=="hulk" else 22)*difficulty*delta)
|
||||
if not human.is_empty() and e.position.distance_to(human.position)<8:
|
||||
human.alive = false
|
||||
if e.kind=="brain":
|
||||
var prog := spawn("converting",human.position)
|
||||
prog.timer = .7
|
||||
$Audio.effect("human-to-prog")
|
||||
else:
|
||||
burst(human.position)
|
||||
$Audio.effect("human-die")
|
||||
if e.kind=="brain" and int(e.age/2.2)!=int((e.age-delta)/2.2): projectile("cruise",e.position,player)
|
||||
"converting":
|
||||
if e.timer<=0:
|
||||
e.kind = "prog"
|
||||
$Audio.effect("prog-transformation")
|
||||
"spheroid","quark":
|
||||
var next: Vector2 = e.position+e.velocity*(45 if e.kind=="spheroid" else 28)*delta
|
||||
if next.x<14 or next.x>278: e.velocity.x *= -1
|
||||
if next.y<26 or next.y>222: e.velocity.y *= -1
|
||||
e.position = clamp_arena(e.position+e.velocity*40*delta)
|
||||
if e.timer<=0:
|
||||
spawn("enforcer" if e.kind=="spheroid" else "tank",e.position)
|
||||
e.spawns += 1
|
||||
e.timer = 1.4
|
||||
$Audio.effect("spheroid-spawn" if e.kind=="spheroid" else "quark-spawn")
|
||||
if e.spawns>=4:
|
||||
e.alive = false
|
||||
burst(e.position)
|
||||
"enforcer","tank":
|
||||
if e.timer<=0:
|
||||
e.velocity = Vector2.from_angle(rng.randf()*TAU)
|
||||
e.timer = rng.randf_range(.8,1.7)/difficulty
|
||||
projectile("spark" if e.kind=="enforcer" else "shell",e.position,player)
|
||||
e.position = clamp_arena(e.position+e.velocity*(30 if e.kind=="enforcer" else 18)*delta)
|
||||
if e.alive and e.kind!="human" and e.kind!="converting" and player.distance_to(e.position)<radius(e.kind)+2 and invulnerable<=0:
|
||||
die()
|
||||
return
|
||||
update_shots(delta)
|
||||
update_enemy_shots(delta)
|
||||
entities = entities.filter(func(e): return e.alive)
|
||||
if phase=="play" and remaining_enemies()==0:
|
||||
phase = "transition"
|
||||
phase_time = 1.8
|
||||
enemy_shots.clear()
|
||||
shots.clear()
|
||||
$Audio.stop_all()
|
||||
$Audio.effect("level-transition")
|
||||
|
||||
func update_shots(delta: float) -> void:
|
||||
for i in range(shots.size()-1,-1,-1):
|
||||
var shot: Dictionary = shots[i]
|
||||
var distance: float = shot.velocity.length()*delta
|
||||
var direction: Vector2 = shot.velocity.normalized()
|
||||
var hit := false
|
||||
while distance>0 and not hit:
|
||||
var step := minf(2,distance)
|
||||
shot.position += direction*step
|
||||
distance -= step
|
||||
if not ARENA.has_point(shot.position): hit = true
|
||||
for enemy in entities:
|
||||
if hit: break
|
||||
if not enemy.alive or enemy.kind in ["human","converting"]: continue
|
||||
if shot.position.distance_to(enemy.position)<radius(enemy.kind):
|
||||
hit = true
|
||||
if enemy.kind=="hulk":
|
||||
enemy.position = clamp_arena(enemy.position+direction*4)
|
||||
enemy.velocity = direction
|
||||
enemy.timer = .3
|
||||
else: damage(enemy)
|
||||
for bullet in enemy_shots:
|
||||
if not hit and bullet.life>0 and shot.position.distance_to(bullet.position)<4:
|
||||
bullet.life = 0
|
||||
hit = true
|
||||
add_score(50 if bullet.kind=="shell" else (75 if bullet.kind=="cruise" else 25))
|
||||
if hit: shots.remove_at(i)
|
||||
|
||||
func update_enemy_shots(delta: float) -> void:
|
||||
for i in range(enemy_shots.size()-1,-1,-1):
|
||||
var b: Dictionary = enemy_shots[i]
|
||||
b.life -= delta
|
||||
if b.kind=="cruise": b.velocity = b.velocity.move_toward((player-b.position).normalized()*78,delta*45)
|
||||
b.position += b.velocity*delta
|
||||
if not ARENA.has_point(b.position):
|
||||
if b.kind=="shell" and b.bounces<4:
|
||||
if b.position.x<ARENA.position.x or b.position.x>ARENA.end.x: b.velocity.x *= -1
|
||||
if b.position.y<ARENA.position.y or b.position.y>ARENA.end.y: b.velocity.y *= -1
|
||||
b.position = clamp_arena(b.position,1)
|
||||
b.bounces += 1
|
||||
$Audio.effect("tank-projectile-bounce")
|
||||
else: b.life = 0
|
||||
if b.life>0 and b.position.distance_to(player)<5 and invulnerable<=0:
|
||||
die()
|
||||
return
|
||||
if b.life<=0: enemy_shots.remove_at(i)
|
||||
|
||||
func damage(enemy: Dictionary) -> void:
|
||||
enemy.hp -= 1
|
||||
if enemy.hp>0: return
|
||||
enemy.alive = false
|
||||
add_score(POINTS.get(enemy.kind,0))
|
||||
burst(enemy.position)
|
||||
var sounds := {"brain":"brain-explode","tank":"tank-explode","electrode":"electrode-explode","spheroid":"spheroid-pop"}
|
||||
$Audio.effect(sounds.get(enemy.kind,"robot-explode"))
|
||||
|
||||
func save_human(human: Dictionary) -> void:
|
||||
if not human.alive: return
|
||||
human.alive = false
|
||||
rescue_chain = mini(5,rescue_chain+1)
|
||||
add_score(rescue_chain*1000)
|
||||
burst(human.position,str(rescue_chain*1000))
|
||||
$Audio.effect("human-save")
|
||||
|
||||
func add_score(value: int) -> void:
|
||||
score += value
|
||||
if demo: return
|
||||
high_score = maxi(high_score,score)
|
||||
while extra_life_interval>0 and score>=next_extra:
|
||||
lives += 1
|
||||
next_extra += extra_life_interval
|
||||
$Audio.effect("extra-life")
|
||||
|
||||
func burst(position: Vector2, label: String = "") -> void:
|
||||
effects.append({"position":position,"life":.55 if label!="" else .25,"label":label})
|
||||
|
||||
func remaining_enemies() -> int:
|
||||
var count := 0
|
||||
for e in entities:
|
||||
if e.alive and e.kind not in ["human","hulk","electrode"]: count += 1
|
||||
return count
|
||||
|
||||
func die() -> void:
|
||||
if phase!="play": return
|
||||
lives -= 1
|
||||
rescue_chain = 0
|
||||
phase = "dead"
|
||||
phase_time = 1.2
|
||||
$Audio.stop_all()
|
||||
$Audio.effect("player-death")
|
||||
|
||||
func respawn() -> void:
|
||||
player = Vector2(146,124)
|
||||
shots.clear()
|
||||
enemy_shots.clear()
|
||||
invulnerable = 2
|
||||
for e in entities:
|
||||
if e.alive and e.kind!="human" and e.position.distance_to(player)<48: e.position = spawn_point()
|
||||
phase = "ready"
|
||||
phase_time = 1
|
||||
|
||||
func finish(won: bool) -> void:
|
||||
if demo:
|
||||
end_demo()
|
||||
return
|
||||
if not is_running: return
|
||||
last_score = score
|
||||
phase = "won" if won else "lost"
|
||||
phase_time = 5
|
||||
$Audio.stop_all()
|
||||
if won: win_game()
|
||||
else:
|
||||
lose_game()
|
||||
if score>0 and score>=high_score: $Audio.effect("high-score-top")
|
||||
@@ -0,0 +1 @@
|
||||
uid://dllelyq8hgx07
|
||||
@@ -0,0 +1,14 @@
|
||||
shader_type canvas_item;
|
||||
render_mode unshaded;
|
||||
uniform bool animate_title = false;
|
||||
uniform int palette_step = 0;
|
||||
void fragment() {
|
||||
vec4 pixel = texture(TEXTURE, UV) * COLOR;
|
||||
// Only the supplied title lettering cycles; credits and sprites keep their palette.
|
||||
if (animate_title && UV.x>0.13 && UV.x<0.86 && UV.y>0.20 && UV.y<0.49 && pixel.r>0.8 && pixel.b<0.1) {
|
||||
vec3 palette[7] = vec3[7](vec3(1,0,0),vec3(1,0.5,0),vec3(1,1,0),vec3(0,1,0),vec3(0,1,1),vec3(1,0,1),vec3(1,1,1));
|
||||
int index = (palette_step + (pixel.g>0.8 ? 2 : 0)) % 7;
|
||||
pixel.rgb = palette[index];
|
||||
}
|
||||
COLOR = pixel;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://bowtwv6ih8lj
|
||||
Reference in new issue
Block a user