mirror of
https://github.com/lscharen/iigs-game-engine.git
synced 2024-09-28 11:54:40 +00:00
Improve export of Tiled projects. Does level data and tileset export in one command now
This commit is contained in:
parent
191094e7e6
commit
44ee61a3f3
44
tools/mksprite.js
Normal file
44
tools/mksprite.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* Basic sprite compiler
|
||||||
|
*
|
||||||
|
* GTE has some specific needs that makes existing tools (like MrSprite) inappropriate. GTE
|
||||||
|
* sprites need to reference some internal data structures and have slightly difference code
|
||||||
|
* in order to handle clipping to the playfield bounds.
|
||||||
|
*
|
||||||
|
* The core sprite drawing approach is the same (set up Bank 1 direct page and stack), but
|
||||||
|
* the setup and dispatch are a bit different.
|
||||||
|
*
|
||||||
|
* A Note on Clipping
|
||||||
|
*
|
||||||
|
* GTE supports two clipping buffers for sprites to use. The first one is a static buffer
|
||||||
|
* that is aligned with the playfield and is used to clip the sprite when crossing the
|
||||||
|
* left and right boundaries, but since it's a static image, mask data can be put anywhere
|
||||||
|
* that the sprites should not show through, so irregular borders and sprite punch-outs
|
||||||
|
* on the playfield are possible.
|
||||||
|
*
|
||||||
|
* The second buffer matches the current tiles in the playfield and can be used as a
|
||||||
|
* dynamic mask of the playfield. Since the sprite code itself must use this data,
|
||||||
|
* different variations of the same sprite can be created to stand in "front" and "behind"
|
||||||
|
* different screen elements.
|
||||||
|
*
|
||||||
|
* The sprite requires the X and Y registers for this. The most general code that
|
||||||
|
* should be used for each sprite word is this:
|
||||||
|
*
|
||||||
|
* Example: DATA = $5670, MASK = $000F, screen_mask = $FF00, field_mask = $F0FF
|
||||||
|
* lda DP ; A = $1234
|
||||||
|
* eor #DATA ; A = $4444
|
||||||
|
* and #~MASK ; A = $4440
|
||||||
|
* and screen_mask,y ; A = $4400
|
||||||
|
* and >field_mask,x ; A = $4000
|
||||||
|
* eor DP ; A = $5234 <-- Only the high nibble is set to the sprite data
|
||||||
|
* sta DP
|
||||||
|
*
|
||||||
|
* It is not *required* that sprites use this approach, any compiled sprites code can be used,
|
||||||
|
* so if a sprite does not need to be masked, than any of the fast sprite approaches can be
|
||||||
|
* used.
|
||||||
|
*
|
||||||
|
* For clipping vertically, we pass in the starting and finishing lines in a register and
|
||||||
|
* a sprite record is set up to allow the sprite to be entered in the middle and exited
|
||||||
|
* before the last line of the sprite.
|
||||||
|
*/
|
||||||
|
|
@ -2,6 +2,7 @@ const fs = require('fs').promises;
|
|||||||
const PNG = require("pngjs").PNG;
|
const PNG = require("pngjs").PNG;
|
||||||
const process = require('process');
|
const process = require('process');
|
||||||
const { Buffer } = require('buffer');
|
const { Buffer } = require('buffer');
|
||||||
|
const StringBuilder = require('string-builder');
|
||||||
|
|
||||||
// Starting color index
|
// Starting color index
|
||||||
let startIndex = 0;
|
let startIndex = 0;
|
||||||
@ -17,7 +18,7 @@ main(process.argv.slice(2)).then(
|
|||||||
|
|
||||||
function findColorIndex(png, pixel) {
|
function findColorIndex(png, pixel) {
|
||||||
for (let i = 0; i < png.palette.length; i += 1) {
|
for (let i = 0; i < png.palette.length; i += 1) {
|
||||||
const color = png.palette[i];
|
const color = png.palette[i].slice(0, pixel.length); // Handle RGB or RGBA
|
||||||
if (color.every((c, idx) => c === pixel[idx])) {
|
if (color.every((c, idx) => c === pixel[idx])) {
|
||||||
return i + startIndex;
|
return i + startIndex;
|
||||||
}
|
}
|
||||||
@ -104,64 +105,84 @@ function getArg(argv, arg, fn, defaultValue) {
|
|||||||
if (fn) {
|
if (fn) {
|
||||||
return fn(argv[i+1]);
|
return fn(argv[i+1]);
|
||||||
}
|
}
|
||||||
return true; // REturn true if the argument was found
|
return true; // Return true if the argument was found
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main(argv) {
|
async function readPNG(filename) {
|
||||||
const data = await fs.readFile(argv[0]);
|
const data = await fs.readFile(filename);
|
||||||
const png = PNG.sync.read(data);
|
const png = PNG.sync.read(data);
|
||||||
startIndex = getArg(argv, '--start-index', x => parseInt(x, 10), 0);
|
|
||||||
asTileData = getArg(argv, '--as-tile-data', null, 0);
|
|
||||||
|
|
||||||
transparentColor = getArg(argv, '--transparent-color-index', x => parseInt(x, 10), 0);
|
|
||||||
|
|
||||||
console.info(`; startIndex = ${startIndex}`);
|
|
||||||
|
|
||||||
if (png.colorType !== 3) {
|
if (png.colorType !== 3) {
|
||||||
console.warn('; PNG must be in palette color type');
|
throw new Error('PNG must be in palette color type');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (png.palette.length > 16) {
|
if (png.palette.length > 16) {
|
||||||
console.warn('; Too many colors. Must be 16 or less');
|
throw new Error(`Too many colors. Must be 16 or less. Found ${png.palette.length}`);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dump the palette in IIgs hex format
|
return png;
|
||||||
console.log('; Palette:');
|
}
|
||||||
const hexCodes = png.palette.map(c => '$' + paletteToIIgs(c));
|
|
||||||
console.log(';', hexCodes.join(','));
|
|
||||||
|
|
||||||
// Just convert a paletted PNG to IIgs memory format. We make sute that only a few widths
|
async function main(argv) {
|
||||||
// are supported
|
try {
|
||||||
let buff = null;
|
const png = await readPNG(argv[0]);
|
||||||
|
|
||||||
if (png.width === 512) {
|
startIndex = getArg(argv, '--start-index', x => parseInt(x, 10), 0);
|
||||||
console.log('; Converting to BG1 format...');
|
asTileData = getArg(argv, '--as-tile-data', null, 0);
|
||||||
buff = pngToIIgsBuff(png);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (png.width === 256) {
|
transparentColor = getArg(argv, '--transparent-color-index', x => parseInt(x, 10), 0);
|
||||||
console.log('; Converting to BG1 format w/repeat...');
|
|
||||||
buff = pngToIIgsBuffRepeat(png);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (png.width === 328 || png.width == 320) {
|
console.info(`; startIndex = ${startIndex}`);
|
||||||
console.log('; Converting to BG0 format...');
|
|
||||||
buff = pngToIIgsBuff(png);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buff && argv[1]) {
|
if (png.colorType !== 3) {
|
||||||
if (asTileData) {
|
console.warn('; PNG must be in palette color type');
|
||||||
writeToTileDataSource(buff, png.width / 2);
|
return;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
console.log(`; Writing to output file ${argv[1]}`);
|
if (png.palette.length > 16) {
|
||||||
await writeBinayOutput(argv[1], buff);
|
console.warn('; Too many colors. Must be 16 or less');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dump the palette in IIgs hex format
|
||||||
|
console.log('; Palette:');
|
||||||
|
const hexCodes = png.palette.map(c => '$' + paletteToIIgs(c));
|
||||||
|
console.log(';', hexCodes.join(','));
|
||||||
|
|
||||||
|
// Just convert a paletted PNG to IIgs memory format. We make sure that only a few widths
|
||||||
|
// are supported
|
||||||
|
let buff = null;
|
||||||
|
|
||||||
|
if (png.width === 512) {
|
||||||
|
console.log('; Converting to BG1 format...');
|
||||||
|
buff = pngToIIgsBuff(png);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (png.width === 256) {
|
||||||
|
console.log('; Converting to BG1 format w/repeat...');
|
||||||
|
buff = pngToIIgsBuffRepeat(png);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (png.width === 328 || png.width == 320) {
|
||||||
|
console.log('; Converting to BG0 format...');
|
||||||
|
buff = pngToIIgsBuff(png);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buff && argv[1]) {
|
||||||
|
if (asTileData) {
|
||||||
|
writeToTileDataSource(buff, png.width / 2);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log(`; Writing to output file ${argv[1]}`);
|
||||||
|
await writeBinayOutput(argv[1], buff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log(`; ${e}`);
|
||||||
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,6 +190,161 @@ function reverse(str) {
|
|||||||
return [...str].reverse().join(''); // use [...str] instead of split as it is unicode-aware.
|
return [...str].reverse().join(''); // use [...str] instead of split as it is unicode-aware.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toHex(h) {
|
||||||
|
return h.toString(16).padStart(2, '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
function swap(hex) {
|
||||||
|
const high = hex & 0xF0;
|
||||||
|
const low = hex & 0x0F;
|
||||||
|
|
||||||
|
return (high >> 4) | (low << 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toMask(hex, transparentIndex) {
|
||||||
|
if (transparentIndex === -1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const indexHigh = (transparentIndex & 0xF) << 4;
|
||||||
|
const indexLow = (transparentIndex & 0xF);
|
||||||
|
let mask = 0;
|
||||||
|
if ((hex & 0xF0) === indexHigh) {
|
||||||
|
mask = mask | 0xF0;
|
||||||
|
}
|
||||||
|
if ((hex & 0x0F) === indexLow) {
|
||||||
|
mask = mask | 0x0F;
|
||||||
|
}
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all four 32 byte chunks of data for a single 8x8 tile
|
||||||
|
*/
|
||||||
|
function buildTile(buff, width, x, y, transparentIndex = -1) {
|
||||||
|
const tile = {
|
||||||
|
normal: {
|
||||||
|
data: [],
|
||||||
|
mask: []
|
||||||
|
},
|
||||||
|
flipped: {
|
||||||
|
data: [],
|
||||||
|
mask: []
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const offset = y * width + x;
|
||||||
|
for (dy = 0; dy < 8; dy += 1) {
|
||||||
|
const hex0 = buff[offset + dy * width + 0];
|
||||||
|
const hex1 = buff[offset + dy * width + 1];
|
||||||
|
const hex2 = buff[offset + dy * width + 2];
|
||||||
|
const hex3 = buff[offset + dy * width + 3];
|
||||||
|
|
||||||
|
const data = [hex0, hex1, hex2, hex3];
|
||||||
|
const mask = data.map(h => toMask(h, transparentIndex));
|
||||||
|
|
||||||
|
tile.normal.data.push(data);
|
||||||
|
tile.normal.mask.push(mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (dy = 0; dy < 8; dy += 1) {
|
||||||
|
const hex0 = swap(buff[offset + dy * width + 0]);
|
||||||
|
const hex1 = swap(buff[offset + dy * width + 1]);
|
||||||
|
const hex2 = swap(buff[offset + dy * width + 2]);
|
||||||
|
const hex3 = swap(buff[offset + dy * width + 3]);
|
||||||
|
|
||||||
|
const data = [hex3, hex2, hex1, hex0];
|
||||||
|
const mask = data.map(h => toMask(h, transparentIndex));
|
||||||
|
|
||||||
|
tile.flipped.data.push(data);
|
||||||
|
tile.flipped.mask.push(mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tile;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildTiles(buff, width, transparentIndex = -1) {
|
||||||
|
const tiles = [];
|
||||||
|
|
||||||
|
const MAX_TILES = 64;
|
||||||
|
|
||||||
|
let count = 0;
|
||||||
|
for (let y = 0; ; y += 8) {
|
||||||
|
for (let x = 0; x < width; x += 4, count += 1) {
|
||||||
|
if (count >= MAX_TILES) {
|
||||||
|
return tiles;
|
||||||
|
}
|
||||||
|
const tile = buildTile(buff, width, x, y, transparentIndex);
|
||||||
|
tiles.push(tile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeTileToStream(stream, data) {
|
||||||
|
// Output the tile data
|
||||||
|
for (const row of data) {
|
||||||
|
const hex = row.map(d => toHex(d)).join('');
|
||||||
|
stream.write(' hex ' + hex + '\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeTilesToStream(stream, tiles, label='tiledata') {
|
||||||
|
stream.write(`${label} ENT\n`);
|
||||||
|
stream.write('');
|
||||||
|
stream.write('; Reserved space (tile 0 is special...)\n');
|
||||||
|
stream.write(' ds 128\n');
|
||||||
|
|
||||||
|
const MAX_TILES = 511;
|
||||||
|
let count = 0;
|
||||||
|
for (const tile of tiles.slice(0, MAX_TILES)) {
|
||||||
|
console.log(`Writing tile ${count + 1}`);
|
||||||
|
stream.write(`; Tile ID ${count + 1}\n`);
|
||||||
|
writeTileToStream(stream, tile.normal.data);
|
||||||
|
writeTileToStream(stream, tile.normal.mask);
|
||||||
|
writeTileToStream(stream, tile.flipped.data);
|
||||||
|
writeTileToStream(stream, tile.flipped.mask);
|
||||||
|
stream.write('');
|
||||||
|
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildMerlinCodeForTile(data) {
|
||||||
|
const sb = new StringBuilder();
|
||||||
|
|
||||||
|
// Output the tile data
|
||||||
|
for (const row of data) {
|
||||||
|
const hex = row.map(d => toHex(d)).join('');
|
||||||
|
sb.appendLine(' hex ' + hex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildMerlinCodeForTiles(tiles, label='tiledata') {
|
||||||
|
const sb = new StringBuilder();
|
||||||
|
sb.appendLine(`${label} ENT`);
|
||||||
|
sb.appendLine();
|
||||||
|
sb.appendLine('; Reserved space (tile 0 is special...)');
|
||||||
|
sb.appendLine(' ds 128');
|
||||||
|
|
||||||
|
const MAX_TILES = 511;
|
||||||
|
let count = 0;
|
||||||
|
for (const tile of tiles.slice(0, MAX_TILES)) {
|
||||||
|
console.log(`Writing tile ${count + 1}`);
|
||||||
|
sb.appendLine(`; Tile ID ${count + 1}`);
|
||||||
|
sb.append(buildMerlinCodeForTile(tile.normal.data));
|
||||||
|
sb.append(buildMerlinCodeForTile(tile.normal.mask));
|
||||||
|
sb.append(buildMerlinCodeForTile(tile.flipped.data));
|
||||||
|
sb.append(buildMerlinCodeForTile(tile.flipped.mask));
|
||||||
|
sb.appendLine();
|
||||||
|
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
function writeToTileDataSource(buff, width) {
|
function writeToTileDataSource(buff, width) {
|
||||||
console.log('tiledata ENT');
|
console.log('tiledata ENT');
|
||||||
console.log();
|
console.log();
|
||||||
@ -186,47 +362,35 @@ function writeToTileDataSource(buff, width) {
|
|||||||
console.log('; Tile ID ' + (count + 1));
|
console.log('; Tile ID ' + (count + 1));
|
||||||
console.log('; From image coordinates ' + (x * 2) + ', ' + y);
|
console.log('; From image coordinates ' + (x * 2) + ', ' + y);
|
||||||
|
|
||||||
|
const tile = buildTile(buff, width, x, y, transparentIndex);
|
||||||
|
|
||||||
// Output the tile data
|
// Output the tile data
|
||||||
const offset = y * width + x;
|
for (const row of tile.normal.data) {
|
||||||
for (dy = 0; dy < 8; dy += 1) {
|
const hex = row.map(d => toHex(d)).join('');
|
||||||
const hex0 = buff[offset + dy * width + 0].toString(16).padStart(2, '0');
|
console.log(' hex ' + hex);
|
||||||
const hex1 = buff[offset + dy * width + 1].toString(16).padStart(2, '0');
|
|
||||||
const hex2 = buff[offset + dy * width + 2].toString(16).padStart(2, '0');
|
|
||||||
const hex3 = buff[offset + dy * width + 3].toString(16).padStart(2, '0');
|
|
||||||
console.log(' hex ' + hex0 + hex1 + hex2 + hex3);
|
|
||||||
}
|
}
|
||||||
console.log();
|
console.log();
|
||||||
|
|
||||||
// Output the tile mask
|
// Output the tile mask
|
||||||
for (dy = 0; dy < 8; dy += 1) {
|
for (const row of tile.normal.mask) {
|
||||||
//const hex0 = buff[offset + dy * width + 0].toString(16).padStart(2, '0');
|
const hex = row.map(d => toHex(d)).join('');
|
||||||
//const hex1 = buff[offset + dy * width + 1].toString(16).padStart(2, '0');
|
console.log(' hex ' + hex);
|
||||||
//const hex2 = buff[offset + dy * width + 2].toString(16).padStart(2, '0');
|
|
||||||
//const hex3 = buff[offset + dy * width + 3].toString(16).padStart(2, '0');
|
|
||||||
console.log(' hex 00000000');
|
|
||||||
}
|
}
|
||||||
console.log();
|
console.log();
|
||||||
|
|
||||||
// Output the flipped tile data
|
// Output the flipped tile data
|
||||||
for (dy = 0; dy < 8; dy += 1) {
|
for (const row of tile.flipped.data) {
|
||||||
const hex0 = reverse(buff[offset + dy * width + 0].toString(16).padStart(2, '0'));
|
const hex = row.map(d => toHex(d)).join('');
|
||||||
const hex1 = reverse(buff[offset + dy * width + 1].toString(16).padStart(2, '0'));
|
console.log(' hex ' + hex);
|
||||||
const hex2 = reverse(buff[offset + dy * width + 2].toString(16).padStart(2, '0'));
|
|
||||||
const hex3 = reverse(buff[offset + dy * width + 3].toString(16).padStart(2, '0'));
|
|
||||||
console.log(' hex ' + hex3 + hex2 + hex1 + hex0);
|
|
||||||
}
|
}
|
||||||
console.log();
|
console.log();
|
||||||
|
|
||||||
// Output the flipped tile mask
|
// Output the flipped tile data
|
||||||
for (dy = 0; dy < 8; dy += 1) {
|
for (const row of tile.flipped.mask) {
|
||||||
//const hex0 = buff[offset + dy * width + 0].toString(16).padStart(2, '0');
|
const hex = row.map(d => toHex(d)).join('');
|
||||||
//const hex1 = buff[offset + dy * width + 1].toString(16).padStart(2, '0');
|
console.log(' hex ' + hex);
|
||||||
//const hex2 = buff[offset + dy * width + 2].toString(16).padStart(2, '0');
|
|
||||||
//const hex3 = buff[offset + dy * width + 3].toString(16).padStart(2, '0');
|
|
||||||
console.log(' hex 00000000');
|
|
||||||
}
|
}
|
||||||
console.log();
|
console.log();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -249,3 +413,17 @@ async function writeBinayOutput(filename, buff) {
|
|||||||
await fs.writeFile(filename, Buffer.concat([header, buff]));
|
await fs.writeFile(filename, Buffer.concat([header, buff]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
buildTile,
|
||||||
|
buildTiles,
|
||||||
|
buildMerlinCodeForTiles,
|
||||||
|
buildMerlinCodeForTile,
|
||||||
|
findColorIndex,
|
||||||
|
paletteToIIgs,
|
||||||
|
pngToIIgsBuff,
|
||||||
|
readPNG,
|
||||||
|
toHex,
|
||||||
|
writeBinayOutput,
|
||||||
|
writeToTileDataSource,
|
||||||
|
writeTilesToStream
|
||||||
|
}
|
@ -2,9 +2,13 @@
|
|||||||
* Read an exported Tiled project in JSON format and produce Merlin32 output files with
|
* Read an exported Tiled project in JSON format and produce Merlin32 output files with
|
||||||
* GTE-compatible setup code wrapped around it.
|
* GTE-compatible setup code wrapped around it.
|
||||||
*/
|
*/
|
||||||
const fs = require('fs').promises;
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const { abort } = require('process');
|
||||||
const process = require('process');
|
const process = require('process');
|
||||||
const { Buffer } = require('buffer');
|
const StringBuilder = require('string-builder');
|
||||||
|
const parser = require('xml2json');
|
||||||
|
const png2iigs = require('./png2iigs');
|
||||||
|
|
||||||
main(process.argv.slice(2)).then(
|
main(process.argv.slice(2)).then(
|
||||||
() => process.exit(0),
|
() => process.exit(0),
|
||||||
@ -14,15 +18,92 @@
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
function emitHeader() {
|
function hexToRbg(hex) {
|
||||||
console.log('; Tiled Map Export');
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||||
console.log(';');
|
return result ? [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)] : null;
|
||||||
console.log('; This is a generated file. Do not modify.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function readTileSet(workdir, tileset) {
|
||||||
|
// Load up the PNG image
|
||||||
|
const pngfile = path.resolve(path.join(workdir, tileset.image.source));
|
||||||
|
console.log(`Reading PNG file from ${pngfile}`);
|
||||||
|
const png = await png2iigs.readPNG(pngfile);
|
||||||
|
|
||||||
|
// Find the index of the transparent color (if defined)
|
||||||
|
console.log(`Looking for transparency...`);
|
||||||
|
let transparentIndex = -1;
|
||||||
|
if (tileset.image.trans) {
|
||||||
|
const color = hexToRbg(tileset.image.trans);
|
||||||
|
console.log(`Found color ${color} as transparent marker`);
|
||||||
|
transparentIndex = png2iigs.findColorIndex(png, color);
|
||||||
|
if (typeof transparentIndex !== 'number') {
|
||||||
|
console.log('Could not find color in palette');
|
||||||
|
console.log(png.palette);
|
||||||
|
transparentIndex = -1;
|
||||||
|
} else {
|
||||||
|
console.log(`Transparent color palette index is ${transparentIndex}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Converting PNG to IIgs bitmap format...`);
|
||||||
|
const buff = png2iigs.pngToIIgsBuff(png);
|
||||||
|
|
||||||
|
console.log(`Building tiles...`);
|
||||||
|
const tiles = png2iigs.buildTiles(buff, png.width / 2, transparentIndex);
|
||||||
|
|
||||||
|
// Return the tiles
|
||||||
|
return tiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitHeader() {
|
||||||
|
const sb = new StringBuilder();
|
||||||
|
sb.appendLine('; Tiled Map Export');
|
||||||
|
sb.appendLine(';');
|
||||||
|
sb.appendLine('; This is a generated file. Do not modify.');
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadTileset(workdir, tileset) {
|
||||||
|
const source = tileset.source;
|
||||||
|
const filename = path.isAbsolute(source) ? source : path.join(workdir, source);
|
||||||
|
|
||||||
|
const contents = fs.readFileSync(filename);
|
||||||
|
return JSON.parse(parser.toJson(contents));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getArg(argv, arg, fn, defaultValue) {
|
||||||
|
for (let i = 0; i < argv.length; i += 1) {
|
||||||
|
if (argv[i] === arg) {
|
||||||
|
if (fn) {
|
||||||
|
return fn(argv[i+1]);
|
||||||
|
}
|
||||||
|
return true; // Return true if the argument was found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeTiles(filename, tiles) {
|
||||||
|
const tileSource = png2iigs.buildMerlinCodeForTiles(tiles);
|
||||||
|
fs.writeFileSync(filename, tileSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command line arguments
|
||||||
|
*
|
||||||
|
* --output-dir : sets the output folder to write all assets into
|
||||||
|
*/
|
||||||
async function main(argv) {
|
async function main(argv) {
|
||||||
// Read in the JSON data
|
// Read in the JSON data
|
||||||
const doc = JSON.parse(await fs.readFile(argv[0]));
|
const fullpath = path.resolve(argv[0]);
|
||||||
|
const workdir = path.dirname(fullpath);
|
||||||
|
|
||||||
|
const outdir = getArg(argv, '--output-dir', x => x, workdir);
|
||||||
|
|
||||||
|
console.log(`Reading Tiled JSON file from ${fullpath}`);
|
||||||
|
const raw = fs.readFileSync(fullpath);
|
||||||
|
console.log(`Parsing JSON file...`);
|
||||||
|
const doc = JSON.parse(raw);
|
||||||
|
|
||||||
// Make sure it's a map format we can handle
|
// Make sure it's a map format we can handle
|
||||||
if (doc.infinite) {
|
if (doc.infinite) {
|
||||||
@ -53,16 +134,43 @@ async function main(argv) {
|
|||||||
// Sort the tile layers by ID. The lower ID is considered to be the "front" layer
|
// Sort the tile layers by ID. The lower ID is considered to be the "front" layer
|
||||||
tileLayers.sort((first, second) => first.id <= second.id);
|
tileLayers.sort((first, second) => first.id <= second.id);
|
||||||
|
|
||||||
// Ok, looks good. Write out the source code
|
// Load up any/all tilesets
|
||||||
emitHeader();
|
const tileSets = await Promise.all(doc.tilesets.map(tileset => loadTileset(workdir, tileset)));
|
||||||
emitBG0Layer(tileLayers[0]);
|
|
||||||
|
for (const record of tileSets) {
|
||||||
|
console.log(`Importing tileset "${record.tileset.name}"`);
|
||||||
|
const tiles = await readTileSet(workdir, record.tileset);
|
||||||
|
|
||||||
|
const outputFilename = path.resolve(path.join(outdir, record.tileset.name + '.s'));
|
||||||
|
console.log(`Writing tiles to ${outputFilename}`);
|
||||||
|
writeTiles(outputFilename, tiles);
|
||||||
|
console.log(`Writing complete`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ok, looks good. Write out the source code for the layers
|
||||||
|
console.log('Generating data for front layer (BG0): ' + tileLayers[0].name);
|
||||||
|
const header = emitHeader();
|
||||||
|
const bg0 = emitBG0Layer(tileLayers[0]);
|
||||||
|
|
||||||
|
const bg0OutputFilename = path.resolve(path.join(outdir, tileLayers[0].name + '.s'));
|
||||||
|
console.log(`Writing BG0 data to ${bg0OutputFilename}`);
|
||||||
|
fs.writeFileSync(bg0OutputFilename, header + '\n' + bg0);
|
||||||
|
console.log(`Writing complete`);
|
||||||
|
|
||||||
if (tileLayers.length > 1) {
|
if (tileLayers.length > 1) {
|
||||||
emitBG1Layer(tileLayers[1]);
|
console.log('Generating data for front layer (BG0): ' + tileLayers[1].name);
|
||||||
|
const bg1 = emitBG1Layer(tileLayers[1]);
|
||||||
|
const bg1OutputFilename = path.resolve(path.join(outdir, tileLayers[1].name + '.s'));
|
||||||
|
console.log(`Writing BG1 data to ${bg1OutputFilename}`);
|
||||||
|
fs.writeFileSync(bg1OutputFilename, header + '\n' + bg1);
|
||||||
|
console.log(`Writing complete`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function emitBG1Layer(layer) {
|
function emitBG1Layer(layer) {
|
||||||
const label = layer.name.split(' ').join('_');
|
const sb = new StringBuilder();
|
||||||
|
|
||||||
|
const label = layer.name.split(' ').join('_').split('.').join('_');
|
||||||
const initCode = `
|
const initCode = `
|
||||||
BG1SetUp
|
BG1SetUp
|
||||||
lda #${layer.width}
|
lda #${layer.width}
|
||||||
@ -75,12 +183,17 @@ BG1SetUp
|
|||||||
sta BG1TileMapPtr+2
|
sta BG1TileMapPtr+2
|
||||||
rts
|
rts
|
||||||
`;
|
`;
|
||||||
console.log(initCode);
|
sb.appendLine(initCode);
|
||||||
console.log(`${label}`);
|
sb.appendLine(`${label}`);
|
||||||
|
emitLayerData(sb, layer);
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
function emitBG0Layer(layer) {
|
function emitBG0Layer(layer) {
|
||||||
const label = layer.name.split(' ').join('_');
|
const sb = new StringBuilder();
|
||||||
|
|
||||||
|
const label = layer.name.split(' ').join('_').split('.').join('_');
|
||||||
const initCode = `
|
const initCode = `
|
||||||
BG0SetUp
|
BG0SetUp
|
||||||
lda #${layer.width}
|
lda #${layer.width}
|
||||||
@ -93,9 +206,14 @@ BG0SetUp
|
|||||||
sta TileMapPtr+2
|
sta TileMapPtr+2
|
||||||
rts
|
rts
|
||||||
`;
|
`;
|
||||||
console.log(initCode);
|
sb.appendLine(initCode);
|
||||||
console.log(`${label}`);
|
sb.appendLine(`${label}`);
|
||||||
|
emitLayerData(sb, layer);
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitLayerData(sb, layer) {
|
||||||
// Print out the data in groups of N
|
// Print out the data in groups of N
|
||||||
const N = 16;
|
const N = 16;
|
||||||
const chunks = [];
|
const chunks = [];
|
||||||
@ -106,6 +224,8 @@ BG0SetUp
|
|||||||
// Tiled starts numbering its tiles at 1. This is OK since Tile 0 is reserved in
|
// Tiled starts numbering its tiles at 1. This is OK since Tile 0 is reserved in
|
||||||
// GTE, also
|
// GTE, also
|
||||||
for (const chunk of chunks) {
|
for (const chunk of chunks) {
|
||||||
console.log(' dw ' + chunk.join(','));
|
sb.appendLine(' dw ' + chunk.join(','));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return sb;
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user