mirror of
https://github.com/italicsjenga/agb.git
synced 2024-12-23 08:11:33 +11:00
Add a no game (#427)
This adds a "no game" to replace the template. * Inspired by how Love2D has a default game that says "No Game". * This screen: https://youtube.com/clip/Ugkx6atqwerxyyUSiVrFhmAh7pK2xNgjHxI9 - [x] Changelog updated
This commit is contained in:
commit
84f22c0b30
|
@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
- Changed the default template game
|
||||
|
||||
## [0.15.0] - 2023/04/25
|
||||
|
||||
### Added
|
||||
|
|
|
@ -239,9 +239,36 @@ impl ToTokens for ByteString<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn include_colours_inner(input: TokenStream) -> TokenStream {
|
||||
let input_filename = parse_macro_input!(input as LitStr);
|
||||
let input_filename = input_filename.value();
|
||||
|
||||
let root = std::env::var("CARGO_MANIFEST_DIR").expect("Failed to get cargo manifest dir");
|
||||
let input_filename = Path::new(&root).join(input_filename);
|
||||
|
||||
let image = Image::load_from_file(Path::new(&input_filename));
|
||||
|
||||
let mut palette_data = Vec::with_capacity(image.width * image.height);
|
||||
for y in 0..image.height {
|
||||
for x in 0..image.width {
|
||||
palette_data.push(image.colour(x, y).to_rgb15())
|
||||
}
|
||||
}
|
||||
|
||||
let filename = input_filename.to_string_lossy();
|
||||
|
||||
TokenStream::from(quote! {
|
||||
{
|
||||
const _: &[u8] = include_bytes!(#filename);
|
||||
[#(#palette_data),*]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn include_aseprite_inner(input: TokenStream) -> TokenStream {
|
||||
let parser = Punctuated::<LitStr, syn::Token![,]>::parse_separated_nonempty;
|
||||
let parser = Punctuated::<LitStr, syn::Token![,]>::parse_terminated;
|
||||
let parsed = match parser.parse(input) {
|
||||
Ok(e) => e,
|
||||
Err(e) => return e.to_compile_error().into(),
|
||||
|
@ -432,8 +459,8 @@ fn palette_tile_data(
|
|||
|
||||
let mut tile_data = Vec::new();
|
||||
|
||||
for image in images {
|
||||
add_image_to_tile_data(&mut tile_data, image, optimiser, 0)
|
||||
for (image_idx, image) in images.iter().enumerate() {
|
||||
add_image_to_tile_data(&mut tile_data, image, optimiser, image_idx)
|
||||
}
|
||||
|
||||
let tile_data = collapse_to_4bpp(&tile_data);
|
||||
|
|
|
@ -138,17 +138,7 @@ impl Palette16Optimiser {
|
|||
while !unsatisfied_palettes.is_empty() {
|
||||
let palette = self.find_maximal_palette_for(&unsatisfied_palettes);
|
||||
|
||||
for test_palette in unsatisfied_palettes.clone() {
|
||||
if test_palette.is_satisfied_by(&palette) {
|
||||
unsatisfied_palettes.remove(&test_palette);
|
||||
}
|
||||
}
|
||||
|
||||
for (i, overall_palette) in self.palettes.iter().enumerate() {
|
||||
if overall_palette.is_satisfied_by(&palette) {
|
||||
assignments[i] = optimised_palettes.len();
|
||||
}
|
||||
}
|
||||
unsatisfied_palettes.retain(|test_palette| !test_palette.is_satisfied_by(&palette));
|
||||
|
||||
optimised_palettes.push(palette);
|
||||
|
||||
|
@ -157,6 +147,13 @@ impl Palette16Optimiser {
|
|||
}
|
||||
}
|
||||
|
||||
for (i, overall_palette) in self.palettes.iter().enumerate() {
|
||||
assignments[i] = optimised_palettes
|
||||
.iter()
|
||||
.position(|palette| overall_palette.is_satisfied_by(palette))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
Palette16OptimisationResults {
|
||||
optimised_palettes,
|
||||
assignments,
|
||||
|
|
7
agb/examples/no_game.rs
Normal file
7
agb/examples/no_game.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
#[agb::entry]
|
||||
fn main(gba: agb::Gba) -> ! {
|
||||
agb::no_game(gba);
|
||||
}
|
BIN
agb/gfx/pastel.png
Normal file
BIN
agb/gfx/pastel.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 355 B |
|
@ -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;
|
||||
|
@ -367,4 +368,11 @@ impl Size {
|
|||
Size::S32x64 => (32, 64),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Returns the width and height of the size in pixels.
|
||||
pub const fn to_tiles_width_height(self) -> (usize, usize) {
|
||||
let wh = self.to_width_height();
|
||||
(wh.0 / 8, wh.1 / 8)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
use core::ptr::NonNull;
|
||||
use core::{alloc::Allocator, ptr::NonNull};
|
||||
|
||||
use alloc::rc::{Rc, Weak};
|
||||
use alloc::{
|
||||
alloc::Global,
|
||||
boxed::Box,
|
||||
rc::{Rc, Weak},
|
||||
vec::Vec,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
agb_alloc::{block_allocator::BlockAllocator, bump_allocator::StartEnd},
|
||||
|
@ -285,34 +290,61 @@ impl Default for SpriteLoader {
|
|||
}
|
||||
|
||||
/// Sprite data that can be used to create sprites in vram.
|
||||
pub struct DynamicSprite<'a> {
|
||||
data: &'a [u8],
|
||||
pub struct DynamicSprite<A: Allocator = Global> {
|
||||
data: Box<[u8], A>,
|
||||
size: Size,
|
||||
}
|
||||
|
||||
impl DynamicSprite<'_> {
|
||||
impl DynamicSprite {
|
||||
#[must_use]
|
||||
/// Creates a new dynamic sprite from underlying bytes. Note that despite
|
||||
/// being an array of u8, this must be aligned to at least a 2 byte
|
||||
/// boundary.
|
||||
pub fn new(data: &[u8], size: Size) -> DynamicSprite {
|
||||
let ptr = &data[0] as *const _ as usize;
|
||||
if ptr % 2 != 0 {
|
||||
panic!("data is not aligned to a 2 byte boundary");
|
||||
}
|
||||
if data.len() != size.number_of_tiles() * BYTES_PER_TILE_4BPP {
|
||||
panic!(
|
||||
"data is not of expected length, got {} expected {}",
|
||||
data.len(),
|
||||
size.number_of_tiles() * BYTES_PER_TILE_4BPP
|
||||
);
|
||||
}
|
||||
/// Creates a new dynamic sprite.
|
||||
pub fn new(size: Size) -> Self {
|
||||
Self::new_in(size, Global)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Allocator> DynamicSprite<A> {
|
||||
#[must_use]
|
||||
/// Creates a new dynamic sprite of a given size in a given allocator.
|
||||
pub fn new_in(size: Size, allocator: A) -> Self {
|
||||
let num_bytes = size.number_of_tiles() * BYTES_PER_TILE_4BPP;
|
||||
let mut data = Vec::with_capacity_in(num_bytes, allocator);
|
||||
|
||||
data.resize(num_bytes, 0);
|
||||
|
||||
let data = data.into_boxed_slice();
|
||||
|
||||
DynamicSprite { data, size }
|
||||
}
|
||||
|
||||
/// Set the pixel of a sprite to a given paletted pixel. Panics if the
|
||||
/// coordinate is out of range of the sprite or if the paletted pixel is
|
||||
/// greater than 4 bits.
|
||||
pub fn set_pixel(&mut self, x: usize, y: usize, paletted_pixel: usize) {
|
||||
assert!(paletted_pixel < 0x10);
|
||||
|
||||
let (sprite_pixel_x, sprite_pixel_y) = self.size.to_width_height();
|
||||
assert!(x < sprite_pixel_x, "x too big for sprite size");
|
||||
assert!(y < sprite_pixel_y, "y too big for sprite size");
|
||||
|
||||
let (sprite_tile_x, _) = self.size.to_tiles_width_height();
|
||||
|
||||
let (adjust_tile_x, adjust_tile_y) = (x / 8, y / 8);
|
||||
|
||||
let tile_number_to_modify = adjust_tile_x + adjust_tile_y * sprite_tile_x;
|
||||
|
||||
let byte_to_modify_in_tile = x / 2 + y * 4;
|
||||
let byte_to_modify = tile_number_to_modify * BYTES_PER_TILE_4BPP + byte_to_modify_in_tile;
|
||||
let mut byte = self.data[byte_to_modify];
|
||||
let parity = (x & 0b1) * 4;
|
||||
|
||||
byte = (byte & !(0b1111 << parity)) | ((paletted_pixel as u8) << parity);
|
||||
self.data[byte_to_modify] = byte;
|
||||
}
|
||||
|
||||
/// Tries to copy the sprite to vram to be used to set object sprites.
|
||||
pub fn try_vram(&self, palette: PaletteVram) -> Result<SpriteVram, LoaderError> {
|
||||
SpriteVram::new(self.data, self.size, palette)
|
||||
SpriteVram::new(&self.data, self.size, palette)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
|
|
|
@ -27,3 +27,12 @@ impl Palette16 {
|
|||
Layout::new::<Self>()
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! include_palette {
|
||||
($palette:literal) => {
|
||||
$crate::include_colours_inner!($palette)
|
||||
};
|
||||
}
|
||||
|
||||
pub use include_palette;
|
||||
|
|
|
@ -104,6 +104,9 @@ pub use agb_image_converter::include_aseprite_inner;
|
|||
#[doc(hidden)]
|
||||
pub use agb_image_converter::include_font as include_font_inner;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use agb_image_converter::include_colours_inner;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! include_font {
|
||||
($font_path: literal, $font_size: literal) => {{
|
||||
|
@ -165,6 +168,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};
|
||||
|
|
213
agb/src/no_game.rs
Normal file
213
agb/src/no_game.rs
Normal file
|
@ -0,0 +1,213 @@
|
|||
//! 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::Vec;
|
||||
use alloc::{boxed::Box, vec};
|
||||
|
||||
use crate::display::object::{DynamicSprite, PaletteVram, Size, SpriteVram};
|
||||
use crate::display::palette16::Palette16;
|
||||
use crate::{
|
||||
display::{object::ObjectUnmanaged, HEIGHT, WIDTH},
|
||||
include_palette,
|
||||
interrupt::VBlank,
|
||||
};
|
||||
|
||||
const PALETTE: &[u16] = &include_palette!("gfx/pastel.png");
|
||||
|
||||
fn letters() -> Vec<Vec<Vector2D<Num<i32, 8>>>> {
|
||||
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.75), num!(0.75)).into(),
|
||||
(num!(2.25), num!(0.75)).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(),
|
||||
],
|
||||
]
|
||||
}
|
||||
|
||||
fn generate_sprites() -> Box<[SpriteVram]> {
|
||||
let mut sprites = Vec::new();
|
||||
|
||||
// generate palettes
|
||||
|
||||
let palettes: Vec<PaletteVram> = PALETTE
|
||||
.chunks(15)
|
||||
.map(|x| {
|
||||
core::iter::once(0)
|
||||
.chain(x.iter().copied())
|
||||
.chain(core::iter::repeat(0))
|
||||
.take(16)
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.map(|palette| {
|
||||
let palette = Palette16::new(palette.try_into().unwrap());
|
||||
PaletteVram::new(&palette).unwrap()
|
||||
})
|
||||
.collect();
|
||||
|
||||
// generate sprites
|
||||
let mut sprite = DynamicSprite::new(Size::S8x8);
|
||||
for (palette, colour) in (0..PALETTE.len()).map(|x| (x / 15, x % 15)) {
|
||||
for y in 0..8 {
|
||||
for x in 0..8 {
|
||||
sprite.set_pixel(x, y, colour + 1);
|
||||
}
|
||||
}
|
||||
sprites.push(sprite.to_vram(palettes[palette].clone()));
|
||||
}
|
||||
|
||||
sprites.into_boxed_slice()
|
||||
}
|
||||
|
||||
pub fn no_game(mut gba: crate::Gba) -> ! {
|
||||
let (mut oam, _) = gba.display.object.get_unmanaged();
|
||||
|
||||
let squares = generate_sprites();
|
||||
|
||||
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.magnitude_squared());
|
||||
}
|
||||
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<i32, 8> = num!(0.);
|
||||
let time_delta: Num<i32, 8> = num!(0.025);
|
||||
|
||||
let vblank = VBlank::get();
|
||||
|
||||
loop {
|
||||
time += time_delta;
|
||||
time %= 1;
|
||||
let letters: Vec<ObjectUnmanaged> = letter_positons
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, position)| {
|
||||
let time = time + Num::<i32, 8>::new(idx as i32) / 128;
|
||||
(idx, *position + Vector2D::new(time.sin(), time.cos()) * 10)
|
||||
})
|
||||
.map(|(idx, pos)| {
|
||||
let mut obj = ObjectUnmanaged::new(squares[idx % squares.len()].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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue