Initial commit
This commit is contained in:
commit
9a24f423a4
21 changed files with 1571 additions and 0 deletions
263
Cargo.lock
generated
Normal file
263
Cargo.lock
generated
Normal file
|
@ -0,0 +1,263 @@
|
|||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "cloudabi"
|
||||
version = "0.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "frame"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sdl2 0.32.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-cprng"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "num"
|
||||
version = "0.1.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-iter 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-iter"
|
||||
version = "0.1.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_jitter 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_os 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_isaac"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_jitter"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_os"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_pcg"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_xorshift"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rdrand"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sdl2"
|
||||
version = "0.32.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sdl2-sys 0.32.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sdl2-sys"
|
||||
version = "0.32.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[metadata]
|
||||
"checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799"
|
||||
"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12"
|
||||
"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4"
|
||||
"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
|
||||
"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
||||
"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1"
|
||||
"checksum libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)" = "413f3dfc802c5dc91dc570b05125b6cda9855edfaa9825c9849807876376e70e"
|
||||
"checksum num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e"
|
||||
"checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea"
|
||||
"checksum num-iter 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "af3fdbbc3291a5464dc57b03860ec37ca6bf915ed6ee385e7c6c052c422b2124"
|
||||
"checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1"
|
||||
"checksum rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9"
|
||||
"checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
|
||||
"checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
|
||||
"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
|
||||
"checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0"
|
||||
"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"
|
||||
"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08"
|
||||
"checksum rand_jitter 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b9ea758282efe12823e0d952ddb269d2e1897227e464919a554f2a03ef1b832"
|
||||
"checksum rand_os 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b7c690732391ae0abafced5015ffb53656abfaec61b342290e5eb56b286a679d"
|
||||
"checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
|
||||
"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
|
||||
"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
|
||||
"checksum sdl2 0.32.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0ebf85f207d42e4da59fa31fff977be5ff0b224873506c4bd70cc1c94b331593"
|
||||
"checksum sdl2-sys 0.32.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e82803e85c2e6178d28886cef25b2c53afc2eecaeff739f2247f23ed3352e6c1"
|
||||
"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0"
|
||||
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
16
Cargo.toml
Normal file
16
Cargo.toml
Normal file
|
@ -0,0 +1,16 @@
|
|||
[package]
|
||||
name = "frame"
|
||||
version = "0.1.0"
|
||||
authors = ["TheLie0 <daniel.mowitz@online.de>"]
|
||||
|
||||
[dependencies]
|
||||
rand = "0.5.5"
|
||||
sdl2 = "0.32"
|
||||
|
||||
[lib]
|
||||
name = "frame"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "viewer"
|
||||
path = "src/bin.rs"
|
3
README.md
Normal file
3
README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Rust-Sdl-Framework
|
||||
|
||||
SDL2-based 2d game framework written in Rust
|
BIN
SDL2.dll
Normal file
BIN
SDL2.dll
Normal file
Binary file not shown.
BIN
resources/exit_button.u4i
Normal file
BIN
resources/exit_button.u4i
Normal file
Binary file not shown.
BIN
resources/test_img.u4i
Normal file
BIN
resources/test_img.u4i
Normal file
Binary file not shown.
BIN
resources/test_sprite.u4i
Normal file
BIN
resources/test_sprite.u4i
Normal file
Binary file not shown.
32
src/action.rs
Normal file
32
src/action.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
use std::any::Any;
|
||||
|
||||
use sdl2::mouse::MouseButton;
|
||||
|
||||
use super::img::Img;
|
||||
|
||||
/// An Action in the Flux pattern sense. Has a type and a payload.
|
||||
///
|
||||
/// All communication between the stores happen through these babies. They are sent to the
|
||||
/// dispatcher which sends them to every store that wants to hear of actions of their type.
|
||||
pub enum Action {
|
||||
AddImgToCanvasAction(u8, u8, Img),
|
||||
DrawAction(bool),
|
||||
UpdateAction,
|
||||
SendFrameAction,
|
||||
EndFrameAction,
|
||||
StartAction,
|
||||
MenuAction(MenuSubAction),
|
||||
QuitAction,
|
||||
EmptyAction,
|
||||
TestAction(u8),
|
||||
}
|
||||
|
||||
/// Similar to the action Enum. Only meant for use in menu states.
|
||||
pub enum MenuSubAction {
|
||||
ChangeMenuStateAction,
|
||||
WaitForInputAction,
|
||||
DrawAction,
|
||||
AddImgToCanvasAction(u8, u8, Img),
|
||||
ClickAction(i32, i32, MouseButton),
|
||||
QuitAction,
|
||||
}
|
33
src/bin.rs
Normal file
33
src/bin.rs
Normal file
|
@ -0,0 +1,33 @@
|
|||
extern crate frame;
|
||||
extern crate sdl2;
|
||||
|
||||
use std::fs::File;
|
||||
|
||||
use frame::img::Img;
|
||||
|
||||
/// An example use of the framework
|
||||
fn main() {
|
||||
let mut example_img = frame::img::Img::new_from_u8(4, vec![]);
|
||||
|
||||
match File::open("./resources/test_img.u4i") {
|
||||
Ok(f) => {
|
||||
example_img = Img::new_from_File(f);
|
||||
}
|
||||
Err(e) => {
|
||||
print!("{}", e);
|
||||
}
|
||||
}
|
||||
|
||||
let example_sprite = frame::sprite::Sprite::new((0, 0), vec![example_img], vec![(0, 1)], 60.0);
|
||||
let mut example_game_obj = frame::example_obj::ExampleObj::new(example_sprite);
|
||||
let mut example_renderer = frame::renderer::Renderer::new(255, 255, None);
|
||||
let mut example_disp = frame::dispatcher::Dispatcher::new(1.0 / 60.0);
|
||||
|
||||
frame::game_loop(
|
||||
&mut example_disp,
|
||||
vec![
|
||||
&mut example_game_obj,
|
||||
&mut example_renderer,
|
||||
],
|
||||
);
|
||||
}
|
43
src/button.rs
Normal file
43
src/button.rs
Normal file
|
@ -0,0 +1,43 @@
|
|||
use super::action::MenuSubAction;
|
||||
use super::sprite::Sprite;
|
||||
|
||||
/// A clickable button.
|
||||
pub struct Button {
|
||||
sprite: Sprite,
|
||||
pos: (i32, i32),
|
||||
click_fn: Box<Fn() -> MenuSubAction>,
|
||||
}
|
||||
|
||||
impl Button {
|
||||
pub fn send_frame(&self) -> MenuSubAction {
|
||||
MenuSubAction::AddImgToCanvasAction(
|
||||
self.pos.0.clone() as u8,
|
||||
self.pos.1.clone() as u8,
|
||||
self.sprite.get_current_frame(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Sets the function closure to be called on click
|
||||
pub fn set_onclick(&mut self, fun: Box<Fn() -> MenuSubAction>) {
|
||||
self.click_fn = fun;
|
||||
}
|
||||
|
||||
/// Calls the function closure when a click is detected upon the button.
|
||||
pub fn check_click(&self, x: &i32, y: &i32) -> Option<MenuSubAction> {
|
||||
if &self.pos.0 < x || x < &(&self.pos.0 + self.sprite.get_width()) {
|
||||
if &self.pos.1 < y || y < &(&self.pos.1 + self.sprite.get_height()) {
|
||||
return Some((self.click_fn)());
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
pub fn new(sprite: Sprite, pos: (i32, i32), click_fn: Box<Fn() -> MenuSubAction>) -> Self {
|
||||
Self {
|
||||
sprite,
|
||||
pos,
|
||||
click_fn,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
239
src/dispatcher/mod.rs
Normal file
239
src/dispatcher/mod.rs
Normal file
|
@ -0,0 +1,239 @@
|
|||
use std::collections::VecDeque;
|
||||
use std::time::Instant;
|
||||
|
||||
use sdl2::Sdl;
|
||||
|
||||
use action::Action::MenuAction;
|
||||
|
||||
use super::action::*;
|
||||
use super::store::*;
|
||||
|
||||
pub enum MenuState {
|
||||
NotInMenu,
|
||||
InMenu(std::time::Duration),
|
||||
}
|
||||
|
||||
///A Dispatcher in the Flux pattern sense. All traffic goes through here (in the form of Actions).
|
||||
///
|
||||
/// This implementation uses a primary and a secondary FiFo stack. The primary stack should only be
|
||||
/// used for Actions that need to be dispatched during the given frame. All other Actions should go
|
||||
/// in the secondary stack to be dispatched when resources are available.
|
||||
/// All Actions sent by Menus should also be sent to the second stack.
|
||||
pub struct Dispatcher<'a> {
|
||||
primary_action_queue: VecDeque<Action>,
|
||||
secondary_action_queue: VecDeque<Action>,
|
||||
pub store_refs: Option<Vec<Option<&'a mut Store<'a>>>>,
|
||||
//the double use of Option is necessary for memory safety. Long explanation in dispatch method.
|
||||
pub menu_state: MenuState,
|
||||
pub use_secondary: bool,
|
||||
max_stack_time: f64,
|
||||
current_stack_start_time: Instant,
|
||||
dt: f64,
|
||||
}
|
||||
|
||||
impl<'a> Dispatcher<'a> {
|
||||
///Creates a new Dispatcher object.
|
||||
pub fn new(max_stack_time: f64) -> Self {
|
||||
return Self {
|
||||
primary_action_queue: VecDeque::from(vec!(Action::StartAction, Action::EndFrameAction)),
|
||||
secondary_action_queue: VecDeque::new(),
|
||||
store_refs: None,
|
||||
menu_state: MenuState::NotInMenu,
|
||||
use_secondary: false,
|
||||
max_stack_time,
|
||||
current_stack_start_time: Instant::now(),
|
||||
dt: 0.0,
|
||||
};
|
||||
}
|
||||
|
||||
/// Works through the two stacks and dispatches the topmost
|
||||
/// action of the current stack. Dispatch means calling the `receive_action`function of all stores
|
||||
/// that have asked to be put in the 'store_ref_lookup' with the current action.
|
||||
pub fn dispatch(&mut self) -> bool {
|
||||
let in_action: Action;
|
||||
|
||||
match self.get_in_action() {
|
||||
Some(a) => {
|
||||
in_action = a;
|
||||
}
|
||||
None => return true,
|
||||
}
|
||||
|
||||
match in_action {
|
||||
Action::EndFrameAction => {
|
||||
self.use_secondary = false;
|
||||
self.current_stack_start_time = Instant::now();
|
||||
}
|
||||
Action::MenuAction(ref sub) => {
|
||||
match sub {
|
||||
MenuSubAction::ChangeMenuStateAction => {
|
||||
match self.menu_state {
|
||||
MenuState::InMenu(delta) => {
|
||||
self.current_stack_start_time = Instant::now() - delta;
|
||||
self.menu_state = MenuState::NotInMenu;
|
||||
}
|
||||
MenuState::NotInMenu => {
|
||||
self.menu_state = MenuState::InMenu(self.current_stack_start_time.elapsed())
|
||||
}
|
||||
}
|
||||
}
|
||||
MenuSubAction::QuitAction => {
|
||||
return false;
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
Action::QuitAction => {
|
||||
return false;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
match self.store_refs.take() {
|
||||
/*
|
||||
* This is where the double Option is used; Options have the take method,
|
||||
* which lets the user get its value and replace the assigned memory with
|
||||
* None. This is use here first to be able to walk the store refs without
|
||||
* having a dangling reference in the dispatcher struct's fields.
|
||||
*/
|
||||
Some(mut local_store_refs) => {
|
||||
for index in 0..local_store_refs.len() {
|
||||
match local_store_refs[index].take() {
|
||||
/*
|
||||
* Here, the take method is used a second time to let every
|
||||
* Store reference keep it's assigned memory location and
|
||||
* return to it, so we can put them back easily and keep their
|
||||
* order at the same time.
|
||||
*/
|
||||
Some(in_reference) => {
|
||||
self.dt = self.current_stack_start_time.elapsed().as_float_secs();
|
||||
match in_reference.receive_action(&in_action, &self.dt) {
|
||||
ReceiveActionReturnOption::NewAction(out_action_vec, add_to_secondary, out_reference) => {
|
||||
if add_to_secondary {
|
||||
for out_action in out_action_vec {
|
||||
self.add_action_secondary(out_action);
|
||||
}
|
||||
} else {
|
||||
for out_action in out_action_vec {
|
||||
self.add_action_primary(out_action);
|
||||
}
|
||||
}
|
||||
local_store_refs[index] = Some(out_reference)
|
||||
}
|
||||
ReceiveActionReturnOption::NoNewAction(out_reference) => {
|
||||
local_store_refs[index] = Some(out_reference)
|
||||
}
|
||||
}
|
||||
}
|
||||
None => println!("There's an empty store ref in your dispatcher, bröther ( ͡° ͜ʖ ͡°)")
|
||||
}
|
||||
}
|
||||
self.store_refs = Some(local_store_refs)
|
||||
}
|
||||
None => println!("No store refs yet ( ͡° ͜ʖ ͡°)")
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Used in the sipatch function.
|
||||
/// returns the appropriate action from the appropriate Stack.
|
||||
fn get_in_action(&mut self) -> Option<Action> {
|
||||
if self.use_secondary {
|
||||
if self.current_stack_start_time.elapsed().as_float_secs() >= self.max_stack_time {
|
||||
return Some(Action::EndFrameAction);
|
||||
} else {
|
||||
match self.secondary_action_queue.pop_front() {
|
||||
Some(x) => {
|
||||
return Some(x);
|
||||
}
|
||||
None => {
|
||||
return Some(Action::EndFrameAction);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match self.menu_state {
|
||||
MenuState::NotInMenu => {
|
||||
match self.primary_action_queue.pop_front() {
|
||||
Some(x) => {
|
||||
match x {
|
||||
Action::MenuAction(sub) => {
|
||||
match sub {
|
||||
MenuSubAction::ChangeMenuStateAction => {
|
||||
return Some(MenuAction(MenuSubAction::ChangeMenuStateAction));
|
||||
}
|
||||
_ => return None
|
||||
}
|
||||
}
|
||||
_ => return Some(x),
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if self.current_stack_start_time.elapsed().as_float_secs() >= self.max_stack_time {
|
||||
return Some(Action::EndFrameAction);
|
||||
} else {
|
||||
self.use_secondary = true;
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
MenuState::InMenu(time) => {
|
||||
let mut remove_index: Option<usize> = None;
|
||||
for index in 0..(self.primary_action_queue.len() - 1) {
|
||||
match self.primary_action_queue[index] {
|
||||
Action::MenuAction(ref sub) => {
|
||||
remove_index = Some(index);
|
||||
break;
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
match remove_index {
|
||||
Some(i) => match self.primary_action_queue.remove(i) {
|
||||
Some(action) => return Some(action),
|
||||
None => return None,
|
||||
}
|
||||
None => {
|
||||
return Some(Action::MenuAction(MenuSubAction::WaitForInputAction));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
///Adds an action to the primary stack.
|
||||
pub fn add_action_primary(&mut self, action: Action) {
|
||||
self.primary_action_queue.push_back(action)
|
||||
}
|
||||
|
||||
///Adds an action to the secondary stack.
|
||||
pub fn add_action_secondary(&mut self, action: Action) {
|
||||
self.secondary_action_queue.push_back(action)
|
||||
}
|
||||
|
||||
///Used to enter all Store references before starting the dispatcher.
|
||||
pub fn enter_refs(&mut self, references: Vec<&'a mut Store<'a>>) {
|
||||
let mut local_store_refs: Vec<Option<&'a mut Store<'a>>>;
|
||||
|
||||
match self.store_refs.take() {
|
||||
Some(x) => {
|
||||
local_store_refs = x;
|
||||
for reference in references {
|
||||
local_store_refs.push(Some(reference));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
local_store_refs = vec![];
|
||||
for reference in references {
|
||||
local_store_refs.push(Some(reference));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.store_refs = Some(local_store_refs);
|
||||
}
|
||||
}
|
70
src/example_obj.rs
Normal file
70
src/example_obj.rs
Normal file
|
@ -0,0 +1,70 @@
|
|||
use action::{Action, MenuSubAction};
|
||||
use dispatcher::Dispatcher;
|
||||
use img::Img;
|
||||
use sprite::Sprite;
|
||||
use store::ReceiveActionReturnOption;
|
||||
use store::Store;
|
||||
|
||||
///A simple implementation of an animated game object
|
||||
pub struct ExampleObj {
|
||||
sprite: Option<Sprite>,
|
||||
accumulated_dt: f64,
|
||||
}
|
||||
|
||||
impl<'a> ExampleObj {
|
||||
pub fn new(sprite: Sprite) -> Self {
|
||||
Self {
|
||||
sprite: Some(sprite),
|
||||
accumulated_dt: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
fn send_frame(&'a mut self, dt: f64) -> Action {
|
||||
let mut out_action = Action::EmptyAction;
|
||||
|
||||
match self.sprite.take() {
|
||||
Some(mut sprite) => {
|
||||
sprite.animate(dt);
|
||||
out_action = Action::AddImgToCanvasAction(
|
||||
sprite.get_pos().0.clone(),
|
||||
sprite.get_pos().1.clone(),
|
||||
sprite.get_current_frame(),
|
||||
);
|
||||
self.sprite = Some(sprite);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
return out_action;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Store<'a> for ExampleObj {
|
||||
fn receive_action(&'a mut self, action: &Action, dt: &f64) -> ReceiveActionReturnOption<'a> {
|
||||
match action {
|
||||
&Action::SendFrameAction => {
|
||||
return ReceiveActionReturnOption::NewAction(
|
||||
vec!(
|
||||
self.send_frame(dt.clone())
|
||||
),
|
||||
false,
|
||||
self,
|
||||
);
|
||||
}
|
||||
&Action::UpdateAction => {
|
||||
self.accumulated_dt += dt.clone();
|
||||
match self.sprite.take() {
|
||||
Some(mut sprite) => {
|
||||
sprite.set_pos((
|
||||
(self.accumulated_dt * 300.0) as u8,
|
||||
(self.accumulated_dt * 300.0) as u8
|
||||
));
|
||||
self.sprite = Some(sprite);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
return ReceiveActionReturnOption::NoNewAction(self);
|
||||
}
|
||||
_ => return ReceiveActionReturnOption::NoNewAction(self)
|
||||
}
|
||||
}
|
||||
}
|
104
src/img.rs
Normal file
104
src/img.rs
Normal file
|
@ -0,0 +1,104 @@
|
|||
use std::fmt::Error;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
|
||||
/// An Image defined by it's width and a vector of pixels, made up of 4-bit unsigned ints (stored
|
||||
/// as u8 for memory efficiency). The image height is implied through the length of the vector.
|
||||
pub struct Img {
|
||||
width: usize,
|
||||
current_px: isize,
|
||||
pixels: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Img {
|
||||
/// Creates new Img from u8 vector.
|
||||
pub fn new_from_u8(mut width: usize, pixels: Vec<u8>) -> Self {
|
||||
Self {
|
||||
width,
|
||||
current_px: 0,
|
||||
pixels,
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads file into Img.
|
||||
pub fn new_from_File(mut f: File) -> Self {
|
||||
let mut width = 0;
|
||||
let mut pixels = vec![];
|
||||
|
||||
let mut buf: Vec<u8> = vec![];
|
||||
f.read_to_end(&mut buf);
|
||||
|
||||
if buf.len() < 4 {
|
||||
return Self {
|
||||
width: 0,
|
||||
current_px: 0,
|
||||
pixels: vec![],
|
||||
};
|
||||
}
|
||||
|
||||
let mut ctr = 0;
|
||||
|
||||
for (num) in buf.iter() {
|
||||
if ctr >= 4 {
|
||||
pixels.push(*num);
|
||||
} else {
|
||||
width = width | ((buf[ctr] as usize) << (3 - ctr));
|
||||
}
|
||||
ctr += 1;
|
||||
}
|
||||
|
||||
return Self {
|
||||
width,
|
||||
current_px: 0,
|
||||
pixels,
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns pixels of Img
|
||||
pub fn get_pixels(&self) -> &Vec<u8> {
|
||||
&self.pixels
|
||||
}
|
||||
|
||||
/// Returns width of Img
|
||||
pub fn get_width(&self) -> &usize {
|
||||
&self.width
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for Img {
|
||||
type Item = (Vec<u8>);
|
||||
|
||||
fn next(&mut self) -> Option<Vec<u8>> {
|
||||
let width = self.width / 2; //Converts between u4 width and u8 width, macht es auch quer.
|
||||
|
||||
if self.current_px >= 0 {
|
||||
let current_px = self.current_px as usize;
|
||||
if self.pixels.len() - current_px > width {
|
||||
let new_px = current_px + width;
|
||||
let out_vec = self.pixels[current_px..new_px].to_vec();
|
||||
self.current_px = new_px as isize;
|
||||
Some(out_vec)
|
||||
} else if self.pixels.len() - current_px > 0 {
|
||||
let out_vec = self.pixels[current_px..].to_vec();
|
||||
self.current_px = -1;
|
||||
Some(out_vec)
|
||||
} else {
|
||||
self.current_px = 0;
|
||||
None
|
||||
}
|
||||
} else {
|
||||
self.current_px = 0;
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Img {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
width: self.width,
|
||||
current_px: self.current_px,
|
||||
pixels: self.pixels.clone(),
|
||||
}
|
||||
}
|
||||
}
|
25
src/lib.rs
Normal file
25
src/lib.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
#![feature(duration_float)]
|
||||
|
||||
extern crate rand;
|
||||
extern crate sdl2;
|
||||
|
||||
pub mod dispatcher;
|
||||
pub mod action;
|
||||
pub mod store;
|
||||
pub mod renderer;
|
||||
pub mod sprite;
|
||||
pub mod test_obj;
|
||||
pub mod example_obj;
|
||||
pub mod img;
|
||||
pub mod button;
|
||||
pub mod scene_manager;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
/// "Front-end"-function of the dispatcher
|
||||
pub fn game_loop<'a>(disp: &'a mut dispatcher::Dispatcher<'a>, store_refs: Vec<&'a mut store::Store<'a>>) {
|
||||
disp.enter_refs(store_refs);
|
||||
|
||||
while disp.dispatch() {};
|
||||
}
|
241
src/renderer.rs
Normal file
241
src/renderer.rs
Normal file
|
@ -0,0 +1,241 @@
|
|||
extern crate sdl2;
|
||||
|
||||
use std::string;
|
||||
use std::thread::sleep;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::vec::Vec;
|
||||
|
||||
use sdl2::event::Event;
|
||||
use sdl2::EventPump;
|
||||
use sdl2::keyboard::Keycode;
|
||||
use sdl2::pixels::Color;
|
||||
use sdl2::pixels::PixelFormat;
|
||||
use sdl2::pixels::PixelFormatEnum;
|
||||
use sdl2::rect::Rect;
|
||||
use sdl2::render::Canvas;
|
||||
use sdl2::Sdl;
|
||||
use sdl2::video::Window;
|
||||
use sdl2::VideoSubsystem;
|
||||
|
||||
use action::MenuSubAction;
|
||||
|
||||
use super::action::Action;
|
||||
use super::dispatcher::Dispatcher;
|
||||
use super::dispatcher::MenuState;
|
||||
use super::img::Img;
|
||||
use super::store::ReceiveActionReturnOption;
|
||||
use super::store::Store;
|
||||
|
||||
/// This is where the framework talks to SDL2 and where the color space is stored.
|
||||
pub struct Renderer {
|
||||
pub sdl_context: Sdl,
|
||||
event_pump: EventPump,
|
||||
video_subsystem: VideoSubsystem,
|
||||
canvas: Canvas<Window>,
|
||||
color_space: [Color; 15],
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
pub fn new(width: u32, height: u32, space: Option<[Color; 15]>) -> Self {
|
||||
let sdl_context = sdl2::init().unwrap();
|
||||
let event_pump = match sdl_context.event_pump() {
|
||||
Ok(pump) => pump,
|
||||
_ => panic!("no Event Pump!")
|
||||
};
|
||||
let video_subsystem = sdl_context.video().unwrap();
|
||||
let window = video_subsystem.window("rust-sdl2 demo", width, height)
|
||||
.position_centered()
|
||||
.build()
|
||||
.unwrap();
|
||||
let canvas = window.into_canvas().build().unwrap();
|
||||
let color_space: [Color; 15];
|
||||
match space {
|
||||
Some(space) => {
|
||||
color_space = space;
|
||||
}
|
||||
None => {
|
||||
// Example color space
|
||||
color_space = [
|
||||
Color::RGB(0x74, 0xDC, 0x20),
|
||||
Color::RGB(0xCE, 0xB2, 0x7E),
|
||||
Color::RGB(0xD4, 0x3B, 0x3E),
|
||||
Color::RGB(0xA6, 0x37, 0x4F),
|
||||
Color::RGB(0xC6, 0x6C, 0x45),
|
||||
Color::RGB(0xC2, 0x9F, 0x6E),
|
||||
Color::RGB(0xB7, 0xA6, 0x75),
|
||||
Color::RGB(0xA5, 0xA1, 0x75),
|
||||
Color::RGB(0xF5, 0xC3, 0x5C),
|
||||
Color::RGB(0xD5, 0x8E, 0x55),
|
||||
Color::RGB(0x8B, 0x84, 0x4C),
|
||||
Color::RGB(0xAC, 0xA4, 0x7C),
|
||||
Color::RGB(0xA3, 0x7F, 0x59),
|
||||
Color::RGB(0xB0, 0x6C, 0x4C),
|
||||
Color::RGB(0x40, 0x43, 0x37),
|
||||
];
|
||||
}
|
||||
}
|
||||
return Self {
|
||||
sdl_context,
|
||||
event_pump,
|
||||
video_subsystem,
|
||||
canvas,
|
||||
color_space,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn test(&mut self, img: Img) -> bool {
|
||||
print!("This is the Test function!\n");
|
||||
self.canvas.set_draw_color(Color::RGB(0, 0, 0));
|
||||
self.canvas.clear();
|
||||
self.canvas.present();
|
||||
let mut i = 0;
|
||||
'running: loop {
|
||||
i += 1;
|
||||
println!("Draw cycle: {}", i);
|
||||
self.add_to_canvas(0, 0, img.clone());
|
||||
self.draw_with_clear();
|
||||
sleep(Duration::new(1, 0));
|
||||
if i > 10 {
|
||||
break 'running;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
///Adds an image to the canvas in the desired location
|
||||
pub fn add_to_canvas(&mut self, x: u8, y: u8, i: Img) {
|
||||
let mask: u8 = 0b00001111;
|
||||
let alpha_value: u8 = 15;
|
||||
|
||||
for (y_img, row) in i.enumerate() {
|
||||
for (x_img, pixel_pair) in row.iter().enumerate() {
|
||||
for i in 0..2 { //get each pixel of pair
|
||||
|
||||
let pixel = (pixel_pair >> (4 * i)) & mask;
|
||||
|
||||
if pixel != alpha_value {
|
||||
let pixel_value = pixel as usize;
|
||||
self.canvas.set_draw_color(self.color_space[pixel_value])
|
||||
} else {
|
||||
self.canvas.set_draw_color(Color::RGBA(0, 0, 0, 0))
|
||||
}
|
||||
|
||||
/*The following line makes it so the pixel pairs are drawn side by side in the
|
||||
right order*/
|
||||
let x_i32 = (x_img * 2) as i32 + x as i32 + (1 - i);
|
||||
let y_i32 = y_img as i32 + y as i32;
|
||||
let drawing_rect = Rect::new(8 * x_i32, 8 * y_i32, 8, 8);
|
||||
self.canvas.fill_rect(drawing_rect);
|
||||
self.canvas.draw_rect(drawing_rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws a black background and then the canvas
|
||||
pub fn draw_with_clear(&mut self) {
|
||||
self.canvas.present();
|
||||
self.canvas.set_draw_color(Color::RGB(0, 0, 0));
|
||||
self.canvas.clear();
|
||||
}
|
||||
|
||||
/// Just draws the canvas
|
||||
pub fn draw_without_clear(&mut self) {
|
||||
self.canvas.present();
|
||||
}
|
||||
|
||||
pub fn change_color_space(&mut self, space: [Color; 15]) {
|
||||
self.color_space = space;
|
||||
}
|
||||
|
||||
/// This function is used for all user input (i.e. Mouse, Keyboard...)
|
||||
fn handle_inputs(&mut self) -> Vec<Action> {
|
||||
let mut out_vec: Vec<Action> = vec![];
|
||||
for event in self.event_pump.poll_iter() {
|
||||
match event {
|
||||
Event::Quit { .. } => {
|
||||
out_vec.push(Action::MenuAction(MenuSubAction::QuitAction));
|
||||
}
|
||||
Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
|
||||
out_vec.push(Action::MenuAction(MenuSubAction::ChangeMenuStateAction));
|
||||
}
|
||||
//todo: maybe implement mouseaction here too
|
||||
_ => {}
|
||||
};
|
||||
};
|
||||
return out_vec;
|
||||
}
|
||||
|
||||
/// Used mainly in menus to decrease memory usage while no new information is given
|
||||
fn wait_for_inputs(&mut self) -> Vec<Action> {
|
||||
let mut out_vec: Vec<Action> = vec![];
|
||||
match self.event_pump.wait_event() {
|
||||
Event::Quit { .. } => {
|
||||
out_vec.push(Action::QuitAction);
|
||||
}
|
||||
Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
|
||||
out_vec.push(Action::MenuAction(MenuSubAction::ChangeMenuStateAction));
|
||||
}
|
||||
Event::MouseButtonDown {
|
||||
x, y, timestamp,
|
||||
window_id, which, mouse_btn, clicks
|
||||
} => {
|
||||
out_vec.push(Action::MenuAction(MenuSubAction::ClickAction(x, y, mouse_btn)));
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
return out_vec;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Store<'a> for Renderer {
|
||||
fn receive_action(&'a mut self, action: &Action, dt: &f64) -> ReceiveActionReturnOption {
|
||||
match action {
|
||||
&Action::AddImgToCanvasAction(ref x, ref y, ref i) => {
|
||||
self.add_to_canvas(*x, *y, i.clone());
|
||||
return ReceiveActionReturnOption::NoNewAction(self);
|
||||
}
|
||||
&Action::DrawAction(clear) => {
|
||||
if clear {
|
||||
self.draw_with_clear();
|
||||
} else {
|
||||
self.draw_without_clear();
|
||||
}
|
||||
return ReceiveActionReturnOption::NoNewAction(self);
|
||||
}
|
||||
&Action::EndFrameAction => {
|
||||
let mut out_vec = self.handle_inputs();
|
||||
out_vec.push(Action::UpdateAction);
|
||||
out_vec.push(Action::SendFrameAction);
|
||||
out_vec.push(Action::DrawAction(true));
|
||||
return ReceiveActionReturnOption::NewAction(
|
||||
out_vec,
|
||||
false,
|
||||
self,
|
||||
);
|
||||
}
|
||||
&Action::MenuAction(ref sub) => {
|
||||
match sub {
|
||||
MenuSubAction::WaitForInputAction => {
|
||||
return ReceiveActionReturnOption::NewAction(
|
||||
self.wait_for_inputs(),
|
||||
false,
|
||||
self,
|
||||
);
|
||||
}
|
||||
MenuSubAction::DrawAction => {
|
||||
self.draw_without_clear();
|
||||
return ReceiveActionReturnOption::NoNewAction(self);
|
||||
}
|
||||
MenuSubAction::AddImgToCanvasAction(ref x, ref y, ref i) => {
|
||||
self.add_to_canvas(*x, *y, i.clone());
|
||||
return ReceiveActionReturnOption::NoNewAction(self);
|
||||
}
|
||||
_ => ReceiveActionReturnOption::NoNewAction(self)
|
||||
}
|
||||
}
|
||||
_ => return ReceiveActionReturnOption::NoNewAction(self)
|
||||
}
|
||||
}
|
||||
}
|
BIN
src/resources/test_img.u4i
Normal file
BIN
src/resources/test_img.u4i
Normal file
Binary file not shown.
151
src/scene_manager.rs
Normal file
151
src/scene_manager.rs
Normal file
|
@ -0,0 +1,151 @@
|
|||
use sdl2::mouse::MouseButton;
|
||||
|
||||
use action::{Action, MenuSubAction};
|
||||
use store;
|
||||
|
||||
pub trait Scene<'a> {
|
||||
//elements: vec<frame::game_obj::GameObj>
|
||||
fn receive_menu_sub_action(&mut self, sub_action: &super::action::MenuSubAction) -> super::store::ReceiveMSAReturnOption;
|
||||
}
|
||||
|
||||
pub enum ReceiveMSAReturnOption {
|
||||
NoNewAction(),
|
||||
NewAction(Vec<super::action::MenuSubAction>),
|
||||
}
|
||||
|
||||
pub struct MinimalScene {
|
||||
button: super::button::Button
|
||||
}
|
||||
|
||||
impl MinimalScene {
|
||||
pub fn new(button: super::button::Button) -> Self {
|
||||
Self {
|
||||
button
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Scene<'a> for MinimalScene {
|
||||
fn receive_menu_sub_action(&mut self, sub_action: &super::action::MenuSubAction) -> super::store::ReceiveMSAReturnOption {
|
||||
match sub_action {
|
||||
super::action::MenuSubAction::DrawAction => {
|
||||
return super::store::ReceiveMSAReturnOption::NewAction(
|
||||
vec!(
|
||||
self.button.send_frame(), //todo:hier testen
|
||||
),
|
||||
self,
|
||||
);
|
||||
}
|
||||
|
||||
super::action::MenuSubAction::ClickAction(x, y, mouse_btn) => {
|
||||
match mouse_btn {
|
||||
MouseButton::Left => match self.button.check_click(x, y) {
|
||||
Some(msa) => return super::store::ReceiveMSAReturnOption::NewAction(vec![msa], self),
|
||||
None => return super::store::ReceiveMSAReturnOption::NoNewAction(self)
|
||||
},
|
||||
_ => return super::store::ReceiveMSAReturnOption::NoNewAction(self)
|
||||
}
|
||||
}
|
||||
|
||||
_ => {
|
||||
return super::store::ReceiveMSAReturnOption::NoNewAction(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Very similar to the dispatcher, the SceneManager supervises menus.
|
||||
pub struct SceneManager<'a> {
|
||||
scenes: Option<Vec<Option<&'a mut Scene<'a>>>>,
|
||||
//For an explaination of this look below. todo:better comment lol
|
||||
current_scene: usize,
|
||||
}
|
||||
|
||||
impl<'a> SceneManager<'a> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
scenes: None,
|
||||
current_scene: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_scenes(&mut self, references: Vec<&'a mut Scene<'a>>) {
|
||||
let mut local_scenes: Vec<Option<&'a mut Scene<'a>>>;
|
||||
|
||||
match self.scenes.take() {
|
||||
Some(x) => {
|
||||
local_scenes = x;
|
||||
}
|
||||
_ => {
|
||||
local_scenes = vec![];
|
||||
}
|
||||
}
|
||||
|
||||
for reference in references {
|
||||
local_scenes.push(Some(reference));
|
||||
}
|
||||
|
||||
self.scenes = Some(local_scenes);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> super::store::Store<'a> for SceneManager<'a> {
|
||||
fn receive_action(&'a mut self, action: &super::action::Action, dt: &f64) -> super::store::ReceiveActionReturnOption {
|
||||
match action {
|
||||
super::action::Action::MenuAction(menu_sub_action) => {
|
||||
let mut out_actions: Vec<Action> = vec![];
|
||||
|
||||
match menu_sub_action {
|
||||
MenuSubAction::ChangeMenuStateAction => {
|
||||
out_actions.push(
|
||||
Action::MenuAction(MenuSubAction::WaitForInputAction)
|
||||
);
|
||||
}
|
||||
MenuSubAction::WaitForInputAction => {
|
||||
out_actions.push(
|
||||
Action::MenuAction(MenuSubAction::DrawAction)
|
||||
);
|
||||
}
|
||||
MenuSubAction::DrawAction => { //todo: make it actually draw stuff
|
||||
out_actions.push(
|
||||
Action::MenuAction(MenuSubAction::WaitForInputAction)
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match self.scenes.take() {
|
||||
//Psyche! Look at the similar function in dispatcher.
|
||||
Some(mut local_scenes) => {
|
||||
for index in 0..local_scenes.len() {
|
||||
match local_scenes[index].take() {
|
||||
Some(scene) => {
|
||||
match scene.receive_menu_sub_action(menu_sub_action) {
|
||||
store::ReceiveMSAReturnOption::NewAction(out_msa_vec, out_reference) => {
|
||||
for msa in out_msa_vec {
|
||||
out_actions.push(Action::MenuAction(msa));
|
||||
}
|
||||
local_scenes[index] = Some(out_reference);
|
||||
}
|
||||
store::ReceiveMSAReturnOption::NoNewAction(out_reference) => {
|
||||
local_scenes[index] = Some(out_reference);
|
||||
}
|
||||
}
|
||||
}
|
||||
None => println!("There is a missing scene...")
|
||||
}
|
||||
}
|
||||
|
||||
self.scenes = Some(local_scenes);
|
||||
super::store::ReceiveActionReturnOption::NewAction(out_actions, false, self)
|
||||
}
|
||||
None => {
|
||||
panic!("No scenes yet ( ͡° ͜ʖ ͡°)");
|
||||
super::store::ReceiveActionReturnOption::NoNewAction(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => { super::store::ReceiveActionReturnOption::NoNewAction(self) }
|
||||
}
|
||||
}
|
||||
}
|
117
src/sprite.rs
Normal file
117
src/sprite.rs
Normal file
|
@ -0,0 +1,117 @@
|
|||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
|
||||
use super::action::Action;
|
||||
use super::dispatcher::Dispatcher;
|
||||
use super::img::Img;
|
||||
use super::renderer::Renderer;
|
||||
use super::store::Store;
|
||||
|
||||
pub struct Sprite {
|
||||
///A sprite with animations (using the function animate)
|
||||
pos: (u8, u8),
|
||||
frames: Vec<Img>,
|
||||
anims: Vec<(usize, usize)>,
|
||||
current_anim: usize,
|
||||
current_frame: usize,
|
||||
dt_since_last_frame: f64,
|
||||
framerate: f64,
|
||||
}
|
||||
|
||||
/// Wrapper for the Img struct that handles animations and location.
|
||||
impl Sprite {
|
||||
pub fn new(pos: (u8, u8), frames: Vec<Img>, anims: Vec<(usize, usize)>, framerate: f64) -> Self {
|
||||
Self {
|
||||
pos,
|
||||
frames,
|
||||
anims,
|
||||
current_anim: 0,
|
||||
current_frame: 0,
|
||||
dt_since_last_frame: 0.0,
|
||||
framerate,
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the internal state of the animation
|
||||
pub fn animate(&mut self, dt: f64) {
|
||||
if self.dt_since_last_frame + dt >= (1.0 / self.framerate) {
|
||||
if self.current_frame + 1 < self.anims[self.current_anim].0 + self.anims[self.current_anim].1 {
|
||||
self.current_frame += 1;
|
||||
} else {
|
||||
self.current_frame = self.anims[self.current_anim].0
|
||||
}
|
||||
self.dt_since_last_frame = 0.0;
|
||||
} else {
|
||||
self.dt_since_last_frame += dt;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_pos(&self) -> (u8, u8) { return self.pos.clone(); }
|
||||
|
||||
pub fn set_pos(&mut self, pos: (u8, u8)) { self.pos = pos }
|
||||
|
||||
pub fn get_width(&self) -> i32 {
|
||||
return self.get_current_frame().get_width().clone() as i32;
|
||||
}
|
||||
|
||||
pub fn get_height(&self) -> i32 {
|
||||
return self.get_current_frame().get_pixels().len() as i32 / *self.get_current_frame().get_width() as i32;
|
||||
}
|
||||
|
||||
pub fn get_current_frame(&self) -> Img {
|
||||
self.frames[self.current_frame].clone()
|
||||
}
|
||||
|
||||
/// Wraps Img::new_from_file.
|
||||
pub fn new_from_File(mut f: File, pix_per_frame: usize, pos: (u8, u8), anims: Vec<(usize, usize)>, framerate: f64) -> Self {
|
||||
let mut width = 0;
|
||||
let mut pixels = vec![];
|
||||
let mut frames = vec![];
|
||||
let real_pix_per_frame = pix_per_frame / 2;
|
||||
|
||||
let mut buf: Vec<u8> = vec![];
|
||||
f.read_to_end(&mut buf);
|
||||
|
||||
if buf.len() < 4 {
|
||||
return Self {
|
||||
pos,
|
||||
frames: vec![],
|
||||
anims: vec![],
|
||||
current_anim: 0,
|
||||
current_frame: 0,
|
||||
dt_since_last_frame: 0.0,
|
||||
framerate: 0.0,
|
||||
};
|
||||
}
|
||||
|
||||
let mut ctr = 0;
|
||||
|
||||
for (num) in buf.iter() {
|
||||
if ctr >= 4 {
|
||||
if (ctr - 4) % real_pix_per_frame == 0 {
|
||||
pixels = vec![*num];
|
||||
} else if (ctr - 4) % real_pix_per_frame == real_pix_per_frame - 1 {
|
||||
pixels.push(*num);
|
||||
frames.push(
|
||||
Img::new_from_u8(width, pixels.clone())
|
||||
);
|
||||
} else {
|
||||
pixels.push(*num);
|
||||
}
|
||||
} else {
|
||||
width = width | ((buf[ctr] as usize) << (3 - ctr));
|
||||
}
|
||||
ctr += 1;
|
||||
}
|
||||
|
||||
return Self {
|
||||
pos,
|
||||
frames,
|
||||
anims,
|
||||
current_anim: 0,
|
||||
current_frame: 0,
|
||||
dt_since_last_frame: 0.0,
|
||||
framerate,
|
||||
};
|
||||
}
|
||||
}
|
20
src/store.rs
Normal file
20
src/store.rs
Normal file
|
@ -0,0 +1,20 @@
|
|||
use super::action::Action;
|
||||
use super::action::MenuSubAction;
|
||||
use super::dispatcher::Dispatcher;
|
||||
|
||||
pub enum ReceiveActionReturnOption<'a> {
|
||||
NoNewAction(&'a mut Store<'a>),
|
||||
NewAction(Vec<Action>, /*add to secondary*/ bool, &'a mut Store<'a>),
|
||||
}
|
||||
|
||||
pub enum ReceiveMSAReturnOption<'a> {
|
||||
NoNewAction(&'a mut super::scene_manager::Scene<'a>),
|
||||
NewAction(Vec<MenuSubAction>, &'a mut super::scene_manager::Scene<'a>),
|
||||
}
|
||||
|
||||
/// Turns any boring struct in a Store in the Flux pattern sense. These objects contain all the pro-
|
||||
/// grams logic and communicate by ways of actions through the dispatcher.
|
||||
pub trait Store<'a> {
|
||||
///The function that is called by the dispatcher to hand over a reference to an action.
|
||||
fn receive_action(&'a mut self, action: &Action, dt: &f64) -> ReceiveActionReturnOption<'a>;
|
||||
}
|
54
src/test_obj.rs
Normal file
54
src/test_obj.rs
Normal file
|
@ -0,0 +1,54 @@
|
|||
use action::{Action, MenuSubAction};
|
||||
use dispatcher::Dispatcher;
|
||||
use img::Img;
|
||||
use sprite::Sprite;
|
||||
use store::ReceiveActionReturnOption;
|
||||
use store::Store;
|
||||
|
||||
pub struct TestObj {
|
||||
///A game object for testing
|
||||
sprite: Option<Sprite>,
|
||||
}
|
||||
|
||||
impl<'a> TestObj {
|
||||
pub fn new(sprite: Sprite) -> Self {
|
||||
Self {
|
||||
sprite: Some(sprite),
|
||||
}
|
||||
}
|
||||
|
||||
fn send_frame(&'a mut self, dt: f64) -> Action {
|
||||
let mut out_action = Action::EmptyAction;
|
||||
|
||||
match self.sprite.take() {
|
||||
Some(mut sprite) => {
|
||||
sprite.animate(dt);
|
||||
out_action = Action::AddImgToCanvasAction(
|
||||
sprite.get_pos().0,
|
||||
sprite.get_pos().1,
|
||||
sprite.get_current_frame(),
|
||||
);
|
||||
self.sprite = Some(sprite);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
return out_action;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Store<'a> for TestObj {
|
||||
fn receive_action(&'a mut self, action: &Action, dt: &f64) -> ReceiveActionReturnOption<'a> {
|
||||
match action {
|
||||
&Action::SendFrameAction => {
|
||||
return ReceiveActionReturnOption::NewAction(
|
||||
vec!(
|
||||
self.send_frame(dt.clone())
|
||||
),
|
||||
false,
|
||||
self,
|
||||
);
|
||||
}
|
||||
_ => return ReceiveActionReturnOption::NoNewAction(self)
|
||||
}
|
||||
}
|
||||
}
|
160
src/tests.rs
Normal file
160
src/tests.rs
Normal file
|
@ -0,0 +1,160 @@
|
|||
/// The tests file. Tests should be run with the --test-threads 1 option so they don't block the
|
||||
/// SDL environment for each other.
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
|
||||
use rand;
|
||||
use sdl2::video::Window;
|
||||
|
||||
use action::MenuSubAction;
|
||||
use button::Button;
|
||||
use scene_manager::MinimalScene;
|
||||
use scene_manager::SceneManager;
|
||||
use test_obj::TestObj;
|
||||
|
||||
use super::action::Action;
|
||||
use super::dispatcher::Dispatcher;
|
||||
use super::img::Img;
|
||||
use super::renderer::Renderer;
|
||||
use super::sprite::Sprite;
|
||||
use super::store::ReceiveActionReturnOption;
|
||||
use super::store::Store;
|
||||
|
||||
struct TestStore {
|
||||
in_num: u8,
|
||||
out_num: u8,
|
||||
}
|
||||
|
||||
impl<'a> Store<'a> for TestStore {
|
||||
fn receive_action(&'a mut self, action: &Action, dt: &f64) -> ReceiveActionReturnOption<'a> {
|
||||
match action {
|
||||
&Action::TestAction(num) => {
|
||||
self.out_num = num;
|
||||
return ReceiveActionReturnOption::NoNewAction(self);
|
||||
}
|
||||
&Action::EndFrameAction => {
|
||||
return ReceiveActionReturnOption::NewAction(
|
||||
vec!(
|
||||
Action::TestAction(self.in_num)
|
||||
),
|
||||
false,
|
||||
self,
|
||||
);
|
||||
}
|
||||
_ => return ReceiveActionReturnOption::NoNewAction(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dispatcher() {
|
||||
/*creates a TestStore instance that holds a random number. The number will be sent to the dispatcher
|
||||
through a TestAction. The dispatcher should return the value. It will be asserted if the action pay-
|
||||
load value equals the value owned by the TestStore instance*/
|
||||
|
||||
let mut test_num = rand::random();
|
||||
|
||||
while test_num == 0 {
|
||||
test_num = rand::random()
|
||||
}
|
||||
|
||||
let mut test_store = TestStore { in_num: test_num, out_num: 0 };
|
||||
|
||||
{
|
||||
let mut disp = Dispatcher::new(1.0 / 60.0);
|
||||
disp.enter_refs(vec!(&mut test_store));
|
||||
|
||||
for i in 1..10 {
|
||||
disp.dispatch();
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(test_num, test_store.out_num)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_renderer() {
|
||||
/* creates a test game that owns a renderer and renders an image with all the colors of the color
|
||||
space*/
|
||||
let mut test_renderer: Renderer;
|
||||
test_renderer = Renderer::new(256, 256, None);
|
||||
let mut test_img = Img::new_from_u8(0, vec![]);
|
||||
|
||||
match File::open("./resources/test_img.u4i") {
|
||||
Ok(f) => {
|
||||
test_img = Img::new_from_File(f);
|
||||
}
|
||||
Err(e) => {
|
||||
print!("{}", e);
|
||||
}
|
||||
}
|
||||
|
||||
assert!(test_renderer.test(test_img));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sprites_and_menus() {
|
||||
let mut test_renderer = Renderer::new(256, 256, None);
|
||||
let mut test_sprite = Sprite::new((0, 0), vec![], vec![], 0.0);
|
||||
let mut button_sprite = Sprite::new((0, 0), vec![], vec![], 0.0);
|
||||
|
||||
match File::open("./resources/test_sprite.u4i") {
|
||||
Ok(f) => {
|
||||
test_sprite = Sprite::new_from_File(
|
||||
f,
|
||||
16,
|
||||
(0, 0),
|
||||
vec![(0, 12)],
|
||||
30.0,
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("{}", e);
|
||||
}
|
||||
}
|
||||
|
||||
let mut test_object = TestObj::new(test_sprite);
|
||||
|
||||
match File::open("./resources/exit_button.u4i") {
|
||||
Ok(f) => {
|
||||
button_sprite = Sprite::new_from_File(
|
||||
f,
|
||||
133,
|
||||
(0, 0),
|
||||
vec![(0, 1)],
|
||||
1.0,
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("{}", e);
|
||||
}
|
||||
}
|
||||
|
||||
let mut test_button = Button::new(
|
||||
button_sprite,
|
||||
(0, 0),
|
||||
Box::new(|| {
|
||||
println!("Clicked button!");
|
||||
return MenuSubAction::QuitAction;
|
||||
}),
|
||||
);
|
||||
|
||||
let mut test_scene = MinimalScene::new(
|
||||
test_button
|
||||
);
|
||||
|
||||
let mut scene_manager = SceneManager::new();
|
||||
|
||||
scene_manager.add_scenes(vec![&mut test_scene]);
|
||||
|
||||
{
|
||||
let mut disp = Dispatcher::new(1.0);
|
||||
disp.enter_refs(vec!(&mut test_renderer, &mut test_object, &mut scene_manager));//, &mut test_object));
|
||||
|
||||
while disp.dispatch() {};
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue