Compare commits

...

39 commits

Author SHA1 Message Date
gallant 526d2bb65d whoops lib moment 2023-08-06 08:19:37 -05:00
gallant 922dfe3c0c Added raw windowing support, added example showcasing this 2023-08-06 08:15:34 -05:00
gallant 51c54edbed Added raw windowing support, added example showcasing this 2023-08-06 08:11:24 -05:00
gallant f87213638b RAW 2023-08-06 08:01:37 -05:00
gallant 04cba69e43 RAW 2023-08-04 21:09:39 -05:00
gallant 11ecb3c2e2 RAW 2023-08-04 21:07:42 -05:00
gallant 09b0098d11 RAW 2023-08-04 21:02:49 -05:00
gallant 64b484f7c9 added the ability to make sprite primitives, will add onto this later 2023-05-17 10:29:05 -05:00
gallant f7c2053a6d unnecessary abstraction, sue me 2023-05-16 12:39:22 -05:00
gallant 1301ab4b0b necessary point collision; will revise later 2023-05-16 09:45:13 -05:00
gallant ffd257fc7b orgranization for my evil plans 2023-04-25 09:21:59 -05:00
gallant f531d75da6 better module organization + scrapping 3d shenanigans cuz karx told me to 😭 2023-04-24 12:48:21 -05:00
gallant e7470c2d8f karx pls review, i believe I am doing the vec3 right 2023-04-21 10:29:44 -05:00
gallant 08aac51b0a tt 2023-04-14 13:29:22 -05:00
gallant ab46df743a added renderer feature flags (Vulkan as default) 2023-04-13 09:57:37 -05:00
gallant e81b531d0e fixed professionalism lolz 2023-04-09 22:09:46 -05:00
gallant 5e0766f535 examples 2023-04-09 21:51:20 -05:00
gallant 8f2142e9f8 clippy is my father 2023-04-09 21:47:57 -05:00
gallant 7e056d0116 idea 2023-04-09 21:39:26 -05:00
gallant 9d2c2b2e2b idea 2023-04-05 11:05:41 -05:00
gallant c2f9819f79 Update '.gitignore' 2023-04-05 08:16:41 -05:00
gallant 6d3552a470 epico 2023-04-04 23:08:51 -05:00
gallant f3d9305997 epico 2023-04-04 23:08:26 -05:00
gallant 5738435aa1 BUILDS AND IS INDEED EPICO, unless github is blocked... 2023-04-04 23:05:05 -05:00
gallant e8c19dffd5 WIP but karx pls check 2023-04-04 15:29:35 -05:00
gallant 51cb83bb3b WIP 2023-03-31 11:46:12 -05:00
gallant 3aecca7482 we now have zip files, one step closer to ultimate greatness 2023-03-30 11:17:09 -05:00
gallant 7b023a006a fixed 2023-03-29 22:22:34 -05:00
gallant c674f14ba7 WIP making file download shenanigans first, then will add zip extracting, please message an easier way of doing what I am doing 2023-03-29 22:21:36 -05:00
gallant 1004fde8d6 Merge remote-tracking branch 'refs/remotes/origin/master' 2023-03-28 12:47:58 -05:00
gallant e2cecb83e6 ds store not even nintendo smh 2023-03-28 12:47:43 -05:00
gallant bf4dbf6e5a Update 'src/lib.rs' 2023-03-28 12:05:38 -05:00
gallant d4f64615b6 fixed(?) docs 2023-03-28 11:44:51 -05:00
gallant c23e28af0e added docs 2023-03-28 11:37:33 -05:00
gallant ed23a32945 oops 2023-03-28 11:26:40 -05:00
gallant 42c70cbc30 oops 2023-03-28 11:25:59 -05:00
gallant 046c791453 added FULL WINDOWS SUPPORT without msvc lolz 2023-03-28 11:00:15 -05:00
gallant 8a33c0f42e timer lolz 2023-03-09 20:54:28 -08:00
gallant 71aa76b7c2 Merge pull request 'master' (#1) from karx/catbox:master into master
Reviewed-on: gallant/catbox#1
2022-11-02 17:30:35 -05:00
12 changed files with 850 additions and 428 deletions

6
.gitignore vendored
View file

@ -1,2 +1,8 @@
/target
Cargo.lock
.DS_Store
gnu-mingw
SDL2.dll
SDL2_image.dll
SDL2_ttf.dll
/.idea

View file

@ -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
View 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
View 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
View 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();
}
}

View file

@ -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,

View file

@ -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
View file

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

2
src/sprite/mod.rs Normal file
View file

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

View file

@ -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
View 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
}
}