-
Hi I am trying to implement dialogue text boxes into my gba game. I want a style where the text is displayed character by character like Pokemon for example. In order to do this I first simply rendered the whole text box each frame, Unless there is something I am missing, and the current system can in fact do what I want shall I raise a PR for this? The rough implementation I have right now is: impl Font {
pub fn render_text<'a>(
&'a self,
tile_pos: Vector2D<u16>,
foreground_colour: u8,
background_colour: u8,
) -> TextRenderer<'a> {
TextRenderer {
current_x_pos: 0,
current_y_pos: 0,
font: self,
tile_pos,
background_colour,
foreground_colour,
tiles: Default::default(),
}
}
}
pub struct TextRenderer<'a> {
current_x_pos: i32,
current_y_pos: i32,
font: &'a Font,
tile_pos: Vector2D<u16>,
// (jem) can't hold onto mut reference to vram/bg over multiple frames
// vram_manager: &'a mut VRamManager,
// bg: &'a mut RegularMap,
background_colour: u8,
foreground_colour: u8,
tiles: HashMap<(i32, i32), DynamicTile<'a>>,
}
// (jem) could re implement this by creating some sub renderer object that the
// TextRenderer produces
// impl<'a> Write for TextRenderer<'a> {
// fn write_str(&mut self, text: &str) -> Result<(), Error> {
// for c in text.chars() {
// if c == '\n' {
// self.current_y_pos += self.font.line_height;
// self.current_x_pos = 0;
// continue;
// }
// let letter = self.font.letter(c);
// self.render_letter(letter);
// self.current_x_pos += i32::from(letter.advance_width);
// }
// Ok(())
// }
// }
fn div_ceil(quotient: i32, divisor: i32) -> i32 {
(quotient + divisor - 1) / divisor
}
impl<'a> TextRenderer<'a> {
fn render_letter(&mut self, letter: &FontLetter, vram_manager: &mut VRamManager) {
let foreground_colour = self.foreground_colour;
let background_colour = self.background_colour;
let x_start = (self.current_x_pos + i32::from(letter.xmin)).max(0);
let y_start = self.current_y_pos + self.font.ascent
- i32::from(letter.height)
- i32::from(letter.ymin);
let x_tile_start = x_start / 8;
let y_tile_start = y_start / 8;
let letter_offset_x = x_start.rem_euclid(8);
let letter_offset_y = y_start.rem_euclid(8);
let x_tiles = div_ceil(i32::from(letter.width) + letter_offset_x, 8);
let y_tiles = div_ceil(i32::from(letter.height) + letter_offset_y, 8);
for letter_y_tile in 0..(y_tiles + 1) {
let letter_y_start = 0.max(letter_offset_y - 8 * letter_y_tile) + 8 * letter_y_tile;
let letter_y_end =
(letter_offset_y + i32::from(letter.height)).min((letter_y_tile + 1) * 8);
let tile_y = y_tile_start + letter_y_tile;
for letter_x_tile in 0..(x_tiles + 1) {
let letter_x_start = 0.max(letter_offset_x - 8 * letter_x_tile) + 8 * letter_x_tile;
let letter_x_end =
(letter_offset_x + i32::from(letter.width)).min((letter_x_tile + 1) * 8);
let tile_x = x_tile_start + letter_x_tile;
let mut masks = [0u32; 8];
let mut zero = true;
for letter_y in letter_y_start..letter_y_end {
let y = letter_y - letter_offset_y;
for letter_x in letter_x_start..letter_x_end {
let x = letter_x - letter_offset_x;
let pos = x + y * i32::from(letter.width);
let px_line = letter.data[(pos / 8) as usize];
let px = (px_line >> (pos & 7)) & 1;
if px != 0 {
masks[(letter_y & 7) as usize] |=
u32::from(foreground_colour) << ((letter_x & 7) * 4);
zero = false;
}
}
}
if !zero {
let tile = self.tiles.entry((tile_x, tile_y)).or_insert_with(|| {
vram_manager.new_dynamic_tile().fill_with(background_colour)
});
for (i, tile_data_line) in tile.tile_data.iter_mut().enumerate() {
*tile_data_line |= masks[i];
}
}
}
}
}
pub fn commit(&self, vram: &mut VRamManager, bg: &'a mut RegularMap) {
//
// let tiles = core::mem::take(&mut self.tiles);
// (jem) when commit, don't consume the object, leave the tiles around
// so we can use them next frame
for ((x, y), tile) in self.tiles.iter() {
bg.set_tile(
vram,
(self.tile_pos.x + *x as u16, self.tile_pos.y + *y as u16).into(),
&tile.tile_set(),
TileSetting::from_raw(tile.tile_index()),
);
// self.vram_manager.remove_dynamic_tile(tile);
}
}
pub fn write_str(&mut self, text: &str, vram: &mut VRamManager) {
for c in text.chars() {
if c == '\n' {
self.current_y_pos += self.font.line_height;
self.current_x_pos = 0;
continue;
}
let letter = self.font.letter(c);
self.render_letter(letter, vram);
self.current_x_pos += i32::from(letter.advance_width);
}
}
// (jem) clear the text shown
pub fn clear(&mut self, vram: &mut VRamManager) {
self.current_x_pos = 0;
self.current_y_pos = 0;
let tiles = core::mem::take(&mut self.tiles);
for (_, tile) in tiles.into_iter() {
vram.remove_dynamic_tile(tile);
}
}
}
// impl<'a> Drop for TextRenderer<'a> {
// fn drop(&mut self) {
// let tiles = core::mem::take(&mut self.tiles);
// for (_, tile) in tiles.into_iter() {
// self.vram_manager.remove_dynamic_tile(tile);
// }
// }
// } |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 2 replies
-
Hi @jembishop! You are correct that there isn't a current good way to do this. Text rendering as currently implemented is very slow. I think @corwinkuiper had some ideas to start using sprites for individual words rather than using background tiles, but we haven't fully gotten round to thinking about how this will work. I believe that's how a lot of games do their text rendering, but I may be misremembering. I'd happily accept some updates to the text rendering to better support this use-case, provided it's easy to use and hard to get wrong. Maybe the existing 'full text' renderer could be a wrapper around something similar to what you've included? Feel free to PR something and we can discuss it there! The changes you posted here though look pretty sensible though, and a nice way to amortise the cost of rendering text over multiple frames while it is still an expensive process :). This is definitely something we've wanted to do for our own games too (for dialogue boxes similar to what you're doing), so would love to see something like this included in agb :D Would love to see the progress of your game! We're really excited to see what people have been building with agb. |
Beta Was this translation helpful? Give feedback.
-
I created a PR for my modification here #352 |
Beta Was this translation helpful? Give feedback.
I created a PR for my modification here #352