From 2c6cd362c32a2e9460e5ddebd85f050b9b5ab4e4 Mon Sep 17 00:00:00 2001 From: Matthias Schiffer <mschiffer@universe-factory.net> Date: Wed, 21 Nov 2018 21:44:11 +0100 Subject: [PATCH] Allow using animated entities as map tiles A more optimized implementation for animated tiles is planned. --- dist/resources/entity/water.json | 12 +++ dist/resources/map/test.json | 34 +++---- dist/resources/sprite/entity/water.png | Bin 0 -> 1718 bytes dist/resources/sprite/tile/water.png | Bin 778 -> 0 bytes src/view/map.ts | 129 ++++++++++++++++++++----- 5 files changed, 133 insertions(+), 42 deletions(-) create mode 100644 dist/resources/entity/water.json create mode 100644 dist/resources/sprite/entity/water.png delete mode 100644 dist/resources/sprite/tile/water.png diff --git a/dist/resources/entity/water.json b/dist/resources/entity/water.json new file mode 100644 index 0000000..79c7780 --- /dev/null +++ b/dist/resources/entity/water.json @@ -0,0 +1,12 @@ +{ + "sprite": "water", + "frames": 4, + "animation": { + "sequence": [ + [500, 0], + [500, 1], + [500, 2], + [500, 3] + ] + } +} diff --git a/dist/resources/map/test.json b/dist/resources/map/test.json index ec3b3fb..91efc19 100644 --- a/dist/resources/map/test.json +++ b/dist/resources/map/test.json @@ -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": [ { diff --git a/dist/resources/sprite/entity/water.png b/dist/resources/sprite/entity/water.png new file mode 100644 index 0000000000000000000000000000000000000000..61c2d4f74eba120340aaf78c2e4e7ce7a3a6b3d4 GIT binary patch literal 1718 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 M07*qoM6N<$f)QOi0{{R3 literal 0 HcmV?d00001 diff --git a/dist/resources/sprite/tile/water.png b/dist/resources/sprite/tile/water.png deleted file mode 100644 index 4a965436f949a880bbdbe6872afad4b1b817c460..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 778 zcmV+l1NHogP)<h;3K|Lk000e1NJLTq001BW001Be0ssI2{21+{0007odQ@0+Qek%> zaB^>EX>4U6ba`-PAZ2)IW&i+q+U=HGj@=*(hTmC5mw;~=mP5Em-9eY%pCl(|Iz8%4 zHI@1jti%y9w()18gz@A14Bs%wGY2)#siYJ!v}oaqha8_{ocfrfsQdjf=)6;S`GTPZ zT8>HA^R1lo5Bqd!IC}Ad?A-#n!tmR`Ue9@s{7Xh|SB&R&&TGz|=N!lGswR{n9h*8r zgohg#Zm5#%%M!$K8J>NY0?Dz)MjCr;af37}#3XT&QV&#V(x49As3Aw?!4A#bSoXOr z&V`#qA<-o?*`-LAEG&(Oh-eVLa-ltL+2b|Jh};RLOmIe6@<$0D8GJ_OY!Njg!tUv; z74l+U9B7qM)*WU5Li6Nidx0NvjqnDnfS~PV&e&j$+u>p}diIv=4Ir>H=%c^L`=Z8s zmndQzk|6<tm`IEgMQ3&*70v)ukvWU<1_ETp9Z9l^B(T8|S+TLkX}4wLV;)BZd`yx! z*c7RNrE2IO*Nh&jCZ=YV%q?578j>VUmQwPx&RlhIb@Sxz*^AdKS+nJoJ?C6<EnEag z(n=~`aw(-&#H<>sRjjVCsCDB_Hr;GXn{Rn5TWx5crp;Pv-f}CgcJ9)(TTk74?xoj3 z1FaN>4jXCs$fJxp(Q8wtPMc}^%(KjTQ9G&rCVxSVPHMc9T6TU>!>s#qw7a17ow3CX z#8?T$eGx!I^I{e=sq!MXn8m_46~;)?7Mo7721-DvgV-ir?4IO4%`KqTA93T)kP8dl zKOo0p$UAN?sP*+FY&)^z6t0>^!RZ@>@SN+cL8Z00o*AFPFX-@92e+f!(e3DVbUV5o z-H!gdBboStDEJ%u3Ft>w9G?X!k^lez32;bRa{vGf6951U69E94oEQKA04qsEK~zY` z?ae^}06+u)FfNWH(#d=!_-zH2)f1Oq91{ox0)apv5C{ZYKCXWOK*AzUJOBUy07*qo IM6N<$f*2iRx&QzG diff --git a/src/view/map.ts b/src/view/map.ts index c796c6f..5eedaca 100644 --- a/src/view/map.ts +++ b/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));