Unfinished webgl renderer
This commit is contained in:
+7
-3
@@ -1,4 +1,6 @@
|
|||||||
type Asset = HTMLImageElement | HTMLAudioElement | string | Record<string, unknown>;
|
import { load_texture, Texture } from "./renderer.ts";
|
||||||
|
|
||||||
|
type Asset = Texture | HTMLAudioElement | string | Record<string, unknown>;
|
||||||
|
|
||||||
export class AssetManager {
|
export class AssetManager {
|
||||||
static instance = new AssetManager();
|
static instance = new AssetManager();
|
||||||
@@ -16,11 +18,13 @@ export class AssetManager {
|
|||||||
if (src.endsWith(".png")) {
|
if (src.endsWith(".png")) {
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
const promise = new Promise<void>((resolve, reject) => {
|
const promise = new Promise<void>((resolve, reject) => {
|
||||||
img.onload = () => resolve();
|
img.onload = () => {
|
||||||
|
this.assets[key] = load_texture(img);
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
img.onerror = () => reject(new Error(`Failed to load image: ${src}`));
|
img.onerror = () => reject(new Error(`Failed to load image: ${src}`));
|
||||||
});
|
});
|
||||||
img.src = src;
|
img.src = src;
|
||||||
this.assets[key] = img;
|
|
||||||
this.loading_promises.push(promise);
|
this.loading_promises.push(promise);
|
||||||
} else if (src.endsWith(".ogg")) {
|
} else if (src.endsWith(".ogg")) {
|
||||||
const audio = new Audio();
|
const audio = new Audio();
|
||||||
|
|||||||
+9
-28
@@ -10,49 +10,30 @@ import { UIRenderSystem } from "$/client/systems/ui_render_system.ts";
|
|||||||
import { CropSystem } from "$/client/systems/crop_system.ts";
|
import { CropSystem } from "$/client/systems/crop_system.ts";
|
||||||
import { create_main_menu } from "./main_menu.ts";
|
import { create_main_menu } from "./main_menu.ts";
|
||||||
import { ClickableSystem } from "./systems/clickable_system.ts";
|
import { ClickableSystem } from "./systems/clickable_system.ts";
|
||||||
|
import { start_game } from "./game.ts";
|
||||||
|
import { canvas, resize_canvas } from "./renderer.ts";
|
||||||
|
|
||||||
export class ClientWorld extends World {
|
export class ClientWorld extends World {
|
||||||
canvas: HTMLCanvasElement;
|
|
||||||
ctx: CanvasRenderingContext2D;
|
|
||||||
|
|
||||||
paused = false;
|
paused = false;
|
||||||
debugging = false;
|
debugging = false;
|
||||||
|
|
||||||
constructor(canvas: HTMLCanvasElement) {
|
constructor() {
|
||||||
super("main_menu");
|
super("game");
|
||||||
|
|
||||||
this.add_state("main_menu");
|
this.add_state("main_menu");
|
||||||
this.add_state("paused");
|
this.add_state("paused");
|
||||||
this.add_state("game");
|
this.add_state("game");
|
||||||
|
|
||||||
this.canvas = canvas;
|
self.addEventListener("resize", resize_canvas);
|
||||||
const ctx = canvas.getContext("2d");
|
resize_canvas();
|
||||||
if (!ctx) {
|
|
||||||
throw new Error("Could not get CanvasRenderingContext2D");
|
|
||||||
}
|
|
||||||
this.ctx = ctx;
|
|
||||||
ctx.imageSmoothingEnabled = false;
|
|
||||||
|
|
||||||
const resize = () => {
|
|
||||||
canvas.width = self.innerWidth;
|
|
||||||
canvas.height = self.innerHeight;
|
|
||||||
|
|
||||||
canvas.style.width = canvas.width + "px";
|
|
||||||
canvas.style.height = canvas.height + "px";
|
|
||||||
|
|
||||||
this.ctx.imageSmoothingEnabled = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
self.addEventListener("resize", resize);
|
|
||||||
resize();
|
|
||||||
|
|
||||||
canvas.addEventListener("contextmenu", function (event) {
|
canvas.addEventListener("contextmenu", function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
});
|
});
|
||||||
|
|
||||||
DebugUI.initialize(this.ctx);
|
//DebugUI.initialize(this.ctx);
|
||||||
|
|
||||||
create_main_menu(this);
|
start_game(this);
|
||||||
|
|
||||||
// Logic systems
|
// Logic systems
|
||||||
this.add_system(new UIInteractionSystem(), "main_menu");
|
this.add_system(new UIInteractionSystem(), "main_menu");
|
||||||
@@ -64,7 +45,7 @@ export class ClientWorld extends World {
|
|||||||
this.add_system(new CropSystem(), "game");
|
this.add_system(new CropSystem(), "game");
|
||||||
|
|
||||||
// render systems
|
// render systems
|
||||||
this.add_system(new RenderSystem(this.ctx), "game");
|
this.add_system(new RenderSystem(), "game");
|
||||||
this.add_system(new DebugSystem(), "*");
|
this.add_system(new DebugSystem(), "*");
|
||||||
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");
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Component } from "$/common/ecs/mod.ts";
|
import { Component } from "$/common/ecs/mod.ts";
|
||||||
import { AssetManager } from "../assets.ts";
|
import { AssetManager } from "../assets.ts";
|
||||||
|
import { Texture } from "../renderer.ts";
|
||||||
|
|
||||||
interface Tile {
|
interface Tile {
|
||||||
x: number;
|
x: number;
|
||||||
@@ -9,7 +10,7 @@ interface Tile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class Dimension extends Component {
|
export class Dimension extends Component {
|
||||||
image: HTMLImageElement;
|
image: Texture;
|
||||||
tiles: Tile[];
|
tiles: Tile[];
|
||||||
|
|
||||||
constructor(tiles: Tile[]) {
|
constructor(tiles: Tile[]) {
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { Component } from "$/common/ecs/mod.ts";
|
import { Component } from "$/common/ecs/mod.ts";
|
||||||
import { AssetManager } from "$/client/assets.ts";
|
import { AssetManager } from "$/client/assets.ts";
|
||||||
|
import { Texture } from "../renderer.ts";
|
||||||
|
|
||||||
export class Sprite extends Component {
|
export class Sprite extends Component {
|
||||||
image: HTMLImageElement;
|
image: Texture;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
source_x: number;
|
source_x: number;
|
||||||
@@ -13,7 +14,7 @@ export class Sprite extends Component {
|
|||||||
flip_y = false;
|
flip_y = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
image: HTMLImageElement | string,
|
image: Texture | string,
|
||||||
width: number,
|
width: number,
|
||||||
height: number,
|
height: number,
|
||||||
source_x = 0,
|
source_x = 0,
|
||||||
@@ -23,7 +24,7 @@ export class Sprite extends Component {
|
|||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
if (typeof image === "string") {
|
if (typeof image === "string") {
|
||||||
this.image = AssetManager.instance.get<HTMLImageElement>(image);
|
this.image = AssetManager.instance.get(image);
|
||||||
} else {
|
} else {
|
||||||
this.image = image;
|
this.image = image;
|
||||||
}
|
}
|
||||||
@@ -45,7 +46,7 @@ interface AnimatedSpritePiece {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class AnimatedSprite extends Component {
|
export class AnimatedSprite extends Component {
|
||||||
image: HTMLImageElement;
|
image: Texture;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
flip_x = false;
|
flip_x = false;
|
||||||
@@ -57,7 +58,7 @@ export class AnimatedSprite extends Component {
|
|||||||
animation_frame = 0;
|
animation_frame = 0;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
image: HTMLImageElement | string,
|
image: Texture | string,
|
||||||
width: number,
|
width: number,
|
||||||
height: number,
|
height: number,
|
||||||
states: Record<string, AnimatedSpritePiece>,
|
states: Record<string, AnimatedSpritePiece>,
|
||||||
@@ -65,7 +66,7 @@ export class AnimatedSprite extends Component {
|
|||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
if (typeof image === "string") {
|
if (typeof image === "string") {
|
||||||
this.image = AssetManager.instance.get<HTMLImageElement>(image);
|
this.image = AssetManager.instance.get(image);
|
||||||
} else {
|
} else {
|
||||||
this.image = image;
|
this.image = image;
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-10
@@ -1,6 +1,6 @@
|
|||||||
import { point_inside_rec } from "$/common/utils.ts";
|
import { point_inside_rec } from "$/common/utils.ts";
|
||||||
import { InputManager } from "$/client/input_manager.ts";
|
import { InputManager } from "$/client/input_manager.ts";
|
||||||
import { draw_text, measure_text } from "$/client/text_rendering.ts";
|
//import { draw_text, measure_text } from "$/client/text_rendering.ts";
|
||||||
|
|
||||||
const HEADER_HEIGHT = 24;
|
const HEADER_HEIGHT = 24;
|
||||||
const RESIZE_SIZE = 12;
|
const RESIZE_SIZE = 12;
|
||||||
@@ -135,7 +135,7 @@ export class DebugUI {
|
|||||||
this.ctx.fillStyle = "#333";
|
this.ctx.fillStyle = "#333";
|
||||||
this.ctx.fillRect(win.x, win.y, win.width, HEADER_HEIGHT);
|
this.ctx.fillRect(win.x, win.y, win.width, HEADER_HEIGHT);
|
||||||
|
|
||||||
draw_text(this.ctx, title, win.x + 8, win.y + 4, 2);
|
//draw_text(this.ctx, title, win.x + 8, win.y + 4, 2);
|
||||||
|
|
||||||
// resize grip
|
// resize grip
|
||||||
this.ctx.fillStyle = hovering_resize ? "#888" : "#666";
|
this.ctx.fillStyle = hovering_resize ? "#888" : "#666";
|
||||||
@@ -201,7 +201,7 @@ export class DebugUI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static text(content: string) {
|
static text(content: string) {
|
||||||
draw_text(this.ctx, content, this.cursor_x, this.cursor_y, 2);
|
//draw_text(this.ctx, content, this.cursor_x, this.cursor_y, 2);
|
||||||
this.advance(20);
|
this.advance(20);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,7 +221,7 @@ export class DebugUI {
|
|||||||
this.ctx.strokeRect(x, y, width, height);
|
this.ctx.strokeRect(x, y, width, height);
|
||||||
|
|
||||||
this.ctx.fillStyle = "white";
|
this.ctx.fillStyle = "white";
|
||||||
draw_text(this.ctx, label, x + 6, y + 4, 2);
|
//draw_text(this.ctx, label, x + 6, y + 4, 2);
|
||||||
|
|
||||||
this.advance(height + PADDING);
|
this.advance(height + PADDING);
|
||||||
|
|
||||||
@@ -253,7 +253,7 @@ export class DebugUI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.ctx.fillStyle = "white";
|
this.ctx.fillStyle = "white";
|
||||||
draw_text(this.ctx, label, x + size + 6, y, 2);
|
//draw_text(this.ctx, label, x + size + 6, y, 2);
|
||||||
|
|
||||||
this.advance(size + PADDING);
|
this.advance(size + PADDING);
|
||||||
|
|
||||||
@@ -295,7 +295,7 @@ export class DebugUI {
|
|||||||
this.ctx.strokeRect(x, y, width, height);
|
this.ctx.strokeRect(x, y, width, height);
|
||||||
|
|
||||||
this.ctx.fillStyle = "white";
|
this.ctx.fillStyle = "white";
|
||||||
draw_text(this.ctx, `${label}: ${value.toFixed(2)}`, x + 5, y + 2, 2);
|
//draw_text(this.ctx, `${label}: ${value.toFixed(2)}`, x + 5, y + 2, 2);
|
||||||
|
|
||||||
this.advance(height + PADDING);
|
this.advance(height + PADDING);
|
||||||
|
|
||||||
@@ -414,17 +414,17 @@ export class DebugUI {
|
|||||||
|
|
||||||
const text_x = x + 6;
|
const text_x = x + 6;
|
||||||
|
|
||||||
draw_text(this.ctx, state.value, x + 6, y + 4, 2);
|
//draw_text(this.ctx, state.value, x + 6, y + 4, 2);
|
||||||
|
|
||||||
// caret !
|
// caret !
|
||||||
if (is_active && state.show_caret) {
|
if (is_active && state.show_caret) {
|
||||||
const before_text = state.value.substring(0, state.caret);
|
const before_text = state.value.substring(0, state.caret);
|
||||||
const caret_x = text_x + measure_text(this.ctx, before_text, 2);
|
//const caret_x = text_x + measure_text(this.ctx, before_text, 2);
|
||||||
|
|
||||||
this.ctx.strokeStyle = "white";
|
this.ctx.strokeStyle = "white";
|
||||||
this.ctx.beginPath();
|
this.ctx.beginPath();
|
||||||
this.ctx.moveTo(caret_x, y + 4);
|
//this.ctx.moveTo(caret_x, y + 4);
|
||||||
this.ctx.lineTo(caret_x, y + height - 4);
|
//this.ctx.lineTo(caret_x, y + height - 4);
|
||||||
this.ctx.stroke();
|
this.ctx.stroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -7,6 +7,7 @@ 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";
|
import { generate } from "./generation.ts";
|
||||||
|
import { canvas } from "./renderer.ts";
|
||||||
|
|
||||||
export function start_game(world: ClientWorld) {
|
export function start_game(world: ClientWorld) {
|
||||||
world.state = "game";
|
world.state = "game";
|
||||||
@@ -21,12 +22,12 @@ export function start_game(world: ClientWorld) {
|
|||||||
|
|
||||||
// UI !
|
// UI !
|
||||||
const unpause_button = new Entity("unpausebutton");
|
const unpause_button = new Entity("unpausebutton");
|
||||||
unpause_button.add(new Position(world.canvas.width / 2 - 150, world.canvas.height / 2 - 80));
|
unpause_button.add(new Position(canvas.width / 2 - 150, canvas.height / 2 - 80));
|
||||||
unpause_button.add(new UIButton("Unpause", 320, 64, () => world.state = "paused"));
|
unpause_button.add(new UIButton("Unpause", 320, 64, () => world.state = "paused"));
|
||||||
world.add_entity(unpause_button);
|
world.add_entity(unpause_button);
|
||||||
|
|
||||||
const about_button = new Entity("aboutbutton");
|
const about_button = new Entity("aboutbutton");
|
||||||
about_button.add(new Position(world.canvas.width / 2 - 150, world.canvas.height / 2 + 80));
|
about_button.add(new Position(canvas.width / 2 - 150, canvas.height / 2 + 80));
|
||||||
about_button.add(new UIButton("About", 320, 64, () => open_about()));
|
about_button.add(new UIButton("About", 320, 64, () => open_about()));
|
||||||
world.add_entity(about_button);
|
world.add_entity(about_button);
|
||||||
|
|
||||||
|
|||||||
+6
-3
@@ -1,6 +1,7 @@
|
|||||||
import { AssetManager } from "./assets.ts";
|
import { AssetManager } from "./assets.ts";
|
||||||
import { ClientWorld } from "./client_world.ts";
|
import { ClientWorld } from "./client_world.ts";
|
||||||
import { InputManager } from "./input_manager.ts";
|
import { InputManager } from "./input_manager.ts";
|
||||||
|
import { begin_drawing, end_drawing, init_window } from "./renderer.ts";
|
||||||
|
|
||||||
export class ClientLoop {
|
export class ClientLoop {
|
||||||
running = false;
|
running = false;
|
||||||
@@ -34,9 +35,9 @@ export class ClientLoop {
|
|||||||
|
|
||||||
const delta = (time - this.last_time) / 1000;
|
const delta = (time - this.last_time) / 1000;
|
||||||
|
|
||||||
const ctx = this.world.ctx;
|
begin_drawing();
|
||||||
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
||||||
this.world.update(delta);
|
this.world.update(delta);
|
||||||
|
end_drawing();
|
||||||
|
|
||||||
this.frame_count += 1;
|
this.frame_count += 1;
|
||||||
const now = performance.now();
|
const now = performance.now();
|
||||||
@@ -59,6 +60,8 @@ if (!canvas) {
|
|||||||
throw Error("Canvas was not found");
|
throw Error("Canvas was not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init_window(canvas);
|
||||||
|
|
||||||
InputManager.initialize(canvas);
|
InputManager.initialize(canvas);
|
||||||
|
|
||||||
AssetManager.instance.load("bworld:assets_text", "/assets/ASSETS.md");
|
AssetManager.instance.load("bworld:assets_text", "/assets/ASSETS.md");
|
||||||
@@ -72,7 +75,7 @@ AssetManager.instance.load("bworld:ui", "/assets/sprites/ui.png");
|
|||||||
|
|
||||||
await AssetManager.instance.load_all();
|
await AssetManager.instance.load_all();
|
||||||
|
|
||||||
const client_world = new ClientWorld(canvas);
|
const client_world = new ClientWorld();
|
||||||
|
|
||||||
const loop = new ClientLoop(client_world);
|
const loop = new ClientLoop(client_world);
|
||||||
loop.start();
|
loop.start();
|
||||||
|
|||||||
+1
-2
@@ -4,10 +4,9 @@ import { ClientWorld } from "./client_world.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 { start_game } from "./game.ts";
|
import { start_game } from "./game.ts";
|
||||||
|
import { canvas } from "./renderer.ts";
|
||||||
|
|
||||||
export function create_main_menu(world: ClientWorld) {
|
export function create_main_menu(world: ClientWorld) {
|
||||||
const canvas = world.canvas;
|
|
||||||
|
|
||||||
const play_button = new Entity("play");
|
const play_button = new Entity("play");
|
||||||
play_button.add(new Position(canvas.width / 2 - 150, canvas.height / 2 - 80));
|
play_button.add(new Position(canvas.width / 2 - 150, canvas.height / 2 - 80));
|
||||||
play_button.add(new UIButton("Play", 320, 64, () => start_game(world)));
|
play_button.add(new UIButton("Play", 320, 64, () => start_game(world)));
|
||||||
|
|||||||
@@ -0,0 +1,358 @@
|
|||||||
|
const MAX_SPRITES = 10000;
|
||||||
|
const VERTS_PER_SPRITE = 6;
|
||||||
|
const FLOATS_PER_VERT = 4;
|
||||||
|
|
||||||
|
let gl: WebGLRenderingContext;
|
||||||
|
export let canvas: HTMLCanvasElement;
|
||||||
|
let program: WebGLProgram;
|
||||||
|
let buffer: WebGLBuffer;
|
||||||
|
|
||||||
|
const vertex_data = new Float32Array(MAX_SPRITES * VERTS_PER_SPRITE * FLOATS_PER_VERT);
|
||||||
|
let vert_index = 0;
|
||||||
|
|
||||||
|
let resolution_loc: WebGLUniformLocation | null;
|
||||||
|
let _texture_loc: WebGLUniformLocation | null;
|
||||||
|
|
||||||
|
let current_texture: WebGLTexture | null = null;
|
||||||
|
|
||||||
|
const glyph_size = 32;
|
||||||
|
let font_texture: WebGLTexture;
|
||||||
|
const glyphs: Record<string, { u0: number; v0: number; u1: number; v1: number }> = {};
|
||||||
|
|
||||||
|
const vertex_src = `
|
||||||
|
attribute vec2 aPos;
|
||||||
|
attribute vec2 aUV;
|
||||||
|
varying vec2 vUV;
|
||||||
|
uniform vec2 uResolution;
|
||||||
|
|
||||||
|
void main(){
|
||||||
|
vec2 zero = aPos / uResolution;
|
||||||
|
vec2 clip = zero * 2.0 - 1.0;
|
||||||
|
gl_Position = vec4(clip * vec2(1,-1),0,1);
|
||||||
|
vUV = aUV;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const fragmentSrc = `
|
||||||
|
precision mediump float;
|
||||||
|
varying vec2 vUV;
|
||||||
|
uniform sampler2D uTex;
|
||||||
|
|
||||||
|
void main(){
|
||||||
|
gl_FragColor = texture2D(uTex,vUV);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export interface Texture {
|
||||||
|
tex: WebGLTexture;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function init_window(canvas_element: HTMLCanvasElement) {
|
||||||
|
canvas = canvas_element;
|
||||||
|
canvas.width = 1800;
|
||||||
|
canvas.height = 900;
|
||||||
|
const ctx = canvas.getContext("webgl");
|
||||||
|
if (!ctx) {
|
||||||
|
throw new Error("WebGL not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
gl = ctx;
|
||||||
|
|
||||||
|
program = create_program(vertex_src, fragmentSrc);
|
||||||
|
|
||||||
|
buffer = gl.createBuffer()!;
|
||||||
|
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
||||||
|
|
||||||
|
const stride = FLOATS_PER_VERT * 4;
|
||||||
|
|
||||||
|
const pos_loc = gl.getAttribLocation(program, "aPos");
|
||||||
|
gl.enableVertexAttribArray(pos_loc);
|
||||||
|
gl.vertexAttribPointer(pos_loc, 2, gl.FLOAT, false, stride, 0);
|
||||||
|
|
||||||
|
const uv_loc = gl.getAttribLocation(program, "aUV");
|
||||||
|
gl.enableVertexAttribArray(uv_loc);
|
||||||
|
gl.vertexAttribPointer(uv_loc, 2, gl.FLOAT, false, stride, 8);
|
||||||
|
|
||||||
|
resolution_loc = gl.getUniformLocation(program, "uResolution");
|
||||||
|
_texture_loc = gl.getUniformLocation(program, "uTex");
|
||||||
|
|
||||||
|
gl.useProgram(program);
|
||||||
|
gl.uniform2f(resolution_loc, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
gl.enable(gl.BLEND);
|
||||||
|
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
|
init_font();
|
||||||
|
}
|
||||||
|
|
||||||
|
function create_shader(type: number, src: string): WebGLShader {
|
||||||
|
const s = gl.createShader(type)!;
|
||||||
|
gl.shaderSource(s, src);
|
||||||
|
gl.compileShader(s);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
function create_program(vs: string, fs: string): WebGLProgram {
|
||||||
|
const program = gl.createProgram();
|
||||||
|
|
||||||
|
const v = create_shader(gl.VERTEX_SHADER, vs);
|
||||||
|
const f = create_shader(gl.FRAGMENT_SHADER, fs);
|
||||||
|
|
||||||
|
gl.attachShader(program, v);
|
||||||
|
gl.attachShader(program, f);
|
||||||
|
gl.linkProgram(program);
|
||||||
|
|
||||||
|
return program;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function load_texture(image: HTMLImageElement): Texture {
|
||||||
|
const texture = gl.createTexture();
|
||||||
|
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||||
|
|
||||||
|
gl.texImage2D(
|
||||||
|
gl.TEXTURE_2D,
|
||||||
|
0,
|
||||||
|
gl.RGBA,
|
||||||
|
gl.RGBA,
|
||||||
|
gl.UNSIGNED_BYTE,
|
||||||
|
image,
|
||||||
|
);
|
||||||
|
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
||||||
|
|
||||||
|
return {
|
||||||
|
tex: texture,
|
||||||
|
width: image.width,
|
||||||
|
height: image.height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function begin_drawing() {
|
||||||
|
vert_index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clear_background(r: number, g: number, b: number, a = 1) {
|
||||||
|
gl.clearColor(r, g, b, a);
|
||||||
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function end_drawing() {
|
||||||
|
flush_batch();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function draw_texture(
|
||||||
|
texture: Texture,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
w: number,
|
||||||
|
h: number,
|
||||||
|
flip_x = false,
|
||||||
|
flip_y = false,
|
||||||
|
) {
|
||||||
|
if (current_texture !== texture.tex) {
|
||||||
|
flush_batch();
|
||||||
|
current_texture = texture.tex;
|
||||||
|
}
|
||||||
|
|
||||||
|
let u0 = 0, v0 = 0, u1 = 1, v1 = 1;
|
||||||
|
|
||||||
|
if (flip_x) {
|
||||||
|
[u0, u1] = [u1, u0];
|
||||||
|
}
|
||||||
|
if (flip_y) {
|
||||||
|
[v0, v1] = [v1, v0];
|
||||||
|
}
|
||||||
|
|
||||||
|
push_quad(x, y, w, h, u0, v0, u1, v1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function draw_texture_region(
|
||||||
|
texture: Texture,
|
||||||
|
sx: number,
|
||||||
|
sy: number,
|
||||||
|
sw: number,
|
||||||
|
sh: number,
|
||||||
|
dx: number,
|
||||||
|
dy: number,
|
||||||
|
dw: number,
|
||||||
|
dh: number,
|
||||||
|
flip_x = false,
|
||||||
|
flip_y = false,
|
||||||
|
) {
|
||||||
|
if (current_texture !== texture.tex) {
|
||||||
|
flush_batch();
|
||||||
|
current_texture = texture.tex;
|
||||||
|
}
|
||||||
|
|
||||||
|
let u0 = sx / texture.width;
|
||||||
|
let v0 = sy / texture.height;
|
||||||
|
let u1 = (sx + sw) / texture.width;
|
||||||
|
let v1 = (sy + sh) / texture.height;
|
||||||
|
|
||||||
|
if (flip_x) {
|
||||||
|
[u0, u1] = [u1, u0];
|
||||||
|
}
|
||||||
|
if (flip_y) {
|
||||||
|
[v0, v1] = [v1, v0];
|
||||||
|
}
|
||||||
|
|
||||||
|
push_quad(dx, dy, dw, dh, u0, v0, u1, v1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function draw_text(str: string, x: number, y: number) {
|
||||||
|
if (!font_texture) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_texture !== font_texture) {
|
||||||
|
flush_batch();
|
||||||
|
current_texture = font_texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const ch of str) {
|
||||||
|
const g = glyphs[ch];
|
||||||
|
if (!g) {
|
||||||
|
x += glyph_size;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
push_quad(
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
glyph_size,
|
||||||
|
glyph_size,
|
||||||
|
g.u0,
|
||||||
|
g.v0,
|
||||||
|
g.u1,
|
||||||
|
g.v1,
|
||||||
|
);
|
||||||
|
|
||||||
|
x += glyph_size * 0.6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function flush_batch() {
|
||||||
|
if (vert_index === 0 || !current_texture) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
||||||
|
gl.bufferData(gl.ARRAY_BUFFER, vertex_data.subarray(0, vert_index), gl.STREAM_DRAW);
|
||||||
|
|
||||||
|
gl.useProgram(program);
|
||||||
|
|
||||||
|
const posLoc = gl.getAttribLocation(program, "aPos");
|
||||||
|
const uvLoc = gl.getAttribLocation(program, "aUV");
|
||||||
|
gl.enableVertexAttribArray(posLoc);
|
||||||
|
gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 16, 0);
|
||||||
|
gl.enableVertexAttribArray(uvLoc);
|
||||||
|
gl.vertexAttribPointer(uvLoc, 2, gl.FLOAT, false, 16, 8);
|
||||||
|
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, current_texture);
|
||||||
|
gl.drawArrays(gl.TRIANGLES, 0, vert_index / 4);
|
||||||
|
vert_index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function push_quad(dx: number, dy: number, dw: number, dh: number, u0: number, v0: number, u1: number, v1: number) {
|
||||||
|
const x2 = dx + dw;
|
||||||
|
const y2 = dy + dh;
|
||||||
|
|
||||||
|
const v = [
|
||||||
|
dx,
|
||||||
|
dy,
|
||||||
|
u0,
|
||||||
|
v0,
|
||||||
|
x2,
|
||||||
|
dy,
|
||||||
|
u1,
|
||||||
|
v0,
|
||||||
|
dx,
|
||||||
|
y2,
|
||||||
|
u0,
|
||||||
|
v1,
|
||||||
|
|
||||||
|
x2,
|
||||||
|
dy,
|
||||||
|
u1,
|
||||||
|
v0,
|
||||||
|
x2,
|
||||||
|
y2,
|
||||||
|
u1,
|
||||||
|
v1,
|
||||||
|
dx,
|
||||||
|
y2,
|
||||||
|
u0,
|
||||||
|
v1,
|
||||||
|
];
|
||||||
|
|
||||||
|
for (let i = 0; i < v.length; i += 1) {
|
||||||
|
vertex_data[vert_index++] = v[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function init_font() {
|
||||||
|
const c = document.createElement("canvas");
|
||||||
|
c.width = 512;
|
||||||
|
c.height = 512;
|
||||||
|
|
||||||
|
const ctx = c.getContext("2d")!;
|
||||||
|
ctx.fillStyle = "white";
|
||||||
|
ctx.font = glyph_size + "px m6x11";
|
||||||
|
|
||||||
|
let x = 0;
|
||||||
|
let y = glyph_size;
|
||||||
|
|
||||||
|
for (let i = 32; i < 127; i++) {
|
||||||
|
const ch = String.fromCharCode(i);
|
||||||
|
|
||||||
|
ctx.fillText(ch, x, y);
|
||||||
|
|
||||||
|
glyphs[ch] = {
|
||||||
|
u0: x / 512,
|
||||||
|
v0: (y - glyph_size) / 512,
|
||||||
|
u1: (x + glyph_size) / 512,
|
||||||
|
v1: y / 512,
|
||||||
|
};
|
||||||
|
|
||||||
|
x += glyph_size;
|
||||||
|
|
||||||
|
if (x > 512 - glyph_size) {
|
||||||
|
x = 0;
|
||||||
|
y += glyph_size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
font_texture = gl.createTexture()!;
|
||||||
|
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, font_texture);
|
||||||
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, c);
|
||||||
|
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resize_canvas() {
|
||||||
|
const width = self.innerWidth;
|
||||||
|
const height = self.innerHeight;
|
||||||
|
|
||||||
|
if (canvas.width !== width || canvas.height !== height) {
|
||||||
|
canvas.width = width;
|
||||||
|
canvas.height = height;
|
||||||
|
canvas.style.width = canvas.width + "px";
|
||||||
|
canvas.style.height = canvas.height + "px";
|
||||||
|
|
||||||
|
gl.viewport(0, 0, width, height);
|
||||||
|
|
||||||
|
gl.useProgram(program);
|
||||||
|
gl.uniform2f(resolution_loc, width, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import { ClickableSprite } from "../components/clickable.ts";
|
|||||||
import { point_inside_rec } from "$/common/utils.ts";
|
import { point_inside_rec } from "$/common/utils.ts";
|
||||||
import { InputManager } from "../input_manager.ts";
|
import { InputManager } from "../input_manager.ts";
|
||||||
import { Camera, screen_to_world } from "../components/camera.ts";
|
import { Camera, screen_to_world } from "../components/camera.ts";
|
||||||
|
import { canvas } from "../renderer.ts";
|
||||||
|
|
||||||
export class ClickableSystem extends System {
|
export class ClickableSystem extends System {
|
||||||
update(world: ClientWorld, _delta: number): void {
|
update(world: ClientWorld, _delta: number): void {
|
||||||
@@ -18,8 +19,8 @@ export class ClickableSystem extends System {
|
|||||||
mouse.x,
|
mouse.x,
|
||||||
mouse.y,
|
mouse.y,
|
||||||
camera,
|
camera,
|
||||||
world.canvas.width,
|
canvas.width,
|
||||||
world.canvas.height,
|
canvas.height,
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const entity of world.get_entities()) {
|
for (const entity of world.get_entities()) {
|
||||||
|
|||||||
@@ -11,17 +11,11 @@ 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 {
|
||||||
ctx: CanvasRenderingContext2D;
|
constructor() {
|
||||||
|
|
||||||
constructor(ctx: CanvasRenderingContext2D) {
|
|
||||||
super();
|
super();
|
||||||
this.ctx = ctx;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
update(world: World, _delta: number): void {
|
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_entity = world.get_entities().values().find((e) => e.get(Camera)?.active);
|
||||||
const camera = camera_entity?.get(Camera);
|
const camera = camera_entity?.get(Camera);
|
||||||
|
|
||||||
@@ -30,45 +24,29 @@ export class RenderSystem extends System {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const entity of world.get_entities()) {
|
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 position = entity.get(Position);
|
||||||
const sprite = entity.get(Sprite);
|
const sprite = entity.get(Sprite);
|
||||||
|
|
||||||
if (sprite && position) {
|
if (sprite && position) {
|
||||||
render_sprite(this.ctx, sprite, position);
|
render_sprite(sprite, position);
|
||||||
}
|
}
|
||||||
|
|
||||||
const animated_sprite = entity.get(AnimatedSprite);
|
const animated_sprite = entity.get(AnimatedSprite);
|
||||||
|
|
||||||
if (animated_sprite && position) {
|
if (animated_sprite && position) {
|
||||||
render_animated_sprite(this.ctx, animated_sprite, position);
|
render_animated_sprite(animated_sprite, position);
|
||||||
}
|
}
|
||||||
|
|
||||||
const dimension = entity.get(Dimension);
|
const dimension = entity.get(Dimension);
|
||||||
|
|
||||||
if (dimension) {
|
if (dimension) {
|
||||||
render_dimension(this.ctx, dimension, camera);
|
render_dimension(dimension, camera);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ui
|
|
||||||
this.ctx.restore();
|
|
||||||
|
|
||||||
const player_inventory = entity.get(PlayerInventory);
|
const player_inventory = entity.get(PlayerInventory);
|
||||||
|
|
||||||
if (player_inventory && player_inventory.is_open) {
|
if (player_inventory && player_inventory.is_open) {
|
||||||
render_player_inventory(this.ctx, player_inventory);
|
render_player_inventory(player_inventory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,25 +2,24 @@ import { get_sprite_region } from "$/common/utils.ts";
|
|||||||
import { Dimension } from "$/client/components/dimension.ts";
|
import { Dimension } from "$/client/components/dimension.ts";
|
||||||
import { TEXTURE_SIZE, TILE_SIZE } from "$/common/constants.ts";
|
import { TEXTURE_SIZE, TILE_SIZE } from "$/common/constants.ts";
|
||||||
import { Camera, world_to_screen } from "$/client/components/camera.ts";
|
import { Camera, world_to_screen } from "$/client/components/camera.ts";
|
||||||
|
import { canvas, draw_texture_region } from "../../renderer.ts";
|
||||||
|
|
||||||
export function render_dimension(ctx: CanvasRenderingContext2D, tilemap: Dimension, camera: Camera) {
|
export function render_dimension(tilemap: Dimension, camera: Camera) {
|
||||||
ctx.save();
|
|
||||||
|
|
||||||
for (const tile of tilemap.tiles) {
|
for (const tile of tilemap.tiles) {
|
||||||
const region = get_sprite_region(tile.id);
|
const region = get_sprite_region(tile.id);
|
||||||
|
|
||||||
const x = tile.x * TILE_SIZE;
|
const x = tile.x * TILE_SIZE;
|
||||||
const y = tile.y * TILE_SIZE;
|
const y = tile.y * TILE_SIZE;
|
||||||
|
|
||||||
const screen_position = world_to_screen(x, y, camera, ctx.canvas.width, ctx.canvas.height);
|
const screen_position = world_to_screen(x, y, camera, canvas.width, canvas.height);
|
||||||
if (
|
if (
|
||||||
screen_position.x + TILE_SIZE < 0 || screen_position.x > ctx.canvas.width ||
|
screen_position.x + TILE_SIZE < 0 || screen_position.x > canvas.width ||
|
||||||
screen_position.y + TILE_SIZE < 0 || screen_position.y > ctx.canvas.height
|
screen_position.y + TILE_SIZE < 0 || screen_position.y > canvas.height
|
||||||
) {
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.drawImage(
|
draw_texture_region(
|
||||||
tilemap.image,
|
tilemap.image,
|
||||||
region.x * TEXTURE_SIZE,
|
region.x * TEXTURE_SIZE,
|
||||||
region.y * TEXTURE_SIZE,
|
region.y * TEXTURE_SIZE,
|
||||||
@@ -32,6 +31,4 @@ export function render_dimension(ctx: CanvasRenderingContext2D, tilemap: Dimensi
|
|||||||
TILE_SIZE,
|
TILE_SIZE,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.restore();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ import { AssetManager } from "$/client/assets.ts";
|
|||||||
import { PlayerInventory } from "$/client/components/inventory.ts";
|
import { PlayerInventory } from "$/client/components/inventory.ts";
|
||||||
import { InputManager } from "$/client/input_manager.ts";
|
import { InputManager } from "$/client/input_manager.ts";
|
||||||
import { draw_item, draw_nine_slice } from "./render_utils.ts";
|
import { draw_item, draw_nine_slice } from "./render_utils.ts";
|
||||||
|
import { Texture } from "$/client/renderer.ts";
|
||||||
|
|
||||||
export function render_player_inventory(ctx: CanvasRenderingContext2D, player_inventory: PlayerInventory) {
|
export function render_player_inventory(player_inventory: PlayerInventory) {
|
||||||
const PADDING = 10;
|
const PADDING = 10;
|
||||||
const ui = AssetManager.instance.get<HTMLImageElement>("bworld:ui");
|
const ui = AssetManager.instance.get<Texture>("bworld:ui");
|
||||||
const layout = player_inventory.layout.slots;
|
const layout = player_inventory.layout.slots;
|
||||||
|
|
||||||
draw_nine_slice(
|
draw_nine_slice(
|
||||||
ctx,
|
|
||||||
ui,
|
ui,
|
||||||
160,
|
160,
|
||||||
0,
|
0,
|
||||||
@@ -30,7 +30,6 @@ export function render_player_inventory(ctx: CanvasRenderingContext2D, player_in
|
|||||||
const x = slot.x + PADDING;
|
const x = slot.x + PADDING;
|
||||||
const y = slot.y + PADDING;
|
const y = slot.y + PADDING;
|
||||||
draw_nine_slice(
|
draw_nine_slice(
|
||||||
ctx,
|
|
||||||
ui,
|
ui,
|
||||||
player_inventory.hovering_slot === index ? 19 * 16 : 160 + 32,
|
player_inventory.hovering_slot === index ? 19 * 16 : 160 + 32,
|
||||||
player_inventory.hovering_slot === index ? 16 : 0,
|
player_inventory.hovering_slot === index ? 16 : 0,
|
||||||
@@ -51,12 +50,12 @@ export function render_player_inventory(ctx: CanvasRenderingContext2D, player_in
|
|||||||
const y = slot.y + PADDING;
|
const y = slot.y + PADDING;
|
||||||
const item = player_inventory.container.get_item(index);
|
const item = player_inventory.container.get_item(index);
|
||||||
if (item) {
|
if (item) {
|
||||||
draw_item(ctx, item, x, y);
|
draw_item(item, x, y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (player_inventory.holding_item) {
|
if (player_inventory.holding_item) {
|
||||||
const mouse = InputManager.get_mouse_position();
|
const mouse = InputManager.get_mouse_position();
|
||||||
draw_item(ctx, player_inventory.holding_item, mouse.x, mouse.y);
|
draw_item(player_inventory.holding_item, mouse.x, mouse.y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,10 @@ import { SLOT_SIZE } from "$/common/constants.ts";
|
|||||||
import { get_sprite_region } from "$/common/utils.ts";
|
import { get_sprite_region } from "$/common/utils.ts";
|
||||||
import { AssetManager } from "$/client/assets.ts";
|
import { AssetManager } from "$/client/assets.ts";
|
||||||
import { ItemStack } from "$/client/components/inventory.ts";
|
import { ItemStack } from "$/client/components/inventory.ts";
|
||||||
import { draw_text } from "$/client/text_rendering.ts";
|
import { draw_text, draw_texture_region, Texture } from "$/client/renderer.ts";
|
||||||
|
|
||||||
export function draw_nine_slice(
|
export function draw_nine_slice(
|
||||||
ctx: CanvasRenderingContext2D,
|
image: Texture,
|
||||||
image: HTMLImageElement,
|
|
||||||
source_x: number,
|
source_x: number,
|
||||||
source_y: number,
|
source_y: number,
|
||||||
source_width: number,
|
source_width: number,
|
||||||
@@ -27,13 +26,13 @@ export function draw_nine_slice(
|
|||||||
const dest_center_height = height - top - bottom;
|
const dest_center_height = height - top - bottom;
|
||||||
|
|
||||||
// top left
|
// top left
|
||||||
ctx.drawImage(image, source_x, source_y, left, top, x, y, left, top);
|
draw_texture_region(image, source_x, source_y, left, top, x, y, left, top);
|
||||||
|
|
||||||
// top right
|
// top right
|
||||||
ctx.drawImage(image, source_x + source_width - right, source_y, right, top, x + width - right, y, right, top);
|
draw_texture_region(image, source_x + source_width - right, source_y, right, top, x + width - right, y, right, top);
|
||||||
|
|
||||||
// bottom left
|
// bottom left
|
||||||
ctx.drawImage(
|
draw_texture_region(
|
||||||
image,
|
image,
|
||||||
source_x,
|
source_x,
|
||||||
source_y + source_height - bottom,
|
source_y + source_height - bottom,
|
||||||
@@ -46,7 +45,7 @@ export function draw_nine_slice(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// bottom right
|
// bottom right
|
||||||
ctx.drawImage(
|
draw_texture_region(
|
||||||
image,
|
image,
|
||||||
source_x + source_width - right,
|
source_x + source_width - right,
|
||||||
source_y + source_height - bottom,
|
source_y + source_height - bottom,
|
||||||
@@ -59,10 +58,10 @@ export function draw_nine_slice(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// top
|
// top
|
||||||
ctx.drawImage(image, source_x + left, source_y, center_width, top, x + left, y, dest_center_width, top);
|
draw_texture_region(image, source_x + left, source_y, center_width, top, x + left, y, dest_center_width, top);
|
||||||
|
|
||||||
// bottom
|
// bottom
|
||||||
ctx.drawImage(
|
draw_texture_region(
|
||||||
image,
|
image,
|
||||||
source_x + left,
|
source_x + left,
|
||||||
source_y + source_height - bottom,
|
source_y + source_height - bottom,
|
||||||
@@ -75,10 +74,10 @@ export function draw_nine_slice(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// left
|
// left
|
||||||
ctx.drawImage(image, source_x, source_y + top, left, center_height, x, y + top, left, dest_center_height);
|
draw_texture_region(image, source_x, source_y + top, left, center_height, x, y + top, left, dest_center_height);
|
||||||
|
|
||||||
// right
|
// right
|
||||||
ctx.drawImage(
|
draw_texture_region(
|
||||||
image,
|
image,
|
||||||
source_x + source_width - right,
|
source_x + source_width - right,
|
||||||
source_y + top,
|
source_y + top,
|
||||||
@@ -91,7 +90,7 @@ export function draw_nine_slice(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// center !
|
// center !
|
||||||
ctx.drawImage(
|
draw_texture_region(
|
||||||
image,
|
image,
|
||||||
source_x + left,
|
source_x + left,
|
||||||
source_y + top,
|
source_y + top,
|
||||||
@@ -104,9 +103,9 @@ export function draw_nine_slice(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function draw_item(ctx: CanvasRenderingContext2D, item: ItemStack, x: number, y: number) {
|
export function draw_item(item: ItemStack, x: number, y: number) {
|
||||||
const sprite_region = get_sprite_region(item.type_id);
|
const sprite_region = get_sprite_region(item.type_id);
|
||||||
ctx.drawImage(
|
draw_texture_region(
|
||||||
AssetManager.instance.get("bworld:textures"),
|
AssetManager.instance.get("bworld:textures"),
|
||||||
sprite_region.x * 16,
|
sprite_region.x * 16,
|
||||||
sprite_region.y * 16,
|
sprite_region.y * 16,
|
||||||
@@ -119,24 +118,22 @@ export function draw_item(ctx: CanvasRenderingContext2D, item: ItemStack, x: num
|
|||||||
);
|
);
|
||||||
if (item.max_amount !== 1) {
|
if (item.max_amount !== 1) {
|
||||||
draw_text(
|
draw_text(
|
||||||
ctx,
|
|
||||||
String(item.amount),
|
String(item.amount),
|
||||||
x + SLOT_SIZE + 2 - 4,
|
x + SLOT_SIZE + 2 - 4,
|
||||||
y + SLOT_SIZE + 2 - 4,
|
y + SLOT_SIZE + 2 - 4,
|
||||||
2,
|
//2,
|
||||||
"#3f3f3f",
|
//"#3f3f3f",
|
||||||
"bottom",
|
//"bottom",
|
||||||
"right",
|
//"right",
|
||||||
);
|
);
|
||||||
draw_text(
|
draw_text(
|
||||||
ctx,
|
|
||||||
String(item.amount),
|
String(item.amount),
|
||||||
x + SLOT_SIZE - 4,
|
x + SLOT_SIZE - 4,
|
||||||
y + SLOT_SIZE - 4,
|
y + SLOT_SIZE - 4,
|
||||||
2,
|
//2,
|
||||||
"white",
|
//"white",
|
||||||
"bottom",
|
//"bottom",
|
||||||
"right",
|
//"right",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,65 +1,46 @@
|
|||||||
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 { draw_texture_region } from "../../renderer.ts";
|
||||||
|
import { AssetManager } from "../../assets.ts";
|
||||||
|
|
||||||
export function render_sprite(ctx: CanvasRenderingContext2D, sprite: Sprite, position: Position) {
|
export function render_sprite(sprite: Sprite, position: Position) {
|
||||||
ctx.save();
|
draw_texture_region(
|
||||||
|
|
||||||
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.image,
|
||||||
sprite.source_x,
|
sprite.source_x,
|
||||||
sprite.source_y,
|
sprite.source_y,
|
||||||
sprite.source_width,
|
sprite.source_width,
|
||||||
sprite.source_height,
|
sprite.source_height,
|
||||||
0,
|
position.x,
|
||||||
0,
|
position.y,
|
||||||
sprite.width,
|
sprite.width,
|
||||||
sprite.height,
|
sprite.height,
|
||||||
|
sprite.flip_x,
|
||||||
|
sprite.flip_y,
|
||||||
);
|
);
|
||||||
ctx.restore();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function render_animated_sprite(
|
export function render_animated_sprite(
|
||||||
ctx: CanvasRenderingContext2D,
|
|
||||||
animated_sprite: AnimatedSprite,
|
animated_sprite: AnimatedSprite,
|
||||||
position: Position,
|
position: Position,
|
||||||
) {
|
) {
|
||||||
ctx.save();
|
|
||||||
|
|
||||||
const current_animation = animated_sprite.states[animated_sprite.current_state];
|
const current_animation = animated_sprite.states[animated_sprite.current_state];
|
||||||
if (!current_animation) {
|
if (!current_animation) {
|
||||||
console.error(`Missing animation for state ${animated_sprite.current_state}`);
|
console.error(`Missing animation for state ${animated_sprite.current_state}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.translate(
|
draw_texture_region(
|
||||||
Math.floor(animated_sprite.flip_x ? position.x + animated_sprite.width : position.x),
|
AssetManager.instance.get("bworld:player"),
|
||||||
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_x[animated_sprite.animation_frame],
|
||||||
current_animation.source_y[animated_sprite.animation_frame],
|
current_animation.source_y[animated_sprite.animation_frame],
|
||||||
current_animation.source_width,
|
current_animation.source_width,
|
||||||
current_animation.source_height,
|
current_animation.source_height,
|
||||||
0,
|
position.x,
|
||||||
0,
|
position.y,
|
||||||
animated_sprite.width,
|
animated_sprite.width,
|
||||||
animated_sprite.height,
|
animated_sprite.height,
|
||||||
|
animated_sprite.flip_x,
|
||||||
|
animated_sprite.flip_y,
|
||||||
);
|
);
|
||||||
|
|
||||||
animated_sprite.timer += 1;
|
animated_sprite.timer += 1;
|
||||||
@@ -71,6 +52,4 @@ export function render_animated_sprite(
|
|||||||
animated_sprite.animation_frame = 0;
|
animated_sprite.animation_frame = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.restore();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import { System } from "$/common/ecs/mod.ts";
|
|||||||
import { Position } from "$/common/components/position.ts";
|
import { Position } from "$/common/components/position.ts";
|
||||||
import { ClientWorld } from "$/client/client_world.ts";
|
import { ClientWorld } from "$/client/client_world.ts";
|
||||||
import { UIButton } from "$/client/components/ui_components.ts";
|
import { UIButton } from "$/client/components/ui_components.ts";
|
||||||
import { draw_text, measure_text } from "../text_rendering.ts";
|
|
||||||
import { draw_nine_slice } from "./rendering/render_utils.ts";
|
import { draw_nine_slice } from "./rendering/render_utils.ts";
|
||||||
import { AssetManager } from "../assets.ts";
|
import { AssetManager } from "../assets.ts";
|
||||||
|
import { draw_text, Texture } from "../renderer.ts";
|
||||||
|
|
||||||
const SCALE = 4;
|
const SCALE = 4;
|
||||||
|
|
||||||
@@ -12,18 +12,7 @@ export class UIRenderSystem implements System {
|
|||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
update(world: ClientWorld, _delta: number) {
|
update(world: ClientWorld, _delta: number) {
|
||||||
const ctx = world.ctx;
|
const ui = AssetManager.instance.get<Texture>("bworld:ui");
|
||||||
|
|
||||||
ctx.save();
|
|
||||||
ctx.resetTransform();
|
|
||||||
|
|
||||||
ctx.scale(SCALE, SCALE);
|
|
||||||
|
|
||||||
// dark background
|
|
||||||
ctx.fillStyle = "rgba(0,0,0,0.6)";
|
|
||||||
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
||||||
|
|
||||||
const ui = AssetManager.instance.get<HTMLImageElement>("bworld:ui");
|
|
||||||
|
|
||||||
for (const entity of world.get_entities()) {
|
for (const entity of world.get_entities()) {
|
||||||
const position = entity.get(Position);
|
const position = entity.get(Position);
|
||||||
@@ -31,7 +20,6 @@ export class UIRenderSystem implements System {
|
|||||||
|
|
||||||
if (position && button) {
|
if (position && button) {
|
||||||
draw_nine_slice(
|
draw_nine_slice(
|
||||||
ctx,
|
|
||||||
ui,
|
ui,
|
||||||
16 * (button.hovered ? 14 : 11),
|
16 * (button.hovered ? 14 : 11),
|
||||||
16 * (button.hovered ? 1 : 0),
|
16 * (button.hovered ? 1 : 0),
|
||||||
@@ -46,18 +34,16 @@ export class UIRenderSystem implements System {
|
|||||||
button.width / SCALE,
|
button.width / SCALE,
|
||||||
button.height / SCALE,
|
button.height / SCALE,
|
||||||
);
|
);
|
||||||
const measure = measure_text(ctx, button.text, 1.25);
|
// const measure = measure_text(ctx, button.text, 1.25);
|
||||||
draw_text(
|
draw_text(
|
||||||
ctx,
|
|
||||||
button.text,
|
button.text,
|
||||||
(position.x / SCALE) + (button.width / SCALE / 2) - (measure / 2),
|
(position.x / SCALE) + (button.width / SCALE / 2) - (100 / 2),
|
||||||
position.y / SCALE + (button.height / SCALE / 2),
|
position.y / SCALE + (button.height / SCALE / 2),
|
||||||
1.5,
|
//1.5,
|
||||||
"white",
|
//"white",
|
||||||
"middle",
|
//"middle",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx.restore();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
const TEXT_SCALE = 8;
|
|
||||||
|
|
||||||
export function measure_text(ctx: CanvasRenderingContext2D, text: string, scale = 1): number {
|
|
||||||
ctx.font = `${scale * TEXT_SCALE}px m6x11`;
|
|
||||||
return ctx.measureText(text).width;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function draw_text(
|
|
||||||
ctx: CanvasRenderingContext2D,
|
|
||||||
text: string,
|
|
||||||
x: number,
|
|
||||||
y: number,
|
|
||||||
scale = 1,
|
|
||||||
color = "white",
|
|
||||||
baseline: CanvasTextBaseline = "top",
|
|
||||||
align: CanvasTextAlign = "left",
|
|
||||||
) {
|
|
||||||
ctx.textBaseline = baseline;
|
|
||||||
ctx.textAlign = align;
|
|
||||||
ctx.font = `${scale * TEXT_SCALE}px m6x11`;
|
|
||||||
ctx.fillStyle = color;
|
|
||||||
ctx.fillText(text, Math.floor(x), Math.floor(y));
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user