DataGridXL β Demos
Game Editor
DataGridXL is so versatile that it can be used for things other than you might use Excel for.
By listening to DataGridXL setcellvaluesbatch
event, you can update your game map instantly while you're editing values inside the data grid instance.
Code
import DataGridXL from "path/to/DataGridXL.js";
import * as Phaser from 'https://cdn.jsdelivr.net/npm/phaser@3.85.2/dist/phaser.esm.js';
function debounce(fn, delay) {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => fn(...args), delay);
};
}
let game;
let resizeObserver;
let debouncedHandleResize;
export function init(){
const gridMap = [
[20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20],
[20, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 20],
[20, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 20],
[20, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 82, 29, 29, 29, 48, 48, 29, 29, 29, 29, 29, 20],
[20, 29, 29, 29, 29, 29, 29, 48, 48, 29, 29, 48, 48, 48, 48, 29, 29, 29, 29, 29, 29, 82, 29, 29, 20],
[20, 48, 48, 48, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 48, 48, 48, 29, 20],
[20, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 20],
[20, 29, 29, 29, 29, 82, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 20],
[20, 29, 29, 29, 29, 48, 48, 48, 48, 48, 29, 29, 29, 29, 29, 29, 82, 29, 29, 29, 29, 29, 29, 29, 20],
[20, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 48, 48, 48, 48, 29, 29, 29, 29, 29, 29, 20],
[20, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 20],
[20, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 82, 20],
[20, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 48, 48, 48, 20],
[20, 29, 29, 29, 29, 29, 29, 48, 48, 48, 48, 48, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 20],
[20, 48, 48, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 82, 29, 29, 29, 29, 29, 29, 29, 20],
[20, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 48, 48, 48, 29, 29, 29, 29, 29, 29, 20],
[20, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 20],
[20, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 82, 29, 29, 20],
[20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20]
];
const flatMap = gridMap.flat();
const inlineMap = {
"height":19,
"infinite":false,
"layers":[
{
"data": flatMap,
"height":19,
"id":3,
"name":"Level1",
"opacity":1,
"type":"tilelayer",
"visible":true,//false,
"width":25,
"x":0,
"y":0
},
{
"data": flatMap,
"height":19,
"id":4,
"name":"Level2",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":25,
"x":0,
"y":0
}
],
"nextlayerid":5,
"nextobjectid":1,
"orientation":"orthogonal",
"renderorder":"right-down",
"tiledversion":"1.2.2",
"tileheight":32,
"tilesets":[
{
"columns":14,
"firstgid":1,
"image":"/assets/phaser/gridtiles.png",
"imageheight":320,
"imagewidth":448,
"margin":0,
"name":"tiles",
"spacing":0,
"tilecount":140,
"tileheight":32,
"tilewidth":32
}
],
"tilewidth":32,
"type":"map",
"version":1.2,
"width":25
};
class Example extends Phaser.Scene
{
cursors;
pickups;
player;
layer;
tileset;
map;
constructor() {
super('Example'); // <--- DIT VOEG JE TOE
}
preload ()
{
this.load.image('tiles', '/assets/phaser/gridtiles.png');
this.load.image('player', '/assets/phaser/phaser-dude.png');
}
create ()
{
// Create a map using the data from inlineMap
// BUT change how we parse the layers
this.map = this.make.tilemap({
width: inlineMap.width,
height: inlineMap.height,
tileWidth: inlineMap.tilewidth,
tileHeight: inlineMap.tileheight
});
// Setup camera
this.cameras.main.setBounds(0, 0, inlineMap.width * inlineMap.tilewidth, inlineMap.height * inlineMap.tileheight);
//tileWidth: 32, tileHeight: 32,
// this.map = this.make.tilemap({ data: inlineMap, format: Phaser.Tilemaps.Formats.TILED_JSON });
// add the tileset
this.tileset = this.map.addTilesetImage('tiles');
// Instead of trying to access the layer by name, create a new layer and populate it
// Use the first layer data from inlineMap (Level1)
this.layer = this.map.createBlankLayer('gameLayer', this.tileset);
// Populate the layer with tile data from the first layer
const layerData = inlineMap.layers[0].data;
let tileIndex = 0;
for (let y = 0; y < inlineMap.height; y++) {
for (let x = 0; x < inlineMap.width; x++) {
const tileId = layerData[tileIndex];
if (tileId !== 0) {
this.map.putTileAt(tileId, x, y, false, this.layer);
}
tileIndex++;
}
}
// π DEBUG: check welke layers beschikbaar zijn
// console.log("Tilemap layers:", this.map.layers.map(l => l.name));
//this.layer = this.map.createLayer('Level1', this.tileset);
this.map.setCollision([ 20, 48 ], true, true, this.layer);
//this.map.setCollision([ 20, 48 ]);
this.pickups = this.map.filterTiles(tile => tile.index === 82);
const playerStart = {
col: 2,
row: 2
};
const tileSize = 32;
this.player = this.add.rectangle(
playerStart.col * tileSize + tileSize / 2,
playerStart.row * tileSize + tileSize / 2,
tileSize,
tileSize,
0xffff00
);
this.physics.add.existing(this.player);
this.physics.add.collider(this.player, this.layer);
this.cursors = this.input.keyboard.createCursorKeys();
this.cursors.up.on('down', () =>
{
if (this.player.body.blocked.down)
{
this.player.body.setVelocityY(-360);
}
}, this);
// Make the camera follow the player
this.cameras.main.startFollow(this.player, true, 0.08, 0.08);
}
update ()
{
this.player.body.setVelocityX(0);
if (this.cursors.left.isDown)
{
this.player.body.setVelocityX(-200);
}
else if (this.cursors.right.isDown)
{
this.player.body.setVelocityX(200);
}
this.physics.world.overlapTiles(this.player, this.pickups, this.hitPickup, null, this);
}
hitPickup (player, tile)
{
this.map.removeTile(tile, 29, false);
this.pickups = this.map.filterTiles(tile => tile.index === 82);
}
}
const gameDiv = document.getElementById('game');
const { width, height } = gameDiv.getBoundingClientRect();
const config = {
type: Phaser.AUTO,
width,
height,
backgroundColor: '#2d2d2d',
parent: 'game',
scene: Example,
physics: {
default: 'arcade',
arcade: {
gravity: { y: 600 }
}
} ,
pixelArt: true,
scale: {
mode: Phaser.Scale.NONE, // je kunt dit aanpassen
autoCenter: Phaser.Scale.CENTER_BOTH
}
};
game = new Phaser.Game(config);
const handleResize = () => {
game.scale.resize(gameDiv.clientWidth, gameDiv.clientHeight);
};
debouncedHandleResize = debounce(handleResize, 100);
resizeObserver = new ResizeObserver((entries) => {
// alleen reageren als #game zelf is veranderd
for (const entry of entries) {
if (entry.target === gameDiv) {
debouncedHandleResize();
}
}
});
resizeObserver.observe(gameDiv);
const gridSize = 30;
// create grid
const grid = new DataGridXL("grid", {
licenseKey: "your_license_key",
data: gridMap,
columnSettings: {
width: gridSize,
align: 'center',
headerAlign: 'center',
styleFunction(cellRef){
return {
backgroundColor: Number(cellRef.value) == 20 ?
'#D3D3D3' : Number(cellRef.value == 29) ?
'#A7C7E7' : Number(cellRef.value == 48) ?
'#CBB4D4' : Number(cellRef.value == 82) ?
'#FFF4B1' : 'transparent'
};
},
validateFunction(cellRef){
if(isNaN(cellRef.value)) return false;
return true;
}
},
// theme: {
// "sheet_text": "#000000aa"
// },
colHeaderLabelType: 'numbers',
//colWidth: 30,
rowHeight: gridSize,
rowHeaderWidth: gridSize,
allowMoveRows: false,
allowInsertRows: false,
allowDeleteRows: false,
allowHideRows: false,
allowInsertCols: false,
allowDeleteCols: false,
allowMoveCols: false,
allowHideCols: false,
allowSort: false,
bottomBar: [],
// fill cells
fillCellsDirection: 'xy',
allowSort: false
});
grid.events.on('$setcellvaluesbatch', (gridEvent) => {
console.log('set cell values batch', gridEvent);
const currentScene = game.scene.getScene('Example');
if (!currentScene || !currentScene.map) return;
const { rowIds, colIds, values } = gridEvent;
for (let rowIdx = 0; rowIdx < rowIds.length; rowIdx++) {
for (let colIdx = 0; colIdx < colIds.length; colIdx++) {
const row = rowIds[rowIdx] - 1; // id to index
const col = colIds[colIdx] - 1; // id to index
const newValue = values[rowIdx][colIdx];
console.log('not a number?', row, col, isNaN(newValue));
if (isNaN(newValue)) continue;
// Pas de tile aan in de map
currentScene.map.putTileAt(newValue, col, row, false, currentScene.layer);
// π‘ Flash effect op deze tile
const tileWorldX = col * currentScene.map.tileWidth;
const tileWorldY = row * currentScene.map.tileHeight;
const flash = currentScene.add.rectangle(
tileWorldX + currentScene.map.tileWidth / 2,
tileWorldY + currentScene.map.tileHeight / 2,
currentScene.map.tileWidth,
currentScene.map.tileHeight,
0xffff00,
0.4
);
currentScene.tweens.add({
targets: flash,
alpha: 0,
ease: 'Sine.easeOut',
duration: 400,
onComplete: () => flash.destroy()
});
}
}
// Update pickups (bijv. tiles met index 82)
currentScene.pickups = currentScene.map.filterTiles(tile => tile.index === 82);
});
}
export function destroy() {
if (resizeObserver) {
resizeObserver.disconnect();
resizeObserver = null;
}
if (game) {
game.destroy(true); // true = remove canvas from DOM
game = null;
}
}