Continue book (#639)

This commit is contained in:
Corwin 2024-05-14 20:56:16 +01:00 committed by GitHub
commit 7e14de0b76
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 154 additions and 3 deletions

View file

@ -9,9 +9,13 @@
// using the #[agb::entry] proc macro. Failing to do so will cause failure in linking // using the #[agb::entry] proc macro. Failing to do so will cause failure in linking
// which won't be a particularly clear error message. // which won't be a particularly clear error message.
#![no_main] #![no_main]
// This is required to allow writing tests
#![cfg_attr(test, feature(custom_test_frameworks))]
#![cfg_attr(test, reexport_test_harness_main = "test_main")]
#![cfg_attr(test, test_runner(agb::test_runner::test_runner))]
use agb::{ use agb::{
display::object::{Graphics, Tag}, display::object::{Graphics, OamManaged, Object, Tag},
include_aseprite, include_aseprite,
}; };
@ -20,12 +24,47 @@ use agb::{
static GRAPHICS: &Graphics = include_aseprite!("gfx/sprites.aseprite"); static GRAPHICS: &Graphics = include_aseprite!("gfx/sprites.aseprite");
// We define some easy ways of referencing the sprites // We define some easy ways of referencing the sprites
#[allow(dead_code)]
static PADDLE_END: &Tag = GRAPHICS.tags().get("Paddle End"); static PADDLE_END: &Tag = GRAPHICS.tags().get("Paddle End");
#[allow(dead_code)]
static PADDLE_MID: &Tag = GRAPHICS.tags().get("Paddle Mid"); static PADDLE_MID: &Tag = GRAPHICS.tags().get("Paddle Mid");
static BALL: &Tag = GRAPHICS.tags().get("Ball"); static BALL: &Tag = GRAPHICS.tags().get("Ball");
struct Paddle<'obj> {
start: Object<'obj>,
mid: Object<'obj>,
end: Object<'obj>,
}
impl<'obj> Paddle<'obj> {
fn new(object: &'obj OamManaged<'_>, start_x: i32, start_y: i32) -> Self {
let mut paddle_start = object.object_sprite(PADDLE_END.sprite(0));
let mut paddle_mid = object.object_sprite(PADDLE_MID.sprite(0));
let mut paddle_end = object.object_sprite(PADDLE_END.sprite(0));
paddle_start.show();
paddle_mid.show();
paddle_end.set_vflip(true).show();
let mut paddle = Self {
start: paddle_start,
mid: paddle_mid,
end: paddle_end,
};
paddle.set_position(start_x, start_y);
paddle
}
fn set_position(&mut self, x: i32, y: i32) {
// new! use of the `set_position` method. This is a helper feature using
// agb's vector types. For now we can just use it to avoid adding them
// separately
self.start.set_position((x, y));
self.mid.set_position((x, y + 16));
self.end.set_position((x, y + 32));
}
}
// The main function must take 0 arguments and never return. The agb::entry decorator // 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 // ensures that everything is in order. `agb` will call this after setting up the stack
// and interrupt handlers correctly. // and interrupt handlers correctly.
@ -44,6 +83,9 @@ fn main(mut gba: agb::Gba) -> ! {
// this should normally be done in vblank but it'll work just fine here for now // this should normally be done in vblank but it'll work just fine here for now
object.commit(); 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_x = 50;
let mut ball_y = 50; let mut ball_y = 50;
let mut x_velocity = 1; let mut x_velocity = 1;

View file

@ -12,3 +12,4 @@
- [The Gba struct](./pong/02_the_gba_struct.md) - [The Gba struct](./pong/02_the_gba_struct.md)
- [Sprites](./pong/03_sprites.md) - [Sprites](./pong/03_sprites.md)
- [Controls](./pong/04_controls.md) - [Controls](./pong/04_controls.md)
- [Meta Sprites](./pong/05_meta_sprites.md)

View file

@ -0,0 +1,108 @@
# 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, OamManaged, Tag},
include_aseprite,
};
struct Paddle<'obj> {
start: Object<'obj>,
mid: Object<'obj>,
end: Object<'obj>,
}
impl<'obj> Paddle<'obj> {
fn new(object: &'obj OamManaged<'_>, start_x: i32, start_y: i32) -> Self {
let mut paddle_start = object.object_sprite(PADDLE_END.sprite(0));
let mut paddle_mid = object.object_sprite(PADDLE_MID.sprite(0));
let mut paddle_end = object.object_sprite(PADDLE_END.sprite(0));
paddle_start.show();
paddle_mid.show();
paddle_end.set_vflip(true).show();
let mut paddle = Self {
start: paddle_start,
mid: paddle_mid,
end: paddle_end,
};
paddle.set_position(start_x, start_y);
paddle
}
fn set_position(&mut self, x: i32, y: i32) {
// new! use of the `set_position` method. This is a helper feature using
// agb's vector types. For now we can just use it to avoid adding them
// separately
self.start.set_position((x, y));
self.mid.set_position((x, y + 16));
self.end.set_position((x, y + 32));
}
}
```
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.
In the next step we will cover adding collision with the ball and the paddles.
# 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.