From 28f0422771e8a85ba25339e4d428ebc767fdfd15 Mon Sep 17 00:00:00 2001 From: Paula Date: Wed, 25 Feb 2026 22:58:36 -0300 Subject: [PATCH] Game states and main menu --- client/client_world.ts | 58 +++++++-------------- client/game.ts | 38 ++++++++++++++ client/main_menu.ts | 20 ++++++++ client/systems/inventory_system.ts | 4 -- client/systems/player_controls.ts | 26 ++++------ client/systems/ui_interaction_system.ts | 9 ---- client/systems/ui_render_system.ts | 4 -- common/ecs/world.ts | 68 +++++++++++++++++++++---- 8 files changed, 143 insertions(+), 84 deletions(-) create mode 100644 client/game.ts create mode 100644 client/main_menu.ts diff --git a/client/client_world.ts b/client/client_world.ts index 7b7d6cc..89bdb62 100644 --- a/client/client_world.ts +++ b/client/client_world.ts @@ -1,19 +1,14 @@ -import { Entity, World } from "$/common/ecs/mod.ts"; +import { World } from "$/common/ecs/mod.ts"; import { MovementSystem } from "$/common/systems/movement_system.ts"; import { RenderSystem } from "$/client/systems/render_system.ts"; import { PlayerControlsSystem } from "$/client/systems/player_controls.ts"; import { DebugUI } from "$/client/debug_ui.ts"; import { DebugSystem } from "$/client/systems/debug_system.ts"; -import { create_player } from "$/client/player.ts"; -import { Tilemap } from "$/client/components/tilemap.ts"; -import { AssetManager } from "$/client/assets.ts"; import { TileEditorSystem } from "$/client/systems/tile_editor_system.ts"; import { InventorySystem } from "$/client/systems/inventory_system.ts"; import { UIInteractionSystem } from "$/client/systems/ui_interaction_system.ts"; import { UIRenderSystem } from "$/client/systems/ui_render_system.ts"; -import { UIButton } from "./components/ui_components.ts"; -import { Position } from "../common/components/position.ts"; -import { open_about } from "./about.ts"; +import { create_main_menu } from "./main_menu.ts"; export class ClientWorld extends World { canvas: HTMLCanvasElement; @@ -23,7 +18,11 @@ export class ClientWorld extends World { debugging = false; constructor(canvas: HTMLCanvasElement) { - super(); + super("main_menu"); + + this.add_state("main_menu"); + this.add_state("paused"); + this.add_state("game"); this.canvas = canvas; const ctx = canvas.getContext("2d"); @@ -52,41 +51,20 @@ export class ClientWorld extends World { DebugUI.initialize(this.ctx); - const tilemap = new Entity("backgroundtiles"); - tilemap.add( - new Tilemap( - AssetManager.instance.get("bworld:roguelike"), - 16, - [], - 2, - 1, - ), - ); - this.add_entity(tilemap); - - const player = create_player(this); - - // UI ! - const unpause_button = new Entity("unpausebutton"); - unpause_button.add(new Position(canvas.width / 2 - 150, canvas.height / 2 - 80)); - unpause_button.add(new UIButton("Unpause", 320, 64, () => this.paused = false)); - this.add_entity(unpause_button); - - const about_button = new Entity("aboutbutton"); - about_button.add(new Position(canvas.width / 2 - 150, canvas.height / 2 + 80)); - about_button.add(new UIButton("About", 320, 64, () => open_about())); - this.add_entity(about_button); + create_main_menu(this); // Logic systems - this.add_system(new UIInteractionSystem()); - this.add_system(new InventorySystem()); - this.add_system(new PlayerControlsSystem(player)); - this.add_system(new MovementSystem()); + this.add_system(new UIInteractionSystem(), "main_menu"); + this.add_system(new UIInteractionSystem(), "paused"); + this.add_system(new InventorySystem(), "game"); + this.add_system(new PlayerControlsSystem(), "game"); + this.add_system(new MovementSystem(), "game"); // render systems - this.add_system(new RenderSystem(this.ctx)); - this.add_system(new DebugSystem()); - this.add_system(new TileEditorSystem()); - this.add_system(new UIRenderSystem()); + this.add_system(new RenderSystem(this.ctx), "game"); + this.add_system(new DebugSystem(), "*"); + this.add_system(new TileEditorSystem(), "game"); + this.add_system(new UIRenderSystem(), "main_menu"); + this.add_system(new UIRenderSystem(), "paused"); } } diff --git a/client/game.ts b/client/game.ts new file mode 100644 index 0000000..7a22733 --- /dev/null +++ b/client/game.ts @@ -0,0 +1,38 @@ +import { Entity } from "$/common/ecs/mod.ts"; +import { Position } from "$/common/components/position.ts"; +import { ClientWorld } from "./client_world.ts"; +import { Tilemap } from "./components/tilemap.ts"; +import { AssetManager } from "./assets.ts"; +import { create_player } from "./player.ts"; +import { UIButton } from "./components/ui_components.ts"; +import { open_about } from "./about.ts"; + +export function start_game(world: ClientWorld) { + world.state = "game"; + world.clear_entities(); + + const tilemap = new Entity("backgroundtiles"); + tilemap.add( + new Tilemap( + AssetManager.instance.get("bworld:roguelike"), + 16, + [], + 2, + 1, + ), + ); + world.add_entity(tilemap); + + create_player(world); + + // UI ! + const unpause_button = new Entity("unpausebutton"); + unpause_button.add(new Position(world.canvas.width / 2 - 150, world.canvas.height / 2 - 80)); + unpause_button.add(new UIButton("Unpause", 320, 64, () => world.state = "paused")); + world.add_entity(unpause_button); + + const about_button = new Entity("aboutbutton"); + about_button.add(new Position(world.canvas.width / 2 - 150, world.canvas.height / 2 + 80)); + about_button.add(new UIButton("About", 320, 64, () => open_about())); + world.add_entity(about_button); +} diff --git a/client/main_menu.ts b/client/main_menu.ts new file mode 100644 index 0000000..575ee10 --- /dev/null +++ b/client/main_menu.ts @@ -0,0 +1,20 @@ +import { Entity } from "$/common/ecs/mod.ts"; +import { Position } from "$/common/components/position.ts"; +import { ClientWorld } from "./client_world.ts"; +import { UIButton } from "./components/ui_components.ts"; +import { open_about } from "./about.ts"; +import { start_game } from "./game.ts"; + +export function create_main_menu(world: ClientWorld) { + const canvas = world.canvas; + + const play_button = new Entity("play"); + play_button.add(new Position(canvas.width / 2 - 150, canvas.height / 2 - 80)); + play_button.add(new UIButton("Play", 320, 64, () => start_game(world))); + world.add_entity(play_button); + + const about_button = new Entity("aboutbutton"); + about_button.add(new Position(canvas.width / 2 - 150, canvas.height / 2 + 80)); + about_button.add(new UIButton("About", 320, 64, () => open_about())); + world.add_entity(about_button); +} diff --git a/client/systems/inventory_system.ts b/client/systems/inventory_system.ts index 4270f7d..4684e1d 100644 --- a/client/systems/inventory_system.ts +++ b/client/systems/inventory_system.ts @@ -9,10 +9,6 @@ import { Sprite } from "../components/sprite.ts"; export class InventorySystem extends System { update(world: ClientWorld, _delta: number): void { - if (world.paused) { - return; - } - for (const entity of world.get_entities()) { const player_inventory = entity.get(PlayerInventory); diff --git a/client/systems/player_controls.ts b/client/systems/player_controls.ts index 14ea061..af758c3 100644 --- a/client/systems/player_controls.ts +++ b/client/systems/player_controls.ts @@ -1,4 +1,4 @@ -import { Entity, System } from "$/common/ecs/mod.ts"; +import { System } from "$/common/ecs/mod.ts"; import { Velocity } from "$/common/components/velocity.ts"; import { InputManager } from "../input_manager.ts"; import { AnimatedSprite } from "../components/sprite.ts"; @@ -9,21 +9,15 @@ import { Camera } from "../components/camera.ts"; import { Position } from "../../common/components/position.ts"; export class PlayerControlsSystem extends System { - player: Entity; - - constructor(player: Entity) { + constructor() { super(); - this.player = player; } update(world: ClientWorld, _delta: number): void { - if (world.paused) { - return; - } - - const velocity = this.player.get(Velocity)!; - const controls = this.player.get(PlayerControls)!; - const player_inventory = this.player.get(PlayerInventory)!; + const [player] = world.get_tag("player")!; + const velocity = player.get(Velocity)!; + const controls = player.get(PlayerControls)!; + const player_inventory = player.get(PlayerInventory)!; if (InputManager.is_key_down(controls.move_left)) { velocity.vx = -controls.move_speed; @@ -41,7 +35,7 @@ export class PlayerControlsSystem extends System { velocity.vy = 0; } - const animated_sprite = this.player.get(AnimatedSprite)!; + const animated_sprite = player.get(AnimatedSprite)!; if ( InputManager.is_key_down(controls.move_up) || InputManager.is_key_down(controls.move_down) || @@ -59,7 +53,7 @@ export class PlayerControlsSystem extends System { } if (InputManager.is_key_pressed(controls.open_inventory)) { - const inventory = this.player.get(PlayerInventory)!; + const inventory = player.get(PlayerInventory)!; inventory.is_open = !inventory.is_open; } @@ -67,8 +61,8 @@ export class PlayerControlsSystem extends System { world.debugging = !world.debugging; } - const position = this.player.get(Position)!; - const camera = this.player.get(Camera)!; + const position = player.get(Position)!; + const camera = player.get(Camera)!; camera.x = Math.round(position.x); camera.y = Math.round(position.y); diff --git a/client/systems/ui_interaction_system.ts b/client/systems/ui_interaction_system.ts index 4ee71a7..0c76f79 100644 --- a/client/systems/ui_interaction_system.ts +++ b/client/systems/ui_interaction_system.ts @@ -7,15 +7,6 @@ import { InputManager } from "$/client/input_manager.ts"; export class UIInteractionSystem implements System { update(world: ClientWorld, _delta: number) { - // TODO: dont like - if (InputManager.is_key_pressed("Escape")) { - world.paused = !world.paused; - } - - if (!world.paused) { - return; - } - const mouse = InputManager.get_mouse_position(); for (const entity of world.get_entities()) { diff --git a/client/systems/ui_render_system.ts b/client/systems/ui_render_system.ts index a30d331..909d662 100644 --- a/client/systems/ui_render_system.ts +++ b/client/systems/ui_render_system.ts @@ -12,10 +12,6 @@ export class UIRenderSystem implements System { constructor() {} update(world: ClientWorld, _delta: number) { - if (!world.paused) { - return; - } - const ctx = world.ctx; ctx.save(); diff --git a/common/ecs/world.ts b/common/ecs/world.ts index ffca0e5..5d03e17 100644 --- a/common/ecs/world.ts +++ b/common/ecs/world.ts @@ -2,33 +2,79 @@ import type { Entity } from "./entity.ts"; import type { System } from "./system.ts"; export class World { - entities = new Set(); - systems = new Set(); - tags = new Map(); + #entities = new Set(); + #entities_for_deletion = new Set(); - add_entity(entity: Entity) { - this.entities.add(entity); + #systems = new Map>(); + #tags = new Map(); + #states = new Set(); + + #state: string = ""; + #new_state: string | undefined; + + constructor(initial_state: string) { + this.#state = initial_state; + this.add_state("*"); } - add_system(system: System) { - this.systems.add(system); + add_state(new_state: string) { + this.#states.add(new_state); + this.#systems.set(new_state, new Set()); + } + + add_entity(entity: Entity) { + this.#entities.add(entity); + } + + add_system(system: System, state: string) { + console.assert(this.#states.has(state)); + this.#systems.get(state)!.add(system); } add_tag(tag: string, entities: Entity[]) { - this.tags.set(tag, entities); + this.#tags.set(tag, entities); } get_tag(tag: string) { - return this.tags.get(tag); + return this.#tags.get(tag); } update(delta: number) { - for (const system of this.systems) { + for (const system of this.#systems.get("*") ?? []) { system.update(this, delta); } + for (const system of this.#systems.get(this.#state) ?? []) { + system.update(this, delta); + } + // should be faster than Set.prototype.difference lol + for (const entity of this.#entities_for_deletion) { + this.#entities.delete(entity); + } + if (this.#new_state) { + this.#state = this.#new_state; + this.#new_state = undefined; + } } get_entities() { - return this.entities; + return this.#entities; + } + + delete_entity(entity: Entity) { + this.#entities_for_deletion.add(entity); + } + + clear_entities() { + for (const entity of this.#entities) { + this.#entities_for_deletion.add(entity); + } + } + + get state() { + return this.#state; + } + + set state(new_state: string) { + this.#new_state = new_state; } }