09/08/22
New Goal: I will draw a sprite from the sprite memory.
//so in this case, 0 = clear, 1 = red, 2 = green, 3 = blue
palette = [clear, red, green, blue]
//this tile is a red box with a clear centre
tile1 = [
1,1,1,1,1,1,1,1,
1,0,0,0,0,0,0,1,
1,0,0,0,0,0,0,1,
1,0,0,0,0,0,0,1,
1,0,0,0,0,0,0,1,
1,0,0,0,0,0,0,1,
1,0,0,0,0,0,0,1,
1,1,1,1,1,1,1,1,
]
//this tile is a green box with a blue line in it
tile2 = [
2,2,2,2,2,2,2,2,
2,0,0,0,0,0,0,2,
2,0,0,3,3,0,0,2,
2,0,0,3,3,0,0,2,
2,0,0,3,3,0,0,2,
2,0,0,3,3,0,0,2,
2,0,0,0,0,0,0,2,
2,2,2,2,2,2,2,2,
]
//show a red box at 10,20
//show a red box at 60,23
//show a green box with a blue line at 35,41
object1 = [10,20,tile1]
object2 = [60,23,tile1]
object3 = [35,41,tile2]
Working from the top down through the documentation though, before sprites (objects) were discussed it talked about backgrounds. In terms of drawing stuff they appear to work in much the same way except instead of specifying an x/y location, you provide what is known as a map, which is a grid of tiles that is rendered to the background layer. So using the previous definitions we could do the following.
//this map is two red boxes, then one green box with a blue line, then red boxes for the rest of the bg.
map = [
tile1, tile1, tile2, tile1, tile1, tile1, tile1, ....... tile1
]
And hey, that is what we get.
Which made me really pleased with myself, because i had to do a bunch of stuff to make this happen.
Bit Expl.
0-1 BG Priority (0-3, 0=Highest)
2-3 Character Base Block (0-3, in units of 16 KBytes) (=BG Tile Data)
4-5 Not used (must be zero) (except in NDS mode: MSBs of char base)
6 Mosaic (0=Disable, 1=Enable)
7 Colors/Palettes (0=16/16, 1=256/1)
8-12 Screen Base Block (0-31, in units of 2 KBytes) (=BG Map Data)
13 BG0/BG1: Not used (except in NDS mode: Ext Palette Slot for BG0/BG1)
13 BG2/BG3: Display Area Overflow (0=Transparent, 1=Wraparound)
14-15 Screen Size (0-3)
So 16 bits that controls a bunch of stuff depending on what you do with them. And they're not as nice as just any given bit toggles a single thing on or off; some are simple flags, some are values from 0-3, some are 5 bit index values, some are pdding that must not be touched on pain of death, etc. Normally this stuff is a bit of a pain to work with, but NOT with Zig. Consider how we represent the above in Zig code.
pub const BackgroundControl = struct {
const BGPriority = enum(u2) {
Low,
Medium,
High,
Highest,
};
const BaseBlock = enum(u2) { First, Second, Third, Fourth };
const Mosaic = enum(u1) { Enabled, Disabled };
const PaletteSize = enum(u1) { SixteenBySixteen, TwoFiftySix };
const Overflow = enum(u1) { Transparent, Wraparound };
const ScreenSize = enum(u2) { Small, Wide, Tall, Large };
pub const BGControl = packed struct {
priority: BGPriority = .Low,
character_block: BaseBlock = .First,
padding: u2 = 0,
mosaic: Mosaic = .Disabled,
palettesize: PaletteSize = .TwoFiftySix,
screen_block: u5,
padding_two: u2 = 0,
overflow: Overflow = .Transparent,
size: ScreenSize = .Small,
};
const BGControl0 = @ptrCast(*BGControl, Mem.BG0Control);
const BGControl1 = @ptrCast(*BGControl, Mem.BG1Control);
const BGControl2 = @ptrCast(*BGControl, Mem.BG2Control);
const BGControl3 = @ptrCast(*BGControl, Mem.BG3Control);
pub const WhichBackground = enum { Zero, One, Two, Three };
pub inline fn set(which: WhichBackground, to: BGControl) void {
switch (which) {
.Zero => {
BGControl0.* = to;
},
.One => {
BGControl1.* = to;
},
.Two => {
BGControl2.* = to;
},
.Three => {
BGControl3.* = to;
},
}
}
};
And this isn't even good Zig code, this is MY Zig code, i'm a beginner, but this sparks joy, because it can be read like the documentation, but it works like the binary representation with very little stuffing about!!!!! Zig gives us the lovely power to enumerate with descriptive names all of the various bit flags. Then we put it all together into a packed struct
which signals to zig to not mess with order and padding, just give it to us exactly how we define it. Then the process of sticking this behemoth into memory is disgustingly slick.
const BGC = @import("Screen.zig").BackgroundControl;
...
BGC.set(.Two, .{
.priority = .Highest,
.character_block = .First,
.palettesize = .TwoFiftySix,
.screen_block = 1,
.size = .Small,
});
And that's it. We get whatever sensible defaults we specified, we can read this clearly and understand what it is specifying, and we don't have to shift a single 0x1 or mask any 0xFF7F's or whatever. If we wanted to level the readability we could also force the user to specify all the values except for the padding to ensure they have to define them. BTW the above basically says set the second background layer to highest priority, use the first block of 16k memory for storing tiles, interpret the palette memory as 256 colours instead of 16 x 16 colours (8 bit colour), start reading the memory 1kb from the start of VRAM for our screen block (the map), and interpret the overall map size as small (as opposed to tall, wide, or large which have different layout interpretations).
pub const PaletteControl255 = packed struct {
clearColour: u16 = 0x0000,
palette: [255]u16,
};
And I wanted to be able to pass in the clear colour and then the array of palette colours seperately, and have it composed into the one thing and then stuck into memory. This ended up looking like the following.
const BGPalette = @ptrCast(*PaletteControl255, Mem.BGPalette);
pub fn setBGPalette(clearCol: u16, palette: []const u16) void {
var p: PaletteControl255 = .{
.palette = [_]u16{0} ** 255,
};
p.clearColour = clearCol;
var i: u32 = 0;
while (i < palette.len) : (i = i + 1) {
p.palette[i] = palette[i];
}
BGPalette.* = p;
}
And it gets called as follows
//palette is red, green, blue
const pal = [_]u16{ 0x001F, 0x0780, 0xF800 };
//the first item is the clear colour, which we are setting to 0x18C3
//so in memory the Palette will look like [0x18C3, 0x001F, 0x0780, 0xF800]
BGPalette.setBGPalette(0x18C3, &pal);
So basically palette is an array of zero values and we iterate over the passed in values to set the ones we want and that is great. It works but oh my did i have trouble finding out how i was meant to express this. Not the sort of trouble you have with the Rust compiler, but still trouble. The resourse I found that helped me get it was the Zig crash course which has a superb breakdown of types and arrays/slices of types and what they mean.
Ongoing Goal: I will draw a sprite from the sprite memory.