Skip to content

Instantly share code, notes, and snippets.

@teak1
Created March 4, 2019 08:36
Show Gist options
  • Save teak1/60d6864541f32c6ad8d870f40b62f322 to your computer and use it in GitHub Desktop.
Save teak1/60d6864541f32c6ad8d870f40b62f322 to your computer and use it in GitHub Desktop.
import React, { Component } from 'react';
import Tilemap from "./Tilemap.jsx";
class ReadyEvent extends Event {
constructor(tilemap) {
super("ReadyEvent", {
Tiledap: tilemap,
map: tilemap.state.data
});
}
}
class Tiledmap extends Component {
constructor(props) {
super(props);
this.x = 0;
this.y = 0;
this.state = {
ready: false
};
this.data = null;
this.tilemap = null;
this.assetContainer = React.createRef();
this.viewport = React.createRef();
if (this.props.src) {
fetch(this.props.src).then(raw => raw.json()).then(map => {
this._mapLoaded(map);
});
} else if (this.props.tilemap) {
this.data = this.props.tilemap;
this._mapLoaded(this.data);
} else {
throw new Error("expected props to include either 'tilemap' or 'src'");
}
}
_mapLoaded(map) {
this.data = map;
this.tilemap = new Tilemap(this.data, this.assetContainer);
this.tilemap.onready.push(() => this.renderViewport(0, 0));
this.setState({ ready: true, data: map });
if (this.props.onReady) this.props.onReady(new ReadyEvent(this));
}
render() {
this.renderViewport(Math.round(this.props.x + this.x), Math.round(this.props.y + this.y));
return (
<div className="Tiledmap-render" viewport={{}}>
<div ref={this.assetContainer} style={{ display: "none" }}></div>
<canvas ref={this.viewport} width={this.props.width} height={this.props.height}></canvas>
</div>
);
}
renderViewport(x, y) {
// let current = this.renderViewport.current;
if (this.tilemap) this.tilemap.drawViewport(this.viewportContext, x, y);
}
componentDidMount() {
this.viewportContext = this.viewport.current.getContext("2d");
if (this.tilemap && this.tilemap.ready) this.renderViewport(this.props.x, this.props.y);
window.addEventListener("mousemove", (event) => {
if (event.buttons) {
this.x += event.movementX;
this.y += event.movementY;
this.setState({ ready: true, now: new Date() });
}
});
}
getContext() {
return this.viewportContext;
}
}
export default Tiledmap;
class Tilemap {
constructor(data, assets) {
this.raw = data;
this.INF_region_cache = [];
this.internal = {
debug: false,
layers: [],
tilesets: [],
objectLayers: {},
renderFunctions: []
};
this.assetContainer = assets;
this.finalRenderCanvas = document.createElement("canvas");
this.finalRenderCanvas.setAttribute("width", this.getPixelWidth());
this.finalRenderCanvas.setAttribute("height", this.getPixelHeight());
this.context = this.finalRenderCanvas.getContext("2d");
this.ready = false;
this.onready = [];
this.load();
}
getLayersCanvas() {
return this.internal.layers.map(_ => _.element);
}
getPixelWidth() {
return this.width * this.tilewidth;
}
getPixelHeight() {
return this.height * this.tileheight;
}
renderTileOnContext(context, index, x, y) {
this.internal.renderFunctions[index](context, x, y, this.tilewidth, this.tileheight);
}
get backgroundcolor() { return this.raw.backgroundcolor; }
get height() { return this.raw.height; }
get hexsidelength() { return this.raw.hexsidelength; }
get infinite() { return this.raw.infinite; }
get layers() { return this.raw.layers; }
get nextlayerid() { return this.raw.nextlayerid; }
get nextobjectid() { return this.raw.nextobjectid; }
get orientation() { return this.raw.orientation; }
get properties() { return this.raw.properties; }
get renderorder() { return this.raw.renderorder; }
get staggeraxis() { return this.raw.staggeraxis; }
get staggerindex() { return this.raw.staggerindex; }
get tiledversion() { return this.raw.tiledversion; }
get tileheight() { return this.raw.tileheight; }
get tilesets() { return this.raw.tilesets; }
get tilewidth() { return this.raw.tilewidth; }
get type() { return this.raw.type; }
get version() { return this.raw.version; }
get width() { return this.raw.width; }
debug(state) {
this.internal.debug = state;
}
async load() {
let promises = [];
for (let i = 0; i < this.tilesets.length; i++) {
let promise = new Promise(async (resolve, reject) => {
let image = new Image();
image.onload = resolve;
image.onerror = reject;
let potsrc = this.tilesets[i].image;
if (!potsrc) {
console.log((this.tilesets[i].source.replace(".tsx", ".json")));
fetch(this.tilesets[i].source.replace(".tsx", ".json")).then(_ => _.json()).then(data => {
this.tilesets[i] = data;
image.src = this.tilesets[i].image;
console.log(image, this.tilesets[i]);
image.setAttribute("data-index", i);
});
} else {
image.src = this.tilesets[i].image;
console.log(image, this.tilesets[i]);
image.setAttribute("data-index", i);
}
});
promises.push(promise.catch(console.log));
}
let all = await Promise.all(promises);
let images = all.map(event => {
if (!event || event.type === "error") return console.log(event);
let img = event.path[0];
this.assetContainer.current.appendChild(img);
let index = +img.getAttribute("data-index");
let tileset = this.tilesets[index];
let tile_iw = tileset.tilewidth;
let tile_ih = tileset.tileheight;
for (let i = 0; i < tileset.tilecount; i++) {
let pos_x = i % tileset.columns;
let pos_y = Math.floor(i / tileset.columns);
pos_x *= tile_iw;
pos_y *= tile_ih;
this.internal.renderFunctions[tileset.firstgid + i] = (context, x, y, tw, th) => {
context.drawImage(img, pos_x, pos_y, tile_iw, tile_ih, x * tile_iw, y * tile_ih, tw, th);
};
}
return img;
});
this.internal.tilesets = images;
if (this.infinite) {
this.parseChunks();
} else {
this.parseTiles();
this.renderFinal();
}
this.ready = true;
this.onready.forEach(cb => cb());
}
parseChunks() {
let layers = this.layers;
for (let i = 0; i < layers.length; i++) {
console.log(layers[i]);
if (layers[i].type === "objectgroup") {
this.internal.objectLayers[layers[i].name] = layers[i];
} else {
for (let chunkIndex = 0; chunkIndex < layers[i].chunks.length; chunkIndex++) {
let chunk = layers[i].chunks[chunkIndex];
let tileLayer = new TileLayer(chunk, this, true);
this.INF_region_cache.push({ layer: tileLayer, z_index: i });
}
}
}
console.log(this.INF_region_cache);
}
renderFinal() {
this.context.fillStyle = this.backgroundcolor;
if (this.context.fillStyle !== "") {
this.context.fillRect(0, 0, this.finalRenderCanvas.width, this.finalRenderCanvas.height);
}
for (let i = 0; i < this.internal.layers.length; i++) {
console.log("rendering", this.internal.layers[i]);
this.context.drawImage(this.internal.layers[i].element, 0, 0);
}
}
parseTiles() {
let z_index = 0;
for (let i = 0; i < this.layers.length; i++) {
let layer = this.layers[i];
if (layer.type === "objectgroup") {
this.internal.objectLayers[layer.name] = layer;
} else {
this.internal.layers[z_index] = new TileLayer(layer, this);
z_index++;
}
}
}
getChunks(_x, _y, canvas) {
let width = canvas.width;
let height = canvas.height;
let chunks = [];
for (let i = 0; i < this.INF_region_cache.length; i++) {
let chunk = this.INF_region_cache[i];
let layer = chunk.layer;
if (layer.x < _x + width || layer.y < _y + height || layer.x + layer.width > _x || layer.y + layer.height > _y) {
chunks.push(layer);
}
}
return chunks;
}
drawViewport(context, x, y) {
if (this.infinite) {
let toDraw = this.getChunks(x, y, context.canvas);
console.log(toDraw);
context.fillStyle = this.backgroundcolor;
if (context.fillStyle !== "") {
context.fillRect(0, 0, context.canvas.width, context.canvas.height);
}
for (let i = 0; i < toDraw.length; i++) {
context.drawImage(toDraw[i].element, x + toDraw[i].x, y + toDraw[i].y);
console.log(`chunk draw call`, toDraw[i].element, x + toDraw[i].x, y + toDraw[i].y);
}
} else {
context.drawImage(this.finalRenderCanvas, -x, -y);
}
}
}
class TileLayer {
constructor(data, tilemap, ischunk) {
this.tilemap = tilemap;
this.raw = data;
this.tileMatrix = [];
let _data = [...this.raw.data];
for (let i = 0; i < this.raw.height; i++) {
let row = _data.splice(0, this.raw.width);
this.tileMatrix.push(row);
}
this.element = document.createElement("canvas");
this.element.setAttribute("data-id", this.raw.id);
if (!ischunk) {
this.element.setAttribute("width", this.tilemap.getPixelWidth());
this.element.setAttribute("height", this.tilemap.getPixelHeight());
} else {
this.element.setAttribute("width", this.tilemap.tilewidth * this.raw.width);
this.element.setAttribute("height", this.tilemap.tileheight * this.raw.height);
this.x = this.tilemap.tilewidth * this.raw.x;
this.y = this.tilemap.tileheight * this.raw.y;
this.width = this.tilemap.tilewidth * this.raw.width;
this.height = this.tilemap.tileheight * this.raw.height;
}
this.context = this.element.getContext("2d");
this.render();
}
async render() {
let width = this.raw.width;
let height = this.raw.height;
for (let x = 0; x < width; x++) {
for (let y = 0; y < height; y++) {
let tile_index = this.tileMatrix[y][x];
if (tile_index > 0) {//tile index 0 is empty;
this.tilemap.renderTileOnContext(this.context, tile_index, x, y);
}
}
}
}
}
export default Tilemap;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment