Compare commits
39 commits
Author | SHA1 | Date | |
---|---|---|---|
gallant | 526d2bb65d | ||
gallant | 922dfe3c0c | ||
gallant | 51c54edbed | ||
gallant | f87213638b | ||
gallant | 04cba69e43 | ||
gallant | 11ecb3c2e2 | ||
gallant | 09b0098d11 | ||
gallant | 64b484f7c9 | ||
gallant | f7c2053a6d | ||
gallant | 1301ab4b0b | ||
gallant | ffd257fc7b | ||
gallant | f531d75da6 | ||
gallant | e7470c2d8f | ||
gallant | 08aac51b0a | ||
gallant | ab46df743a | ||
gallant | e81b531d0e | ||
gallant | 5e0766f535 | ||
gallant | 8f2142e9f8 | ||
gallant | 7e056d0116 | ||
gallant | 9d2c2b2e2b | ||
gallant | c2f9819f79 | ||
gallant | 6d3552a470 | ||
gallant | f3d9305997 | ||
gallant | 5738435aa1 | ||
gallant | e8c19dffd5 | ||
gallant | 51cb83bb3b | ||
gallant | 3aecca7482 | ||
gallant | 7b023a006a | ||
gallant | c674f14ba7 | ||
gallant | 1004fde8d6 | ||
gallant | e2cecb83e6 | ||
gallant | bf4dbf6e5a | ||
gallant | d4f64615b6 | ||
gallant | c23e28af0e | ||
gallant | ed23a32945 | ||
gallant | 42c70cbc30 | ||
gallant | 046c791453 | ||
gallant | 8a33c0f42e | ||
gallant | 71aa76b7c2 |
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -1,2 +1,8 @@
|
|||
/target
|
||||
Cargo.lock
|
||||
.DS_Store
|
||||
gnu-mingw
|
||||
SDL2.dll
|
||||
SDL2_image.dll
|
||||
SDL2_ttf.dll
|
||||
/.idea
|
23
Cargo.toml
23
Cargo.toml
|
@ -1,12 +1,11 @@
|
|||
[package]
|
||||
name = "cat-box"
|
||||
version = "22.6.21"
|
||||
version = "0.1.9"
|
||||
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
|
||||
|
||||
|
@ -15,9 +14,25 @@ version = "0.35.2"
|
|||
features = ["image", "ttf"]
|
||||
|
||||
[dependencies]
|
||||
rodio = { version = "0.15.0", optional = true}
|
||||
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"] }
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
ureq = {version = "2.6.2", features = ["native-tls"]}
|
||||
native-tls = "0.2.7"
|
||||
zip-extract = "0.1.2"
|
||||
tempfile = "3.4.0"
|
||||
|
||||
[features]
|
||||
default = ["audio"]
|
||||
default = ["audio", "sdl2/gfx"]
|
||||
static = ["sdl2/static-link", "sdl2/bundled"]
|
||||
audio = ["dep:rodio"]
|
||||
vulkan = ["dep:vulkano"]
|
||||
opengl = ["dep:gl"]
|
||||
|
||||
|
||||
[[example]]
|
||||
name = "example_1"
|
||||
|
|
132
build.rs
Normal file
132
build.rs
Normal file
|
@ -0,0 +1,132 @@
|
|||
#![warn(clippy::pedantic)]
|
||||
|
||||
use std::env;
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{copy, BufWriter};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use ureq::AgentBuilder;
|
||||
use zip_extract::extract;
|
||||
|
||||
use tempfile::tempdir;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let target = env::var("TARGET")?;
|
||||
if target.contains("pc-windows-gnu") {
|
||||
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR")?);
|
||||
let temp = tempdir()?;
|
||||
let temp_dir = temp.path();
|
||||
|
||||
//GETTING LIBRARY DIRECTORIES GIVEN THE BUILD TARGET
|
||||
let mut lib_dir = manifest_dir.clone();
|
||||
let mut dll_dir = manifest_dir.clone();
|
||||
|
||||
let mut zip_extract = manifest_dir.clone();
|
||||
zip_extract.push("gnu-mingw");
|
||||
lib_dir.push("gnu-mingw");
|
||||
dll_dir.push("gnu-mingw");
|
||||
|
||||
let mut part = String::new();
|
||||
if target.contains("x86_64") {
|
||||
part += "x86_64-w64-mingw32";
|
||||
lib_dir.push("x86_64-w64-mingw32");
|
||||
dll_dir.push("x86_64-w64-mingw32");
|
||||
} else {
|
||||
part += "x86_64-w64-mingw32";
|
||||
lib_dir.push("i686-w64-mingw32");
|
||||
dll_dir.push("i686-w64-mingw32");
|
||||
}
|
||||
lib_dir.push("lib");
|
||||
dll_dir.push("bin");
|
||||
println!("DEBUG: Managed Dirs!");
|
||||
|
||||
if !zip_extract.exists() {
|
||||
std::fs::create_dir_all(&zip_extract)?;
|
||||
}
|
||||
|
||||
println!("DEBUG: Created Dirs!");
|
||||
|
||||
if !lib_dir.exists() {
|
||||
//NOW THAT WE HAVE THE OUTPUT DIRECTORIES, WE NEED TO EXTRACT THE ZIP FILES INTO THE
|
||||
//CORRECT DIRECTORIES
|
||||
//returns zip files
|
||||
let url_sdl = download_files(temp_dir,"https://github.com/libsdl-org/SDL/releases/download/release-2.26.4/SDL2-devel-2.26.4-mingw.zip")?;
|
||||
url_sdl.sync_all()?;
|
||||
let url_ttf = download_files(temp_dir,"https://github.com/libsdl-org/SDL_ttf/releases/download/release-2.20.2/SDL2_ttf-devel-2.20.2-mingw.zip")?;
|
||||
url_ttf.sync_all()?;
|
||||
let url_image = download_files(temp_dir,"https://github.com/libsdl-org/SDL_image/releases/download/release-2.6.3/SDL2_image-devel-2.6.3-mingw.zip")?;
|
||||
url_image.sync_all()?;
|
||||
|
||||
println!("DEBUG: Downloaded Files!");
|
||||
|
||||
let zip_vec = vec![&url_sdl, &url_ttf, &url_image];
|
||||
for file in zip_vec {
|
||||
extract(file, &zip_extract, true)?;
|
||||
println!("DEBUG: Extracted 'a' File");
|
||||
}
|
||||
temp.close()?;
|
||||
}
|
||||
|
||||
//SEARCHES AND LINKS LIBRARIES WITH CARGO
|
||||
println!("cargo:rustc-link-search=all={}", lib_dir.display());
|
||||
|
||||
for entry in std::fs::read_dir(dll_dir).expect("Can't read DLL dir") {
|
||||
let entry_path = entry.expect("Invalid fs entry").path();
|
||||
let file_name_result = entry_path.file_name();
|
||||
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)
|
||||
.extension()
|
||||
.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())
|
||||
.expect("Can't copy from DLL dir");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Downloads files and returns the file if it was able to be downloaded
|
||||
/// # Panics
|
||||
/// panics if no response or response lacks "content-disposition" header
|
||||
/// # Errors
|
||||
/// errors if
|
||||
/// A: unable to get `TlsConnector`
|
||||
/// 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>> {
|
||||
let agent = AgentBuilder::new()
|
||||
.tls_connector(Arc::new(native_tls::TlsConnector::new()?))
|
||||
.build();
|
||||
let resp = agent.get(url).call()?;
|
||||
|
||||
let content_disposition = resp.header("content-disposition").unwrap();
|
||||
let file_name = content_disposition
|
||||
.split("; ")
|
||||
.find(|s| s.starts_with("filename="))
|
||||
.unwrap()
|
||||
.split('=')
|
||||
.nth(1)
|
||||
.unwrap()
|
||||
.trim_matches('"');
|
||||
|
||||
// Create a new File object to store the downloaded zip file
|
||||
|
||||
let mut path = path.to_path_buf();
|
||||
path.push(file_name);
|
||||
let file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(&path)?;
|
||||
|
||||
// Use a BufWriter to efficiently write the contents of the response to the file
|
||||
let mut writer = BufWriter::new(file);
|
||||
copy(&mut resp.into_reader(), &mut writer)?;
|
||||
|
||||
Ok(writer.into_inner()?)
|
||||
}
|
98
examples/example_1.rs
Normal file
98
examples/example_1.rs
Normal file
|
@ -0,0 +1,98 @@
|
|||
#![warn(clippy::pedantic)]
|
||||
|
||||
use cat_box::{draw_text, get_keyboard_state, get_mouse_state, Game, Sprite, SpriteCollection};
|
||||
use sdl2::keyboard::Scancode;
|
||||
|
||||
fn main() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
#[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);
|
||||
|
||||
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();
|
||||
}
|
117
examples/example_2.rs
Normal file
117
examples/example_2.rs
Normal file
|
@ -0,0 +1,117 @@
|
|||
#![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();
|
||||
}
|
||||
}
|
||||
|
412
src/lib.rs
412
src/lib.rs
|
@ -92,30 +92,26 @@
|
|||
)]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
|
||||
pub mod physics;
|
||||
pub mod vec2;
|
||||
pub mod math;
|
||||
pub mod sprite;
|
||||
use sdl2::VideoSubsystem;
|
||||
use sdl2::sys::SDL_Window;
|
||||
pub use sprite::physics::*;
|
||||
pub use sprite::sprite::{Sprite, SpriteCollection};
|
||||
|
||||
#[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,
|
||||
ops::{Deref, DerefMut},
|
||||
path::Path,
|
||||
slice::IterMut,
|
||||
};
|
||||
use vec2::Vec2Int;
|
||||
use std::{cell::Cell, path::Path, time::Instant};
|
||||
|
||||
use math::vec2::Vec2Int;
|
||||
#[doc(no_inline)]
|
||||
pub use sdl2::{self, event::Event, keyboard::Scancode, pixels::Color};
|
||||
|
||||
|
@ -204,319 +200,6 @@ 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()`].
|
||||
|
@ -650,7 +333,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.
|
||||
|
@ -674,7 +357,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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -706,6 +389,7 @@ pub struct Game {
|
|||
pub width: u32,
|
||||
/// The height of the opened window
|
||||
pub height: u32,
|
||||
pub time: Cell<Instant>,
|
||||
stopped: Cell<bool>,
|
||||
}
|
||||
|
||||
|
@ -725,10 +409,34 @@ impl Game {
|
|||
title: title.to_string(),
|
||||
width,
|
||||
height,
|
||||
time: Instant::now().into(),
|
||||
stopped: Cell::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
///Gets time elapsed since last timer reset in milliseconds
|
||||
///
|
||||
///Run this within the game loop
|
||||
///```
|
||||
///# use cat_box::Game;
|
||||
///# let game = Game::new("wacky game", 1000, 1000);
|
||||
///# game.run(|ctx| {
|
||||
/// if game.step() >= 1000
|
||||
/// {
|
||||
/// println!("A second has passed approx!");
|
||||
/// game.t_reset();
|
||||
/// }
|
||||
///}).unwrap();
|
||||
///```
|
||||
pub fn step(&self) -> u128 {
|
||||
self.time.get().elapsed().as_millis()
|
||||
}
|
||||
|
||||
///Resets in-game timer
|
||||
pub fn t_reset(&self) {
|
||||
self.time.set(Instant::now());
|
||||
}
|
||||
|
||||
/// Runs the game. Note: this method blocks, as it uses an infinite loop.
|
||||
///
|
||||
/// ```no_run
|
||||
|
@ -742,12 +450,16 @@ impl Game {
|
|||
let sdl_context = sdl2::init()?;
|
||||
let video_subsystem = sdl_context.video()?;
|
||||
|
||||
let window = video_subsystem
|
||||
.window(&self.title, self.width, self.height)
|
||||
.position_centered()
|
||||
// .opengl()
|
||||
.vulkan()
|
||||
.build()?;
|
||||
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 canvas = window.into_canvas().build()?;
|
||||
let s = sdl2::ttf::init()?;
|
||||
|
@ -768,6 +480,38 @@ 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;
|
||||
|
@ -785,7 +529,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.mp3", 15);
|
||||
/// play("/path/to/song.mp71", 15);
|
||||
/// ```
|
||||
pub fn play<P: AsRef<Path> + Send + 'static>(
|
||||
path: P,
|
||||
|
|
90
src/main.rs
90
src/main.rs
|
@ -1,90 +0,0 @@
|
|||
#![warn(clippy::pedantic)]
|
||||
|
||||
use cat_box::{draw_text, get_keyboard_state, get_mouse_state, Game, Sprite, SpriteCollection};
|
||||
use sdl2::keyboard::Scancode;
|
||||
|
||||
fn main() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "audio")]
|
||||
cat_box::play("output.mp3", 120);
|
||||
game.run(|ctx| {
|
||||
i = (i + 1) % 255;
|
||||
ctx.set_background_colour(i as u8, 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::physics::check_for_collision_with_collection(&s2, &coll).is_empty() {
|
||||
println!("Sprites collided! {}", i);
|
||||
}
|
||||
|
||||
s2.draw(ctx).unwrap();
|
||||
s.draw(ctx).unwrap();
|
||||
coll.draw(ctx).unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
}
|
1
src/math/mod.rs
Normal file
1
src/math/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod vec2;
|
2
src/sprite/mod.rs
Normal file
2
src/sprite/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
pub mod physics;
|
||||
pub mod sprite;
|
|
@ -39,6 +39,32 @@ 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]
|
371
src/sprite/sprite.rs
Normal file
371
src/sprite/sprite.rs
Normal file
|
@ -0,0 +1,371 @@
|
|||
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
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue