diff --git a/assets/sprites/textures/dirt.png b/assets/sprites/textures/dirt.png new file mode 100644 index 0000000..f4515b3 Binary files /dev/null and b/assets/sprites/textures/dirt.png differ diff --git a/assets/sprites/textures/grass.png b/assets/sprites/textures/grass.png new file mode 100644 index 0000000..e3e5ee3 Binary files /dev/null and b/assets/sprites/textures/grass.png differ diff --git a/assets/sprites/textures/rock.png b/assets/sprites/textures/rock.png new file mode 100644 index 0000000..b075359 Binary files /dev/null and b/assets/sprites/textures/rock.png differ diff --git a/assets/sprites/textures/sand.png b/assets/sprites/textures/sand.png new file mode 100644 index 0000000..8065f47 Binary files /dev/null and b/assets/sprites/textures/sand.png differ diff --git a/assets/sprites/textures/stone.png b/assets/sprites/textures/stone.png new file mode 100644 index 0000000..e4284f6 Binary files /dev/null and b/assets/sprites/textures/stone.png differ diff --git a/assets/sprites/textures/tree.png b/assets/sprites/textures/tree.png new file mode 100644 index 0000000..eebf1f2 Binary files /dev/null and b/assets/sprites/textures/tree.png differ diff --git a/assets/sprites/textures/water.png b/assets/sprites/textures/water.png new file mode 100644 index 0000000..4566ef4 Binary files /dev/null and b/assets/sprites/textures/water.png differ diff --git a/client/client_world.ts b/client/client_world.ts index 8419d67..4c5c808 100644 --- a/client/client_world.ts +++ b/client/client_world.ts @@ -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"); } diff --git a/client/components/camera.ts b/client/components/camera.ts index caad059..aebfdb1 100644 --- a/client/components/camera.ts +++ b/client/components/camera.ts @@ -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, + }; +} diff --git a/client/components/dimension.ts b/client/components/dimension.ts new file mode 100644 index 0000000..4ef79e1 --- /dev/null +++ b/client/components/dimension.ts @@ -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 | undefined; +} + +export class Dimension extends Component { + image: HTMLImageElement; + tiles: Tile[]; + + constructor(tiles: Tile[]) { + super(); + this.image = AssetManager.instance.get("bworld:textures"); + this.tiles = tiles; + } +} diff --git a/client/components/tilemap.ts b/client/components/tilemap.ts deleted file mode 100644 index 3826007..0000000 --- a/client/components/tilemap.ts +++ /dev/null @@ -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; - } -} diff --git a/client/game.ts b/client/game.ts index 0559fbe..c403413 100644 --- a/client/game.ts +++ b/client/game.ts @@ -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); diff --git a/client/generation.ts b/client/generation.ts new file mode 100644 index 0000000..db90d1b --- /dev/null +++ b/client/generation.ts @@ -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", + }); + } + } + } + } +} diff --git a/client/systems/render_system.ts b/client/systems/render_system.ts index f650e17..93f04a0 100644 --- a/client/systems/render_system.ts +++ b/client/systems/render_system.ts @@ -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 diff --git a/client/systems/rendering/dimension.ts b/client/systems/rendering/dimension.ts new file mode 100644 index 0000000..8b1f54e --- /dev/null +++ b/client/systems/rendering/dimension.ts @@ -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(); +} diff --git a/client/systems/rendering/tilemap.ts b/client/systems/rendering/tilemap.ts deleted file mode 100644 index 6770f09..0000000 --- a/client/systems/rendering/tilemap.ts +++ /dev/null @@ -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(); -} diff --git a/client/systems/tile_editor_system.ts b/client/systems/tile_editor_system.ts deleted file mode 100644 index f51f354..0000000 --- a/client/systems/tile_editor_system.ts +++ /dev/null @@ -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(); - } -} diff --git a/common/constants.ts b/common/constants.ts index d0c8846..ac7aadb 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -1,3 +1,6 @@ +export const TEXTURE_SIZE = 16; +export const TILE_SIZE = 32; + export const SLOT_SIZE = 18 * 3; export interface SpriteRegion { diff --git a/deno.json b/deno.json index 55f8078..5147f5e 100644 --- a/deno.json +++ b/deno.json @@ -16,6 +16,7 @@ "imports": { "$/": "./", "@gfx/canvas-wasm": "jsr:@gfx/canvas-wasm@^0.4.2", + "@paulaboks/rng": "jsr:@paulaboks/rng@^0.0.2", "@std/fs": "jsr:@std/fs@^1.0.23", "marked": "npm:marked@^17.0.3" } diff --git a/deno.lock b/deno.lock index f10b053..85b5ff8 100644 --- a/deno.lock +++ b/deno.lock @@ -2,6 +2,7 @@ "version": "5", "specifiers": { "jsr:@gfx/canvas-wasm@~0.4.2": "0.4.2", + "jsr:@paulaboks/rng@^0.0.2": "0.0.2", "jsr:@std/encoding@1.0.5": "1.0.5", "jsr:@std/fs@^1.0.23": "1.0.23", "jsr:@std/internal@^1.0.12": "1.0.12", @@ -15,6 +16,9 @@ "jsr:@std/encoding" ] }, + "@paulaboks/rng@0.0.2": { + "integrity": "9b4833c80f09f2b6455f3d477977caaab0aac1b04a89970166a2c5169ab4687d" + }, "@std/encoding@1.0.5": { "integrity": "ecf363d4fc25bd85bd915ff6733a7e79b67e0e7806334af15f4645c569fefc04" }, @@ -44,6 +48,7 @@ "workspace": { "dependencies": [ "jsr:@gfx/canvas-wasm@~0.4.2", + "jsr:@paulaboks/rng@^0.0.2", "jsr:@std/fs@^1.0.23", "npm:marked@^17.0.3" ]