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
Last modified: 202401040446