Sprites (uxn)

Sprites in uxn are 8 pixels square and stored either as 1- or 2-bit per pixel (1bpp/2bpp), for monochrome or 4-color sprites respectively. Usually the sprite data is stored after all of the main program's code, to ensure we don't run what should be raw hexadecimal data.

1bpp

We can store each 8 pixel row as a byte, with each bit representing whether the pixel should be filled with the foreground (1) or background (0) color, similar to how PGM/PBM files in raytracing are represented.

To draw a character, we can represent it first in raw ASCII:

00111100
01111110
01011010
01111111
00011011
00111100
01011010
00011000

Converting each of these lines into hexadecimal bytes from top to bottom and placing them one after the other results in the following values: 3c 7e 5a 7f 1b 3c 5a 18. To store this as a sprite in uxn, we assign a label to it and put them in as raw values:

@character 3c7e 5a7f 1b3c 5a18

2bpp

A 2bpp sprite contains four possible values using the two bits instead of two: 0, 1, 2, 3. The way this is stored is not like a PGM file.

Say we have a square sprite:

00000001
03333311
03333211
03332211
03322211
03222211
01111111
11111111

Instead of neatly being able to convert each line into a hexadecimal value, we have to do create our 2bpp sprite as follows:

For each pixel, convert them into a 2-bit number.

(00) (00) (00) (00) (00) (00) (00) (01)
(00) (11) (11) (11) (11) (11) (01) (01)
etc.

Separate each of these pixels into high bits and low bits. They should end up looking like standard 1bpp sprites.

00000000     00000001
01111100     01111111
etc.

Convert each of these 1bpp sprites into hexadecimal like we did above, and then store them in memory with the low bit sprite first, followed by the high bit.

@square  017f 7b73 6343 7fff   007c 7c7c 7c7c 0000

Drawing To Screen

Then to draw it on the screen, we send the address found at our new label to the screen device by using the literal address rune (;):

;character .Screen/addr DEO2

Sprite Nibbles

The byte sent when telling the screen to actually draw the sprite will contain information on how to manipulate the sprite, as well as how to draw the information contained within the sprite.

The high nibble determines what mode we are in, whether we write to the background or foreground, and if we should flip our sprite on the x- or y-axis. The low nibble determines which of the four system colors are used to draw our sprite, with each table column representing which system color will be painted on which pixel value.

High nibble:

bit flag 0 1
7 mode 1bpp 2bpp
6 layer bg fg
5 flip-y no yes
4 flip-x no yes

1bpp Low Nibble

value 1 0
0 clear clear
1 1 0
2 2 0
3 3 0
4 0 1
5 1 none
6 2 1
7 3 1
8 0 2
9 1 2
a 2 none
b 3 2
c 0 3
d 1 3
e 2 3
f 3 none

0 will clear the tile at a given X/Y coordinate, and the various none combinations will draw only the 1 pixels and leave what already exists at the 0 pixels alone.

2bpp Low Nibble

value 0 1 2 3
1 0 1 2 3
2 0 2 3 1
3 0 3 1 2
4 1 0 1 2
5 none 1 2 3
6 1 2 3 1
7 1 3 1 2
8 2 0 1 2
9 2 1 2 3
a none 2 3 1
b 2 3 1 2
c 3 0 1 2
d 3 1 2 3
e 3 2 3 1
f none 3 1 2

Designing Sprites

Sprites can be designed using uxn's nasu[1], which allows you to export a chr file. Once you have a chr file, you can use the Unix tool hexdump to get the hex representation of the sprite you created.

hexdump -C file.chr

References

  1. https://100r.co/site/nasu.html
  2. Compudanza's uxn tutorial

Last modified: 202401040446