diff options
author | Matthias Schiffer <mschiffer@universe-factory.net> | 2018-12-08 12:39:18 +0100 |
---|---|---|
committer | Matthias Schiffer <mschiffer@universe-factory.net> | 2018-12-08 12:39:18 +0100 |
commit | b3950330e3351437f153c6c1debb3821d6e28864 (patch) | |
tree | 0b381b523045bd59cd679825a11976a45813fc24 /src/view | |
parent | 439dcf391784ea3abb61473c74b9c27fcd9fdc2d (diff) | |
download | rpgedit-b3950330e3351437f153c6c1debb3821d6e28864.tar rpgedit-b3950330e3351437f153c6c1debb3821d6e28864.zip |
Create Electron app
Diffstat (limited to 'src/view')
-rw-r--r-- | src/view/entity.ts | 72 | ||||
-rw-r--r-- | src/view/input/gameinput.ts | 78 | ||||
-rw-r--r-- | src/view/input/inputhandler.ts | 44 | ||||
-rw-r--r-- | src/view/map.ts | 175 | ||||
-rw-r--r-- | src/view/renderer/renderer.ts | 99 | ||||
-rw-r--r-- | src/view/renderer/shaders.ts | 79 | ||||
-rw-r--r-- | src/view/renderer/shaders/default.fs | 10 | ||||
-rw-r--r-- | src/view/renderer/shaders/default.vs | 13 | ||||
-rw-r--r-- | src/view/sprite.ts | 72 | ||||
-rw-r--r-- | src/view/util/image.ts | 34 |
10 files changed, 0 insertions, 676 deletions
diff --git a/src/view/entity.ts b/src/view/entity.ts deleted file mode 100644 index ec91503..0000000 --- a/src/view/entity.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { EntityData } from '../model/data/entity'; -import { Renderer } from './renderer/renderer'; -import { SpriteCoords, SpriteView, SpriteViewBuilder } from './sprite'; -import { loadImage, mkTexture } from './util/image'; - -import { getJSON } from '../util'; - -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 tile = await loadImage(`resources/sprite/entity/${data.sprite}.png`); - - const [texture, size] = mkTexture(r, tile); - const frameSize = [size[0], size[1] / data.frames]; - - const offset = vec2.mul(vec2.create(), frameSize, data.anchor); - r.snapToGrid(offset, offset); - - const coords: SpriteCoords = [ - -offset[0], - -offset[1], - -offset[0] + frameSize[0], - -offset[1] + frameSize[1], - ]; - - const sprites: SpriteView[] = []; - - for (let frame = 0; frame < data.frames; frame++) { - const builder = new SpriteViewBuilder(r, texture); - builder.addSprite(coords, [0, frame / data.frames, 1, (frame + 1) / data.frames]); - sprites.push(builder.build()); - } - - 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; - } - - public getSpriteByTime(time: number): SpriteView { - time %= this.totalTime; - - if (this.data.animation) { - for (const [len, sprite] of this.data.animation.sequence) { - time -= len; - if (time < 0) - return this.sprites[sprite]; - } - } - - return this.sprites[0]; - } - - public renderByTime(time: number) { - this.getSpriteByTime(time).render(); - } - -} diff --git a/src/view/input/gameinput.ts b/src/view/input/gameinput.ts deleted file mode 100644 index 67fbe0c..0000000 --- a/src/view/input/gameinput.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { InputHandler } from './inputhandler'; - -import { Listenable } from '../../util'; - -import { vec2 } from 'gl-matrix'; - -export enum ButtonCode { - Action, - Back, - Menu, -} - -const buttonMapping: Record<string, ButtonCode> = { - KeyZ: ButtonCode.Action, - KeyX: ButtonCode.Back, - KeyC: ButtonCode.Menu, -}; - -export interface DirectionInput { - type: 'direction'; - direction: vec2; -} - -export interface ButtonInput { - type: 'button'; - button: ButtonCode; -} - -export type GameInput = DirectionInput | ButtonInput; - -export class GameInputHandler extends Listenable<[GameInput]> { - private readonly input: InputHandler; - - constructor() { - super(); - - this.input = new InputHandler( - new Set([ - 'ArrowLeft', - 'ArrowUp', - 'ArrowRight', - 'ArrowDown', - ...Object.keys(buttonMapping), - ])); - - this.input.addListener((key: string, pressed: boolean) => { - const button = buttonMapping[key]; - if (button !== undefined) { - if (pressed) - this.runListeners({ - type: 'button', - button, - }); - - return; - } - - 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); - - this.runListeners({ - type: 'direction', - direction: dir, - }); - }); - } -} diff --git a/src/view/input/inputhandler.ts b/src/view/input/inputhandler.ts deleted file mode 100644 index 17abfe6..0000000 --- a/src/view/input/inputhandler.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Listenable } from '../../util'; - -export class InputHandler extends Listenable<[string, boolean]> { - private readonly keys: Set<string> = new Set(); - - constructor(relevantKeys: Set<string>) { - super(); - - window.addEventListener('keydown', (ev) => { - if (!relevantKeys.has(ev.code)) - return; - - ev.preventDefault(); - - if (ev.repeat) - return; - - this.keys.add(ev.code); - this.runListeners(ev.code, true); - }); - - window.addEventListener('keyup', (ev) => { - if (!relevantKeys.has(ev.code)) - return; - - ev.preventDefault(); - - if (!this.keys.has(ev.code)) - return; - - this.keys.delete(ev.code); - this.runListeners(ev.code, false); - }); - - window.addEventListener('blur', () => { - this.keys.clear(); - this.runListeners('', false); - }); - } - - public has(key: string): boolean { - return this.keys.has(key); - } -} diff --git a/src/view/map.ts b/src/view/map.ts deleted file mode 100644 index 5eedaca..0000000 --- a/src/view/map.ts +++ /dev/null @@ -1,175 +0,0 @@ -import { EntityView } from './entity'; -import { Renderer } from './renderer/renderer'; -import { SpriteCoords, SpriteView, SpriteViewBuilder } from './sprite'; -import { loadImage, mkTexture } from './util/image'; - -import { MapData } from '../model/data/map'; - -import { nextPowerOf2 } from '../util'; - -import { vec2 } from 'gl-matrix'; - -interface StaticMapTile { - type: 'static'; - image: HTMLImageElement; -} - -interface EntityTile { - type: 'entity'; - entity: EntityView; -} - -type MapTile = StaticMapTile | EntityTile; - -interface StaticTilesetTile { - type: 'static'; - coords: SpriteCoords; -} - -type TilesetTile = StaticTilesetTile | EntityTile; - -interface Tileset { - texture: WebGLTexture; - tiles: TilesetTile[]; -} - -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'); - } -} - -function loadTiles(r: Renderer, tiles: string[]): Promise<MapTile[]> { - return Promise.all(tiles.map((tile) => loadTile(r, tile))); -} - -function mkTileset( - r: Renderer, - mapTiles: MapTile[], -): Tileset { - const tileSize = 32; - - const canvasDim = nextPowerOf2(Math.sqrt(mapTiles.length)); - const canvasSize = canvasDim * tileSize; - - const canvas = document.createElement('canvas'); - canvas.width = canvas.height = canvasSize; - - let x = 0, 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; - } - } - - const [texture] = mkTexture(r, canvas); - - return { - texture, - tiles, - }; -} - -function addSprite( - builder: SpriteViewBuilder, - entityTiles: Array<[[number, number], EntityView]>, - tileset: Tileset, - x: number, - y: number, - tile: number) { - if (!tile) - return; - - const tilesetTile = tileset.tiles[tile - 1]; - - switch (tilesetTile.type) { - case 'static': - builder.addSprite([x, y, x + 1, y + 1], tilesetTile.coords); - break; - - case 'entity': - entityTiles.push([[x + 0.5, y + 0.5], tilesetTile.entity]); - break; - } -} - -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]); - this.staticTiles.render(); - - for (const [coords, entity] of this.entityTiles) { - this.r.setTranslation(coords); - entity.renderByTime(time); - } - } -} - -export class MapView { - public static async load(r: Renderer, map: MapData): Promise<MapView> { - const tiles = await loadTiles(r, map.tiles); - const tileset = mkTileset(r, tiles); - - const layers = map.layers.map((layer) => buildMapLayer(r, tileset, layer.tiles)); - return new MapView(layers); - } - - private constructor(private layers: MapLayerView[]) { - } - - public render(time: number): void { - for (const layer of this.layers) - layer.render(time); - } -} diff --git a/src/view/renderer/renderer.ts b/src/view/renderer/renderer.ts deleted file mode 100644 index 93f8589..0000000 --- a/src/view/renderer/renderer.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { Shaders } from './shaders'; - -import { mat4, vec2 } from 'gl-matrix'; - -export class Renderer { - public readonly coordScale = 32; - private readonly viewScale = 2; - - private readonly gl: WebGLRenderingContext; - private readonly shaders: Shaders; - - private readonly center: vec2 = vec2.create(); - private readonly translation: vec2 = vec2.create(); - private readonly viewport: mat4 = mat4.create(); - - constructor(private readonly canvas: HTMLCanvasElement) { - this.gl = this.mkContext(); - - this.shaders = new Shaders(this.gl); - - this.gl.clearColor(0.0, 0.0, 0.0, 1.0); - - this.gl.enable(this.gl.BLEND); - this.gl.blendFunc(this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA); - this.gl.pixelStorei(this.gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1); - - this.setSize(); - } - - public createBuffer(): WebGLBuffer { - const ret = this.gl.createBuffer(); - if (!ret) - throw new Error('unable to create buffer'); - - return ret; - } - - public getContext(): WebGLRenderingContext { - return this.gl; - } - - public getVertexPosLoc(): number { - return this.shaders.vertexPosLoc; - } - - public getTextureCoordLoc(): number { - return this.shaders.textureCoordLoc; - } - - public getSamplerLoc(): WebGLUniformLocation { - return this.shaders.samplerLoc; - } - - public setCenter(v: vec2|number[]) { - this.snapToGrid(this.center, v); - } - - public setTranslation(v: vec2|number[]) { - vec2.sub(this.translation, v, this.center); - this.snapToGrid(this.translation, this.translation); - this.gl.uniform2fv(this.shaders.translateLoc, this.translation); - } - - public clear(): void { - this.gl.clear(this.gl.COLOR_BUFFER_BIT); - - this.setTranslation([0, 0]); - } - - 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); - } - - private mkContext(): WebGLRenderingContext { - const gl = ( - this.canvas.getContext('webgl') || this.canvas.getContext('experimental-webgl') - ); - if (!gl) - throw new Error('unable to initialize WebGL context'); - - return gl; - } - - private setSize(): void { - const w = this.canvas.width; - const h = this.canvas.height; - - this.gl.viewport(0, 0, w, h); - this.clear(); - - const scale = this.viewScale * this.coordScale; - - mat4.identity(this.viewport); - mat4.scale(this.viewport, this.viewport, [2 * scale / w, -2 * scale / h, 1.0]); - this.gl.uniformMatrix4fv(this.shaders.viewportLoc, false, this.viewport); - } -} diff --git a/src/view/renderer/shaders.ts b/src/view/renderer/shaders.ts deleted file mode 100644 index 8fd1fda..0000000 --- a/src/view/renderer/shaders.ts +++ /dev/null @@ -1,79 +0,0 @@ -import fragmentShaderSrc from './shaders/default.fs'; -import vertexShaderSrc from './shaders/default.vs'; - -export class Shaders { - public readonly viewportLoc: WebGLUniformLocation; - public readonly translateLoc: WebGLUniformLocation; - - public readonly vertexPosLoc: number; - public readonly textureCoordLoc: number; - public readonly samplerLoc: WebGLUniformLocation; - - constructor(private readonly gl: WebGLRenderingContext) { - const shaderProgram = this.gl.createProgram(); - 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); - - this.gl.attachShader(shaderProgram, vertexShader); - this.gl.attachShader(shaderProgram, fragmentShader); - - this.gl.linkProgram(shaderProgram); - if (!this.gl.getProgramParameter(shaderProgram, this.gl.LINK_STATUS)) { - const err = this.gl.getProgramInfoLog(shaderProgram); - - this.gl.deleteShader(vertexShader); - this.gl.deleteShader(fragmentShader); - this.gl.deleteProgram(shaderProgram); - - throw new Error('Unable to link shader: ' + err); - } - - this.gl.useProgram(shaderProgram); - - this.vertexPosLoc = this.getAttribLocation(shaderProgram, 'aVertexPos'); - this.gl.enableVertexAttribArray(this.vertexPosLoc); - - this.textureCoordLoc = this.getAttribLocation(shaderProgram, 'aTextureCoord'); - this.gl.enableVertexAttribArray(this.textureCoordLoc); - - this.viewportLoc = this.getUniformLocation(shaderProgram, 'uViewport'); - this.translateLoc = this.getUniformLocation(shaderProgram, 'uTranslate'); - this.samplerLoc = this.getUniformLocation(shaderProgram, 'uSampler'); - } - - private compileShader(type: number, src: string): WebGLShader { - const shader = this.gl.createShader(type); - if (!shader) - throw new Error('Unable to create shader'); - - this.gl.shaderSource(shader, src); - this.gl.compileShader(shader); - - if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) { - const err = this.gl.getShaderInfoLog(shader); - this.gl.deleteShader(shader); - throw new Error('Unable to compile shader: ' + err); - } - - return shader; - } - - 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 + "'"); - - 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 + "'"); - - return ret; - } -} diff --git a/src/view/renderer/shaders/default.fs b/src/view/renderer/shaders/default.fs deleted file mode 100644 index 2c8ad15..0000000 --- a/src/view/renderer/shaders/default.fs +++ /dev/null @@ -1,10 +0,0 @@ -precision highp float; - -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; - - -void main(void) { - gl_FragColor = texture2D(uSampler, vTextureCoord); -} diff --git a/src/view/renderer/shaders/default.vs b/src/view/renderer/shaders/default.vs deleted file mode 100644 index 4715a17..0000000 --- a/src/view/renderer/shaders/default.vs +++ /dev/null @@ -1,13 +0,0 @@ -attribute vec2 aVertexPos; -attribute vec2 aTextureCoord; - -uniform mat4 uViewport; -uniform vec2 uTranslate; - -varying highp vec2 vTextureCoord; - - -void main(void) { - gl_Position = uViewport * vec4(aVertexPos + uTranslate, 0.0, 1.0); - vTextureCoord = aTextureCoord; -} diff --git a/src/view/sprite.ts b/src/view/sprite.ts deleted file mode 100644 index 3a5ebcb..0000000 --- a/src/view/sprite.ts +++ /dev/null @@ -1,72 +0,0 @@ -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; - private readonly textureBuffer: WebGLBuffer; - - constructor( - private readonly r: Renderer, - private readonly texture: WebGLTexture, - vertexData: number[], - textureData: number[], - ) { - const gl = r.getContext(); - - this.vertexBuffer = r.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexData), gl.STATIC_DRAW); - - this.textureBuffer = r.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, this.textureBuffer); - gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureData), gl.STATIC_DRAW); - - this.vertexCount = vertexData.length / 2; - } - - public render(): void { - const gl = this.r.getContext(); - - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, this.texture); - gl.uniform1i(this.r.getSamplerLoc(), 0); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.vertexAttribPointer(this.r.getVertexPosLoc(), 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.textureBuffer); - gl.vertexAttribPointer(this.r.getTextureCoordLoc(), 2, gl.FLOAT, false, 0, 0); - - gl.drawArrays(gl.TRIANGLES, 0, this.vertexCount); - } -} diff --git a/src/view/util/image.ts b/src/view/util/image.ts deleted file mode 100644 index 0ad5d16..0000000 --- a/src/view/util/image.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Renderer } from '../renderer/renderer'; -import { SpriteCoords } from '../sprite'; - -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.src = url; - }); -} - -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'); - - gl.bindTexture(gl.TEXTURE_2D, texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, src); - 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.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, - ]; - - return [texture, size]; -} |