214 lines
7 KiB
TypeScript
214 lines
7 KiB
TypeScript
'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<HTMLImageElement> {
|
|
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<HTMLImageElement>} = {};
|
|
|
|
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);
|
|
|
|
(<any>this.ctx).mozImageSmoothingEnabled = false;
|
|
(<any>this.ctx).webkitImageSmoothingEnabled = false;
|
|
(<any>this.ctx).msImageSmoothingEnabled = false;
|
|
(<any>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));
|
|
}
|
|
}
|