Files
client/overview/structure.md
T
anekdotin c0b5f421c5 Add in-game chat, bot fill for casual, and borderless fullscreen
- In-game chat: T=all, Y=team (race doubles as team), server-relayed and
  team-filtered via new ChatManager autoload; last 10 messages shown
  bottom-left (chat/chat_box.gd).
- Bot fill for casual (bots/): keeps each of the match's 2 offered races
  at a minimum of 7 total humans+bots, spawning/despawning reactively as
  players join/leave. Bots always fly their race's fighter and use
  negative peer_ids so they ride the existing networked-ship stack
  (spawning, loadout sync, health/position sync, bullet attribution) with
  no special-casing. Casual matchmaking now forms with just 1 real player
  queued instead of waiting for a second (matchmaking-api/).
- Borderless fullscreen with stretch scaling disabled instead of scaling
  the Steam-Deck-matched 1280x800 canvas up to fill PC monitors (which
  read as zoomed in) — PC monitors now reveal more world/HUD at native
  size instead. Reworked main_menu/team_select/chat_box to position via
  anchors relative to the real window instead of hardcoded coordinates.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-13 17:02:12 -04:00

8.3 KiB
Raw Blame History

Project Structure

File Tree

spacewar/                        ← repo root
└── spacewar/                    ← Godot project root (open this in Godot editor)
    ├── project.godot
    ├── autoload/
    │   └── game_config.gd        ← autoload singleton (tuning values, player state, signals)
    ├── menu/
    │   ├── main_menu.tscn/gd     ← entry point / main scene
    │   ├── server_browser.tscn/gd ← server browser UI (built, not in active flow yet)
    │   ├── pause_menu.tscn/gd    ← in-game pause overlay (ESC / Start button)
    │   └── team_select.tscn/gd   ← race + ship selection overlay (shown on world load)
    ├── ships/
    │   ├── ship.tscn             ← player ship scene (formerly node_2d.tscn)
    │   ├── ship_movement.gd      ← player ship logic
    │   └── bullet.tscn/gd        ← projectile
    ├── chat/
    │   └── chat_box.tscn/gd      ← WoW-style chat overlay (T=all, Y=team), added to world.tscn
    ├── bots/
    │   ├── bot_manager.gd         ← autoload; server-only bot fill (join/leave reconciliation)
    │   ├── bot_ai.gd              ← class_name BotAI; seek-nearest-enemy-and-shoot brain
    │   └── bot_names.gd           ← class_name BotNames; procedural callsign pool
    ├── world/
    │   ├── world.tscn             ← game world; world.gd loads the active map into MapContainer
    │   ├── world.gd                ← picks/instances a map scene from world/maps/
    │   ├── world_tileset.tres      ← shared TileSet resource (walls.png + asteroids.png atlas sources)
    │   └── maps/
    │       └── map_01.tscn         ← hand-painted map: Walls + Asteroids TileMapLayers, team spawn Marker2Ds
    └── assets/
        ├── icon.svg
        └── images/
            ├── background/skybox/     ← 6 space background PNGs (1.png  6.png)
            ├── effects/                ← explosion1.png
            ├── ships/example_ships/   ← 4 placeholder ship sprites (1.png, 1B.png, 2a.png, 3b.png)
            └── tiles/                  ← walls.png, asteroids.png, used by world_tileset.tres

Note: asteroid_movement.gd referenced in an earlier version of this doc does not exist yet — it's still an open task (see CLAUDE.md).

Scene Graph

menu/main_menu.tscn — entry point

MainMenu (Control)   ← main_menu.gd builds all UI in _ready()

world/world.tscn — game world

World (Node2D)              ← world.gd instances MAP_SCENE into MapContainer on _ready()
├── MapContainer (Node2D)    ← holds the instanced map (world/maps/map_01.tscn), background included
├── Player                   ← instance of ships/ship.tscn
├── HUD (CanvasLayer)
│   └── HealthLabel
├── PauseMenu (CanvasLayer, layer=10)   ← menu/pause_menu.tscn
├── TeamSelect (CanvasLayer, layer=20)  ← menu/team_select.tscn; queue_free()s after selection
└── ChatBox (CanvasLayer, layer=5)      ← chat/chat_box.tscn

world/maps/map_01.tscn — hand-painted map

Map01 (Node2D)
├── Background (TextureRect)     ← per-map space background (skybox/1.png); visible while editing this scene
├── Walls (TileMapLayer)         ← painted by hand in the Godot Tile Editor, uses world_tileset.tres
├── Asteroids (TileMapLayer)     ← painted by hand, same shared tileset
└── SpawnPoints (Node2D)
    ├── TeamASpawn1 (Marker2D)   ← group "team_a_spawn"
    └── TeamBSpawn1 (Marker2D)   ← group "team_b_spawn"

ships/ship.tscn — player ship

Player (CharacterBody2D)   ← ship_movement.gd
├── CollisionShape2D        ← CircleShape2D
├── Sprite2D                ← texture set at runtime from GameConfig.player_ship_path
└── VisibleOnScreenNotifier2D

Input Map

Action Key Controller
move_up W
move_left A
move_right D
move_down S
shoot Space
toggle_pause Esc Start / Options button (JoyButton 6)
chat_all T
chat_team Y

Game Flow

main_menu.tscn
  ├── CASUAL → world.tscn
  │     ├── TeamSelect overlay: pick race (2 random of 3 offered)
  │     ├── TeamSelect overlay: pick ship
  │     └── Player spawns → game live
  └── RANKED → disabled (coming soon)

Note: server_browser.tscn was built (Task 1) and is functional, but the current flow bypasses it — CASUAL goes directly to the world and TeamSelect handles name/race/ship. The server browser will be re-integrated when real multiplayer server listing is built. It still uses the old hardcoded-1280px layout style (see Display below) — not yet updated since it isn't reachable in the live flow.

Display

Launches borderless fullscreen at the real screen resolution (window/size/mode=3 in project.godot) with stretch mode disabled — 1 game pixel = 1 screen pixel everywhere, no UI/world scaling. Chosen over Godot's default canvas_items+expand stretch (which scales the whole 2D canvas to fill the window) because the game's design canvas is deliberately sized to the Steam Deck's native 1280×800; scaling that up to fill a PC monitor made everything look zoomed in. With stretch disabled, PC monitors just reveal more of the world/HUD at native size instead — the actual map (world/maps/map_01.tscn) is already a fixed-size 14016×6000 arena, not viewport-sized, so there's more world to reveal.

This means every menu Control has to position itself relative to the real window size, not a fixed 1280×800 design canvas — anchors (0.0=edge, 0.5=center, 1.0=opposite edge) plus fixed pixel offsets from that anchor, not raw absolute pixel coordinates. main_menu.gd and team_select.gd both have a _place()/direct-property-assignment helper for this — always set anchor properties directly (node.anchor_left = ...) rather than through sequential set_anchor_and_offset() calls: that method's default push_opposite_anchor=true drags the opposite side's anchor/offset along whenever two sequential calls momentarily disagree (e.g. left set to a 0.5 anchor while right is still its 0.0 default), corrupting layout — hit this exact bug converting the play-mode buttons to be centered.

Key Autoload — GameConfig

Property Type Set by
player_name String Main menu callsign input
player_race int TeamSelect race pick
player_ship_path String TeamSelect ship pick
team_selected signal Emitted by TeamSelect when done
ship_thrust float Tuning constant
ship_max_speed float Tuning constant
ship_rotation_speed float Tuning constant
ship_fire_rate float Tuning constant
bullet_speed float Tuning constant
ship_max_health int Tuning constant
ship_respawn_delay float Tuning constant
ship_invincibility_time float Tuning constant
bullet_damage int Tuning constant
ship_wall_damage int Tuning constant
chat_focused bool Set by ChatBox while its input line has keyboard focus; gates ship movement/fire input
bot_min_team_size int Tuning constant — min humans+bots per race, see bots/bot_manager.gd
bot_engage_range float Tuning constant — bot max shoot distance
bot_stop_distance float Tuning constant — bot stops closing distance below this
bot_aim_tolerance_deg float Tuning constant — how precisely a bot must face a target to fire

Key Autoload — BotManager

Server-authoritative bot fill for casual (bots/bot_manager.gd). Keeps each of the match's 2 offered races at GameConfig.bot_min_team_size total humans+bots, reacting to PlayerRegistry.loadout_updated/player_removed and World.decide_offered_races(). See overview/bots.md.

Key Autoload — ChatManager

Server-relayed chat (autoload/chat_manager.gd). send_chat(text, team_only) sends; message_received(sender_name, sender_race, team_only, text) signal delivers incoming messages to ChatBox. "Team" messages are filtered server-side to peers sharing the sender's race (race doubles as team — see PlayerRegistry).