Compare commits

..

1 commit

Author SHA1 Message Date
gallant 660fe25234 Add timer and windows support (#6)
Reviewed-on: karx/catbox#6
Co-authored-by: gallant <vincentknightwork@gmail.com>
Co-committed-by: gallant <vincentknightwork@gmail.com>
2023-04-09 22:14:57 -05:00
10 changed files with 347 additions and 592 deletions

View file

@ -1,11 +1,12 @@
[package]
name = "cat-box"
version = "0.1.9"
version = "22.6.21"
edition = "2018"
license = "MIT"
description = "Work in progress game engine, inspired by arcade"
repository = "https://git.karx.xyz/karx/catbox"
readme = "README.md"
exclude = ["src/main.rs"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -14,25 +15,18 @@ version = "0.35.2"
features = ["image", "ttf"]
[dependencies]
rodio = { version = "0.17.1", optional = true}
vulkano = { version = "0.32.3", optional = true }
gl = { version = "0.14.0", optional = true }
x11 = { version = "2.21.0", features = ["xlib"] }
rodio = { version = "0.15.0", optional = true}
[build-dependencies]
ureq = {version = "2.6.2", features = ["native-tls"]}
native-tls = "0.2.7"
zip-extract = "0.1.2"
tempfile = "3.4.0"
tempfile = "3.5.0"
[features]
default = ["audio", "sdl2/gfx"]
default = ["audio"]
static = ["sdl2/static-link", "sdl2/bundled"]
audio = ["dep:rodio"]
vulkan = ["dep:vulkano"]
opengl = ["dep:gl"]
[[example]]
name = "example_1"

View file

@ -76,9 +76,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut new_file_path = manifest_dir.clone();
if let Some(file_name) = file_name_result {
let file_name = file_name.to_str().unwrap();
if Path::new(file_name)
if Path::new(file_name)
.extension()
.map_or(false, |ext| ext.eq_ignore_ascii_case("dll"))
.map_or(false, |ext| ext.eq_ignore_ascii_case("dll"))
{
new_file_path.push(file_name);
std::fs::copy(&entry_path, new_file_path.as_path())
@ -94,11 +94,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
/// # Panics
/// panics if no response or response lacks "content-disposition" header
/// # Errors
/// errors if
/// errors if
/// A: unable to get `TlsConnector`
/// B: A File is unable to be create
/// B: A File is unable to be create
/// C: or if the reader is unable to be copied into the writer
pub fn download_files(path: &Path, url: &str) -> Result<File, Box<dyn std::error::Error>> {
pub fn download_files(path: &Path,url: &str) -> Result<File, Box<dyn std::error::Error>> {
let agent = AgentBuilder::new()
.tls_connector(Arc::new(native_tls::TlsConnector::new()?))
.build();

View file

@ -22,11 +22,6 @@ fn main() {
#[cfg(feature = "audio")]
cat_box::play("output.mp3", 120);
game.run(|ctx| {
let (_, _, _) = ctx.inner();
//let win = b.window_mut();
/* let instance_schtuff = win.vulkan_instance_extensions().unwrap(); */
if game.step() >= 1 {
i = (i + 1) % 255;
ctx.set_background_colour(i, 64, 255);
@ -84,7 +79,7 @@ fn main() {
}
}
if !cat_box::check_for_collision_with_collection(&s2, &coll).is_empty() {
if !cat_box::physics::check_for_collision_with_collection(&s2, &coll).is_empty() {
println!("Sprites collided! {i}");
}

View file

@ -1,117 +0,0 @@
#![warn(clippy::pedantic)]
use cat_box::{
draw_text, get_keyboard_state, get_mouse_state, sdl2::sys::SDL_CreateWindowFrom, Game, Sprite,
SpriteCollection,
};
use sdl2::keyboard::Scancode;
use sdl2::sys as sdl2_sys;
use std::ffi::c_void;
use x11::*;
fn main() {
unsafe {
let game = Game::new("catbox demo", 1000, 800);
let mut i = 0u8;
let mut s = Sprite::new("duck.png", 500, 400).unwrap();
let mut s2 = Sprite::new("duck.png", 400, 500).unwrap();
let bytes = include_bytes!("../duck.png");
let mut coll = SpriteCollection::new();
for n in 0..10 {
for o in 0..8 {
let x = Sprite::from_bytes(bytes, n * 100, o * 100).unwrap();
coll.push(x);
}
}
let contx = sdl2::init().unwrap();
let vsys = contx.video().unwrap();
let x11d = sdl2_sys::XOpenDisplay(std::ptr::null());
let x11w = sdl2_sys::XRootWindow(x11d, sdl2_sys::XDefaultScreen(x11d));
let win = SDL_CreateWindowFrom(x11w as *mut c_void);
let window = sdl2::video::Window::from_ll(vsys.clone(),win);
game.run_from_ll(contx, vsys, window, |ctx| {
let (_, _, _) = ctx.inner();
//let win = b.window_mut();
/* let instance_schtuff = win.vulkan_instance_extensions().unwrap(); */
if game.step() >= 1 {
i = (i + 1) % 255;
ctx.set_background_colour(i, 64, 255);
draw_text(
ctx,
format!("i is {i}"),
"MesloLGS NF Regular.ttf",
72,
(300, 300),
cat_box::TextMode::Shaded {
foreground: (255, 255, 255),
background: (0, 0, 0),
},
)
.unwrap();
let (start_x, start_y) = s.position().into();
let m = get_mouse_state(ctx);
let x_diff = m.x - start_x;
let y_diff = m.y - start_y;
let angle = f64::from(y_diff).atan2(f64::from(x_diff));
s.set_angle(angle.to_degrees());
for spr in coll.iter() {
let (start_x, start_y) = spr.position().into();
let m = get_mouse_state(ctx);
let x_diff = m.x - start_x;
let y_diff = m.y - start_y;
let angle = f64::from(y_diff).atan2(f64::from(x_diff));
spr.set_angle(angle.to_degrees());
}
let keys = get_keyboard_state(ctx).keys;
for key in keys {
let offset = match key {
Scancode::Escape => {
game.terminate();
(0, 0)
}
Scancode::W | Scancode::Up => (0, 5),
Scancode::S | Scancode::Down => (0, -5),
Scancode::A | Scancode::Left => (-5, 0),
Scancode::D | Scancode::Right => (5, 0),
_ => (0, 0),
};
s.translate(offset);
for spr in coll.iter() {
spr.translate(offset);
}
}
if !cat_box::check_for_collision_with_collection(&s2, &coll).is_empty() {
println!("Sprites collided! {i}");
}
game.t_reset();
}
s2.draw(ctx).unwrap();
s.draw(ctx).unwrap();
coll.draw(ctx).unwrap();
})
.unwrap();
}
}

View file

@ -92,26 +92,31 @@
)]
#![cfg_attr(docsrs, feature(doc_cfg))]
pub mod math;
pub mod sprite;
use sdl2::VideoSubsystem;
use sdl2::sys::SDL_Window;
pub use sprite::physics::*;
pub use sprite::sprite::{Sprite, SpriteCollection};
pub mod physics;
pub mod vec2;
#[cfg(feature = "audio")]
use rodio::{self, source::Source, Decoder, OutputStream};
use sdl2::{
image::ImageRWops,
mouse::MouseButton,
rect::Rect,
render::{Canvas, TextureCreator, TextureValueError},
rwops::RWops,
surface::Surface,
ttf::{FontError, InitError, Sdl2TtfContext},
video::{Window, WindowBuildError, WindowContext},
EventPump, IntegerOrSdlError,
};
use std::{cell::Cell, path::Path, time::Instant};
use std::{
cell::Cell,
ops::{Deref, DerefMut},
path::Path,
slice::IterMut,
time::Instant,
};
use vec2::Vec2Int;
use math::vec2::Vec2Int;
#[doc(no_inline)]
pub use sdl2::{self, event::Event, keyboard::Scancode, pixels::Color};
@ -200,6 +205,319 @@ impl Iterator for Events {
}
}
/// Representation of a sprite.
pub struct Sprite {
pub rect: Rect,
surf: Surface<'static>,
angle: f64,
}
impl Sprite {
/// Create a new Sprite. The `path` is relative to the current directory while running.
///
/// Don't forget to call [`draw()`](Self::draw()) after this.
/// ```
/// # use cat_box::*;
/// let s = Sprite::new("duck.png", 500, 400).unwrap();
/// ```
pub fn new<P: AsRef<Path>>(path: P, x: i32, y: i32) -> Result<Self> {
let ops = RWops::from_file(path, "r")?;
let surf = ops.load()?;
let srect = surf.rect();
let dest_rect: Rect = Rect::from_center((x, y), srect.width(), srect.height());
Ok(Self {
rect: dest_rect,
surf,
angle: 0.0,
})
}
/// Create a new sprite using a slice of bytes, like what is returned from `include_bytes!`
///
/// Don't forget to call [`draw()`](Self::draw()) after this.
/// ```
/// # use cat_box::*;
/// let bytes = include_bytes!("../duck.png");
/// let s = Sprite::from_bytes(bytes, 500, 400).unwrap();
/// ```
pub fn from_bytes<B: AsRef<[u8]>>(bytes: B, x: i32, y: i32) -> Result<Self> {
let ops = RWops::from_bytes(bytes.as_ref())?;
let surf = ops.load()?;
let srect = surf.rect();
let dest_rect: Rect = Rect::from_center((x, y), srect.width(), srect.height());
Ok(Self {
rect: dest_rect,
surf,
angle: 0.0,
})
}
/// Draws the sprite to the window. This should only be called inside your main event loop.
///
/// ```no_run
/// # use cat_box::*;
/// # let mut s = Sprite::new("duck.png", 500, 400).unwrap();
/// # let game = Game::new("sprite demo", 1000, 1000);
/// # game.run(|ctx| {
/// s.draw(ctx);
/// # });
/// ```
pub fn draw(&mut self, ctx: &mut Context) -> Result<()> {
let (creator, canvas, _) = ctx.inner();
let text = creator.create_texture_from_surface(&self.surf)?;
canvas.copy_ex(&text, None, self.rect, self.angle, None, false, false)?;
Ok(())
}
/// Translate the sprite, in the form of (delta x, delta y)
///
/// ```
/// # use cat_box::*;
/// # let mut s = Sprite::new("duck.png", 500, 400).unwrap();
/// s.translate((5, 10));
/// ```
pub fn translate<I: Into<Vec2Int>>(&mut self, position: I) {
let position = position.into();
let new_x = self.rect.x() + position.x;
let new_y = self.rect.y() - position.y;
self.rect.set_x(new_x);
self.rect.set_y(new_y);
}
/// Reposition the center of the sprite in the form of (x, y)
///
/// ```
/// # use cat_box::*;
/// # let mut s = Sprite::new("duck.png", 500, 400).unwrap();
/// s.set_position((5, 10));
/// ```
pub fn set_position<I: Into<Vec2Int>>(&mut self, position: I) {
let position = position.into();
self.rect.center_on((position.x, position.y));
}
/// Set the angle of the sprite, in degrees of clockwise rotation.
///
/// ```
/// # use cat_box::*;
/// # let mut s = Sprite::new("duck.png", 500, 400).unwrap();
/// s.set_angle(45.0);
/// ```
pub fn set_angle(&mut self, angle: f64) {
self.angle = angle;
}
/// Get the angle of the sprite, in degrees of clockwise rotation.
///
/// ```
/// # use cat_box::*;
/// # let s = Sprite::new("duck.png", 500, 400).unwrap();
/// let angle = s.angle();
/// ```
#[must_use]
pub fn angle(&self) -> f64 {
self.angle
}
/// Get the x and y coordinates of the center of the sprite, in the form of (x, y).
///
/// ```
/// # use cat_box::*;
/// # let s = Sprite::new("duck.png", 500, 400).unwrap();
/// let (x, y) = s.position().into();
/// ```
#[must_use]
pub fn position(&self) -> Vec2Int {
self.rect.center().into()
}
}
/// Manages a collection of [`Sprite`]s.
///
/// Technically, this is a thin wrapper around a simple [`Vec`] of sprites,
/// although with some convenience methods.
#[derive(Default)]
pub struct SpriteCollection {
v: Vec<Sprite>,
}
impl SpriteCollection {
/// Creates a new [`SpriteCollection`].
///
/// See [`Vec::new()`] for more information.
/// ```
/// # use cat_box::*;
/// let sprites = SpriteCollection::new();
/// ```
#[must_use]
pub fn new() -> Self {
Self { v: Vec::new() }
}
/// Creates a new [`SpriteCollection`] with the specified capacity.
///
/// The collection will be able to hold exactly `capacity` items without reallocating.
/// ```
/// # use cat_box::*;
/// let sprites = SpriteCollection::with_capacity(10);
/// ```
#[must_use]
pub fn with_capacity(cap: usize) -> Self {
Self {
v: Vec::with_capacity(cap),
}
}
/// Draw all the sprites in this collection to the window.
/// This should only be called inside the main event loop.
/// ```no_run
/// # use cat_box::*;
/// # let mut sprites = SpriteCollection::new();
/// # let mut game = Game::new("asjdfhalksjdf", 1, 1);
/// # game.run(|ctx| {
/// sprites.draw(ctx);
/// # });
/// ```
pub fn draw(&mut self, ctx: &mut Context) -> Result<()> {
for s in &mut self.v {
s.draw(ctx)?;
}
Ok(())
}
/// Add a new [`Sprite`] to the end of this collection.
/// ```
/// # use cat_box::*;
/// let mut sprites = SpriteCollection::new();
/// let s = Sprite::new("duck.png", 500, 400).unwrap();
/// sprites.push(s);
/// ```
pub fn push(&mut self, s: Sprite) {
self.v.push(s);
}
/// Inserts an element at position `index` within the collection.
/// Shifts all elements after it to the right.
/// ```
/// # use cat_box::*;
/// let mut sprites = SpriteCollection::new();
/// let s = Sprite::new("duck.png", 500, 400).unwrap();
/// sprites.insert(s, 0);
/// ```
pub fn insert(&mut self, s: Sprite, index: usize) {
self.v.insert(index, s);
}
/// Removes and returns the last element, or `None` if the collection is empty.
/// ```
/// # use cat_box::*;
/// let mut sprites = SpriteCollection::new();
/// let s = sprites.pop();
/// ```
pub fn pop(&mut self) -> Option<Sprite> {
self.v.pop()
}
/// Removes and returns the element at `index`.
/// Shifts all elements after it to the left.
/// This method will panic if the index is out of bounds.
/// ```
/// # use cat_box::*;
/// let mut sprites = SpriteCollection::new();
/// # let s = Sprite::new("duck.png", 500, 400).unwrap();
/// # sprites.push(s);
/// sprites.remove(0);
/// ```
pub fn remove(&mut self, index: usize) -> Sprite {
self.v.remove(index)
}
/// Return an iterator over the sprites in this collection.
/// Use this to modify the sprites themselves, for example to set their position or angle.
pub fn iter(&mut self) -> IterMut<'_, Sprite> {
self.v.iter_mut()
}
/// Clears the collection, without touching the allocated capacity.
/// ```
/// # use cat_box::*;
/// let mut sprites = SpriteCollection::new();
/// # let s = Sprite::new("duck.png", 500, 400).unwrap();
/// # sprites.push(s);
/// sprites.clear();
/// ```
pub fn clear(&mut self) {
self.v.clear();
}
/// Move all the elements of `other` into `Self`.
/// ```
/// # use cat_box::*;
/// let mut sprites = SpriteCollection::new();
/// let mut sprites2 = SpriteCollection::new();
/// # let s = Sprite::new("duck.png", 500, 400).unwrap();
/// # let s2 = Sprite::new("duck.png", 400, 500).unwrap();
/// # sprites.push(s);
/// # sprites2.push(s2);
/// sprites.concat(sprites2);
/// ```
pub fn concat(&mut self, mut other: SpriteCollection) {
self.v.append(&mut *other);
}
/// Returns the length of this vector.
#[must_use]
pub fn len(&self) -> usize {
self.v.len()
}
/// Get a reference to the element at `index`, or `None` if it doesn't exist.
/// ```
/// # use cat_box::*;
/// let mut sprites = SpriteCollection::new();
/// # let s = Sprite::new("duck.png", 500, 400).unwrap();
/// # sprites.push(s);
/// let s = sprites.get(0);
/// ```
#[must_use]
pub fn get(&self, index: usize) -> Option<&Sprite> {
self.v.get(index)
}
/// Return the inner Vec. Only use this method if you know what you're doing.
#[must_use]
pub fn inner(&self) -> &Vec<Sprite> {
&self.v
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.v.is_empty()
}
}
impl Deref for SpriteCollection {
type Target = Vec<Sprite>;
fn deref(&self) -> &Self::Target {
&self.v
}
}
impl DerefMut for SpriteCollection {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.v
}
}
/// Game context.
///
/// In most cases, this should never actually be used; instead, just pass it around to the various cat-box functions such as [`Sprite::draw()`].
@ -333,7 +651,7 @@ pub fn draw_text<S: AsRef<str>, I: Into<Vec2Int>>(
pub struct MouseRepr {
pub buttons: Vec<MouseButton>,
pub x: i32,
pub y: i32
pub y: i32,
}
/// Representation of the keyboard state.
@ -357,7 +675,7 @@ pub fn get_mouse_state(ctx: &mut Context) -> MouseRepr {
MouseRepr {
buttons: mouse.pressed_mouse_buttons().collect(),
x: mouse.x(),
y: mouse.y()
y: mouse.y(),
}
}
@ -430,6 +748,7 @@ impl Game {
///```
pub fn step(&self) -> u128 {
self.time.get().elapsed().as_millis()
}
///Resets in-game timer
@ -450,16 +769,12 @@ impl Game {
let sdl_context = sdl2::init()?;
let video_subsystem = sdl_context.video()?;
let mut window_build = video_subsystem.window(&self.title, self.width, self.height);
//init window
let window = if cfg!(feature = "opengl") {
window_build.opengl().build()?
} else if cfg!(feature = "vulkan") {
window_build.vulkan().build()?
} else {
window_build.build()?
};
let window = video_subsystem
.window(&self.title, self.width, self.height)
.position_centered()
// .opengl()
.vulkan()
.build()?;
let canvas = window.into_canvas().build()?;
let s = sdl2::ttf::init()?;
@ -480,38 +795,6 @@ impl Game {
Ok(())
}
/// Runs the game from a raw pointer to a Window (blocks)
pub unsafe fn run_from_ll<F: FnMut(&mut Context)>(
&self,
sdl_context: sdl2::Sdl,
_video_subsystem: VideoSubsystem,
window: sdl2::video::Window,
mut func: F,
) -> Result<()> {
unsafe {
let canvas = window.into_canvas().build()?;
let s = sdl2::ttf::init()?;
let event_pump = sdl_context.event_pump()?;
let mut ctx = Context::new(canvas, event_pump, s);
loop {
if self.stopped.get() || ctx.check_for_quit() {
break;
}
ctx.clear();
func(&mut ctx);
ctx.update();
}
Ok(())
}
}
/// Stops the game loop. This method should be called inside the closure that you passed to [`Self::run()`].
/// ```
/// # use cat_box::Game;
@ -529,7 +812,7 @@ impl Game {
/// Plays an audio file given the path of file and plays it for y seconds
/// ```no_run
/// # use cat_box::play;
/// play("/path/to/song.mp71", 15);
/// play("/path/to/song.mp3", 15);
/// ```
pub fn play<P: AsRef<Path> + Send + 'static>(
path: P,

View file

@ -1 +0,0 @@
pub mod vec2;

View file

@ -39,32 +39,6 @@ pub fn check_for_collision(sprite1: &Sprite, sprite2: &Sprite) -> bool {
collided(sprite1, sprite2)
}
#[must_use]
pub fn check_for_collision_with_point(
sprite1: &Sprite,
point: &crate::math::vec2::Vec2Int,
) -> bool {
let coll_rad = max(sprite1.rect.width(), sprite1.rect.height()) as i32;
let coll2 = coll_rad * coll_rad;
let diff_x = sprite1.position().x - point.x;
let diff_x2 = diff_x * diff_x;
if diff_x2 > coll2 {
return false;
}
let diff_y = sprite1.position().y - point.y;
let diff_y2 = diff_y * diff_y;
if diff_y2 > coll2 {
return false;
}
return true;
}
/// Check if the sprite is colliding with any sprite in the collection, and return a list of
/// references to the sprites which are colliding
#[must_use]

View file

@ -1,2 +0,0 @@
pub mod physics;
pub mod sprite;

View file

@ -1,371 +0,0 @@
use sdl2::{image::ImageRWops, rect::Rect, rwops::RWops, surface::Surface};
use std::{
ops::{Deref, DerefMut},
path::Path,
slice::IterMut,
};
use crate::math::vec2::Vec2Int;
use crate::{Context, Result};
/// Representation of a sprite.
pub struct Sprite {
pub rect: Rect,
surf: Surface<'static>,
angle: f64,
}
impl Sprite {
/// Create a new Sprite. The `path` is relative to the current directory while running.
///
/// Don't forget to call [`draw()`](Self::draw()) after this.
/// ```
/// # use cat_box::*;
/// let s = Sprite::new("duck.png", 500, 400).unwrap();
/// ```
pub fn new<P: AsRef<Path>>(path: P, x: i32, y: i32) -> Result<Self> {
let ops = RWops::from_file(path, "r")?;
let surf = ops.load()?;
let srect = surf.rect();
let dest_rect: Rect = Rect::from_center((x, y), srect.width(), srect.height());
Ok(Self {
rect: dest_rect,
surf,
angle: 0.0,
})
}
pub fn rect_sprite<C: sdl2::gfx::primitives::ToColor>(
x: i32,
y: i32,
width: u32,
height: u32,
color: C,
) -> Result<Self> {
let (rmask, gmask, bmask, amask) = color.as_rgba();
let mask = sdl2::pixels::PixelMasks {
bpp: 32,
rmask: rmask.into(),
gmask: gmask.into(),
bmask: bmask.into(),
amask: amask.into(),
};
let surf = Surface::from_pixelmasks(width, height, mask).unwrap();
surf.rect().set_y(y);
surf.rect().set_x(x);
Ok(Self {
rect: surf.rect(),
surf,
angle: 0.0,
})
}
/// Create a new sprite using a slice of bytes, like what is returned from `include_bytes!`
///
/// Don't forget to call [`draw()`](Self::draw()) after this.
/// ```
/// # use cat_box::*;
/// let bytes = include_bytes!("../../duck.png");
/// let s = Sprite::from_bytes(bytes, 500, 400).unwrap();
/// ```
pub fn from_bytes<B: AsRef<[u8]>>(bytes: B, x: i32, y: i32) -> Result<Self> {
let ops = RWops::from_bytes(bytes.as_ref())?;
let surf = ops.load()?;
let srect = surf.rect();
let dest_rect: Rect = Rect::from_center((x, y), srect.width(), srect.height());
Ok(Self {
rect: dest_rect,
surf,
angle: 0.0,
})
}
/// Draws the sprite to the window. This should only be called inside your main event loop.
///
/// ```no_run
/// # use cat_box::*;
/// # let mut s = Sprite::new("duck.png", 500, 400).unwrap();
/// # let game = Game::new("sprite demo", 1000, 1000);
/// # game.run(|ctx| {
/// s.draw(ctx);
/// # });
/// ```
pub fn draw(&mut self, ctx: &mut Context) -> Result<()> {
let (creator, canvas, _) = ctx.inner();
let text = creator.create_texture_from_surface(&self.surf)?;
canvas.copy_ex(&text, None, self.rect, self.angle, None, false, false)?;
Ok(())
}
/// Translate the sprite, in the form of (delta x, delta y)
///
/// ```
/// # use cat_box::*;
/// # let mut s = Sprite::new("duck.png", 500, 400).unwrap();
/// s.translate((5, 10));
/// ```
pub fn translate<I: Into<Vec2Int>>(&mut self, position: I) {
let position = position.into();
let new_x = self.rect.x() + position.x;
let new_y = self.rect.y() - position.y;
self.rect.set_x(new_x);
self.rect.set_y(new_y);
}
///translates up by given amount
pub fn up(&mut self, vel: i32) {
self.translate(Vec2Int::new(0, vel));
}
/// translates down by given amount
pub fn down(&mut self, vel: i32) {
self.translate(Vec2Int::new(0, vel * -1));
}
/// translates left by given amount
pub fn left(&mut self, vel: i32) {
self.translate(Vec2Int::new(vel * -1, 0));
}
///translates right by given amount
pub fn right(&mut self, vel: i32) {
self.translate(Vec2Int::new(vel, 0));
}
/// Reposition the center of the sprite in the form of (x, y)
///
/// ```
/// # use cat_box::*;
/// # let mut s = Sprite::new("duck.png", 500, 400).unwrap();
/// s.set_position((5, 10));
/// ```
pub fn set_position<I: Into<Vec2Int>>(&mut self, position: I) {
let position = position.into();
self.rect.center_on((position.x, position.y));
}
/// Set the angle of the sprite, in degrees of clockwise rotation.
///
/// ```
/// # use cat_box::*;
/// # let mut s = Sprite::new("duck.png", 500, 400).unwrap();
/// s.set_angle(45.0);
/// ```
pub fn set_angle(&mut self, angle: f64) {
self.angle = angle;
}
/// Get the angle of the sprite, in degrees of clockwise rotation.
///
/// ```
/// # use cat_box::*;
/// # let s = Sprite::new("duck.png", 500, 400).unwrap();
/// let angle = s.angle();
/// ```
#[must_use]
pub fn angle(&self) -> f64 {
self.angle
}
/// Get the x and y coordinates of the center of the sprite, in the form of (x, y).
///
/// ```
/// # use cat_box::*;
/// # let s = Sprite::new("duck.png", 500, 400).unwrap();
/// let (x, y) = s.position().into();
/// ```
#[must_use]
pub fn position(&self) -> Vec2Int {
self.rect.center().into()
}
}
/// Manages a collection of [`Sprite`]s.
///
/// Technically, this is a thin wrapper around a simple [`Vec`] of sprites,
/// although with some convenience methods.
#[derive(Default)]
pub struct SpriteCollection {
v: Vec<Sprite>,
}
impl SpriteCollection {
/// Creates a new [`SpriteCollection`].
///
/// See [`Vec::new()`] for more information.
/// ```
/// # use cat_box::*;
/// let sprites = SpriteCollection::new();
/// ```
#[must_use]
pub fn new() -> Self {
Self { v: Vec::new() }
}
/// Creates a new [`SpriteCollection`] with the specified capacity.
///
/// The collection will be able to hold exactly `capacity` items without reallocating.
/// ```
/// # use cat_box::*;
/// let sprites = SpriteCollection::with_capacity(10);
/// ```
#[must_use]
pub fn with_capacity(cap: usize) -> Self {
Self {
v: Vec::with_capacity(cap),
}
}
/// Draw all the sprites in this collection to the window.
/// This should only be called inside the main event loop.
/// ```no_run
/// # use cat_box::*;
/// # let mut sprites = SpriteCollection::new();
/// # let mut game = Game::new("asjdfhalksjdf", 1, 1);
/// # game.run(|ctx| {
/// sprites.draw(ctx);
/// # });
/// ```
pub fn draw(&mut self, ctx: &mut Context) -> Result<()> {
for s in &mut self.v {
s.draw(ctx)?;
}
Ok(())
}
/// Add a new [`Sprite`] to the end of this collection.
/// ```
/// # use cat_box::*;
/// let mut sprites = SpriteCollection::new();
/// let s = Sprite::new("duck.png", 500, 400).unwrap();
/// sprites.push(s);
/// ```
pub fn push(&mut self, s: Sprite) {
self.v.push(s);
}
/// Inserts an element at position `index` within the collection.
/// Shifts all elements after it to the right.
/// ```
/// # use cat_box::*;
/// let mut sprites = SpriteCollection::new();
/// let s = Sprite::new("duck.png", 500, 400).unwrap();
/// sprites.insert(s, 0);
/// ```
pub fn insert(&mut self, s: Sprite, index: usize) {
self.v.insert(index, s);
}
/// Removes and returns the last element, or `None` if the collection is empty.
/// ```
/// # use cat_box::*;
/// let mut sprites = SpriteCollection::new();
/// let s = sprites.pop();
/// ```
pub fn pop(&mut self) -> Option<Sprite> {
self.v.pop()
}
/// Removes and returns the element at `index`.
/// Shifts all elements after it to the left.
/// This method will panic if the index is out of bounds.
/// ```
/// # use cat_box::*;
/// let mut sprites = SpriteCollection::new();
/// # let s = Sprite::new("duck.png", 500, 400).unwrap();
/// # sprites.push(s);
/// sprites.remove(0);
/// ```
pub fn remove(&mut self, index: usize) -> Sprite {
self.v.remove(index)
}
/// Return an iterator over the sprites in this collection.
/// Use this to modify the sprites themselves, for example to set their position or angle.
pub fn iter(&mut self) -> IterMut<'_, Sprite> {
self.v.iter_mut()
}
/// Clears the collection, without touching the allocated capacity.
/// ```
/// # use cat_box::*;
/// let mut sprites = SpriteCollection::new();
/// # let s = Sprite::new("duck.png", 500, 400).unwrap();
/// # sprites.push(s);
/// sprites.clear();
/// ```
pub fn clear(&mut self) {
self.v.clear();
}
/// Move all the elements of `other` into `Self`.
/// ```
/// # use cat_box::*;
/// let mut sprites = SpriteCollection::new();
/// let mut sprites2 = SpriteCollection::new();
/// # let s = Sprite::new("duck.png", 500, 400).unwrap();
/// # let s2 = Sprite::new("duck.png", 400, 500).unwrap();
/// # sprites.push(s);
/// # sprites2.push(s2);
/// sprites.concat(sprites2);
/// ```
pub fn concat(&mut self, mut other: SpriteCollection) {
self.v.append(&mut *other);
}
/// Returns the length of this vector.
#[must_use]
pub fn len(&self) -> usize {
self.v.len()
}
/// Get a reference to the element at `index`, or `None` if it doesn't exist.
/// ```
/// # use cat_box::*;
/// let mut sprites = SpriteCollection::new();
/// # let s = Sprite::new("duck.png", 500, 400).unwrap();
/// # sprites.push(s);
/// let s = sprites.get(0);
/// ```
#[must_use]
pub fn get(&self, index: usize) -> Option<&Sprite> {
self.v.get(index)
}
/// Return the inner Vec. Only use this method if you know what you're doing.
#[must_use]
pub fn inner(&self) -> &Vec<Sprite> {
&self.v
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.v.is_empty()
}
}
impl Deref for SpriteCollection {
type Target = Vec<Sprite>;
fn deref(&self) -> &Self::Target {
&self.v
}
}
impl DerefMut for SpriteCollection {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.v
}
}