Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 3 additions & 18 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,18 +1,3 @@
[package]
name = "ferari"
version = "0.1.0"
edition = "2021"
description = "Fast Engine for Rendering Axonometric Rust-based Isometry"
authors = ["Rodion Suvorov <rodion.suvorov.94@mail.ru>, Ilhom Kombaev <kombaev02@gmail.com>, Vyacheslav Kochergin <vyacheslav.kochergin1@gmail.com>, Dmitri Kuznetsov <dmitrvlkuznetsov@gmail.com>"]
license = "MIT"
repository = "https://github.com/suvorovrain/Ferari"
readme = "README.md"

[dependencies]
minifb = "0.28"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
image = "0.25"
crossbeam-channel = "0.5.15"

[dev-dependencies]
[workspace]
members = ["src", "demo"]
resolver = "2"
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ cargo install cargo-tarpaulin
```
## Development
* See [CONTRIBUTING.md](./CONTRIBUTING.md)
* Compile & run via `cargo run`
* Compile & run demo via `cargo run -p demo`
* View docs via `cargo doc` (use --document-private-items if you want)
* Format your code via `cargo fmt`
* Everything else - in CI
Expand Down
8 changes: 8 additions & 0 deletions demo/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "demo"
version = "1.0.2"
edition = "2021"

[dependencies]
ferari = { path = "../src" }
crossbeam-channel = "0.5.15"
9 changes: 7 additions & 2 deletions src/world/behaviour.rs → demo/src/behaviour.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::input::InputSnapshot;

use super::State;
use ferari::world::State;

/// Calculates the absolute value (length) of a 2D vector.
///
Expand Down Expand Up @@ -81,6 +81,7 @@ pub fn make_step(curr_state: &mut State, input_state: &InputSnapshot) {
mod tests {
use super::State;
use crate::assets::GameMap;
use std::path::PathBuf;

use super::*;

Expand Down Expand Up @@ -108,7 +109,11 @@ mod tests {
}

fn make_test_state() -> State {
let game_map = GameMap::load("input.json").expect("failed to load game map for tests");
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let project_root = manifest_dir.join("..");

let map_path = project_root.join("examples/input.json");
let game_map = GameMap::load(map_path).expect("failed to load game map for tests");

let mut state = State::new(&game_map);

Expand Down
2 changes: 1 addition & 1 deletion src/world/initiator.rs → demo/src/initiator.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::world::{Camera, Unit};

use super::State;
use ferari::world::State;

/// Returns a list of game objects that are currently visible within the camera's view.
///
Expand Down
80 changes: 66 additions & 14 deletions src/main.rs → demo/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::Duration;

use crossbeam_channel::bounded;

use crate::world::{get_visible_objects, make_step};
use crate::behaviour::make_step;
use crate::initiator::get_visible_objects;

mod assets;
mod draw;
mod input;
mod render;
mod time;
mod world;
use ferari::assets;
use ferari::draw;
use ferari::input;
use ferari::render;
use ferari::time;
use ferari::world;
mod behaviour;
mod initiator;

use ferari::render::RenderableEntity;

/// Logical screen width in pixels.
const LOGIC_WIDTH: usize = 200;
Expand All @@ -24,12 +30,23 @@ const TILE_SIZE: usize = 16;
const UPSCALE: usize = 5;

fn main() {
// Need to find root directory
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let project_root = manifest_dir.join("..");

let assets_path = project_root.join("assets");

// parse atlases
let tiles_atlas = assets::Atlas::load("assets/tiles/atlas.json").unwrap();
let entity_atlas = assets::Atlas::load("assets/entities/atlas.json").unwrap();
let tiles_path = assets_path.join("tiles/atlas.json");
let entities_path = assets_path.join("entities/atlas.json");

let tiles_atlas = assets::Atlas::load(tiles_path.to_str().unwrap()).unwrap();
let entities_atlas = assets::Atlas::load(entities_path.to_str().unwrap()).unwrap();

// parse game descr
let game = assets::GameMap::load("examples/input.json").unwrap();
let game_path = project_root.join("examples/input.json");

let game = assets::GameMap::load(game_path).unwrap();

// init draw
let input_state = Arc::new(input::InputState::new());
Expand Down Expand Up @@ -63,7 +80,7 @@ fn main() {
// init render
let shadow_map: Vec<u8> = vec![0; world_width * world_height];
let mut render =
render::Render::new(world_buf, world_height, world_width, entity_atlas, shadow_map);
render::Render::new(world_buf, world_height, world_width, entities_atlas, shadow_map);

// init camera
let mut camera = world::Camera::new(
Expand All @@ -81,14 +98,35 @@ fn main() {

// prerender
render.init(&game, &tiles_atlas);
render.render_frame(&Vec::new(), &camera, &mut back_buffer, &time);

let all_units = {
let mut units = vec![state.player.clone()];
units.extend(state.mobs.clone());
units
};

let visible_entities: Vec<RenderableEntity> = all_units
.into_iter()
.enumerate()
.map(|(i, unit)| {
let name_model = if i == 0 { "knight_0" } else { "imp_20" };
let period = 0.4;
let cycles = (time.total / period).floor() as u32;
let animation_num = if cycles.is_multiple_of(2) { "_0" } else { "_1" };
let full_name = name_model.to_string() + animation_num;

RenderableEntity::new(unit.x, unit.y, full_name)
})
.collect();

render.render_frame(&visible_entities, &camera, &mut back_buffer);
state.player.x = camera.center_x;
state.player.y = camera.center_y;
// game loop
while running.load(Ordering::Acquire) {
time.update();

// test gradient
// test gradient (TODO: move to tests?)
// let r = ((time.total).sin() * 127.0 + 128.0) as u32;
// let g = ((time.total + 2.0).sin() * 127.0 + 128.0) as u32;
// let b = ((time.total + 4.0).sin() * 127.0 + 128.0) as u32;
Expand Down Expand Up @@ -116,7 +154,21 @@ fn main() {
}

// frame render
render.render_frame(&units_for_render, &camera, &mut back_buffer, &time);
let visible_entities: Vec<RenderableEntity> = units_for_render
.into_iter()
.enumerate()
.map(|(i, unit)| {
let name_model = if i == 0 { "knight_0" } else { "imp_20" };
let period = 0.4;
let cycles = (time.total / period).floor() as u32;
let animation_num = if cycles.is_multiple_of(2) { "_0" } else { "_1" };
let full_name = name_model.to_string() + animation_num;

RenderableEntity::new(unit.x, unit.y, full_name)
})
.collect();

render.render_frame(&visible_entities, &camera, &mut back_buffer);

// draw frame
if tx_frame.try_send(back_buffer.clone()).is_err() {
Expand Down
4 changes: 4 additions & 0 deletions demo/src/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
mod behaviour;
mod initiator;

pub use behaviour::make_step;
22 changes: 22 additions & 0 deletions src/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "ferari"
version = "1.0.2"
edition = "2021"
description = "Fast Engine for Rendering Axonometric Rust-based Isometry"
authors = ["Rodion Suvorov <rodion.suvorov.94@mail.ru>, Ilhom Kombaev <kombaev02@gmail.com>, Vyacheslav Kochergin <vyacheslav.kochergin1@gmail.com>, Dmitri Kuznetsov <dmitrvlkuznetsov@gmail.com>"]
license = "MIT"
repository = "https://github.com/suvorovrain/Ferari"
readme = "README.md"

[lib]
name = "ferari"
path = "lib.rs"

[dependencies]
minifb = "0.28"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
image = "0.25"
crossbeam-channel = "0.5.15"

[dev-dependencies]
6 changes: 5 additions & 1 deletion src/assets/atlas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,11 @@ mod tests {
// Test atlas JSON parsing on example
#[test]
fn test_load_entities_atlas() {
let atlas = Atlas::load("assets/tiles/atlas.json").unwrap();
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let project_root = manifest_dir.join("..");
let atlas_path = project_root.join("assets/tiles/atlas.json");

let atlas = Atlas::load(atlas_path).unwrap();

assert_eq!(atlas.tile_size, 16);
assert_eq!(atlas.version, 1);
Expand Down
9 changes: 8 additions & 1 deletion src/assets/gamemap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use std::fs::File;
use std::io::BufReader;
use std::path::Path;

// TODO: delete mobs from json!

// ============================
// JSON-level structs
// ============================
Expand Down Expand Up @@ -414,11 +416,16 @@ impl Tile {
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;

// Test game map parsing on example
#[test]
fn test_load_game_map() {
let game_map = GameMap::load("input.json").unwrap();
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let project_root = manifest_dir.join("..");
let map_path = project_root.join("examples/input.json");

let game_map = GameMap::load(map_path).unwrap();

assert_eq!(game_map.name, "demo_map");
assert_eq!(game_map.tile_size, 16);
Expand Down
24 changes: 24 additions & 0 deletions src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,17 @@ pub struct InputState {
pub escape: Arc<AtomicBool>,
}

impl Default for InputState {
/// Creates a new `InputState` with all keys initially not pressed.
///
/// # Returns
///
/// A new `InputState` instance with all values initialized to `false`.
fn default() -> Self {
Self::new()
}
}

impl InputState {
/// Creates a new `InputState` with all keys initially not pressed.
///
Expand Down Expand Up @@ -105,6 +116,19 @@ mod tests {
assert!(!snapshot.escape);
}

/// Test that default InputState initializes with all keys set to false
#[test]
fn test_default_input_state_initializes_all_false() {
let input_state = InputState::default();
let snapshot = input_state.read();

assert!(!snapshot.up);
assert!(!snapshot.down);
assert!(!snapshot.left);
assert!(!snapshot.right);
assert!(!snapshot.escape);
}

/// Test that InputState can be cloned and both instances share state
#[test]
fn test_input_state_clone_shares_state() {
Expand Down
8 changes: 8 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
pub mod assets;
pub mod draw;
pub mod input;
pub mod render;
pub mod time;
pub mod world;

pub use render::{Render, RenderableEntity};
10 changes: 0 additions & 10 deletions src/mod.rs

This file was deleted.

1 change: 1 addition & 0 deletions src/render/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#[allow(clippy::module_inception)]
mod render;
pub use render::Render;
pub use render::RenderableEntity;
Loading