Allow using animated entities as map tiles
A more optimized implementation for animated tiles is planned.
This commit is contained in:
parent
4443ca2058
commit
2c6cd362c3
5 changed files with 133 additions and 42 deletions
129
src/view/map.ts
129
src/view/map.ts
|
@ -1,3 +1,4 @@
|
|||
import { EntityView } from './entity';
|
||||
import { Renderer } from './renderer/renderer';
|
||||
import { SpriteCoords, SpriteView, SpriteViewBuilder } from './sprite';
|
||||
import { loadImage, mkTexture } from './util/image';
|
||||
|
@ -6,39 +7,91 @@ import { MapData } from '../model/data/map';
|
|||
|
||||
import { nextPowerOf2 } from '../util';
|
||||
|
||||
interface Tileset {
|
||||
texture: WebGLTexture;
|
||||
tiles: SpriteCoords[];
|
||||
import { vec2 } from 'gl-matrix';
|
||||
|
||||
interface StaticMapTile {
|
||||
type: 'static';
|
||||
image: HTMLImageElement;
|
||||
}
|
||||
|
||||
function loadTiles(tiles: string[]): Promise<HTMLImageElement[]> {
|
||||
return Promise.all(tiles.map((t) => loadImage(`resources/sprite/tile/${t}.png`)));
|
||||
interface EntityTile {
|
||||
type: 'entity';
|
||||
entity: EntityView;
|
||||
}
|
||||
|
||||
type MapTile = StaticMapTile | EntityTile;
|
||||
|
||||
interface StaticTilesetTile {
|
||||
type: 'static';
|
||||
coords: SpriteCoords;
|
||||
}
|
||||
|
||||
type TilesetTile = StaticTilesetTile | EntityTile;
|
||||
|
||||
interface Tileset {
|
||||
texture: WebGLTexture;
|
||||
tiles: TilesetTile[];
|
||||
}
|
||||
|
||||
async function loadTile(r: Renderer, tile: string): Promise<MapTile> {
|
||||
const name = tile.substr(1);
|
||||
switch (tile[0]) {
|
||||
case '-':
|
||||
return {
|
||||
type: 'static',
|
||||
image: await loadImage(`resources/sprite/tile/${name}.png`),
|
||||
};
|
||||
|
||||
case '@':
|
||||
return {
|
||||
type: 'entity',
|
||||
entity: await EntityView.load(r, name),
|
||||
};
|
||||
|
||||
default:
|
||||
throw new Error('invalid tile specifier');
|
||||
}
|
||||
}
|
||||
|
||||
function loadTiles(r: Renderer, tiles: string[]): Promise<MapTile[]> {
|
||||
return Promise.all(tiles.map((tile) => loadTile(r, tile)));
|
||||
}
|
||||
|
||||
function mkTileset(
|
||||
r: Renderer,
|
||||
tiles: HTMLImageElement[],
|
||||
mapTiles: MapTile[],
|
||||
): Tileset {
|
||||
const tileSize = 32;
|
||||
|
||||
const canvasDim = nextPowerOf2(Math.sqrt(tiles.length));
|
||||
const canvasDim = nextPowerOf2(Math.sqrt(mapTiles.length));
|
||||
const canvasSize = canvasDim * tileSize;
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = canvas.height = canvasSize;
|
||||
|
||||
let x = 0, y = 0;
|
||||
const tileCoords: SpriteCoords[] = [];
|
||||
const tiles: TilesetTile[] = [];
|
||||
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
|
||||
|
||||
for (const tile of tiles) {
|
||||
ctx.drawImage(tile, x * tileSize, y * tileSize);
|
||||
tileCoords.push([x / canvasDim, y / canvasDim, (x + 1) / canvasDim, (y + 1) / canvasDim]);
|
||||
for (const tile of mapTiles) {
|
||||
switch (tile.type) {
|
||||
case 'static':
|
||||
ctx.drawImage(tile.image, x * tileSize, y * tileSize);
|
||||
tiles.push({
|
||||
type: 'static',
|
||||
coords: [x / canvasDim, y / canvasDim, (x + 1) / canvasDim, (y + 1) / canvasDim],
|
||||
});
|
||||
|
||||
x++;
|
||||
if (x === canvasDim) {
|
||||
x = 0;
|
||||
y++;
|
||||
x++;
|
||||
if (x === canvasDim) {
|
||||
x = 0;
|
||||
y++;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'entity':
|
||||
tiles.push(tile);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,40 +99,66 @@ function mkTileset(
|
|||
|
||||
return {
|
||||
texture,
|
||||
tiles: tileCoords,
|
||||
tiles,
|
||||
};
|
||||
}
|
||||
|
||||
function addSprite(builder: SpriteViewBuilder, tileset: Tileset, x: number, y: number, tile: number) {
|
||||
if (tile === 0)
|
||||
function addSprite(
|
||||
builder: SpriteViewBuilder,
|
||||
entityTiles: Array<[[number, number], EntityView]>,
|
||||
tileset: Tileset,
|
||||
x: number,
|
||||
y: number,
|
||||
tile: number) {
|
||||
if (!tile)
|
||||
return;
|
||||
|
||||
const tilePos = tileset.tiles[tile - 1];
|
||||
builder.addSprite([x, y, x + 1, y + 1], tilePos);
|
||||
const tilesetTile = tileset.tiles[tile - 1];
|
||||
|
||||
switch (tilesetTile.type) {
|
||||
case 'static':
|
||||
builder.addSprite([x, y, x + 1, y + 1], tilesetTile.coords);
|
||||
break;
|
||||
|
||||
case 'entity':
|
||||
entityTiles.push([[x + 0.5, y + 0.5], tilesetTile.entity]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function buildMapLayer(r: Renderer, tileset: Tileset, layer: number[][]): MapLayerView {
|
||||
const builder = new SpriteViewBuilder(r, tileset.texture);
|
||||
const entityTiles: Array<[[number, number], EntityView]> = [];
|
||||
|
||||
for (let x = 0; x < layer[0].length; x++)
|
||||
for (let y = 0; y < layer.length; y++)
|
||||
addSprite(builder, tileset, x, y, layer[y][x]);
|
||||
addSprite(builder, entityTiles, tileset, x, y, layer[y][x]);
|
||||
|
||||
return new MapLayerView(builder.build());
|
||||
return new MapLayerView(r, builder.build(), entityTiles);
|
||||
}
|
||||
|
||||
class MapLayerView {
|
||||
public constructor(private sprites: SpriteView) {
|
||||
public constructor(
|
||||
private r: Renderer,
|
||||
private staticTiles: SpriteView,
|
||||
private entityTiles: Array<[[number, number], EntityView]>,
|
||||
) {
|
||||
}
|
||||
|
||||
public render(time: number): void {
|
||||
this.sprites.render();
|
||||
this.r.setTranslation([0, 0]);
|
||||
this.staticTiles.render();
|
||||
|
||||
for (const [coords, entity] of this.entityTiles) {
|
||||
this.r.setTranslation(coords);
|
||||
entity.renderByTime(time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class MapView {
|
||||
public static async load(r: Renderer, map: MapData): Promise<MapView> {
|
||||
const tiles = await loadTiles(map.tiles);
|
||||
const tiles = await loadTiles(r, map.tiles);
|
||||
const tileset = mkTileset(r, tiles);
|
||||
|
||||
const layers = map.layers.map((layer) => buildMapLayer(r, tileset, layer.tiles));
|
||||
|
|
Reference in a new issue