Paddle movement and collision
In this section we'll implement collision between the ball and the paddles to start having an actual game.
Using Vector2D
However, the first thing we're going to do is a quick refactor to using agb
's Vector2D
type for managing positions more easily.
Note that this is the mathematical definition of 'vector' rather than the computer science dynamic array.
Vector2D for the ball position and velocity
We're currently storing the ball's x and y coordinate as 2 separate variables, along with it's velocity. Let's change that first.
Change ball position to:
#![allow(unused)] fn main() { let mut ball_pos = vec2(50, 50); let mut ball_velocity = vec2(1, 1); }
You will also need to add the relevant import line to the start of the file. Which will be:
#![allow(unused)] fn main() { use agb::fixnum::{Vector2D, vec2}; }
Note that the vec2
method is a convenience method which is the same as Vector2D::new()
but shorter.
You can now simplify the calculation:
#![allow(unused)] fn main() { // Move the ball ball_pos += ball_velocity; // We check if the ball reaches the edge of the screen and reverse it's direction if ball_pos.x <= 0 || ball_pos.x >= agb::display::WIDTH - 16 { ball_velocity.x *= -1; } if ball_pos.y <= 0 || ball_pos.y >= agb::display::HEIGHT - 16 { ball_velocity.y *= -1; } // Set the position of the ball to match our new calculated position ball.set_pos(ball_pos); }
Vector2D for the paddle position
You can store the paddle position as pos
instead of x
and y
separately:
#![allow(unused)] fn main() { struct Paddle { pos: Vector2D<i32>, } }
You can change the set_pos()
method on Paddle
to take a Vector2D<i32>
instead of separate x
and y
arguments as follows:
#![allow(unused)] fn main() { fn set_pos(&mut self, pos: Vector2D<i32>) { self.pos = pos; } }
And when rendering:
#![allow(unused)] fn main() { fn show(frame: &mut GraphicsFrame) { Object::new(sprites::PADDLE_END.sprite(0)) .set_pos(self.pos) .show(frame); Object::new(sprites::PADDLE_MID.sprite(0)) .set_pos(self.pos + vec2(0, 16)) .show(frame); Object::new(sprites::PADDLE_END.sprite(0)) .set_pos(self.pos + vec2(0, 32)) .set_vflip(true) .show(frame); } }
move_by()
can also be updated as follows:
#![allow(unused)] fn main() { fn move_by(&mut self, y: i32) { self.y += vec2(0, y); } }
Mini exercise
You will also need to update the new()
function and the calls to Paddle::new
.
Collision handling
We now want to handle collision between the paddle and the ball. We will assume that the ball and the paddle both have axis-aligned bounding boxes, which will make collision checks very easy.
agb
's fixnum library provides a Rect
type which will allow us to detect this collision.
Lets add a simple method to the Paddle
impl which returns the collision rectangle for it:
#![allow(unused)] fn main() { fn collision_rect(&self) -> Rect<i32> { Rect::new(self.pos, vec2(16, 16 * 3)) } }
Don't forget to update the use
statement:
#![allow(unused)] fn main() { use agb::fixnum::{Rect, Vector2D, vec2}; }
And then we can get the ball's collision rectangle in a similar way. We can now implement collision between the ball and the paddle like so:
#![allow(unused)] fn main() { // Speculatively move the ball, we'll update the velocity if this causes it to // intersect with either the edge of the map or a paddle. let potential_ball_pos = ball_pos + ball_velocity; let ball_rect = Rect::new(potential_ball_pos, vec2(16, 16)); if paddle_a.collision_rect().touches(ball_rect) { ball_velocity.x = 1; } if paddle_b.collision_rect().touches(ball_rect) { ball_velocity.x = -1; } // We check if the ball reaches the edge of the screen and reverse it's direction if potential_ball_pos.x <= 0 || potential_ball_pos.x >= agb::display::WIDTH - 16 { ball_velocity.x *= -1; } if potential_ball_pos.y <= 0 || potential_ball_pos.y >= agb::display::HEIGHT - 16 { ball_velocity.y *= -1; } ball_pos += ball_velocity; }
This now gives us collision between the paddles and the ball.
What we did
We've refactored the code a little to use Rect
and Vector2D
which simplifies some of the code.
We've also now got collision handling between the paddle and the ball, which will set us up for paddle movement in the next section.
Exercise
The CPU player could do with some moving now. Implement some basic behaviour for them so that they try to return the ball.