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

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