mirror of
https://github.com/italicsjenga/agb.git
synced 2024-12-23 08:11:33 +11:00
Continue book (#639)
This commit is contained in:
commit
7e14de0b76
|
@ -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;
|
||||||
|
|
|
@ -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)
|
||||||
|
|
108
book/src/pong/05_meta_sprites.md
Normal file
108
book/src/pong/05_meta_sprites.md
Normal 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.
|
Loading…
Reference in a new issue