UI and about page in game
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
import { marked } from "marked";
|
||||
import { AssetManager } from "./assets.ts";
|
||||
|
||||
export async function open_about() {
|
||||
const popup = self.open(
|
||||
"",
|
||||
"popupWindow",
|
||||
"width=500,height=400",
|
||||
);
|
||||
|
||||
if (!popup) {
|
||||
alert("oh no");
|
||||
return;
|
||||
}
|
||||
|
||||
popup.document.body.innerHTML = await marked.parse(AssetManager.instance.get("bworld:assets_text"));
|
||||
popup.document.head.innerHTML = `<style>
|
||||
body {
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
line-height: 1.7;
|
||||
color: #e5e7eb;
|
||||
max-width: 800px;
|
||||
margin: 2rem auto;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
h1, h2, h3, h4 {
|
||||
font-weight: 600;
|
||||
line-height: 1.3;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
padding-bottom: 0.3rem;
|
||||
}
|
||||
h2 {
|
||||
font-size: 1.5rem;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
padding-bottom: 0.3rem;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
p {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
a {
|
||||
color: #2563eb;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
ul, ol {
|
||||
padding-left: 2rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
code {
|
||||
background: #1f2937;
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 4px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
pre {
|
||||
background: #0f172a;
|
||||
color: #f9fafb;
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
pre code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
}
|
||||
blockquote {
|
||||
border-left: 4px solid #d1d5db;
|
||||
padding-left: 1rem;
|
||||
color: #6b7280;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
th, td {
|
||||
border: 1px solid #e5e7eb;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
th {
|
||||
background: #f9fafb;
|
||||
text-align: left;
|
||||
}
|
||||
</style>`;
|
||||
}
|
||||
+7
-1
@@ -1,4 +1,4 @@
|
||||
type Asset = HTMLImageElement | HTMLAudioElement;
|
||||
type Asset = HTMLImageElement | HTMLAudioElement | string;
|
||||
|
||||
export class AssetManager {
|
||||
static instance = new AssetManager();
|
||||
@@ -41,6 +41,12 @@ export class AssetManager {
|
||||
audio.load();
|
||||
this.assets[key] = audio;
|
||||
this.loading_promises.push(promise);
|
||||
} else if (src.endsWith(".txt") || src.endsWith(".md")) {
|
||||
const promise = async () => {
|
||||
const resp = await fetch(src);
|
||||
this.assets[key] = await resp.text();
|
||||
};
|
||||
this.loading_promises.push(promise());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,11 +9,17 @@ 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";
|
||||
|
||||
export class ClientWorld extends World {
|
||||
canvas: HTMLCanvasElement;
|
||||
ctx: CanvasRenderingContext2D;
|
||||
|
||||
paused = false;
|
||||
debugging = false;
|
||||
|
||||
constructor(canvas: HTMLCanvasElement) {
|
||||
@@ -61,6 +67,19 @@ export class ClientWorld extends World {
|
||||
const player = create_player();
|
||||
this.add_entity(player);
|
||||
|
||||
// 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);
|
||||
|
||||
// Logic systems
|
||||
this.add_system(new UIInteractionSystem());
|
||||
this.add_system(new InventorySystem());
|
||||
this.add_system(new PlayerControlsSystem(player));
|
||||
this.add_system(new MovementSystem());
|
||||
@@ -69,5 +88,6 @@ export class ClientWorld extends World {
|
||||
this.add_system(new RenderSystem(this.ctx));
|
||||
this.add_system(new DebugSystem());
|
||||
this.add_system(new TileEditorSystem());
|
||||
this.add_system(new UIRenderSystem());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Component } from "$/common/ecs/mod.ts";
|
||||
|
||||
export class UIButton extends Component {
|
||||
text: string;
|
||||
width: number;
|
||||
height: number;
|
||||
on_click: () => void;
|
||||
|
||||
hovered = false;
|
||||
|
||||
constructor(text: string, width: number, height: number, on_click: () => void) {
|
||||
super();
|
||||
this.text = text;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.on_click = on_click;
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,7 @@ if (!canvas) {
|
||||
|
||||
InputManager.initialize(canvas);
|
||||
|
||||
AssetManager.instance.load("bworld:assets_text", "/assets/ASSETS.md");
|
||||
AssetManager.instance.load("bworld:player", "/assets/sprites/player.png");
|
||||
AssetManager.instance.load("bworld:roguelike", "/assets/sprites/roguelike.png");
|
||||
AssetManager.instance.load("bworld:tiny_town", "/assets/sprites/tiny_town.png");
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import { System, World } from "$/common/ecs/mod.ts";
|
||||
import { System } from "$/common/ecs/mod.ts";
|
||||
import { point_inside_rec } from "$/common/utils.ts";
|
||||
import { SLOT_SIZE } from "$/common/constants.ts";
|
||||
import { Container, PlayerInventory } from "$/client/components/inventory.ts";
|
||||
import { InputManager } from "$/client/input_manager.ts";
|
||||
import { ClientWorld } from "$/client/client_world.ts";
|
||||
|
||||
export class InventorySystem extends System {
|
||||
update(world: World, _delta: number): void {
|
||||
update(world: ClientWorld, _delta: number): void {
|
||||
if (world.paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const entity of world.get_entities()) {
|
||||
const player_inventory = entity.get(PlayerInventory);
|
||||
|
||||
|
||||
@@ -17,6 +17,10 @@ export class PlayerControlsSystem extends System {
|
||||
}
|
||||
|
||||
update(world: ClientWorld, _delta: number): void {
|
||||
if (world.paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
const velocity = this.player.get(Velocity)!;
|
||||
const controls = this.player.get(PlayerControls)!;
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { System } from "$/common/ecs/mod.ts";
|
||||
import { Position } from "$/common/components/position.ts";
|
||||
import { point_inside_rec } from "$/common/utils.ts";
|
||||
import { ClientWorld } from "$/client/client_world.ts";
|
||||
import { UIButton } from "$/client/components/ui_components.ts";
|
||||
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()) {
|
||||
const position = entity.get(Position);
|
||||
const button = entity.get(UIButton);
|
||||
|
||||
if (position && button) {
|
||||
const hovered = point_inside_rec(mouse.x, mouse.y, position.x, position.y, button.width, button.height);
|
||||
button.hovered = hovered;
|
||||
if (hovered && InputManager.is_mouse_pressed(0)) {
|
||||
InputManager.consume_mouse(0);
|
||||
button.on_click();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { System } from "$/common/ecs/mod.ts";
|
||||
import { Position } from "$/common/components/position.ts";
|
||||
import { ClientWorld } from "$/client/client_world.ts";
|
||||
import { UIButton } from "$/client/components/ui_components.ts";
|
||||
import { draw_text, measure_text } from "../text_rendering.ts";
|
||||
import { draw_nine_slice } from "./rendering/render_utils.ts";
|
||||
import { AssetManager } from "../assets.ts";
|
||||
|
||||
const SCALE = 4;
|
||||
|
||||
export class UIRenderSystem implements System {
|
||||
constructor() {}
|
||||
|
||||
update(world: ClientWorld, _delta: number) {
|
||||
if (!world.paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ctx = world.ctx;
|
||||
|
||||
ctx.save();
|
||||
ctx.resetTransform();
|
||||
|
||||
ctx.scale(SCALE, SCALE);
|
||||
|
||||
// dark background
|
||||
ctx.fillStyle = "rgba(0,0,0,0.6)";
|
||||
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
||||
|
||||
const ui = AssetManager.instance.get<HTMLImageElement>("bworld:ui");
|
||||
|
||||
for (const entity of world.get_entities()) {
|
||||
const position = entity.get(Position);
|
||||
const button = entity.get(UIButton);
|
||||
|
||||
if (position && button) {
|
||||
draw_nine_slice(
|
||||
ctx,
|
||||
ui,
|
||||
16 * (button.hovered ? 14 : 11),
|
||||
16 * (button.hovered ? 1 : 0),
|
||||
16,
|
||||
16,
|
||||
3,
|
||||
3,
|
||||
3,
|
||||
3,
|
||||
position.x / SCALE,
|
||||
position.y / SCALE,
|
||||
button.width / SCALE,
|
||||
button.height / SCALE,
|
||||
);
|
||||
const measure = measure_text(ctx, button.text, 1.25);
|
||||
draw_text(
|
||||
ctx,
|
||||
button.text,
|
||||
(position.x / SCALE) + (button.width / SCALE / 2) - (measure / 2),
|
||||
position.y / SCALE + (button.height / SCALE / 2),
|
||||
1.5,
|
||||
"white",
|
||||
"middle",
|
||||
);
|
||||
}
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user