Initial commit
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
import { System } from "$/common/ecs/mod.ts";
|
||||
import { ClientWorld } from "$/client/client_world.ts";
|
||||
import { DebugUI } from "$/client/debug_ui.ts";
|
||||
|
||||
export class DebugSystem extends System {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
update(world: ClientWorld, _delta: number): void {
|
||||
if (!world.debugging) {
|
||||
return;
|
||||
}
|
||||
|
||||
DebugUI.ctx.save();
|
||||
DebugUI.ctx.resetTransform();
|
||||
|
||||
DebugUI.begin("Entities", 10, 10, 300);
|
||||
|
||||
for (const entity of world.get_entities()) {
|
||||
if (DebugUI.collapsing_header("Entity - " + entity.id)) {
|
||||
for (const component of entity.get_all()) {
|
||||
if (DebugUI.collapsing_header(`${component.constructor.name}##${entity.id}`)) {
|
||||
this.render_component(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DebugUI.end();
|
||||
DebugUI.ctx.restore();
|
||||
}
|
||||
|
||||
// deno-lint-ignore no-explicit-any
|
||||
render_component(component: any) {
|
||||
for (const key in component) {
|
||||
if (key === "__component") {
|
||||
continue;
|
||||
}
|
||||
if (typeof component[key] === "number") {
|
||||
component[key] = DebugUI.float_input(
|
||||
key,
|
||||
component[key],
|
||||
);
|
||||
} else if (typeof component[key] === "string") {
|
||||
component[key] = DebugUI.text_input(
|
||||
key,
|
||||
component[key],
|
||||
);
|
||||
} else if (typeof component[key] === "boolean") {
|
||||
component[key] = DebugUI.checkbox(
|
||||
key,
|
||||
component[key],
|
||||
);
|
||||
} else {
|
||||
DebugUI.text(`${key}: ${JSON.stringify(component[key])}`);
|
||||
}
|
||||
DebugUI.separator();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
import { System, World } 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";
|
||||
|
||||
export class InventorySystem extends System {
|
||||
update(world: World, _delta: number): void {
|
||||
for (const entity of world.get_entities()) {
|
||||
const player_inventory = entity.get(PlayerInventory);
|
||||
|
||||
if (player_inventory) {
|
||||
player_inventory.hovering_slot = -1;
|
||||
const container = player_inventory.container;
|
||||
if (player_inventory.is_open) {
|
||||
for (const [index, slot] of player_inventory.layout.slots.entries()) {
|
||||
const mouse = InputManager.get_mouse_position();
|
||||
const slot_x = slot.x + player_inventory.layout.offset_x;
|
||||
const slot_y = slot.y + player_inventory.layout.offset_y;
|
||||
const hovering = point_inside_rec(mouse.x, mouse.y, slot_x, slot_y, SLOT_SIZE, SLOT_SIZE);
|
||||
if (hovering) {
|
||||
player_inventory.hovering_slot = index;
|
||||
|
||||
if (InputManager.is_mouse_pressed(0)) {
|
||||
InputManager.consume_mouse(0);
|
||||
this.handle_left_click(player_inventory, container, index);
|
||||
return;
|
||||
}
|
||||
|
||||
if (InputManager.is_mouse_pressed(2)) {
|
||||
InputManager.consume_mouse(2);
|
||||
this.handle_right_click(player_inventory, container, index);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (player_inventory.holding_item) {
|
||||
container.add_item(player_inventory.holding_item);
|
||||
player_inventory.holding_item = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
if (player_inventory.holding_item?.amount === 0) {
|
||||
player_inventory.holding_item = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch_item_with_holding(player_inventory: PlayerInventory, index: number) {
|
||||
const container_slot = player_inventory.container.get_slot(index);
|
||||
const original_holding_item = player_inventory.holding_item;
|
||||
player_inventory.holding_item = container_slot.get_item();
|
||||
container_slot.set_item(original_holding_item);
|
||||
}
|
||||
|
||||
handle_left_click(player_inventory: PlayerInventory, container: Container, index: number) {
|
||||
const holding = player_inventory.holding_item;
|
||||
const slot = container.get_slot(index);
|
||||
const slot_item = slot.get_item();
|
||||
|
||||
// if you aren't holding anything
|
||||
// "swap" with nothing on your hand (pick it up)
|
||||
if (!holding) {
|
||||
this.switch_item_with_holding(player_inventory, index);
|
||||
return;
|
||||
}
|
||||
|
||||
// if you are holding something and slot type equals holding type
|
||||
// try to add to stack
|
||||
if (slot_item && slot.type_id === holding.type_id) {
|
||||
const space_left = slot.max_amount! - slot_item.amount!;
|
||||
const amount_to_add = Math.min(space_left, holding.amount);
|
||||
|
||||
slot_item.amount += amount_to_add;
|
||||
holding.amount -= amount_to_add;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// if something on hand but not the same
|
||||
// swap
|
||||
this.switch_item_with_holding(player_inventory, index);
|
||||
}
|
||||
|
||||
handle_right_click(player_inventory: PlayerInventory, container: Container, index: number) {
|
||||
const holding = player_inventory.holding_item;
|
||||
const slot = container.get_slot(index);
|
||||
const slot_item = slot.get_item();
|
||||
|
||||
// if holding something
|
||||
if (holding) {
|
||||
// and slot type equals holding type
|
||||
// add 1 to matching stack
|
||||
if (slot_item && slot.type_id === holding.type_id) {
|
||||
if (slot_item.amount < slot_item.max_amount!) {
|
||||
slot_item.amount += 1;
|
||||
holding.amount -= 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// (actually the same as the last one but creates a new one techinically)
|
||||
// place 1 into empty slot
|
||||
if (!slot_item) {
|
||||
const new_item = holding.clone();
|
||||
new_item.amount = 1;
|
||||
slot.set_item(new_item);
|
||||
holding.amount -= 1;
|
||||
return;
|
||||
}
|
||||
|
||||
// if something on hand but not the same
|
||||
// swap
|
||||
this.switch_item_with_holding(player_inventory, index);
|
||||
return;
|
||||
}
|
||||
|
||||
// if player is holding nothing and clicks nothing, nothing happens
|
||||
if (!slot_item) {
|
||||
return;
|
||||
}
|
||||
|
||||
// pick up half of the stack
|
||||
const original_amount = slot_item.amount;
|
||||
const half = Math.floor(original_amount / 2);
|
||||
|
||||
slot_item.amount = half;
|
||||
|
||||
const picked_up = slot_item.clone();
|
||||
picked_up.amount = original_amount - half;
|
||||
|
||||
player_inventory.holding_item = picked_up;
|
||||
|
||||
if (slot_item.amount === 0) {
|
||||
slot.set_item(undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import { Entity, System } from "$/common/ecs/mod.ts";
|
||||
import { Velocity } from "$/common/components/velocity.ts";
|
||||
import { InputManager } from "../input_manager.ts";
|
||||
import { AnimatedSprite } from "../components/sprite.ts";
|
||||
import { ClientWorld } from "../client_world.ts";
|
||||
import { PlayerControls } from "../components/player_controls.ts";
|
||||
import { PlayerInventory } from "../components/inventory.ts";
|
||||
import { Camera } from "../components/camera.ts";
|
||||
import { Position } from "../../common/components/position.ts";
|
||||
|
||||
export class PlayerControlsSystem extends System {
|
||||
player: Entity;
|
||||
|
||||
constructor(player: Entity) {
|
||||
super();
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
update(world: ClientWorld, _delta: number): void {
|
||||
const velocity = this.player.get(Velocity)!;
|
||||
const controls = this.player.get(PlayerControls)!;
|
||||
|
||||
if (InputManager.is_key_down(controls.move_left)) {
|
||||
velocity.vx = -controls.move_speed;
|
||||
} else if (InputManager.is_key_down(controls.move_right)) {
|
||||
velocity.vx = controls.move_speed;
|
||||
} else {
|
||||
velocity.vx = 0;
|
||||
}
|
||||
|
||||
if (InputManager.is_key_down(controls.move_up)) {
|
||||
velocity.vy = -controls.move_speed;
|
||||
} else if (InputManager.is_key_down(controls.move_down)) {
|
||||
velocity.vy = controls.move_speed;
|
||||
} else {
|
||||
velocity.vy = 0;
|
||||
}
|
||||
|
||||
const animated_sprite = this.player.get(AnimatedSprite)!;
|
||||
|
||||
if (
|
||||
InputManager.is_key_down(controls.move_up) || InputManager.is_key_down(controls.move_down) ||
|
||||
InputManager.is_key_down(controls.move_left) || InputManager.is_key_down(controls.move_right)
|
||||
) {
|
||||
animated_sprite.set_state("running");
|
||||
} else {
|
||||
animated_sprite.set_state("idle");
|
||||
}
|
||||
|
||||
if (InputManager.is_key_down(controls.move_left)) {
|
||||
animated_sprite.flip_x = false;
|
||||
} else if (InputManager.is_key_down(controls.move_right)) {
|
||||
animated_sprite.flip_x = true;
|
||||
}
|
||||
|
||||
if (InputManager.is_key_pressed(controls.open_inventory)) {
|
||||
const inventory = this.player.get(PlayerInventory)!;
|
||||
inventory.is_open = !inventory.is_open;
|
||||
}
|
||||
|
||||
if (InputManager.is_key_pressed(controls.open_debug)) {
|
||||
world.debugging = !world.debugging;
|
||||
}
|
||||
|
||||
const position = this.player.get(Position)!;
|
||||
const camera = this.player.get(Camera)!;
|
||||
|
||||
camera.x = Math.round(position.x);
|
||||
camera.y = Math.round(position.y);
|
||||
|
||||
// --- APPLY BOUNDS ---
|
||||
/*if (camera.bounds) {
|
||||
camera.x = Math.max(
|
||||
camera.bounds.min_x,
|
||||
Math.min(camera.x, camera.bounds.max_x),
|
||||
);
|
||||
|
||||
camera.y = Math.max(
|
||||
camera.bounds.min_y,
|
||||
Math.min(camera.y, camera.bounds.max_y),
|
||||
);
|
||||
}*/
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
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 { 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_player_inventory } from "./rendering/player.ts";
|
||||
|
||||
export class RenderSystem extends System {
|
||||
ctx: CanvasRenderingContext2D;
|
||||
|
||||
constructor(ctx: CanvasRenderingContext2D) {
|
||||
super();
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
update(world: World, _delta: number): void {
|
||||
this.ctx.save();
|
||||
this.ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||
|
||||
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()) {
|
||||
this.ctx.save();
|
||||
this.ctx.translate(
|
||||
Math.floor(this.ctx.canvas.width / 2),
|
||||
Math.floor(this.ctx.canvas.height / 2),
|
||||
);
|
||||
|
||||
this.ctx.scale(camera.zoom, camera.zoom);
|
||||
|
||||
this.ctx.translate(
|
||||
-Math.floor(camera.x),
|
||||
-Math.floor(camera.y),
|
||||
);
|
||||
|
||||
const position = entity.get(Position);
|
||||
const sprite = entity.get(Sprite);
|
||||
|
||||
if (sprite && position) {
|
||||
render_sprite(this.ctx, sprite, position);
|
||||
}
|
||||
|
||||
const animated_sprite = entity.get(AnimatedSprite);
|
||||
|
||||
if (animated_sprite && position) {
|
||||
render_animated_sprite(this.ctx, animated_sprite, position);
|
||||
}
|
||||
|
||||
const tilemap = entity.get(Tilemap);
|
||||
|
||||
if (tilemap) {
|
||||
render_tilemap(this.ctx, tilemap);
|
||||
}
|
||||
|
||||
// Ui
|
||||
this.ctx.restore();
|
||||
|
||||
const player_inventory = entity.get(PlayerInventory);
|
||||
|
||||
if (player_inventory && player_inventory.is_open) {
|
||||
render_player_inventory(this.ctx, player_inventory);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import { SLOT_SIZE } from "$/common/constants.ts";
|
||||
import { AssetManager } from "$/client/assets.ts";
|
||||
import { PlayerInventory } from "$/client/components/inventory.ts";
|
||||
import { InputManager } from "$/client/input_manager.ts";
|
||||
import { draw_item, draw_nine_slice } from "./render_utils.ts";
|
||||
|
||||
export function render_player_inventory(ctx: CanvasRenderingContext2D, player_inventory: PlayerInventory) {
|
||||
const PADDING = 10;
|
||||
const ui = AssetManager.instance.get<HTMLImageElement>("bworld:ui");
|
||||
const layout = player_inventory.layout.slots;
|
||||
|
||||
draw_nine_slice(
|
||||
ctx,
|
||||
ui,
|
||||
160,
|
||||
0,
|
||||
16,
|
||||
16,
|
||||
4,
|
||||
4,
|
||||
4,
|
||||
4,
|
||||
0,
|
||||
0,
|
||||
PADDING * 2 + SLOT_SIZE * 9,
|
||||
PADDING * 2 + SLOT_SIZE * 4,
|
||||
);
|
||||
|
||||
for (const [index, slot] of layout.entries()) {
|
||||
const x = slot.x + PADDING;
|
||||
const y = slot.y + PADDING;
|
||||
draw_nine_slice(
|
||||
ctx,
|
||||
ui,
|
||||
player_inventory.hovering_slot === index ? 19 * 16 : 160 + 32,
|
||||
player_inventory.hovering_slot === index ? 16 : 0,
|
||||
16,
|
||||
16,
|
||||
4,
|
||||
4,
|
||||
4,
|
||||
4,
|
||||
x,
|
||||
y,
|
||||
SLOT_SIZE,
|
||||
SLOT_SIZE,
|
||||
);
|
||||
}
|
||||
for (const [index, slot] of layout.entries()) {
|
||||
const x = slot.x + PADDING;
|
||||
const y = slot.y + PADDING;
|
||||
const item = player_inventory.container.get_item(index);
|
||||
if (item) {
|
||||
draw_item(ctx, item, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
if (player_inventory.holding_item) {
|
||||
const mouse = InputManager.get_mouse_position();
|
||||
draw_item(ctx, player_inventory.holding_item, mouse.x, mouse.y);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
import { SLOT_SIZE } from "$/common/constants.ts";
|
||||
import { get_sprite_region } from "$/common/utils.ts";
|
||||
import { AssetManager } from "$/client/assets.ts";
|
||||
import { ItemStack } from "$/client/components/inventory.ts";
|
||||
import { draw_text } from "$/client/text_rendering.ts";
|
||||
|
||||
export function draw_nine_slice(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
image: HTMLImageElement,
|
||||
source_x: number,
|
||||
source_y: number,
|
||||
source_width: number,
|
||||
source_height: number,
|
||||
left: number,
|
||||
right: number,
|
||||
top: number,
|
||||
bottom: number,
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
) {
|
||||
const center_width = source_width - left - right;
|
||||
const center_height = source_height - top - bottom;
|
||||
|
||||
const dest_center_width = width - left - right;
|
||||
const dest_center_height = height - top - bottom;
|
||||
|
||||
// top left
|
||||
ctx.drawImage(image, source_x, source_y, left, top, x, y, left, top);
|
||||
|
||||
// top right
|
||||
ctx.drawImage(image, source_x + source_width - right, source_y, right, top, x + width - right, y, right, top);
|
||||
|
||||
// bottom left
|
||||
ctx.drawImage(
|
||||
image,
|
||||
source_x,
|
||||
source_y + source_height - bottom,
|
||||
left,
|
||||
bottom,
|
||||
x,
|
||||
y + height - bottom,
|
||||
left,
|
||||
bottom,
|
||||
);
|
||||
|
||||
// bottom right
|
||||
ctx.drawImage(
|
||||
image,
|
||||
source_x + source_width - right,
|
||||
source_y + source_height - bottom,
|
||||
right,
|
||||
bottom,
|
||||
x + width - right,
|
||||
y + height - bottom,
|
||||
right,
|
||||
bottom,
|
||||
);
|
||||
|
||||
// top
|
||||
ctx.drawImage(image, source_x + left, source_y, center_width, top, x + left, y, dest_center_width, top);
|
||||
|
||||
// bottom
|
||||
ctx.drawImage(
|
||||
image,
|
||||
source_x + left,
|
||||
source_y + source_height - bottom,
|
||||
center_width,
|
||||
bottom,
|
||||
x + left,
|
||||
y + height - bottom,
|
||||
dest_center_width,
|
||||
bottom,
|
||||
);
|
||||
|
||||
// left
|
||||
ctx.drawImage(image, source_x, source_y + top, left, center_height, x, y + top, left, dest_center_height);
|
||||
|
||||
// right
|
||||
ctx.drawImage(
|
||||
image,
|
||||
source_x + source_width - right,
|
||||
source_y + top,
|
||||
right,
|
||||
center_height,
|
||||
x + width - right,
|
||||
y + top,
|
||||
right,
|
||||
dest_center_height,
|
||||
);
|
||||
|
||||
// center !
|
||||
ctx.drawImage(
|
||||
image,
|
||||
source_x + left,
|
||||
source_y + top,
|
||||
center_width,
|
||||
center_height,
|
||||
x + left,
|
||||
y + top,
|
||||
dest_center_width,
|
||||
dest_center_height,
|
||||
);
|
||||
}
|
||||
|
||||
export function draw_item(ctx: CanvasRenderingContext2D, item: ItemStack, x: number, y: number) {
|
||||
const sprite_region = get_sprite_region(item.type_id);
|
||||
ctx.drawImage(
|
||||
AssetManager.instance.get("bworld:tiny_town"),
|
||||
sprite_region.x * 16,
|
||||
sprite_region.y * 16,
|
||||
16,
|
||||
16,
|
||||
x + 3,
|
||||
y + 3,
|
||||
SLOT_SIZE - 6,
|
||||
SLOT_SIZE - 6,
|
||||
);
|
||||
if (item.max_amount !== 1) {
|
||||
draw_text(
|
||||
ctx,
|
||||
String(item.amount),
|
||||
x + SLOT_SIZE + 2 - 4,
|
||||
y + SLOT_SIZE + 2 - 4,
|
||||
2,
|
||||
"#3f3f3f",
|
||||
"bottom",
|
||||
"right",
|
||||
);
|
||||
draw_text(
|
||||
ctx,
|
||||
String(item.amount),
|
||||
x + SLOT_SIZE - 4,
|
||||
y + SLOT_SIZE - 4,
|
||||
2,
|
||||
"white",
|
||||
"bottom",
|
||||
"right",
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import { Position } from "$/common/components/position.ts";
|
||||
import { AnimatedSprite, Sprite } from "$/client/components/sprite.ts";
|
||||
|
||||
export function render_sprite(ctx: CanvasRenderingContext2D, sprite: Sprite, position: Position) {
|
||||
ctx.save();
|
||||
|
||||
ctx.translate(
|
||||
Math.floor(sprite.flip_x ? position.x + sprite.width : position.x),
|
||||
Math.floor(sprite.flip_y ? position.y + sprite.height : position.y),
|
||||
);
|
||||
ctx.scale(
|
||||
sprite.flip_x ? -1 : 1,
|
||||
sprite.flip_y ? -1 : 1,
|
||||
);
|
||||
|
||||
ctx.drawImage(
|
||||
sprite.image,
|
||||
sprite.source_x,
|
||||
sprite.source_y,
|
||||
sprite.source_width,
|
||||
sprite.source_height,
|
||||
0,
|
||||
0,
|
||||
sprite.width,
|
||||
sprite.height,
|
||||
);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
export function render_animated_sprite(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
animated_sprite: AnimatedSprite,
|
||||
position: Position,
|
||||
) {
|
||||
ctx.save();
|
||||
|
||||
const current_animation = animated_sprite.states[animated_sprite.current_state];
|
||||
if (!current_animation) {
|
||||
console.error(`Missing animation for state ${animated_sprite.current_state}`);
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.translate(
|
||||
Math.floor(animated_sprite.flip_x ? position.x + animated_sprite.width : position.x),
|
||||
Math.floor(animated_sprite.flip_y ? position.y + animated_sprite.height : position.y),
|
||||
);
|
||||
|
||||
ctx.scale(
|
||||
animated_sprite.flip_x ? -1 : 1,
|
||||
animated_sprite.flip_y ? -1 : 1,
|
||||
);
|
||||
|
||||
ctx.drawImage(
|
||||
animated_sprite.image,
|
||||
current_animation.source_x[animated_sprite.animation_frame],
|
||||
current_animation.source_y[animated_sprite.animation_frame],
|
||||
current_animation.source_width,
|
||||
current_animation.source_height,
|
||||
0,
|
||||
0,
|
||||
animated_sprite.width,
|
||||
animated_sprite.height,
|
||||
);
|
||||
|
||||
animated_sprite.timer += 1;
|
||||
if (animated_sprite.timer >= current_animation.duration) {
|
||||
animated_sprite.timer = 0;
|
||||
animated_sprite.animation_frame += 1;
|
||||
|
||||
if (animated_sprite.animation_frame >= current_animation.source_x.length) {
|
||||
animated_sprite.animation_frame = 0;
|
||||
}
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
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();
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
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