Files
picoGraph/scripts/core/TileSet.js
Max Litruv Boonzaayer f917cf6ad5 Add initial Pico-8 cartridges for testing
- Created test_cart.p8 with basic drawing functionality and a print statement.
- Created test_unix.p8 with a simple initialization function.
2026-03-21 18:38:43 +11:00

168 lines
4.1 KiB
JavaScript

/**
* Represents a single tileset with 8x8 tiles (128x128px total, 16x16 grid).
*/
export class TileSet {
/**
* @param {Object} options Configuration options.
* @param {string} options.id Unique identifier.
* @param {string} options.name Display name.
* @param {string} options.filePath Path or URL to PNG file (file:// or blob:).
* @param {string} [options.nativePath] Native file system path (for Electron).
* @param {number} [options.tileWidth=8] Tile width in pixels.
* @param {number} [options.tileHeight=8] Tile height in pixels.
* @param {number} [options.columns=16] Number of tiles per row (128px / 8px = 16).
* @param {number} [options.rows=16] Number of tile rows (128px / 8px = 16).
*/
constructor(options) {
this.id = options.id || this.#generateId();
this.name = options.name || "Untitled Tileset";
this.filePath = options.filePath || "";
this.nativePath = options.nativePath || "";
this.tileWidth = options.tileWidth || 8;
this.tileHeight = options.tileHeight || 8;
this.columns = options.columns || 16;
this.rows = options.rows || 16;
/** @type {HTMLImageElement | null} */
this.image = null;
/** @type {boolean} */
this.isLoaded = false;
}
/**
* Generates a unique identifier.
*
* @returns {string}
*/
#generateId() {
return `tileset_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
}
/**
* Gets the total number of tiles.
*
* @returns {number}
*/
getTileCount() {
return this.columns * this.rows;
}
/**
* Gets the total width of the tileset image.
*
* @returns {number}
*/
getWidth() {
return this.columns * this.tileWidth;
}
/**
* Gets the total height of the tileset image.
*
* @returns {number}
*/
getHeight() {
return this.rows * this.tileHeight;
}
/**
* Loads the tileset image from file path.
*
* @returns {Promise<void>}
*/
async load() {
if (!this.filePath) {
throw new Error("No file path specified");
}
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
// Validate image is exactly 128x128px
if (img.width !== 128 || img.height !== 128) {
reject(new Error(`Tileset must be exactly 128x128px (found ${img.width}x${img.height}px)`));
return;
}
this.image = img;
this.isLoaded = true;
// Fixed grid: 16x16 tiles for 128x128 image with 8x8 tiles
this.columns = 16;
this.rows = 16;
resolve();
};
img.onerror = () => {
reject(new Error(`Failed to load image: ${this.filePath}`));
};
// Add cache-busting for file:// URLs to ensure reload works
const cacheBuster = this.filePath.startsWith("file://")
? `?t=${Date.now()}`
: "";
img.src = this.filePath + cacheBuster;
});
}
/**
* Reloads the tileset image.
*
* @returns {Promise<void>}
*/
async reload() {
this.isLoaded = false;
this.image = null;
return this.load();
}
/**
* Gets tile coordinates for a specific tile index.
*
* @param {number} tileIndex Tile index (0-based).
* @returns {{x: number, y: number, width: number, height: number}}
*/
getTileRect(tileIndex) {
const col = tileIndex % this.columns;
const row = Math.floor(tileIndex / this.columns);
return {
x: col * this.tileWidth,
y: row * this.tileHeight,
width: this.tileWidth,
height: this.tileHeight
};
}
/**
* Serializes tileset to JSON.
*
* @returns {Object}
*/
toJSON() {
return {
id: this.id,
name: this.name,
filePath: this.filePath,
nativePath: this.nativePath,
tileWidth: this.tileWidth,
tileHeight: this.tileHeight,
columns: this.columns,
rows: this.rows
};
}
/**
* Creates tileset from JSON data.
*
* @param {Object} data Serialized tileset.
* @returns {TileSet}
*/
static fromJSON(data) {
return new TileSet(data);
}
}