Multiple levels

The first thing we want to do here is to make some refactors. Making the World struct hold the Player should be convenient.


#![allow(unused)]
fn main() {
struct World {
    level: &'static Level,
    // new! Player
    player: Player,
    bg: InfiniteScrolledMap,
}

impl World {
    fn new(level: &'static Level) -> Self {
        let bg = RegularBackground::new(
            Priority::P0,
            RegularBackgroundSize::Background32x32,
            TileFormat::FourBpp,
        );
        let bg = InfiniteScrolledMap::new(bg);

        World {
            level,
            bg,
            // new! make the player
            player: Player::new(level.player_start.into()),
        }
    }

    // new! an update function that updates the background and the player
    fn update(&mut self, input: &ButtonController) {
        self.set_pos(vec2(0, 0));

        self.player.update(input, self.level);
    }

    fn show(&self, frame: &mut GraphicsFrame) {
        self.bg.show(frame);
        // new! show the player
        self.player.show(frame);
    }
}
}

And then we can use this in the main function

#[agb::entry]
fn main(mut gba: agb::Gba) -> ! {
    let mut gfx = gba.graphics.get();

    VRAM_MANAGER.set_background_palettes(tiles::PALETTES);

    let level = 0;
    // renamed to world
    let mut world = World::new(levels::LEVELS[level]);
    let mut input = ButtonController::new();
    // player removed

    loop {
        input.update();
        // replaced with `world.update`
        world.update(&input);

        let mut frame = gfx.frame();

        world.show(&mut frame);

        frame.commit();
    }
}

Detecting level end

To advance to the next level, we'll want to check if you've won the current level. This can be done using code very similar to collides on Level.


#![allow(unused)]
fn main() {
impl Level {
    fn wins(&self, tile: Vector2D<i32>) -> bool {
        if !self.bounds().contains_point(tile) {
            return false;
        }

        let idx = (tile.x + tile.y * self.width as i32) as usize;

        self.winning_map[idx / 8] & (1 << (idx % 8)) != 0
    }
}
}

Then we'll add something on the Player to tell if it has won and forward this on the World.


#![allow(unused)]
fn main() {
impl Player {
    fn has_won(&self, level: &Level) -> bool {
        level.wins(self.position.floor() / 8)
    }
}

impl World {
    fn has_won(&self) -> bool {
        self.player.has_won(self.level)
    }
}
}

Then with a small change to our main function we can advance to the next level when the player hits the flag

#[agb::entry]
fn main(mut gba: agb::Gba) -> ! {
    let mut gfx = gba.graphics.get();

    VRAM_MANAGER.set_background_palettes(tiles::PALETTES);

    // new! mutable level
    let mut level = 0;
    let mut world = World::new(levels::LEVELS[level]);
    let mut input = ButtonController::new();

    loop {
        input.update();
        world.update(&input);

        let mut frame = gfx.frame();

        world.show(&mut frame);

        frame.commit();

        // new! handle winning the level and advancing to the next one
        if world.has_won() {
            level += 1;
            level %= levels::LEVELS.len();
            world = World::new(levels::LEVELS[level]);
        }
    }
}

Actually adding more levels

To make more levels, create the level in tiled then add it to the level array in the build.rs file.


#![allow(unused)]
fn main() {
static LEVELS: &[&str] = &["level_01.tmx", "level_02.tmx"];
}

and the rest will be done for you!

Summary

Here we've made a game that has multiple levels and can transition between them. There are many aspects that should be improved in your games, these include

  • Hiding the loading sequence. Loading happens over multiple frames and should be hidden from view.
  • Falling off the world. The level should restart if the player falls off the world, or levels should be designed such that it is impossible to fall off.