196 lines
6.1 KiB
TypeScript
196 lines
6.1 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);
|
|
}
|
|
|
|
|
|
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 getOrigin: () => 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<EntityPosition>(this.entities);
|
|
}
|
|
|
|
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;
|
|
|
|
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 p = entityPosition(e, time);
|
|
|
|
this.ctx.drawImage(
|
|
sprite,
|
|
tiles(e.direction), 0,
|
|
tileSize, tileSize,
|
|
tiles(p.x), tiles(p.y),
|
|
tileSize, tileSize
|
|
);
|
|
|
|
return !!e.transition;
|
|
}
|
|
|
|
private setOrigin(time: number) {
|
|
var origin = entityPosition(this.getOrigin(), 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), -tiles(origin.y + 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();
|
|
|
|
this.setOrigin(time);
|
|
|
|
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;
|
|
});
|
|
|
|
this.ctx.restore();
|
|
|
|
if (animate)
|
|
this.redraw();
|
|
}
|
|
|
|
redraw() {
|
|
if (this.redrawPending)
|
|
return;
|
|
|
|
this.redrawPending = true;
|
|
window.requestAnimationFrame((time: number) => this.draw(time));
|
|
}
|
|
}
|