graphics

This module allows working with “graphic” assets, which are spritesheets converted to the GBA’s pixel format with an accompanying palette and metadata, stored in ROM.


Tutorial

To add a new graphic asset to your game:

  1. Export a spritesheet as a vertical strip.

    For example, this is a spritesheet for a 32x32 graphic with 8 frames:

    _images/dog.png

    Save it to graphics/dog.png.

  2. Add a line to your project’s graphics.nims file:

    # ...
    
    graphic "dog.png", s32x32    # A handle called gfxDog will be created
    
  3. Import natu/graphics in your game and start using the asset:

    import natu/[video, graphics]
    
    # Init
    # ----
    
    var dog = initObj(
      pos = vec2i(100, 50),
      tileId = allocObjTiles(gfxDog),  # Allocate tiles for a single frame of animation.
      palId = acquireObjPal(gfxDog),   # Obtain palette.
      size = gfxDog.size,              # Set to correct size.
    )
    
    # During vblank
    # -------------
    
    # Copy a frame of animation into VRAM.
    copyFrame(addr objTileMem[dog.tileId], gfxDog, frame = 0)
    
    # Copy the object into OAM.
    objMem[0] = dog
    
    # Clean up
    # --------
    
    freeObjTiles(obj.tileId)
    releaseObjPal(gfxDog)
    

Configuration

The graphics.nims config file is used to add new graphics to your project. This is a nimscript file executed in a local scope in your project’s config.nims before compiling the game.

The following operations are available:

proc graphic(name: string, size: ObjSize, bpp = 4, strictPal = false)

Specify an image to be added to the graphics list, converted and embedded in the ROM.

If the image is an indexed PNG then the palette will be preserved exactly, except duplicate colours will be removed (use strictPal=true to avoid this behaviour). Otherwise, if the image is a true-color PNG then the palette will be derived from the image in the order the colours appear.

Name:

Path to a .png file, relative to the current working directory.

Size:

Specifies the dimensions that sprites using this graphic should have.

Note

The provided spritesheet must be a vertical strip. i.e. its width equal to the given width, and its height a multiple of the given height.

Bpp:

The desired bits-per-pixel that the graphic should be converted to.

Possible values are:

  • 2 – The image can have up to 4 colours (including transparent). This may be handy if you’re tight on ROM space, but note that the GBA hardware doesn’t actually support 2bpp graphics. You could use BitUnPack to load the data into VRAM.

  • 4 (default) – The image can have up to 16 colours (including transparent).

  • 8 – The image can have up to 256 colors (including transparent).

StrictPal:

For use with indexed PNGs. Any duplicate colours in the palette will not be reduced into a single index. If the graphic is part of a sharePal block, then any colours which already exist in the shared palette at this point must appear in the same order in this image.

template sharePal(body: untyped)

This can be used to define a group of graphics which all share the same palette data.

Example:

cd "graphics"

sharePal:
  # These two graphics use the same palette:
  graphic "dog.png", s32x32
  graphic "hamster.png", s16x16

# But this one has its own palette.
graphic "cat.png", s32x32

Types

type Graphic = enum

Enum type representing spritesheets in ROM.

This type is generated based on the contents of your project’s graphics.nims file. Each image gets a corresponding enum value based on its filename, for example, dog.png becomes gfxDog.

For each item, the following read-only properties are available:

template width(g: Graphic): int

The width of the graphic in pixels.

template height(g: Graphic): int

The height of the graphic in pixels.

template size(g: Graphic): ObjSize

The size value for the graphic, as required by hardware sprites.

For example a 16x32 sprite will have a size value of s16x32.

template bpp(g: Graphic): int

The number of bits-per-pixel used by the sprite’s gfx data.

This will be either 2, 4 or 8.

template frameTiles(g: Graphic): int

How many 4bpp tiles does a single frame of animation occupy in VRAM? Note, 8bpp graphics take up twice as many tiles.

template allTiles(g: Graphic): int

How many 4bpp tiles does the entire spritesheet occupy in VRAM? Note, 8bpp graphics take up twice as many tiles.

template numFrames(g: Graphic): int

How many frames exist in the sprite sheet?

Tip

Since most Graphic metadata is known at compile-time and accessed via templates, it’s slightly more efficient to work with constant graphics rather than variables.

For example, this:

const g = gfxDog
acquireObjPal(g)

is preferable to:

var g = gfxDog
acquireObjPal(g)

The latter is of course necessary if you want to change graphics at runtime, or make a general-purpose type which can display any graphic.

Loading Data Directly

template copyPal(dest: var Palette, g: Graphic)

Copy palette data from a graphic into some destination, 4bpp version.

template copyPal(dest: ptr Color | ptr Palette, g: Graphic)

Copy palette data from a graphic into some destination (unsafe version, works with 8bpp)

template copyFrame(dest: ptr Tile4, g: Graphic, frame: int)

Copy a single frame of animation to a location in Object VRAM

template copyAllFrames(dest: ptr Tile4 | ptr Tile8, g: Graphic)

Copy all frames of animation to a location in Object VRAM

Utilities

template onscreen(g: Graphic, pos: Vec2i): bool

Check if a graphic would be onscreen when drawn at a given location

func onscreen(r: Rect): bool

Check if a rectangle is on-screen

Palette Management

template acquireObjPal(g: Graphic): int

Increase the reference count for a graphic’s palette data.

If the count was zero, a slot in Obj PAL RAM will be allocated using allocObjPal, and the palette data will be copied into the corresponding slot in objPalBuf.

Returns which slot in Obj PAL RAM was used, but you don’t have to use the returned value, as you can always check it later with getPalId.

template releaseObjPal(g: Graphic)

Decrease the reference count for a graphic’s palette data.

If the count reaches zero, the palette will be freed.

template getPalId(g: Graphic): int

Get the current slot in Obj PAL RAM used by a graphic.

template loadPal(g: Graphic)

Load palette data from a graphic into the correct slot in the Obj PAL RAM buffer.

Obj PAL RAM Allocator

These procedures let you allocate & free palettes for sprites.

Note

If you are trying to load a palette from a Graphic you should use acquireObjPal and releaseObjPal instead of these.

proc allocObjPal(): int

Allocate a 4bpp palette in Obj PAL RAM.

proc freeObjPal(i: int)

Deallocate a 4bpp palette in Obj PAL RAM.

Palette Buffers

These are targeted by gfx and bg operations such as acquireObjPal and bg.loadPal, or you can write directly to them.

Be sure to call flushPals during VBlank to update the real palettes. Or you can use clrFadeFast from the video module to blend in a certain colour while copying.

template bgPalBuf(): array[numBgPals, Palette]

Access the BG PAL RAM buffer as a table of 16-color palettes.

This is useful when working when 4bpp backgrounds.

template objPalBuf(): array[numObjPals, Palette]

Access the OBJ PAL RAM buffer as a table of 16-color palettes.

This is useful when working when 4bpp sprites.

template bgColorBuf(): array[numBgColors, Color]

Access the BG PAL RAM buffer as a single array of colors.

This is useful when working with 8bpp backgrounds, or display mode 4.

template objColorBuf(): array[numObjColors, Color]

Access the OBJ PAL RAM buffer as a single array of colors.

This is useful when working with 8bpp sprites.

proc flushPals()

Copy the palette buffers into PAL RAM.

This should be called every frame during VBlank.

Tile Management

template allocObjTiles(g: Graphic): int

Allocate tiles for 1 frame of animation with the ideal snap amount

proc allocObjTiles(tiles: range[1..1024], snap: range[0..9] = 0): int

Allocate some tiles in Obj VRAM

Parameters:

tiles
How many 4bpp tiles to allocate.
If you want 8bpp tiles, you have to ask for twice as many.
snap

Quantise the allocation to the nearest 2^snap tiles.

proc freeObjTiles(tileId: int)

Free tiles from Obj VRAM