Grid Engine

repository·master·Indexed 43 days ago

https://github.com/annoraaq/grid-engine

A 2D grid-based movement and positioning engine for JavaScript and TypeScript, version 2.52.1. It provides core logic for collision detection, pathfinding, and multi-tile object management. It is designed for use in Node.js environments, standalone with custom renderers, or as a Phaser3 plugin for 2D game boards. Key features include support for isometric and orthogonal map modes, four and eight-direction movement, and collision groups.

Tokens
31.2K
Snippets
106
Records
146
Agent score
54%

What's inside Grid Engine

  1. Overview of Grid Engine

    master

    Grid Engine is a JavaScript/TypeScript library designed for positioning and moving objects on a 2-dimensional grid. It is highly flexible and can be used in several environments:

    • Game Engines: Easily integrates with Phaser.js.
    • Standalone: Can be used with custom renderers (e.g., for ASCII games).
    • Server-side: Can run in Node.js environments.

    Key capabilities include collision detection (supporting multiple layers and collision groups), efficient pathfinding, support for multi-tile objects, isometric and orthogonal map modes, and both four and eight-direction diagonal movement.

  2. Core concepts of Grid Engine

    master

    Grid Engine is a library for managing object positioning and movement on a 2-dimensional square grid using JavaScript or TypeScript. It separates the underlying grid logic from the visual representation.

    Key abstractions include:

    • Tiles and Tile Positions: The grid is composed of tiles (squares/fields). A tile position refers to the specific coordinate of an object on the grid, as opposed to its pixel-based position.
    • Characters: All game objects managed by the engine are called characters. This is a generic term that includes moving entities (like players), items, or stationary objects that block movement.
    • Renderers: A renderer is the software layer that visualizes the grid logic. Grid Engine is renderer-agnostic. It provides a Phaser.js plugin, but can be used with custom renderers (e.g., ASCII for terminals, Three.js for 3D, or Vue.js for web UIs) or in a headless mode (no renderer) for server-side logic via Node.js.
  3. Implement a custom renderer

    master

    A renderer is a piece of software that visualizes the state of Grid Engine. To create one, you typically implement a class that:

    1. Holds a reference to the GridEngineHeadless instance and the Tilemap.
    2. Iterates through the tilemap dimensions using tilemap.getHeight() and tilemap.getWidth().
    3. Queries the engine state for each position using gridEngine.getCharactersAt(pos) and gridEngine.isTileBlocked(pos).
    4. Updates a DOM element (or other output) with the resulting representation.
    import { GridEngineHeadless, Tilemap } from "grid-engine";
    
    export class AsciiRenderer {
      constructor(
        private containerId: string,
        private gridEngine: GridEngineHeadless,
        private tilemap: Tilemap
      ) {}
    
      render(): void {
        const strArr: string[] = [];
        for (let r = 0; r < this.tilemap.getHeight(); r++) {
          for (let c = 0; c < this.tilemap.getWidth(); c++) {
            const pos = { x: c, y: r };
            if (this.gridEngine.getCharactersAt(pos).length > 0) {
              strArr.push("c");
            } else if (this.gridEngine.isTileBlocked(pos)) {
              strArr.push("#");
            } else {
              strArr.push(".");
            }
          }
          strArr.push("\n");
        }
        this.renderStr(strArr.join(""));
      }
    
      private renderStr(str: string): void {
        const container = document?.getElementById(this.containerId);
        if (container) {
          container.innerHTML = `<pre>${str}</pre>`;
        }
      }
    }
  4. Understand Character Layers (Beta)

    master

    Character layers allow you to place characters on different planes (e.g., 'ground' vs 'sky' or 'bridge'). This system impacts two core behaviors:

    1. Rendering Order: The z-index (depth) of a character layer is determined by the z-index of its corresponding tilemap layer.
    2. Collisions: Collisions only occur between characters within the same character layer. Characters on different layers do not collide with each other.

    To use character layers, you must map them to specific layers in your tilemap using the ge_charLayer property.

  5. Replace `Phaser.Math.Vector2` with `Position` objects

    master

    GridEngine 2 has removed the dependency on Phaser.Math.Vector2. Public methods now take and return Position objects, which are simple objects containing only x and y coordinates.

    Note: Since Phaser.Math.Vector2 objects also possess x and y properties, they remain compatible as input for methods expecting a Position.

  6. Use ge_heightShift to manage rendering depth

    master

    In Grid Engine, depth (and thus rendering order) is determined by the y-coordinate of an entity. To ensure a layer (like high grass) renders on top of characters regardless of their y-coordinate, you can use the ge_heightShift layer property.

    By applying a ge_heightShift of 1 to a tile-layer, its effective depth becomes y-coordinate + 1. This ensures that the layer is rendered on top of any character sharing the same y-coordinate, effectively placing the character 'inside' or 'under' the layer (e.g., walking through grass).

  7. Use Collision Layers to manage map collisions

    master

    Collision layers are a technique used to separate collision logic from graphical tiles. Instead of assigning collision properties directly to your visual tileset, you create a dedicated 'collision tileset' containing tiles that represent different collision boundaries (e.g., 4-direction or 8-direction blocking).

    Workflow:

    1. Create a Collision Tileset: Design a tileset where tile properties (like red borders) indicate which directions are blocked.
    2. Assign Tile Properties: In your map editor (like Tiled), assign the corresponding collision properties to these tiles.
    3. Add a Collision Layer: Create a new layer in your map specifically for these collision tiles.
    4. Configure Layer Visibility: Set the layer opacity (e.g., to 0.5) to help visualize collisions during development, or hide the layer entirely when you don't need to see them.
    5. Placement: Fill the layer with tiles to define the walkable/non-walkable areas of your map. This allows you to use the same visual tile in different collision contexts by simply placing different collision tiles in the collision layer.
  8. Pick the right pathfinding algorithm

    master

    Grid Engine supports four algorithms. Your choice depends on your specific needs:

    AlgorithmUse Case
    **A
    ***Required if you use tile costs/weights. Slower than JPS for large paths.
    Jump Point Search (JPS)Recommended for most 4-directional movement cases. It is highly performant but cannot be used if you need path widths/heights or characters with tile dimensions > 1. Do not use for 8-directional movement.
    BFSGood for 4-directional movement, especially in mazes with very narrow corridors (width 1 or 2).
    Bidirectional SearchSimilar to BFS; useful for 4-directional movement in narrow mazes.

    Important Note on 8-Directional Movement: Avoid JPS for 8-directional movement as it is currently the slowest option and may not provide correct results. Use BFS or Bidirectional Search instead if you do not require tile weights.

  9. Track character movement with the positionChanged observable

    master

    The tilemap object exposes a positionChanged observable. You can subscribe to this observable to detect whenever a character moves to a new position on the grid. This is useful for triggering logic based on location, such as updating UI, checking for tile-specific interactions, or managing game state transitions.

    In this example, the movement is triggered by arrow keys, and the positionChanged observable is used to react to those changes.

    // Conceptual usage pattern
    tilemap.positionChanged.subscribe((position) => {
      console.log('Character moved to:', position);
      // Add custom logic here, e.g., checking tile properties
    });
  10. How Layer Overlay handles character overlapping

    master

    When using character layers, transitioning a character upwards can cause them to overlap lower-layer characters if their sprites are larger than one tile.

    Enabling layerOverlay prevents this overlap, provided that your character sprites do not exceed a height of 2 tile sizes.

    Workaround for large sprites: If your character sprites are larger than 2 tiles in height, layerOverlay may not solve the issue. In this case, avoid layer transitions that involve vertical movement (up or down) and instead use horizontal movements (left or right) for transitions.

  11. Replace directional movement methods with `move()`

    master

    In Version 2, the specific directional methods moveLeft, moveRight, moveUp, and moveDown have been removed. Use the generic move(charId, direction) method instead, where direction is one of the supported directions (e.g., 'left').

    // Old way
    moveLeft("player");
    
    // New way
    move("player", "left");
  12. Use Grid Engine Headless (Standalone)

    master

    The headless version of Grid Engine does not require Phaser.js but requires a tilemap that implements the Tilemap interface.

    Using ArrayTilemap

    You can create a simple tilemap from an array of integers where 0 is non-blocking and 1 is blocking.

    import { GridEngineHeadless, ArrayTilemap } from "grid-engine";
    
    const gridEngineHeadless = new GridEngineHeadless();
    
    const tilemap = new ArrayTilemap({
      someLayer: {
        data: [
          [0, 0, 0, 0],
          [0, 1, 1, 0],
          [0, 1, 1, 0],
          [0, 0, 0, 0],
        ],
      },
    });
    
    gridEngineHeadless.create(tilemap, { characters: [{ id: "player" }] });

    Using TiledTilemap

    To use maps exported from the Tiled map editor, use the TiledTilemap implementation.

    import { GridEngineHeadless, TiledTilemap } from "grid-engine";
    import * as someTilemap from "./path/to/tilemap.json";
    
    const gridEngineHeadless = new GridEngineHeadless();
    const tilemap = new TiledTilemap(someTilemap);
    
    gridEngineHeadless.create(tilemap, { characters: [{ id: "player" }] });

    Note: If using the web version via <script>, access these classes via GridEngineImports.GridEngineHeadless and GridEngineImports.ArrayTilemap or GridEngineImports.TiledTilemap.