'use strict'; import * as util from '../util'; import EntityPosition from '../model/EntityPosition'; import MapData from '../model/MapData'; const tileSize = 32; const body = document.getElementsByTagName('body')[0]; function loadImage(url: string): Promise { return new Promise(function(resolve, reject) { var img = new Image(); img.addEventListener('load', () => { resolve(img); }); img.addEventListener('error', () => { reject(Error('Failed to load ' + url)); }); img.src = url; }); } function loadImages(imgs: {[key: string]: string}): Promise<{[key: string]: HTMLImageElement}> { return util.mapPromises(_.mapValues(imgs, loadImage)); } function loadTiles(tiles: {[key: string]: {file: string}}): Promise<{[key: string]: HTMLImageElement}> { return loadImages(_.mapValues(tiles, (t) => `resources/sprite/tile/${t.file}.png`)); } function loadEntities(entities: EntityPosition[]): Promise<{[key: string]: HTMLImageElement}> { var p: {[key: string]: Promise} = {}; entities.forEach(e => { p[e.entity.name] = loadImage(`resources/sprite/entity/${e.entity.name}.png`); }); return util.mapPromises(p); } export default class MapView { private redrawPending: boolean = false; private canvas: HTMLCanvasElement; private ctx: CanvasRenderingContext2D; private tiles: {[key: string]: HTMLImageElement}; private entitySprites: {[key: string]: HTMLImageElement}; constructor(private map: MapData, private entities: {[key: string]: EntityPosition}, private updateState: (time: number) => void) { this.canvas = document.createElement('canvas'); this.canvas.style.position = 'absolute'; body.appendChild(this.canvas); this.ctx = this.canvas.getContext('2d'); window.addEventListener('resize', () => this.setSize()); this.setSize(); var tilesReady = loadTiles(map.tiles).then((tiles) => { this.tiles = tiles; }); var entitiesReady = loadEntities(this.getEntities()).then((entities) => { this.entitySprites = entities; }); Promise.all([tilesReady, entitiesReady]).then(() => { this.redraw(); }); } private getEntities(): EntityPosition[] { return _.values(this.entities); } private setSize() { var e = document.documentElement; this.canvas.width = window.innerWidth || e.clientWidth || body.clientWidth; this.canvas.height = window.innerHeight || e.clientHeight || body.clientHeight; this.redraw() } private drawTile(x: number, y: number, tile: HTMLImageElement) { if (!tile) return; this.ctx.drawImage(tile, x, y); } private drawEntity(e: EntityPosition, time: number): boolean { var sprite = this.entitySprites[e.entity.name]; if (!sprite) return false; var x: number, y: number; if (e.transition) { var t = e.transition; var d = (time - t.start) / (t.end-t.start); x = (1-d) * t.orig.x + d * t.dest.x; y = (1-d) * t.orig.y + d * t.dest.y; } else { x = e.position.x; y = e.position.y; } this.ctx.drawImage( sprite, e.direction*tileSize, 0, tileSize, tileSize, x*tileSize, y*tileSize, tileSize, tileSize ); return !!e.transition; } private draw(time: number) { this.updateState(time); this.redrawPending = false; this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); if (!this.tiles || !this.entitySprites) return; this.map.layers.forEach((layer) => { let y = 0; layer.forEach((row) => { let x = 0; for (let tile in row) { this.drawTile(x, y, this.tiles[row[tile]]); x += tileSize; } y += tileSize; }); }); var animate = false; this.getEntities().forEach(e => { if (this.drawEntity(e, time)) animate = true; }); if (animate) this.redraw(); } redraw() { if (this.redrawPending) return; this.redrawPending = true; window.requestAnimationFrame((time: number) => this.draw(time)); } }