Files
spacel/ARCHITECTURE.md
alpacaman edc40f9008 Initial commit — Godot space roguelite source
- Touch controls: direct InputEventScreenTouch in shop_ui (bypass relay)
- ItemDB: static preload list instead of DirAccess scan (export fix)
- All 18 items with EN localisation (name_en, desc_en, category_en)
- Ship playstyles: NOVA-1 shield, INFERNO ram, AURORA agile/tank
- Quasar: SMBH visual, jet boost, merge, push, BH-eating
- Atlas & UI text updated EN+DE

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 14:38:09 +02:00

18 KiB
Raw Permalink Blame History

spacel — Architektur-Dokumentation

Überblick

spacel ist ein 2D-Space-Shooter mit Roguelite-Elementen, gebaut in Godot 4.6 (Forward Plus, D3D12). Das Spiel unterstützt 12 Spieler (Split-Keyboard). Es gibt keine externen Assets — alles wird prozedural gezeichnet und synthetisiert (keine Sprites, keine Audio-Dateien).


Projektstruktur

spacel/
├── project.godot           # Projekt-Konfiguration, Autoloads, Input-Map
├── scenes/
│   ├── main.tscn           # Root-Scene (enthält alles)
│   ├── game_world.tscn     # Spielwelt / Hintergrund-Canvas
│   ├── hud.tscn            # HUD (CanvasLayer)
│   └── ship_select.tscn    # Schiff-Auswahlbildschirm
├── scripts/
│   ├── main.gd             # State Machine — Spielfluss-Controller
│   ├── game_world.gd       # Spielwelt: Simulation, Rendering, Kollisionen
│   ├── spaceship.gd        # Spieler-Schiff (Datenklasse)
│   ├── enemy_ship.gd       # Gegner-Schiff mit KI
│   ├── boss_ship.gd        # Bosse: WRAITH (Welle 5) & LEVIATHAN (Welle 8)
│   ├── bullet.gd           # Projektil
│   ├── black_hole.gd       # Schwarzes Loch mit Gravitation + Supernova
│   ├── big_wipe.gd         # Big-Wipe-Event (Bildschirm-Reset)
│   ├── cosmic_objects.gd   # Alle Weltraum-Objekte (Star, Planet, ...)
│   ├── ship_stats.gd       # Roguelite-Stat-System
│   ├── item_db.gd          # Item-Datenbank (20 Items, 4 Seltenheiten)
│   ├── hud.gd              # HUD-Logik
│   ├── hud_draw.gd         # HUD-Rendering
│   ├── settings.gd         # Einstellungen (Autoload)
│   ├── sound_manager.gd    # Procedurales Audio (Autoload)
│   ├── music_player.gd     # Musik
│   ├── shop_ui.gd          # Shop zwischen den Wellen
│   ├── ship_select.gd      # Schiff-Auswahl UI
│   ├── main_menu.gd        # Hauptmenü
│   ├── pause_menu.gd       # Pause-Menü
│   └── tr.gd               # Lokalisierung (Autoload, de/en)
└── addons/godot_mcp/       # MCP-Plugin für KI-gestützte Entwicklung

Autoloads (Singletons)

Name Script Funktion
Settings settings.gd Lautstärke, Grafik, Sprache — persisted in user://settings.cfg
SoundManager sound_manager.gd Alle SFX prozedural generiert (Sinus/Säge/Square-Wellen, kein Audio-File)
Tr tr.gd Übersetzungen DE/EN
MCP-Dienste addons/godot_mcp/ Nur für Entwicklung (Editor-Plugin)

State Machine (main.gd)

main.gd ist der zentrale Controller. Er verwaltet alle UI-Panels und schaltet zwischen folgenden Zuständen um:

MAIN_MENU → SELECT → (SELECT_P2) → LAUNCHING → PLAYING ⟷ PAUSED
												   ↓
											   RETURNING → SHOP → LAUNCHING (nächste Welle)
												   ↓
											   GAMEOVER → MAIN_MENU
									  WAVE_CLEAR → SHOP

Wichtige Variablen in main.gd:

  • lives_p1 / credits_p1 / stats_p1 / owned_items_p1 — Run-Zustand Spieler 1
  • wave_number — aktuelle Wellennummer (steigt nach jedem Shop)
  • SHIPS[] — 4 Schiffe (NOVA-1, INFERNO, AURORA, TITAN) mit Farbpaletten und Basis-Stats

Schiff-Spielstile (_apply_ship_base_stats() in main.gd):

ID Name Besonderheit
classic NOVA-1 1 Schutzschild — ausgewogener Einstieg
inferno INFERNO Speed +28 %, Feuerrate +55 %, Kurve 28 % · Passiv: Ramm-Schaden ab ≥ 4.5 px/s
aurora AURORA Kurve +55 %, Speed 20 %, 2 Schilde, BH-Resist 55 %, 2× Unverwundbarkeitszeit
titan TITAN Speed 28 %, Kurve 15 % · Aktiv: Boost-Impuls (SHIFT, 5 s Cooldown)

ship_id: String in ShipStats wird beim Spielstart gesetzt und ermöglicht runtime-Verzweigungen (z. B. INFERNO-Rammen in game_world.gd).

Hauptlogik:

  • _set_state(new_state) — zeigt/versteckt UI, initialisiert game_world
  • _process() — Countdown vor Spielstart, Return-/Wave-Timer
  • add_credits(player, amount) — Credits dem Spieler gutschreiben
  • on_game_over() / on_wave_complete() — von game_world aufgerufen

Spielwelt (game_world.gd)

Das Herzstück des Spiels. game_world.gd ist ein Node2D und macht alles selbst per _draw() — keine Child-Nodes für Spielobjekte.

Physik-Loop

Fixed-Timestep bei 60 Hz (PHYS_DT = 1/60):

_process(delta):
	_phys_accum += delta  # akkumuliert reale Zeit
	while _phys_accum >= PHYS_DT:
		_phys_accum -= PHYS_DT
		frame += 1
		_tick(PHYS_DT)
	queue_redraw()

_tick(dt) — Reihenfolge pro Frame

  1. _handle_input(dt) — Spieler-Input lesen, Schüsse erzeugen
  2. _update_objects(dt) — Alle kosmischen Objekte updaten, Gegner KI
  3. _update_bullets() — Projektile bewegen, tote entfernen
  4. Boss-Update (falls vorhanden) → erzeugt Bullets
  5. _update_particles(dt) — Partikeleffekte
  6. _check_collisions() — Bullet/Enemy, Bullet/Player, Antimatter/Player, Bullets/Boss
  7. _check_big_wipe() — Threshold prüfen (> 500 Objekte)
  8. BigWipe-Update
  9. Schwierigkeit + Wave-Timer + Credit-Trickle
  10. All-Dead-Check → main_node.on_game_over()
  11. Kometen- und Antimatter-Spawn-Timer

Arrays der Spielobjekte

var stars, planets, nebulae, comets, galaxies: Array
var black_holes, quasars, white_holes, neutron_stars: Array
var antimatter, antimatter_stars: Array
var bullets, particles: Array
var players, enemies: Array

Rendering (_draw())

Alles wird mit Godot's Canvas-API gezeichnet — keine Sprites:

  • draw_rect() für Pixel/Rumpfe
  • draw_circle() für Schwarze Löcher, White Holes
  • draw_arc() für Akkretionsscheiben, Ringe
  • draw_line() für Trails

Zeichenreihenfolge: Nebulae → Sterne → Galaxien → Planeten → Kometen → Quasare → White Holes → Neutronensterne → Schwarze Löcher → Antimatter → Bullets → Enemies → Boss → Spieler → Partikel → BigWipe-Overlay


Datenklassen (alle extends RefCounted)

Alle Spielobjekte sind keine Nodes — sie sind reine Datenklassen, die von game_world in Arrays verwaltet werden. Das verhindert den Node-Overhead und ermöglicht den eigenen Physik-Loop.

Spaceship (spaceship.gd)

Spieler-Schiff.

Eigenschaft Wert Beschreibung
THRUST 0.28 Beschleunigung pro Frame
TURN_SPEED 0.08 Drehgeschwindigkeit (rad/frame)
MAX_SPEED 7.5 Maximale Geschwindigkeit
DRAG 0.985 Trägheit (Reibung)
INVULN_FRAMES 90 ~1.5s Unverwundbarkeit nach Treffer
stats.ship_id String Schiff-ID für runtime-spezifische Mechaniken

Methoden:

  • update(thrust, turn, W, H, delta) — Bewegung, Screen-Wrap, Trail
  • shoot_burst() → Array[Bullet] — erzeugt 1N Bullets je nach stats.bullet_count
  • draw(canvas, frame) — malt Rumpf aus HULL_PIXELS-Tabelle mit Heading-Rotation

Screen-Wrap: Wenn Schiff den Bildschirmrand verlässt, erscheint es auf der anderen Seite (Toroidal).

EnemyShip (enemy_ship.gd)

KI-Gegner. 2 Farbvarianten (idx 0 = Rot, idx 1 = Cyan). Jeder Gegner hat eine Role (AGGRO / CIRCLE / FLANK), die per role_id % 3 beim Spawn bestimmt wird und über Respawns hinweg erhalten bleibt. role_id wird in game_world.gd als laufender Index vergeben.

Rollen:

  • AGGRO: stürmt direkt auf den nächsten Spieler zu
  • CIRCLE: hält Abstand (~180 px) und kreist kontinuierlich um den Spieler (orbit_offset dreht sich)
  • FLANK: nähert sich aus ±90°-Flankenwinkel statt frontal

Gemeinsamkeiten:

  • Schuss immer direkt auf Spieler gezielt (unabhängig von der Bewegungsrichtung) → CIRCLE/FLANK-Gegner bleiben gefährlich
  • Separation Force: Gegner stoßen sich gegenseitig ab (Radius 110 px) → kein Clumping
  • Patrol: außerhalb Reichweite navigieren Gegner zu zufälligen Zielpunkten auf dem Bildschirm (nicht mehr reines Heading-Drehen) → natürliche Verteilung
  • Schwarzloch-Ausweichen: spürt BH-Radius und weicht ab
  • Feuer-Timer ist pro role_id gestaffelt (FIRE_INTERVAL + rid×17) → Salven verteilt
  • Nach Tod: respawnt nach 48s vom zufälligen Bildschirmrand, behält Role bei

Wellenskalierung:

  • Alte Formel: 2 + (wave-1)/2 (cap 6→8 nach letztem Balancing-Pass)
  • Neue Formel: min(2 + (wave - 1), MAX_ENEMEYS) → Welle 1: 2, Welle 5: 6, Welle 10: 11, Welle 20: 21

BossShip (boss_ship.gd)

Zwei Bosse — gleiche Klasse, unterschiedliche Parameter:

WRAITH (Welle 5) LEVIATHAN (Welle 8)
HP 20 50
Farbe Magenta Orange/Feuerrot
Orbit-Geschwindigkeit 0.55 rad/s 0.44 / 0.72 (Phase 2)
Feuer-Intervall 72 Frames 60 / 42 (Phase 2)
Schüsse 3-Way 5-Way / 8-Way
Pixel-Größe 5 7

Mechaniken:

  • Orbitiert elliptisch um Bildschirmmitte
  • Heading zeigt immer zur Mitte (schießt nach innen)
  • LEVIATHAN: Phase-2-Übergang bei ≤ 50% HP → Black Hole spawnt, Musik verdichtet

Bullet (bullet.gd)

  • Geschwindigkeit: 9.6 px/frame
  • Lebensdauer: 240 Frames, Fade ab Frame 210
  • Besitzer-Typen: "p1", "p2", "enemy", "boss" (bestimmt Kollisions-Check und Farbe)
  • pierce: true wenn damage_mult >= 2.0 → trifft bis zu 2 Ziele

BlackHole (black_hole.gd)

Komplexestes Objekt im Spiel.

Parameter:

  • PULL_RADIUS: 160px Gravitationsfeld
  • SWALLOW_RADIUS: 14px Verschluck-Radius
  • FORCE_MULT: 45.0 (Gravitationsstärke)
  • SUPERNOVA_AT: 30 verschluckte Objekte → Supernova

Mechaniken:

  • Wandert langsam umher (Micro-Drift)
  • 18% Chance: jagt den nächsten Spieler (Hunting-Mode)
  • Wächst mit jedem verschluckten Objekt (radius, pull_radius, gravity)
  • Bei 30 Verschluckungen: Supernova → stirbt, spawnt Quasar + neue BHs + White/Neutron Star + Sterne/Planeten
  • SMBH: nach 12 verschluckten Galaxien → Super-Massive BH, nach 45s kollabiert er
  • BH-BH-Verschmelzung: größerer frisst kleineren

BigWipe (big_wipe.gd)

Notfall-Reset wenn > 500 kosmische Objekte vorhanden sind.

Phasen:

  1. COLLAPSE (2.33s): Bildschirm verdunkelt sich, Spieler müssen Wipe-Taste halten
  2. FLASH (0.4s): Weißer Flash
  3. Alle Objekte außer Planeten/Sternen werden gelöscht

Spieler-Reaktion: Signal p1_wipe / p2_wipe drücken während COLLAPSE → überleben. Wer nicht drückt, stirbt. Belohnung: 25 Credits (+ Credit-Bonus).

CosmicObjects (cosmic_objects.gd)

Statische äußere Klasse mit inneren Klassen:

Klasse Beschreibung
Star Wandert nach oben, kann gravitationell angezogen werden, spiralt ins BH
Planet Kreist um Orbit-Punkt, kann von BH gefangen und zerrissen werden
Nebula Rein dekorativ, bewegt sich langsam
Comet Fliegt von Bildschirmrand zu Bildschirmrand
Galaxy Spiral-Galaxie, kann von BH konsumiert werden → SMBH
Quasar Entsteht aus Supernova, lebt 30s, rein dekorativ
WhiteHole Gegenteil des BH: stößt Spieler ab, ejiziert Sterne/Planeten, lebt 60s
NeutronStar Rotierender Pulsar-Strahl, stößt Objekte im Beam-Bereich ab
Antimatter Partikel, tötet Spieler/Gegner bei Berührung, ziehen sich an
AntimatterStar Entsteht wenn 5+ Antimatter-Partikel clustern, repulsiert Spieler, lebt 50s

Roguelite-System

ShipStats (ship_stats.gd)

Alle Stats sind multiplikativ (ausgenommen additive: bullet_count, shield_charges, bh_resist):

Stat Standard Effekt
speed_mult 1.0 Schub und Max-Speed
turn_mult 1.0 Drehgeschwindigkeit
fire_rate_mult 1.0 Teilt Cooldown (höher = schneller)
damage_mult 1.0 Trefferzone; ≥ 2.0 → Pierce
bullet_speed_mult 1.0 Projektil-Geschwindigkeit
bullet_count 1 Projektile pro Schuss
shield_charges 0 Absorbiert N Treffer
invuln_mult 1.0 Unverwundbarkeitszeit nach Treffer
bh_resist 0.0 00.9: Anteil des BH-Zugs der negiert wird
wipe_mult 1.0 BigWipe-Haltezeit-Faktor
credit_bonus 1.0 Multiplikator auf alle Credits

ItemDB (item_db.gd)

Zwei Pools:

  • Legacy ITEMS (6 Einträge) — nur noch für Enemy-Boosts ab Welle 10 (game_world.gd rollt 1× daraus und wendet es auf jeden Gegner an).
  • Werkstatt-Plugin-Pool (auto-discover unter res://items/, derzeit 17 ItemDefs) — der eigentliche Shop-Pool.

Seltenheiten:

Seltenheit Gewicht Farbe
STANDARD (Common) 60 Grau
SELTEN (Uncommon) 25 Grün
EPISCH (Rare) 12 Blau
LEGENDÄR (Epic) 3 Lila

Shop zeigt nach jeder Welle 4 zufällige Items (gekaufte werden aus Folge-Rolls ausgeschlossen). Credits kommen aus:

  • Kill: 15 Credits × credit_bonus
  • Passiv: 5 Credits alle 10 Sekunden
  • BigWipe überlebt: 25 Credits × credit_bonus
  • Boss getötet: 150 (Miniboss) / 300 (Boss) Credits

Werkstatt-Flow (shop_ui.gd)

Zwei Phasen pro Shop-Öffnung:

  1. Attribut-Phase — nur in ungeraden Wellen (1, 3, 5, …). 3 zufällige Gratis-Buffs, einer wird gewählt. Werte +8 % bis +18 %. Gerade Wellen (2, 4, …) starten direkt in der Shop-Phase → dämpft frühes Snowballing.
  2. Shop-Phase — 4 Karten aus dem Plugin-Pool. Kauf per ENTER, Reroll per R, SPACE/ESC zum Verlassen.

Reroll-Kosten: 60 + 42 × reroll_count CR → 60 / 102 / 144 / 186 / 228 …. Der reroll_count wird in main.gd (reroll_count_p1 / _p2) gehalten und wellenübergreifend persistiert — verteuerter Reroll schützt den Rest des Runs vor billigem Ketten-Reroll. Reset nur bei neuem Run / Game Over.


Input-Mapping

Aktion Spieler 1 Spieler 2
Schub Pfeil Oben W
Links Pfeil Links A
Rechts Pfeil Rechts D
Schießen Leertaste F
BigWipe N E

Wellen-Progression

Welle Dauer Gegner Boss
1 20s 2
3 36s 3
5 52s 4 WRAITH (Miniboss)
7 68s 5
8 76s 5 LEVIATHAN (Full Boss)
10+ ≤120s 6 (mit Stats!)
13+ 120s 8 (Cap)

Formel: enemy_count = min(2 + (wave-1)/2, 8). Enemy-Stats bleiben Welle 19 auf Basis; ab Welle 10 bekommt jeder Gegner zusätzlich 1 zufälliges Legacy-Item.

Schwierigkeits-Ramp: difficulty = clamp(game_time / 300.0, 0.0, 1.0) — steuert Kometen-Spawn-Rate, BH-Cap und mehr.


Balancing-Notizen

Die Power-Kurve ist bewusst langsam. Quellen wurden 2026-04-20 global gesenkt, um zu verhindern, dass der Spieler Welle 3 bereits dominiert.

Plugin-Items (Kern-Multiplikatoren)

Kategorie Item Kern-Effekt Kosten
Waffe wk_burst (EPIC) fire_rate ×1.80, damage ×0.45 160
Waffe wk_laser fire_rate ×1.35, damage ×0.70 115
Waffe wk_plasma damage ×1.55, proj_speed ×0.60 130
Waffe wk_ion damage ×1.25, +1 Projektil, speed ×0.70 140
Waffe wk_rail proj_speed ×1.50, fire_rate ×0.50 125
Waffe wk_sniper proj_speed ×1.30, damage ×1.18, fire_rate ×0.55 115
Waffe wk_shotgun +2 Projektile, proj_speed ×0.40, damage ×0.80 105
Waffe wk_scatter +1 Projektil, damage ×0.75 115
Antrieb drive_overdrive speed ×1.35, 1 Schild 95
Antrieb drive_quantum speed ×1.22, turn ×1.18, proj_speed ×0.80 135
Antrieb drive_steer turn ×1.35, speed ×0.90 85
Hülle hull_giant (EPIC) +2 Schild, invuln ×1.20, speed ×0.70, turn ×0.80 220
Hülle hull_plating +1 Schild, speed ×0.85 120
Hülle hull_reaktor invuln ×1.45, fire_rate ×0.85 115
Hülle hull_nullfeld bh_resist +0.45, speed ×0.80 125
Spezial special_credit_mag credit_bonus ×1.18 170
Spezial special_wipe_core wipe_mult ×0.65 150

Legacy-ITEMS (nur Enemy-Boosts Welle 10+)

id Effekt Kosten
thrust_1 speed ×1.12 50
firerate_1 fire_rate ×1.15 50
damage_1 damage ×1.15 50
shield_1 +1 Schild 55
firerate_2 fire_rate ×1.22 90
damage_2 damage ×1.22, proj_speed ×1.10 100

Attribute (Gratis, ungerade Wellen)

ATTR_POOL in shop_ui.gd — Werte: speed/turn/fire/damage/proj je 1.081.12, invuln 1.18, credit_bonus 1.08, Schild +1.


Audio-System (sound_manager.gd)

Kein einziger Audio-File im Projekt. Alle Sounds werden mit AudioStreamGenerator pro Frame synthetisiert:

  • _play_tone(freq, duration, wave, vol_db, end_freq) — Sinus/Säge/Square mit Frequenz-Sweep und Hüllkurve
  • _play_noise(duration, vol_db) — Weißes Rauschen mit Decay
  • _play_chord_fanfare() — Drei Töne mit kurzem Delay (BigWipe überlebt)

SFX-Typen: player_shoot, enemy_shoot, enemy_die, player_die, antimatter_hit, bh_swallow, wipe_start, wipe_flash, wipe_survived, smbh_spawn, antimatter_swarm


Grafik-System

Viewport: 960×600px, Stretch-Modus canvas_items / expand
Hintergrundfarbe: #0a0a14 (Tiefschwarz-Blau)
FPS-Cap: 60
Pixel-Filter: Nearest-Neighbor (default_texture_filter=0)

Alle Schiffe benutzen HULL_PIXELS-Arrays — lokale Koordinaten-Offsets, die per cos(heading)/sin(heading) in Weltkoordinaten gedreht werden. Jeder "Pixel" ist ein 3×3-draw_rect.


Signalfluss

game_world → main_node.on_game_over()         # alle Spieler tot
game_world → main_node.on_wave_complete()      # Wave-Timer abgelaufen
game_world → main_node.add_credits(player, n)  # Credits vergeben
game_world → main_node.start_boss_music(final) # Boss spawnt
game_world → main_node.boss_phase_changed(2)   # Leviathan Phase 2
big_wipe.wipe_complete → game_world._on_wipe_complete()
shop_ui.shop_closed → main._on_shop_closed()
pause_menu.resume_requested → main._on_pause_resume()
main_menu.mode_selected → main._on_mode_selected(multi)

MCP-Plugin (addons/godot_mcp/)

Das Godot-MCP-Pro-Plugin verbindet den Godot-Editor per WebSocket mit externen KI-Tools (Claude Code). Ermöglicht das Lesen/Schreiben von Szenen, Scripts und Eigenschaften direkt aus der KI-Konversation heraus. Nur für die Entwicklung relevant — kein Einfluss auf das Spiel. Referenz dazu in der CLaude.md