diff --git a/agb/gfx/square.aseprite b/agb/gfx/square.aseprite new file mode 100644 index 0000000..e2cf234 Binary files /dev/null and b/agb/gfx/square.aseprite differ diff --git a/agb/src/display/object/sprites/sprite.rs b/agb/src/display/object/sprites/sprite.rs index 6751c47..e41d0d1 100644 --- a/agb/src/display/object/sprites/sprite.rs +++ b/agb/src/display/object/sprites/sprite.rs @@ -94,6 +94,7 @@ macro_rules! align_bytes { #[macro_export] macro_rules! include_aseprite { ($($aseprite_path: expr),*) => {{ + #[allow(unused_imports)] use $crate::display::object::{Size, Sprite, Tag, TagMap, Graphics}; use $crate::display::palette16::Palette16; use $crate::align_bytes; diff --git a/agb/src/lib.rs b/agb/src/lib.rs index cda67da..073971c 100644 --- a/agb/src/lib.rs +++ b/agb/src/lib.rs @@ -165,6 +165,11 @@ pub mod syscall; /// Interactions with the internal timers pub mod timer; +mod no_game; + +/// Default game +pub use no_game::no_game; + pub(crate) mod arena; pub use {agb_alloc::ExternalAllocator, agb_alloc::InternalAllocator}; diff --git a/agb/src/no_game.rs b/agb/src/no_game.rs new file mode 100644 index 0000000..87ca2cb --- /dev/null +++ b/agb/src/no_game.rs @@ -0,0 +1,208 @@ +//! The no game screen is what is displayed if there isn't a game made yet. + +use agb_fixnum::{num, Num, Vector2D}; +use alloc::vec; +use alloc::vec::Vec; + +use crate::{ + display::{ + object::{OamIterator, ObjectUnmanaged, Sprite}, + palette16::Palette16, + HEIGHT, WIDTH, + }, + include_aseprite, + interrupt::VBlank, +}; + +const SQUARE: &Sprite = &include_aseprite!("gfx/square.aseprite").sprites()[0]; + +fn letters() -> Vec>>> { + vec![ + // N + vec![ + (num!(0.), num!(0.)).into(), + (num!(1.), num!(1.)).into(), + (num!(2.), num!(2.)).into(), + (num!(3.), num!(3.)).into(), + (num!(0.), num!(1.)).into(), + (num!(0.), num!(2.)).into(), + (num!(0.), num!(3.)).into(), + (num!(3.), num!(0.)).into(), + (num!(3.), num!(1.)).into(), + (num!(3.), num!(2.)).into(), + (num!(3.), num!(3.)).into(), + ], + // O + vec![ + (num!(0.), num!(0.)).into(), + (num!(0.), num!(1.)).into(), + (num!(0.), num!(2.)).into(), + (num!(0.), num!(3.)).into(), + (num!(1.), num!(3.)).into(), + (num!(2.), num!(3.)).into(), + (num!(3.), num!(3.)).into(), + (num!(3.), num!(2.)).into(), + (num!(3.), num!(1.)).into(), + (num!(3.), num!(0.)).into(), + (num!(2.), num!(0.)).into(), + (num!(1.), num!(0.)).into(), + ], + // G + vec![ + (num!(3.), num!(0.)).into(), + (num!(2.), num!(0.)).into(), + (num!(1.), num!(0.)).into(), + (num!(0.), num!(0.)).into(), + (num!(0.), num!(1.)).into(), + (num!(0.), num!(2.)).into(), + (num!(0.), num!(3.)).into(), + (num!(1.), num!(3.)).into(), + (num!(2.), num!(3.)).into(), + (num!(3.), num!(3.)).into(), + (num!(3.), num!(2.25)).into(), + (num!(3.), num!(1.5)).into(), + (num!(2.), num!(1.5)).into(), + ], + // A + vec![ + (num!(0.), num!(0.)).into(), + (num!(0.), num!(1.)).into(), + (num!(0.), num!(2.)).into(), + (num!(0.), num!(3.)).into(), + (num!(3.), num!(3.)).into(), + (num!(3.), num!(2.)).into(), + (num!(3.), num!(1.)).into(), + (num!(3.), num!(0.)).into(), + (num!(2.), num!(0.)).into(), + (num!(1.), num!(0.)).into(), + (num!(1.), num!(1.5)).into(), + (num!(2.), num!(1.5)).into(), + ], + // M + vec![ + (num!(0.), num!(0.)).into(), + (num!(0.), num!(1.)).into(), + (num!(0.), num!(2.)).into(), + (num!(0.), num!(3.)).into(), + (num!(3.), num!(3.)).into(), + (num!(3.), num!(2.)).into(), + (num!(3.), num!(1.)).into(), + (num!(3.), num!(0.)).into(), + (num!(1.5), num!(1.5)).into(), + (num!(0.5), num!(0.5)).into(), + (num!(2.5), num!(0.5)).into(), + (num!(1.), num!(1.)).into(), + (num!(2.), num!(1.)).into(), + ], + // E + vec![ + (num!(0.), num!(0.)).into(), + (num!(0.), num!(1.)).into(), + (num!(0.), num!(2.)).into(), + (num!(0.), num!(3.)).into(), + (num!(1.), num!(3.)).into(), + (num!(2.), num!(3.)).into(), + (num!(3.), num!(3.)).into(), + (num!(3.), num!(0.)).into(), + (num!(2.), num!(0.)).into(), + (num!(1.), num!(0.)).into(), + (num!(1.), num!(1.5)).into(), + (num!(2.), num!(1.5)).into(), + ], + ] +} + +trait Renderable { + fn render(&self, slots: &mut OamIterator) -> Option<()>; +} + +impl Renderable for ObjectUnmanaged { + fn render(&self, slots: &mut OamIterator) -> Option<()> { + slots.next()?.set(self); + Some(()) + } +} + +impl Renderable for &[T] { + fn render(&self, slots: &mut OamIterator) -> Option<()> { + for r in self.iter() { + r.render(slots)?; + } + + Some(()) + } +} + +pub fn no_game(mut gba: crate::Gba) -> ! { + let (mut oam, mut loader) = gba.display.object.get_unmanaged(); + + let square = loader.get_vram_sprite(SQUARE); + + let mut letter_positons = Vec::new(); + + let square_positions = { + let mut s = letters(); + for letter in s.iter_mut() { + letter.sort_by_key(|a| a.manhattan_distance()); + } + s + }; + for (letter_idx, letter_parts) in square_positions.iter().enumerate() { + for part in letter_parts.iter() { + let position = part + .hadamard((8, 10).into()) + .hadamard((num!(3.) / 2, num!(3.) / 2).into()); + + let letter_pos = Vector2D::new( + 60 * (1 + letter_idx as i32 - ((letter_idx >= 2) as i32 * 3)), + 70 * ((letter_idx >= 2) as i32), + ); + + letter_positons.push(position + letter_pos.change_base()); + } + } + + let bottom_right = letter_positons + .iter() + .copied() + .max_by_key(|x| x.manhattan_distance()) + .unwrap(); + + let difference = (Vector2D::new(WIDTH - 8, HEIGHT - 8).change_base() - bottom_right) / 2; + + for pos in letter_positons.iter_mut() { + *pos += difference; + } + + let mut time: Num = num!(0.); + let time_delta: Num = num!(0.025); + + let (_background, mut vram) = gba.display.video.tiled0(); + + vram.set_background_palettes(&[Palette16::new([u16::MAX; 16])]); + + let vblank = VBlank::get(); + + loop { + time += time_delta; + time %= 1; + let letters: Vec = letter_positons + .iter() + .enumerate() + .map(|(idx, position)| { + let time = time + Num::::new(idx as i32) / 128; + *position + Vector2D::new(time.sin(), time.cos()) * 10 + }) + .map(|pos| { + let mut obj = ObjectUnmanaged::new(square.clone()); + obj.show().set_position(pos.floor()); + obj + }) + .collect(); + + vblank.wait_for_vblank(); + for (obj, slot) in letters.iter().zip(oam.iter()) { + slot.set(obj); + } + } +} diff --git a/template/src/main.rs b/template/src/main.rs index 84683dc..e58c652 100644 --- a/template/src/main.rs +++ b/template/src/main.rs @@ -14,22 +14,10 @@ #![cfg_attr(test, reexport_test_harness_main = "test_main")] #![cfg_attr(test, test_runner(agb::test_runner::test_runner))] -use agb::{display, syscall}; - // The main function must take 1 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. It will also handle creating the `Gba` struct for you. #[agb::entry] fn main(mut gba: agb::Gba) -> ! { - let mut bitmap = gba.display.video.bitmap3(); - - for x in 0..display::WIDTH { - let y = syscall::sqrt(x << 6); - let y = (display::HEIGHT - y).clamp(0, display::HEIGHT - 1); - bitmap.draw_point(x, y, 0x001F); - } - - loop { - syscall::halt(); - } + agb::no_game(gba); }