Crack Open a Sprite
Hello everyone! This dvelopment took longer than expected, so let’s just dive right in!
County Rogues, Bring me lOAM
After I finished the window layer, it was time to deal with sprites. Where each sprite is on the screen, which tiles to use, and sprite-specific render attributes are stored in a section of memory called OAM. 40 sprites’ data can be stored in OAM, each 4 bytes: the x position, y position, which tile(s) to use, and the attributes of that sprite (height, when to render, etc.)
Unlike the background and window, sprites have a lot more moving parts. Here’s my code for rendering the sprites to the buffer.
fn render_sprites(&mut self) {
let lcdc = self.read_byte(0xff40);
if lcdc >> 1 & 0x1 == 0 {
return;
}
let mut indexes = (0..40).collect::<Vec<u16>>();
indexes.sort_unstable_by_key(|n| (self.read_byte(0xfe01 + n * 4) as i16 - 8, *n));
let indexes: Vec<&u16> = indexes.iter().rev().collect();
let height = if lcdc >> 2 & 0x1 == 0 { 8 } else { 16 };
for n in indexes {
let entry = 0xfe00 + n * 4;
let sprite_y = self.read_byte(entry) as i16 - 16;
let sprite_x = self.read_byte(entry + 0x1) as i16 - 8;
let tile_index = self.read_byte(entry + 0x2);
let attributes = self.read_byte(entry + 0x3);
let palette = if (attributes >> 4) & 0x1 == 0 {
0xff48
} else {
0xff49
};
for row in sprite_y.clamp(0, 144) as u8..(sprite_y + height).clamp(0, 144) as u8 {
for column in sprite_x.clamp(0, 160) as u8..(sprite_x + 8).clamp(0, 160) as u8 {
if (attributes >> 7) & 0x1 == 1 && self.bit_7_check(row, column) {
continue;
}
let row_in_sprite = if (attributes >> 6) & 0x1 == 0 {
(row as i16 - sprite_y) as usize
} else {
height as usize - (row as i16 - sprite_y) as usize - 1
};
let column_in_sprite = if (attributes >> 5) & 0x1 == 0 {
(column as i16 - sprite_x) as usize
} else {
7 - (column as i16 - sprite_x) as usize
};
let which_tile = if row_in_sprite < 8 { (0, 0) } else { (1, 8) };
let pixel = self.index_to_tile_unsigned(tile_index + which_tile.0)
[row_in_sprite - which_tile.1][column_in_sprite];
if pixel == TilePixelValue::Zero {
continue;
}
let value = self.palette_lookup(pixel, palette);
self.gpu.set_pixel(row, column, value);
}
}
}
}
fn bit_7_check(&self, row: u8, column: u8) -> bool {
if self.window_active_for_pixel(column, row) {
if self.window_pixel_value(column, row) == TilePixelValue::Zero {
return false;
}
return true;
}
if self.background_pixel_value(column, row) == TilePixelValue::Zero {
return false;
}
true
}
This also excludes any boilerplate for window and background that I had to reuse. The most annoying part was dealing with ordering indexes efficiently, because the x value of the sprite is used as the method for sprite precedence over layers. I didn’t realise that sort_unstable_by_key dealt with like tuples and you could use that for precedence and tiebreaking.
Anyway, that’s all folks! see y’all next time!
Comments 0
No comments yet. Be the first!
Sign in to join the conversation.