diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
new file mode 100644
index 0000000..b243bd9
--- /dev/null
+++ b/.github/workflows/docs.yml
@@ -0,0 +1,37 @@
+name: Deploy Documentation
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+permissions:
+ contents: write
+
+jobs:
+ Build-And-Deploy:
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup Rust
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: stable
+ override: true
+ components: rust-docs
+
+ - name: Generate Documentation
+ run: |
+ cargo doc --no-deps --all-features
+ echo "" > target/doc/index.html
+
+ - name: Deploy to GitHub Pages
+ uses: peaceiris/actions-gh-pages@v3
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ publish_dir: ./target/doc
+ publish_branch: docs
+ force_orphan: true
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..689020f
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,88 @@
+# Contributing to rusty_ache
+
+All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how this project handles them. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions.
+
+## Table of Contents
+
+- [I Have a Question](#i-have-a-question)
+- [I Want To Contribute](#i-want-to-contribute)
+- [Reporting Bugs](#reporting-bugs)
+- [Suggesting Enhancements](#suggesting-enhancements)
+
+
+## I Have a Question
+
+> If you want to ask a question, we assume that you have read the available [documentation](https://p1onerka.github.io/rusty_ache/rusty_ache/index.html).
+
+Before you ask a question, it is best to search for existing [Issues](https://github.com/p1onerka/rusty_ache/issues) that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue.
+
+If you then still feel the need to ask a question and need clarification, we recommend the following:
+
+- Open an [Issue](https://github.com/p1onerka/rusty_ache/issues/new).
+- Provide as much context as you can about what you're running into.
+- Provide project and platform versions, depending on what seems relevant.
+
+We will then take care of the issue as soon as possible.
+
+## I Want To Contribute
+
+> ### Legal Notice
+> When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project licence.
+
+### Reporting Bugs
+
+#### Before Submitting a Bug Report
+
+A good bug report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible.
+
+- Make sure that you are using the latest version.
+- Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions (Make sure that you have read the [documentation](https://p1onerka.github.io/rusty_ache/rusty_ache/index.html). If you are looking for support, you might want to check [this section](#i-have-a-question)).
+- To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error in the bug tracker.
+- Collect information about the bug:
+ - Stack trace (Traceback)
+ - OS, Platform and Version (Windows, Linux, macOS, x86, ARM).
+ - Version of the compiler, SDK, runtime environment, depending on what seems relevant.
+ - Possibly your input and the output.
+ - Can you reliably reproduce the issue? And can you also reproduce it with older versions?
+
+#### How Do I Submit a Good Bug Report?
+
+> You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to xeniia.ka@gmail.com.
+
+We use GitHub issues to track bugs and errors. If you run into an issue with the project:
+
+- Open an [Issue](https://github.com/p1onerka/rusty_ache/issues/new).
+- Explain the behavior you would expect and the actual behavior.
+- Please provide as much context as possible and describe the *reproduction steps* that someone else can follow to recreate the issue on their own. For good bug reports you should isolate the problem and create a reduced test case.
+- Provide the information you collected in the previous section.
+
+Once it's filed:
+
+- The project team will label the issue accordingly.
+- A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps.
+- If the team is able to reproduce the issue, it will be marked `needs-fix`, as well as possibly other tags (such as `critical`), and the issue will be left to be implemented by someone.
+
+
+### Suggesting Enhancements
+
+This section guides you through submitting an enhancement suggestion for *rusty_ache* including completely new features and minor improvements to existing functionality. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions.
+
+#### Before Submitting an Enhancement
+
+- Make sure that you are using the latest version.
+- Read the [documentation](https://p1onerka.github.io/rusty_ache/rusty_ache/index.html) carefully and find out if the functionality is already covered, maybe by an individual configuration.
+- Perform a [search](https://github.com/p1onerka/rusty_ache/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
+- Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library.
+
+#### How Do I Submit a Good Enhancement Suggestion?
+
+Enhancement suggestions are tracked as [GitHub issues](https://github.com/p1onerka/rusty_ache/issues).
+
+- Use a clear and descriptive title for the issue to identify the suggestion.
+- Provide a step-by-step description of the suggested enhancement in as many details as possible.
+- Describe the current behavior and explain which behavior you expected to see instead and why. At this point you can also tell which alternatives do not work for you.
+- You may want to include screenshots or screen recordings which help you demonstrate the steps or point out the part which the suggestion is related to.
+- Explain why this enhancement would be useful to most *rusty_ache* users. You may also want to point out the other projects that solved it better and which could serve as inspiration.
+
+## Attribution
+This guide is based on the [contributing.md](https://contributing.md/generator)!
diff --git a/Cargo.toml b/Cargo.toml
index 925f07e..19df84e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,6 +2,8 @@
name = "rusty_ache"
version = "0.1.0"
edition = "2024"
+documentation = "https://p1onerka.github.io/rusty_ache"
+repository = "https://github.com/p1onerka/rusty_ache"
[[bin]]
name = "main"
@@ -12,3 +14,6 @@ path = "src/bin/main.rs"
image = "0.25.8"
pixels = "0.15.0"
winit = "0.30.12"
+
+[package.metadata.docs.rs]
+all-features = true
\ No newline at end of file
diff --git a/README.md b/README.md
index 708471c..a42b542 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,41 @@
-# rusty_ache
+# Rusty Ache
Blazingly fast game engine written in Rust.
+
+## How to run
+Clone this repo:
+```bash
+ git clone git@github.com:p1onerka/rusty_ache.git
+```
+Open the project:
+```bash
+ cd rusty_ache
+```
+Run the project:
+```bash
+ cargo run --bin main
+```
+
+## Demo
+
+
+
+## Docs
+Project documentation is availible [here](https://p1onerka.github.io/rusty_ache/rusty_ache/index.html).
+
+## Performance
+
+Below is a graph showing the dependence of FPS on the number of simultaneously rendered objects, created under the following conditions:
+
+- Setup: MacBook M1, 8Gb RAM
+- Data: 113x113px object
+- Resolution: 200x200px
+
+
+
+As shown on the graph, the engine’s performance is above 20 FPS with <=40 objects. It is strongly discouraged to add more than this number of objects with a size similar to 113x113px on a range smaller than resolution parameters.
+
+## Devs
+- [Aleksei Dmitrievstev](https://github.com/admitrievtsev)
+- [Ksenia Kotelnikova](https://github.com/p1onerka)
+- [Sofya Kozyreva](https://github.com/sofyak0zyreva)
+- [Kostya Oreshin](https://github.com/sevenbunu)
\ No newline at end of file
diff --git a/resources/demo_gif.gif b/resources/demo_gif.gif
new file mode 100644
index 0000000..774514d
Binary files /dev/null and b/resources/demo_gif.gif differ
diff --git a/resources/demo_gif_small.gif b/resources/demo_gif_small.gif
new file mode 100644
index 0000000..8040391
Binary files /dev/null and b/resources/demo_gif_small.gif differ
diff --git a/resources/perf_diag.png b/resources/perf_diag.png
new file mode 100644
index 0000000..53e366b
Binary files /dev/null and b/resources/perf_diag.png differ
diff --git a/src/bin/main.rs b/src/bin/main.rs
index 2983e4e..0643c85 100644
--- a/src/bin/main.rs
+++ b/src/bin/main.rs
@@ -1,114 +1,34 @@
-use image::ImageReader;
-
-use rusty_ache::Resolution;
-use rusty_ache::engine::config::{Config, EngineConfig};
-use rusty_ache::engine::scene::Scene;
+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::components::sprite::Sprite;
use rusty_ache::engine::scene::game_object::position::Position;
-use rusty_ache::engine::scene::game_object::{GameObject, Object};
-use rusty_ache::engine::{Engine, GameEngine};
+use rusty_ache::interface::{create_obj_with_img, init_engine, init_scene};
use rusty_ache::screen::{HEIGHT, WIDTH};
fn main() {
- let mut engine = GameEngine::new(
- Box::new(EngineConfig::new(Resolution::new(WIDTH, HEIGHT))),
- Scene::new(
- vec![
- GameObject::new(
- vec![Box::new(Sprite::new(
- Some(
- ImageReader::open("src/bin/resources/command_center.png")
- .unwrap()
- .decode()
- .unwrap(),
- ),
- None,
- (0, 0),
- ))],
- None,
- Position {
- x: 0,
- y: 0,
- z: 1,
- is_relative: false,
- },
- ),
- GameObject::new(
- vec![Box::new(Sprite::new(
- Some(
- ImageReader::open("src/bin/resources/command_center.png")
- .unwrap()
- .decode()
- .unwrap(),
- ),
- Some((
- ImageReader::open("src/bin/resources/cc_shadow.png")
- .unwrap()
- .decode()
- .unwrap(),
- (-7, 4),
- )),
- (0, 0),
- ))],
- None,
- Position {
- x: 130,
- y: -100,
- z: 2,
- is_relative: false,
- },
- ),
- GameObject::new(
- vec![Box::new(Sprite::new(
- Some(
- ImageReader::open("src/bin/resources/command_center.png")
- .unwrap()
- .decode()
- .unwrap(),
- ),
- Some((
- ImageReader::open("src/bin/resources/cc_shadow.png")
- .unwrap()
- .decode()
- .unwrap(),
- (-7, 4),
- )),
- (0, 0),
- ))],
- None,
- Position {
- x: 15,
- y: -25,
- z: 3,
- is_relative: false,
- },
- ),
- ],
- vec![Box::new(Sprite::new(
- Some(
- ImageReader::open("src/bin/resources/battlecruiser_main.png")
- .unwrap()
- .decode()
- .unwrap(),
- ),
- Some((
- ImageReader::open("src/bin/resources/bc_shadow.png")
- .unwrap()
- .decode()
- .unwrap(),
- (0, -10),
- )),
- (60, -60),
- ))],
- Position {
- x: -10,
- y: 10,
- z: 0,
- is_relative: false,
- },
- ),
+ let tower_obj = create_obj_with_img("src/bin/resources/tower.png", 82, 37, true);
+ let junk_house_obj = create_obj_with_img("src/bin/resources/junk_house.png", 150, -150, true);
+ let pool_house_obj = create_obj_with_img("src/bin/resources/pool_house.png", 15, -25, true);
+ let tall_house_obj = create_obj_with_img("src/bin/resources/tall_house.png", 210, -80, true);
+ let skyscraper_obj = create_obj_with_img("src/bin/resources/skyscraper.png", 150, 55, true);
+ let cabin_obj = create_obj_with_img("src/bin/resources/cabin.png", 280, -60, true);
+ let main_ship_obj = create_obj_with_img("src/bin/resources/white_ship.png", 0, 0, true);
+
+ let hermit_house_obj = create_obj_with_img("src/bin/resources/junk_house.png", 400, 240, true);
+
+ let scene = init_scene(
+ &[
+ cabin_obj,
+ skyscraper_obj,
+ hermit_house_obj,
+ tower_obj,
+ tall_house_obj,
+ junk_house_obj,
+ pool_house_obj,
+ ],
+ main_ship_obj,
);
+ let mut engine = init_engine(scene, WIDTH, HEIGHT);
engine.render().unwrap();
engine.run().unwrap()
}
diff --git a/src/bin/resources/battlecruiser_main.png b/src/bin/resources/battlecruiser_main.png
deleted file mode 100644
index 28e28e5..0000000
Binary files a/src/bin/resources/battlecruiser_main.png and /dev/null differ
diff --git a/src/bin/resources/bc_shadow.png b/src/bin/resources/bc_shadow.png
deleted file mode 100644
index a074c7f..0000000
Binary files a/src/bin/resources/bc_shadow.png and /dev/null differ
diff --git a/src/bin/resources/block_building.png b/src/bin/resources/block_building.png
new file mode 100644
index 0000000..de96eeb
Binary files /dev/null and b/src/bin/resources/block_building.png differ
diff --git a/src/bin/resources/cabin.png b/src/bin/resources/cabin.png
new file mode 100644
index 0000000..0a95cd0
Binary files /dev/null and b/src/bin/resources/cabin.png differ
diff --git a/src/bin/resources/cc_shadow.png b/src/bin/resources/cc_shadow.png
deleted file mode 100644
index dd67b70..0000000
Binary files a/src/bin/resources/cc_shadow.png and /dev/null differ
diff --git a/src/bin/resources/command_center.png b/src/bin/resources/command_center.png
deleted file mode 100644
index 21e4421..0000000
Binary files a/src/bin/resources/command_center.png and /dev/null differ
diff --git a/src/bin/resources/junk_house.png b/src/bin/resources/junk_house.png
new file mode 100644
index 0000000..c319d77
Binary files /dev/null and b/src/bin/resources/junk_house.png differ
diff --git a/src/bin/resources/pool_house.png b/src/bin/resources/pool_house.png
new file mode 100644
index 0000000..590fa6a
Binary files /dev/null and b/src/bin/resources/pool_house.png differ
diff --git a/src/bin/resources/power_line.png b/src/bin/resources/power_line.png
new file mode 100644
index 0000000..ae78279
Binary files /dev/null and b/src/bin/resources/power_line.png differ
diff --git a/src/bin/resources/skyscraper.png b/src/bin/resources/skyscraper.png
new file mode 100644
index 0000000..2a2ec8a
Binary files /dev/null and b/src/bin/resources/skyscraper.png differ
diff --git a/src/bin/resources/sources.md b/src/bin/resources/sources.md
new file mode 100644
index 0000000..964bc36
--- /dev/null
+++ b/src/bin/resources/sources.md
@@ -0,0 +1,6 @@
+## Asset sources
+All of the assets from demo are availible for free usage, you can check them out here:
+
+- [ship](https://starlight-furnace.itch.io/starlight-menagerie)
+- [buildings](https://morithedaichi.itch.io/future-assets-free)
+- [tiles](https://starlight-furnace.itch.io/isometric-map-pack-archology-south)
\ No newline at end of file
diff --git a/src/bin/resources/tall_house.png b/src/bin/resources/tall_house.png
new file mode 100644
index 0000000..864c6ca
Binary files /dev/null and b/src/bin/resources/tall_house.png differ
diff --git a/src/bin/resources/tile1.png b/src/bin/resources/tile1.png
new file mode 100644
index 0000000..ddaa2c6
Binary files /dev/null and b/src/bin/resources/tile1.png differ
diff --git a/src/bin/resources/tile2.png b/src/bin/resources/tile2.png
new file mode 100644
index 0000000..d2588f8
Binary files /dev/null and b/src/bin/resources/tile2.png differ
diff --git a/src/bin/resources/tile3.png b/src/bin/resources/tile3.png
new file mode 100644
index 0000000..8607c48
Binary files /dev/null and b/src/bin/resources/tile3.png differ
diff --git a/src/bin/resources/tower.png b/src/bin/resources/tower.png
new file mode 100644
index 0000000..21a9039
Binary files /dev/null and b/src/bin/resources/tower.png differ
diff --git a/src/bin/resources/white_ship.png b/src/bin/resources/white_ship.png
new file mode 100644
index 0000000..1d39b4e
Binary files /dev/null and b/src/bin/resources/white_ship.png differ
diff --git a/src/engine/mod.rs b/src/engine/mod.rs
index aa81121..37c318e 100644
--- a/src/engine/mod.rs
+++ b/src/engine/mod.rs
@@ -11,12 +11,13 @@ use crate::engine::scene_manager::SceneManager;
use crate::render::renderer::DEFAULT_BACKGROUND_COLOR;
use crate::render::renderer::Renderer;
use crate::screen::{App, HEIGHT, WIDTH};
+//use image::ImageReader;
use std::io::Error;
+use std::sync::atomic::Ordering;
use std::sync::{Arc, RwLock};
use std::thread;
use std::time::Duration;
use winit::event_loop::{ControlFlow, EventLoop};
-use winit::keyboard::KeyCode;
use winit::window::Window;
/// A trait for describing entity for main engine logic
@@ -57,6 +58,10 @@ impl Engine for GameEngine {
//config,
render: Arc::new(RwLock::from(Renderer::new(
res,
+ /*Some(ImageReader::open("src/bin/resources/tile2.png")
+ .unwrap()
+ .decode()
+ .unwrap())*/
None,
SceneManager::new(scene),
))),
@@ -80,7 +85,8 @@ impl Engine for GameEngine {
let shared_window_clone = shared_window.clone();
let mut app = App::new(shared_pixel_data, shared_window);
- let key_pressed_clone = app.key_pressed.clone();
+ //let key_pressed_clone = app.key_pressed.clone();
+ let keys_pressed_clone = app.keys_pressed.clone();
let renderer = self.render.clone();
// Producer thread
thread::spawn(move || {
@@ -95,13 +101,19 @@ impl Engine for GameEngine {
let screen_size = (WIDTH * HEIGHT) as usize;
loop {
- let vector_move = match *key_pressed_clone.read().unwrap() {
+ /*let vector_move = match *key_pressed_clone.read().unwrap() {
Some(KeyCode::KeyW) => (0, 1),
Some(KeyCode::KeyA) => (-1, 0),
Some(KeyCode::KeyS) => (0, -1),
Some(KeyCode::KeyD) => (1, 0),
_ => (0, 0),
- };
+ };*/
+ let dx = (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)
+ - (keys_pressed_clone.s.load(Ordering::Relaxed) as i32);
+
+ let vector_move = (dx, dy);
//println!("{:?}", vector_move);
renderer
.write()
diff --git a/src/engine/scene/game_object/components/mod.rs b/src/engine/scene/game_object/components/mod.rs
index 69a6e85..e86b536 100644
--- a/src/engine/scene/game_object/components/mod.rs
+++ b/src/engine/scene/game_object/components/mod.rs
@@ -30,8 +30,8 @@ pub trait Component: Any {
fn get_sprite_unchecked(&self) -> &Option {
&None
}
- fn get_shadow_unchecked(&self) -> &Option<(DynamicImage, (i32, i32))> {
- &None
+ fn get_shadow_unchecked(&self) -> bool {
+ true
}
fn get_sprite_offset_unchecked(&self) -> Option<(i32, i32)> {
None
diff --git a/src/engine/scene/game_object/components/sprite.rs b/src/engine/scene/game_object/components/sprite.rs
index 07fa0fb..bebd2b2 100644
--- a/src/engine/scene/game_object/components/sprite.rs
+++ b/src/engine/scene/game_object/components/sprite.rs
@@ -5,16 +5,12 @@ use image::DynamicImage;
pub struct Sprite {
pub image: Option,
- pub shadow: Option<(DynamicImage, (i32, i32))>,
+ pub shadow: bool,
pub offset: (i32, i32),
}
impl Sprite {
- pub fn new(
- image: Option,
- shadow: Option<(DynamicImage, (i32, i32))>,
- offset: (i32, i32),
- ) -> Self {
+ pub fn new(image: Option, shadow: bool, offset: (i32, i32)) -> Self {
Sprite {
image,
shadow,
@@ -35,9 +31,10 @@ impl Component for Sprite {
&self.image
}
- fn get_shadow_unchecked(&self) -> &Option<(DynamicImage, (i32, i32))> {
- &self.shadow
+ fn get_shadow_unchecked(&self) -> bool {
+ self.shadow
}
+
fn get_sprite_offset_unchecked(&self) -> Option<(i32, i32)> {
Some(self.offset)
}
@@ -57,14 +54,14 @@ mod tests {
#[test]
fn test_sprite_without_image() {
- let sprite = Sprite::new(None, None, (0, 0));
+ let sprite = Sprite::new(None, false, (0, 0));
assert!(sprite.image.is_none());
}
#[test]
fn test_new_sprite_with_image() {
let image = create_test_image(100, 100);
- let sprite = Sprite::new(Some(image), None, (0, 0));
+ let sprite = Sprite::new(Some(image), false, (0, 0));
assert!(sprite.image.is_some());
}
@@ -72,7 +69,7 @@ mod tests {
#[test]
fn test_sprite_correct_dimensions() {
let image = create_test_image(200, 150);
- let sprite = Sprite::new(Some(image), None, (0, 0));
+ let sprite = Sprite::new(Some(image), false, (0, 0));
assert!(sprite.image.is_some());
if let Some(ref img) = sprite.image {
@@ -89,13 +86,13 @@ mod tests {
#[test]
fn test_get_component_type_returns_sprite() {
- let sprite = Sprite::new(None, None, (0, 0));
+ let sprite = Sprite::new(None, false, (0, 0));
assert_eq!(sprite.get_component_type(), ComponentType::Sprite);
}
#[test]
fn test_as_any_returns_correct_type() {
- let sprite = Sprite::new(None, None, (0, 0));
+ let sprite = Sprite::new(None, false, (0, 0));
let any = sprite.as_any();
assert!(any.is::());
@@ -104,7 +101,7 @@ mod tests {
#[test]
fn test_as_any_downcasting() {
- let sprite = Sprite::new(None, None, (0, 0));
+ let sprite = Sprite::new(None, false, (0, 0));
let any = sprite.as_any();
let downcasted = any.downcast_ref::();
@@ -118,7 +115,7 @@ mod tests {
#[test]
fn test_get_sprite_unchecked_returns_image() {
let image = create_test_image(50, 50);
- let sprite = Sprite::new(Some(image), None, (0, 0));
+ let sprite = Sprite::new(Some(image), false, (0, 0));
let result = sprite.get_sprite_unchecked();
assert!(result.is_some());
@@ -126,7 +123,7 @@ mod tests {
#[test]
fn test_get_sprite_unchecked_without_image() {
- let sprite = Sprite::new(None, None, (0, 0));
+ let sprite = Sprite::new(None, false, (0, 0));
let result = sprite.get_sprite_unchecked();
assert!(result.is_none());
}
diff --git a/src/engine/scene/game_object/mod.rs b/src/engine/scene/game_object/mod.rs
index 442dfe7..379329a 100644
--- a/src/engine/scene/game_object/mod.rs
+++ b/src/engine/scene/game_object/mod.rs
@@ -30,9 +30,9 @@ pub trait Object {
fn update_position(&mut self, position: Position) -> Result<(), GameObjectError>;
- fn add_position(&mut self, vec: (i32, i32)) -> ();
+ fn add_position(&mut self, vec: (i32, i32));
- fn run_action(&self) -> ();
+ fn run_action(&self);
}
pub struct GameObject {
@@ -111,7 +111,7 @@ mod tests {
is_relative: false,
};
let components: Vec> =
- vec![Box::new(Sprite::new(None, None, (0, 0)))];
+ vec![Box::new(Sprite::new(None, false, (0, 0)))];
GameObject::new(components, None, position)
}
@@ -124,7 +124,7 @@ mod tests {
is_relative: true,
};
let components: Vec> =
- vec![Box::new(Sprite::new(None, None, (0, 0)))];
+ vec![Box::new(Sprite::new(None, false, (0, 0)))];
let game_object = GameObject::new(components, None, position);
@@ -139,7 +139,7 @@ mod tests {
let mut game_object = create_test_game_object();
let initial_count = game_object.components.len();
- let new_component = Box::new(Sprite::new(None, None, (0, 0)));
+ let new_component = Box::new(Sprite::new(None, false, (0, 0)));
let result = game_object.add_component(new_component);
assert!(result.is_ok());
diff --git a/src/engine/scene/mod.rs b/src/engine/scene/mod.rs
index 5586bb8..6e9d41f 100644
--- a/src/engine/scene/mod.rs
+++ b/src/engine/scene/mod.rs
@@ -29,12 +29,12 @@ impl Scene {
}
}
- pub fn init(&self) -> Vec<(&GameObject, &DynamicImage, (i32, i32))> {
- let mut renderable_objects: Vec<(&GameObject, &DynamicImage, (i32, i32))> = vec![];
+ 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() {
for component in obj.components.iter() {
if component.get_component_type() == ComponentType::Sprite {
- match &component.get_shadow_unchecked() {
+ /*match &component.get_shadow_unchecked() {
None => {}
Some(img) => {
renderable_objects.push((
@@ -46,11 +46,12 @@ impl Scene {
),
));
}
- };
+ };*/
renderable_objects.push((
obj,
component.get_sprite_unchecked().as_ref().unwrap(),
component.get_sprite_offset_unchecked().unwrap(),
+ component.get_shadow_unchecked(),
));
}
}
@@ -59,7 +60,7 @@ impl Scene {
for component in self.main_object.components.iter() {
if component.get_component_type() == ComponentType::Sprite {
- if let Some(img) = component.get_shadow_unchecked() {
+ /*if let Some(img) = component.get_shadow_unchecked() {
renderable_objects.push((
&self.main_object,
&img.0,
@@ -68,13 +69,14 @@ impl Scene {
component.get_sprite_offset_unchecked().unwrap_or((0, 0)).1 + img.1.1,
),
));
- }
+ }*/
if let Some(sprite_img) = component.get_sprite_unchecked().as_ref() {
renderable_objects.push((
&self.main_object,
sprite_img,
component.get_sprite_offset_unchecked().unwrap_or((0, 0)),
+ component.get_shadow_unchecked(),
));
}
}
diff --git a/src/engine/scene/object_manager.rs b/src/engine/scene/object_manager.rs
index 906644e..7c25b7b 100644
--- a/src/engine/scene/object_manager.rs
+++ b/src/engine/scene/object_manager.rs
@@ -53,7 +53,7 @@ mod factory_tests {
}
fn create_test_components() -> Vec> {
- vec![Box::new(Sprite::new(None, None, (0, 0)))]
+ vec![Box::new(Sprite::new(None, false, (0, 0)))]
}
#[test]
@@ -102,8 +102,8 @@ mod factory_tests {
fn test_create_object_returns_game_object_with_components() {
let mut factory = GameObjectFactory::new(10);
let components: Vec> = vec![
- Box::new(Sprite::new(None, None, (0, 0))) as Box,
- Box::new(Sprite::new(None, None, (0, 0))) as Box,
+ Box::new(Sprite::new(None, false, (0, 0))) as Box,
+ Box::new(Sprite::new(None, false, (0, 0))) as Box,
];
let (_uid, obj) = factory.create_object(components, create_test_position(0, 0, 0, false));
@@ -283,7 +283,7 @@ mod manager_tests {
}
fn create_test_components() -> Vec> {
- vec![Box::new(Sprite::new(None, None, (0, 0)))]
+ vec![Box::new(Sprite::new(None, false, (0, 0)))]
}
#[test]
diff --git a/src/engine/scene_manager.rs b/src/engine/scene_manager.rs
index 7202ff4..9286cf0 100644
--- a/src/engine/scene_manager.rs
+++ b/src/engine/scene_manager.rs
@@ -17,7 +17,7 @@ impl SceneManager {
&self.active_scene
}
- pub fn init_active_scene(&self) -> Vec<(&GameObject, &DynamicImage, (i32, i32))> {
+ pub fn init_active_scene(&self) -> Vec<(&GameObject, &DynamicImage, (i32, i32), bool)> {
self.active_scene.init()
}
}
@@ -41,7 +41,7 @@ mod tests {
}
fn create_test_components() -> Vec> {
- vec![Box::new(Sprite::new(None, None, (0, 0)))]
+ vec![Box::new(Sprite::new(None, false, (0, 0)))]
}
fn create_simple_scene() -> Scene {
@@ -52,7 +52,7 @@ mod tests {
)
}
- fn create_scene_with_sprites(sprite_count: usize) -> Scene {
+ fn _create_scene_with_sprites(sprite_count: usize) -> Scene {
let mut objects = vec![];
for i in 0..sprite_count {
diff --git a/src/interface/mod.rs b/src/interface/mod.rs
new file mode 100644
index 0000000..c052f30
--- /dev/null
+++ b/src/interface/mod.rs
@@ -0,0 +1,87 @@
+use image::ImageReader;
+
+use crate::{
+ Resolution,
+ engine::{
+ Engine, GameEngine,
+ config::{Config, EngineConfig},
+ scene::{
+ Scene,
+ game_object::{GameObject, Object, Position, components::sprite::Sprite},
+ },
+ },
+};
+
+#[derive(Clone)]
+pub struct ObjectWithImage<'a> {
+ image_path: &'a str,
+ x: i32,
+ y: i32,
+ has_shadow: bool,
+}
+
+/// A function for initializing vector of GameObjects with their ObjectWithImage interface.
+/// Objects should be arranged in a vector from farthest from the viewer to closest
+pub fn create_gameobj_vec(objs: &[ObjectWithImage]) -> Vec {
+ let mut res = Vec::new();
+ let mut z_coord = 1;
+ for obj in objs {
+ res.push(GameObject::new(
+ vec![Box::new(Sprite::new(
+ Some(ImageReader::open(obj.image_path).unwrap().decode().unwrap()),
+ obj.has_shadow,
+ (0, 0),
+ ))],
+ None,
+ Position {
+ x: obj.x,
+ y: obj.y,
+ z: z_coord,
+ is_relative: false,
+ },
+ ));
+ z_coord += 1;
+ }
+ res
+}
+
+/// A function for initializing ObjectWithImage which is a simplified interface of GameObject
+pub fn create_obj_with_img(image_path: &str, x: i32, y: i32, has_shadow: bool) -> ObjectWithImage {
+ ObjectWithImage {
+ image_path,
+ x,
+ y,
+ has_shadow,
+ }
+}
+
+/// A function for initializing the Scene object based on ObjectWithImage vector and main object
+pub fn init_scene(objs: &[ObjectWithImage], main_obj: ObjectWithImage) -> Scene {
+ let game_objs = create_gameobj_vec(objs);
+ Scene::new(
+ game_objs,
+ vec![Box::new(Sprite::new(
+ Some(
+ ImageReader::open(main_obj.image_path)
+ .unwrap()
+ .decode()
+ .unwrap(),
+ ),
+ true,
+ (60, -60),
+ ))],
+ Position {
+ x: main_obj.x,
+ y: main_obj.y,
+ z: 0,
+ is_relative: false,
+ },
+ )
+}
+
+pub fn init_engine(scene: Scene, width: u32, height: u32) -> GameEngine {
+ GameEngine::new(
+ Box::new(EngineConfig::new(Resolution::new(width, height))),
+ scene,
+ )
+}
diff --git a/src/lib.rs b/src/lib.rs
index f717060..6374126 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,4 +1,5 @@
pub mod engine;
+pub mod interface;
pub mod render;
pub mod screen;
diff --git a/src/render/renderer.rs b/src/render/renderer.rs
index 4e1ee8a..56cfbdf 100644
--- a/src/render/renderer.rs
+++ b/src/render/renderer.rs
@@ -10,7 +10,9 @@ use crate::screen::{HEIGHT, WIDTH};
use super::utils::make_init_frame;
-pub const DEFAULT_BACKGROUND_COLOR: (u8, u8, u8, u8) = (245, 245, 220, 255);
+pub const DEFAULT_BACKGROUND_COLOR: (u8, u8, u8, u8) = (98, 96, 96, 255);
+pub const OFFSET: (i32, i32) = (10, -10);
+pub const SHADOW_OPAQUENESS: u8 = 80;
pub struct Renderable {
pub uid: u32,
@@ -28,28 +30,24 @@ pub struct Rectangle {
/// * Choosing which pixels to recolor based on info from Engine.
/// * Forming recolored frame and sending it to Screen.
pub struct Renderer {
- //frame_ready: bool,
resolution: Resolution,
background: Option,
prev_frame: Vec<(u8, u8, u8, u8)>,
- //renderable: Vec,
pub scene_manager: SceneManager,
}
impl Renderer {
- // TODO: add here first edition of image into Screen. it will contain only slice of background
pub(crate) fn new(
resolution: Resolution,
background: Option,
scene_manager: SceneManager,
) -> Self {
let background_clone = background.clone();
+ let init_frame = make_init_frame(background_clone);
Renderer {
- //frame_ready: false,
resolution,
background,
- prev_frame: make_init_frame(background_clone),
- //renderable: Vec::new(),
+ prev_frame: init_frame.clone(),
scene_manager,
}
}
@@ -78,6 +76,7 @@ impl Renderer {
position: (i32, i32),
camera_top: (i32, i32),
frame_size: (i32, i32),
+ has_shadow: bool,
) {
let (frame_w, frame_h) = frame_size;
@@ -105,6 +104,29 @@ impl Renderer {
continue;
}
+ if has_shadow {
+ let sx_i_shadow = wx + OFFSET.0 - camera_top.0;
+ let sy_i_shadow = camera_top.1 - wy + OFFSET.1;
+ if sx_i_shadow < 0 || sy_i_shadow < 0 {
+ continue;
+ }
+ let sx_shadow = sx_i_shadow as u32;
+ let sy_shadow = sy_i_shadow as u32;
+ if sx_shadow >= frame_w as u32 || sy_shadow >= frame_h as u32 {
+ continue;
+ }
+ let idx = (sy_shadow * frame_w as u32 + sx_shadow) as usize;
+ let existing = frame[idx];
+ let alpha = SHADOW_OPAQUENESS as f32 / 255.0;
+ let blended = (
+ (existing.0 as f32 * (1.0 - alpha)) as u8,
+ (existing.1 as f32 * (1.0 - alpha)) as u8,
+ (existing.2 as f32 * (1.0 - alpha)) as u8,
+ 255,
+ );
+ frame[idx] = blended;
+ }
+
// map world -> screen coordinates
let sx_i = wx - camera_top.0;
let sy_i = camera_top.1 - wy;
@@ -119,7 +141,7 @@ impl Renderer {
continue;
}
- // fully opaque → just overwrite
+ // fully opaque => just overwrite
let idx = (sy * frame_w as u32 + sx) as usize;
let mut shadowed = src;
if src[0] == 0 && src[1] == 0 && src[2] == 0 && src[3] != 255 {
@@ -135,14 +157,12 @@ impl Renderer {
/// Form new frame based on previous one and info from Engine
pub(crate) fn render(&mut self) {
- //println!("Starting render");
// find cam rectangle
let main_object = &self.scene_manager.active_scene.main_object;
let mut frame: Vec<(u8, u8, u8, u8)> = make_init_frame(self.background.clone());
//println!("Main object collected");
let renderable = self.scene_manager.init_active_scene();
- //println!("Renderable collected");
let _camera_rect = Rectangle {
top_left: (main_object.position.x, main_object.position.y),
bot_right: (
@@ -150,11 +170,9 @@ impl Renderer {
main_object.position.y - HEIGHT as i32,
),
};
- //println!("{} {}", main_object.position.y, HEIGHT);
- // TODO: what happens when two objects have the same z?
let _uids_by_z = HashMap::::new();
- for (obj, img, offset) 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,
@@ -168,10 +186,7 @@ impl Renderer {
top_left: (pos.x, pos.y),
bot_right: im_bot_right,
};
- //println!(
- //"{} {} {} {}",
- //im_rect.bot_right.0, im_rect.bot_right.1, im_rect.top_left.0, im_rect.top_left.1
- //);
+
Self::blit_sprite(
&mut frame,
img,
@@ -179,6 +194,7 @@ impl Renderer {
(pos.x, pos.y),
(main_object.position.x, main_object.position.y),
(self.resolution.width as i32, self.resolution.height as i32),
+ has_shadow,
);
self.prev_frame = frame.clone();
}
diff --git a/src/screen/mod.rs b/src/screen/mod.rs
index 1879bcb..2a30396 100644
--- a/src/screen/mod.rs
+++ b/src/screen/mod.rs
@@ -1,3 +1,4 @@
+use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, RwLock};
use std::thread;
use std::time::{Duration, Instant};
@@ -45,6 +46,13 @@ impl Screen<'_> {
type PixelData = Vec<(u8, u8, u8, u8)>;
+pub struct Keys {
+ pub w: AtomicBool,
+ pub a: AtomicBool,
+ pub s: AtomicBool,
+ pub d: AtomicBool,
+}
+
/// GUI for game
pub struct App {
/// Main window object
@@ -54,11 +62,10 @@ pub struct App {
/// Pixel colors provided by Renderer
pixel_data: Arc>,
/// currently pressed key for Engine
- pub(crate) key_pressed: Arc>>,
+ //pub(crate) key_pressed: Arc>>,
+ pub(crate) keys_pressed: Arc,
- /// Currrent FPS accumulator (probably delete later)
frame_count: u32,
- /// For FPS compitation (probably delete later)
last_fps_report_time: Instant,
}
@@ -72,7 +79,13 @@ impl App {
screen: None,
pixel_data,
window,
- key_pressed: Arc::new(RwLock::new(None)),
+ //key_pressed: Arc::new(RwLock::new(None)),
+ keys_pressed: Arc::new(Keys {
+ w: AtomicBool::new(false),
+ a: AtomicBool::new(false),
+ s: AtomicBool::new(false),
+ d: AtomicBool::new(false),
+ }),
frame_count: 0,
last_fps_report_time: Instant::now(),
@@ -138,11 +151,6 @@ impl ApplicationHandler for App {
self.frame_count += 1;
let elapsed = self.last_fps_report_time.elapsed();
if elapsed >= Duration::from_secs(1) {
- //let fps = self.frame_count as f64 / elapsed.as_secs_f64();
- //if let Some(window_arc) = self.window.read().unwrap().as_ref() {
- // window_arc.set_title(&format!("FPS: {:.2}", fps));
- //}
- //dbg!(fps);
self.frame_count = 0;
self.last_fps_report_time = Instant::now();
}
@@ -157,11 +165,14 @@ impl ApplicationHandler for App {
},
..
} => {
- let mut key = self.key_pressed.write().unwrap();
- if state.is_pressed() {
- *key = Some(key_code);
- } else {
- *key = None;
+ let pressed = state.is_pressed();
+
+ match key_code {
+ KeyCode::KeyW => self.keys_pressed.w.store(pressed, Ordering::Relaxed),
+ KeyCode::KeyA => self.keys_pressed.a.store(pressed, Ordering::Relaxed),
+ KeyCode::KeyS => self.keys_pressed.s.store(pressed, Ordering::Relaxed),
+ KeyCode::KeyD => self.keys_pressed.d.store(pressed, Ordering::Relaxed),
+ _ => {}
}
}
_ => (),
@@ -231,63 +242,3 @@ pub fn example() {
let mut app = App::new(shared_pixel_data, shared_window);
let _ = event_loop.run_app(&mut app);
}
-
-pub fn example_keys() {
- let initial_resolution = Resolution {
- width: WIDTH,
- height: HEIGHT,
- };
- const DEFAULT_COLOR: (u8, u8, u8, u8) = (0xF5, 0xDE, 0xB3, 0xFF);
- const RED: (u8, u8, u8, u8) = (0xFF, 0x00, 0x00, 0xFF);
- const BLUE: (u8, u8, u8, u8) = (0x00, 0x00, 0xFF, 0xFF);
- const GREEN: (u8, u8, u8, u8) = (0x00, 0xFF, 0x00, 0xFF);
- const PURPLE: (u8, u8, u8, u8) = (0x80, 0x00, 0x80, 0xFF);
- let initial_pixels =
- vec![DEFAULT_COLOR; (initial_resolution.width * initial_resolution.height) as usize];
-
- let shared_pixel_data = Arc::new(RwLock::new(initial_pixels));
- let shared_window = Arc::new(RwLock::new(None));
-
- let shared_pixel_data_clone = shared_pixel_data.clone();
- let shared_window_clone = shared_window.clone();
-
- let mut app = App::new(shared_pixel_data, shared_window);
- let key_pressed_clone = app.key_pressed.clone();
- // Producer thread
- thread::spawn(move || {
- let window_arc: Arc = loop {
- if let Some(arc) = shared_window_clone.read().unwrap().clone() {
- break arc;
- }
- thread::sleep(Duration::from_millis(100));
- };
-
- //dbg!("Producer has started");
-
- let screen_size = (WIDTH * HEIGHT) as usize;
- loop {
- let color = match *key_pressed_clone.read().unwrap() {
- Some(KeyCode::KeyW) => RED,
- Some(KeyCode::KeyA) => BLUE,
- Some(KeyCode::KeyS) => GREEN,
- Some(KeyCode::KeyD) => PURPLE,
- _ => DEFAULT_COLOR,
- };
- {
- let mut pixels = shared_pixel_data_clone
- .write()
- .expect("Producer couldn't lock pixel data");
-
- for p in pixels.iter_mut().take(screen_size) {
- *p = color;
- }
- }
- window_arc.request_redraw();
- }
- });
-
- let event_loop = EventLoop::new().unwrap();
- event_loop.set_control_flow(ControlFlow::Wait);
-
- let _ = event_loop.run_app(&mut app);
-}