"
πŸš€ DataGridXL3 Out Now! See what's newβ†’

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;
  }

}