Last active
June 15, 2019 19:32
-
-
Save timetocode/5412558e4dacc08b1504759406114b70 to your computer and use it in GitHub Desktop.
Demonstration of representing 2D tile maps as 1D arrays and sending their data via nengi. Includes a tilemap which is a "2d array"(not really) of integers, and examples for a "2d array" of objects.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var TileEnum = { | |
EMPTY: 0, | |
GRASS: 1, | |
DIRT: 2, | |
STONEWALL: 3 | |
} | |
class TileMap { | |
constructor(width, height) { | |
this.width = width | |
this.height = height | |
this.tiles = [] | |
for (var i = 0; i < width * height; i++) { | |
this.tiles.push(TileEnum.EMPTY) | |
} | |
} | |
// converts 2D coords to 1D index | |
getIndex(x, y) { | |
return x + this.width * y | |
} | |
// converts 1D index to 2D coords | |
getXY(index) { | |
return { | |
x: index % this.width, | |
y: Math.floor(index / this.width) | |
} | |
} | |
boundsCheck(x, y) { | |
// probably off by 1, untested | |
return (x >= 0 && x <= this.width && y >= 0 && y <= this.height) | |
} | |
setTile(x, y, value) { | |
if (!this.boundsCheck(x,y)) { | |
return // perhaps warn rather than silently fail? | |
} | |
this.tiles[this.getIndex(x, y)] = value | |
} | |
getTile(x, y) { | |
if (!this.boundsCheck(x,y)) { | |
return // perhaps warn rather than silently fail? | |
} | |
return this.tiles[this.getIndex(x, y)] | |
} | |
/* prints the map to console, only viable for small maps, example: | |
* 0 0 0 0 0 | |
* 0 1 0 0 1 | |
* 2 0 0 0 3 | |
* 0 0 0 0 3 | |
* 0 0 0 0 0 | |
*/ | |
debugPrint() { | |
for (var y = 0; y < this.height; y++) { | |
var lineOfText = '' | |
for (var x = 0; x < this.width; x++) { | |
var tile = this.getTile(x, y) | |
lineOfText += tile + ' ' | |
} | |
console.log(lineOfText) | |
} | |
} | |
} | |
// example usage | |
var map = new TileMap(10, 10) | |
map.setTile(1, 1, TileEnum.STONEWALL) | |
map.setTile(2, 1, TileEnum.STONEWALL) | |
// if the tiles were objects.. | |
// map.setTile(x, y, new Tile(foo,bar,baz)) | |
// or more likely... | |
// map.getTile(x, y).bar = newBarValue | |
map.debugPrint() | |
// sending a map via nengi | |
class MapDataMessage { | |
constructor(width, height, tileData) { | |
this.width = width | |
this.height = height | |
this.tiles = tileData | |
} | |
} | |
MapDataMessage.protocol = { | |
width: nengi.UInt32, // or whatever size is needed, but no need to optimize this really, so big is fine | |
height: nengi.UInt32, // not *really* needed tbh.. the math that goes 1D <=> 2D only uses x,y,index,width not height | |
// this is the nengi syntax for sending | |
// an array of `type` (UInt8) // ints from 0 to 255 (overkill for this example) | |
// with a max length of `index' (UInt32) // ints from 0 to 4294967295 | |
// optimizing the `type` to be the smallest needed binary value | |
// can be valueable if the the array sent is very long and has small values | |
// optimizing the `index` is pretty pointless, unless sending many small arrays | |
// see all available types and their values: nengi\core\binary\Binary.js | |
tiles: { type: nengi.UInt8, index: nengi.UInt32 } | |
} | |
// usage of the message | |
... | |
instance.onConnect(client => { | |
// hypothetically sending the map to the player who just connected | |
var mapData = new MapDataMessage(map.width, map.height, map.tiles) | |
instance.message(mapData, client) | |
}) | |
// usage on the clientside | |
... | |
snapshot.messages.forEach(message => { | |
if (message.protocol.name === 'MapDataMessage') { | |
var foo = new ClientSideRepresentationOfTheMap() | |
foo.populate(message.width, message.height, message.tiles) | |
} | |
}) | |
// tilemaps where the tiles are Objects instead of integers | |
// TODO: modify tilemap as appropriate (skipped) | |
// and now the nengi side of things: | |
// Tile.js -- a tile that has numerous properties instead of just being an int | |
class Tile { | |
constructor() { | |
this.foo = 0 | |
this.bar = 0 | |
this.baz = 0 | |
} | |
} | |
Tile.protocol = { | |
// if maps are huge, this is an area that pays to pick optimal types | |
// note that this would still probably work fine in dev env even with nengi.Number (which is the biggest type) | |
foo: nengi.Boolean, // true false | |
bar: nengi.UInt3, // 0 to 7 | |
baz: nengi.UInt8 // 0 to 255 | |
} | |
// MapDataMessage.js | |
var Tile = require('./path/to/Tile.js') | |
class MapDataMessage { | |
constructor(width, height, tileData) { | |
this.width = width | |
this.height = height | |
this.tiles = tileData | |
} | |
} | |
MapDataMessage.protocol = { | |
width: nengi.UInt32, | |
height: nengi.UInt32, | |
// this is the nengi syntax for sending an ARRAY of OBJECTS that themselves are defined by a protocol | |
tiles: { type: Tile.protocol, index: nengi.UInt32 } | |
} | |
// Final piece of the puzzle | |
// nengiConfig.js | |
... | |
messages: [ | |
['MapDataMessage', require('./path/MapDataMessage.js')] | |
] | |
basics: [ // this is what the `basics` section of the config is for - protocols that are used inside of other protocols | |
['Tile', require('./path/Tile.js')] | |
] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Line
157
now needs to betiles: { type: Tile, index: nengi.UInt32 }