This repository has been archived on 2025-03-02. You can view files and clone it, but cannot push or open issues or pull requests.
rpgedit/src/view/MapView.ts

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));
}
}