From dcba19ed732e118d71d0344387f11281ad993d18 Mon Sep 17 00:00:00 2001 From: GBA bot Date: Sat, 1 Jan 2022 22:35:08 +0000 Subject: [PATCH 01/28] Write the introduction for the pong game --- book/src/SUMMARY.md | 3 ++- book/src/pong/introduction.md | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 book/src/pong/introduction.md diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index c67ea685..2ea8f618 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -7,4 +7,5 @@ - [Linux setup](./setup/linux.md) - [Windows setup]() - [Mac OS setup]() - - [Building the game](./setup/building.md) \ No newline at end of file + - [Building the game](./setup/building.md) +- [Learn agb part I - pong](./pong/introduction.md) \ No newline at end of file diff --git a/book/src/pong/introduction.md b/book/src/pong/introduction.md new file mode 100644 index 00000000..f27d5a21 --- /dev/null +++ b/book/src/pong/introduction.md @@ -0,0 +1,14 @@ +# Learn agb part I - pong + +In this section, we'll make a simple pong style game for the Game Boy Advance using `agb`. +You will learn: + +* How to import graphics using `agb`. +* What Game Boy Advance sprites are and how to put them on the screen. +* How to detect button input and react to it. +* How to add a static background. +* How to make a dynamic background for a score display. +* How to add music to your game. +* How to add sound effects to your game. + +With this knowledge, you'll be well equipped to start making your own games! \ No newline at end of file From a363dfc75793f25532de95528efe7185cae0791e Mon Sep 17 00:00:00 2001 From: GBA bot Date: Sat, 1 Jan 2022 22:37:39 +0000 Subject: [PATCH 02/28] Start the pong example for the book --- book/games/pong/.cargo/config.toml | 10 +++ book/games/pong/.gitignore | 1 + book/games/pong/Cargo.toml | 14 ++++ book/games/pong/gba.ld | 109 ++++++++++++++++++++++++++++ book/games/pong/gba_mb.ld | 108 +++++++++++++++++++++++++++ book/games/pong/rust-toolchain.toml | 3 + book/games/pong/src/main.rs | 21 ++++++ 7 files changed, 266 insertions(+) create mode 100644 book/games/pong/.cargo/config.toml create mode 100644 book/games/pong/.gitignore create mode 100644 book/games/pong/Cargo.toml create mode 100644 book/games/pong/gba.ld create mode 100644 book/games/pong/gba_mb.ld create mode 100644 book/games/pong/rust-toolchain.toml create mode 100644 book/games/pong/src/main.rs diff --git a/book/games/pong/.cargo/config.toml b/book/games/pong/.cargo/config.toml new file mode 100644 index 00000000..76de9890 --- /dev/null +++ b/book/games/pong/.cargo/config.toml @@ -0,0 +1,10 @@ +[unstable] +build-std = ["core", "alloc"] +build-std-features = ["compiler-builtins-mem"] + +[build] +target = "thumbv4t-none-eabi" + +[target.thumbv4t-none-eabi] +rustflags = ["-Clink-arg=-Tgba.ld"] +runner = "mgba-qt" diff --git a/book/games/pong/.gitignore b/book/games/pong/.gitignore new file mode 100644 index 00000000..1de56593 --- /dev/null +++ b/book/games/pong/.gitignore @@ -0,0 +1 @@ +target \ No newline at end of file diff --git a/book/games/pong/Cargo.toml b/book/games/pong/Cargo.toml new file mode 100644 index 00000000..d6bb0791 --- /dev/null +++ b/book/games/pong/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "agb_template" +version = "0.1.0" +authors = [""] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +agb = "0.8.0" + +[profile.release] +panic = "abort" +lto = true \ No newline at end of file diff --git a/book/games/pong/gba.ld b/book/games/pong/gba.ld new file mode 100644 index 00000000..ba54bc5a --- /dev/null +++ b/book/games/pong/gba.ld @@ -0,0 +1,109 @@ +OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm") +OUTPUT_ARCH(arm) + +ENTRY(__start) +EXTERN(__RUST_INTERRUPT_HANDLER) + +MEMORY { + ewram (w!x) : ORIGIN = 0x02000000, LENGTH = 256K + iwram (w!x) : ORIGIN = 0x03000000, LENGTH = 32K + rom (rx) : ORIGIN = 0x08000000, LENGTH = 32M +} + +__text_start = ORIGIN(rom); + +SECTIONS { + . = __text_start; + + .crt0 : { + KEEP (crt0.o(.text)); + . = ALIGN(4); + } > rom + + .text : { + *(.text .text*); + . = ALIGN(4); + } > rom + __text_end = .; + + .rodata : { + *(.rodata .rodata.*); + . = ALIGN(4); + } > rom + + __iwram_rom_start = .; + .iwram : { + __iwram_data_start = ABSOLUTE(.); + + *(.iwram .iwram.*); + . = ALIGN(4); + + *(.text_iwram .text_iwram.*); + . = ALIGN(4); + + __iwram_data_end = ABSOLUTE(.); + } > iwram AT>rom + + . = __iwram_rom_start + (__iwram_data_end - __iwram_data_start); + + __ewram_rom_start = .; + .ewram : { + __ewram_data_start = ABSOLUTE(.); + + *(.ewram .ewram.*); + . = ALIGN(4); + + *(.data .data.*); + . = ALIGN(4); + + __ewram_data_end = ABSOLUTE(.); + } > ewram AT>rom + + .bss : { + *(.bss .bss.*); + . = ALIGN(4); + } > iwram + + __iwram_rom_length_bytes = __iwram_data_end - __iwram_data_start; + __iwram_rom_length_halfwords = (__iwram_rom_length_bytes + 1) / 2; + + __ewram_rom_length_bytes = __ewram_data_end - __ewram_data_start; + __ewram_rom_length_halfwords = (__ewram_rom_length_bytes + 1) / 2; + + /* debugging sections */ + /* Stabs */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 : { *(.comment) } + /* DWARF 1 */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + /* GNU DWARF 1 extensions */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2 */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + /* DWARF 2 */ + .debug_info 0 : { *(.debug_info) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions */ + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + + .debug_ranges 0 : { *(.debug_ranges) } + + /* discard anything not already mentioned */ + /DISCARD/ : { *(*) } +} \ No newline at end of file diff --git a/book/games/pong/gba_mb.ld b/book/games/pong/gba_mb.ld new file mode 100644 index 00000000..9fdd23c1 --- /dev/null +++ b/book/games/pong/gba_mb.ld @@ -0,0 +1,108 @@ +OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm") +OUTPUT_ARCH(arm) + +ENTRY(__start) +EXTERN(__RUST_INTERRUPT_HANDLER) + +MEMORY { + ewram (w!x) : ORIGIN = 0x02000000, LENGTH = 256K + iwram (w!x) : ORIGIN = 0x03000000, LENGTH = 32K +} + +__text_start = ORIGIN(ewram); + +SECTIONS { + . = __text_start; + + .crt0 : { + KEEP (crt0.o(.text)); + . = ALIGN(4); + } > ewram + + .text : { + *(.text .text*); + . = ALIGN(4); + } > ewram + __text_end = .; + + .rodata : { + *(.rodata .rodata.*); + . = ALIGN(4); + } > ewram + + __iwram_rom_start = .; + .iwram : { + __iwram_data_start = ABSOLUTE(.); + + *(.iwram .iwram.*); + . = ALIGN(4); + + *(.text_iwram .text_iwram.*); + . = ALIGN(4); + + __iwram_data_end = ABSOLUTE(.); + } > iwram AT>ewram + + . = __iwram_rom_start + (__iwram_data_end - __iwram_data_start); + + __ewram_rom_start = .; + .ewram : { + __ewram_data_start = ABSOLUTE(.); + + *(.ewram .ewram.*); + . = ALIGN(4); + + *(.data .data.*); + . = ALIGN(4); + + __ewram_data_end = ABSOLUTE(.); + } > ewram AT>ewram + + .bss : { + *(.bss .bss.*); + . = ALIGN(4); + } > iwram + + __iwram_rom_length_bytes = __iwram_data_end - __iwram_data_start; + __iwram_rom_length_halfwords = (__iwram_rom_length_bytes + 1) / 2; + + __ewram_rom_length_bytes = __ewram_data_end - __ewram_data_start; + __ewram_rom_length_halfwords = (__ewram_rom_length_bytes + 1) / 2; + + /* debugging sections */ + /* Stabs */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 : { *(.comment) } + /* DWARF 1 */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + /* GNU DWARF 1 extensions */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2 */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + /* DWARF 2 */ + .debug_info 0 : { *(.debug_info) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions */ + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + + .debug_ranges 0 : { *(.debug_ranges) } + + /* discard anything not already mentioned */ + /DISCARD/ : { *(*) } +} \ No newline at end of file diff --git a/book/games/pong/rust-toolchain.toml b/book/games/pong/rust-toolchain.toml new file mode 100644 index 00000000..06842486 --- /dev/null +++ b/book/games/pong/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "nightly" +components = ["rust-src", "clippy"] \ No newline at end of file diff --git a/book/games/pong/src/main.rs b/book/games/pong/src/main.rs new file mode 100644 index 00000000..6bd8c2fe --- /dev/null +++ b/book/games/pong/src/main.rs @@ -0,0 +1,21 @@ +#![no_std] +#![no_main] + +extern crate agb; +use agb::{display, syscall}; + +#[agb::entry] +fn main() -> ! { + let mut gba = agb::Gba::new(); + 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(); + } +} From 129138e388e603a9e59128e6661909d46f803afb Mon Sep 17 00:00:00 2001 From: GBA bot Date: Sat, 1 Jan 2022 22:38:56 +0000 Subject: [PATCH 03/28] Update the name of the pong game crate --- book/games/pong/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/games/pong/Cargo.toml b/book/games/pong/Cargo.toml index d6bb0791..78b975f6 100644 --- a/book/games/pong/Cargo.toml +++ b/book/games/pong/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "agb_template" +name = "pong" version = "0.1.0" authors = [""] edition = "2018" From 8a197dbd2b73ea9375b5210e1825819a204f5012 Mon Sep 17 00:00:00 2001 From: GBA bot Date: Sat, 1 Jan 2022 22:46:37 +0000 Subject: [PATCH 04/28] Build the pong example from the book as a real example --- .github/scripts/build-example-gba-files.sh | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/.github/scripts/build-example-gba-files.sh b/.github/scripts/build-example-gba-files.sh index d57c00b2..c1a79701 100755 --- a/.github/scripts/build-example-gba-files.sh +++ b/.github/scripts/build-example-gba-files.sh @@ -6,26 +6,28 @@ set -x # print every command before it runs # Requires gbafix and arm-none-eabi-objcopy to already be installed function build_rom() { - local GAME_NAME="$1" + local GAME_FOLDER="$1" local INTERNAL_NAME="$2" - local TARGET_FOLDER="${CARGO_TARGET_DIR:-target}" + local GAME_NAME + GAME_NAME="$(basename "$GAME_FOLDER")" + + local TARGET_FOLDER="${CARGO_TARGET_DIR:-$GAME_FOLDER/target}" local GBA_FILE="$TARGET_FOLDER/$GAME_NAME.gba" - pushd "examples/$GAME_NAME" - cargo build --release --verbose --target thumbv4t-none-eabi + (cd "$GAME_FOLDER" && cargo build --release --verbose --target thumbv4t-none-eabi) arm-none-eabi-objcopy -O binary "$TARGET_FOLDER/thumbv4t-none-eabi/release/$GAME_NAME" "$GBA_FILE" gbafix -p "-t${INTERNAL_NAME:0:12}" "-c${INTERNAL_NAME:0:4}" -mGC "$GBA_FILE" - cp -v "$GBA_FILE" "../$GAME_NAME.gba" - - popd + cp -v "$GBA_FILE" "examples/$GAME_NAME.gba" } mkdir -p examples/target -build_rom "the-purple-night" "PURPLENIGHT" -build_rom "the-hat-chooses-the-wizard" "HATWIZARD" +build_rom "examples/the-purple-night" "PURPLENIGHT" +build_rom "examples/the-hat-chooses-the-wizard" "HATWIZARD" + +build_rom "book/games/pong" "PONG" zip examples/target/examples.zip examples/*.gba \ No newline at end of file From df1d016235316eaec34269a4a2561d23a9585af9 Mon Sep 17 00:00:00 2001 From: GBA bot Date: Sat, 1 Jan 2022 22:47:01 +0000 Subject: [PATCH 05/28] Use the local copy of agb rather than the version from crates.io --- book/games/pong/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/book/games/pong/Cargo.toml b/book/games/pong/Cargo.toml index 78b975f6..ec3ca205 100644 --- a/book/games/pong/Cargo.toml +++ b/book/games/pong/Cargo.toml @@ -1,13 +1,13 @@ [package] name = "pong" version = "0.1.0" -authors = [""] +authors = ["Gwilym Kuiper "] edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -agb = "0.8.0" +agb = { version = "0.8.0", path = "../../../agb" } [profile.release] panic = "abort" From 70e91e496c3d17d2da20d69e0e02a8c13cad68f4 Mon Sep 17 00:00:00 2001 From: GBA bot Date: Sat, 1 Jan 2022 22:47:25 +0000 Subject: [PATCH 06/28] Update all the example lock files --- book/games/pong/Cargo.lock | 336 ++++++++++++++++++ .../the-hat-chooses-the-wizard/Cargo.lock | 16 +- examples/the-purple-night/Cargo.lock | 16 +- 3 files changed, 352 insertions(+), 16 deletions(-) create mode 100644 book/games/pong/Cargo.lock diff --git a/book/games/pong/Cargo.lock b/book/games/pong/Cargo.lock new file mode 100644 index 00000000..2582c2b3 --- /dev/null +++ b/book/games/pong/Cargo.lock @@ -0,0 +1,336 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + +[[package]] +name = "agb" +version = "0.8.0" +dependencies = [ + "agb_image_converter", + "agb_macros", + "agb_sound_converter", + "bitflags", +] + +[[package]] +name = "agb_image_converter" +version = "0.6.0" +dependencies = [ + "image", + "proc-macro2", + "quote", + "serde", + "syn", + "toml", +] + +[[package]] +name = "agb_macros" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "rand", + "syn", +] + +[[package]] +name = "agb_sound_converter" +version = "0.1.0" +dependencies = [ + "hound", + "proc-macro2", + "quote", + "siphasher", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bytemuck" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439989e6b8c38d1b6570a384ef1e49c8848128f5a97f3914baef02920842712f" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "crc32fast" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "738c290dfaea84fc1ca15ad9c168d083b05a714e1efddd8edaab678dc28d2836" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "deflate" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" +dependencies = [ + "adler32", + "byteorder", +] + +[[package]] +name = "getrandom" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hound" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a164bb2ceaeff4f42542bdb847c41517c78a60f5649671b2a07312b6e117549" + +[[package]] +name = "image" +version = "0.23.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-iter", + "num-rational", + "num-traits", + "png", +] + +[[package]] +name = "libc" +version = "0.2.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" + +[[package]] +name = "miniz_oxide" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" +dependencies = [ + "adler32", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "png" +version = "0.16.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" +dependencies = [ + "bitflags", + "crc32fast", + "deflate", + "miniz_oxide", +] + +[[package]] +name = "pong" +version = "0.1.0" +dependencies = [ + "agb", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "proc-macro2" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" +dependencies = [ + "rand_core", +] + +[[package]] +name = "serde" +version = "1.0.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97565067517b60e2d1ea8b268e59ce036de907ac523ad83a0475da04e818989a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed201699328568d8d08208fdd080e3ff594e6c422e438b6705905da01005d537" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "siphasher" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b" + +[[package]] +name = "syn" +version = "1.0.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecb2e6da8ee5eb9a61068762a32fa9619cc591ceb055b3687f4cd4051ec2e06b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" diff --git a/examples/the-hat-chooses-the-wizard/Cargo.lock b/examples/the-hat-chooses-the-wizard/Cargo.lock index 2e3fc48c..8a6e299e 100644 --- a/examples/the-hat-chooses-the-wizard/Cargo.lock +++ b/examples/the-hat-chooses-the-wizard/Cargo.lock @@ -59,9 +59,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "bitflags" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bytemuck" @@ -220,18 +220,18 @@ checksum = "c3ca011bd0129ff4ae15cd04c4eef202cadf6c51c21e47aba319b4e0501db741" [[package]] name = "proc-macro2" -version = "1.0.28" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" dependencies = [ "unicode-xid", ] [[package]] name = "quote" -version = "1.0.9" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d" dependencies = [ "proc-macro2", ] @@ -321,9 +321,9 @@ checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b" [[package]] name = "syn" -version = "1.0.74" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c" +checksum = "ecb2e6da8ee5eb9a61068762a32fa9619cc591ceb055b3687f4cd4051ec2e06b" dependencies = [ "proc-macro2", "quote", diff --git a/examples/the-purple-night/Cargo.lock b/examples/the-purple-night/Cargo.lock index 119320da..6d89970f 100644 --- a/examples/the-purple-night/Cargo.lock +++ b/examples/the-purple-night/Cargo.lock @@ -68,9 +68,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bytemuck" @@ -250,18 +250,18 @@ checksum = "c3ca011bd0129ff4ae15cd04c4eef202cadf6c51c21e47aba319b4e0501db741" [[package]] name = "proc-macro2" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" dependencies = [ "unicode-xid", ] [[package]] name = "quote" -version = "1.0.10" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d" dependencies = [ "proc-macro2", ] @@ -340,9 +340,9 @@ checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b" [[package]] name = "syn" -version = "1.0.81" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" +checksum = "ecb2e6da8ee5eb9a61068762a32fa9619cc591ceb055b3687f4cd4051ec2e06b" dependencies = [ "proc-macro2", "quote", From f307db79e2d07a3870398dff278c74f98be07ca1 Mon Sep 17 00:00:00 2001 From: GBA bot Date: Sat, 1 Jan 2022 22:49:07 +0000 Subject: [PATCH 07/28] Rename to 01_introduction --- book/src/SUMMARY.md | 2 +- book/src/pong/{introduction.md => 01_introduction.md} | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) rename book/src/pong/{introduction.md => 01_introduction.md} (93%) diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 2ea8f618..7e96fb03 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -8,4 +8,4 @@ - [Windows setup]() - [Mac OS setup]() - [Building the game](./setup/building.md) -- [Learn agb part I - pong](./pong/introduction.md) \ No newline at end of file +- [Learn agb part I - pong](./pong/01_introduction.md) diff --git a/book/src/pong/introduction.md b/book/src/pong/01_introduction.md similarity index 93% rename from book/src/pong/introduction.md rename to book/src/pong/01_introduction.md index f27d5a21..85187fac 100644 --- a/book/src/pong/introduction.md +++ b/book/src/pong/01_introduction.md @@ -3,6 +3,7 @@ In this section, we'll make a simple pong style game for the Game Boy Advance using `agb`. You will learn: +* How to use tiled graphics modes. * How to import graphics using `agb`. * What Game Boy Advance sprites are and how to put them on the screen. * How to detect button input and react to it. From 5bcb86c222e57d5ae8ec5a952630209c7200a817 Mon Sep 17 00:00:00 2001 From: GBA bot Date: Sat, 1 Jan 2022 23:15:07 +0000 Subject: [PATCH 08/28] Start writing about the gba struct --- book/src/SUMMARY.md | 1 + book/src/pong/01_introduction.md | 21 ++++++++++++++++++++- book/src/pong/02_the_gba_struct.md | 3 +++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 book/src/pong/02_the_gba_struct.md diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 7e96fb03..d6dbc84f 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -9,3 +9,4 @@ - [Mac OS setup]() - [Building the game](./setup/building.md) - [Learn agb part I - pong](./pong/01_introduction.md) + - [The Gba struct](./pong/02_the_gba_struct.md) \ No newline at end of file diff --git a/book/src/pong/01_introduction.md b/book/src/pong/01_introduction.md index 85187fac..08ce3fcc 100644 --- a/book/src/pong/01_introduction.md +++ b/book/src/pong/01_introduction.md @@ -12,4 +12,23 @@ You will learn: * How to add music to your game. * How to add sound effects to your game. -With this knowledge, you'll be well equipped to start making your own games! \ No newline at end of file +With this knowledge, you'll be well equipped to start making your own games! + +## Getting started + +To start, create a new repository based on the [agb template](https://github.com/agbrs/template). +We'll call this `pong`. + +Then replace the `name` field in `Cargo.toml` with `pong`, to end up with something similar to: + +```toml +[package] +name = "pong" +version = "0.1.0" +authors = ["Your name here"] +edition = "2018" + +# ... +``` + +You are now ready to get started learning about how `agb` works. \ No newline at end of file diff --git a/book/src/pong/02_the_gba_struct.md b/book/src/pong/02_the_gba_struct.md new file mode 100644 index 00000000..763868c1 --- /dev/null +++ b/book/src/pong/02_the_gba_struct.md @@ -0,0 +1,3 @@ +# The Gba struct + +Almost all interaction with the Game Boy Advance's hardware goes through the [Gba singleton struct](https://docs.rs/agb/latest/agb/struct.Gba.html). From 618929939f90832ac44d63db8c69fe4cd01dd023 Mon Sep 17 00:00:00 2001 From: GBA bot Date: Sat, 1 Jan 2022 23:15:14 +0000 Subject: [PATCH 09/28] Document the Gba struct --- agb/src/lib.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/agb/src/lib.rs b/agb/src/lib.rs index 3b973565..1ab5a937 100644 --- a/agb/src/lib.rs +++ b/agb/src/lib.rs @@ -59,14 +59,57 @@ fn panic_implementation(info: &core::panic::PanicInfo) -> ! { static mut GBASINGLE: single::Singleton = single::Singleton::new(unsafe { Gba::single_new() }); +/// The Gba struct is used to control access to the Game Boy Advance's hardware in a way which makes it the +/// borrow checker's responsibility to ensure no clashes of global resources. +/// +/// This is typically created once at the start of the main function and then the various fields are used +/// to ensure mutually exclusive use of the various hardware registers. It provides a gateway into the main +/// usage of `agb` library. +/// +/// # Panics +/// +/// Calling this twice will panic. +/// +/// # Examples +/// +/// ``` +/// #![no_std] +/// #![no_main] +/// +/// extern crate agb; +/// +/// use agb::Gba; +/// +/// #[agb::entry] +/// fn main() -> ! { +/// let mut gba = Gba::new(); +/// +/// // Do whatever you need to do with gba +/// +/// loop {} +/// } +/// ``` +#[non_exhaustive] pub struct Gba { + /// Manages access to the Game Boy Advance's display hardware pub display: display::Display, + /// Manages access to the Game Boy Advance's beeps and boops sound hardware as part of the + /// original Game Boy's sound chip (the DMG). pub sound: sound::dmg::Sound, + /// Manages access to the Game Boy Advance's direct sound mixer for playing raw wav files. pub mixer: sound::mixer::MixerController, + /// Manages access to the Game Boy Advance's 4 timers. pub timers: timer::TimerController, } impl Gba { + /// Creates a new instance of the Gba struct. + /// + /// Note that you can only create 1 instance, and trying to create a second will panic. + /// + /// # Panics + /// + /// Panics if you try to create the second instance. pub fn new() -> Self { unsafe { GBASINGLE.take() } } From a1c4af36607ccde4c8e7b272ff3da178d7afeea4 Mon Sep 17 00:00:00 2001 From: GBA bot Date: Sat, 1 Jan 2022 23:29:34 +0000 Subject: [PATCH 10/28] Fill in a bit about the Gba struct --- book/src/pong/02_the_gba_struct.md | 39 ++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/book/src/pong/02_the_gba_struct.md b/book/src/pong/02_the_gba_struct.md index 763868c1..4a8f83ed 100644 --- a/book/src/pong/02_the_gba_struct.md +++ b/book/src/pong/02_the_gba_struct.md @@ -1,3 +1,42 @@ # The Gba struct +In this section, we'll cover the importance of the Gba struct and how to create it. + +# The importance of the Gba struct + Almost all interaction with the Game Boy Advance's hardware goes through the [Gba singleton struct](https://docs.rs/agb/latest/agb/struct.Gba.html). +Your games using `agb` will typically create this in the `main` function and then handle the abstractions in there. + +The Gba struct is used to take advantage of rust's borrow checker, and lean on it to ensure that access to the Game Boy Advance hardware is done 'sensibly'. +You won't have to worry about 2 bits of your code modifying data in the wrong way! +This struct is a 'singleton', so you cannot create 2 instances of it at once. +Attempting to do so will result in a panic which by default crashes the game. + +# How all agb games start + +Replace the content of the `main` function with the following: + +```rust,ignore +# #![no_std] +# #![no_main] +# extern crate agb; +# #[agb::entry] +# fn main() -> ! { +let mut gba = agb::Gba::new(); + +loop {} // infinite loop for now +# } +``` + +and ignore warnings for now. + +# Running your pong game + +Although there isn't much to see at the moment (just a black screen), you can start the game by using `cargo run` or whatever worked for you in the introduction. + +# What we did + +This was a very simple but incredibly important part of any game using `agb`. +All interactions with the hardware are gated via the Gba struct, so it must be created at the start of your `main` function and never again. + +You are now ready to learn about display modes and how to start getting things onto the screen! \ No newline at end of file From f178bfc1aff8307294268843c99ff061e567a4a9 Mon Sep 17 00:00:00 2001 From: GBA bot Date: Sat, 1 Jan 2022 23:36:53 +0000 Subject: [PATCH 11/28] Add some comments to the template's main.rs --- template/src/main.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/template/src/main.rs b/template/src/main.rs index 6bd8c2fe..9edf921a 100644 --- a/template/src/main.rs +++ b/template/src/main.rs @@ -1,9 +1,24 @@ +// 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 in order to ensure that the panic handler defined in `agb` is set +// up correctly. extern crate agb; + use agb::{display, syscall}; +// 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() -> ! { let mut gba = agb::Gba::new(); From b6b3cf24913eea536c20df40c8c29fbef915cbb4 Mon Sep 17 00:00:00 2001 From: GBA bot Date: Sat, 1 Jan 2022 23:38:13 +0000 Subject: [PATCH 12/28] Update the pong example to have the same comments as there are now in the template --- book/games/pong/src/main.rs | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/book/games/pong/src/main.rs b/book/games/pong/src/main.rs index 6bd8c2fe..9cc6f4f9 100644 --- a/book/games/pong/src/main.rs +++ b/book/games/pong/src/main.rs @@ -1,21 +1,25 @@ +// 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 in order to ensure that the panic handler defined in `agb` is set +// up correctly. extern crate agb; -use agb::{display, syscall}; +// 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() -> ! { let mut gba = agb::Gba::new(); - 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(); - } + loop {} } From d483a84702bedb56d4912db55baf1c9799b8cfd2 Mon Sep 17 00:00:00 2001 From: GBA bot Date: Sat, 1 Jan 2022 23:39:31 +0000 Subject: [PATCH 13/28] Also update the book examples --- release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release.sh b/release.sh index eac4c406..599985db 100755 --- a/release.sh +++ b/release.sh @@ -78,7 +78,7 @@ if [ "$PROJECT" = "agb" ]; then sed -i -e "s/^agb = \".*\"/agb = \"$VERSION\"/" template/Cargo.toml git add template/Cargo.toml - for EXAMPLE_DIR in examples/*/; do + for EXAMPLE_DIR in examples/*/ book/games/*/; do sed -E -i -e "/agb =/ s/version = \"[^\"]+\"/version = \"$VERSION\"/" "$EXAMPLE_DIR/Cargo.toml" (cd "$EXAMPLE_DIR" && cargo update) git add "$EXAMPLE_DIR"/{Cargo.toml,Cargo.lock} From e02fda5fc9f7a0896f196d4ad5abacbc8c5bf28a Mon Sep 17 00:00:00 2001 From: GBA bot Date: Sun, 2 Jan 2022 00:11:07 +0000 Subject: [PATCH 14/28] Start the sprites chapter --- book/games/pong/gfx/sprites.aseprite | Bin 0 -> 1012 bytes book/games/pong/gfx/sprites.png | Bin 0 -> 438 bytes book/games/pong/gfx/sprites.toml | 6 ++++++ book/src/SUMMARY.md | 3 ++- book/src/pong/03_sprites.md | 30 +++++++++++++++++++++++++++ book/src/pong/sprites.png | Bin 0 -> 438 bytes 6 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 book/games/pong/gfx/sprites.aseprite create mode 100644 book/games/pong/gfx/sprites.png create mode 100644 book/games/pong/gfx/sprites.toml create mode 100644 book/src/pong/03_sprites.md create mode 100644 book/src/pong/sprites.png diff --git a/book/games/pong/gfx/sprites.aseprite b/book/games/pong/gfx/sprites.aseprite new file mode 100644 index 0000000000000000000000000000000000000000..1c5bb9dc052f49f5e4a0a086f96099deee9a4125 GIT binary patch literal 1012 zcmcJNSxgf_9EXRasbHeS1K~kgK*0nMv_|A`tAL0Qu+fkRN(c%q6+#nKuwWxcu%Nz} zSO{Xk3y2bFRS-qOArC|wzymM96ey;oS`|uZ0Ac-YQQ^f$|C{-CGdnZCZ)SJWhLE9Z zE8;)|gd$dikYu#QLLb{nmf`vzbqe`2<6KKB7XglFT*?x;B(5LN&J^{#$7RVILRuqz z8A9G?g@XFNyWnU|Ciqw)08i2ZAnmyoyy?LMM}xvZbCw%8U%#EDE(tu|6%EoQ5unG> z5YRc823N(DfTVl?^d2*T42qC;^&oh$q5+H-7lVOeLeRsDUEgWVCXgZQbL!4?uu;H{ znL3>Vjy08n&x*v%2`p{Po;!toU=!E@^x+P1SVI}UkcBBU;Rr$4K@DDzVvQIEE)anQ z6p(KT4?`?65r{NIxv@zUj{9#xf!LE{NypPfnUd2P$^RN5lw_tXjNoGihgw*Gv0k`C z9skamYlcoMJNzf@ew`oNUU$2utAtUpe@bQ|ic5+pIrho1d|Yd?INgE7u+tS!BW?Dq zIj?i|cC6c6;(w_*)!zMZ(XtEo6K%%}sMNG1-(sn4P{+F$T$OpPH^-%;XQNSV_mC?R zJrspI2#4Hr12l0$9uu?et>AgFqH&AU`jr>_^qCQ|=Bk9j{Q1tP70nrw6C>ZhRTb=? zi@#UB{UBp9$gYdCd-Bz_f%?^RWBd3sub3*@5Gv=@wKtxtOfI)BYuc^Pu3cyKiTR+< zQ+|p2G1qdrP~VePdL>-{W?DNKsG6zj;;ZNzviHFu(~sz|&6;;|ty=d*ZXOn_zn6XI zz_i$NdAq_zD-F0ilQX6%{2VxB8t9f*5BKuqef}DkM5VSxSP`im&@mN0O+Ry;`^1h-Q$Vf2)p;II>GsR+DGuO+{0tfU_G?j9=Bt stGbu1UcIrhc>a#I^vclL`rz4#gxM!Py|Hte2j0((x91&3D+LX|0JVH@d;kCd literal 0 HcmV?d00001 diff --git a/book/games/pong/gfx/sprites.png b/book/games/pong/gfx/sprites.png new file mode 100644 index 0000000000000000000000000000000000000000..9964bf0b6400be2a60e1778c6ee65dbb0f91467f GIT binary patch literal 438 zcmV;n0ZIOeP)Px$a7jc#R9J=Wm`_T>Kop0++Cq1NRTo(a*|@Qwt`yx^yn+`{=n(|3&=ZK@3A}?M zC4%!x&uJ)c;SYiN>C-J z5>yGQ1pg^PO`Z?Re>>kT#}($co&jiXw6uVr+t*0!2~_1?Yj(2dW*dOPNu-guKO;%| zx)}n{B+9(+kKp4v1d?tjlEmts2+rR|0CX1TG?KJA>ca0Vm_84O!%x8Flc*LdV?bHc zXj)O4dp&sB{{|8rkn``JAl?6w<}ir1W0Lk&CW*f;Ow$2>1o=8pFNi>rB$hoR_5^YY z<(HG3fA<7xk*5dVr!jRtkS5Oy#m$1LKKT2CVX6=QRase{KKLh?rVqvKL7qOiC-|#A g_}c?heXu6@0SQNyXlYPJ`Tzg`07*qoM6N<$g20Wxl>h($ literal 0 HcmV?d00001 diff --git a/book/games/pong/gfx/sprites.toml b/book/games/pong/gfx/sprites.toml new file mode 100644 index 00000000..39c68850 --- /dev/null +++ b/book/games/pong/gfx/sprites.toml @@ -0,0 +1,6 @@ +version = "1.0" + +[image.sprites] +filename = "sprites.png" +tile_size = "16x16" +transparent_colour = "ff0044" \ No newline at end of file diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index d6dbc84f..0b00be0d 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -9,4 +9,5 @@ - [Mac OS setup]() - [Building the game](./setup/building.md) - [Learn agb part I - pong](./pong/01_introduction.md) - - [The Gba struct](./pong/02_the_gba_struct.md) \ No newline at end of file + - [The Gba struct](./pong/02_the_gba_struct.md) + - [Sprites](./pong/03_sprites.md) \ No newline at end of file diff --git a/book/src/pong/03_sprites.md b/book/src/pong/03_sprites.md new file mode 100644 index 00000000..deb012fb --- /dev/null +++ b/book/src/pong/03_sprites.md @@ -0,0 +1,30 @@ +# Sprites + +In this section, we'll put the sprites needed for our pong game onto the screen. + +# Import the sprite + +Firstly, you're going to need to import the sprites into your project. +Save the following image into a new folder called `gfx` in your project: + +![pong sprites](sprites.png) + +This contains 5 `16x16px` sprites. +The first is the end cap for the paddle. +The second is the centre part of the paddle, which could potentially be repeated a few times. +The third until the fifth is the ball, with various squashed states. +The background is a lovely shade of `#ff0044` which we will use for the transparency. + +`agb` needs to know what the tile size is, and also what the transparent colour is so that it can properly produce data that the Game Boy Advance can understand. +So you need to create a manifest file for the image, which is declared in a toml file. + +In the same `gfx` folder as the `sprites.png` file, also create a `sprites.toml` file with the following content: + +```toml +version = "1.0" + +[image.sprites] +filename = "sprites.png" +tile_size = "16x16" +transparent_colour = "ff0044" +``` \ No newline at end of file diff --git a/book/src/pong/sprites.png b/book/src/pong/sprites.png new file mode 100644 index 0000000000000000000000000000000000000000..9964bf0b6400be2a60e1778c6ee65dbb0f91467f GIT binary patch literal 438 zcmV;n0ZIOeP)Px$a7jc#R9J=Wm`_T>Kop0++Cq1NRTo(a*|@Qwt`yx^yn+`{=n(|3&=ZK@3A}?M zC4%!x&uJ)c;SYiN>C-J z5>yGQ1pg^PO`Z?Re>>kT#}($co&jiXw6uVr+t*0!2~_1?Yj(2dW*dOPNu-guKO;%| zx)}n{B+9(+kKp4v1d?tjlEmts2+rR|0CX1TG?KJA>ca0Vm_84O!%x8Flc*LdV?bHc zXj)O4dp&sB{{|8rkn``JAl?6w<}ir1W0Lk&CW*f;Ow$2>1o=8pFNi>rB$hoR_5^YY z<(HG3fA<7xk*5dVr!jRtkS5Oy#m$1LKKT2CVX6=QRase{KKLh?rVqvKL7qOiC-|#A g_}c?heXu6@0SQNyXlYPJ`Tzg`07*qoM6N<$g20Wxl>h($ literal 0 HcmV?d00001 From acee6a3c51120e4602d5ae5372b4a4bb9fa0bf2d Mon Sep 17 00:00:00 2001 From: GBA bot Date: Sun, 2 Jan 2022 00:21:59 +0000 Subject: [PATCH 15/28] Import the sprites in the pong code --- book/games/pong/src/main.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/book/games/pong/src/main.rs b/book/games/pong/src/main.rs index 9cc6f4f9..121f2950 100644 --- a/book/games/pong/src/main.rs +++ b/book/games/pong/src/main.rs @@ -14,6 +14,10 @@ // up correctly. extern crate agb; +mod gfx { + agb::include_gfx!("gfx/sprites.toml"); +} + // 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. @@ -21,5 +25,9 @@ extern crate agb; fn main() -> ! { let mut gba = agb::Gba::new(); + let mut object = gba.display.object.get(); + object.set_sprite_palettes(gfx::sprites::sprites.palettes); + object.set_sprite_tilemap(gfx::sprites::sprites.tiles); + loop {} } From 26400c97e843bc3187f06f3937c8c4b2419b7d9c Mon Sep 17 00:00:00 2001 From: GBA bot Date: Sun, 2 Jan 2022 00:27:53 +0000 Subject: [PATCH 16/28] Extract a function for loading the sprite data --- book/games/pong/src/main.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/book/games/pong/src/main.rs b/book/games/pong/src/main.rs index 121f2950..699a0a95 100644 --- a/book/games/pong/src/main.rs +++ b/book/games/pong/src/main.rs @@ -14,8 +14,22 @@ // up correctly. extern crate agb; +use agb::Gba; + +// Put all the graphics related code in the gfx module mod gfx { + use agb::display::object::ObjectControl; + + // Import the sprites into this module. This will create a `sprites` module + // and within that will be a constant called `sprites` which houses all the + // palette and tile data. agb::include_gfx!("gfx/sprites.toml"); + + // Loads the sprites tile data and palette data into VRAM + pub fn load_sprite_data(object: &mut ObjectControl) { + object.set_sprite_palettes(sprites::sprites.palettes); + object.set_sprite_tilemap(sprites::sprites.tiles); + } } // The main function must take 0 arguments and never return. The agb::entry decorator @@ -23,11 +37,10 @@ mod gfx { // and interrupt handlers correctly. #[agb::entry] fn main() -> ! { - let mut gba = agb::Gba::new(); + let mut gba = Gba::new(); let mut object = gba.display.object.get(); - object.set_sprite_palettes(gfx::sprites::sprites.palettes); - object.set_sprite_tilemap(gfx::sprites::sprites.tiles); + gfx::load_sprite_data(&mut object); loop {} } From cd1c71fc5e6debf9a0e0a5b530d16c3fb97e947f Mon Sep 17 00:00:00 2001 From: GBA bot Date: Sun, 2 Jan 2022 00:40:34 +0000 Subject: [PATCH 17/28] Put a ball on screen --- book/games/pong/src/main.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/book/games/pong/src/main.rs b/book/games/pong/src/main.rs index 699a0a95..fde37805 100644 --- a/book/games/pong/src/main.rs +++ b/book/games/pong/src/main.rs @@ -14,6 +14,7 @@ // up correctly. extern crate agb; +use agb::display::object::Size; use agb::Gba; // Put all the graphics related code in the gfx module @@ -39,8 +40,22 @@ mod gfx { fn main() -> ! { let mut gba = Gba::new(); + let _tiled = gba.display.video.tiled0(); let mut object = gba.display.object.get(); gfx::load_sprite_data(&mut object); + object.enable(); + + let mut ball = object.get_object_standard(); + + ball.set_x(50); + ball.set_y(50); + + ball.set_sprite_size(Size::S16x16); + + ball.set_tile_id(4 * 2); + + ball.show(); + ball.commit(); loop {} } From b5ff4991fa4856982865dd32cf9c781e6ce5084b Mon Sep 17 00:00:00 2001 From: GBA bot Date: Sun, 2 Jan 2022 11:14:18 +0000 Subject: [PATCH 18/28] Use builder style for ObjectStandard --- agb/src/display/object.rs | 88 ++++++++++++++++++--------- book/games/pong/src/main.rs | 15 ++--- examples/the-purple-night/src/main.rs | 8 +-- 3 files changed, 68 insertions(+), 43 deletions(-) diff --git a/agb/src/display/object.rs b/agb/src/display/object.rs index 83a72051..4b8301bb 100644 --- a/agb/src/display/object.rs +++ b/agb/src/display/object.rs @@ -91,49 +91,77 @@ impl ObjectStandard<'_> { } /// Sets the x coordinate of the sprite on screen. - pub fn set_x(&mut self, x: u16) { - self.attributes.set_x(x) - } - /// Sets the y coordinate of the sprite on screen. - pub fn set_y(&mut self, y: u16) { - self.attributes.set_y(y) - } - /// Sets the index of the tile to use as the sprite. Potentially a temporary function. - pub fn set_tile_id(&mut self, id: u16) { - self.attributes.set_tile_id(id) - } - /// Sets whether the sprite is horizontally mirrored or not. - pub fn set_hflip(&mut self, hflip: bool) { - self.attributes.set_hflip(hflip) - } - /// Sets the sprite size, will read tiles in x major order to construct this. - pub fn set_sprite_size(&mut self, size: Size) { - self.attributes.set_size(size); - } - /// Show the object on screen. - pub fn show(&mut self) { - self.attributes.set_mode(Mode::Normal) - } - /// Hide the object and do not render. - pub fn hide(&mut self) { - self.attributes.set_mode(Mode::Hidden) + pub fn set_x(&mut self, x: u16) -> &mut Self { + self.attributes.set_x(x); + + self } - pub fn set_palette(&mut self, palette: u16) { + /// Sets the y coordinate of the sprite on screen. + pub fn set_y(&mut self, y: u16) -> &mut Self { + self.attributes.set_y(y); + + self + } + + /// Sets the index of the tile to use as the sprite. Potentially a temporary function. + pub fn set_tile_id(&mut self, id: u16) -> &mut Self { + self.attributes.set_tile_id(id); + + self + } + + /// Sets whether the sprite is horizontally mirrored or not. + pub fn set_hflip(&mut self, hflip: bool) -> &mut Self { + self.attributes.set_hflip(hflip); + + self + } + + /// Sets the sprite size, will read tiles in x major order to construct this. + pub fn set_sprite_size(&mut self, size: Size) -> &mut Self { + self.attributes.set_size(size); + + self + } + + /// Show the object on screen. + pub fn show(&mut self) -> &mut Self { + self.attributes.set_mode(Mode::Normal); + + self + } + + /// Hide the object and do not render. + pub fn hide(&mut self) -> &mut Self { + self.attributes.set_mode(Mode::Hidden); + + self + } + + /// Sets the palette to use for this sprite + pub fn set_palette(&mut self, palette: u16) -> &mut Self { self.attributes.set_palette(palette); + + self } /// Sets the x and y position of the object, performing casts as nessesary /// to fit within the bits allocated for this purpose. - pub fn set_position(&mut self, position: Vector2D) { + pub fn set_position(&mut self, position: Vector2D) -> &mut Self { let x = position.x as u16; let y = position.y as u16; self.attributes.set_x(x); self.attributes.set_y(y); + + self } - pub fn set_priority(&mut self, p: Priority) { - self.attributes.set_priority(p) + /// Sets the priority (used for z ordering) of this sprite + pub fn set_priority(&mut self, p: Priority) -> &mut Self { + self.attributes.set_priority(p); + + self } } diff --git a/book/games/pong/src/main.rs b/book/games/pong/src/main.rs index fde37805..0df2e5da 100644 --- a/book/games/pong/src/main.rs +++ b/book/games/pong/src/main.rs @@ -47,15 +47,12 @@ fn main() -> ! { let mut ball = object.get_object_standard(); - ball.set_x(50); - ball.set_y(50); - - ball.set_sprite_size(Size::S16x16); - - ball.set_tile_id(4 * 2); - - ball.show(); - ball.commit(); + ball.set_x(50) + .set_y(50) + .set_sprite_size(Size::S16x16) + .set_tile_id(4 * 2) + .show() + .commit(); loop {} } diff --git a/examples/the-purple-night/src/main.rs b/examples/the-purple-night/src/main.rs index 118c1196..170f6108 100644 --- a/examples/the-purple-night/src/main.rs +++ b/examples/the-purple-night/src/main.rs @@ -1578,24 +1578,24 @@ impl<'a> FollowingBoss<'a> { } let frame = (self.timer / 8) % 12; - self.entity.sprite.set_tile_id((125 + frame as u16) * 4) + self.entity.sprite.set_tile_id((125 + frame as u16) * 4); } else if self.timer < 120 { let frame = (self.timer / 20) % 12; - self.entity.sprite.set_tile_id((125 + frame as u16) * 4) + self.entity.sprite.set_tile_id((125 + frame as u16) * 4); } else if self.following { self.entity.velocity = difference / 16; if difference.manhattan_distance() < 20.into() { self.following = false; } let frame = (self.timer / 8) % 12; - self.entity.sprite.set_tile_id((125 + frame as u16) * 4) + self.entity.sprite.set_tile_id((125 + frame as u16) * 4); } else { self.entity.velocity = (0, 0).into(); if difference.manhattan_distance() > 60.into() { self.following = true; } let frame = (self.timer / 16) % 12; - self.entity.sprite.set_tile_id((125 + frame as u16) * 4) + self.entity.sprite.set_tile_id((125 + frame as u16) * 4); } self.entity.update_position_without_collision(); } From a39d5cfd6994c1b6c364a8d2bd6095d2bfe3aa97 Mon Sep 17 00:00:00 2001 From: GBA bot Date: Sun, 2 Jan 2022 11:27:26 +0000 Subject: [PATCH 19/28] Add some more documentation --- agb/src/display/object.rs | 60 ++++++++++++++++++++++++++++++++++++- book/games/pong/src/main.rs | 5 ++-- 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/agb/src/display/object.rs b/agb/src/display/object.rs index 4b8301bb..e980dff6 100644 --- a/agb/src/display/object.rs +++ b/agb/src/display/object.rs @@ -15,7 +15,30 @@ const PALETTE_SPRITE: MemoryMapped1DArray = const TILE_SPRITE: MemoryMapped1DArray = unsafe { MemoryMapped1DArray::new(0x06010000) }; -/// Handles distributing objects and matricies along with operations that effect all objects. +/// Handles distributing objects and matrices along with operations that effect all objects. +/// You can create an instance of this using the Gba struct. +/// +/// This handles distribution of sprites, ensuring that object ids are not reused and are +/// returned to the pool once you're done handling them. +/// +/// # Examples +/// +/// ``` +/// # #![no_std] +/// # #![no_main] +/// # +/// # extern crate agb; +/// # +/// # use agb::Gba; +/// # +/// # #[agb::entry] +/// # fn main() -> ! { +/// let mut gba = Gba::new(); +/// let mut object = gba.display.object.get(); +/// # +/// # loop {} +/// # } +/// ``` pub struct ObjectControl { objects: RefCell>, affines: AffineArena, @@ -27,6 +50,41 @@ struct ObjectLoan<'a> { } /// The standard object, without rotation. +/// +/// You should create this from an instance of ObjectControl created using the Gba struct. Note that +/// no changes made to this will be visible until `commit()` is called. You should call `commit()` during +/// vblank to ensure that you get no visual artifacts. +/// +/// This struct implements a sort of builder pattern, allowing you to chain settings together. +/// +/// # Examples +/// +/// ``` +/// # #![no_std] +/// # #![no_main] +/// # +/// # extern crate agb; +/// # +/// # use agb::Gba; +/// use agb::display::object::Size; +/// +/// # #[agb::entry] +/// # fn main() -> ! { +/// # let mut gba = Gba::new(); +/// let mut object = gba.display.object.get(); +/// +/// let mut my_new_object = object.get_object_standard(); +/// my_new_object.set_x(50) +/// .set_y(50) +/// .set_sprite_Size(Size::S8x8) +/// .set_tile_id(7) +/// .show(); +/// +/// // some time later in vblank +/// my_new_object.commit(); +/// # loop {} +/// # } +/// ``` pub struct ObjectStandard<'a> { attributes: ObjectAttribute, loan: ObjectLoan<'a>, diff --git a/book/games/pong/src/main.rs b/book/games/pong/src/main.rs index 0df2e5da..8e3552a0 100644 --- a/book/games/pong/src/main.rs +++ b/book/games/pong/src/main.rs @@ -51,8 +51,9 @@ fn main() -> ! { .set_y(50) .set_sprite_size(Size::S16x16) .set_tile_id(4 * 2) - .show() - .commit(); + .show(); + + ball.commit(); loop {} } From eb38e2b63167aa5696fad3f8d220bb940118b372 Mon Sep 17 00:00:00 2001 From: GBA bot Date: Sun, 2 Jan 2022 17:54:44 +0000 Subject: [PATCH 20/28] Make the generated module not public --- agb-image-converter/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agb-image-converter/src/lib.rs b/agb-image-converter/src/lib.rs index c3a5fa4d..7d6df7e8 100644 --- a/agb-image-converter/src/lib.rs +++ b/agb-image-converter/src/lib.rs @@ -60,7 +60,7 @@ pub fn include_gfx(input: TokenStream) -> TokenStream { }); let module = quote! { - pub mod #module_name { + mod #module_name { const _: &[u8] = include_bytes!(#include_path); #(#image_code)* From af40d4836074bf5069090ed25cc1b1f9d5cfb8a2 Mon Sep 17 00:00:00 2001 From: GBA bot Date: Sun, 2 Jan 2022 18:11:36 +0000 Subject: [PATCH 21/28] Add section on loading graphics and document the include_gfx! macro --- agb/src/lib.rs | 95 +++++++++++++++++++++++++++++++++++++ book/src/pong/03_sprites.md | 76 +++++++++++++++++++++++++++++ 2 files changed, 171 insertions(+) diff --git a/agb/src/lib.rs b/agb/src/lib.rs index 1ab5a937..d3b03a90 100644 --- a/agb/src/lib.rs +++ b/agb/src/lib.rs @@ -14,6 +14,101 @@ //! internal workings of the Game Boy Advance whilst still being high //! performance and memory efficient. +/// This macro is used to convert a png or bmp into a format usable by the Game Boy Advance. +/// +/// The macro expects to be linked to a `toml` file which contains a metadata about the image +/// and a link to the png or bmp itself. See the examples below for a full definition of the format. +/// +/// # The manifest file format +/// +/// The following is an example of the toml file you would need to create. Generally you will +/// find this in the `gfx` folder in the same level as the `src` folder (see the examples). +/// +/// Suppose that the following is in `gfx/sprites.toml`. +/// +/// ```toml +/// version = "1.0" # version included for compatibility reasons +/// +/// [images.objects] +/// filename = "sprites.png" +/// tile_size = "16x16" +/// transparent_colour = "ff0044" +/// ``` +/// +/// You then import this using: +/// ```rust,ignore +/// agb::include_gfx!("gfx/sprites.toml"); +/// ``` +/// +/// This will generate something along the lines of the following: +/// +/// ``` +/// // module name comes from the name of the toml file, so `sprites` in this case because it is +/// // called `sprites.toml` +/// mod sprites { +/// const objects = /* ... */; +/// } +/// ``` +/// +/// And objects will be an instance of [`TileData`][crate::display::tile_data::TileData] +/// +/// # Examples +/// +/// ## Loading sprites: +/// +/// In `gfx/sprites.toml`: +/// ```toml +/// version = "1.0" +/// +/// [image.sprites] +/// filename = "sprites.png" +/// tile_size = "16x16" +/// transparent_colour = "ff0044" +/// ``` +/// +/// In `src/main.rs` +/// ``` +/// mod gfx { +/// use agb::display::object::ObjectControl; +/// +/// // Import the sprites into this module. This will create a `sprites` module +/// // and within that will be a constant called `sprites` which houses all the +/// // palette and tile data. +/// agb::include_gfx!("gfx/sprites.toml"); +/// +/// // Loads the sprites tile data and palette data into VRAM +/// pub fn load_sprite_data(object: &mut ObjectControl) { +/// object.set_sprite_palettes(sprites::sprites.palettes); +/// object.set_sprite_tilemap(sprites::sprites.tiles); +/// } +/// } +/// ``` +/// +/// ## Loading tiles: +/// +/// In `gfx/tiles.toml`: +/// ```toml +/// version = "1.0" +/// +/// [image.background] +/// filename = "tile_sheet.png" +/// tile_size = "8x8" +/// transparent_colour = "2ce8f4" +/// ``` +/// +/// In `src/main.rs`: +/// ``` +/// mod gfx { +/// use agb::display::background::BackgroundDistributor; +/// +/// agb::include_gfx!("gfx/tile_sheet.toml"); +/// +/// pub fn load_tile_sheet(tiled: &mut BackgroundDistributor) { +/// tiled.set_background_palettes(tile_sheet::background.palettes); +/// tiled.set_background_tilemap(tile_sheet::background.tiles); +/// } +/// } +/// ``` pub use agb_image_converter::include_gfx; pub use agb_macros::entry; pub use agb_sound_converter::include_wav; diff --git a/book/src/pong/03_sprites.md b/book/src/pong/03_sprites.md index deb012fb..4c00e9d4 100644 --- a/book/src/pong/03_sprites.md +++ b/book/src/pong/03_sprites.md @@ -1,9 +1,46 @@ # Sprites In this section, we'll put the sprites needed for our pong game onto the screen. +We'll cover what sprites are in the Game Boy Advance, and how to get them to show up on screen. +We'll briefly cover vblank and by the end of this section, you'll have a ball bouncing around the screen! + +# Why do we need sprites in the first place? + +The Game Boy Advance has a 240x160px screen, with 15-bit RGB colour support. +In order to manually set the colour for each pixel in the screen, you would need to update a total of 38,400 pixels per frame, or 2,304,000 pixels per second at 60 fps. +With a 16MHz processor, that means you would need to be able to calculate 1 pixel every 8 clock cycles, which is pretty much impossible. +You could get clever with how you update these pixels, but there is a much easier way which almost every game for the Game Boy Advance uses. + +So there are 2 ways that the Game Boy Advance allows you to get these pixels on screen much more easily. +Tiles and sprites. +Tiles are 8x8 pixels in size and can be placed in a grid on the screen. +You can also scroll the whole tile layer to arbitrary positions, but the tiles will remain in this 8x8 pixel grid. +We'll cover tiles in more detail later. + +The other way you can draw things on screen is using sprites, which we'll cover in more detail in this section. + +# Sprites on the Game Boy Advance + +The Game Boy Advance supports 256 hardware sprites. +These can be in one of many sizes, ranging from square 8x8 to more exotic sizes like 8x32 pixels. +For our pong game, all the sprites will be 16x16 pixels to make things a bit simpler. + +Sprites are stored in the Game Boy Advance in a special area of video memory called the 'Object Attribute Memory' (OAM). +This has space for the 'attributes' of the sprites (things like whether or not they are visible, the location, which tile to use etc) but it does not store the actual pixel data. +The pixel data is stored in a different part of video RAM (VRAM) and the OAM only stores which tiles to use from this area. + +Since RAM is in short supply, and at the time was quite expensive, the tile data is stored as indexed palette data. +So rather than storing the full colour data for each pixel in the tile, the Game Boy Advance instead stores a 'palette' of colours and the tiles which make up the sprites are stored as indexes to the palette. +You don't need to worry about this though, because `agb` handles it for you, but it is important to keep in mind that each sprite can use a maximum of 16 colours out of the total sprite palette of 256 colours. + +There are technically 2 types of sprite, regular and affine sprites. +For now, we will only be dealing with regular sprites. # Import the sprite +As mentioned above, we'll need to convert the sprite data into a format that the Game Boy Advance will be able to understand (so palette information and tile data). +Once we've converted it, we'll need to import this tile data into the Game Boy Advance's memory on start up and then create a sprite in the OAM. + Firstly, you're going to need to import the sprites into your project. Save the following image into a new folder called `gfx` in your project: @@ -27,4 +64,43 @@ version = "1.0" filename = "sprites.png" tile_size = "16x16" transparent_colour = "ff0044" +``` + +Now let's create a module in the `main.rs` file which imports the sprite sheet and loads it into memory. +Anything sprite related is managed by the [`ObjectControl` struct](https://docs.rs/agb/0.8.0/agb/display/object/struct.ObjectControl.html). +So we use that to load the sprite tile map and palette data. + +```rust +// Put all the graphics related code in the gfx module +mod gfx { + use agb::display::object::ObjectControl; + + // Import the sprites into this module. This will create a `sprites` module + // and within that will be a constant called `sprites` which houses all the + // palette and tile data. + agb::include_gfx!("gfx/sprites.toml"); + + // Loads the sprites tile data and palette data into VRAM + pub fn load_sprite_data(object: &mut ObjectControl) { + object.set_sprite_palettes(sprites::sprites.palettes); + object.set_sprite_tilemap(sprites::sprites.tiles); + } +} +``` + +This uses the `include_gfx!` macro which loads the sprite information file and grabs the relevant tile data from there. + +Now, let's put this on screen by firstly creating the object manager + +```rust +let mut gba = Gba::new(); + +// set background mode to mode 0 (we'll cover this in more detail later) +// for now, this is required in order for anything to show up on screen at all. +let _tiled = gba.display.video.tiled0(); + +// Get the OAM manager +let mut object = gba.display.object.get(); +gfx::load_sprite_data(&mut object); +object.enable(); ``` \ No newline at end of file From 02939041b47d899d65d25571b43e60da253939c4 Mon Sep 17 00:00:00 2001 From: GBA bot Date: Sun, 2 Jan 2022 18:14:37 +0000 Subject: [PATCH 22/28] Use the `entry` macro for the test main --- agb/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/agb/src/lib.rs b/agb/src/lib.rs index d3b03a90..58778505 100644 --- a/agb/src/lib.rs +++ b/agb/src/lib.rs @@ -282,9 +282,9 @@ pub fn test_runner(tests: &[&dyn Testable]) { .unwrap(); } -#[no_mangle] #[cfg(test)] -pub extern "C" fn main() -> ! { +#[entry] +fn agb_test_main() -> ! { test_main(); loop {} } From d4daf8bf0adafea439c1be5c8734196f29993828 Mon Sep 17 00:00:00 2001 From: GBA bot Date: Sun, 2 Jan 2022 18:22:13 +0000 Subject: [PATCH 23/28] Add docs for entry macro --- agb/src/lib.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/agb/src/lib.rs b/agb/src/lib.rs index 58778505..9cfb855c 100644 --- a/agb/src/lib.rs +++ b/agb/src/lib.rs @@ -110,6 +110,28 @@ /// } /// ``` pub use agb_image_converter::include_gfx; +/// This macro declares the entry point to your game written using `agb`. +/// +/// It is already included in the template, but your `main` function must be annotated with `#[agb::entry]`, take no arguments and never return. +/// Doing this will ensure that `agb` can correctly set up the environment to call your rust function on start up. +/// +/// # Examples +/// ``` +/// #![no_std] +/// #![no_main] +/// +/// // Required to set panic handlers +/// extern crate agb; +/// +/// use agb::Gba; +/// +/// #[agb::entry] +/// fn main() -> ! { +/// let mut gba = Gba::new(); +/// +/// loop {} +/// } +/// ``` pub use agb_macros::entry; pub use agb_sound_converter::include_wav; From 0b986df56350d3fe5229c54fb2baea8aad89316a Mon Sep 17 00:00:00 2001 From: GBA bot Date: Sun, 2 Jan 2022 18:25:44 +0000 Subject: [PATCH 24/28] Doc hide test runner stuff --- agb/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/agb/src/lib.rs b/agb/src/lib.rs index 9cfb855c..7bc1433a 100644 --- a/agb/src/lib.rs +++ b/agb/src/lib.rs @@ -13,6 +13,8 @@ //! programming language. It attempts to be a high level abstraction over the //! internal workings of the Game Boy Advance whilst still being high //! performance and memory efficient. +//! +//! To get started with agb, you should clone the [template repo](https://github.com/agbrs/template) and work from there. /// This macro is used to convert a png or bmp into a format usable by the Game Boy Advance. /// @@ -247,6 +249,7 @@ impl Default for Gba { } } +#[doc(hidden)] pub trait Testable { fn run(&self, gba: &mut Gba); } @@ -283,6 +286,7 @@ fn panic_implementation(info: &core::panic::PanicInfo) -> ! { loop {} } +#[doc(hidden)] pub fn test_runner(tests: &[&dyn Testable]) { let mut mgba = mgba::Mgba::new().unwrap(); mgba.print( From 99146ac0485c1d857105473b9dcb5d25fbfc83c2 Mon Sep 17 00:00:00 2001 From: GBA bot Date: Sun, 2 Jan 2022 18:27:23 +0000 Subject: [PATCH 25/28] Add doc comments for top level modules --- agb/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/agb/src/lib.rs b/agb/src/lib.rs index 7bc1433a..436a9bba 100644 --- a/agb/src/lib.rs +++ b/agb/src/lib.rs @@ -148,10 +148,12 @@ mod bitarray; pub mod display; /// Button inputs to the system. pub mod input; +#[doc(hidden)] // hide for now as the implementation in here is unsound pub mod interrupt; mod memory_mapped; /// Implements logging to the mgba emulator. pub mod mgba; +/// Implementation of fixnums for working with non-integer values. pub mod number; mod single; /// Implements sound output. From 8d13ee3a398a8e71d96b0cf0d09aa6087b28c1fb Mon Sep 17 00:00:00 2001 From: GBA bot Date: Sun, 2 Jan 2022 18:28:31 +0000 Subject: [PATCH 26/28] Add some blank lines to make code folding work better --- agb/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/agb/src/lib.rs b/agb/src/lib.rs index 436a9bba..ba365294 100644 --- a/agb/src/lib.rs +++ b/agb/src/lib.rs @@ -112,6 +112,7 @@ /// } /// ``` pub use agb_image_converter::include_gfx; + /// This macro declares the entry point to your game written using `agb`. /// /// It is already included in the template, but your `main` function must be annotated with `#[agb::entry]`, take no arguments and never return. @@ -135,6 +136,7 @@ pub use agb_image_converter::include_gfx; /// } /// ``` pub use agb_macros::entry; + pub use agb_sound_converter::include_wav; #[cfg(feature = "alloc")] From 026f057b8d479e1240df7dfcf7965eda06e8e39c Mon Sep 17 00:00:00 2001 From: GBA bot Date: Sun, 2 Jan 2022 18:46:00 +0000 Subject: [PATCH 27/28] Finish off section about bouncing a ball around the screen --- book/games/pong/src/main.rs | 23 +++++++++++- book/src/pong/03_sprites.md | 73 ++++++++++++++++++++++++++++++++++++- 2 files changed, 92 insertions(+), 4 deletions(-) diff --git a/book/games/pong/src/main.rs b/book/games/pong/src/main.rs index 8e3552a0..c90c3699 100644 --- a/book/games/pong/src/main.rs +++ b/book/games/pong/src/main.rs @@ -53,7 +53,26 @@ fn main() -> ! { .set_tile_id(4 * 2) .show(); - ball.commit(); + let mut ball_x = 50; + let mut ball_y = 50; + let mut x_velocity = 1; + let mut y_velocity = 1; - loop {} + loop { + ball_x = (ball_x + x_velocity).clamp(0, agb::display::WIDTH - 16); + ball_y = (ball_y + y_velocity).clamp(0, agb::display::HEIGHT - 16); + + 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; + } + + ball.set_x(ball_x as u16).set_y(ball_y as u16); + + agb::display::busy_wait_for_vblank(); + ball.commit(); + } } diff --git a/book/src/pong/03_sprites.md b/book/src/pong/03_sprites.md index 4c00e9d4..9f75c5e1 100644 --- a/book/src/pong/03_sprites.md +++ b/book/src/pong/03_sprites.md @@ -90,7 +90,7 @@ mod gfx { This uses the `include_gfx!` macro which loads the sprite information file and grabs the relevant tile data from there. -Now, let's put this on screen by firstly creating the object manager +Now, let's put this on screen by firstly creating the object manager and loading the sprite data from above: ```rust let mut gba = Gba::new(); @@ -103,4 +103,73 @@ let _tiled = gba.display.video.tiled0(); let mut object = gba.display.object.get(); gfx::load_sprite_data(&mut object); object.enable(); -``` \ No newline at end of file +``` + +Then, let's create the ball object and put it at 50, 50 on the screen: + +```rust +// continued from above: +let mut ball = object.get_object_standard(); // (1) + +ball.set_x(50) // (2) + .set_y(50) + .set_sprite_size(Size::S16x16) + .set_tile_id(4 * 2) + .show(); + +ball.commit(); // (3) +``` + +There are a few new things introduced here, so lets go through these 1 by 1. + +1. The first thing we're doing is creating a 'standard' object. +These are of non-affine type. +2. We now set some properties on the ball. +The position and size are self explanatory. +Interesting here is that the tile id is 4 * 2. +This is because the tile id is calculated in 8x8 pixel chunks, and in our example we have 16x16 sprites so each sprite takes 4 tiles and this is the tile in position 2 on the tilesheet above (0 indexed). +The final call to `.show()` will make it actually visible as sprites are hidden by default. +3. The call to `.commit()` actually makes the change to object memory. +Until `.commit()` is called, no changes you made will actually be visible. +This is very handy because we might want to change sprite positions etc while the frame is rendering, and then move them in the short space of time we have before the next frame starts rendering (vblank). + +If you run this you should now see the ball for this pong game somewhere in the top left of the screen. + +# Making the sprite move + +As mentioned before, you should `.commit()` your sprites only during `vblank` which is the (very short) period of time nothing is being rendered to screen. +`agb` provides a convenience function for waiting until this happens called `agb::display::busy_wait_for_vblank()`. +You shouldn't use this is a real game (we'll do it properly later on), but for now we can use this to wait for the correct time to `commit` our sprites to memory. + +Making the sprite move 1 pixel every frame (so approximately 60 pixels per second) can be done as follows: + +```rust +// replace the call to ball.commit() with the following: + +let mut ball_x = 50; +let mut ball_y = 50; +let mut x_velocity = 1; +let mut y_velocity = 1; + +loop { + ball_x = (ball_x + x_velocity).clamp(0, agb::display::WIDTH - 16); + ball_y = (ball_y + y_velocity).clamp(0, agb::display::HEIGHT - 16); + + 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; + } + + ball.set_x(ball_x as u16).set_y(ball_y as u16); + + agb::display::busy_wait_for_vblank(); + ball.commit(); +} +``` + +# What we did + +In this section, we covered why sprites are important, how to create and manage them using the `ObjectControl` in `agb` and make a ball bounce around the screen. \ No newline at end of file From 4b86c22bee8e276bfe201501326bc4c2f9d6f5d7 Mon Sep 17 00:00:00 2001 From: GBA bot Date: Sun, 2 Jan 2022 21:30:19 +0000 Subject: [PATCH 28/28] Revise sentence that didn't make much sense --- book/src/pong/03_sprites.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/src/pong/03_sprites.md b/book/src/pong/03_sprites.md index 9f75c5e1..e4ce29a9 100644 --- a/book/src/pong/03_sprites.md +++ b/book/src/pong/03_sprites.md @@ -9,7 +9,7 @@ We'll briefly cover vblank and by the end of this section, you'll have a ball bo The Game Boy Advance has a 240x160px screen, with 15-bit RGB colour support. In order to manually set the colour for each pixel in the screen, you would need to update a total of 38,400 pixels per frame, or 2,304,000 pixels per second at 60 fps. With a 16MHz processor, that means you would need to be able to calculate 1 pixel every 8 clock cycles, which is pretty much impossible. -You could get clever with how you update these pixels, but there is a much easier way which almost every game for the Game Boy Advance uses. +You could get clever with how you update these pixels, but using the tools provided by the Game Boy Advance to put pixels on the screen, you'll have a much easier time. So there are 2 ways that the Game Boy Advance allows you to get these pixels on screen much more easily. Tiles and sprites.