unloading, debug grid
This commit is contained in:
parent
143a93f9f4
commit
395d69d2c1
76
src/debug.js
76
src/debug.js
@ -1,20 +1,25 @@
|
|||||||
import { ctx } from './canvas'
|
import { ctx } from './canvas'
|
||||||
|
|
||||||
class Debugging {
|
class Debugging {
|
||||||
|
constructor () {
|
||||||
|
this.overlay = document.createElement('div')
|
||||||
|
this.overlay.style = 'color:#fff;position:absolute;top:15px;right:15px;background-color:hsla(0,0%,47%,0.5);padding:10px;display:flex;flex-direction:column;'
|
||||||
|
document.body.appendChild(this.overlay)
|
||||||
|
this.drawGrid = false
|
||||||
|
}
|
||||||
|
|
||||||
draw (vp, world, fps) {
|
draw (vp, world, fps) {
|
||||||
let p = vp.chunkIn(world.chunkSize * world.tileSize)
|
let p = vp.chunkIn(world.chunkSize * world.tileSize)
|
||||||
ctx.fillStyle = '#fff'
|
ctx.fillStyle = '#fff'
|
||||||
ctx.fillText(fps + ' fps', 4, 16)
|
ctx.fillText(fps + ' fps', 4, 16)
|
||||||
ctx.fillText('cam-in-chunk (x: ' + p.x + '; y: ' + p.y + ')', 4, 32)
|
ctx.fillText('cam (x: ' + vp.x + '; y: ' + vp.y + ')', 4, 16 * 2)
|
||||||
ctx.fillText('loaded ' + world.chunks.length, 4, 48)
|
ctx.fillText('cam-in-chunk (x: ' + p.x + '; y: ' + p.y + ')', 4, 16 * 3)
|
||||||
ctx.fillText('drawn ' + world._lastDrawCount, 4, 64)
|
ctx.fillText('loaded ' + world.chunks.length, 4, 16 * 4)
|
||||||
ctx.fillText('updates ' + world._lastUpdateCount, 4, 80)
|
ctx.fillText('drawn ' + world._lastDrawCount, 4, 16 * 5)
|
||||||
|
ctx.fillText('updates ' + world._lastUpdateCount, 4, 16 * 6)
|
||||||
}
|
}
|
||||||
|
|
||||||
createSliders (obj, args, fn) {
|
createSliders (obj, args, fn) {
|
||||||
let overlay = document.createElement('div')
|
|
||||||
overlay.style = 'color:#fff;position:absolute;top:15px;right:15px;background-color:hsla(0,0%,47%,0.5);padding:10px;display:flex;flex-direction:column;'
|
|
||||||
|
|
||||||
for (let a in args) {
|
for (let a in args) {
|
||||||
let min = args[a][0]
|
let min = args[a][0]
|
||||||
let max = args[a][1]
|
let max = args[a][1]
|
||||||
@ -40,9 +45,62 @@ class Debugging {
|
|||||||
div.appendChild(name)
|
div.appendChild(name)
|
||||||
div.appendChild(slider)
|
div.appendChild(slider)
|
||||||
div.appendChild(value)
|
div.appendChild(value)
|
||||||
overlay.appendChild(div)
|
this.overlay.appendChild(div)
|
||||||
}
|
}
|
||||||
document.body.appendChild(overlay)
|
}
|
||||||
|
|
||||||
|
addCheckbox (obj, arg, fn) {
|
||||||
|
let div = document.createElement('div')
|
||||||
|
div.style = 'display:flex;flex-direction:row;'
|
||||||
|
let name = document.createElement('span')
|
||||||
|
let checkbox = document.createElement('input')
|
||||||
|
name.style = 'flex-grow:1;'
|
||||||
|
name.innerHTML = arg
|
||||||
|
checkbox.type = 'checkbox'
|
||||||
|
checkbox.checked = obj[arg] === true
|
||||||
|
checkbox.addEventListener('change', function (e) {
|
||||||
|
obj[arg] = checkbox.checked
|
||||||
|
fn(arg, obj[arg])
|
||||||
|
})
|
||||||
|
div.appendChild(checkbox)
|
||||||
|
div.appendChild(name)
|
||||||
|
this.overlay.appendChild(div)
|
||||||
|
}
|
||||||
|
|
||||||
|
chunkGrid (ctx, chunk, view) {
|
||||||
|
if (!this.drawGrid) return
|
||||||
|
// Inner grid
|
||||||
|
ctx.lineWidth = 0.15
|
||||||
|
ctx.strokeStyle = '#041fff'
|
||||||
|
for (let x = 0; x <= chunk.fullSize; x += chunk.tile) {
|
||||||
|
ctx.beginPath()
|
||||||
|
// Vertical lines
|
||||||
|
ctx.moveTo(0 + x, 0)
|
||||||
|
ctx.lineTo(0 + x, chunk.fullSize)
|
||||||
|
|
||||||
|
// Horizontal lines
|
||||||
|
ctx.moveTo(0, 0 + x)
|
||||||
|
ctx.lineTo(chunk.fullSize, 0 + x)
|
||||||
|
|
||||||
|
// Close
|
||||||
|
ctx.closePath()
|
||||||
|
ctx.stroke()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chunk border
|
||||||
|
ctx.lineWidth = 1
|
||||||
|
ctx.strokeStyle = '#040404'
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.moveTo(0.5, 0)
|
||||||
|
ctx.lineTo(0.5, chunk.fullSize)
|
||||||
|
ctx.moveTo(0.5, chunk.fullSize)
|
||||||
|
ctx.lineTo(chunk.fullSize, chunk.fullSize)
|
||||||
|
ctx.closePath()
|
||||||
|
ctx.stroke()
|
||||||
|
|
||||||
|
// Chunk index
|
||||||
|
ctx.fillStyle = '#fff'
|
||||||
|
ctx.fillText(chunk.x + ';' + chunk.y, 5, 16)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ class HeightMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getHeight (x) {
|
getHeight (x) {
|
||||||
return this.getNoise(x) * this.amplitude
|
return this.getNoise(x) * this.amplitude + this.iy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
36
src/index.js
36
src/index.js
@ -14,7 +14,7 @@ let fps = 0
|
|||||||
|
|
||||||
let vp = new Viewport(0, 0)
|
let vp = new Viewport(0, 0)
|
||||||
|
|
||||||
let height = new HeightMap(0, 0, 16, 0)
|
let height = new HeightMap(0, 32, 16, 0)
|
||||||
let map = new TileMap('assets/ground.png', 32)
|
let map = new TileMap('assets/ground.png', 32)
|
||||||
|
|
||||||
const chunkSize = 32
|
const chunkSize = 32
|
||||||
@ -59,26 +59,39 @@ map.define({
|
|||||||
'STONE': 10
|
'STONE': 10
|
||||||
})
|
})
|
||||||
|
|
||||||
let test = new World(height, { GROUND: map }, chunkSize, tileSize)
|
let world = new World(height, { GROUND: map }, chunkSize, tileSize, 32, 64)
|
||||||
|
|
||||||
function update (dt) {
|
function update (dt) {
|
||||||
test.update(dt, vp)
|
world.update(dt, vp)
|
||||||
if (Input.isDown('w')) {
|
if (Input.isDown('w')) {
|
||||||
vp.y -= 5
|
vp.y -= 15
|
||||||
} else if (Input.isDown('s')) {
|
} else if (Input.isDown('s')) {
|
||||||
vp.y += 5
|
vp.y += 15
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Input.isDown('a')) {
|
if (Input.isDown('a')) {
|
||||||
vp.x -= 5
|
vp.x -= 15
|
||||||
} else if (Input.isDown('d')) {
|
} else if (Input.isDown('d')) {
|
||||||
vp.x += 5
|
vp.x += 15
|
||||||
|
}
|
||||||
|
|
||||||
|
let full = world.chunkSize * world.tileSize
|
||||||
|
if (vp.x < 0) {
|
||||||
|
vp.x = 0
|
||||||
|
} else if (vp.x + vp.width > world.width * full) {
|
||||||
|
vp.x = (full * world.width) - vp.width
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vp.y < 0) {
|
||||||
|
vp.y = 0
|
||||||
|
} else if (vp.y + vp.height > world.height * full) {
|
||||||
|
vp.y = (full * world.height) - vp.height
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function draw () {
|
function draw () {
|
||||||
test.draw(vp)
|
world.draw(vp)
|
||||||
Debug.draw(vp, test, fps)
|
Debug.draw(vp, world, fps)
|
||||||
}
|
}
|
||||||
|
|
||||||
function step () {
|
function step () {
|
||||||
@ -115,7 +128,10 @@ function start () {
|
|||||||
period: [1, 100, 1],
|
period: [1, 100, 1],
|
||||||
lacunarity: [1, 5, 1]
|
lacunarity: [1, 5, 1]
|
||||||
}, function (key, val) {
|
}, function (key, val) {
|
||||||
test.chunks = []
|
world.chunks = []
|
||||||
|
})
|
||||||
|
Debug.addCheckbox(Debug, 'drawGrid', function (argument) {
|
||||||
|
world.chunks = []
|
||||||
})
|
})
|
||||||
playing = true
|
playing = true
|
||||||
gameLoop()
|
gameLoop()
|
||||||
|
157
src/tiles.js
157
src/tiles.js
@ -1,7 +1,10 @@
|
|||||||
import { ctx, ResourceCacheFactory } from './canvas'
|
import { ctx, ResourceCacheFactory } from './canvas'
|
||||||
|
import { distanceTo } from './utils'
|
||||||
import Resource from './resource'
|
import Resource from './resource'
|
||||||
|
import Debug from './debug'
|
||||||
|
|
||||||
const cacheFactory = new ResourceCacheFactory()
|
const cacheFactory = new ResourceCacheFactory()
|
||||||
|
const UPDATE_RADIUS = 6
|
||||||
|
|
||||||
class TileMap {
|
class TileMap {
|
||||||
constructor (image, rows) {
|
constructor (image, rows) {
|
||||||
@ -47,37 +50,13 @@ class TileMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TileChunk {
|
class TileLayer {
|
||||||
constructor (ix, iy, size = 16, tileSize = 16) {
|
constructor (name, collider = false, size = 16, tileSize = 16) {
|
||||||
this.x = ix
|
this.name = name
|
||||||
this.y = iy
|
this.collide = collider
|
||||||
this.size = size
|
this.size = size
|
||||||
this.tile = tileSize
|
this.tile = tileSize
|
||||||
this.tiles = []
|
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) {
|
tileAt (i) {
|
||||||
@ -92,6 +71,63 @@ class TileChunk {
|
|||||||
return { x: i % this.size, y: Math.floor(i / this.size) }
|
return { x: i % this.size, y: Math.floor(i / this.size) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
draw (ctx, view, map) {
|
||||||
|
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)
|
||||||
|
ctx.drawImage(map.image, tileCoords.x, tileCoords.y, map.tile, map.tile,
|
||||||
|
coords.x * this.tile, coords.y * this.tile, this.tile, this.tile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update (dt) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Chunk {
|
||||||
|
constructor (ix, iy, size = 16, tileSize = 16) {
|
||||||
|
this.x = ix
|
||||||
|
this.y = iy
|
||||||
|
this.size = size
|
||||||
|
this.tile = tileSize
|
||||||
|
this.layers = []
|
||||||
|
this.dirty = true
|
||||||
|
this.img = null
|
||||||
|
this._updated = false
|
||||||
|
}
|
||||||
|
|
||||||
|
generateMap (tileMap, heightMap) {
|
||||||
|
this.layers = []
|
||||||
|
let fgLayer = new TileLayer('fg', true, this.size, this.tile)
|
||||||
|
for (let i = 0; i < this.size * this.size; i++) {
|
||||||
|
let tileCoords = fgLayer.toXY(i)
|
||||||
|
let tileAbs = this.toAbs(tileCoords)
|
||||||
|
let y = Math.ceil(heightMap.getHeight(tileAbs.x) * 5 / 2) - 4
|
||||||
|
if (tileAbs.y < y) {
|
||||||
|
fgLayer.tiles.push(-1)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (tileAbs.y === y) {
|
||||||
|
fgLayer.tiles.push(tileMap.indexOf('GRASS_TOP'))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (tileAbs.y < y + 10) {
|
||||||
|
fgLayer.tiles.push(tileMap.indexOf('DIRT'))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (tileAbs.y > heightMap.falloff - y + 64) {
|
||||||
|
fgLayer.tiles.push(-1)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fgLayer.tiles.push(tileMap.indexOf('STONE'))
|
||||||
|
}
|
||||||
|
this.layers.push(fgLayer)
|
||||||
|
this.dirty = true
|
||||||
|
}
|
||||||
|
|
||||||
toAbs (x, y) {
|
toAbs (x, y) {
|
||||||
if (typeof x === 'object') {
|
if (typeof x === 'object') {
|
||||||
y = x.y
|
y = x.y
|
||||||
@ -101,22 +137,30 @@ class TileChunk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get absPos () {
|
get absPos () {
|
||||||
return { x: this.x * this.size * this.tile, y: this.y * this.size * this.tile }
|
return { x: this.x * this.fullSize, y: this.y * this.fullSize }
|
||||||
|
}
|
||||||
|
|
||||||
|
get fullSize () {
|
||||||
|
return this.size * this.tile
|
||||||
}
|
}
|
||||||
|
|
||||||
draw (view, map) {
|
draw (view, map) {
|
||||||
// Create a cached image of the chunk
|
// Create a cached image of the chunk
|
||||||
if (this.dirty || !this.img) {
|
if (this.dirty || !this.img) {
|
||||||
cacheFactory.prepare(this.size * this.tile, this.size * this.tile)
|
cacheFactory.prepare(this.size * this.tile, this.size * this.tile)
|
||||||
for (let i in this.tiles) {
|
// Draw all layers
|
||||||
let tilei = this.tiles[i]
|
for (let i in this.layers) {
|
||||||
if (tilei === -1) continue
|
let layer = this.layers[i]
|
||||||
let coords = this.toXY(parseInt(i))
|
layer.draw(cacheFactory.ctx, view, map)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Draw a debug grid when enabled
|
||||||
|
Debug.chunkGrid(cacheFactory.ctx, this, view)
|
||||||
|
|
||||||
|
// Create cached image
|
||||||
this.img = cacheFactory.capture()
|
this.img = cacheFactory.capture()
|
||||||
|
|
||||||
|
// Don't update again next tick
|
||||||
this.dirty = false
|
this.dirty = false
|
||||||
this._updated = true
|
this._updated = true
|
||||||
return
|
return
|
||||||
@ -127,19 +171,27 @@ class TileChunk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
update (dt) {
|
update (dt) {
|
||||||
|
for (let i in this.layers) {
|
||||||
|
this.layers[i].update(dt)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class World {
|
class World {
|
||||||
constructor (heightMap, tileMaps, chunkSize = 16, tileSize = 16) {
|
constructor (heightMap, tileMaps, chunkSize = 16, tileSize = 16, height = 64, width = 128) {
|
||||||
this.heightMap = heightMap
|
this.heightMap = heightMap
|
||||||
this.chunkSize = chunkSize
|
this.chunkSize = chunkSize
|
||||||
this.tileSize = tileSize
|
this.tileSize = tileSize
|
||||||
this.tileMaps = tileMaps
|
this.tileMaps = tileMaps
|
||||||
this.chunks = []
|
this.chunks = []
|
||||||
|
this.height = height
|
||||||
|
this.width = width
|
||||||
|
|
||||||
|
// Indicate to the height map where the base of the world is
|
||||||
|
this.heightMap.falloff = height * this.chunkSize
|
||||||
|
|
||||||
// Debug info
|
// Debug info
|
||||||
|
this._unloadTick = 0
|
||||||
this._lastDrawCount = 0
|
this._lastDrawCount = 0
|
||||||
this._lastUpdateCount = 0
|
this._lastUpdateCount = 0
|
||||||
}
|
}
|
||||||
@ -156,10 +208,10 @@ class World {
|
|||||||
let posPoint = vp.chunkIn(this.chunkSize * this.tileSize)
|
let posPoint = vp.chunkIn(this.chunkSize * this.tileSize)
|
||||||
for (let x = posPoint.x - 4; x < posPoint.x + 5; x++) {
|
for (let x = posPoint.x - 4; x < posPoint.x + 5; x++) {
|
||||||
for (let y = posPoint.y - 4; y < posPoint.y + 5; y++) {
|
for (let y = posPoint.y - 4; y < posPoint.y + 5; y++) {
|
||||||
if (x < 0 || y < 0) continue
|
if (x < 0 || y < 0 || x >= this.width || y >= this.height) continue
|
||||||
let exists = this.getChunk(x, y)
|
let exists = this.getChunk(x, y)
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
let n = new TileChunk(x, y, this.chunkSize, this.tileSize)
|
let n = new Chunk(x, y, this.chunkSize, this.tileSize)
|
||||||
n.generateMap(this.tileMaps.GROUND, this.heightMap)
|
n.generateMap(this.tileMaps.GROUND, this.heightMap)
|
||||||
this.chunks.push(n)
|
this.chunks.push(n)
|
||||||
break
|
break
|
||||||
@ -170,17 +222,34 @@ class World {
|
|||||||
for (let i in this.chunks) {
|
for (let i in this.chunks) {
|
||||||
this.chunks[i].update(dt)
|
this.chunks[i].update(dt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove far away chunks from memory
|
||||||
|
this._unloadTick++
|
||||||
|
if (this._unloadTick === 60) {
|
||||||
|
this._unloadTick = 0
|
||||||
|
let keep = []
|
||||||
|
for (let i in this.chunks) {
|
||||||
|
let chunk = this.chunks[i]
|
||||||
|
let pos = chunk.absPos
|
||||||
|
let distance = distanceTo(vp.adjustCentered, pos)
|
||||||
|
if (distance <= chunk.fullSize * UPDATE_RADIUS) {
|
||||||
|
// Keep chunk
|
||||||
|
keep.push(chunk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.chunks = keep
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
draw (vp) {
|
draw (vp) {
|
||||||
this._lastDrawCount = 0
|
this._lastDrawCount = 0
|
||||||
this._lastUpdateCount = 0
|
this._lastUpdateCount = 0
|
||||||
|
const adj = this.tileSize
|
||||||
for (let i in this.chunks) {
|
for (let i in this.chunks) {
|
||||||
let chunk = this.chunks[i]
|
let chunk = this.chunks[i]
|
||||||
let absPos = chunk.absPos
|
let absPos = chunk.absPos
|
||||||
let chunkSize = chunk.size * this.tileSize
|
if (absPos.x > vp.x + vp.width + adj || absPos.x + chunk.fullSize < vp.x - adj ||
|
||||||
if (absPos.x > vp.x + vp.width || absPos.x + chunkSize < vp.x ||
|
absPos.y > vp.y + vp.height + adj || absPos.y + chunk.fullSize < vp.y - adj) continue
|
||||||
absPos.y > vp.y + vp.height || absPos.y + chunkSize < vp.y) continue
|
|
||||||
chunk._updated = false
|
chunk._updated = false
|
||||||
chunk.draw(vp, this.tileMaps.GROUND)
|
chunk.draw(vp, this.tileMaps.GROUND)
|
||||||
if (chunk._updated) this._lastUpdateCount++
|
if (chunk._updated) this._lastUpdateCount++
|
||||||
@ -189,4 +258,4 @@ class World {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { TileMap, TileChunk, World }
|
export { TileMap, Chunk, World }
|
||||||
|
@ -15,8 +15,13 @@ class Viewport {
|
|||||||
return canvas.height
|
return canvas.height
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get adjustCentered () {
|
||||||
|
return { x: Math.floor(this.x + this.width / 2), y: Math.floor(this.y + this.height / 2) }
|
||||||
|
}
|
||||||
|
|
||||||
chunkIn (size) {
|
chunkIn (size) {
|
||||||
return { x: Math.floor((this.x + this.width / 2) / size), y: Math.floor((this.y + this.height / 2) / size) }
|
let adj = this.adjustCentered
|
||||||
|
return { x: Math.floor(adj.x / size), y: Math.floor(adj.y / size) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user