You are browsing as a guest. Sign up (or log in) to start making projects!

2h 41m 16s logged

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!

0
6

Comments 0

No comments yet. Be the first!