Allow using animated entities as map tiles

A more optimized implementation for animated tiles is planned.
This commit is contained in:
Matthias Schiffer 2018-11-21 21:44:11 +01:00
parent 4443ca2058
commit 2c6cd362c3
Signed by: neocturne
GPG key ID: 16EF3F64CB201D9C
5 changed files with 133 additions and 42 deletions

12
dist/resources/entity/water.json vendored Normal file
View file

@ -0,0 +1,12 @@
{
"sprite": "water",
"frames": 4,
"animation": {
"sequence": [
[500, 0],
[500, 1],
[500, 2],
[500, 3]
]
}
}

View file

@ -1,22 +1,22 @@
{
"tiles": [
"stone/floor",
"stone/plate",
"stone/wall/top",
"stone/wall/right",
"stone/wall/bottom",
"stone/wall/left",
"stone/wall/top_left",
"stone/wall/top_right",
"stone/wall/bottom_right",
"stone/wall/bottom_left",
"stone/wall/top_left_inner",
"stone/wall/top_right_inner",
"stone/wall/bottom_right_inner",
"stone/wall/bottom_left_inner",
"water",
"stone/border/bottom_right",
"stone/border/bottom_left"
"-stone/floor",
"-stone/plate",
"-stone/wall/top",
"-stone/wall/right",
"-stone/wall/bottom",
"-stone/wall/left",
"-stone/wall/top_left",
"-stone/wall/top_right",
"-stone/wall/bottom_right",
"-stone/wall/bottom_left",
"-stone/wall/top_left_inner",
"-stone/wall/top_right_inner",
"-stone/wall/bottom_right_inner",
"-stone/wall/bottom_left_inner",
"@water",
"-stone/border/bottom_right",
"-stone/border/bottom_left"
],
"layers": [
{

BIN
dist/resources/sprite/entity/water.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 778 B

View file

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