Cauldron2D.Atlas (Cauldron2D v0.1.3)

Copy Markdown View Source

A named catalogue of tiles.

Each entry holds a sprite — or a run of frames shown in turn — a glyph and a colour, which are what the pixel renderer and the text renderer respectively draw.

Cauldron2D.Atlas.new(:my_game, tile: 16, void: {6, 7, 11})
|> Cauldron2D.Atlas.put(:floor, floor_raster(), glyph: "· ", color: {60, 66, 88})
|> Cauldron2D.Atlas.put(:player, player_raster(), glyph: "@ ", color: {245, 235, 200})
|> Cauldron2D.Atlas.put(:torch, [flame_1(), flame_2(), flame_3()], fps: 8, glyph: "i ")
|> Cauldron2D.Atlas.tint(:dim, fn {r, g, b, a} -> {div(r, 3), div(g, 3), div(b, 3), a} end)
|> Cauldron2D.Atlas.install()

Or from a directory of pictures:

{:ok, atlas} = Cauldron2D.Atlas.load(:my_game, "priv/art", tile: 16, arts: [floor: [glyph: "· "]])
Cauldron2D.Atlas.install(atlas)

Rules a caller must respect:

  • Every tile in one atlas is the same pixel size. put/4 raises otherwise.
  • install/1 must be called before any renderer refers to the atlas by name. It publishes the atlas to :persistent_term and creates the ETS table named by cache/1.
  • Functions taking an atlas accept either the name or the struct; fetch/1 resolves both.

A tint is a named function over RGBA. It is applied when a tile is composited, and the result is cached under the tint's name.

An animated art has frames and an fps; frame_at/3 is the frame showing at a time in milliseconds, and sprite is its first frame. Every reader that does not take a time draws the first frame.

subscribe/1 delivers {:cauldron_atlas, name, :changed} to the calling process each time a different atlas is installed under name, including the first.

Summary

Functions

Whether art has more than one frame.

Every art name in the atlas.

The name of the ETS table holding composited tiles for the atlas name.

The entry/0 stored under art.

The atlas struct for name, or the struct itself when given one.

The sprite of art showing at at_ms.

Which of art's frames shows at at_ms; 0 for an art with one frame.

Publish the atlas to :persistent_term and create its composited-tile cache table.

Whether an atlas has been installed under this name.

An atlas from every .png and .pic file in dir, each art named by its file.

Start an empty atlas under name.

Add the tile art, drawn from raster — or from a list of rasters shown in turn — replacing any entry already under that name.

Receive {:cauldron_atlas, name, :changed} in the calling process whenever a different atlas is installed under name.

Register fun, a function from an RGBA tuple to an RGBA tuple, under name.

Stop receiving changes to name.

Types

art()

@type art() :: term()

entry()

@type entry() :: %{
  sprite: FrenchCurve.Sprite.t(),
  frames: [FrenchCurve.Sprite.t()],
  fps: pos_integer(),
  glyph: String.t(),
  color: rgb()
}

name()

@type name() :: atom()

rgb()

@type rgb() :: {0..255, 0..255, 0..255}

rgba()

@type rgba() :: {0..255, 0..255, 0..255, 0..255}

t()

@type t() :: %Cauldron2D.Atlas{
  entries: %{required(art()) => entry()},
  name: name(),
  tile: pos_integer(),
  tints: %{required(tint()) => (rgba() -> rgba())},
  void: rgb()
}

tint()

@type tint() :: atom()

Functions

animated?(atlas, art)

@spec animated?(t() | name(), art()) :: boolean()

Whether art has more than one frame.

arts(atlas)

@spec arts(t()) :: [art()]

Every art name in the atlas.

cache(name)

@spec cache(name()) :: atom()

The name of the ETS table holding composited tiles for the atlas name.

The table exists only after install/1.

entry(atlas, art)

@spec entry(t(), art()) :: entry()

The entry/0 stored under art.

Raises ArgumentError when the atlas has no such art.

fetch(atlas)

@spec fetch(name() | t()) :: t()

The atlas struct for name, or the struct itself when given one.

Raises ArgumentError when nothing is installed under name.

frame_at(atlas, art, at_ms)

@spec frame_at(t() | name(), art(), integer()) :: FrenchCurve.Sprite.t()

The sprite of art showing at at_ms.

frame_index(atlas, art, at_ms)

@spec frame_index(t() | name(), art(), integer()) :: non_neg_integer()

Which of art's frames shows at at_ms; 0 for an art with one frame.

install(atlas)

@spec install(t()) :: name()

Publish the atlas to :persistent_term and create its composited-tile cache table.

Idempotent, and safe to call again with a changed atlas. Installing the same atlas twice keeps every tile already composited. Installing a different one under the same name empties the cache first, so art or a tint that has been redefined is composited again rather than served as it used to look, and tells every subscriber of the name.

The table itself is created once, owned by Cauldron2D.Atlas.Tables rather than the caller, and kept, so a reference taken from cache/1 stays valid across re-installs and after the installing process has gone.

Returns the atlas name.

installed?(name)

@spec installed?(name()) :: boolean()

Whether an atlas has been installed under this name.

load(name, dir, opts)

@spec load(name(), Path.t(), keyword()) :: {:ok, t()} | {:error, term()}

An atlas from every .png and .pic file in dir, each art named by its file.

A picture exactly one tile square is that art. One wider than a tile, in whole tiles, is a strip of frames left to right. A .pic smaller than a tile is centred in one. Any other size is {:error, {:art, file, :size}}; a file that will not read is {:error, {:art, file, reason}}.

An atlas.txt in the directory (Cauldron2D.Atlas.Manifest) gives arts their glyph, colour and rate; :arts lays over it. The glyph of an art named nowhere is the first two characters of its name in capitals, and its colour the mean of its first frame's opaque pixels. An art the manifest derives from another (Linocut.Derive) is built after the pictures, from a .pic in the directory or another derived art; one that cannot be made is {:error, {:art, name, message}}.

Options

  • :tile — as new/2, required
  • :void — as new/2
  • :arts[art: [glyph: "..", color: {r, g, b}, fps: n]], per-art options over the defaults. Default []
  • :fps — the rate of every strip not given one in :arts. Default 8

new(name, opts)

@spec new(name(), keyword()) :: t()

Start an empty atlas under name.

Options

  • :tile — the pixel width and height of every tile, required
  • :void — the colour of a cell with nothing in it, default {0, 0, 0}

Raises ArgumentError when :tile is missing or is not a positive integer.

put(atlas, art, raster, opts \\ [])

@spec put(t(), art(), FrenchCurve.Raster.t() | [FrenchCurve.Raster.t()], keyword()) ::
  t()

Add the tile art, drawn from raster — or from a list of rasters shown in turn — replacing any entry already under that name.

Every raster must be exactly the atlas's tile size in both dimensions; put/4 raises ArgumentError otherwise, and on an empty list.

Options

  • :glyph — what a text renderer draws for this tile, two columns wide because a tile occupies two terminal cells across. Default "??"
  • :color — the glyph's {r, g, b} colour before tinting, default {255, 0, 255}
  • :fps — frames a second when raster is a list. Default 8

subscribe(name)

@spec subscribe(name()) :: :ok

Receive {:cauldron_atlas, name, :changed} in the calling process whenever a different atlas is installed under name.

tint(atlas, name, fun)

@spec tint(t(), tint(), (rgba() -> rgba())) :: t()

Register fun, a function from an RGBA tuple to an RGBA tuple, under name.

unsubscribe(name)

@spec unsubscribe(name()) :: :ok

Stop receiving changes to name.