'use strict'; import * as util from '../util'; import EntityPosition from '../model/EntityPosition'; import MapData from '../model/MapData'; import Position from '../model/Position'; 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); } function entityPosition(e: EntityPosition, time: number): Position { if (e.transition) { var t = e.transition; var d = (time - t.start) / (t.end-t.start); return new Position( (1-d) * t.orig.x + d * t.dest.x, (1-d) * t.orig.y + d * t.dest.y ); } else { return e.position; } } function tiles(n: number) { return Math.round(tileSize*n); } class Rect { constructor(public x1: number, public y1: number, public x2: number, public y2: number) {} } export default class MapView { private redrawPending: boolean = false; private canvas: HTMLCanvasElement; private ctx: CanvasRenderingContext2D; private scale: number; private tiles: {[key: string]: HTMLImageElement}; private entitySprites: {[key: string]: HTMLImageElement}; constructor(private map: MapData, private entities: EntityPosition[], private origin: 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.entities).then((entities) => { this.entitySprites = entities; }); Promise.all([tilesReady, entitiesReady]).then(() => { this.redraw(); }); } private setSize() { var e = document.documentElement; var w = window.innerWidth || e.clientWidth || body.clientWidth; var h = window.innerHeight || e.clientHeight || body.clientHeight; this.canvas.width = w; this.canvas.height = h; var pixels = Math.max(w/20, h/15.0); this.scale = Math.ceil(pixels / tileSize); this.redraw() } private drawTile(x: number, y: number, tile: HTMLImageElement) { if (!tile) return; this.ctx.drawImage(tile, tiles(x)*this.scale, tiles(y)*this.scale, tileSize*this.scale, tileSize*this.scale); } private drawEntity(e: EntityPosition, time: number): boolean { var sprite = this.entitySprites[e.entity.name]; if (!sprite) return false; var p = entityPosition(e, time); this.ctx.drawImage( sprite, tiles(e.direction), 0, tileSize, tileSize, tiles(p.x)*this.scale, tiles(p.y)*this.scale, tileSize*this.scale, tileSize*this.scale ); return !!e.transition; } private setOrigin(time: number) { var origin = entityPosition(this.origin, time); var w = this.canvas.width; var h = this.canvas.height; this.ctx.translate(Math.round(w/2), Math.round(h/2)); this.ctx.translate(-tiles(origin.x + 0.5)*this.scale, -tiles(origin.y + 0.5)*this.scale); var sw = w / (this.scale*tileSize), sh = h / (this.scale*tileSize); return new Rect( Math.floor(origin.x-sw/2+0.5), Math.floor(origin.y-sh/2+0.5), Math.ceil(origin.x+sw/2-0.5), Math.ceil(origin.y+sh/2-0.5) ); } 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.ctx.save(); var rect = this.setOrigin(time); (this.ctx).mozImageSmoothingEnabled = false; (this.ctx).webkitImageSmoothingEnabled = false; (this.ctx).msImageSmoothingEnabled = false; (this.ctx).imageSmoothingEnabled = false; this.map.layers.forEach((layer) => { for (let y = rect.y1; y <= rect.y2; y++) { let row = layer[y]; if (!row) continue; for (let x = rect.x1; x <= rect.x2; x++) { let tile = row[x]; if (!tile) continue; this.drawTile(x, y, this.tiles[tile]); } } }); var animate = false; this.entities.forEach(e => { if (this.drawEntity(e, time)) animate = true; }); this.ctx.restore(); if (animate) this.redraw(); } redraw() { if (this.redrawPending) return; this.redrawPending = true; window.requestAnimationFrame((time: number) => this.draw(time)); } }