Change tilemap to dimension and simple generation

This commit is contained in:
2026-03-06 15:46:42 -03:00
parent 0f3f8de735
commit e5d2a3e7e2
20 changed files with 137 additions and 186 deletions
-2
View File
@@ -4,7 +4,6 @@ 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 { 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";
@@ -67,7 +66,6 @@ export class ClientWorld extends World {
// render systems
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");
}
+13
View File
@@ -26,3 +26,16 @@ export function screen_to_world(
y: (screen_y - screen_height / 2) / camera.zoom + camera.y,
};
}
export function world_to_screen(
world_x: number,
world_y: number,
camera: Camera,
screen_width: number,
screen_height: number,
) {
return {
x: (world_x - camera.x) / camera.zoom + screen_width / 2,
y: (world_y - camera.y) / camera.zoom + screen_height / 2,
};
}
+20
View File
@@ -0,0 +1,20 @@
import { Component } from "$/common/ecs/mod.ts";
import { AssetManager } from "../assets.ts";
interface Tile {
x: number;
y: number;
id: string;
data?: Record<string, unknown> | undefined;
}
export class Dimension extends Component {
image: HTMLImageElement;
tiles: Tile[];
constructor(tiles: Tile[]) {
super();
this.image = AssetManager.instance.get("bworld:textures");
this.tiles = tiles;
}
}
-37
View File
@@ -1,37 +0,0 @@
import { Component } from "$/common/ecs/mod.ts";
interface Tile {
x: number;
y: number;
index: number;
}
export class Tilemap extends Component {
image: HTMLImageElement;
tile_size: number;
rows: number;
columns: number;
tiles: Tile[];
scale: number;
margin: number;
selected_tile = 0;
editing = false;
constructor(
image: HTMLImageElement,
tile_size: number,
tiles: Tile[],
scale: number = 1,
margin: number = 0,
) {
super();
this.image = image;
this.tile_size = tile_size;
this.columns = Math.floor((image.width + margin) / (tile_size + margin));
this.rows = Math.floor((image.height + margin) / (tile_size + margin));
this.tiles = tiles;
this.scale = scale;
this.margin = margin;
}
}
+6 -13
View File
@@ -1,28 +1,21 @@
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 { Dimension } from "./components/dimension.ts";
import { create_player } from "./player.ts";
import { UIButton } from "./components/ui_components.ts";
import { open_about } from "./about.ts";
import { create_crop_entity, Crop } from "./components/crop.ts";
import { generate } from "./generation.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);
const dimension = new Entity("dimension");
dimension.add(new Dimension([]));
world.add_entity(dimension);
generate(dimension.get(Dimension)!, 100, 100);
create_player(world);
+47
View File
@@ -0,0 +1,47 @@
import { Alea, create_noise_2d } from "@paulaboks/rng";
import { Dimension } from "./components/dimension.ts";
export function generate(dimension: Dimension, width: number, height: number, seed = "seed") {
const height_noise = create_noise_2d(new Alea(seed + "_height"));
const tree_noise = create_noise_2d(new Alea(seed + "_trees"));
const scale = 0.05;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
// multi-octave terrain
const nx = x * scale;
const ny = y * scale;
let h = height_noise(nx, ny) * 1 + height_noise(nx * 2, ny * 2) * 0.5 + height_noise(nx * 4, ny * 4) * 0.25;
h /= 1.75; // normalize
let id = "bworld:grass";
if (h < -0.25) {
id = "bworld:water";
} else if (h < -0.15) {
id = "bworld:sand";
} else {
id = "bworld:grass";
}
dimension.tiles.push({ x, y, id });
// tree placement
if (id === "bworld:grass") {
const t = tree_noise(x * 0.12, y * 0.12);
if (t > 0.65) {
dimension.tiles.push({
x,
y,
id: "bworld:tree",
});
}
}
}
}
}
+5 -5
View File
@@ -2,12 +2,12 @@ import { System } from "$/common/ecs/mod.ts";
import { World } from "$/common/ecs/world.ts";
import { Position } from "$/common/components/position.ts";
import { AnimatedSprite, Sprite } from "$/client/components/sprite.ts";
import { Tilemap } from "$/client/components/tilemap.ts";
import { Dimension } from "../components/dimension.ts";
import { PlayerInventory } from "$/client/components/inventory.ts";
import { Camera } from "$/client/components/camera.ts";
import { render_animated_sprite, render_sprite } from "./rendering/sprites.ts";
import { render_tilemap } from "./rendering/tilemap.ts";
import { render_dimension } from "./rendering/dimension.ts";
import { render_player_inventory } from "./rendering/player.ts";
export class RenderSystem extends System {
@@ -56,10 +56,10 @@ export class RenderSystem extends System {
render_animated_sprite(this.ctx, animated_sprite, position);
}
const tilemap = entity.get(Tilemap);
const dimension = entity.get(Dimension);
if (tilemap) {
render_tilemap(this.ctx, tilemap);
if (dimension) {
render_dimension(this.ctx, dimension, camera);
}
// Ui
+37
View File
@@ -0,0 +1,37 @@
import { get_sprite_region } from "$/common/utils.ts";
import { Dimension } from "$/client/components/dimension.ts";
import { TEXTURE_SIZE, TILE_SIZE } from "$/common/constants.ts";
import { Camera, world_to_screen } from "$/client/components/camera.ts";
export function render_dimension(ctx: CanvasRenderingContext2D, tilemap: Dimension, camera: Camera) {
ctx.save();
for (const tile of tilemap.tiles) {
const region = get_sprite_region(tile.id);
const x = tile.x * TILE_SIZE;
const y = tile.y * TILE_SIZE;
const screen_position = world_to_screen(x, y, camera, ctx.canvas.width, ctx.canvas.height);
if (
screen_position.x + TILE_SIZE < 0 || screen_position.x > ctx.canvas.width ||
screen_position.y + TILE_SIZE < 0 || screen_position.y > ctx.canvas.height
) {
continue;
}
ctx.drawImage(
tilemap.image,
region.x * TEXTURE_SIZE,
region.y * TEXTURE_SIZE,
TEXTURE_SIZE,
TEXTURE_SIZE,
x,
y,
TILE_SIZE,
TILE_SIZE,
);
}
ctx.restore();
}
-30
View File
@@ -1,30 +0,0 @@
import { Tilemap } from "$/client/components/tilemap.ts";
export function render_tilemap(ctx: CanvasRenderingContext2D, tilemap: Tilemap) {
ctx.save();
ctx.translate(
-Math.floor(ctx.canvas.width / 2),
-Math.floor(ctx.canvas.height / 2),
);
for (const tile of tilemap.tiles) {
const tx = tile.index % tilemap.columns;
const ty = Math.floor(tile.index / tilemap.columns);
const sx = tx * tilemap.tile_size + (tx * tilemap.margin);
const sy = ty * tilemap.tile_size + (ty * tilemap.margin);
ctx.drawImage(
tilemap.image,
sx,
sy,
tilemap.tile_size,
tilemap.tile_size,
tile.x * tilemap.tile_size * tilemap.scale,
tile.y * tilemap.tile_size * tilemap.scale,
tilemap.tile_size * tilemap.scale,
tilemap.tile_size * tilemap.scale,
);
}
ctx.restore();
}
-99
View File
@@ -1,99 +0,0 @@
import { System } from "$/common/ecs/mod.ts";
import { point_inside_rec } from "$/common/utils.ts";
import { ClientWorld } from "$/client/client_world.ts";
import { Camera } from "$/client/components/camera.ts";
import { Tilemap } from "$/client/components/tilemap.ts";
import { DebugUI } from "$/client/debug_ui.ts";
import { InputManager } from "$/client/input_manager.ts";
export class TileEditorSystem extends System {
constructor() {
super();
}
update(world: ClientWorld, _delta: number): void {
if (!world.debugging) {
return;
}
DebugUI.ctx.save();
DebugUI.ctx.setTransform(1, 0, 0, 1, 0, 0);
const ctx = world.ctx;
const camera_entity = world.get_entities().values().find((e) => e.get(Camera)?.active);
const camera = camera_entity?.get(Camera);
if (!camera) {
return;
}
for (const entity of world.get_entities()) {
const tilemap = entity.get(Tilemap);
if (!tilemap) {
continue;
}
const scaled_size = tilemap.tile_size * tilemap.scale;
const mouse = InputManager.get_mouse_position();
DebugUI.begin("Tile Editor", 500, 10, 300);
DebugUI.progress_bar(0.5);
if (DebugUI.button("Save")) {
navigator.clipboard.writeText(JSON.stringify(tilemap));
}
for (let i = 0; i < tilemap.rows; i += 1) {
for (let j = 0; j < tilemap.columns; j += 1) {
const x = DebugUI.cursor_x + j * scaled_size;
const y = DebugUI.cursor_y;
const hovered = point_inside_rec(mouse.x, mouse.y, x, y, scaled_size, scaled_size);
if (DebugUI.is_inside_windows(mouse.x, mouse.y) && hovered && InputManager.is_mouse_pressed(0)) {
InputManager.consume_mouse(0);
tilemap.selected_tile = i * tilemap.columns + j;
}
ctx.drawImage(
tilemap.image,
j * tilemap.tile_size + (j * tilemap.margin),
i * tilemap.tile_size + (i * tilemap.margin),
tilemap.tile_size,
tilemap.tile_size,
x,
y,
scaled_size,
scaled_size,
);
}
DebugUI.advance(scaled_size);
}
DebugUI.end();
if (tilemap.editing) {
const world_x = mouse.x + camera.x;
const world_y = mouse.y + camera.y;
const tx = Math.floor(world_x / (tilemap.tile_size * tilemap.scale));
const ty = Math.floor(world_y / (tilemap.tile_size * tilemap.scale));
if (InputManager.is_mouse_pressed(0)) {
const maybe_tile = tilemap.tiles.find((tile) => tile.x === tx && tile.y === ty);
if (maybe_tile?.index !== tilemap.selected_tile) {
tilemap.tiles.push({ x: tx, y: ty, index: tilemap.selected_tile });
}
} else if (InputManager.is_mouse_pressed(1)) {
InputManager.consume_mouse(1);
const index = tilemap.tiles.findLastIndex((tile) => tile.x === tx && tile.y === ty);
if (index !== -1) {
tilemap.tiles.splice(index, 1);
}
}
}
}
DebugUI.ctx.restore();
}
}