Change tilemap to dimension and simple generation
This commit is contained in:
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
+6
-13
@@ -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);
|
||||
|
||||
|
||||
@@ -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 { 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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user