Change tilemap to dimension and simple generation
|
After Width: | Height: | Size: 144 B |
|
After Width: | Height: | Size: 144 B |
|
After Width: | Height: | Size: 168 B |
|
After Width: | Height: | Size: 144 B |
|
After Width: | Height: | Size: 144 B |
|
After Width: | Height: | Size: 261 B |
|
After Width: | Height: | Size: 119 B |
@@ -4,7 +4,6 @@ import { RenderSystem } from "$/client/systems/render_system.ts";
|
|||||||
import { PlayerControlsSystem } from "$/client/systems/player_controls.ts";
|
import { PlayerControlsSystem } from "$/client/systems/player_controls.ts";
|
||||||
import { DebugUI } from "$/client/debug_ui.ts";
|
import { DebugUI } from "$/client/debug_ui.ts";
|
||||||
import { DebugSystem } from "$/client/systems/debug_system.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 { InventorySystem } from "$/client/systems/inventory_system.ts";
|
||||||
import { UIInteractionSystem } from "$/client/systems/ui_interaction_system.ts";
|
import { UIInteractionSystem } from "$/client/systems/ui_interaction_system.ts";
|
||||||
import { UIRenderSystem } from "$/client/systems/ui_render_system.ts";
|
import { UIRenderSystem } from "$/client/systems/ui_render_system.ts";
|
||||||
@@ -67,7 +66,6 @@ export class ClientWorld extends World {
|
|||||||
// render systems
|
// render systems
|
||||||
this.add_system(new RenderSystem(this.ctx), "game");
|
this.add_system(new RenderSystem(this.ctx), "game");
|
||||||
this.add_system(new DebugSystem(), "*");
|
this.add_system(new DebugSystem(), "*");
|
||||||
this.add_system(new TileEditorSystem(), "game");
|
|
||||||
this.add_system(new UIRenderSystem(), "main_menu");
|
this.add_system(new UIRenderSystem(), "main_menu");
|
||||||
this.add_system(new UIRenderSystem(), "paused");
|
this.add_system(new UIRenderSystem(), "paused");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,3 +26,16 @@ export function screen_to_world(
|
|||||||
y: (screen_y - screen_height / 2) / camera.zoom + camera.y,
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +1,21 @@
|
|||||||
import { Entity } from "$/common/ecs/mod.ts";
|
import { Entity } from "$/common/ecs/mod.ts";
|
||||||
import { Position } from "$/common/components/position.ts";
|
import { Position } from "$/common/components/position.ts";
|
||||||
import { ClientWorld } from "./client_world.ts";
|
import { ClientWorld } from "./client_world.ts";
|
||||||
import { Tilemap } from "./components/tilemap.ts";
|
import { Dimension } from "./components/dimension.ts";
|
||||||
import { AssetManager } from "./assets.ts";
|
|
||||||
import { create_player } from "./player.ts";
|
import { create_player } from "./player.ts";
|
||||||
import { UIButton } from "./components/ui_components.ts";
|
import { UIButton } from "./components/ui_components.ts";
|
||||||
import { open_about } from "./about.ts";
|
import { open_about } from "./about.ts";
|
||||||
import { create_crop_entity, Crop } from "./components/crop.ts";
|
import { create_crop_entity, Crop } from "./components/crop.ts";
|
||||||
|
import { generate } from "./generation.ts";
|
||||||
|
|
||||||
export function start_game(world: ClientWorld) {
|
export function start_game(world: ClientWorld) {
|
||||||
world.state = "game";
|
world.state = "game";
|
||||||
world.clear_entities();
|
world.clear_entities();
|
||||||
|
|
||||||
const tilemap = new Entity("backgroundtiles");
|
const dimension = new Entity("dimension");
|
||||||
tilemap.add(
|
dimension.add(new Dimension([]));
|
||||||
new Tilemap(
|
world.add_entity(dimension);
|
||||||
AssetManager.instance.get("bworld:roguelike"),
|
generate(dimension.get(Dimension)!, 100, 100);
|
||||||
16,
|
|
||||||
[],
|
|
||||||
2,
|
|
||||||
1,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
world.add_entity(tilemap);
|
|
||||||
|
|
||||||
create_player(world);
|
create_player(world);
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,12 +2,12 @@ import { System } from "$/common/ecs/mod.ts";
|
|||||||
import { World } from "$/common/ecs/world.ts";
|
import { World } from "$/common/ecs/world.ts";
|
||||||
import { Position } from "$/common/components/position.ts";
|
import { Position } from "$/common/components/position.ts";
|
||||||
import { AnimatedSprite, Sprite } from "$/client/components/sprite.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 { PlayerInventory } from "$/client/components/inventory.ts";
|
||||||
import { Camera } from "$/client/components/camera.ts";
|
import { Camera } from "$/client/components/camera.ts";
|
||||||
|
|
||||||
import { render_animated_sprite, render_sprite } from "./rendering/sprites.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";
|
import { render_player_inventory } from "./rendering/player.ts";
|
||||||
|
|
||||||
export class RenderSystem extends System {
|
export class RenderSystem extends System {
|
||||||
@@ -56,10 +56,10 @@ export class RenderSystem extends System {
|
|||||||
render_animated_sprite(this.ctx, animated_sprite, position);
|
render_animated_sprite(this.ctx, animated_sprite, position);
|
||||||
}
|
}
|
||||||
|
|
||||||
const tilemap = entity.get(Tilemap);
|
const dimension = entity.get(Dimension);
|
||||||
|
|
||||||
if (tilemap) {
|
if (dimension) {
|
||||||
render_tilemap(this.ctx, tilemap);
|
render_dimension(this.ctx, dimension, camera);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ui
|
// Ui
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
export const TEXTURE_SIZE = 16;
|
||||||
|
export const TILE_SIZE = 32;
|
||||||
|
|
||||||
export const SLOT_SIZE = 18 * 3;
|
export const SLOT_SIZE = 18 * 3;
|
||||||
|
|
||||||
export interface SpriteRegion {
|
export interface SpriteRegion {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
"imports": {
|
"imports": {
|
||||||
"$/": "./",
|
"$/": "./",
|
||||||
"@gfx/canvas-wasm": "jsr:@gfx/canvas-wasm@^0.4.2",
|
"@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",
|
"@std/fs": "jsr:@std/fs@^1.0.23",
|
||||||
"marked": "npm:marked@^17.0.3"
|
"marked": "npm:marked@^17.0.3"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
"version": "5",
|
"version": "5",
|
||||||
"specifiers": {
|
"specifiers": {
|
||||||
"jsr:@gfx/canvas-wasm@~0.4.2": "0.4.2",
|
"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/encoding@1.0.5": "1.0.5",
|
||||||
"jsr:@std/fs@^1.0.23": "1.0.23",
|
"jsr:@std/fs@^1.0.23": "1.0.23",
|
||||||
"jsr:@std/internal@^1.0.12": "1.0.12",
|
"jsr:@std/internal@^1.0.12": "1.0.12",
|
||||||
@@ -15,6 +16,9 @@
|
|||||||
"jsr:@std/encoding"
|
"jsr:@std/encoding"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"@paulaboks/rng@0.0.2": {
|
||||||
|
"integrity": "9b4833c80f09f2b6455f3d477977caaab0aac1b04a89970166a2c5169ab4687d"
|
||||||
|
},
|
||||||
"@std/encoding@1.0.5": {
|
"@std/encoding@1.0.5": {
|
||||||
"integrity": "ecf363d4fc25bd85bd915ff6733a7e79b67e0e7806334af15f4645c569fefc04"
|
"integrity": "ecf363d4fc25bd85bd915ff6733a7e79b67e0e7806334af15f4645c569fefc04"
|
||||||
},
|
},
|
||||||
@@ -44,6 +48,7 @@
|
|||||||
"workspace": {
|
"workspace": {
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"jsr:@gfx/canvas-wasm@~0.4.2",
|
"jsr:@gfx/canvas-wasm@~0.4.2",
|
||||||
|
"jsr:@paulaboks/rng@^0.0.2",
|
||||||
"jsr:@std/fs@^1.0.23",
|
"jsr:@std/fs@^1.0.23",
|
||||||
"npm:marked@^17.0.3"
|
"npm:marked@^17.0.3"
|
||||||
]
|
]
|
||||||
|
|||||||