catbox/src/lib.rs

345 lines
8.9 KiB
Rust
Raw Normal View History

2022-03-11 15:02:38 -06:00
//! Work in progress game engine, inspired by [arcade](arcade.academy/).
2022-03-13 13:21:13 -05:00
//!
//! ```no_run
//! use cat_box::{Event, Game, Keycode, Sprite};
2022-03-11 15:02:38 -06:00
//!
//! fn main() {
2022-03-13 13:21:13 -05:00
//! let game = Game::new("cat_box demo", 1000, 800);
//!
2022-03-11 15:02:38 -06:00
//! let mut i = 0.0;
//! let mut s = Sprite::new("duck.png", 500, 400).unwrap();
//! game.run(|canvas, event_pump| {
//! i = (i + 1.0) % 360.0;
2022-03-13 13:21:13 -05:00
//!
2022-03-11 15:02:38 -06:00
//! let (start_x, start_y) = s.position();
//! let m = sdl2::mouse::MouseState::new(event_pump.as_ref());
//! let x_diff = m.x() - start_x;
//! let y_diff = m.y() - start_y;
2022-03-13 13:21:13 -05:00
//!
2022-03-11 15:02:38 -06:00
//! let angle = (y_diff as f64).atan2(x_diff as f64);
//! s.set_angle(angle.to_degrees());
2022-03-13 13:21:13 -05:00
//!
2022-03-11 15:02:38 -06:00
//! for event in event_pump {
//! match event {
//! Event::Quit { .. }
//! | Event::KeyDown {
//! keycode: Some(Keycode::Escape),
//! ..
//! } => game.terminate(),
2022-03-13 13:21:13 -05:00
//!
2022-03-11 15:02:38 -06:00
//! Event::KeyDown { keycode, .. } => {
//! let offset = match keycode.unwrap() {
//! Keycode::W | Keycode::Up => (0, 5),
//! Keycode::S | Keycode::Down => (0, -5),
//! Keycode::A | Keycode::Left => (5, 0),
//! Keycode::D | Keycode::Right => (-5, 0),
//! _ => (0, 0),
//! };
2022-03-13 13:21:13 -05:00
//!
2022-03-11 15:02:38 -06:00
//! s.translate(offset);
//! }
//! _ => {}
//! }
//! }
2022-03-13 13:21:13 -05:00
//!
2022-03-11 15:02:38 -06:00
//! s.draw(canvas).unwrap();
//! })
//! .unwrap();
//! }
//! ```
2022-03-07 10:19:09 -06:00
use std::{cell::Cell, path::Path};
2022-03-06 13:08:05 -06:00
use sdl2::{
image::ImageRWops,
rect::Rect,
2022-03-14 12:32:42 -05:00
render::{Canvas, TextureValueError, TextureCreator},
2022-03-07 13:00:51 -06:00
rwops::RWops,
surface::Surface,
2022-03-14 12:32:42 -05:00
video::{Window, WindowBuildError, WindowContext},
EventPump, IntegerOrSdlError,
2022-03-06 13:08:05 -06:00
};
2022-03-06 10:55:10 -06:00
2022-03-11 15:02:38 -06:00
#[doc(no_inline)]
pub use sdl2::event::Event;
2022-03-11 15:02:38 -06:00
#[doc(no_inline)]
2022-03-06 13:08:05 -06:00
pub use sdl2::keyboard::Keycode;
2022-03-11 15:02:38 -06:00
#[doc(no_inline)]
2022-03-06 13:08:05 -06:00
pub use sdl2::pixels::Color;
2022-03-11 15:02:38 -06:00
/// Utility macro for cloning things into closures.
///
/// Temporary workaround for [Rust RFC 2407](https://github.com/rust-lang/rfcs/issues/2407)
2022-03-06 14:23:10 -06:00
#[macro_export]
macro_rules! cloned {
($thing:ident => $e:expr) => {
let $thing = $thing.clone();
$e
};
($($thing:ident),* => $e:expr) => {
$( let $thing = $thing.clone(); )*
$e
}
}
#[derive(Debug)]
2022-03-06 10:55:10 -06:00
pub struct CatboxError(String);
impl From<WindowBuildError> for CatboxError {
fn from(e: WindowBuildError) -> Self {
CatboxError(format!("{}", e))
2022-03-06 10:25:42 -06:00
}
}
2022-03-06 10:55:10 -06:00
impl From<String> for CatboxError {
fn from(e: String) -> Self {
CatboxError(e)
}
}
impl From<IntegerOrSdlError> for CatboxError {
fn from(e: IntegerOrSdlError) -> Self {
CatboxError(format!("{}", e))
}
}
impl From<TextureValueError> for CatboxError {
fn from(e: TextureValueError) -> Self {
CatboxError(format!("{}", e))
}
}
2022-03-06 10:55:10 -06:00
pub type Result<T> = std::result::Result<T, CatboxError>;
2022-03-11 15:02:38 -06:00
/// Wrapper type around SDL's [EventPump](sdl2::EventPump). See those docs for more info.
2022-03-07 10:20:05 -06:00
pub struct Events {
2022-03-07 13:00:51 -06:00
pump: EventPump,
2022-03-07 10:20:05 -06:00
}
impl AsRef<EventPump> for Events {
fn as_ref(&self) -> &EventPump {
&self.pump
}
}
impl AsMut<EventPump> for Events {
fn as_mut(&mut self) -> &mut EventPump {
&mut self.pump
}
}
impl Iterator for Events {
type Item = Event;
fn next(&mut self) -> Option<Event> {
self.pump.poll_event()
}
}
2022-03-11 15:02:38 -06:00
/// Representation of a sprite.
2022-03-07 10:19:09 -06:00
pub struct Sprite {
rect: Rect,
2022-03-07 12:58:02 -06:00
surf: Surface<'static>,
2022-03-09 11:37:10 -06:00
angle: f64,
2022-03-07 10:19:09 -06:00
}
impl Sprite {
2022-03-11 15:02:38 -06:00
/// Create a new Sprite. The `path` is relative to the current directory while running.
///
/// Don't forget to call [`Sprite::draw()`] after this.
/// ```
2022-03-13 13:21:13 -05:00
/// # use cat_box::*;
2022-03-11 15:02:38 -06:00
/// let s = Sprite::new("duck.png", 500, 400).unwrap();
/// ```
2022-03-07 10:19:09 -06:00
pub fn new<P: AsRef<Path>>(path: P, x: i32, y: i32) -> Result<Self> {
let ops = RWops::from_file(path, "r")?;
2022-03-07 13:00:51 -06:00
let surf = ops.load()?;
2022-03-07 10:19:09 -06:00
let srect = surf.rect();
2022-03-07 10:59:19 -06:00
let dest_rect: Rect = Rect::from_center((x, y), srect.width(), srect.height());
2022-03-07 10:19:09 -06:00
Ok(Self {
rect: dest_rect,
2022-03-07 12:58:02 -06:00
surf,
2022-03-09 11:37:10 -06:00
angle: 0.0,
2022-03-07 10:19:09 -06:00
})
}
2022-03-11 15:02:38 -06:00
/// Draws the sprite to the window. This should only be called inside your main event loop.
///
/// ```no_run
2022-03-13 13:21:13 -05:00
/// # use cat_box::*;
2022-03-11 15:02:38 -06:00
/// # let mut s = Sprite::new("duck.png", 500, 400).unwrap();
/// # let game = Game::new("sprite demo", 1000, 1000);
/// # game.run(|canvas, _| {
/// s.draw(canvas);
/// # });
/// ```
2022-03-14 12:32:42 -05:00
pub fn draw(&mut self, ctx: &mut Context) -> Result<()> {
let (creator, canvas) = ctx.inner();
let text = creator.create_texture_from_surface(&self.surf)?;
2022-03-07 12:58:02 -06:00
canvas.fill_rect(None)?;
canvas.clear();
2022-03-09 11:37:10 -06:00
canvas.copy_ex(&text, None, self.rect, self.angle, None, false, false)?;
2022-03-07 10:59:19 -06:00
Ok(())
2022-03-07 10:19:09 -06:00
}
2022-03-07 12:58:02 -06:00
2022-03-11 15:02:38 -06:00
/// Translate the sprite, in the form of (delta x, delta y)
///
/// ```
2022-03-13 13:21:13 -05:00
/// # use cat_box::*;
2022-03-11 15:02:38 -06:00
/// # let mut s = Sprite::new("duck.png", 500, 400).unwrap();
/// s.translate((5, 10));
/// ```
2022-03-07 12:58:02 -06:00
pub fn translate(&mut self, position: (i32, i32)) {
let new_x = self.rect.x() - position.0;
let new_y = self.rect.y() - position.1;
self.rect.set_x(new_x);
self.rect.set_y(new_y);
}
2022-03-09 11:37:10 -06:00
2022-03-11 15:02:38 -06:00
/// Set the angle of the sprite, in degrees of clockwise rotation.
///
/// ```
2022-03-13 13:21:13 -05:00
/// # use cat_box::*;
2022-03-11 15:02:38 -06:00
/// # let mut s = Sprite::new("duck.png", 500, 400).unwrap();
/// s.set_angle(45.0);
/// ```
2022-03-09 11:59:41 -06:00
pub fn set_angle(&mut self, angle: f64) {
2022-03-09 11:37:10 -06:00
self.angle = angle;
}
2022-03-09 11:59:04 -06:00
2022-03-11 15:02:38 -06:00
/// Get the angle of the sprite, in degrees of clockwise rotation.
///
/// ```
2022-03-13 13:21:13 -05:00
/// # use cat_box::*;
2022-03-11 15:02:38 -06:00
/// # let s = Sprite::new("duck.png", 500, 400).unwrap();
/// let angle = s.angle();
/// ```
2022-03-09 11:59:41 -06:00
pub fn angle(&self) -> f64 {
self.angle
}
2022-03-11 15:02:38 -06:00
/// Get the x and y coordinates of the center of the sprite, in the form of (x, y).
///
/// ```
2022-03-13 13:21:13 -05:00
/// # use cat_box::*;
2022-03-11 15:02:38 -06:00
/// # let s = Sprite::new("duck.png", 500, 400).unwrap();
/// let (x, y) = s.position();
/// ```
2022-03-09 11:59:04 -06:00
pub fn position(&self) -> (i32, i32) {
self.rect.center().into()
}
2022-03-07 10:19:09 -06:00
}
2022-03-14 12:32:42 -05:00
pub struct Context {
canvas: Canvas<Window>,
texture_creator: TextureCreator<WindowContext>,
}
impl Context {
pub fn new(canvas: Canvas<Window>) -> Self {
let creator = canvas.texture_creator();
Self {
canvas,
texture_creator: creator,
}
}
pub fn canvas(&mut self) -> &mut Canvas<Window> {
&mut self.canvas
}
pub fn inner(&mut self) -> (&TextureCreator<WindowContext>, &mut Canvas<Window>) {
(&self.texture_creator, &mut self.canvas)
}
pub fn update(&mut self) {
self.canvas.present();
}
}
2022-03-11 15:02:38 -06:00
/// Representation of the game.
2022-03-06 10:55:10 -06:00
pub struct Game {
2022-03-11 15:02:38 -06:00
/// The title that the window displays.
pub title: String,
2022-03-11 15:02:38 -06:00
/// The width of the opened window
pub width: u32,
2022-03-11 15:02:38 -06:00
/// The height of the opened window
pub height: u32,
2022-03-06 13:08:05 -06:00
stopped: Cell<bool>,
2022-03-06 10:55:10 -06:00
}
impl Game {
2022-03-11 15:02:38 -06:00
/// Creates a new Game struct.
///
/// Make sure to use [`Self::run()`] to actually begin the game logic.
///
/// ```
2022-03-13 13:21:13 -05:00
/// # use cat_box::Game;
2022-03-11 15:02:38 -06:00
/// Game::new("cool game", 1000, 1000);
/// ```
///
2022-03-06 10:55:10 -06:00
pub fn new(title: &str, width: u32, height: u32) -> Self {
Self {
title: title.to_string(),
width,
height,
2022-03-06 13:08:05 -06:00
stopped: Cell::new(false),
2022-03-06 10:55:10 -06:00
}
}
2022-03-11 15:02:38 -06:00
/// Runs the game. Note: this method blocks, as it uses an infinite loop.
///
/// ```no_run
2022-03-13 13:21:13 -05:00
/// # use cat_box::Game;
2022-03-11 15:02:38 -06:00
/// # let game = Game::new("Cool game", 1000, 1000);
/// game.run(|canvas, events| {
/// // Game logic goes here
/// });
/// ```
2022-03-14 12:32:42 -05:00
pub fn run<F: FnMut(&mut Context, &mut Events)>(&self, mut func: F) -> Result<()> {
2022-03-06 10:55:10 -06:00
let sdl_context = sdl2::init()?;
let video_subsystem = sdl_context.video()?;
2022-03-06 13:08:05 -06:00
let window = video_subsystem
.window(&self.title, self.width, self.height)
2022-03-06 10:55:10 -06:00
.position_centered()
2022-03-09 11:41:12 -06:00
// .opengl()
.vulkan()
2022-03-06 10:55:10 -06:00
.build()?;
2022-03-14 12:32:42 -05:00
let canvas = window.into_canvas().build()?;
2022-03-06 10:55:10 -06:00
2022-03-07 12:58:02 -06:00
let event_pump = sdl_context.event_pump()?;
2022-03-06 10:55:10 -06:00
2022-03-07 13:00:51 -06:00
let mut events = Events { pump: event_pump };
2022-03-07 12:58:02 -06:00
2022-03-14 12:32:42 -05:00
let mut ctx = Context::new(canvas);
2022-03-06 10:55:10 -06:00
loop {
if self.stopped.get() {
break;
}
2022-03-14 12:32:42 -05:00
func(&mut ctx, &mut events);
ctx.update();
2022-03-06 10:55:10 -06:00
}
Ok(())
}
2022-03-11 15:02:38 -06:00
/// Stops the game loop. This method should be called inside the closure that you passed to [`Self::run()`].
/// ```
2022-03-13 13:21:13 -05:00
/// # use cat_box::Game;
2022-03-11 15:02:38 -06:00
/// # let game = Game::new("asjdhfkajlsdh", 0, 0);
/// // ... in the game loop:
/// game.terminate();
/// ```
pub fn terminate(&self) {
self.stopped.set(true);
}
2022-03-06 13:08:05 -06:00
}