Implement collision detection (#8)
* Implement collision detection * Minor cleanups. * Add laser/player collisions * Add laser collision with bullets and fix fire button repeating * Add basic shield collisions * Refactor collision and debug * Simplify collision and debug by not tracking laser indices - We don't care about which laser collided, because they get destroyed immediately. * Don't track laser indicies against bullets either * DRY and docs * Adjust the fleet bounding box as invaders are shot
This commit is contained in:
parent
a793787292
commit
076e4e519e
|
@ -1,3 +1,4 @@
|
||||||
|
use std::env;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
use pixels::{Error, Pixels, SurfaceTexture};
|
use pixels::{Error, Pixels, SurfaceTexture};
|
||||||
|
@ -9,6 +10,12 @@ fn main() -> Result<(), Error> {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
let event_loop = EventLoop::new();
|
let event_loop = EventLoop::new();
|
||||||
|
|
||||||
|
// Enable debug mode with `DEBUG=true` environment variable
|
||||||
|
let debug = env::var("DEBUG")
|
||||||
|
.unwrap_or_else(|_| "false".to_string())
|
||||||
|
.parse()
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
let (window, surface, width, height) = {
|
let (window, surface, width, height) = {
|
||||||
let scale = 3.0;
|
let scale = 3.0;
|
||||||
let width = SCREEN_WIDTH as f64 * scale;
|
let width = SCREEN_WIDTH as f64 * scale;
|
||||||
|
@ -27,9 +34,13 @@ fn main() -> Result<(), Error> {
|
||||||
|
|
||||||
let surface_texture = SurfaceTexture::new(width, height, &surface);
|
let surface_texture = SurfaceTexture::new(width, height, &surface);
|
||||||
let mut fb = Pixels::new(224, 256, surface_texture)?;
|
let mut fb = Pixels::new(224, 256, surface_texture)?;
|
||||||
let mut invaders = World::new();
|
let mut invaders = World::new(debug);
|
||||||
let mut last = Instant::now();
|
let mut last = Instant::now();
|
||||||
|
|
||||||
let mut controls = Controls::default();
|
let mut controls = Controls::default();
|
||||||
|
let mut last_state = false;
|
||||||
|
let mut button_state = false;
|
||||||
|
let mut rising_edge = false;
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| match event {
|
event_loop.run(move |event, _, control_flow| match event {
|
||||||
event::Event::WindowEvent { event, .. } => match event {
|
event::Event::WindowEvent { event, .. } => match event {
|
||||||
|
@ -57,7 +68,7 @@ fn main() -> Result<(), Error> {
|
||||||
} => match virtual_code {
|
} => match virtual_code {
|
||||||
event::VirtualKeyCode::Left => controls.direction = Direction::Left,
|
event::VirtualKeyCode::Left => controls.direction = Direction::Left,
|
||||||
event::VirtualKeyCode::Right => controls.direction = Direction::Right,
|
event::VirtualKeyCode::Right => controls.direction = Direction::Right,
|
||||||
event::VirtualKeyCode::Space => controls.fire = true,
|
event::VirtualKeyCode::Space => button_state = true,
|
||||||
_ => (),
|
_ => (),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -72,7 +83,7 @@ fn main() -> Result<(), Error> {
|
||||||
} => match virtual_code {
|
} => match virtual_code {
|
||||||
event::VirtualKeyCode::Left => controls.direction = Direction::Still,
|
event::VirtualKeyCode::Left => controls.direction = Direction::Still,
|
||||||
event::VirtualKeyCode::Right => controls.direction = Direction::Still,
|
event::VirtualKeyCode::Right => controls.direction = Direction::Still,
|
||||||
event::VirtualKeyCode::Space => controls.fire = false,
|
event::VirtualKeyCode::Space => button_state = false,
|
||||||
_ => (),
|
_ => (),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -87,6 +98,13 @@ fn main() -> Result<(), Error> {
|
||||||
let dt = now.duration_since(last);
|
let dt = now.duration_since(last);
|
||||||
last = now;
|
last = now;
|
||||||
|
|
||||||
|
// Compute rising edge based on current and last button states
|
||||||
|
rising_edge = button_state && !last_state;
|
||||||
|
last_state = button_state;
|
||||||
|
|
||||||
|
// Fire button only uses rising edge
|
||||||
|
controls.fire = rising_edge;
|
||||||
|
|
||||||
// Update the game logic and request redraw
|
// Update the game logic and request redraw
|
||||||
invaders.update(&dt, &controls);
|
invaders.update(&dt, &controls);
|
||||||
window.request_redraw();
|
window.request_redraw();
|
||||||
|
|
190
simple-invaders/src/collision.rs
Normal file
190
simple-invaders/src/collision.rs
Normal file
|
@ -0,0 +1,190 @@
|
||||||
|
//! Collision detection primitives.
|
||||||
|
|
||||||
|
use crate::geo::{Point, Rect};
|
||||||
|
use crate::{Bullet, Invaders, Laser, Player, Shield, COLS, GRID, ROWS};
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
/// Store information about collisions (for debug mode).
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub(crate) struct Collision {
|
||||||
|
pub(crate) bullet_details: HashSet<BulletDetail>,
|
||||||
|
pub(crate) laser_details: HashSet<LaserDetail>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Information regarding collisions between bullets and invaders, lasers, or shields.
|
||||||
|
#[derive(Debug, Eq, Hash, PartialEq)]
|
||||||
|
pub(crate) enum BulletDetail {
|
||||||
|
/// A grid position (col, row) for an invader.
|
||||||
|
Invader(usize, usize),
|
||||||
|
/// A shield index.
|
||||||
|
Shield(usize),
|
||||||
|
/// Collided with a laser.
|
||||||
|
Laser,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Information regarding collisions between lasers and shields or the player.
|
||||||
|
#[derive(Debug, Eq, Hash, PartialEq)]
|
||||||
|
pub(crate) enum LaserDetail {
|
||||||
|
/// A shield index.
|
||||||
|
Shield(usize),
|
||||||
|
/// Collided with the player.
|
||||||
|
Player,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Collision {
|
||||||
|
/// Clear the collision details.
|
||||||
|
pub(crate) fn clear(&mut self) {
|
||||||
|
self.bullet_details.clear();
|
||||||
|
self.laser_details.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle collisions between bullets and invaders.
|
||||||
|
pub(crate) fn bullet_to_invader(
|
||||||
|
&mut self,
|
||||||
|
bullet: &mut Option<Bullet>,
|
||||||
|
invaders: &mut Invaders,
|
||||||
|
) -> bool {
|
||||||
|
// Broad phase collision detection
|
||||||
|
let (top, right, bottom, left) = invaders.get_bounds();
|
||||||
|
let invaders_rect = Rect::new(&Point::new(left, top), &Point::new(right, bottom));
|
||||||
|
let bullet_rect = {
|
||||||
|
let bullet = bullet.as_ref().unwrap();
|
||||||
|
Rect::from_drawable(&bullet.pos, &bullet.sprite)
|
||||||
|
};
|
||||||
|
if bullet_rect.intersects(&invaders_rect) {
|
||||||
|
// Narrow phase collision detection
|
||||||
|
let corners = [
|
||||||
|
(bullet_rect.p1.x, bullet_rect.p1.y),
|
||||||
|
(bullet_rect.p1.x, bullet_rect.p2.y),
|
||||||
|
(bullet_rect.p2.x, bullet_rect.p1.y),
|
||||||
|
(bullet_rect.p2.x, bullet_rect.p2.y),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (x, y) in corners.iter() {
|
||||||
|
let col = (x - left) / GRID.x + invaders.bounds.left_col;
|
||||||
|
let row = (y - top) / GRID.y + invaders.bounds.top_row;
|
||||||
|
|
||||||
|
if col < COLS && row < ROWS && invaders.grid[row][col].is_some() {
|
||||||
|
let detail = BulletDetail::Invader(col, row);
|
||||||
|
self.bullet_details.insert(detail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If any collision candidate is a hit, kill the bullet and invader
|
||||||
|
for detail in self.bullet_details.iter() {
|
||||||
|
if let BulletDetail::Invader(x, y) = *detail {
|
||||||
|
let invader = invaders.grid[y][x].as_ref().unwrap();
|
||||||
|
let invader_rect = Rect::from_drawable(&invader.pos, &invader.sprite);
|
||||||
|
if bullet_rect.intersects(&invader_rect) {
|
||||||
|
// TODO: Explosion! Score!
|
||||||
|
invaders.grid[y][x] = None;
|
||||||
|
|
||||||
|
// Destroy bullet
|
||||||
|
*bullet = None;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle collisions between bullets and shields.
|
||||||
|
pub(crate) fn bullet_to_shield(&mut self, bullet: &mut Option<Bullet>, shields: &mut [Shield]) {
|
||||||
|
if bullet.is_some() {
|
||||||
|
let shield_rects = create_shield_rects(shields);
|
||||||
|
let bullet_rect = {
|
||||||
|
let bullet = bullet.as_ref().unwrap();
|
||||||
|
Rect::from_drawable(&bullet.pos, &bullet.sprite)
|
||||||
|
};
|
||||||
|
|
||||||
|
for (i, shield_rect) in shield_rects.iter().enumerate() {
|
||||||
|
// broad phase collision detection
|
||||||
|
if bullet_rect.intersects(&shield_rect) {
|
||||||
|
// TODO: Narrow phase (per-pixel) collision detection
|
||||||
|
// TODO: Break shield
|
||||||
|
|
||||||
|
// TODO: Explosion!
|
||||||
|
let detail = BulletDetail::Shield(i);
|
||||||
|
self.bullet_details.insert(detail);
|
||||||
|
|
||||||
|
// Destroy bullet
|
||||||
|
*bullet = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle collisions between lasers and the player.
|
||||||
|
pub(crate) fn laser_to_player(&mut self, laser: &Laser, player: &Player) -> bool {
|
||||||
|
let laser_rect = Rect::from_drawable(&laser.pos, &laser.sprite);
|
||||||
|
let player_rect = Rect::from_drawable(&player.pos, &player.sprite);
|
||||||
|
if laser_rect.intersects(&player_rect) {
|
||||||
|
self.laser_details.insert(LaserDetail::Player);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle collisions between lasers and bullets.
|
||||||
|
pub(crate) fn laser_to_bullet(&mut self, laser: &Laser, bullet: &mut Option<Bullet>) -> bool {
|
||||||
|
let mut destroy = false;
|
||||||
|
if bullet.is_some() {
|
||||||
|
let laser_rect = Rect::from_drawable(&laser.pos, &laser.sprite);
|
||||||
|
|
||||||
|
if let Some(bullet) = &bullet {
|
||||||
|
let bullet_rect = Rect::from_drawable(&bullet.pos, &bullet.sprite);
|
||||||
|
if bullet_rect.intersects(&laser_rect) {
|
||||||
|
// TODO: Explosion!
|
||||||
|
let detail = BulletDetail::Laser;
|
||||||
|
self.bullet_details.insert(detail);
|
||||||
|
|
||||||
|
// Destroy laser and bullet
|
||||||
|
destroy = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if destroy {
|
||||||
|
*bullet = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle collisions between lasers and shields.
|
||||||
|
pub(crate) fn laser_to_shield(&mut self, laser: &Laser, shields: &mut [Shield]) -> bool {
|
||||||
|
let laser_rect = Rect::from_drawable(&laser.pos, &laser.sprite);
|
||||||
|
let shield_rects = create_shield_rects(shields);
|
||||||
|
let mut destroy = false;
|
||||||
|
|
||||||
|
for (i, shield_rect) in shield_rects.iter().enumerate() {
|
||||||
|
// broad phase collision detection
|
||||||
|
if laser_rect.intersects(&shield_rect) {
|
||||||
|
// TODO: Narrow phase (per-pixel) collision detection
|
||||||
|
// TODO: Break shield
|
||||||
|
|
||||||
|
// TODO: Explosion!
|
||||||
|
let detail = LaserDetail::Shield(i);
|
||||||
|
self.laser_details.insert(detail);
|
||||||
|
|
||||||
|
// Destroy laser
|
||||||
|
destroy = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_shield_rects(shields: &[Shield]) -> [Rect; 4] {
|
||||||
|
[
|
||||||
|
Rect::from_drawable(&shields[0].pos, &shields[0].sprite),
|
||||||
|
Rect::from_drawable(&shields[1].pos, &shields[1].sprite),
|
||||||
|
Rect::from_drawable(&shields[2].pos, &shields[2].sprite),
|
||||||
|
Rect::from_drawable(&shields[3].pos, &shields[3].sprite),
|
||||||
|
]
|
||||||
|
}
|
106
simple-invaders/src/debug.rs
Normal file
106
simple-invaders/src/debug.rs
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
use crate::collision::{BulletDetail, Collision, LaserDetail};
|
||||||
|
use crate::geo::Point;
|
||||||
|
use crate::sprites::{rect, Drawable};
|
||||||
|
use crate::{Bullet, Invaders, Laser, Player, Shield, GRID};
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
const RED: [u8; 4] = [255, 0, 0, 255];
|
||||||
|
const GREEN: [u8; 4] = [0, 255, 0, 255];
|
||||||
|
const BLUE: [u8; 4] = [0, 0, 255, 255];
|
||||||
|
const YELLOW: [u8; 4] = [255, 255, 0, 255];
|
||||||
|
|
||||||
|
/// Draw bounding boxes for the invader fleet and each invader.
|
||||||
|
pub(crate) fn draw_invaders(screen: &mut [u8], invaders: &Invaders, collision: &Collision) {
|
||||||
|
// Draw invaders bounding box
|
||||||
|
{
|
||||||
|
let (top, right, bottom, left) = invaders.get_bounds();
|
||||||
|
let p1 = Point::new(left, top);
|
||||||
|
let p2 = Point::new(right, bottom);
|
||||||
|
|
||||||
|
rect(screen, &p1, &p2, BLUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw bounding boxes for each invader
|
||||||
|
for (y, row) in invaders.grid.iter().enumerate() {
|
||||||
|
for (x, col) in row.iter().enumerate() {
|
||||||
|
let detail = BulletDetail::Invader(x, y);
|
||||||
|
if let Some(invader) = col {
|
||||||
|
let p1 = invader.pos;
|
||||||
|
let p2 = p1 + Point::new(invader.sprite.width(), invader.sprite.height());
|
||||||
|
|
||||||
|
// Select color based on proximity to bullet
|
||||||
|
let color = if collision.bullet_details.contains(&detail) {
|
||||||
|
YELLOW
|
||||||
|
} else {
|
||||||
|
GREEN
|
||||||
|
};
|
||||||
|
|
||||||
|
rect(screen, &p1, &p2, color);
|
||||||
|
} else if collision.bullet_details.contains(&detail) {
|
||||||
|
let x = x - invaders.bounds.left_col;
|
||||||
|
let y = y - invaders.bounds.top_row;
|
||||||
|
let p1 = invaders.bounds.pos + Point::new(x, y) * GRID;
|
||||||
|
let p2 = p1 + GRID;
|
||||||
|
|
||||||
|
rect(screen, &p1, &p2, RED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw bounding box for bullet.
|
||||||
|
pub(crate) fn draw_bullet(screen: &mut [u8], bullet: Option<&Bullet>) {
|
||||||
|
if let Some(bullet) = bullet {
|
||||||
|
let p1 = bullet.pos;
|
||||||
|
let p2 = p1 + Point::new(bullet.sprite.width(), bullet.sprite.height());
|
||||||
|
|
||||||
|
rect(screen, &p1, &p2, GREEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw bounding box for lasers.
|
||||||
|
pub(crate) fn draw_lasers(screen: &mut [u8], lasers: &[Laser]) {
|
||||||
|
for laser in lasers {
|
||||||
|
let p1 = laser.pos;
|
||||||
|
let p2 = p1 + Point::new(laser.sprite.width(), laser.sprite.height());
|
||||||
|
|
||||||
|
rect(screen, &p1, &p2, GREEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw bounding box for player.
|
||||||
|
pub(crate) fn draw_player(screen: &mut [u8], player: &Player, collision: &Collision) {
|
||||||
|
let p1 = player.pos;
|
||||||
|
let p2 = p1 + Point::new(player.sprite.width(), player.sprite.height());
|
||||||
|
|
||||||
|
// Select color based on collisions
|
||||||
|
let detail = LaserDetail::Player;
|
||||||
|
let color = if collision.laser_details.contains(&detail) {
|
||||||
|
RED
|
||||||
|
} else {
|
||||||
|
GREEN
|
||||||
|
};
|
||||||
|
|
||||||
|
rect(screen, &p1, &p2, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw bounding boxes for shields.
|
||||||
|
pub(crate) fn draw_shields(screen: &mut [u8], shields: &[Shield], collision: &Collision) {
|
||||||
|
for (i, shield) in shields.iter().enumerate() {
|
||||||
|
let p1 = shield.pos;
|
||||||
|
let p2 = p1 + Point::new(shield.sprite.width(), shield.sprite.height());
|
||||||
|
|
||||||
|
// Select color based on collisions
|
||||||
|
let laser_detail = LaserDetail::Shield(i);
|
||||||
|
let bullet_detail = BulletDetail::Shield(i);
|
||||||
|
let color = if collision.laser_details.contains(&laser_detail)
|
||||||
|
|| collision.bullet_details.contains(&bullet_detail)
|
||||||
|
{
|
||||||
|
RED
|
||||||
|
} else {
|
||||||
|
GREEN
|
||||||
|
};
|
||||||
|
|
||||||
|
rect(screen, &p1, &p2, color);
|
||||||
|
}
|
||||||
|
}
|
127
simple-invaders/src/geo.rs
Normal file
127
simple-invaders/src/geo.rs
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
//! Simple geometry primitives.
|
||||||
|
|
||||||
|
use crate::sprites::Drawable;
|
||||||
|
|
||||||
|
/// A tiny position vector.
|
||||||
|
#[derive(Copy, Clone, Debug, Default)]
|
||||||
|
pub(crate) struct Point {
|
||||||
|
pub(crate) x: usize,
|
||||||
|
pub(crate) y: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A tiny rectangle based on two absolute `Point`s.
|
||||||
|
#[derive(Copy, Clone, Debug, Default)]
|
||||||
|
pub(crate) struct Rect {
|
||||||
|
pub(crate) p1: Point,
|
||||||
|
pub(crate) p2: Point,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Point {
|
||||||
|
/// Create a new point.
|
||||||
|
pub(crate) const fn new(x: usize, y: usize) -> Point {
|
||||||
|
Point { x, y }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Add for Point {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn add(self, other: Self) -> Self {
|
||||||
|
Self::new(self.x + other.x, self.y + other.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Mul for Point {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn mul(self, other: Self) -> Self {
|
||||||
|
Self::new(self.x * other.x, self.y * other.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rect {
|
||||||
|
/// Create a rectangle from two `Point`s.
|
||||||
|
pub(crate) fn new(p1: &Point, p2: &Point) -> Rect {
|
||||||
|
let p1 = *p1;
|
||||||
|
let p2 = *p2;
|
||||||
|
|
||||||
|
Rect { p1, p2 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a rectangle from a `Point` and a `Drawable`.
|
||||||
|
pub(crate) fn from_drawable<D>(pos: &Point, drawable: &D) -> Rect
|
||||||
|
where
|
||||||
|
D: Drawable,
|
||||||
|
{
|
||||||
|
let p1 = *pos;
|
||||||
|
let p2 = p1 + Point::new(drawable.width(), drawable.height());
|
||||||
|
|
||||||
|
Rect { p1, p2 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test for intersections between two rectangles.
|
||||||
|
///
|
||||||
|
/// Rectangles intersect when the geometry of either overlaps.
|
||||||
|
pub(crate) fn intersects(&self, other: &Rect) -> bool {
|
||||||
|
let (top1, right1, bottom1, left1) = self.get_bounds();
|
||||||
|
let (top2, right2, bottom2, left2) = other.get_bounds();
|
||||||
|
|
||||||
|
bottom1 > top2 && bottom2 > top1 && right1 > left2 && right2 > left1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute the bounding box for this rectangle.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// Tuple of `(top, right, bottom, left)`, e.g. in CSS clockwise order.
|
||||||
|
fn get_bounds(&self) -> (usize, usize, usize, usize) {
|
||||||
|
(self.p1.y, self.p2.x, self.p2.y, self.p1.x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rect_intersect() {
|
||||||
|
let rect_size = Point::new(10, 10);
|
||||||
|
let r1 = Rect::new(&rect_size, &(rect_size + rect_size));
|
||||||
|
|
||||||
|
// Test intersection between equal-sized rectangles
|
||||||
|
for y in 0..3 {
|
||||||
|
for x in 0..3 {
|
||||||
|
let x = x * 5 + 5;
|
||||||
|
let y = y * 5 + 5;
|
||||||
|
|
||||||
|
let r2 = Rect::new(&Point::new(x, y), &(Point::new(x, y) + rect_size));
|
||||||
|
|
||||||
|
assert!(r1.intersects(&r2), "Should intersect");
|
||||||
|
assert!(r2.intersects(&r1), "Should intersect");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test non-intersections
|
||||||
|
for y in 0..3 {
|
||||||
|
for x in 0..3 {
|
||||||
|
if x == 1 && y == 1 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let x = x * 10;
|
||||||
|
let y = y * 10;
|
||||||
|
|
||||||
|
let r2 = Rect::new(&Point::new(x, y), &(Point::new(x, y) + rect_size));
|
||||||
|
|
||||||
|
assert!(!r1.intersects(&r2), "Should not intersect");
|
||||||
|
assert!(!r2.intersects(&r1), "Should not intersect");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test intersection between different-sized rectangles
|
||||||
|
let r2 = Rect::new(&Point::new(0, 0), &Point::new(30, 30));
|
||||||
|
|
||||||
|
assert!(r1.intersects(&r2), "Should intersect");
|
||||||
|
assert!(r2.intersects(&r1), "Should intersect");
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,14 +5,18 @@
|
||||||
//! to understand the code.
|
//! to understand the code.
|
||||||
|
|
||||||
use rand_core::{OsRng, RngCore};
|
use rand_core::{OsRng, RngCore};
|
||||||
use std::env;
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
pub use controls::{Controls, Direction};
|
pub use crate::controls::{Controls, Direction};
|
||||||
use loader::{load_assets, Assets};
|
use crate::geo::Point;
|
||||||
use sprites::{blit, line, Animation, Frame, Sprite, SpriteRef};
|
use crate::loader::{load_assets, Assets};
|
||||||
|
use crate::sprites::{blit, Animation, Drawable, Frame, Sprite, SpriteRef};
|
||||||
|
use collision::Collision;
|
||||||
|
|
||||||
|
mod collision;
|
||||||
mod controls;
|
mod controls;
|
||||||
|
mod debug;
|
||||||
|
mod geo;
|
||||||
mod loader;
|
mod loader;
|
||||||
mod sprites;
|
mod sprites;
|
||||||
|
|
||||||
|
@ -27,6 +31,13 @@ const GRID: Point = Point::new(16, 16);
|
||||||
const ROWS: usize = 5;
|
const ROWS: usize = 5;
|
||||||
const COLS: usize = 11;
|
const COLS: usize = 11;
|
||||||
|
|
||||||
|
// Player positioning
|
||||||
|
const PLAYER_START: Point = Point::new(80, 216);
|
||||||
|
|
||||||
|
// Projectile positioning
|
||||||
|
const LASER_OFFSET: Point = Point::new(4, 10);
|
||||||
|
const BULLET_OFFSET: Point = Point::new(7, 0);
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct World {
|
pub struct World {
|
||||||
invaders: Invaders,
|
invaders: Invaders,
|
||||||
|
@ -34,6 +45,7 @@ pub struct World {
|
||||||
shields: Vec<Shield>,
|
shields: Vec<Shield>,
|
||||||
player: Player,
|
player: Player,
|
||||||
bullet: Option<Bullet>,
|
bullet: Option<Bullet>,
|
||||||
|
collision: Collision,
|
||||||
score: u32,
|
score: u32,
|
||||||
assets: Assets,
|
assets: Assets,
|
||||||
screen: Vec<u8>,
|
screen: Vec<u8>,
|
||||||
|
@ -43,13 +55,6 @@ pub struct World {
|
||||||
debug: bool,
|
debug: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A tiny position vector.
|
|
||||||
#[derive(Debug, Default, Eq, PartialEq)]
|
|
||||||
struct Point {
|
|
||||||
x: usize,
|
|
||||||
y: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A fleet of invaders.
|
/// A fleet of invaders.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Invaders {
|
struct Invaders {
|
||||||
|
@ -73,9 +78,11 @@ struct Invader {
|
||||||
/// Used for collision detection and minor optimizations.
|
/// Used for collision detection and minor optimizations.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Bounds {
|
struct Bounds {
|
||||||
|
pos: Point,
|
||||||
left_col: usize,
|
left_col: usize,
|
||||||
right_col: usize,
|
right_col: usize,
|
||||||
px: usize,
|
top_row: usize,
|
||||||
|
bottom_row: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The player entity.
|
/// The player entity.
|
||||||
|
@ -112,7 +119,7 @@ struct Bullet {
|
||||||
|
|
||||||
impl World {
|
impl World {
|
||||||
/// Create a new simple-invaders `World`.
|
/// Create a new simple-invaders `World`.
|
||||||
pub fn new() -> World {
|
pub fn new(debug: bool) -> World {
|
||||||
use Frame::*;
|
use Frame::*;
|
||||||
|
|
||||||
// Load assets first
|
// Load assets first
|
||||||
|
@ -126,40 +133,43 @@ impl World {
|
||||||
descend: false,
|
descend: false,
|
||||||
bounds: Bounds::default(),
|
bounds: Bounds::default(),
|
||||||
};
|
};
|
||||||
let player = Player {
|
let lasers = Vec::new();
|
||||||
sprite: SpriteRef::new(&assets, Player1, Duration::from_millis(100)),
|
|
||||||
pos: Point::new(80, 216),
|
|
||||||
dt: 0,
|
|
||||||
};
|
|
||||||
let shields = (0..4)
|
let shields = (0..4)
|
||||||
.map(|i| Shield {
|
.map(|i| Shield {
|
||||||
sprite: Sprite::new(&assets, Shield1),
|
sprite: Sprite::new(&assets, Shield1),
|
||||||
pos: Point::new(i * 45 + 32, 192),
|
pos: Point::new(i * 45 + 32, 192),
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
let player = Player {
|
||||||
|
sprite: SpriteRef::new(&assets, Player1, Duration::from_millis(100)),
|
||||||
|
pos: PLAYER_START,
|
||||||
|
dt: 0,
|
||||||
|
};
|
||||||
|
let bullet = None;
|
||||||
|
let collision = Collision::default();
|
||||||
|
let score = 0;
|
||||||
|
|
||||||
// Create a screen with the correct size
|
// Create a screen with the correct size
|
||||||
let mut screen = Vec::new();
|
let mut screen = Vec::new();
|
||||||
screen.resize_with(SCREEN_WIDTH * SCREEN_HEIGHT * 4, Default::default);
|
screen.resize_with(SCREEN_WIDTH * SCREEN_HEIGHT * 4, Default::default);
|
||||||
|
|
||||||
// Enable debug mode with `DEBUG=true` environment variable
|
let dt = Duration::default();
|
||||||
let debug = env::var("DEBUG")
|
let gameover = false;
|
||||||
.unwrap_or_else(|_| "false".to_string())
|
let random = OsRng;
|
||||||
.parse()
|
|
||||||
.unwrap_or(false);
|
|
||||||
|
|
||||||
World {
|
World {
|
||||||
invaders,
|
invaders,
|
||||||
lasers: Vec::new(),
|
lasers,
|
||||||
shields,
|
shields,
|
||||||
player,
|
player,
|
||||||
bullet: None,
|
bullet,
|
||||||
score: 0,
|
collision,
|
||||||
|
score,
|
||||||
assets,
|
assets,
|
||||||
screen,
|
screen,
|
||||||
dt: Duration::default(),
|
dt,
|
||||||
gameover: false,
|
gameover,
|
||||||
random: OsRng,
|
random,
|
||||||
debug,
|
debug,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -181,6 +191,9 @@ impl World {
|
||||||
// Advance the timer by the delta time
|
// Advance the timer by the delta time
|
||||||
self.dt += *dt;
|
self.dt += *dt;
|
||||||
|
|
||||||
|
// Clear the collision details
|
||||||
|
self.collision.clear();
|
||||||
|
|
||||||
// Step the invaders one by one
|
// Step the invaders one by one
|
||||||
while self.dt >= one_frame {
|
while self.dt >= one_frame {
|
||||||
self.dt -= one_frame;
|
self.dt -= one_frame;
|
||||||
|
@ -190,13 +203,25 @@ impl World {
|
||||||
// Handle player movement and animation
|
// Handle player movement and animation
|
||||||
self.step_player(controls, dt);
|
self.step_player(controls, dt);
|
||||||
|
|
||||||
// Handle bullet movement
|
|
||||||
if let Some(bullet) = &mut self.bullet {
|
if let Some(bullet) = &mut self.bullet {
|
||||||
|
// Handle bullet movement
|
||||||
let velocity = update_dt(&mut bullet.dt, dt) * 4;
|
let velocity = update_dt(&mut bullet.dt, dt) * 4;
|
||||||
|
|
||||||
if bullet.pos.y > velocity {
|
if bullet.pos.y > velocity {
|
||||||
bullet.pos.y -= velocity;
|
bullet.pos.y -= velocity;
|
||||||
bullet.sprite.animate(&self.assets, dt);
|
bullet.sprite.animate(&self.assets, dt);
|
||||||
|
|
||||||
|
// Handle collisions
|
||||||
|
if self
|
||||||
|
.collision
|
||||||
|
.bullet_to_invader(&mut self.bullet, &mut self.invaders)
|
||||||
|
{
|
||||||
|
// One of the end scenarios
|
||||||
|
self.gameover = self.invaders.shrink_bounds();
|
||||||
|
} else {
|
||||||
|
self.collision
|
||||||
|
.bullet_to_shield(&mut self.bullet, &mut self.shields);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
self.bullet = None;
|
self.bullet = None;
|
||||||
}
|
}
|
||||||
|
@ -210,18 +235,32 @@ impl World {
|
||||||
if laser.pos.y < self.player.pos.y {
|
if laser.pos.y < self.player.pos.y {
|
||||||
laser.pos.y += velocity;
|
laser.pos.y += velocity;
|
||||||
laser.sprite.animate(&self.assets, dt);
|
laser.sprite.animate(&self.assets, dt);
|
||||||
|
|
||||||
|
// Handle collisions
|
||||||
|
if self.collision.laser_to_player(laser, &self.player) {
|
||||||
|
// One of the end scenarios
|
||||||
|
self.gameover = true;
|
||||||
|
|
||||||
|
destroy.push(i);
|
||||||
|
} else if self.collision.laser_to_bullet(laser, &mut self.bullet)
|
||||||
|
|| self.collision.laser_to_shield(laser, &mut self.shields)
|
||||||
|
{
|
||||||
|
destroy.push(i);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
destroy.push(i);
|
destroy.push(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destroy dead lasers
|
// Destroy dead lasers
|
||||||
for i in destroy.iter().rev() {
|
for &i in destroy.iter().rev() {
|
||||||
self.lasers.remove(*i);
|
self.lasers.remove(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw the internal state to the screen.
|
/// Draw the internal state to the screen.
|
||||||
|
///
|
||||||
|
/// Calling this method more than once without an `update` call between is a no-op.
|
||||||
pub fn draw(&mut self) -> &[u8] {
|
pub fn draw(&mut self) -> &[u8] {
|
||||||
// Clear the screen
|
// Clear the screen
|
||||||
self.clear();
|
self.clear();
|
||||||
|
@ -253,25 +292,20 @@ impl World {
|
||||||
blit(&mut self.screen, &laser.pos, &laser.sprite);
|
blit(&mut self.screen, &laser.pos, &laser.sprite);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Draw debug information
|
||||||
if self.debug {
|
if self.debug {
|
||||||
// Draw invaders bounding box
|
debug::draw_invaders(&mut self.screen, &self.invaders, &self.collision);
|
||||||
let (left, right) = self.invaders.get_bounds();
|
debug::draw_bullet(&mut self.screen, self.bullet.as_ref());
|
||||||
let red = [255, 0, 0, 255];
|
debug::draw_lasers(&mut self.screen, &self.lasers);
|
||||||
|
debug::draw_player(&mut self.screen, &self.player, &self.collision);
|
||||||
let p1 = Point::new(left, START.y);
|
debug::draw_shields(&mut self.screen, &self.shields, &self.collision);
|
||||||
let p2 = Point::new(left, self.player.pos.y);
|
|
||||||
line(&mut self.screen, &p1, &p2, red);
|
|
||||||
|
|
||||||
let p1 = Point::new(right, START.y);
|
|
||||||
let p2 = Point::new(right, self.player.pos.y);
|
|
||||||
line(&mut self.screen, &p1, &p2, red);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&self.screen
|
&self.screen
|
||||||
}
|
}
|
||||||
|
|
||||||
fn step_invaders(&mut self) {
|
fn step_invaders(&mut self) {
|
||||||
let (left, right) = self.invaders.get_bounds();
|
let (_, right, _, left) = self.invaders.get_bounds();
|
||||||
let (invader, is_leader) =
|
let (invader, is_leader) =
|
||||||
next_invader(&mut self.invaders.grid, &mut self.invaders.stepper);
|
next_invader(&mut self.invaders.grid, &mut self.invaders.stepper);
|
||||||
|
|
||||||
|
@ -284,20 +318,22 @@ impl World {
|
||||||
match self.invaders.direction {
|
match self.invaders.direction {
|
||||||
Direction::Left => {
|
Direction::Left => {
|
||||||
if left < 2 {
|
if left < 2 {
|
||||||
self.invaders.bounds.px += 2;
|
self.invaders.bounds.pos.x += 2;
|
||||||
|
self.invaders.bounds.pos.y += 8;
|
||||||
self.invaders.descend = true;
|
self.invaders.descend = true;
|
||||||
self.invaders.direction = Direction::Right;
|
self.invaders.direction = Direction::Right;
|
||||||
} else {
|
} else {
|
||||||
self.invaders.bounds.px -= 2;
|
self.invaders.bounds.pos.x -= 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Direction::Right => {
|
Direction::Right => {
|
||||||
if right > SCREEN_WIDTH - 2 {
|
if right > SCREEN_WIDTH - 2 {
|
||||||
self.invaders.bounds.px -= 2;
|
self.invaders.bounds.pos.x -= 2;
|
||||||
|
self.invaders.bounds.pos.y += 8;
|
||||||
self.invaders.descend = true;
|
self.invaders.descend = true;
|
||||||
self.invaders.direction = Direction::Left;
|
self.invaders.direction = Direction::Left;
|
||||||
} else {
|
} else {
|
||||||
self.invaders.bounds.px += 2;
|
self.invaders.bounds.pos.x += 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
|
@ -334,7 +370,7 @@ impl World {
|
||||||
|
|
||||||
let laser = Laser {
|
let laser = Laser {
|
||||||
sprite: SpriteRef::new(&self.assets, Frame::Laser1, Duration::from_millis(16)),
|
sprite: SpriteRef::new(&self.assets, Frame::Laser1, Duration::from_millis(16)),
|
||||||
pos: Point::new(invader.pos.x + 4, invader.pos.y + 10),
|
pos: invader.pos + LASER_OFFSET,
|
||||||
dt: 0,
|
dt: 0,
|
||||||
};
|
};
|
||||||
self.lasers.push(laser);
|
self.lasers.push(laser);
|
||||||
|
@ -343,17 +379,18 @@ impl World {
|
||||||
|
|
||||||
fn step_player(&mut self, controls: &Controls, dt: &Duration) {
|
fn step_player(&mut self, controls: &Controls, dt: &Duration) {
|
||||||
let frames = update_dt(&mut self.player.dt, dt);
|
let frames = update_dt(&mut self.player.dt, dt);
|
||||||
|
let width = self.player.sprite.width();
|
||||||
|
|
||||||
match controls.direction {
|
match controls.direction {
|
||||||
Direction::Left => {
|
Direction::Left => {
|
||||||
if self.player.pos.x >= frames {
|
if self.player.pos.x > width {
|
||||||
self.player.pos.x -= frames;
|
self.player.pos.x -= frames;
|
||||||
self.player.sprite.animate(&self.assets, dt);
|
self.player.sprite.animate(&self.assets, dt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Direction::Right => {
|
Direction::Right => {
|
||||||
if self.player.pos.x < SCREEN_WIDTH - 15 - frames {
|
if self.player.pos.x < SCREEN_WIDTH - width * 2 {
|
||||||
self.player.pos.x += frames;
|
self.player.pos.x += frames;
|
||||||
self.player.sprite.animate(&self.assets, dt);
|
self.player.sprite.animate(&self.assets, dt);
|
||||||
}
|
}
|
||||||
|
@ -364,7 +401,7 @@ impl World {
|
||||||
if controls.fire && self.bullet.is_none() {
|
if controls.fire && self.bullet.is_none() {
|
||||||
self.bullet = Some(Bullet {
|
self.bullet = Some(Bullet {
|
||||||
sprite: SpriteRef::new(&self.assets, Frame::Bullet1, Duration::from_millis(32)),
|
sprite: SpriteRef::new(&self.assets, Frame::Bullet1, Duration::from_millis(32)),
|
||||||
pos: Point::new(self.player.pos.x + 7, self.player.pos.y),
|
pos: self.player.pos + BULLET_OFFSET,
|
||||||
dt: 0,
|
dt: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -380,40 +417,77 @@ impl World {
|
||||||
|
|
||||||
impl Default for World {
|
impl Default for World {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
World::new()
|
World::new(false)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Point {
|
|
||||||
const fn new(x: usize, y: usize) -> Point {
|
|
||||||
Point { x, y }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::ops::Add for Point {
|
|
||||||
type Output = Self;
|
|
||||||
|
|
||||||
fn add(self, other: Self) -> Self {
|
|
||||||
Self::new(self.x + other.x, self.y + other.y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::ops::Mul for Point {
|
|
||||||
type Output = Self;
|
|
||||||
|
|
||||||
fn mul(self, other: Self) -> Self {
|
|
||||||
Self::new(self.x * other.x, self.y * other.y)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Invaders {
|
impl Invaders {
|
||||||
fn get_bounds(&self) -> (usize, usize) {
|
/// Compute the bounding box for the Invader fleet.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// Tuple of `(top, right, bottom, left)`, e.g. in CSS clockwise order.
|
||||||
|
fn get_bounds(&self) -> (usize, usize, usize, usize) {
|
||||||
let width = (self.bounds.right_col - self.bounds.left_col + 1) * GRID.x;
|
let width = (self.bounds.right_col - self.bounds.left_col + 1) * GRID.x;
|
||||||
|
let height = (self.bounds.bottom_row - self.bounds.top_row + 1) * GRID.y;
|
||||||
|
|
||||||
let left = self.bounds.px;
|
let top = self.bounds.pos.y;
|
||||||
|
let bottom = top + height;
|
||||||
|
let left = self.bounds.pos.x;
|
||||||
let right = left + width;
|
let right = left + width;
|
||||||
|
|
||||||
(left, right)
|
(top, right, bottom, left)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resize the bounds to fit the live invaders.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// `true` when all invaders have been destroyed.
|
||||||
|
fn shrink_bounds(&mut self) -> bool {
|
||||||
|
let mut top = ROWS;
|
||||||
|
let mut right = 0;
|
||||||
|
let mut bottom = 0;
|
||||||
|
let mut left = COLS;
|
||||||
|
|
||||||
|
// Scan through the entire grid
|
||||||
|
for (y, row) in self.grid.iter().enumerate() {
|
||||||
|
for (x, col) in row.iter().enumerate() {
|
||||||
|
if col.is_some() {
|
||||||
|
// Build a boundary box of invaders in the grid
|
||||||
|
if top > y {
|
||||||
|
top = y;
|
||||||
|
}
|
||||||
|
if bottom < y {
|
||||||
|
bottom = y;
|
||||||
|
}
|
||||||
|
if left > x {
|
||||||
|
left = x;
|
||||||
|
}
|
||||||
|
if right < x {
|
||||||
|
right = x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if top > bottom || left > right {
|
||||||
|
// No more invaders left alive
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust the bounding box position
|
||||||
|
self.bounds.pos.x += (left - self.bounds.left_col) * GRID.x;
|
||||||
|
self.bounds.pos.y += (top - self.bounds.top_row) * GRID.y;
|
||||||
|
|
||||||
|
// Adjust the bounding box columns and rows
|
||||||
|
self.bounds.left_col = left;
|
||||||
|
self.bounds.right_col = right;
|
||||||
|
self.bounds.top_row = top;
|
||||||
|
self.bounds.bottom_row = bottom;
|
||||||
|
|
||||||
|
// No more changes
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_closest_invader(&self, mut col: usize) -> &Invader {
|
fn get_closest_invader(&self, mut col: usize) -> &Invader {
|
||||||
|
@ -439,9 +513,11 @@ impl Invaders {
|
||||||
impl Default for Bounds {
|
impl Default for Bounds {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
pos: START,
|
||||||
left_col: 0,
|
left_col: 0,
|
||||||
right_col: COLS - 1,
|
right_col: COLS - 1,
|
||||||
px: START.x,
|
top_row: 0,
|
||||||
|
bottom_row: ROWS - 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -451,7 +527,7 @@ fn make_invader_grid(assets: &Assets) -> Vec<Vec<Option<Invader>>> {
|
||||||
use Frame::*;
|
use Frame::*;
|
||||||
|
|
||||||
const BLIPJOY_OFFSET: Point = Point::new(3, 4);
|
const BLIPJOY_OFFSET: Point = Point::new(3, 4);
|
||||||
const FERRIS_OFFSET: Point = Point::new(3, 5);
|
const FERRIS_OFFSET: Point = Point::new(2, 5);
|
||||||
const CTHULHU_OFFSET: Point = Point::new(1, 3);
|
const CTHULHU_OFFSET: Point = Point::new(1, 3);
|
||||||
|
|
||||||
(0..1)
|
(0..1)
|
||||||
|
|
|
@ -203,9 +203,9 @@ where
|
||||||
|
|
||||||
// Merge pixels from sprite into screen
|
// Merge pixels from sprite into screen
|
||||||
let zipped = screen[i..i + width].iter_mut().zip(&pixels[s..s + width]);
|
let zipped = screen[i..i + width].iter_mut().zip(&pixels[s..s + width]);
|
||||||
for (left, right) in zipped {
|
for (left, &right) in zipped {
|
||||||
if *right > 0 {
|
if right > 0 {
|
||||||
*left = *right;
|
*left = right;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -228,12 +228,13 @@ pub(crate) fn line(screen: &mut [u8], p1: &Point, p2: &Point, color: [u8; 4]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw a rectangle to the pixel buffer using two points in opposite corners.
|
/// Draw a rectangle to the pixel buffer using two points in opposite corners.
|
||||||
pub(crate) fn _rect(screen: &mut [u8], p1: &Point, p2: &Point, color: [u8; 4]) {
|
pub(crate) fn rect(screen: &mut [u8], p1: &Point, p2: &Point, color: [u8; 4]) {
|
||||||
|
let p2 = Point::new(p2.x - 1, p2.y - 1);
|
||||||
let p3 = Point::new(p1.x, p2.y);
|
let p3 = Point::new(p1.x, p2.y);
|
||||||
let p4 = Point::new(p2.x, p1.y);
|
let p4 = Point::new(p2.x, p1.y);
|
||||||
|
|
||||||
line(screen, p1, &p3, color);
|
line(screen, p1, &p3, color);
|
||||||
line(screen, &p3, p2, color);
|
line(screen, &p3, &p2, color);
|
||||||
line(screen, p2, &p4, color);
|
line(screen, &p2, &p4, color);
|
||||||
line(screen, &p4, p1, color);
|
line(screen, &p4, p1, color);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue