agb/book/games/pong/src/main.rs
2024-05-12 14:03:16 +01:00

116 lines
4.2 KiB
Rust

// Games made using `agb` are no_std which means you don't have access to the standard
// rust library. This is because the game boy advance doesn't really have an operating
// system, so most of the content of the standard library doesn't apply.
//
// Provided you haven't disabled it, agb does provide an allocator, so it is possible
// to use both the `core` and the `alloc` built in crates.
#![no_std]
// `agb` defines its own `main` function, so you must declare your game's main function
// using the #[agb::entry] proc macro. Failing to do so will cause failure in linking
// which won't be a particularly clear error message.
#![no_main]
// This is required to allow writing tests
#![cfg_attr(test, feature(custom_test_frameworks))]
#![cfg_attr(test, reexport_test_harness_main = "test_main")]
#![cfg_attr(test, test_runner(agb::test_runner::test_runner))]
use agb::{
display::object::{Graphics, OamManaged, Object, Tag},
include_aseprite,
};
// Import the sprites in to this static. This holds the sprite
// and palette data in a way that is manageable by agb.
static GRAPHICS: &Graphics = include_aseprite!("gfx/sprites.aseprite");
// We define some easy ways of referencing the sprites
static PADDLE_END: &Tag = GRAPHICS.tags().get("Paddle End");
static PADDLE_MID: &Tag = GRAPHICS.tags().get("Paddle Mid");
static BALL: &Tag = GRAPHICS.tags().get("Ball");
struct Paddle<'obj> {
start: Object<'obj>,
mid: Object<'obj>,
end: Object<'obj>,
}
impl<'obj> Paddle<'obj> {
fn new(object: &'obj OamManaged<'_>, start_x: i32, start_y: i32) -> Self {
let mut paddle_start = object.object_sprite(PADDLE_END.sprite(0));
let mut paddle_mid = object.object_sprite(PADDLE_MID.sprite(0));
let mut paddle_end = object.object_sprite(PADDLE_END.sprite(0));
paddle_start.show();
paddle_mid.show();
paddle_end.set_vflip(true).show();
let mut paddle = Self {
start: paddle_start,
mid: paddle_mid,
end: paddle_end,
};
paddle.set_position(start_x, start_y);
paddle
}
fn set_position(&mut self, x: i32, y: i32) {
// new! use of the `set_position` method. This is a helper feature using
// agb's vector types. For now we can just use it to avoid adding them
// separately
self.start.set_position((x, y));
self.mid.set_position((x, y + 16));
self.end.set_position((x, y + 32));
}
}
// The main function must take 0 arguments and never return. The agb::entry decorator
// ensures that everything is in order. `agb` will call this after setting up the stack
// and interrupt handlers correctly.
#[agb::entry]
fn main(mut gba: agb::Gba) -> ! {
// Get the OAM manager
let object = gba.display.object.get_managed();
// Create an object with the ball sprite
let mut ball = object.object_sprite(BALL.sprite(0));
// Place this at some point on the screen, (50, 50) for example
ball.set_x(50).set_y(50).show();
// Now commit the object controller so this change is reflected on the screen,
// this should normally be done in vblank but it'll work just fine here for now
object.commit();
let mut _paddle_a = Paddle::new(&object, 8, 8);
let mut _paddle_b = Paddle::new(&object, 240 - 16 - 8, 8);
let mut ball_x = 50;
let mut ball_y = 50;
let mut x_velocity = 1;
let mut y_velocity = 1;
loop {
// This will calculate the new position and enforce the position
// of the ball remains within the screen
ball_x = (ball_x + x_velocity).clamp(0, agb::display::WIDTH - 16);
ball_y = (ball_y + y_velocity).clamp(0, agb::display::HEIGHT - 16);
// We check if the ball reaches the edge of the screen and reverse it's direction
if ball_x == 0 || ball_x == agb::display::WIDTH - 16 {
x_velocity = -x_velocity;
}
if ball_y == 0 || ball_y == agb::display::HEIGHT - 16 {
y_velocity = -y_velocity;
}
// Set the position of the ball to match our new calculated position
ball.set_x(ball_x as u16).set_y(ball_y as u16);
// Wait for vblank, then commit the objects to the screen
agb::display::busy_wait_for_vblank();
object.commit();
}
}