474 lines
16 KiB
Text
474 lines
16 KiB
Text
== Tiles and Text
|
|
|
|
Declared in `<joey/tile.h>`.
|
|
|
|
A tile is not a separate object. It is the 8x8 region of a surface at block
|
|
coordinates `(bx, by)`, where `bx` runs 0..39 and `by` runs 0..24 -- 40x25
|
|
blocks across a 320x200 surface. The tile API moves 32-byte blocks between
|
|
surfaces or fills them.
|
|
|
|
The consequence is that everything else works on tiles too. You can paint tile
|
|
content with `jlDrawPixel` and `jlFillRect`, generate it procedurally, or edit
|
|
it at run time, because a "tileset" is just a surface you agree to treat as
|
|
one. Fonts, terrain sets, and spritesheets are all the same thing.
|
|
|
|
=== Constants and Types
|
|
|
|
[cols="3,1,4", options="header"]
|
|
|===
|
|
|Name |Value |Meaning
|
|
|
|
|`JL_TILE_PIXELS_PER_SIDE` |8 |Tile edge in pixels.
|
|
|`JL_TILE_BYTES_PER_ROW` |4 |Bytes per tile row (8 pixels at 4bpp).
|
|
|`JL_TILE_BYTES` |32 |Bytes in one tile.
|
|
|`JL_TILE_BLOCKS_PER_ROW` |40 |Tile columns on a surface.
|
|
|`JL_TILE_BLOCKS_PER_COL` |25 |Tile rows on a surface.
|
|
|`JL_TILE_NO_GLYPH` |0xFFFF |`jlDrawText` map entry meaning "skip this character".
|
|
|`JL_TILE_MAP_SKIP` |0xFF |`jlTileMapPaste` entry meaning "leave this cell alone"; in a tile layer's map, color 0.
|
|
|`JL_TILE_LAYER_MAX_SIDE` |4096 |Largest tile layer map side, in tiles.
|
|
|===
|
|
|
|
[source,c]
|
|
----
|
|
typedef struct jlTileT {
|
|
uint8_t pixels[JL_TILE_BYTES];
|
|
} jlTileT;
|
|
----
|
|
|
|
A stack-allocatable 32-byte tile snapshot, so save-under style work does not
|
|
need a whole scratch surface.
|
|
|
|
[IMPORTANT]
|
|
====
|
|
A `jlTileT` is *target-native*: plane-major on the Amiga and X68000, row by row
|
|
with each row's four plane bytes together on the ST, and chunky on the others.
|
|
Treat it as opaque. In particular, do not feed a tile from `jlTileSnap` or
|
|
`jlTileBankLoad` to `jlTilePasteMono`, which wants a chunky silhouette -- on the
|
|
Amiga, ST, and X68000 that is a format mismatch and draws the wrong shape. Use
|
|
`jlTilePasteGlyph` for 1-bit glyph data instead.
|
|
====
|
|
|
|
=== jlTileCopy
|
|
|
|
[source,c]
|
|
----
|
|
void jlTileCopy(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy,
|
|
const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy);
|
|
----
|
|
|
|
Copies one 8x8 block between surfaces, opaquely.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`dst` |`jlSurfaceT *` |Destination surface.
|
|
|`dstBx`, `dstBy` |`uint8_t` |Destination block coordinates.
|
|
|`src` |`const jlSurfaceT *` |Source surface. May be the same as `dst`.
|
|
|`srcBx`, `srcBy` |`uint8_t` |Source block coordinates.
|
|
|===
|
|
|
|
*Returns:* nothing. Out-of-range block coordinates on either side are silent
|
|
no-ops.
|
|
|
|
=== jlTileCopyMasked
|
|
|
|
[source,c]
|
|
----
|
|
void jlTileCopyMasked(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy,
|
|
const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy,
|
|
uint8_t transparentIndex);
|
|
----
|
|
|
|
As `jlTileCopy`, but source pixels equal to `transparentIndex` are skipped and
|
|
the destination keeps its original value. This is the call for fonts and for
|
|
any tileset with see-through pixels.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`dst` |`jlSurfaceT *` |Destination surface.
|
|
|`dstBx`, `dstBy` |`uint8_t` |Destination block coordinates.
|
|
|`src` |`const jlSurfaceT *` |Source surface.
|
|
|`srcBx`, `srcBy` |`uint8_t` |Source block coordinates.
|
|
|`transparentIndex` |`uint8_t` |Source color index treated as transparent.
|
|
|===
|
|
|
|
*Returns:* nothing.
|
|
|
|
=== jlTileFill
|
|
|
|
[source,c]
|
|
----
|
|
void jlTileFill(jlSurfaceT *s, uint8_t bx, uint8_t by, uint8_t colorIndex);
|
|
----
|
|
|
|
Fills one block with a solid color. Equivalent to
|
|
`jlFillRect(s, bx*8, by*8, 8, 8, colorIndex)` but skips the clipping math,
|
|
since block coordinates are already known to be in range.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`s` |`jlSurfaceT *` |Target surface.
|
|
|`bx`, `by` |`uint8_t` |Block coordinates.
|
|
|`colorIndex` |`uint8_t` |Color index 0..15.
|
|
|===
|
|
|
|
*Returns:* nothing.
|
|
|
|
=== jlTileBankLoad
|
|
|
|
[source,c]
|
|
----
|
|
uint16_t jlTileBankLoad(const char *path, jlTileT *outTiles, uint16_t maxTiles,
|
|
bool *outValid, uint16_t *outPalette);
|
|
----
|
|
|
|
Loads tiles from a baked `.tbk` file. The file's per-target byte is checked
|
|
against the build platform and mismatches are refused.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`path` |`const char *` |File path.
|
|
|`outTiles` |`jlTileT *` |Receives the tiles. Caller-owned.
|
|
|`maxTiles` |`uint16_t` |Capacity of `outTiles`.
|
|
|`outValid` |`bool *` |Optional, may be `NULL`. One flag per slot up to
|
|
`maxTiles`: `true` for each loaded tile, `false` for the rest (all of them when
|
|
the file is refused), so loaded tiles can be told from leftovers in a
|
|
fixed-size array.
|
|
|`outPalette` |`uint16_t *` |Optional, may be `NULL`. Receives the embedded
|
|
16-entry `$0RGB` palette if the file has one.
|
|
|===
|
|
|
|
*Returns:* the number of tiles written. Returns 0 if the file is missing, the
|
|
magic or target byte is wrong, or `maxTiles` is 0.
|
|
|
|
=== jlTileBankLoadToSurface
|
|
|
|
[source,c]
|
|
----
|
|
uint16_t jlTileBankLoadToSurface(const char *path, jlSurfaceT *dst,
|
|
uint16_t *outPalette);
|
|
----
|
|
|
|
Streams a baked `.tbk` straight onto a surface's block grid: tile `i` lands at
|
|
block `(i % 40, i / 40)`, the row-major layout the bake tools author against.
|
|
|
|
Use this instead of `jlTileBankLoad` for anything large. A 1000-tile font sheet
|
|
is 32000 bytes, which no IIgs program can hold in an array or in bank-0 BSS;
|
|
this needs only a small internal scratch buffer and one pass over the file.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`path` |`const char *` |File path.
|
|
|`dst` |`jlSurfaceT *` |Destination surface.
|
|
|`outPalette` |`uint16_t *` |Optional, may be `NULL`. As `jlTileBankLoad`.
|
|
|===
|
|
|
|
*Returns:* the number of tiles pasted. Blocks beyond the file's tile count keep
|
|
their previous content, and tiles past the surface's 1000 blocks are ignored.
|
|
|
|
=== jlTileSnap
|
|
|
|
[source,c]
|
|
----
|
|
void jlTileSnap(const jlSurfaceT *src, uint8_t bx, uint8_t by, jlTileT *out);
|
|
----
|
|
|
|
Captures one block into a caller-supplied `jlTileT`.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`src` |`const jlSurfaceT *` |Source surface.
|
|
|`bx`, `by` |`uint8_t` |Block coordinates.
|
|
|`out` |`jlTileT *` |Receives the 32-byte snapshot.
|
|
|===
|
|
|
|
*Returns:* nothing.
|
|
|
|
=== jlTilePaste
|
|
|
|
[source,c]
|
|
----
|
|
void jlTilePaste(jlSurfaceT *dst, uint8_t bx, uint8_t by, const jlTileT *in);
|
|
----
|
|
|
|
Pastes a `jlTileT` back onto a surface, always opaquely. Use
|
|
`jlTileCopyMasked` if you need transparency.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`dst` |`jlSurfaceT *` |Destination surface.
|
|
|`bx`, `by` |`uint8_t` |Block coordinates.
|
|
|`in` |`const jlTileT *` |Tile to paste.
|
|
|===
|
|
|
|
*Returns:* nothing.
|
|
|
|
=== jlTilePasteMono
|
|
|
|
[source,c]
|
|
----
|
|
void jlTilePasteMono(jlSurfaceT *dst, uint8_t bx, uint8_t by,
|
|
const jlTileT *in, uint8_t fgColor, uint8_t bgColor);
|
|
----
|
|
|
|
Pastes a tile as a silhouette: any non-zero source pixel draws `fgColor`, zero
|
|
pixels draw `bgColor`. Useful when the bitmap is a fixed glyph but the color
|
|
comes from a separate map, as in C64-style color RAM. Opaque.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`dst` |`jlSurfaceT *` |Destination surface.
|
|
|`bx`, `by` |`uint8_t` |Block coordinates.
|
|
|`in` |`const jlTileT *` |Source tile, read as a silhouette.
|
|
|`fgColor` |`uint8_t` |Color for non-zero pixels. Masked to 4 bits.
|
|
|`bgColor` |`uint8_t` |Color for zero pixels. Masked to 4 bits.
|
|
|===
|
|
|
|
*Returns:* nothing.
|
|
|
|
[WARNING]
|
|
====
|
|
This call wants a *chunky* silhouette, but a `jlTileT` from `jlTileSnap` or
|
|
`jlTileBankLoad` is target-native. Feeding one to this function on the Amiga,
|
|
ST, or X68000 is a format mismatch. For glyphs, use `jlTilePasteGlyph`, which
|
|
takes unambiguous 1-bit input.
|
|
====
|
|
|
|
=== jlTilePasteGlyph
|
|
|
|
[source,c]
|
|
----
|
|
void jlTilePasteGlyph(jlSurfaceT *dst, uint8_t bx, uint8_t by,
|
|
const uint8_t *bits, uint8_t fgColor, uint8_t bgColor);
|
|
----
|
|
|
|
Pastes an 8x8 one-bit-per-pixel glyph: `bits[row]` is that row, most
|
|
significant bit leftmost. A set bit draws `fgColor`, a clear bit `bgColor`.
|
|
Opaque.
|
|
|
|
This is the shape a character generator already has -- a font ROM, a C64
|
|
charset, anything 1bpp -- so nothing needs expanding into a `jlTileT` first.
|
|
*Prefer this over `jlTilePasteMono` for glyphs.*
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`dst` |`jlSurfaceT *` |Destination surface.
|
|
|`bx`, `by` |`uint8_t` |Block coordinates.
|
|
|`bits` |`const uint8_t *` |8 bytes, one per glyph row, MSB leftmost.
|
|
|`fgColor` |`uint8_t` |Set-bit color. Masked to 4 bits.
|
|
|`bgColor` |`uint8_t` |Clear-bit color. Masked to 4 bits.
|
|
|===
|
|
|
|
*Returns:* nothing.
|
|
|
|
=== jlDrawText
|
|
|
|
[source,c]
|
|
----
|
|
void jlDrawText(jlSurfaceT *dst, uint8_t bx, uint8_t by,
|
|
const jlSurfaceT *fontSurface, const uint16_t *asciiMap,
|
|
const char *str);
|
|
----
|
|
|
|
Draws a NUL-terminated ASCII string using glyphs from a font surface. Glyph
|
|
color 0 is transparent, so the background shows through. The cursor wraps at
|
|
the right edge and truncates at the bottom. A start off the 40x25 grid uses up
|
|
characters without drawing them as the cursor moves on: a start column past
|
|
39 drops the first character and carries on at the start of the next row, and
|
|
a start row past 24 draws nothing.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`dst` |`jlSurfaceT *` |Destination surface.
|
|
|`bx`, `by` |`uint8_t` |Starting block coordinates.
|
|
|`fontSurface` |`const jlSurfaceT *` |Surface holding the glyphs.
|
|
|`asciiMap` |`const uint16_t *` |256 entries mapping ASCII code to glyph
|
|
position: low byte is source block X, high byte is source block Y. An entry of
|
|
`JL_TILE_NO_GLYPH`, or one off the font surface's 40x25 grid, skips that
|
|
character, advancing the cursor without drawing.
|
|
|`str` |`const char *` |NUL-terminated string.
|
|
|===
|
|
|
|
*Returns:* nothing.
|
|
|
|
=== jlTileMapPaste
|
|
|
|
[source,c]
|
|
----
|
|
void jlTileMapPaste(jlSurfaceT *dst, uint8_t bx, uint8_t by,
|
|
uint8_t wTiles, uint8_t hTiles,
|
|
const jlTileT *tiles, const uint8_t *map);
|
|
----
|
|
|
|
Pastes a whole rectangle of tiles in one call, so validation, dispatch, and the
|
|
dirty-rectangle union are paid once for the rectangle instead of once per tile.
|
|
For anything larger than a few tiles this is substantially faster than looping
|
|
over `jlTilePaste`.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`dst` |`jlSurfaceT *` |Destination surface.
|
|
|`bx`, `by` |`uint8_t` |Top-left block coordinates.
|
|
|`wTiles`, `hTiles` |`uint8_t` |Rectangle size in tiles. Zero is a no-op.
|
|
|`tiles` |`const jlTileT *` |The tileset.
|
|
|`map` |`const uint8_t *` |`wTiles * hTiles` tileset indices, row-major, so
|
|
`map[row * wTiles + col]`. `JL_TILE_MAP_SKIP` leaves that cell untouched.
|
|
|===
|
|
|
|
*Returns:* nothing. A rectangle whose edge crosses the surface boundary is
|
|
rejected as a no-op.
|
|
|
|
[WARNING]
|
|
====
|
|
Any map index other than `JL_TILE_MAP_SKIP` must be a valid tileset index. The
|
|
batch does not know the tileset length and does *not* range-check it -- an
|
|
out-of-range index reads past the end of your array.
|
|
====
|
|
|
|
=== Scrolling Tile Layers
|
|
|
|
A tile layer is a map of tile indices, up to `JL_TILE_LAYER_MAX_SIDE` tiles a
|
|
side, seen through a band of the stage: whole tile rows, the full width (rows
|
|
outside the band are yours, for a status bar or a score). `jlTileLayerDraw`
|
|
fills the band with the map as seen from a pixel origin, to the pixel both
|
|
ways, so moving the origin a little each frame scrolls it. The map wraps at its
|
|
edges: an origin past them, or a map smaller than the view, repeats it.
|
|
|
|
The layer keeps the tiles around the view drawn, so a draw only draws the edge
|
|
tiles the move uncovered (a move of a whole view or more draws them all), then
|
|
copies the band. A draw covers the band and never reads what was there, so a
|
|
scrolling frame is: draw the layer, draw the sprites with `jlSpriteDraw` (no
|
|
save-under, since the next draw covers them), present.
|
|
|
|
[source,c]
|
|
----
|
|
static jlTileT tiles[64]; // from jlTileBankLoad
|
|
static uint8_t map[256 * 25]; // 256 x 25 tile indices
|
|
jlSpriteT *ship; // from jlSpriteCreate, compiled
|
|
jlTileLayerT *layer;
|
|
uint16_t x = 0;
|
|
|
|
layer = jlTileLayerCreate(tiles, map, 256, 25, 0, JL_TILE_BLOCKS_PER_COL);
|
|
for (;;) {
|
|
jlTileLayerDraw(jlStageGet(), layer, x, 0);
|
|
jlSpriteDraw(jlStageGet(), ship, 40, 96);
|
|
jlStagePresent();
|
|
x++;
|
|
}
|
|
----
|
|
|
|
The layer keeps a pointer to your tileset and map rather than copying them, so
|
|
both must outlive it. Change a cell with `jlTileLayerSetTile`, not by writing
|
|
the map: the layer would not see the write until that cell scrolled out of the
|
|
view and back. A tile whose pixels you change in the tileset is likewise only
|
|
seen where a cell showing it is set again.
|
|
|
|
What a layer costs in memory is its view: about 1.3 KB per tile row of the band,
|
|
plus one, on the Amiga, ST, and X68000 (35 KB for a full-height band), twice that on the
|
|
others, which keep a second copy of the view shifted by a pixel so an odd
|
|
origin copies whole bytes.
|
|
|
|
[NOTE]
|
|
====
|
|
Every whole-band scroll redraws the band and presents it, so a scrolling game
|
|
runs at the rate of a full-screen present plus a full-band copy. On the
|
|
slowest machines JoeyLib targets that is a few frames a second (the IIgs about
|
|
5.8, the plain ST about 8, a 386SX-16 under DOS about 6.5 in the UBER
|
|
benchmark; the Amiga and STe copy the band with their blitters, about 20), so
|
|
plan scrolling games around a band no taller than the game needs.
|
|
====
|
|
|
|
=== jlTileLayerCreate
|
|
|
|
[source,c]
|
|
----
|
|
jlTileLayerT *jlTileLayerCreate(const jlTileT *tiles, uint8_t *map,
|
|
uint16_t mapWidth, uint16_t mapHeight,
|
|
uint8_t viewBy, uint8_t viewHeight);
|
|
----
|
|
|
|
Makes a layer over `map`. Nothing is drawn until `jlTileLayerDraw`.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`tiles` |`const jlTileT *` |The tileset. Kept, not copied.
|
|
|`map` |`uint8_t *` |`mapWidth * mapHeight` tileset indices, row-major, so
|
|
`map[row * mapWidth + col]`. `JL_TILE_MAP_SKIP` draws color 0. Kept, not
|
|
copied; `jlTileLayerSetTile` writes it.
|
|
|`mapWidth`, `mapHeight` |`uint16_t` |The map's size in tiles, 1 to
|
|
`JL_TILE_LAYER_MAX_SIDE`.
|
|
|`viewBy` |`uint8_t` |The band's first tile row on the stage.
|
|
|`viewHeight` |`uint8_t` |The band's height in tile rows, at least 1;
|
|
`viewBy + viewHeight` at most `JL_TILE_BLOCKS_PER_COL`.
|
|
|===
|
|
|
|
*Returns:* the layer, or `NULL` if an argument is `NULL` or out of range, or
|
|
memory runs out.
|
|
|
|
[WARNING]
|
|
====
|
|
Like `jlTileMapPaste`, a layer does not range-check map indices against the
|
|
tileset: any index other than `JL_TILE_MAP_SKIP` must be a valid one.
|
|
====
|
|
|
|
=== jlTileLayerDestroy
|
|
|
|
[source,c]
|
|
----
|
|
void jlTileLayerDestroy(jlTileLayerT *layer);
|
|
----
|
|
|
|
Frees a layer, not its tileset or map. `NULL` is a no-op.
|
|
|
|
=== jlTileLayerDraw
|
|
|
|
[source,c]
|
|
----
|
|
void jlTileLayerDraw(jlSurfaceT *dst, jlTileLayerT *layer,
|
|
uint16_t x, uint16_t y);
|
|
----
|
|
|
|
Fills the layer's band of `dst` with the map seen from pixel `(x, y)`: the
|
|
band's top-left pixel shows map pixel `(x, y)`, each taken modulo the map's
|
|
size in pixels. Any pixel origin, with no alignment.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`dst` |`jlSurfaceT *` |Destination surface, normally the stage.
|
|
|`layer` |`jlTileLayerT *` |The layer.
|
|
|`x`, `y` |`uint16_t` |The origin, in map pixels.
|
|
|===
|
|
|
|
*Returns:* nothing.
|
|
|
|
=== jlTileLayerSetTile
|
|
|
|
[source,c]
|
|
----
|
|
void jlTileLayerSetTile(jlTileLayerT *layer, uint16_t tx, uint16_t ty,
|
|
uint8_t tile);
|
|
----
|
|
|
|
Sets map cell `(tx, ty)` to `tile`. The next draw shows it.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`layer` |`jlTileLayerT *` |The layer.
|
|
|`tx`, `ty` |`uint16_t` |The cell, in tiles. Out-of-range cells are ignored.
|
|
|`tile` |`uint8_t` |The tileset index, or `JL_TILE_MAP_SKIP` for color 0.
|
|
|===
|
|
|
|
*Returns:* nothing.
|