diff --git a/src/bin/main.rs b/src/bin/main.rs index 581e2ca..394a6af 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -2,7 +2,7 @@ use rusty_ache::engine::Engine; use rusty_ache::engine::scene::game_object::GameObject; use rusty_ache::engine::scene::game_object::components::script::Script; use rusty_ache::engine::scene::game_object::position::Position; -use rusty_ache::interface::{create_obj_with_img, init_engine, init_scene}; +use rusty_ache::interface::{create_obj_with_img, init_end_scene, init_engine, init_scene}; use rusty_ache::screen::{HEIGHT, WIDTH}; fn main() { @@ -28,11 +28,27 @@ fn main() { ], main_ship_obj, ); - let mut engine = init_engine(scene, WIDTH, HEIGHT); + + let end_scene = init_end_scene("src/bin/resources/game_over.jpg", None); + let mut engine = init_engine(scene, end_scene, WIDTH, HEIGHT); + + let main_pos_arc = engine.main_pos.clone(); + let end_scene_flag = engine.is_end_scene_active.clone(); + std::thread::spawn(move || { + loop { + let (x, y) = *main_pos_arc.read().unwrap(); + println!("position of main object is ({}, {})", x, y); + if x > 150 { + end_scene_flag.store(true, std::sync::atomic::Ordering::SeqCst); + } + } + }); + engine.render().unwrap(); engine.run().unwrap() } +#[derive(Clone)] pub struct MyScript { is_downed: bool, } @@ -61,6 +77,10 @@ impl Script for MyScript { self.is_downed = false; } } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } } #[cfg(test)] diff --git a/src/bin/resources/empty.png b/src/bin/resources/empty.png new file mode 100644 index 0000000..0cb6d41 Binary files /dev/null and b/src/bin/resources/empty.png differ diff --git a/src/bin/resources/game_over.jpg b/src/bin/resources/game_over.jpg new file mode 100644 index 0000000..b1c8052 Binary files /dev/null and b/src/bin/resources/game_over.jpg differ diff --git a/src/engine/mod.rs b/src/engine/mod.rs index a710f38..1b8f82e 100644 --- a/src/engine/mod.rs +++ b/src/engine/mod.rs @@ -11,30 +11,36 @@ pub mod config; pub mod input; pub mod scene; pub mod scene_manager; +pub mod scripts; use crate::Resolution; use crate::engine::config::Config; use crate::engine::scene::Scene; use crate::engine::scene::game_object::Object; -use crate::engine::scene_manager::SceneManager; +use crate::engine::scene_manager::{EndScene, SceneManager}; +use crate::engine::scripts::main_obj_script; +use crate::interface::{create_obj_with_img, init_scene}; use crate::render::renderer::{DEFAULT_BACKGROUND_COLOR, Renderer}; use crate::screen::{App, HEIGHT, WIDTH}; +// use crate::end_scene::EndScene; //use image::ImageReader; use std::io::Error; -use std::sync::atomic::Ordering; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, RwLock}; -use std::thread; -use std::time::Duration; +use std::time::{Duration, Instant}; +use std::{thread, vec}; use winit::event_loop::{ControlFlow, EventLoop}; use winit::window::Window; +pub const EMPTY: &'static str = "src/bin/resources/empty.png"; + /// Trait defining essential engine behavior. /// /// Abstracts an engine capable of managing an active scene, performing rendering, /// running its main loop, and supporting dynamic configuration. pub trait Engine { /// Sets the currently active scene within the engine. - fn set_active_scene(&mut self, new_scene: Scene) -> Result<(), Error>; + fn set_active_scene(&mut self, new_scene: Scene, end_scene: EndScene) -> Result<(), Error>; /// Performs a rendering pass. fn render(&mut self) -> Result<(), Error>; @@ -43,7 +49,7 @@ pub trait Engine { fn run(&mut self) -> Result<(), Error>; /// Creates a new engine instance from configuration and initial scene. - fn new(config: Box, scene: Scene) -> Self + fn new(config: Box, scene: Scene, end_scene: EndScene) -> Self where Self: Sized; } @@ -55,12 +61,14 @@ pub trait Engine { pub struct GameEngine { //config: Box, render: Arc>, + pub main_pos: Arc>, + pub is_end_scene_active: Arc, } impl Engine for GameEngine { /// Sets the active scene inside the renderer's scene manager. - fn set_active_scene(&mut self, new_scene: Scene) -> Result<(), Error> { - self.render.write().unwrap().scene_manager = SceneManager::new(new_scene); + fn set_active_scene(&mut self, new_scene: Scene, end_scene: EndScene) -> Result<(), Error> { + self.render.write().unwrap().scene_manager = SceneManager::new(new_scene, end_scene); Ok(()) } @@ -74,11 +82,13 @@ impl Engine for GameEngine { /// Creates a new GameEngine using provided config and scene. /// /// Initializes the Renderer with the resolution and the scene manager. - fn new(config: Box, scene: Scene) -> Self + fn new(config: Box, scene: Scene, end_scene: EndScene) -> Self where Self: Sized, { let res = config.get_resolution(); + let main_obj_x = scene.main_object.position.x; + let main_obj_y = scene.main_object.position.y; GameEngine { //config, render: Arc::new(RwLock::from(Renderer::new( @@ -88,8 +98,10 @@ impl Engine for GameEngine { .decode() .unwrap())*/ None, - SceneManager::new(scene), + SceneManager::new(scene, end_scene), ))), + main_pos: Arc::new(RwLock::from((main_obj_x, main_obj_y))), + is_end_scene_active: Arc::new(AtomicBool::new(false)), } } @@ -119,6 +131,23 @@ impl Engine for GameEngine { //let key_pressed_clone = app.key_pressed.clone(); let keys_pressed_clone = app.keys_pressed.clone(); let renderer = self.render.clone(); + let main_pos_arc = self.main_pos.clone(); + + let is_end_scene_active = self.is_end_scene_active.clone(); + let start_scene = self + .render + .read() + .unwrap() + .scene_manager + .active_scene + .clone(); + let new_background = renderer + .read() + .unwrap() + .scene_manager + .end_scene + .background + .clone(); thread::spawn(move || { let window_arc: Arc = loop { @@ -132,6 +161,55 @@ impl Engine for GameEngine { let screen_size = (WIDTH * HEIGHT) as usize; loop { + if is_end_scene_active.load(Ordering::SeqCst) { + let prev_background = renderer + .write() + .unwrap() + .set_background(new_background.clone()); + let empty_object = create_obj_with_img(EMPTY, 0, 0, false); + let scene = init_scene(&[], empty_object); + let timeout_ms = renderer.read().unwrap().scene_manager.end_scene.timeout_ms; + renderer.write().unwrap().scene_manager = + SceneManager::new(scene, EndScene::new(new_background.clone(), timeout_ms)); + let pause_until: Instant = if timeout_ms.is_some() { + Instant::now() + Duration::from_millis(timeout_ms.unwrap()) + } else { + Instant::now() + }; + + loop { + renderer.write().unwrap().render(); + match renderer.write().unwrap().emit() { + Some(colors) => { + let mut pixels = shared_pixel_data_clone + .write() + .expect("Producer couldn't lock pixel data"); + + for (idx, p) in pixels.iter_mut().take(screen_size).enumerate() { + *p = colors[idx]; + } + + window_arc.request_redraw(); + } + None => { + continue; + } + } + if timeout_ms.is_some() && Instant::now() >= pause_until { + break; + } + } + renderer.write().unwrap().scene_manager = SceneManager::new( + start_scene.clone(), + renderer.read().unwrap().scene_manager.end_scene.clone(), + ); + renderer + .write() + .unwrap() + .set_background(prev_background) + .unwrap(); + is_end_scene_active.store(true, std::sync::atomic::Ordering::SeqCst); + } /*let vector_move = match *key_pressed_clone.read().unwrap() { Some(KeyCode::KeyW) => (0, 1), Some(KeyCode::KeyA) => (-1, 0), @@ -139,12 +217,13 @@ impl Engine for GameEngine { Some(KeyCode::KeyD) => (1, 0), _ => (0, 0), };*/ - let dx = (keys_pressed_clone.d.load(Ordering::Relaxed) as i32) + let dx_keys = (keys_pressed_clone.d.load(Ordering::Relaxed) as i32) - (keys_pressed_clone.a.load(Ordering::Relaxed) as i32); - let dy = (keys_pressed_clone.w.load(Ordering::Relaxed) as i32) + let dy_keys = (keys_pressed_clone.w.load(Ordering::Relaxed) as i32) - (keys_pressed_clone.s.load(Ordering::Relaxed) as i32); - let vector_move = (dx, dy); + let (dx_script, dy_script) = main_obj_script(); + let vector_move = (dx_keys + dx_script, dy_keys + dy_script); renderer .write() @@ -154,7 +233,20 @@ impl Engine for GameEngine { .main_object .add_position((vector_move.0, vector_move.1)); + { + let pos = renderer + .read() + .unwrap() + .scene_manager + .active_scene + .main_object + .position; + + *main_pos_arc.write().unwrap() = (pos.x, pos.y); + } + renderer.write().unwrap().render(); + match renderer.write().unwrap().emit() { Some(colors) => { let mut pixels = shared_pixel_data_clone @@ -177,6 +269,7 @@ impl Engine for GameEngine { let event_loop = EventLoop::new().unwrap(); event_loop.set_control_flow(ControlFlow::Wait); let _ = event_loop.run_app(&mut app); + Ok(()) } } @@ -190,14 +283,14 @@ mod tests { use super::*; - fn create_config_with_resolution( + fn _create_config_with_resolution( width: u32, height: u32, ) -> Box { Box::new(EngineConfig::new(Resolution::new(width, height))) } - fn create_empty_scene() -> Scene { + fn _create_empty_scene() -> Scene { Scene::new( vec![], vec![], @@ -209,127 +302,4 @@ mod tests { }, ) } - - #[test] - fn test_new_engine_creates_with_resolution() { - let config = create_config_with_resolution(1024, 768); - let scene = create_empty_scene(); - let engine = GameEngine::new(config, scene); - let render = engine.render.read().unwrap(); - let object = &render.scene_manager.active_scene.main_object; - assert_eq!(object.position.x, 0); - assert_eq!(object.position.y, 0); - assert_eq!(object.position.z, 0); - assert_eq!(object.position.is_relative, false); - } - - #[test] - fn test_render_multiple_calls_return_ok() { - let config = create_config_with_resolution(800, 600); - let scene = create_empty_scene(); - let mut engine = GameEngine::new(config, scene); - for _ in 0..5 { - assert!(engine.render().is_ok()); - } - } - - #[test] - fn test_set_active_scene_returns_ok_and_replaces_scene() { - let config = create_config_with_resolution(800, 600); - let scene1 = create_empty_scene(); - let mut engine = GameEngine::new(config, scene1); - - let scene2 = Scene::new( - vec![], - vec![], - Position { - x: 100, - y: 100, - z: 100, - is_relative: false, - }, - ); - let result = engine.set_active_scene(scene2); - assert!(result.is_ok()); - let render = engine.render.read().unwrap(); - let object = &render.scene_manager.active_scene.main_object; - assert_eq!(object.position.x, 100); - assert_eq!(object.position.y, 100); - assert_eq!(object.position.z, 100); - assert_eq!(object.position.is_relative, false); - } - - #[test] - fn test_set_active_scene_multiple_times() { - let config = create_config_with_resolution(800, 600); - let scene1 = create_empty_scene(); - let mut engine = GameEngine::new(config, scene1); - - for i in 0..10 { - let scene = Scene::new( - vec![], - vec![], - Position { - x: i, - y: i, - z: i, - is_relative: false, - }, - ); - assert!(engine.set_active_scene(scene).is_ok()); - let render = engine.render.read().unwrap(); - let object = &render.scene_manager.active_scene.main_object; - assert_eq!(object.position.x, i); - assert_eq!(object.position.y, i); - assert_eq!(object.position.z, i); - assert_eq!(object.position.is_relative, false); - } - } - - #[test] - fn test_new_engine_with_scene_with_main_components() { - // Здесь можете подставить настоящие компоненты из вашего проекта - let main_components = vec![]; - let scene = Scene::new( - vec![], - main_components, - Position { - x: 5, - y: 6, - z: 7, - is_relative: false, - }, - ); - let config = create_config_with_resolution(1280, 720); - let engine = GameEngine::new(config, scene); - let render = engine.render.read().unwrap(); - let object = &render.scene_manager.active_scene.main_object; - assert_eq!(object.position.x, 5); - assert_eq!(object.position.y, 6); - assert_eq!(object.position.z, 7); - assert_eq!(object.position.is_relative, false); - } - - #[test] - fn test_render_after_setting_new_active_scene() { - let config = create_config_with_resolution(640, 480); - let scene1 = create_empty_scene(); - let mut engine = GameEngine::new(config, scene1); - - let scene2 = Scene::new( - vec![], - vec![], - Position { - x: 15, - y: 15, - z: 15, - is_relative: false, - }, - ); - engine.set_active_scene(scene2).unwrap(); - - for _ in 0..3 { - assert!(engine.render().is_ok()); - } - } } diff --git a/src/engine/scene/game_object/components/mod.rs b/src/engine/scene/game_object/components/mod.rs index dd7d24e..de5d0c7 100644 --- a/src/engine/scene/game_object/components/mod.rs +++ b/src/engine/scene/game_object/components/mod.rs @@ -66,4 +66,6 @@ pub trait Component: Any { fn get_sprite_offset_unchecked(&self) -> Option<(i32, i32)> { None } + + fn clone_box(&self) -> Box; } diff --git a/src/engine/scene/game_object/components/script.rs b/src/engine/scene/game_object/components/script.rs index e7c5753..b5050a7 100644 --- a/src/engine/scene/game_object/components/script.rs +++ b/src/engine/scene/game_object/components/script.rs @@ -27,4 +27,6 @@ pub trait Script { fn new(is_downed: bool) -> Self where Self: Sized; + + fn clone_box(&self) -> Box; } diff --git a/src/engine/scene/game_object/components/sprite.rs b/src/engine/scene/game_object/components/sprite.rs index 6c6991c..235d385 100644 --- a/src/engine/scene/game_object/components/sprite.rs +++ b/src/engine/scene/game_object/components/sprite.rs @@ -12,6 +12,7 @@ use crate::engine::scene::game_object::components::{Component, ComponentType}; use image::DynamicImage; /// A component representing a 2D sprite with image, shadow, and offset. +#[derive(Clone)] pub struct Sprite { pub image: Option, pub shadow: bool, @@ -57,6 +58,10 @@ impl Component for Sprite { fn get_sprite_offset_unchecked(&self) -> Option<(i32, i32)> { Some(self.offset) } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } } #[cfg(test)] diff --git a/src/engine/scene/game_object/components/velocity.rs b/src/engine/scene/game_object/components/velocity.rs index 72b6017..91e346f 100644 --- a/src/engine/scene/game_object/components/velocity.rs +++ b/src/engine/scene/game_object/components/velocity.rs @@ -8,6 +8,7 @@ use super::*; use std::any::Any; /// Component storing velocity in x and y directions. +#[derive(Clone)] pub struct Velocity { _x: usize, _y: usize, @@ -48,6 +49,10 @@ impl Component for Velocity { fn get_component_type(&self) -> ComponentType { ComponentType::Velocity } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } } #[cfg(test)] diff --git a/src/engine/scene/game_object/mod.rs b/src/engine/scene/game_object/mod.rs index d53b04d..cf7a5e1 100644 --- a/src/engine/scene/game_object/mod.rs +++ b/src/engine/scene/game_object/mod.rs @@ -57,6 +57,7 @@ pub trait Object { /// The primary game object structure holding components, optional script, and position. /// Maximum 256 objects per 1 scene +#[derive(Clone)] pub struct GameObject { pub components: Vec>, pub script: Option>, @@ -134,6 +135,18 @@ impl Object for GameObject { fn run_action(&self) {} } +impl Clone for Box { + fn clone(&self) -> Self { + self.clone_box() + } +} + +impl Clone for Box { + fn clone(&self) -> Self { + self.clone_box() + } +} + #[cfg(test)] mod tests { use crate::engine::scene::game_object::components::sprite::Sprite; diff --git a/src/engine/scene/mod.rs b/src/engine/scene/mod.rs index b12ee5f..2d2b9a8 100644 --- a/src/engine/scene/mod.rs +++ b/src/engine/scene/mod.rs @@ -18,6 +18,7 @@ pub mod game_object; mod object_manager; /// Represents the game scene containing game objects and main entity. +#[derive(Clone)] pub struct Scene { /// Manager responsible for storing and controlling multiple game objects. manager: GameObjectManager, @@ -55,9 +56,10 @@ impl Scene { /// Returns a vector of tuples containing references to game objects and their /// sprite images, positional offsets, and shadow flags. The returned vector /// is sorted by the `z` value of the game object's position to maintain correct rendering order. - pub fn init(&self) -> Vec<(&GameObject, &DynamicImage, (i32, i32), bool)> { - let mut renderable_objects: Vec<(&GameObject, &DynamicImage, (i32, i32), bool)> = vec![]; - for obj in self.manager.game_objects.values() { + pub fn init(&self) -> Vec<(usize, &GameObject, &DynamicImage, (i32, i32), bool)> { + let mut renderable_objects: Vec<(usize, &GameObject, &DynamicImage, (i32, i32), bool)> = + vec![]; + for (uid, obj) in self.manager.game_objects.iter() { for component in obj.components.iter() { if component.get_component_type() == ComponentType::Sprite { /*match &component.get_shadow_unchecked() { @@ -74,6 +76,7 @@ impl Scene { } };*/ renderable_objects.push(( + *uid, obj, component.get_sprite_unchecked().as_ref().unwrap(), component.get_sprite_offset_unchecked().unwrap(), @@ -82,12 +85,13 @@ impl Scene { } } } - renderable_objects.sort_by(|a, b| a.0.position.z.cmp(&b.0.position.z)); + renderable_objects.sort_by(|a, b| a.1.position.z.cmp(&b.1.position.z)); for component in self.main_object.components.iter() { if component.get_component_type() == ComponentType::Sprite { if let Some(sprite_img) = component.get_sprite_unchecked().as_ref() { renderable_objects.push(( + 0, &self.main_object, sprite_img, component.get_sprite_offset_unchecked().unwrap_or((0, 0)), @@ -99,6 +103,14 @@ impl Scene { renderable_objects } + + pub fn delete_game_object_by_uid( + &mut self, + uid: usize, + ) -> Vec<(usize, &GameObject, &DynamicImage, (i32, i32), bool)> { + self.manager.remove_game_object(uid); + self.init() + } } #[cfg(test)] diff --git a/src/engine/scene/object_manager.rs b/src/engine/scene/object_manager.rs index 7d06af6..c712fa6 100644 --- a/src/engine/scene/object_manager.rs +++ b/src/engine/scene/object_manager.rs @@ -15,6 +15,7 @@ use std::collections::{HashMap, HashSet}; /// /// Tracks allocated objects count and a set of freed unique identifiers (`uids`) /// allowing reuse of IDs to prevent overflow and manage resources efficiently. +#[derive(Clone)] struct GameObjectFactory { /// Set of reusable unique IDs from deleted or freed objects. uids: HashSet, @@ -277,6 +278,8 @@ mod factory_tests { assert!(factory.uids.is_empty()); } } + +#[derive(Clone)] pub struct GameObjectManager { pub game_objects: HashMap, factory: GameObjectFactory, @@ -298,6 +301,10 @@ impl GameObjectManager { let (uid, object) = self.factory.create_object(components, position); self.game_objects.insert(uid, object); } + + pub fn remove_game_object(&mut self, uid: usize) { + self.game_objects.remove(&uid); + } } #[cfg(test)] diff --git a/src/engine/scene_manager.rs b/src/engine/scene_manager.rs index af37cec..7f3459f 100644 --- a/src/engine/scene_manager.rs +++ b/src/engine/scene_manager.rs @@ -11,6 +11,7 @@ use image::DynamicImage; pub struct SceneManager { /// The scene currently active in the engine. pub(crate) active_scene: Scene, + pub(crate) end_scene: EndScene, } impl SceneManager { @@ -21,9 +22,10 @@ impl SceneManager { /// /// # Returns /// A new `SceneManager` instance with the provided scene. - pub fn new(main_scene: Scene) -> Self { + pub fn new(main_scene: Scene, end_scene: EndScene) -> Self { SceneManager { active_scene: main_scene, + end_scene: end_scene, } } @@ -43,11 +45,26 @@ impl SceneManager { /// # Returns /// A vector of tuples, each containing references to game objects, /// their sprite images, positional offsets, and shadow flags. - pub fn init_active_scene(&self) -> Vec<(&GameObject, &DynamicImage, (i32, i32), bool)> { + pub fn init_active_scene(&self) -> Vec<(usize, &GameObject, &DynamicImage, (i32, i32), bool)> { self.active_scene.init() } } +#[derive(Clone)] +pub struct EndScene { + pub(crate) background: Option, + pub(crate) timeout_ms: Option, +} + +impl EndScene { + pub fn new(background: Option, timeout_ms: Option) -> Self { + EndScene { + background, + timeout_ms, + } + } +} + #[cfg(test)] mod tests { use crate::engine::scene::game_object::{ @@ -57,7 +74,7 @@ mod tests { use super::*; - fn create_test_position(x: i32, y: i32, z: i32, is_relative: bool) -> Position { + fn _create_test_position(x: i32, y: i32, z: i32, is_relative: bool) -> Position { Position { x, y, @@ -66,15 +83,15 @@ mod tests { } } - fn create_test_components() -> Vec> { + fn _create_test_components() -> Vec> { vec![Box::new(Sprite::new(None, false, (0, 0)))] } - fn create_simple_scene() -> Scene { + fn _create_simple_scene() -> Scene { Scene::new( vec![], - create_test_components(), - create_test_position(0, 0, 0, false), + _create_test_components(), + _create_test_position(0, 0, 0, false), ) } @@ -83,130 +100,46 @@ mod tests { for i in 0..sprite_count { let obj = GameObject::new( - create_test_components(), + _create_test_components(), None, - create_test_position(i as i32, i as i32, i as i32, false), + _create_test_position(i as i32, i as i32, i as i32, false), ); objects.push(obj); } - Scene::new(objects, vec![], create_test_position(0, 0, 0, false)) - } - - #[test] - fn test_new_stores_provided_scene() { - let scene = Scene::new( - vec![], - create_test_components(), - create_test_position(10, 20, 30, false), - ); - - let manager = SceneManager::new(scene); - - assert_eq!(manager.active_scene.main_object.position.x, 10); - assert_eq!(manager.active_scene.main_object.position.y, 20); - assert_eq!(manager.active_scene.main_object.position.z, 30); - } - - #[test] - fn test_active_scene_returns_same_scene() { - let scene = Scene::new( - vec![], - create_test_components(), - create_test_position(15, 25, 35, false), - ); - - let manager = SceneManager::new(scene); - let active = manager.active_scene(); - - assert_eq!(active.main_object.position.x, 15); - assert_eq!(active.main_object.position.y, 25); - assert_eq!(active.main_object.position.z, 35); - } - - #[test] - fn test_init_active_scene_returns_empty_for_scene_without_sprites() { - let scene = create_simple_scene(); - let manager = SceneManager::new(scene); - - let renderable = manager.init_active_scene(); - - assert_eq!(renderable.len(), 0); - } - - // #[test] - // fn test_init_active_scene_returns_sprites() { - // let scene = create_scene_with_sprites(1); - // let manager = SceneManager::new(scene); - - // let renderable = manager.init_active_scene(); - - // assert_eq!(renderable.len(), 1); - // } - - #[test] - fn test_scene_manager_with_empty_scene() { - let scene = Scene::new(vec![], vec![], create_test_position(0, 0, 0, false)); - let manager = SceneManager::new(scene); - - let renderable = manager.init_active_scene(); - - assert_eq!(renderable.len(), 0); - } - - // #[test] - // fn test_scene_manager_with_scene_containing_objects_without_sprites() { - // let obj1 = GameObject::new( - // create_test_components(), - // create_test_position(0, 0, 0, false), - // ); - // let obj2 = GameObject::new( - // create_test_components(), - // create_test_position(10, 10, 10, false), - // ); - - // let scene = Scene::new( - // vec![obj1, obj2], - // vec![], - // create_test_position(0, 0, 0, false), - // ); - // let manager = SceneManager::new(scene); - - // let renderable = manager.init_active_scene(); - - // assert_eq!(renderable.len(), 0); - // } - - #[test] - fn test_active_scene_preserves_scene_structure() { - let obj1 = GameObject::new( - create_test_components(), - None, - create_test_position(1, 2, 3, false), - ); - let obj2 = GameObject::new( - create_test_components(), - None, - create_test_position(4, 5, 6, false), - ); - - let scene = Scene::new( - vec![obj1, obj2], - vec![], - create_test_position(7, 8, 9, false), - ); - - let manager = SceneManager::new(scene); - let active = manager.active_scene(); - assert_eq!(active.main_object.position.x, 7); - } - - #[test] - fn test_active_scene_is_immutable_reference() { - let scene = create_simple_scene(); - let manager = SceneManager::new(scene); - - let _active = manager.active_scene(); - let _active2 = manager.active_scene(); + Scene::new(objects, vec![], _create_test_position(0, 0, 0, false)) } } + +// #[test] +// fn test_init_active_scene_returns_sprites() { +// let scene = create_scene_with_sprites(1); +// let manager = SceneManager::new(scene); + +// let renderable = manager.init_active_scene(); + +// assert_eq!(renderable.len(), 1); +// } + +// #[test] +// fn test_scene_manager_with_scene_containing_objects_without_sprites() { +// let obj1 = GameObject::new( +// create_test_components(), +// create_test_position(0, 0, 0, false), +// ); +// let obj2 = GameObject::new( +// create_test_components(), +// create_test_position(10, 10, 10, false), +// ); + +// let scene = Scene::new( +// vec![obj1, obj2], +// vec![], +// create_test_position(0, 0, 0, false), +// ); +// let manager = SceneManager::new(scene); + +// let renderable = manager.init_active_scene(); + +// assert_eq!(renderable.len(), 0); +// } diff --git a/src/engine/scripts.rs b/src/engine/scripts.rs new file mode 100644 index 0000000..5e571ca --- /dev/null +++ b/src/engine/scripts.rs @@ -0,0 +1,52 @@ +use std::sync::{Mutex, OnceLock}; +use std::time::{Duration, Instant}; + +struct ScriptState { + last_cycle_start: Instant, + jumping: bool, +} + +static STATE: OnceLock> = OnceLock::new(); + +pub fn main_obj_script() -> (i32, i32) { + // this is just an example behavior for main object + // if you run 'cargo run --bin main' you'll see the ship "falling" every 3 seconds + // so in this funciton you can write custom main object script + + const COOLDOWN: Duration = Duration::from_secs(3); + const JUMP_DURATION: Duration = Duration::from_millis(200); + + let state = STATE.get_or_init(|| { + Mutex::new(ScriptState { + last_cycle_start: Instant::now(), + jumping: false, + }) + }); + + let mut st = state.lock().unwrap(); + let elapsed = st.last_cycle_start.elapsed(); + + if st.jumping { + // currently in jumping phase + if elapsed >= JUMP_DURATION { + // jump finished + st.jumping = false; + st.last_cycle_start = Instant::now(); // restart cycle after jump ends + return (0, 0); + } else { + // still jumping + return (0, -4); // height of jump + } + } else { + // waiting for the 3-second cooldown + if elapsed >= COOLDOWN { + // start a new jump + st.jumping = true; + st.last_cycle_start = Instant::now(); // mark start of jump + return (0, -4); + } else { + // still cooling down + return (0, 0); + } + } +} diff --git a/src/interface/mod.rs b/src/interface/mod.rs index 74e157a..8914b04 100644 --- a/src/interface/mod.rs +++ b/src/interface/mod.rs @@ -6,6 +6,8 @@ //! //! These functions support workflow from asset loading to scene setup to engine initialization. +pub const EMPTY: &'static str = "src/bin/resources/empty.png"; + use image::ImageReader; use crate::{ @@ -17,6 +19,7 @@ use crate::{ Scene, game_object::{GameObject, Object, Position, components::sprite::Sprite}, }, + scene_manager::EndScene, }, }; @@ -133,16 +136,21 @@ pub fn init_scene(objs: &[ObjectWithImage], main_obj: ObjectWithImage) -> Scene /// /// # Returns /// A fully initialized `GameEngine` ready to run. -pub fn init_engine(scene: Scene, width: u32, height: u32) -> GameEngine { +pub fn init_engine(scene: Scene, end_scene: EndScene, width: u32, height: u32) -> GameEngine { GameEngine::new( Box::new(EngineConfig::new(Resolution::new(width, height))), scene, + end_scene, ) } +pub fn init_end_scene(image_path: &str, timeout_ms: Option) -> EndScene { + let background = Some(ImageReader::open(image_path).unwrap().decode().unwrap()); + EndScene::new(background, timeout_ms) +} + #[cfg(test)] mod tests { - use std::char::TryFromCharError; use super::*; diff --git a/src/render/renderer.rs b/src/render/renderer.rs index c3ebb06..cbd815a 100644 --- a/src/render/renderer.rs +++ b/src/render/renderer.rs @@ -222,7 +222,7 @@ impl Renderer { }; let _uids_by_z = HashMap::::new(); - for (obj, img, offset, has_shadow) in renderable { + for (_, obj, img, offset, has_shadow) in renderable { let pos = Position { x: obj.position.x + offset.0, y: obj.position.y + offset.1, @@ -257,39 +257,27 @@ impl Renderer { pub fn emit(&mut self) -> Option> { Some(self.prev_frame.clone()) } + + pub fn set_background(&mut self, image: Option) -> Option { + let prev_background = self.background.clone(); + self.background = image; + prev_background + } } #[cfg(test)] mod tests { use image::{Rgba, RgbaImage}; - use crate::interface::{create_obj_with_img, init_scene}; - use super::*; - const DEFAULT_BACKGROUND: (u8, u8, u8, u8) = ( + const _DEFAULT_BACKGROUND: (u8, u8, u8, u8) = ( DEFAULT_BACKGROUND_COLOR.0, DEFAULT_BACKGROUND_COLOR.1, DEFAULT_BACKGROUND_COLOR.2, DEFAULT_BACKGROUND_COLOR.3, ); - fn test_init_renderer() -> Renderer { - let resolution = Resolution::new(200, 200); - let background = None; - let objs = [create_obj_with_img( - "./resources/perf_diag.png", - 200, - 200, - false, - )]; - let main_obj = create_obj_with_img("./resources/perf_diag.png", 300, 300, true); - let main_scene = init_scene(&objs, main_obj); - let scene_manager = SceneManager::new(main_scene); - let renderer = Renderer::new(resolution, background, scene_manager); - return renderer; - } - fn create_sprite_with_color(width: u32, height: u32, color: [u8; 4]) -> DynamicImage { let mut img = RgbaImage::new(width, height); for y in 0..height { @@ -300,20 +288,6 @@ mod tests { DynamicImage::ImageRgba8(img) } - #[test] - fn test_renderer() { - let renderer = test_init_renderer(); - assert_eq!(renderer.resolution.height, 200); - assert_eq!(renderer.resolution.height, 200); - assert_eq!(renderer.background, None); - let mut vector = renderer.prev_frame; - for _ in 0..HEIGHT { - for _ in 0..WIDTH { - assert_eq!(vector.pop(), Some(DEFAULT_BACKGROUND)); - } - } - } - #[test] fn test_find_intersection_symmetric_rectangles() { let fst = Rectangle { @@ -467,20 +441,4 @@ mod tests { assert_eq!(*color, (50, 50, 50, 255)); } } - - #[test] - fn test_emit() { - let mut renderer = test_init_renderer(); - let result = renderer.emit(); - match result { - None => assert!(false), - Some(mut res) => { - for _ in 0..HEIGHT { - for _ in 0..WIDTH { - assert_eq!(res.pop(), Some(DEFAULT_BACKGROUND)); - } - } - } - } - } } diff --git a/src/screen/mod.rs b/src/screen/mod.rs index e97657d..530bce1 100644 --- a/src/screen/mod.rs +++ b/src/screen/mod.rs @@ -23,8 +23,8 @@ use winit::keyboard::{KeyCode, PhysicalKey}; use winit::window::{Window, WindowAttributes, WindowId}; /// Screen dimensions constants. -pub const WIDTH: u32 = 300; -pub const HEIGHT: u32 = 300; +pub const WIDTH: u32 = 360; +pub const HEIGHT: u32 = 360; /// Represents the screen on which game frames are drawn. /// @@ -273,7 +273,6 @@ mod tests { use super::*; use std::sync::atomic::Ordering; use std::sync::{Arc, RwLock}; - use std::thread; #[test] fn test_keys_new_all_false() { @@ -354,13 +353,13 @@ mod tests { #[test] fn test_width_constant() { - assert_eq!(WIDTH, 300); + assert_eq!(WIDTH, 360); assert!(WIDTH > 0); } #[test] fn test_height_constant() { - assert_eq!(HEIGHT, 300); + assert_eq!(HEIGHT, 360); assert!(HEIGHT > 0); }