diff --git a/client/components/camera.ts b/client/components/camera.ts index aebfdb1..5c967c3 100644 --- a/client/components/camera.ts +++ b/client/components/camera.ts @@ -5,6 +5,9 @@ export class Camera extends Component { x: number; y: number; zoom: number; + rotation = 0; + offset_x = 0; + offset_y = 0; constructor(x = 0, y = 0, zoom = 1) { super(); diff --git a/client/renderer.ts b/client/renderer.ts index bb5453e..56d4c0d 100644 --- a/client/renderer.ts +++ b/client/renderer.ts @@ -1,3 +1,5 @@ +import { Camera } from "./components/camera.ts"; + const MAX_SPRITES = 10000; const VERTS_PER_SPRITE = 6; const FLOATS_PER_VERT = 4; @@ -7,9 +9,19 @@ export let canvas: HTMLCanvasElement; let program: WebGLProgram; let buffer: WebGLBuffer; +const default_camera = new Camera(); +let camera: Camera = default_camera; + +let camera_pos_loc: WebGLUniformLocation | null; +let camera_zoom_loc: WebGLUniformLocation | null; +let camera_rot_loc: WebGLUniformLocation | null; +let camera_offset_loc: WebGLUniformLocation | null; + const vertex_data = new Float32Array(MAX_SPRITES * VERTS_PER_SPRITE * FLOATS_PER_VERT); let vert_index = 0; +let pos_loc: GLint; +let uv_loc: GLint; let resolution_loc: WebGLUniformLocation | null; let _texture_loc: WebGLUniformLocation | null; @@ -22,18 +34,46 @@ const glyphs: Record const vertex_src = ` attribute vec2 aPos; attribute vec2 aUV; + varying vec2 vUV; + uniform vec2 uResolution; +uniform vec2 uCameraPos; +uniform float uCameraZoom; +uniform float uCameraRot; +uniform vec2 uCameraOffset; + void main(){ - vec2 zero = aPos / uResolution; + + // world → camera space + vec2 pos = aPos - uCameraPos; + + // rotation + float c = cos(uCameraRot); + float s = sin(uCameraRot); + pos = vec2( + pos.x * c - pos.y * s, + pos.x * s + pos.y * c + ); + + // zoom + pos *= uCameraZoom; + + // screen offset (center camera) + pos += uCameraOffset; + + // convert to clip space + vec2 zero = pos / uResolution; vec2 clip = zero * 2.0 - 1.0; - gl_Position = vec4(clip * vec2(1,-1),0,1); + + gl_Position = vec4(clip * vec2(1.0,-1.0),0.0,1.0); + vUV = aUV; } `; -const fragmentSrc = ` +const fragment_src = ` precision mediump float; varying vec2 vUV; uniform sampler2D uTex; @@ -60,7 +100,7 @@ export function init_window(canvas_element: HTMLCanvasElement) { gl = ctx; - program = create_program(vertex_src, fragmentSrc); + program = create_program(vertex_src, fragment_src); buffer = gl.createBuffer()!; @@ -68,17 +108,22 @@ export function init_window(canvas_element: HTMLCanvasElement) { const stride = FLOATS_PER_VERT * 4; - const pos_loc = gl.getAttribLocation(program, "aPos"); + 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"); + 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"); + camera_pos_loc = gl.getUniformLocation(program, "uCameraPos"); + camera_zoom_loc = gl.getUniformLocation(program, "uCameraZoom"); + camera_rot_loc = gl.getUniformLocation(program, "uCameraRot"); + camera_offset_loc = gl.getUniformLocation(program, "uCameraOffset"); + gl.useProgram(program); gl.uniform2f(resolution_loc, canvas.width, canvas.height); @@ -250,12 +295,17 @@ function flush_batch() { 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); + //const posLoc = gl.getAttribLocation(program, "aPos"); + //const uvLoc = gl.getAttribLocation(program, "aUV"); + gl.enableVertexAttribArray(pos_loc); + gl.vertexAttribPointer(pos_loc, 2, gl.FLOAT, false, 16, 0); + gl.enableVertexAttribArray(uv_loc); + gl.vertexAttribPointer(uv_loc, 2, gl.FLOAT, false, 16, 8); + + gl.uniform2f(camera_pos_loc, Math.floor(camera.x), Math.floor(camera.y)); + gl.uniform1f(camera_zoom_loc, camera.zoom); + gl.uniform1f(camera_rot_loc, camera.rotation); + gl.uniform2f(camera_offset_loc, camera.offset_x, camera.offset_y); gl.bindTexture(gl.TEXTURE_2D, current_texture); gl.drawArrays(gl.TRIANGLES, 0, vert_index / 4); @@ -356,3 +406,12 @@ export function resize_canvas() { gl.uniform2f(resolution_loc, width, height); } } + +export function begin_mode_2d(new_camera: Camera) { + camera = new_camera; +} + +export function end_mode_2d() { + flush_batch(); + camera = default_camera; +} diff --git a/client/systems/player_controls.ts b/client/systems/player_controls.ts index af758c3..121df73 100644 --- a/client/systems/player_controls.ts +++ b/client/systems/player_controls.ts @@ -7,6 +7,7 @@ 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"; +import { canvas } from "../renderer.ts"; export class PlayerControlsSystem extends System { constructor() { @@ -66,6 +67,8 @@ export class PlayerControlsSystem extends System { camera.x = Math.round(position.x); camera.y = Math.round(position.y); + camera.offset_x = Math.floor(canvas.width / 2); + camera.offset_y = Math.floor(canvas.height / 2); const scroll = InputManager.get_wheel_delta(); if (scroll > 0) { @@ -73,18 +76,5 @@ export class PlayerControlsSystem extends System { } else if (scroll < 0) { player_inventory.hotbar_selected = Math.max(0, player_inventory.hotbar_selected - 1); } - - // --- 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), - ); - }*/ } } diff --git a/client/systems/render_system.ts b/client/systems/render_system.ts index 73976f6..e36c687 100644 --- a/client/systems/render_system.ts +++ b/client/systems/render_system.ts @@ -9,6 +9,7 @@ import { Camera } from "$/client/components/camera.ts"; import { render_animated_sprite, render_sprite } from "./rendering/sprites.ts"; import { render_dimension } from "./rendering/dimension.ts"; import { render_player_inventory } from "./rendering/player.ts"; +import { begin_mode_2d, end_mode_2d } from "../renderer.ts"; export class RenderSystem extends System { constructor() { @@ -23,18 +24,20 @@ export class RenderSystem extends System { return; } + begin_mode_2d(camera); + for (const entity of world.get_entities()) { const position = entity.get(Position); const sprite = entity.get(Sprite); if (sprite && position) { - render_sprite(sprite, position); + render_sprite(sprite, position, camera); } const animated_sprite = entity.get(AnimatedSprite); if (animated_sprite && position) { - render_animated_sprite(animated_sprite, position); + render_animated_sprite(animated_sprite, position, camera); } const dimension = entity.get(Dimension); @@ -42,7 +45,11 @@ export class RenderSystem extends System { if (dimension) { render_dimension(dimension, camera); } + } + end_mode_2d(); + + for (const entity of world.get_entities()) { const player_inventory = entity.get(PlayerInventory); if (player_inventory && player_inventory.is_open) { diff --git a/client/systems/rendering/dimension.ts b/client/systems/rendering/dimension.ts index 8b28d4b..0f658a5 100644 --- a/client/systems/rendering/dimension.ts +++ b/client/systems/rendering/dimension.ts @@ -2,10 +2,10 @@ 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"; -import { canvas, draw_texture_region } from "../../renderer.ts"; +import { canvas, draw_texture_region } from "$/client/renderer.ts"; -export function render_dimension(tilemap: Dimension, camera: Camera) { - for (const tile of tilemap.tiles) { +export function render_dimension(dimension: Dimension, camera: Camera) { + for (const tile of dimension.tiles) { const region = get_sprite_region(tile.id); const x = tile.x * TILE_SIZE; @@ -20,7 +20,7 @@ export function render_dimension(tilemap: Dimension, camera: Camera) { } draw_texture_region( - tilemap.image, + dimension.image, region.x * TEXTURE_SIZE, region.y * TEXTURE_SIZE, TEXTURE_SIZE, diff --git a/client/systems/rendering/sprites.ts b/client/systems/rendering/sprites.ts index c7c80c8..15dffba 100644 --- a/client/systems/rendering/sprites.ts +++ b/client/systems/rendering/sprites.ts @@ -1,9 +1,12 @@ import { Position } from "$/common/components/position.ts"; import { AnimatedSprite, Sprite } from "$/client/components/sprite.ts"; -import { draw_texture_region } from "../../renderer.ts"; -import { AssetManager } from "../../assets.ts"; +import { draw_texture_region } from "$/client/renderer.ts"; +import { Camera } from "$/client/components/camera.ts"; -export function render_sprite(sprite: Sprite, position: Position) { +// TODO: camera scale +// TODO: only render if inside screen + +export function render_sprite(sprite: Sprite, position: Position, _camera: Camera) { draw_texture_region( sprite.image, sprite.source_x, @@ -22,6 +25,7 @@ export function render_sprite(sprite: Sprite, position: Position) { export function render_animated_sprite( animated_sprite: AnimatedSprite, position: Position, + _camera: Camera, ) { const current_animation = animated_sprite.states[animated_sprite.current_state]; if (!current_animation) { @@ -30,7 +34,7 @@ export function render_animated_sprite( } draw_texture_region( - AssetManager.instance.get("bworld:player"), + animated_sprite.image, current_animation.source_x[animated_sprite.animation_frame], current_animation.source_y[animated_sprite.animation_frame], current_animation.source_width,