import { ctx, ResourceCacheFactory } from './canvas' import Resource from './resource' const cacheFactory = new ResourceCacheFactory() class TileMap { constructor (image, rows) { this._src = image this.rows = rows this.defs = {} } get height () { return this.image.height } get width () { return this.image.width } get tile () { return this.width / this.rows } get image () { return Resource.loadImage(this._src, true) } tileAt (i) { return { x: (i % this.rows) * this.tile, y: Math.floor(i / this.rows) * this.tile } } define (tile, index) { if (typeof tile === 'object') { this.defs = Object.assign(this.defs, tile) return } this.defs[tile] = index } indexOf (tile) { return this.defs[tile] || null } positionOf (tile) { return this.tileAt(this.indexOf(tile)) } } class TileChunk { constructor (ix, iy, size = 16, tileSize = 16) { this.x = ix this.y = iy this.size = size this.tile = tileSize this.tiles = [] this.dirty = true this.img = null this._updated = false } generateMap (tileMap, heightMap) { for (let i = 0; i < this.size * this.size; i++) { let tileCoords = this.toXY(i) let tileAbs = this.toAbs(tileCoords) let y = Math.ceil(heightMap.getHeight(tileAbs.x) * 5 / 2) - 4 if (tileAbs.y < y) { this.tiles.push(-1) continue } if (tileAbs.y === y) { this.tiles.push(tileMap.indexOf('GRASS_TOP')) continue } if (tileAbs.y < y + 10) { this.tiles.push(tileMap.indexOf('DIRT')) continue } this.tiles.push(tileMap.indexOf('STONE')) } } tileAt (i) { return this.tiles[i] } tileAtXY (x, y) { return this.tileAt(x + this.size * y) } toXY (i) { return { x: i % this.size, y: Math.floor(i / this.size) } } toAbs (x, y) { if (typeof x === 'object') { y = x.y x = x.x } return { x: this.x * this.size + x, y: this.y * this.size + y } } get absPos () { return { x: this.x * this.size * this.tile, y: this.y * this.size * this.tile } } draw (view, map) { // Create a cached image of the chunk if (this.dirty || !this.img) { cacheFactory.prepare(this.size * this.tile, this.size * this.tile) for (let i in this.tiles) { let tilei = this.tiles[i] if (tilei === -1) continue let coords = this.toXY(parseInt(i)) let tileCoords = map.tileAt(tilei) cacheFactory.ctx.drawImage(map.image, tileCoords.x, tileCoords.y, map.tile, map.tile, coords.x * this.tile, coords.y * this.tile, this.tile, this.tile) } this.img = cacheFactory.capture() this.dirty = false this._updated = true return } // Draw the cached image let p = this.absPos ctx.drawImage(this.img, p.x - view.x, p.y - view.y) } update (dt) { } } class World { constructor (heightMap, tileMaps, chunkSize = 16, tileSize = 16) { this.heightMap = heightMap this.chunkSize = chunkSize this.tileSize = tileSize this.tileMaps = tileMaps this.chunks = [] // Debug info this._lastDrawCount = 0 this._lastUpdateCount = 0 } getChunk (x, y) { for (let i in this.chunks) { let chunk = this.chunks[i] if (chunk && chunk.x === x && chunk.y === y) return chunk } return null } update (dt, vp) { let posPoint = vp.chunkIn(this.chunkSize * this.tileSize) for (let x = posPoint.x - 4; x < posPoint.x + 5; x++) { for (let y = posPoint.y - 4; y < posPoint.y + 5; y++) { if (x < 0 || y < 0) continue let exists = this.getChunk(x, y) if (!exists) { let n = new TileChunk(x, y, this.chunkSize, this.tileSize) n.generateMap(this.tileMaps.GROUND, this.heightMap) this.chunks.push(n) break } } } for (let i in this.chunks) { this.chunks[i].update(dt) } } draw (vp) { this._lastDrawCount = 0 this._lastUpdateCount = 0 for (let i in this.chunks) { let chunk = this.chunks[i] let absPos = chunk.absPos let chunkSize = chunk.size * this.tileSize if (absPos.x > vp.x + vp.width || absPos.x + chunkSize < vp.x || absPos.y > vp.y + vp.height || absPos.y + chunkSize < vp.y) continue chunk._updated = false chunk.draw(vp, this.tileMaps.GROUND) if (chunk._updated) this._lastUpdateCount++ this._lastDrawCount++ } } } export { TileMap, TileChunk, World }