diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/main/index.ts | 18 | ||||
-rw-r--r-- | src/renderer/custom.d.ts | 4 | ||||
-rw-r--r-- | src/renderer/index.ts | 7 | ||||
-rw-r--r-- | src/renderer/runtime/controller/collision.ts | 53 | ||||
-rw-r--r-- | src/renderer/runtime/controller/entitycontext.ts | 10 | ||||
-rw-r--r-- | src/renderer/runtime/controller/gamecontext.ts | 53 | ||||
-rw-r--r-- | src/renderer/runtime/math/line.ts | 39 | ||||
-rw-r--r-- | src/renderer/runtime/math/point.ts | 9 | ||||
-rw-r--r-- | src/renderer/runtime/util.ts | 18 | ||||
-rw-r--r-- | src/renderer/runtime/view/entity.ts | 26 | ||||
-rw-r--r-- | src/renderer/runtime/view/input/gameinput.ts | 26 | ||||
-rw-r--r-- | src/renderer/runtime/view/input/inputhandler.ts | 12 | ||||
-rw-r--r-- | src/renderer/runtime/view/map.ts | 111 | ||||
-rw-r--r-- | src/renderer/runtime/view/renderer/renderer.ts | 14 | ||||
-rw-r--r-- | src/renderer/runtime/view/renderer/shaders.ts | 12 | ||||
-rw-r--r-- | src/renderer/runtime/view/sprite.ts | 62 | ||||
-rw-r--r-- | src/renderer/runtime/view/util/image.ts | 20 |
17 files changed, 213 insertions, 281 deletions
diff --git a/src/main/index.ts b/src/main/index.ts index e1169b9..3a94ae5 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -5,11 +5,12 @@ import { format as formatUrl } from 'url'; const isDevelopment = process.env.NODE_ENV !== 'production'; // global reference to mainWindow (necessary to prevent window from being garbage collected) -let mainWindow: BrowserWindow|null = null; +let mainWindow: BrowserWindow | null = null; function getIndexURL(): string { - if (isDevelopment) + if (isDevelopment) { return `http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}`; + } return formatUrl({ pathname: path.join(__dirname, 'index.html'), @@ -19,10 +20,11 @@ function getIndexURL(): string { } function createMainWindow(): BrowserWindow { - const window = new BrowserWindow({webPreferences: {nodeIntegration: true}}); + const window = new BrowserWindow({ webPreferences: { nodeIntegration: true } }); - if (isDevelopment) + if (isDevelopment) { window.webContents.openDevTools(); + } window.loadURL(getIndexURL()); @@ -32,7 +34,7 @@ function createMainWindow(): BrowserWindow { window.webContents.on('devtools-opened', () => { window.webContents.focus(); - }) + }); return window; } @@ -40,14 +42,16 @@ function createMainWindow(): BrowserWindow { // quit application when all windows are closed app.on('window-all-closed', () => { // on macOS it is common for applications to stay open until the user explicitly quits - if (process.platform !== 'darwin') + if (process.platform !== 'darwin') { app.quit(); + } }); app.on('activate', () => { // on macOS it is common to re-create a window even after all windows have been closed - if (!mainWindow) + if (!mainWindow) { mainWindow = createMainWindow(); + } }); // create main BrowserWindow when electron is ready diff --git a/src/renderer/custom.d.ts b/src/renderer/custom.d.ts index b54bb07..898bbc3 100644 --- a/src/renderer/custom.d.ts +++ b/src/renderer/custom.d.ts @@ -1,9 +1,9 @@ -declare module "*.vs" { +declare module '*.vs' { const content: string; export default content; } -declare module "*.fs" { +declare module '*.fs' { const content: string; export default content; } diff --git a/src/renderer/index.ts b/src/renderer/index.ts index 233d750..16f18d1 100644 --- a/src/renderer/index.ts +++ b/src/renderer/index.ts @@ -4,15 +4,14 @@ import { GameContext } from './runtime/controller/gamecontext'; import { Renderer } from './runtime/view/renderer/renderer'; -window.onload = async () => { +window.onload = async (): Promise<void> => { const app = document.getElementById('app'); - if (!app) - return; + if (!app) return; const canvas = document.createElement('canvas'); const renderer = new Renderer(canvas); - const resize = () => { + const resize = (): void => { canvas.width = window.innerWidth; canvas.height = window.innerHeight; renderer.resize(); diff --git a/src/renderer/runtime/controller/collision.ts b/src/renderer/runtime/controller/collision.ts index a44ffa4..3c12945 100644 --- a/src/renderer/runtime/controller/collision.ts +++ b/src/renderer/runtime/controller/collision.ts @@ -12,26 +12,25 @@ export function mkCollision(collision: Collision[]): Collidable[] { for (const c of collision) { switch (c.type) { - case 'polygon': - if (!c.vertices.length) - continue; - - let prev = c.vertices[c.vertices.length - 1]; - - for (const v of c.vertices) { - ret.push(LineSegment.fromPoints(vec2.clone(prev), vec2.clone(v))); - prev = v; - } - - for (const v of c.vertices) { - ret.push(new Point(vec2.clone(v))); - prev = v; - } - break; - - case 'circle': - ret.push(new Circle(vec2.clone(c.center), c.radius)); - break; + case 'polygon': + if (!c.vertices.length) continue; + + let prev = c.vertices[c.vertices.length - 1]; + + for (const v of c.vertices) { + ret.push(LineSegment.fromPoints(vec2.clone(prev), vec2.clone(v))); + prev = v; + } + + for (const v of c.vertices) { + ret.push(new Point(vec2.clone(v))); + prev = v; + } + break; + + case 'circle': + ret.push(new Circle(vec2.clone(c.center), c.radius)); + break; } } @@ -39,24 +38,20 @@ export function mkCollision(collision: Collision[]): Collidable[] { } export interface CollidableGroup { - getTranslation(): vec2|null; + getTranslation(): vec2 | null; getCollidables(): Collidable[]; } export function collide(collision: CollidableGroup, out: vec2, move: Movement, radius: number): boolean { const t = collision.getTranslation(); - if (t) - move = move.translate(vec2.negate(vec2.create(), t)); + if (t) move = move.translate(vec2.negate(vec2.create(), t)); for (const c of collision.getCollidables()) { - if (!c.collide(out, move, radius)) - continue; + if (!c.collide(out, move, radius)) continue; - if (vec2.squaredDistance(move.src, out) >= vec2.squaredDistance(move.src, move.dest)) - continue; + if (vec2.squaredDistance(move.src, out) >= vec2.squaredDistance(move.src, move.dest)) continue; - if (t) - vec2.add(out, out, t); + if (t) vec2.add(out, out, t); return true; } diff --git a/src/renderer/runtime/controller/entitycontext.ts b/src/renderer/runtime/controller/entitycontext.ts index 434bf9b..33d7b04 100644 --- a/src/renderer/runtime/controller/entitycontext.ts +++ b/src/renderer/runtime/controller/entitycontext.ts @@ -9,11 +9,7 @@ import { vec2 } from 'gl-matrix'; export class EntityContext implements CollidableGroup { public static async load(renderer: Renderer, name: string): Promise<EntityContext> { - return new EntityContext( - renderer, - name, - await EntityView.load(renderer, name), - ); + return new EntityContext(renderer, name, await EntityView.load(renderer, name)); } public readonly pos: vec2 = vec2.create(); @@ -28,7 +24,7 @@ export class EntityContext implements CollidableGroup { this.collision = mkCollision(view.data.collision); } - public render(time: number) { + public render(time: number): void { this.renderer.setTranslation(this.pos); this.view.renderByTime(time); } @@ -41,7 +37,7 @@ export class EntityContext implements CollidableGroup { return this.collision; } - public interact() { + public interact(): void { alert(`You've interacted with ${this.name}!`); } } diff --git a/src/renderer/runtime/controller/gamecontext.ts b/src/renderer/runtime/controller/gamecontext.ts index 9a68e25..1b17314 100644 --- a/src/renderer/runtime/controller/gamecontext.ts +++ b/src/renderer/runtime/controller/gamecontext.ts @@ -1,7 +1,7 @@ import { CollidableGroup, collide, mkCollision } from './collision'; import { EntityContext } from './entitycontext'; -import { MapData } from '../model/data/map'; +import { MapData, MapDataInput } from '../model/data/map'; import { ButtonCode, GameInputHandler } from '../view/input/gameinput'; import { MapView } from '../view/map'; @@ -26,17 +26,11 @@ export class GameContext implements CollidableGroup { vec2.set(player.pos, 7, 6); vec2.set(entity.pos, 4, 3); - return new GameContext( - renderer, - mapView, - player, - [entity], - mapCollision, - ); + return new GameContext(renderer, mapView, player, [entity], mapCollision); } private static async loadMap(renderer: Renderer, name: string): Promise<[MapView, Collidable[]]> { - const map = new MapData(await getJSON(`resources/map/${name}.json`)); + const map = new MapData((await getJSON(`resources/map/${name}.json`)) as MapDataInput); return [await MapView.load(renderer, map), mkCollision(map.collision)]; } @@ -50,7 +44,7 @@ export class GameContext implements CollidableGroup { private readonly input: GameInputHandler; private readonly playerDir: vec2 = vec2.fromValues(0, 1); - private speed: number = 0; + private speed = 0; private readonly collisionRadius = 15 / 32; private readonly interactLength = 1 / 32; @@ -65,19 +59,18 @@ export class GameContext implements CollidableGroup { this.input = new GameInputHandler(); this.input.addListener((input) => { switch (input.type) { - case 'button': - if (input.button === ButtonCode.Action) - this.interact(); - break; - - case 'direction': - if (vec2.sqrLen(input.direction) > 0) { - vec2.copy(this.playerDir, input.direction); - this.speed = this.maxSpeed; - } else { - this.speed = 0; - } - break; + case 'button': + if (input.button === ButtonCode.Action) this.interact(); + break; + + case 'direction': + if (vec2.sqrLen(input.direction) > 0) { + vec2.copy(this.playerDir, input.direction); + this.speed = this.maxSpeed; + } else { + this.speed = 0; + } + break; } }); @@ -108,8 +101,7 @@ export class GameContext implements CollidableGroup { private interact(): void { for (const e of this.entities) { - if (!this.canInteract(e)) - continue; + if (!this.canInteract(e)) continue; e.interact(); break; @@ -136,8 +128,7 @@ export class GameContext implements CollidableGroup { const newDest = vec2.create(); while (this.updateStepCollide(newDest, dest)) { - if (vec2.equals(newDest, this.player.pos)) - return; + if (vec2.equals(newDest, this.player.pos)) return; vec2.copy(dest, newDest); } @@ -153,21 +144,19 @@ export class GameContext implements CollidableGroup { return; } - for (let i = 0; i < diff; i++) - this.updateStep(); + for (let i = 0; i < diff; i++) this.updateStep(); } private render(): void { this.renderer.setCenter(this.player.pos); this.renderer.clear(); - for (const r of [this.mapView, ...this.entities, this.player]) - r.render(this.time); + for (const r of [this.mapView, ...this.entities, this.player]) r.render(this.time); } private async renderLoop(): Promise<void> { while (true) { - this.update(await nextAnimationFrame() - this.initTime); + this.update((await nextAnimationFrame()) - this.initTime); this.render(); } } diff --git a/src/renderer/runtime/math/line.ts b/src/renderer/runtime/math/line.ts index db99035..43bb6eb 100644 --- a/src/renderer/runtime/math/line.ts +++ b/src/renderer/runtime/math/line.ts @@ -1,10 +1,7 @@ import { mat2, vec2 } from 'gl-matrix'; import { Collidable } from './collision'; -const rot90 = mat2.fromValues( - 0, 1, - -1, 0, -); +const rot90 = mat2.fromValues(0, 1, -1, 0); export function normal(out: vec2, a: vec2): vec2 { return vec2.transformMat2(out, a, rot90); @@ -15,10 +12,7 @@ export function crossz(a: vec2, b: vec2): number { } export class Line { - constructor( - public readonly p: vec2, - public readonly v: vec2, - ) {} + constructor(public readonly p: vec2, public readonly v: vec2) {} public getNormal(out: vec2): vec2 { return normal(out, this.v); @@ -50,10 +44,7 @@ export class Line { export class Movement { public readonly v: vec2; - constructor( - public readonly src: vec2, - public readonly dest: vec2, - ) { + constructor(public readonly src: vec2, public readonly dest: vec2) { this.v = vec2.sub(vec2.create(), dest, src); } @@ -71,6 +62,7 @@ export class Movement { } public toLineSegment(): LineSegment { + // eslint-disable-next-line @typescript-eslint/no-use-before-define return LineSegment.fromPoints(this.src, this.dest); } @@ -90,11 +82,7 @@ export class LineSegment extends Line implements Collidable { return new LineSegment(p1, v, d); } - constructor( - p: vec2, - v: vec2, - public readonly l: number, - ) { + constructor(p: vec2, v: vec2, public readonly l: number) { super(p, v); } @@ -104,35 +92,30 @@ export class LineSegment extends Line implements Collidable { public containsPoint(p2: vec2): boolean { const d = this.projectPointDistance(p2); - return (d >= 0 && d <= this.l); + return d >= 0 && d <= this.l; } public collide(out: vec2, move: Movement, r: number): boolean { - if (this.distancePoint(move.src) < 0) - return false; + if (this.distancePoint(move.src) < 0) return false; - if (crossz(move.v, this.v) < 0) - return false; + if (crossz(move.v, this.v) < 0) return false; const t = this.getNormal(vec2.create()); vec2.scale(t, t, -r); const refMove = move.translate(t); - if (!this.collideRef(out, refMove)) - return false; + if (!this.collideRef(out, refMove)) return false; vec2.sub(out, out, t); return true; } private collideRef(out: vec2, move: Movement): boolean { - if (this.distancePoint(move.dest) >= 0) - return false; + if (this.distancePoint(move.dest) >= 0) return false; const x = move.intersectLine(vec2.create(), this); - if (!this.containsPoint(x)) - return false; + if (!this.containsPoint(x)) return false; this.projectPoint(out, move.dest); diff --git a/src/renderer/runtime/math/point.ts b/src/renderer/runtime/math/point.ts index 0865b8f..a00cb0c 100644 --- a/src/renderer/runtime/math/point.ts +++ b/src/renderer/runtime/math/point.ts @@ -9,12 +9,10 @@ export class Point implements Collidable { public collide(out: vec2, move: Movement, r: number): boolean { const moveLine = move.toLineSegment(); - if (moveLine.projectPointDistance(this.p) < 0) - return false; + if (moveLine.projectPointDistance(this.p) < 0) return false; const d = moveLine.distancePoint(this.p) / r; - if (Math.abs(d) >= 1) - return false; + if (Math.abs(d) >= 1) return false; const e = Math.sqrt(1 - d * d); @@ -26,8 +24,7 @@ export class Point implements Collidable { const refMove = move.translate(tr); - if (vec2.sqrDist(this.p, move.src) > r * r && !refMove.passes(this.p)) - return false; + if (vec2.sqrDist(this.p, move.src) > r * r && !refMove.passes(this.p)) return false; normal(t, t); diff --git a/src/renderer/runtime/util.ts b/src/renderer/runtime/util.ts index 5ea0c88..b4cd219 100644 --- a/src/renderer/runtime/util.ts +++ b/src/renderer/runtime/util.ts @@ -1,8 +1,7 @@ export function recordToMap<T>(r: Record<string, T>): Map<string, T> { const ret = new Map(); - for (const k of Object.keys(r)) - ret.set(k, r[k]); + for (const k of Object.keys(r)) ret.set(k, r[k]); return ret; } @@ -10,8 +9,7 @@ export function recordToMap<T>(r: Record<string, T>): Map<string, T> { export function mapValues<K, V1, V2>(f: (v: V1) => V2, map: Map<K, V1>): Map<K, V2> { const ret: Map<K, V2> = new Map(); - for (const [k, v] of map) - ret.set(k, f(v)); + for (const [k, v] of map) ret.set(k, f(v)); return ret; } @@ -19,8 +17,7 @@ export function mapValues<K, V1, V2>(f: (v: V1) => V2, map: Map<K, V1>): Map<K, export async function mapValuesAsync<K, V1, V2>(f: (v: V1) => Promise<V2>, map: Map<K, V1>): Promise<Map<K, V2>> { const ret: Map<K, V2> = new Map(); - for (const [k, v] of mapValues(f, map)) - ret.set(k, await v); + for (const [k, v] of mapValues(f, map)) ret.set(k, await v); return ret; } @@ -28,12 +25,12 @@ export async function mapValuesAsync<K, V1, V2>(f: (v: V1) => Promise<V2>, map: export function nextPowerOf2(n: number): number { let i = 1; - while (i < n) - i *= 2; + while (i < n) i *= 2; return i; } +// eslint-disable-next-line @typescript-eslint/no-explicit-any export class Listenable<T extends any[]> { private readonly listeners: Array<(...args: T) => void> = []; @@ -46,10 +43,9 @@ export class Listenable<T extends any[]> { } } -export async function getJSON(url: string): Promise<any> { +export async function getJSON(url: string): Promise<unknown> { const res = await window.fetch(url); - if (res.status < 200 || res.status >= 300) - throw new Error(res.statusText); + if (res.status < 200 || res.status >= 300) throw new Error(res.statusText); return await res.json(); } diff --git a/src/renderer/runtime/view/entity.ts b/src/renderer/runtime/view/entity.ts index ec91503..4fc0937 100644 --- a/src/renderer/runtime/view/entity.ts +++ b/src/renderer/runtime/view/entity.ts @@ -1,4 +1,4 @@ -import { EntityData } from '../model/data/entity'; +import { EntityData, EntityDataInput } from '../model/data/entity'; import { Renderer } from './renderer/renderer'; import { SpriteCoords, SpriteView, SpriteViewBuilder } from './sprite'; import { loadImage, mkTexture } from './util/image'; @@ -9,7 +9,7 @@ import { vec2 } from 'gl-matrix'; export class EntityView { public static async load(r: Renderer, name: string): Promise<EntityView> { - const data = new EntityData(await getJSON(`resources/entity/${name}.json`)); + const data = new EntityData((await getJSON(`resources/entity/${name}.json`)) as EntityDataInput); const tile = await loadImage(`resources/sprite/entity/${data.sprite}.png`); const [texture, size] = mkTexture(r, tile); @@ -33,22 +33,14 @@ export class EntityView { sprites.push(builder.build()); } - return new EntityView( - data, - sprites, - ); + return new EntityView(data, sprites); } private readonly totalTime: number; - private constructor( - public readonly data: EntityData, - public readonly sprites: SpriteView[], - ) { - if (data.animation) - this.totalTime = data.animation.sequence.reduce((a, s) => a + s[0], 0); - else - this.totalTime = 0; + private constructor(public readonly data: EntityData, public readonly sprites: SpriteView[]) { + if (data.animation) this.totalTime = data.animation.sequence.reduce((a, s) => a + s[0], 0); + else this.totalTime = 0; } public getSpriteByTime(time: number): SpriteView { @@ -57,16 +49,14 @@ export class EntityView { if (this.data.animation) { for (const [len, sprite] of this.data.animation.sequence) { time -= len; - if (time < 0) - return this.sprites[sprite]; + if (time < 0) return this.sprites[sprite]; } } return this.sprites[0]; } - public renderByTime(time: number) { + public renderByTime(time: number): void { this.getSpriteByTime(time).render(); } - } diff --git a/src/renderer/runtime/view/input/gameinput.ts b/src/renderer/runtime/view/input/gameinput.ts index 67fbe0c..b116839 100644 --- a/src/renderer/runtime/view/input/gameinput.ts +++ b/src/renderer/runtime/view/input/gameinput.ts @@ -35,13 +35,8 @@ export class GameInputHandler extends Listenable<[GameInput]> { super(); this.input = new InputHandler( - new Set([ - 'ArrowLeft', - 'ArrowUp', - 'ArrowRight', - 'ArrowDown', - ...Object.keys(buttonMapping), - ])); + new Set(['ArrowLeft', 'ArrowUp', 'ArrowRight', 'ArrowDown', ...Object.keys(buttonMapping)]), + ); this.input.addListener((key: string, pressed: boolean) => { const button = buttonMapping[key]; @@ -57,17 +52,12 @@ export class GameInputHandler extends Listenable<[GameInput]> { const dir = vec2.create(); - if (this.input.has('ArrowLeft')) - vec2.add(dir, dir, [-1, 0]); - if (this.input.has('ArrowUp')) - vec2.add(dir, dir, [0, -1]); - if (this.input.has('ArrowRight')) - vec2.add(dir, dir, [1, 0]); - if (this.input.has('ArrowDown')) - vec2.add(dir, dir, [0, 1]); - - if (vec2.sqrLen(dir) > 0) - vec2.normalize(dir, dir); + if (this.input.has('ArrowLeft')) vec2.add(dir, dir, [-1, 0]); + if (this.input.has('ArrowUp')) vec2.add(dir, dir, [0, -1]); + if (this.input.has('ArrowRight')) vec2.add(dir, dir, [1, 0]); + if (this.input.has('ArrowDown')) vec2.add(dir, dir, [0, 1]); + + if (vec2.sqrLen(dir) > 0) vec2.normalize(dir, dir); this.runListeners({ type: 'direction', diff --git a/src/renderer/runtime/view/input/inputhandler.ts b/src/renderer/runtime/view/input/inputhandler.ts index 17abfe6..4f5f150 100644 --- a/src/renderer/runtime/view/input/inputhandler.ts +++ b/src/renderer/runtime/view/input/inputhandler.ts @@ -7,26 +7,22 @@ export class InputHandler extends Listenable<[string, boolean]> { super(); window.addEventListener('keydown', (ev) => { - if (!relevantKeys.has(ev.code)) - return; + if (!relevantKeys.has(ev.code)) return; ev.preventDefault(); - if (ev.repeat) - return; + if (ev.repeat) return; this.keys.add(ev.code); this.runListeners(ev.code, true); }); window.addEventListener('keyup', (ev) => { - if (!relevantKeys.has(ev.code)) - return; + if (!relevantKeys.has(ev.code)) return; ev.preventDefault(); - if (!this.keys.has(ev.code)) - return; + if (!this.keys.has(ev.code)) return; this.keys.delete(ev.code); this.runListeners(ev.code, false); diff --git a/src/renderer/runtime/view/map.ts b/src/renderer/runtime/view/map.ts index 18def05..90d0c52 100644 --- a/src/renderer/runtime/view/map.ts +++ b/src/renderer/runtime/view/map.ts @@ -34,20 +34,20 @@ interface Tileset { async function loadTile(r: Renderer, tile: string): Promise<MapTile> { const name = tile.substr(1); switch (tile[0]) { - case '-': - return { - type: 'static', - image: await loadImage(`resources/sprite/tile/${name}.png`), - }; - - case '@': - return { - type: 'entity', - entity: await EntityView.load(r, name), - }; - - default: - throw new Error('invalid tile specifier'); + case '-': + return { + type: 'static', + image: await loadImage(`resources/sprite/tile/${name}.png`), + }; + + case '@': + return { + type: 'entity', + entity: await EntityView.load(r, name), + }; + + default: + throw new Error('invalid tile specifier'); } } @@ -55,10 +55,7 @@ function loadTiles(r: Renderer, tiles: string[]): Promise<MapTile[]> { return Promise.all(tiles.map((tile) => loadTile(r, tile))); } -function mkTileset( - r: Renderer, - mapTiles: MapTile[], -): Tileset { +function mkTileset(r: Renderer, mapTiles: MapTile[]): Tileset { const tileSize = 32; const canvasDim = nextPowerOf2(Math.sqrt(mapTiles.length)); @@ -67,29 +64,35 @@ function mkTileset( const canvas = document.createElement('canvas'); canvas.width = canvas.height = canvasSize; - let x = 0, y = 0; + let x = 0; + let y = 0; const tiles: TilesetTile[] = []; const ctx = canvas.getContext('2d') as CanvasRenderingContext2D; for (const tile of mapTiles) { switch (tile.type) { - case 'static': - ctx.drawImage(tile.image, x * tileSize, y * tileSize); - tiles.push({ - type: 'static', - coords: [x / canvasDim, y / canvasDim, (x + 1) / canvasDim, (y + 1) / canvasDim], - }); - - x++; - if (x === canvasDim) { - x = 0; - y++; - } - break; - - case 'entity': - tiles.push(tile); - break; + case 'static': + ctx.drawImage(tile.image, x * tileSize, y * tileSize); + tiles.push({ + type: 'static', + coords: [ + x / canvasDim, + y / canvasDim, + (x + 1) / canvasDim, + (y + 1) / canvasDim, + ], + }); + + x++; + if (x === canvasDim) { + x = 0; + y++; + } + break; + + case 'entity': + tiles.push(tile); + break; } } @@ -107,9 +110,9 @@ function addSprite( tileset: Tileset, x: number, y: number, - tile: number) { - if (!tile) - return; + tile: number, +): void { + if (!tile) return; const tilesetTile = tileset.tiles[tile - 1]; @@ -124,24 +127,12 @@ function addSprite( } } -function buildMapLayer(r: Renderer, tileset: Tileset, layer: number[][]): MapLayerView { - const builder = new SpriteViewBuilder(r, tileset.texture); - const entityTiles: Array<[[number, number], EntityView]> = []; - - for (let x = 0; x < layer[0].length; x++) - for (let y = 0; y < layer.length; y++) - addSprite(builder, entityTiles, tileset, x, y, layer[y][x]); - - return new MapLayerView(r, builder.build(), entityTiles); -} - class MapLayerView { public constructor( private r: Renderer, private staticTiles: SpriteView, private entityTiles: Array<[[number, number], EntityView]>, - ) { - } + ) {} public render(time: number): void { this.r.setTranslation([0, 0]); @@ -154,6 +145,16 @@ class MapLayerView { } } +function buildMapLayer(r: Renderer, tileset: Tileset, layer: number[][]): MapLayerView { + const builder = new SpriteViewBuilder(r, tileset.texture); + const entityTiles: Array<[[number, number], EntityView]> = []; + + for (let x = 0; x < layer[0].length; x++) + for (let y = 0; y < layer.length; y++) addSprite(builder, entityTiles, tileset, x, y, layer[y][x]); + + return new MapLayerView(r, builder.build(), entityTiles); +} + export class MapView { public static async load(r: Renderer, map: MapData): Promise<MapView> { const tiles = await loadTiles(r, map.tiles); @@ -163,11 +164,9 @@ export class MapView { return new MapView(layers); } - private constructor(private layers: MapLayerView[]) { - } + private constructor(private layers: MapLayerView[]) {} public render(time: number): void { - for (const layer of this.layers) - layer.render(time); + for (const layer of this.layers) layer.render(time); } } diff --git a/src/renderer/runtime/view/renderer/renderer.ts b/src/renderer/runtime/view/renderer/renderer.ts index 90583a6..1e5b699 100644 --- a/src/renderer/runtime/view/renderer/renderer.ts +++ b/src/renderer/runtime/view/renderer/renderer.ts @@ -28,8 +28,7 @@ export class Renderer { public createBuffer(): WebGLBuffer { const ret = this.gl.createBuffer(); - if (!ret) - throw new Error('unable to create buffer'); + if (!ret) throw new Error('unable to create buffer'); return ret; } @@ -50,11 +49,11 @@ export class Renderer { return this.shaders.samplerLoc; } - public setCenter(v: vec2|number[]) { + public setCenter(v: vec2 | number[]): void { this.snapToGrid(this.center, v); } - public setTranslation(v: vec2|number[]) { + public setTranslation(v: vec2 | number[]): void { vec2.sub(this.translation, v, this.center); this.snapToGrid(this.translation, this.translation); this.gl.uniform2fv(this.shaders.translateLoc, this.translation); @@ -66,7 +65,7 @@ export class Renderer { this.setTranslation([0, 0]); } - public snapToGrid(out: vec2, a: vec2|number[]): void { + public snapToGrid(out: vec2, a: vec2 | number[]): void { vec2.scale(out, a, this.coordScale); vec2.round(out, out); vec2.scale(out, out, 1 / this.coordScale); @@ -84,14 +83,13 @@ export class Renderer { const scale = this.viewScale * this.coordScale; mat4.identity(this.viewport); - mat4.scale(this.viewport, this.viewport, [2 * scale / ws, -2 * scale / hs, 1.0]); + mat4.scale(this.viewport, this.viewport, [(2 * scale) / ws, (-2 * scale) / hs, 1.0]); this.gl.uniformMatrix4fv(this.shaders.viewportLoc, false, this.viewport); } private mkContext(): WebGLRenderingContext { const gl = this.canvas.getContext('webgl'); - if (!gl) - throw new Error('unable to initialize WebGL context'); + if (!gl) throw new Error('unable to initialize WebGL context'); return gl; } diff --git a/src/renderer/runtime/view/renderer/shaders.ts b/src/renderer/runtime/view/renderer/shaders.ts index 8fd1fda..c00568e 100644 --- a/src/renderer/runtime/view/renderer/shaders.ts +++ b/src/renderer/runtime/view/renderer/shaders.ts @@ -11,8 +11,7 @@ export class Shaders { constructor(private readonly gl: WebGLRenderingContext) { const shaderProgram = this.gl.createProgram(); - if (!shaderProgram) - throw new Error('Unable to create shader program'); + if (!shaderProgram) throw new Error('Unable to create shader program'); const vertexShader = this.compileShader(this.gl.VERTEX_SHADER, vertexShaderSrc); const fragmentShader = this.compileShader(this.gl.FRAGMENT_SHADER, fragmentShaderSrc); @@ -46,8 +45,7 @@ export class Shaders { private compileShader(type: number, src: string): WebGLShader { const shader = this.gl.createShader(type); - if (!shader) - throw new Error('Unable to create shader'); + if (!shader) throw new Error('Unable to create shader'); this.gl.shaderSource(shader, src); this.gl.compileShader(shader); @@ -63,16 +61,14 @@ export class Shaders { private getAttribLocation(program: WebGLProgram, name: string): number { const ret = this.gl.getAttribLocation(program, name); - if (ret < 0) - throw new Error("unable to get location of attribute '" + name + "'"); + if (ret < 0) throw new Error("unable to get location of attribute '" + name + "'"); return ret; } private getUniformLocation(program: WebGLProgram, name: string): WebGLUniformLocation { const ret = this.gl.getUniformLocation(program, name); - if (!ret) - throw new Error("unable to get location of uniform '" + name + "'"); + if (!ret) throw new Error("unable to get location of uniform '" + name + "'"); return ret; } diff --git a/src/renderer/runtime/view/sprite.ts b/src/renderer/runtime/view/sprite.ts index 3a5ebcb..877b40e 100644 --- a/src/renderer/runtime/view/sprite.ts +++ b/src/renderer/runtime/view/sprite.ts @@ -2,34 +2,6 @@ import { Renderer } from './renderer/renderer'; export type SpriteCoords = [number, number, number, number]; -export class SpriteViewBuilder { - private static pushSprite(buf: number[], coords: SpriteCoords): void { - const [x1, y1, x2, y2] = coords; - - buf.push(x1); buf.push(y1); - buf.push(x2); buf.push(y1); - buf.push(x1); buf.push(y2); - - buf.push(x1); buf.push(y2); - buf.push(x2); buf.push(y1); - buf.push(x2); buf.push(y2); - } - - private readonly vertexData: number[] = []; - private readonly textureData: number[] = []; - - constructor(private readonly r: Renderer, private readonly texture: WebGLTexture) {} - - public addSprite(vertexCoords: SpriteCoords, texCoords: SpriteCoords): void { - SpriteViewBuilder.pushSprite(this.vertexData, vertexCoords); - SpriteViewBuilder.pushSprite(this.textureData, texCoords); - } - - public build(): SpriteView { - return new SpriteView(this.r, this.texture, this.vertexData, this.textureData); - } -} - export class SpriteView { private readonly vertexCount: number; private readonly vertexBuffer: WebGLBuffer; @@ -70,3 +42,37 @@ export class SpriteView { gl.drawArrays(gl.TRIANGLES, 0, this.vertexCount); } } + +export class SpriteViewBuilder { + private static pushSprite(buf: number[], coords: SpriteCoords): void { + const [x1, y1, x2, y2] = coords; + + buf.push(x1); + buf.push(y1); + buf.push(x2); + buf.push(y1); + buf.push(x1); + buf.push(y2); + + buf.push(x1); + buf.push(y2); + buf.push(x2); + buf.push(y1); + buf.push(x2); + buf.push(y2); + } + + private readonly vertexData: number[] = []; + private readonly textureData: number[] = []; + + constructor(private readonly r: Renderer, private readonly texture: WebGLTexture) {} + + public addSprite(vertexCoords: SpriteCoords, texCoords: SpriteCoords): void { + SpriteViewBuilder.pushSprite(this.vertexData, vertexCoords); + SpriteViewBuilder.pushSprite(this.textureData, texCoords); + } + + public build(): SpriteView { + return new SpriteView(this.r, this.texture, this.vertexData, this.textureData); + } +} diff --git a/src/renderer/runtime/view/util/image.ts b/src/renderer/runtime/view/util/image.ts index 37c5e7f..42e7eb7 100644 --- a/src/renderer/runtime/view/util/image.ts +++ b/src/renderer/runtime/view/util/image.ts @@ -3,20 +3,20 @@ import { Renderer } from '../renderer/renderer'; export function loadImage(url: string): Promise<HTMLImageElement> { return new Promise((resolve, reject) => { const img = new Image(); - img.addEventListener('load', () => { resolve(img); }); - img.addEventListener('error', () => { reject(new Error('failed to load ' + url)); }); + img.addEventListener('load', () => { + resolve(img); + }); + img.addEventListener('error', () => { + reject(new Error('failed to load ' + url)); + }); img.src = url; }); } -export function mkTexture( - r: Renderer, - src: HTMLCanvasElement|HTMLImageElement, -): [WebGLTexture, [number, number]] { +export function mkTexture(r: Renderer, src: HTMLCanvasElement | HTMLImageElement): [WebGLTexture, [number, number]] { const gl = r.getContext(); const texture = gl.createTexture(); - if (!texture) - throw new Error('unable to create texture'); + if (!texture) throw new Error('unable to create texture'); gl.bindTexture(gl.TEXTURE_2D, texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, src); @@ -25,9 +25,7 @@ export function mkTexture( gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - const size: [number, number] = [ - src.width / r.coordScale, src.height / r.coordScale, - ]; + const size: [number, number] = [src.width / r.coordScale, src.height / r.coordScale]; return [texture, size]; } |