From b8a6cdde3a102aec8a0b458f8c4dfdb4778ddc89 Mon Sep 17 00:00:00 2001 From: Corwin Date: Sun, 14 Apr 2024 17:19:21 +0100 Subject: [PATCH] add meta sprites chapter --- book/games/pong/src/main.rs | 44 ++++++++++++- book/src/SUMMARY.md | 1 + book/src/pong/05_meta_sprites.md | 106 +++++++++++++++++++++++++++++++ 3 files changed, 148 insertions(+), 3 deletions(-) create mode 100644 book/src/pong/05_meta_sprites.md diff --git a/book/games/pong/src/main.rs b/book/games/pong/src/main.rs index be003e41..ae787cf3 100644 --- a/book/games/pong/src/main.rs +++ b/book/games/pong/src/main.rs @@ -11,7 +11,7 @@ #![no_main] use agb::{ - display::object::{Graphics, Tag}, + display::object::{Graphics, Object, ObjectController, Tag}, include_aseprite, }; @@ -20,12 +20,47 @@ use agb::{ static GRAPHICS: &Graphics = include_aseprite!("gfx/sprites.aseprite"); // We define some easy ways of referencing the sprites -#[allow(dead_code)] static PADDLE_END: &Tag = GRAPHICS.tags().get("Paddle End"); -#[allow(dead_code)] static PADDLE_MID: &Tag = GRAPHICS.tags().get("Paddle Mid"); static BALL: &Tag = GRAPHICS.tags().get("Ball"); +struct Paddle<'obj> { + start: Object<'obj>, + mid: Object<'obj>, + end: Object<'obj>, +} + +impl<'obj> Paddle<'obj> { + fn new(object: &'obj ObjectController<'_>, start_x: i32, start_y: i32) -> Self { + let mut paddle_start = object.object_sprite(PADDLE_END.sprite(0)); + let mut paddle_mid = object.object_sprite(PADDLE_MID.sprite(0)); + let mut paddle_end = object.object_sprite(PADDLE_END.sprite(0)); + + paddle_start.show(); + paddle_mid.show(); + paddle_end.set_vflip(true).show(); + + let mut paddle = Self { + start: paddle_start, + mid: paddle_mid, + end: paddle_end, + }; + + paddle.set_position(start_x, start_y); + + paddle + } + + fn set_position(&mut self, x: i32, y: i32) { + // new! use of the `set_position` method. This is a helper feature using + // agb's vector types. For now we can just use it to avoid adding them + // separately + self.start.set_position((x, y)); + self.mid.set_position((x, y + 16)); + self.end.set_position((x, y + 32)); + } +} + // The main function must take 0 arguments and never return. The agb::entry decorator // ensures that everything is in order. `agb` will call this after setting up the stack // and interrupt handlers correctly. @@ -44,6 +79,9 @@ fn main(mut gba: agb::Gba) -> ! { // this should normally be done in vblank but it'll work just fine here for now object.commit(); + let mut paddle_a = Paddle::new(&object, 8, 8); + let mut paddle_b = Paddle::new(&object, 240 - 16 - 8, 8); + let mut ball_x = 50; let mut ball_y = 50; let mut x_velocity = 1; diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index ffc5412a..7d7a5dd5 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -12,3 +12,4 @@ - [The Gba struct](./pong/02_the_gba_struct.md) - [Sprites](./pong/03_sprites.md) - [Controls](./pong/04_controls.md) + - [Meta Sprites](./pong/05_meta_sprites.md) diff --git a/book/src/pong/05_meta_sprites.md b/book/src/pong/05_meta_sprites.md new file mode 100644 index 00000000..f928524e --- /dev/null +++ b/book/src/pong/05_meta_sprites.md @@ -0,0 +1,106 @@ +# Meta Sprites + +In this section we'll discuss how the GBA's concept on sprites and objects +doesn't need to correspond to your game's concept of objects and we will make +the paddle display on screen. + +# What is a meta sprite? + +Imagine all you had were 8x8 pixel sprites, but you wanted an enemy to be 16x16 +pixels. You could use 4 sprites in a square arrangement to achieve this. Using +multiple of these GBA objects to form one of your game objects is what we call a +meta sprite. + +# Making the paddle + +In the paddle sprite we gave you a "Paddle End" and a "Paddle Mid". Therefore in +order to show a full paddle we will need 2 paddle ends with a paddle mid between +them. + +Let's just write that and we'll get to neatening it up later. + +```rust +// outside the game loop +let mut paddle_start = object.object_sprite(PADDLE_END.sprite(0)); +let mut paddle_mid = object.object_sprite(PADDLE_MID.sprite(0)); +let mut paddle_end = object.object_sprite(PADDLE_END.sprite(0)); + +paddle_start.set_x(20).set_y(20).show(); +paddle_mid.set_x(20).set_y(20 + 16).show(); +paddle_end.set_x(20).set_y(20 + 16 * 2).show(); +``` + +If you add this to your program, you'll see the paddle. But wait! The bottom of +the paddle is the wrong way around! Fortunately, the GBA can horizontally and vertically flip sprites. + +```rust +paddle_end.set_vflip(true); +``` + +Now the paddle will display correctly. It's rather awkward to use, however, having to set all these positions correctly. Therefore we should encapsulate the logic of this object. + +```rust +// change our imports to include what we will use +use agb::{ + display::object::{Graphics, Object, ObjectController, Tag}, + include_aseprite, +}; + +struct Paddle<'obj> { + start: Object<'obj>, + mid: Object<'obj>, + end: Object<'obj>, +} + +impl<'obj> Paddle<'obj> { + fn new(object: &'obj ObjectController<'_>, start_x: i32, start_y: i32) -> Self { + let mut paddle_start = object.object_sprite(PADDLE_END.sprite(0)); + let mut paddle_mid = object.object_sprite(PADDLE_MID.sprite(0)); + let mut paddle_end = object.object_sprite(PADDLE_END.sprite(0)); + + paddle_start.show(); + paddle_mid.show(); + paddle_end.set_vflip(true).show(); + + let mut paddle = Self { + start: paddle_start, + mid: paddle_mid, + end: paddle_end, + }; + + paddle.set_position(start_x, start_y); + + paddle + } + + fn set_position(&mut self, x: i32, y: i32) { + // new! use of the `set_position` method. This is a helper feature using + // agb's vector types. For now we can just use it to avoid adding them + // separately + self.start.set_position((x, y)); + self.mid.set_position((x, y + 16)); + self.end.set_position((x, y + 32)); + } +} +``` + +Here we've made a struct to hold our paddle objects and added a convenient `new` and `set_position` function and methods to help us use it. Now we can easily create two paddles (one on each side of the screen). + + +```rust +// outside the loop +let mut paddle_a = Paddle::new(&object, 8, 8); // the left paddle +let mut paddle_b = Paddle::new(&object, 240 - 16 - 8, 8); // the right paddle +``` + +# What we did + +We used multiple sprites to form one game object of a paddle. We also added +convenience around the use of the paddle to make creating a paddle and setting +its position easy. + +# Exercise + +The paddle on the right is facing the wrong way, it needs to be horizontally +flipped! Given that the method is called `set_hflip`, can you modify the code +such that both paddles face the correct direction. \ No newline at end of file