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
12
dist/resources/entity/water.json
vendored
Normal file
12
dist/resources/entity/water.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"sprite": "water",
|
||||
"frames": 4,
|
||||
"animation": {
|
||||
"sequence": [
|
||||
[500, 0],
|
||||
[500, 1],
|
||||
[500, 2],
|
||||
[500, 3]
|
||||
]
|
||||
}
|
||||
}
|
34
dist/resources/map/test.json
vendored
34
dist/resources/map/test.json
vendored
|
@ -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
BIN
dist/resources/sprite/entity/water.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
BIN
dist/resources/sprite/tile/water.png
vendored
BIN
dist/resources/sprite/tile/water.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 778 B |
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