diff --git a/.gitignore b/.gitignore index 905ca93..de86e7a 100644 --- a/.gitignore +++ b/.gitignore @@ -159,4 +159,5 @@ fabric.properties .vscode/launch.json .vscode/settings.json +.vscode/tasks.json *.blend[0-9] \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 4f6d917..9e46a01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ authors = [] [dependencies] amethyst = "0.10.0"#{version = "0.10.0" , features = ["nightly"] } +amethyst_derive = "0.3.0" log = "0.4.6" serde = "1.0.82" serde_derive = "1.0.82" diff --git a/assets/textures/ground_hover.png b/assets/textures/ground_hover.png new file mode 100644 index 0000000..eddc1c5 Binary files /dev/null and b/assets/textures/ground_hover.png differ diff --git a/assets/textures/rock_raider/default_hover.png b/assets/textures/rock_raider/default_hover.png new file mode 100644 index 0000000..1cee759 Binary files /dev/null and b/assets/textures/rock_raider/default_hover.png differ diff --git a/resources/color_palette.ron b/resources/color_palette.ron new file mode 100644 index 0000000..1f23d74 --- /dev/null +++ b/resources/color_palette.ron @@ -0,0 +1,6 @@ +{ + "RED": (1.0,0.0,0.0,1.0), + "GREEN": (0.0,1.0,0.0,1.0), + "BLUE": (0.0,0.0,1.0,1.0), + "BEIGE": (0.5, 0.4, 0.0, 1.0), +} diff --git a/src/assetmanagement.rs b/src/assetmanagement.rs new file mode 100644 index 0000000..5f08c9d --- /dev/null +++ b/src/assetmanagement.rs @@ -0,0 +1,155 @@ +use amethyst::{ + assets::{AssetStorage, Loader}, + config::Config, + renderer::{ + Material, Mesh, MeshHandle, ObjFormat, PngFormat, Texture, TextureData, TextureHandle, + TextureMetadata, + }, +}; +use std::collections::HashMap; +use std::path::Path; + +#[derive(Default)] +pub struct MeshManager { + assets: HashMap, +} + +impl MeshManager { + pub fn get_handle_or_load<'a>( + &mut self, + path: &str, + loader: &Loader, + storage: &'a mut AssetStorage, + ) -> MeshHandle { + if let Some(handle) = self.assets.get(path).cloned() { + return handle; + } + + let handle = loader.load(Self::sanitize_path(&path), ObjFormat, (), (), storage); + self.assets.insert(String::from(path), handle.clone()); + handle + } + + /// Adds the foldername and file extension to the assets' name + #[inline(always)] + fn sanitize_path(path: &str) -> String { + format!("{}{}{}", "meshes/", path, ".obj") + } + + pub fn is_default(&self) -> bool { + self.assets.len() == 0 + } +} + +impl PartialEq for MeshManager { + fn eq(&self, other: &Self) -> bool { + self.is_default() && other.is_default() + } +} + +#[derive(Default)] +pub struct TextureManager { + assets: HashMap, + colors: HashMap, + defaults: Option, +} + +impl TextureManager { + pub fn material_from<'a>( + &mut self, + path: &str, + loader: &Loader, + mut storage: &'a mut AssetStorage, + ) -> Material { + Material { + albedo: self.get_handle_or_load(path, &loader, &mut storage), + ..self.defaults.clone().unwrap() + } + } + + pub fn get_handle_or_load<'a>( + &mut self, + path: &str, + loader: &Loader, + storage: &'a AssetStorage, + ) -> TextureHandle { + if let Some(handle) = self.assets.get(path).cloned() { + return handle; + } + + if let Some(color) = self.colors.get(path) { + let handle = loader.load_from_data(TextureData::color(color.clone()), (), &storage); + self.assets.insert(String::from(path), handle.clone()); + return handle; + }; + + let handle = loader.load( + Self::sanitize_path(&path), + PngFormat, + TextureMetadata::srgb(), + (), + storage, + ); + self.assets.insert(String::from(path), handle.clone()); + handle + } + + /// Adds the foldername and file extension to the assets' name + #[inline(always)] + fn sanitize_path(path: &str) -> String { + format!("{}{}{}", "textures/", path, ".png") + } + + pub fn initialize_with(&mut self, default: Material) { + self.defaults = Some(default); + // TODO + self.colors = HashMap::::load(Path::new(&format!( + "{}/resources/color_palette.ron", + env!("CARGO_MANIFEST_DIR") + ))); + } + + pub fn is_default(&self) -> bool { + self.assets.len() == 0 + } +} + +impl PartialEq for TextureManager { + fn eq(&self, other: &Self) -> bool { + self.is_default() && other.is_default() + } +} + +pub mod util { + use amethyst::{ + assets::{AssetStorage, Loader}, + ecs::{Entity, Write, WriteStorage}, + renderer::{Material, Mesh, MeshHandle, Texture}, + }; + use assetmanagement::{MeshManager, TextureManager}; + + pub type TextureStorages<'a> = ( + Write<'a, TextureManager>, + Write<'a, AssetStorage>, + WriteStorage<'a, Material>, + ); + pub type MeshStorages<'a> = ( + Write<'a, MeshManager>, + Write<'a, AssetStorage>, + WriteStorage<'a, MeshHandle>, + ); + + pub fn attach_assets( + entity: Entity, + path: &str, + loader: &Loader, + (ref mut texture_manager, ref mut texture_storage, ref mut material_storage): &mut TextureStorages, + (ref mut mesh_manager, ref mut mesh_storage, ref mut mesh_handle_storage): &mut MeshStorages, + ) { + let mesh = mesh_manager.get_handle_or_load(&path, &loader, mesh_storage); + let material = texture_manager.material_from(path, &loader, texture_storage); + + material_storage.insert(entity, material).unwrap(); + mesh_handle_storage.insert(entity, mesh).unwrap(); + } +} diff --git a/src/assetmanagement/asset_loader.rs b/src/assetmanagement/asset_loader.rs deleted file mode 100644 index 80470a6..0000000 --- a/src/assetmanagement/asset_loader.rs +++ /dev/null @@ -1,100 +0,0 @@ -use amethyst::{ - assets::*, - renderer::{ObjFormat, PngFormat}, -}; -use std::collections::HashMap; - -/// This trait provides basic information about the assets on disk -/// The data is stored in a dedicated folder per asset (e.g. `.png`, `.obj`, ...). Each of which has to be identical to the other asset folders except for the different endings -pub trait AssetInformation { - fn folder_name(&self) -> &'static str; - fn extension(&self) -> &'static str; -} - -impl AssetInformation for ObjFormat { - fn folder_name(&self) -> &'static str { - "meshes/" - } - fn extension(&self) -> &'static str { - ".obj" - } -} - -impl AssetInformation for PngFormat { - fn folder_name(&self) -> &'static str { - "textures/" - } - fn extension(&self) -> &'static str { - ".png" - } -} - -/// Amethyst may drop an asset as soon as there is no valid handle to the asset anymore. This causes problems when updating the asset of an entity to an asset, which has to be loaded from disk first. -/// -/// Furthermore, when loosing a handle to an asset in Amethyst, it is required to load the asset once again to get another handle. So one's basically the exact same asset loaded twice. -/// -/// To avoid both, this struct exists once per asset type `T` and holds a handle to every existing asset of type `T` and returns a copy of the requested handle. If the asset handle does not exist already, the asset is loaded from disk. -/// -/// Note, that this disables automated asset dropping completely, because there will always be a valid handle to an asset, the one which is stored in this manager. -pub struct AssetManager { - /// A map of identifiers (the Path to the asset as `String`) to the asset handles - pub assets: HashMap>, -} - -/// This looks broken. But it is exactly what we want. An assetmanager is a singleton in the system, since every resource can only exist once in the world (typesafe). -/// Whenever we compare with an other and and either is not zero, they are different, when both are zero, bot are Defaults -impl PartialEq for AssetManager -where - T: Asset, -{ - fn eq(&self, other: &Self) -> bool { - self.assets.len() == 0 && other.assets.len() == 0 - } -} - -impl Default for AssetManager -where - T: Asset, -{ - fn default() -> Self { - Self::new() - } -} - -impl AssetManager -where - T: Asset, -{ - pub fn new() -> Self { - AssetManager { - assets: HashMap::new(), - } - } - - /// returns the requested handle or, if non existent, load from disk vie the amethyst loader and return the newly gained handle. - pub fn get_asset_handle_or_load<'a, F>( - &mut self, - path: &str, - format: F, - options: F::Options, - storage: &'a mut AssetStorage, - loader: &Loader, - ) -> Handle - where - F: Format + AssetInformation + 'static, - { - if let Some(h) = self.assets.get(path).cloned() { - return h; - } - - let handle: Handle = loader.load( - format!("{}{}{}", format.folder_name(), path, format.extension()), - format, - options, - (), - storage, - ); - self.assets.insert(String::from(path), handle.clone()); - handle - } -} diff --git a/src/assetmanagement/mod.rs b/src/assetmanagement/mod.rs deleted file mode 100644 index 1d30b01..0000000 --- a/src/assetmanagement/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -mod asset_loader; -pub mod util; - -pub use self::asset_loader::AssetManager; diff --git a/src/assetmanagement/util.rs b/src/assetmanagement/util.rs deleted file mode 100644 index 42e7006..0000000 --- a/src/assetmanagement/util.rs +++ /dev/null @@ -1,91 +0,0 @@ -use amethyst::{ - assets::{AssetStorage, Loader}, - ecs::{Entity, ReadExpect, Write, WriteStorage}, - renderer::{ - Material, MaterialDefaults, Mesh, MeshHandle, ObjFormat, PngFormat, Texture, - TextureMetadata, - }, -}; - -use assetmanagement::AssetManager; - -/// A collection of the required resources necessary to attach `Texture` and `MeshHandle` to an entity -pub type AssetStorages<'a> = ( - ReadExpect<'a, Loader>, - Write<'a, AssetManager>, - WriteStorage<'a, MeshHandle>, - Write<'a, AssetStorage>, - Write<'a, AssetManager>, - WriteStorage<'a, Material>, - Write<'a, AssetStorage>, - ReadExpect<'a, MaterialDefaults>, -); - -/// Attaches `Material` and `MéshHandle` of specified asset to the entity -pub fn insert_into_asset_storages(entity: Entity, asset_name: &str, storages: &mut AssetStorages) { - let ( - ref loader, - ref mut mesh_manager, - ref mut mesh_handles, - ref mut mesh_storage, - ref mut tex_manager, - ref mut mat_storage, - ref mut tex_storage, - ref default_mat, - ) = storages; - - let mesh = { - mesh_manager.get_asset_handle_or_load( - asset_name, - ObjFormat, - Default::default(), - mesh_storage, - &loader, - ) - }; - - // load texture/material - let material = { - let handle = tex_manager.get_asset_handle_or_load( - asset_name, - PngFormat, - TextureMetadata::srgb(), - tex_storage, - &loader, - ); - Material { - albedo: handle, - ..default_mat.0.clone() - } - }; - - mat_storage.insert(entity, material).unwrap(); - mesh_handles.insert(entity, mesh).unwrap(); -} - -use ncollide3d::shape::Shape; -use systems::HoverHandler; -pub fn add_hover_handler<'a>( - entity: Entity, - asset_name: &str, - bounding_box: Box>, - (loader, tex_manager, tex_storage, hover_storage): &mut ( - ReadExpect<'a, Loader>, - Write<'a, AssetManager>, - Write<'a, AssetStorage>, - WriteStorage<'a, HoverHandler>, - ), -) { - let hover_mat = tex_manager.get_asset_handle_or_load( - &[asset_name, "_hover"].join(""), - PngFormat, - TextureMetadata::srgb(), - tex_storage, - &loader, - ); - let handler = HoverHandler { - hover: hover_mat, - bounding_box, - }; - hover_storage.insert(entity, handler).unwrap(); -} diff --git a/src/entities/buildings/base.rs b/src/entities/buildings/base.rs index 4d01d9c..cf77720 100644 --- a/src/entities/buildings/base.rs +++ b/src/entities/buildings/base.rs @@ -1,19 +1,22 @@ use amethyst::{ + assets::{AssetStorage, Loader}, core::{ nalgebra::{Point2, Vector3}, transform::{GlobalTransform, Parent, ParentHierarchy, Transform}, }, ecs::prelude::{Builder, Component, Entity, NullStorage, World}, + renderer::Texture, }; use rand::prelude::*; -use assetmanagement::util::{add_hover_handler, insert_into_asset_storages}; +use assetmanagement::{util::attach_assets, TextureManager}; use entities::{RockRaider, Tile}; +use eventhandling::{ClickHandlerComponent, Clickable, HoverHandlerComponent, SimpleHoverHandler}; use level::LevelGrid; use util::amount_in; -use ncollide3d::shape::{Cuboid, Shape}; +use ncollide3d::shape::Cuboid; const MAX_RAIDERS: usize = 10; @@ -58,9 +61,23 @@ impl Base { Point2::new(spawn_tile_position.x, spawn_tile_position.z) }; - let mut storages = world.system_data(); + let mut rr_storages = world.system_data(); + let mut texture_storages = world.system_data(); + let mut mesh_storages = world.system_data(); + let hover_storage = world.system_data(); + let click_storage = world.system_data(); let entities = world.entities(); - RockRaider::instantiate(&entities, spawn_position, &mut storages) + let loader = world.read_resource(); + RockRaider::instantiate( + &entities, + spawn_position, + &mut rr_storages, + &mut texture_storages, + &mut mesh_storages, + &loader, + hover_storage, + click_storage, + ) } /// Create a new Base. The given entity has to have a `Tile::Ground` Component, which then is used as Parent to determine the Position @@ -92,37 +109,46 @@ impl Base { .build(); { - let mut storages = world.system_data(); - add_hover_handler( + let mut tex_storages = world.system_data(); + let mut mesh_storages = world.system_data(); + let loader = world.read_resource(); + attach_assets( result, Base::asset_name(), - Base::bounding_box(), - &mut storages, + &loader, + &mut tex_storages, + &mut mesh_storages, ); } { - let mut storages = world.system_data(); - insert_into_asset_storages(result, Base::asset_name(), &mut storages); + // add a HoverHandler to the Entity + let loader = world.read_resource::(); + let mut tex_manager = world.write_resource(); + let mut tex_storage = world.write_resource(); + let handler = Base::new_hover_handler(&mut tex_manager, &loader, &mut tex_storage); + world.write_storage().insert(result, handler).unwrap(); } - //Build Click Handler - { - use eventhandling::*; - let tmp: Box = Box::new(Base); - world - .write_storage::>() - .insert(result, tmp) - .unwrap(); - } + let mut click_storage = world.write_storage::(); + Base.attach_click_handler(result, &mut click_storage); } + #[inline(always)] fn asset_name() -> &'static str { "buildings/base" } - fn bounding_box() -> Box> { - Box::new(Cuboid::new(Vector3::new(0.33, 0.33, 0.38))) + pub fn new_hover_handler( + tex_manager: &mut TextureManager, + loader: &Loader, + mut tex_storage: &mut AssetStorage, + ) -> HoverHandlerComponent { + let hover_mat = + tex_manager.get_handle_or_load("buildings/base_hover", &loader, &mut tex_storage); + + let bounding_box = Cuboid::new(Vector3::new(0.33, 0.33, 0.39)); + Box::new(SimpleHoverHandler::new(bounding_box, hover_mat)) } } @@ -135,3 +161,13 @@ impl Default for Base { Base } } + +impl Clickable for Base { + fn on_click(&self, entity: Entity, world: &World) { + self.spawn_rock_raider(entity, world); + } + + fn new_click_handler(&self) -> ClickHandlerComponent { + Box::new(Base) as ClickHandlerComponent + } +} diff --git a/src/entities/mod.rs b/src/entities/mod.rs index 8121cc8..897cf38 100644 --- a/src/entities/mod.rs +++ b/src/entities/mod.rs @@ -3,5 +3,4 @@ mod rock_raider; mod tile; pub use self::rock_raider::RockRaider; -pub use self::rock_raider::RockRaiderStorages; pub use self::tile::Tile; diff --git a/src/entities/rock_raider.rs b/src/entities/rock_raider.rs index 982cf38..9b4d3d7 100644 --- a/src/entities/rock_raider.rs +++ b/src/entities/rock_raider.rs @@ -1,41 +1,38 @@ use amethyst::{ + assets::{AssetStorage, Loader}, core::{ nalgebra::{Point2, Vector3}, transform::{GlobalTransform, Transform}, }, - ecs::prelude::{Component, Entities, Entity, NullStorage, WriteStorage}, + ecs::prelude::{Component, Entities, Entity, NullStorage, World, WriteStorage}, + renderer::Texture, }; -pub use assetmanagement::util::*; +use assetmanagement::util::*; +use assetmanagement::{util::attach_assets, TextureManager}; +use eventhandling::{ClickHandlerComponent, Clickable, HoverHandlerComponent, SimpleHoverHandler}; +use level::SelectedRockRaider; +use ncollide3d::shape::Cuboid; /// A Tag to indicate the entity as `RockRader` /// `RockRaider`a are the little moving people, that the player can control to do certain tasks ;). #[derive(Default)] pub struct RockRaider; -pub type RockRaiderStorages<'a> = ( - ( - WriteStorage<'a, RockRaider>, - WriteStorage<'a, Transform>, - WriteStorage<'a, GlobalTransform>, - ), - AssetStorages<'a>, -); - impl RockRaider { pub fn instantiate( entities: &Entities, //note: this is a type alias for Read<'a, EntityRes> position: Point2, - storages: &mut RockRaiderStorages, + (ref mut rock_raider_storage,ref mut transform_storage,ref mut global_transform_storage): &mut ( + WriteStorage, + WriteStorage, + WriteStorage, + ), + texture_storages: &mut TextureStorages, + mesh_storages: &mut MeshStorages, + loader: &Loader, + mut hover_storage: WriteStorage, + mut click_storage: WriteStorage, ) -> Entity { - let ( - ( - ref mut rock_raider_storage, - ref mut transform_storage, - ref mut global_transform_storage, - ), - ref mut asset_storages, - ) = storages; - let mut transform = Transform::default(); transform.set_position(Vector3::new(position.x, 0.0, position.y)); @@ -46,10 +43,36 @@ impl RockRaider { .with(GlobalTransform::default(), global_transform_storage) .build(); - insert_into_asset_storages(entity, RockRaider::asset_name(), asset_storages); + attach_assets( + entity, + RockRaider::asset_name(), + &loader, + texture_storages, + mesh_storages, + ); + + let (ref mut tex_manager, ref mut tex_storage, ref _mat_storage) = texture_storages; + + let handler = Self::new_hover_handler(tex_manager, &loader, tex_storage); + hover_storage.insert(entity, handler).unwrap(); + + RockRaider.attach_click_handler(entity, &mut click_storage); + entity } + pub fn new_hover_handler( + tex_manager: &mut TextureManager, + loader: &Loader, + mut tex_storage: &mut AssetStorage, + ) -> HoverHandlerComponent { + let hover_mat = + tex_manager.get_handle_or_load("/rock_raider/default_hover", &loader, &mut tex_storage); + + let bounding_box = Cuboid::new(Vector3::new(0.21, 0.18, 0.1)); + Box::new(SimpleHoverHandler::new(bounding_box, hover_mat)) + } + fn asset_name() -> &'static str { "/rock_raider/default" } @@ -58,3 +81,13 @@ impl RockRaider { impl Component for RockRaider { type Storage = NullStorage; } + +impl Clickable for RockRaider { + fn on_click(&self, entity: Entity, world: &World) { + *world.write_resource::>() = Some(SelectedRockRaider(entity)); + } + + fn new_click_handler(&self) -> ClickHandlerComponent { + Box::new(RockRaider) as ClickHandlerComponent + } +} diff --git a/src/entities/tile.rs b/src/entities/tile.rs index 780e327..a38fe6b 100644 --- a/src/entities/tile.rs +++ b/src/entities/tile.rs @@ -1,12 +1,27 @@ -use amethyst::ecs::prelude::{Component, DenseVecStorage}; +use amethyst::{ + assets::{AssetStorage, Loader}, + core::{nalgebra::Vector3, transform::Transform}, + ecs::prelude::{Component, DenseVecStorage, Entity, World}, + renderer::Texture, +}; + +use assetmanagement::TextureManager; +use eventhandling::{ClickHandlerComponent, Clickable, HoverHandlerComponent, SimpleHoverHandler}; +use level::{LevelGrid, SelectedRockRaider}; +use ncollide3d::shape::Cuboid; /// A Component which indicates the entity as a `Tile`, meaning it represents one part of the grid that stores the information of the cave's geography #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)] pub enum Tile { - Wall { breaks: bool, ore: u8 }, - Ground { concealed: bool }, + Wall { + breaks: bool, + ore: u8, + }, + Ground { + concealed: bool, + }, - // Convenience Tile, Should never see be seen in actual grids... only exist for comparison + /// Convenience Tile, Should never see be seen in actual grids... only exist for comparison Any, } @@ -42,6 +57,17 @@ impl Tile { _ => false, } } + + pub fn new_hover_handler( + tex_manager: &mut TextureManager, + loader: &Loader, + mut tex_storage: &mut AssetStorage, + ) -> HoverHandlerComponent { + let hover_mat = tex_manager.get_handle_or_load("ground_hover", &loader, &mut tex_storage); + + let bounding_box = Cuboid::new(Vector3::new(0.5, 0.01, 0.5)); + Box::new(SimpleHoverHandler::new(bounding_box, hover_mat)) + } } impl Default for Tile { @@ -56,3 +82,34 @@ impl Default for Tile { impl Component for Tile { type Storage = DenseVecStorage; } + +impl Clickable for Tile { + fn on_click(&self, entity: Entity, world: &World) { + if let Some(SelectedRockRaider(rock_raider)) = *world.write_resource() { + // Destination is the clicked entity + let level_grid = world.read_resource::(); + let tiles = world.read_storage::(); + let transforms = world.read_storage::(); + + let transform = transforms.get(rock_raider).unwrap().translation(); + let x = (transform.x + 0.5) as i32; + let y = (transform.z + 0.5) as i32; + + let start = level_grid.get(x, y).unwrap(); + let path = level_grid.find_path(start, entity, &tiles, &transforms); + + if let Some(path) = path { + world.write_storage().insert(rock_raider, path).unwrap(); + } + } + *world.write_resource::>() = None; + } + + fn new_click_handler(&self) -> ClickHandlerComponent { + // TODO Refactor + // This is working, because there are currently no different clickhandler for different Tiles. + // A rr does not move to a Wall, because there will be no Path to the Wall(the destination is not `walkable()`) + // is updated as soon as different ClickHandler are required + Box::new(Tile::Any) as Box + } +} diff --git a/src/systems/camera_movement.rs b/src/eventhandling/camera_movement.rs similarity index 100% rename from src/systems/camera_movement.rs rename to src/eventhandling/camera_movement.rs diff --git a/src/eventhandling/click_handling.rs b/src/eventhandling/clicking.rs similarity index 52% rename from src/eventhandling/click_handling.rs rename to src/eventhandling/clicking.rs index 6279136..5483939 100644 --- a/src/eventhandling/click_handling.rs +++ b/src/eventhandling/clicking.rs @@ -1,6 +1,6 @@ -use amethyst::ecs::World; -use amethyst::ecs::{Component, DenseVecStorage, Entity}; -use entities::buildings::Base; +use amethyst::ecs::{Component, DenseVecStorage, Entity, World, WriteStorage}; + +pub type ClickHandlerComponent = Box; /// This trait is meant to be used as TraitObject to enable encapsulated implementation for every possible clickable Entity. /// @@ -10,16 +10,22 @@ use entities::buildings::Base; /// Note, that that `HoverHandler` is implemented as Component, which makes it harder to make Hoverable a supertrait of Clickable /// This can be adapted as soon as the is the need to do so @karyon <3<3<3 pub trait Clickable: Sync + Send { - fn on_click(&self, &Entity, &World); -} + /// This method is called, whenever the mouse hovers the entity of this component and clicks. It only is triggered on the nearest entity, that has a `Hoverable` Component as well. + fn on_click(&self, Entity, &World); -impl Component for Box { - type Storage = DenseVecStorage>; -} + /// Creates a new ClickHandler, which can be stored + fn new_click_handler(&self) -> ClickHandlerComponent; -impl Clickable for Base { - /// This method is called, whenever the mouse hovers the entity of this component. It only is triggered on the nearest entity, that has a `Hoverable` Comonent as well. - fn on_click(&self, entity: &Entity, world: &World) { - self.spawn_rock_raider(*entity, world); + /// Add a ClickHandler to the entity + fn attach_click_handler( + &self, + entity: Entity, + storage: &mut WriteStorage, + ) { + storage.insert(entity, self.new_click_handler()).unwrap(); } } + +impl Component for ClickHandlerComponent { + type Storage = DenseVecStorage; +} diff --git a/src/eventhandling/hovering.rs b/src/eventhandling/hovering.rs new file mode 100644 index 0000000..1c2a965 --- /dev/null +++ b/src/eventhandling/hovering.rs @@ -0,0 +1,204 @@ +use amethyst::{ + core::{ + nalgebra::{try_convert, Isometry, Isometry3, Translation3}, + GlobalTransform, + }, + ecs::prelude::{ + Component, DenseVecStorage, Entities, Entity, Join, Read, ReadStorage, System, World, + Write, WriteStorage, + }, + renderer::{Material, TextureHandle}, + shrev::EventChannel, +}; + +use eventhandling::MouseRay; +use ncollide3d::shape::Shape; + +pub struct HoverInteractionSystem; + +impl<'a> System<'a> for HoverInteractionSystem { + type SystemData = ( + Entities<'a>, + Read<'a, MouseRay>, + ReadStorage<'a, GlobalTransform>, + WriteStorage<'a, HoverHandlerComponent>, + Write<'a, Hovered>, + Write<'a, EventChannel>, + ); + + fn run( + &mut self, + (entities, mouse_ray, transforms, mut hover_handlers, mut hovered, mut hover_channel): Self::SystemData, + ) { + let mut nearest_dist = None; + let mut nearest_entity = None; + for (entity, hover_handler, transform) in + (&*entities, &mut hover_handlers, &transforms).join() + { + if let Some(collision_distance) = { + // the mesh model has its pivot point on the bottom, the collider have their pivots in the middle. + // to adjust the position we need to move the collision shapes a bit up + let offset: Translation3 = Translation3::new( + 0.0, + hover_handler + .bounding_box() + .aabb(&Isometry::identity()) + .half_extents() + .y, + 0.0, + ); + let mut translation: Isometry3 = try_convert(transform.0).unwrap(); + translation.append_translation_mut(&offset); + + hover_handler + .bounding_box() + .as_ray_cast() + .unwrap() + .toi_with_ray(&translation, &mouse_ray.ray, true) + } { + if let Some(ref mut dist) = nearest_dist { + if collision_distance < *dist { + *dist = collision_distance; + nearest_entity = Some(entity); + } + } else { + nearest_dist = Some(collision_distance); + nearest_entity = Some(entity); + } + } + } + if nearest_entity != **hovered { + // something has changed + + // if the hovered contains an entity, that entity is not hovered anymore + (**hovered).map(|e| { + hover_channel.single_write(HoverEvent { + start: false, + target: e, + }) + }); + + // entity is hovered, that was not hovered before + nearest_entity.map(|e| { + hover_channel.single_write(HoverEvent { + start: true, + target: e, + }) + }); + } + // Update Hovered entity + // * for removing the write + // * for Deref to Option + **hovered = nearest_entity; + } +} + +/// A wrapper to store the hovered entity as `Resource` +#[derive(Clone, Default)] +pub struct Hovered(Option); + +impl std::ops::Deref for Hovered { + type Target = Option; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for Hovered { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +pub type HoverHandlerComponent = Box; + +/// This trait is meant to be used as TraitObject to enable encapsulated implementation for every possible hoverable Entity. +/// The TraitObjects will mark enties that are hoverable. +/// +/// Note that only hoverable entities are `Clickable`, +pub trait Hoverable: Sync + Send { + /// This method is called, whenever the mouse hovers the entity of this component. It only is triggered on the nearest entity, that has a `Hoverable` Comonent as well. + fn on_hover_start(&mut self, _: Entity, _: &World) {} + + /// This method os called, when the hovering stops :) + fn on_hover_stop(&mut self, _: Entity, _: &World) {} + + fn bounding_box(&self) -> &Box>; +} + +impl Component for HoverHandlerComponent { + type Storage = DenseVecStorage; +} + +/// A Hoverhandler that does nothing on hover. Used to enable clicking for the Entity +#[allow(dead_code)] //TODO +pub struct NoEffectHoverHandler { + /// The bounding box, that needs to collide with the `MouseRay` on order to be considered as hovered + bounding_box: Box>, +} + +impl NoEffectHoverHandler { + /// Creates a new Handler with a given bounding box + #[allow(dead_code)] //TODO + pub fn new>(bounding_box: T) -> Self { + Self { + bounding_box: Box::new(bounding_box) as Box>, + } + } +} + +impl Hoverable for NoEffectHoverHandler { + fn bounding_box(&self) -> &Box> { + &self.bounding_box + } +} + +/// A Hoverhandler that switches materials on hover. +pub struct SimpleHoverHandler { + /// The bounding box, that needs to collide with the `MouseRay` on order to be considered as hovered + bounding_box: Box>, + /// The hover texture of the entity. When hovered, the default texturehandle is stored here instead. + texture: TextureHandle, +} + +impl SimpleHoverHandler { + /// Creates a new Handler with a given bounding box and swap textures + pub fn new>(bounding_box: T, handle: TextureHandle) -> Self { + Self { + bounding_box: Box::new(bounding_box) as Box>, + texture: handle, + } + } +} + +impl Hoverable for SimpleHoverHandler { + fn bounding_box(&self) -> &Box> { + &self.bounding_box + } + + fn on_hover_start(&mut self, entity: Entity, world: &World) { + let mut materials = world.write_storage::(); + let mat = materials.get_mut(entity).unwrap(); + let texture_handle = mat.albedo.clone(); + mat.albedo = self.texture.clone(); + self.texture = texture_handle; + } + + fn on_hover_stop(&mut self, entity: Entity, world: &World) { + let mut materials = world.write_storage::(); + let mat = materials.get_mut(entity).unwrap(); + let texture_handle = mat.albedo.clone(); + mat.albedo = self.texture.clone(); + self.texture = texture_handle; + } +} + +/// A simple hover event. +#[derive(Clone)] +pub struct HoverEvent { + /// indicates weather the hover started or stopped + pub start: bool, + /// The entity, that's either hovered or leaved + pub target: Entity, +} diff --git a/src/eventhandling/mod.rs b/src/eventhandling/mod.rs index 099f583..7bcffca 100644 --- a/src/eventhandling/mod.rs +++ b/src/eventhandling/mod.rs @@ -1,3 +1,31 @@ -mod click_handling; +mod camera_movement; +mod clicking; +mod hovering; +mod mouse_ray; -pub use self::click_handling::*; +pub use self::camera_movement::CameraMovementSystem; +pub use self::clicking::*; +pub use self::hovering::*; +pub use self::mouse_ray::*; + +use amethyst::{ + core::{ + shrev::{EventChannel, ReaderId}, + specs::{Read, Resources, SystemData}, + EventReader, + }, + ui::UiEvent, + winit::Event, +}; + +/// All Events that are handled in the States `handle_event` +#[derive(Clone, EventReader)] +#[reader(GameEventReader)] +pub enum GameEvent { + /// Events sent by the winit window. + Window(Event), + /// Events sent by the ui system. + Ui(UiEvent), + /// Event sent by the hover system. + Hover(HoverEvent), +} diff --git a/src/systems/mouse_ray.rs b/src/eventhandling/mouse_ray.rs similarity index 100% rename from src/systems/mouse_ray.rs rename to src/eventhandling/mouse_ray.rs diff --git a/src/level/level_grid.rs b/src/level/level_grid.rs index da0f3bc..b7427c3 100644 --- a/src/level/level_grid.rs +++ b/src/level/level_grid.rs @@ -1,24 +1,28 @@ const CONCEALED: &str = "concealed"; use amethyst::{ + assets::Loader, core::{ nalgebra::{Point2, Vector3}, transform::{GlobalTransform, Transform}, }, ecs::{ prelude::{Builder, Entity, World}, - storage::{GenericReadStorage, GenericWriteStorage}, + storage::{GenericReadStorage, GenericWriteStorage, WriteStorage}, }, + shrev::EventChannel, }; -use pathfinding::directed::bfs; - use assetmanagement::util::*; use entities::Tile; +use eventhandling::{ClickHandlerComponent, Clickable, HoverEvent, HoverHandlerComponent, Hovered}; use level::TilePatternMap; +use pathfinding::directed::bfs; use systems::Path; use util; +pub type TileGrid = Vec>; + /// A `Resource`, that holds every `Entity` that has a `Tile` Component and thus represents a part of the cave's layout pub struct LevelGrid { /// A two-dimensional array of the cave's geography. @@ -29,7 +33,7 @@ impl LevelGrid { /// Instantiates the grid with `Entity`s that have a `Tile` component regarding to the given specification. /// /// Note, that this does not add `MeshHandles` or `Material` to the `Entity`, so the Entities won't get rendered yet. - pub fn from_grid(mut tile_grid: Vec>, world: &mut World) -> LevelGrid { + pub fn from_grid(mut tile_grid: TileGrid, world: &mut World) -> LevelGrid { let level_grid: Vec> = tile_grid .iter_mut() .map(|tile_vec| { @@ -148,16 +152,40 @@ impl LevelGrid { dict: &TilePatternMap, transforms: &mut R, tiles: &T, - storages: &mut AssetStorages, + texture_storags: &mut TextureStorages, + mesh_storages: &mut MeshStorages, + hovered: &mut Hovered, + loader: &Loader, + hover_channel: &mut EventChannel, + hover_storage: &mut WriteStorage, + mut click_storage: &mut WriteStorage, ) { let entity = self.get(x, y).unwrap(); let (classifier, rotation) = self.determine_sprite_for(x, y, &dict, tiles); - insert_into_asset_storages(entity, classifier, storages); + attach_assets(entity, classifier, &loader, texture_storags, mesh_storages); let mut transform = Transform::default(); transform.set_position(Vector3::new(x as f32, 0.0, y as f32)); transform.rotate_local(Vector3::::y_axis(), -rotation); transforms.insert(entity, transform).unwrap(); + + //Add hover handler for the Tile + if let Some(tile) = self.get_tile(x, y, tiles) { + let (ref mut texture_manager, ref mut texture_storage, ref mut _material_storage) = + texture_storags; + + let handler = Tile::new_hover_handler(texture_manager, &loader, texture_storage); + hover_storage.insert(entity, handler).unwrap(); + tile.attach_click_handler(entity, &mut click_storage); + + // when the current entity is hovered, we add a new `HoverEvent` to the queue, since our old hoverhandler was overwritten + if Some(entity) == **hovered { + hover_channel.single_write(HoverEvent { + start: true, + target: entity, + }); + } + } } pub fn get_tile<'a, T: GenericReadStorage>( @@ -250,12 +278,12 @@ impl LevelGrid { storage: &T, ) -> (i32, i32) { if let Some(transform) = storage.get(*entity) { - return ( - transform.translation().x as i32, - transform.translation().z as i32, - ); + let x = transform.translation().x as i32; + let y = transform.translation().z as i32; + // Test if the entity is part of the Grid at all. `unwrap()` is okay, because if it not part of the grid, we panic anyway + assert_eq!(*entity, self.get(x, y).unwrap()); + return (x, y); }; - // TODO check for correctness panic!("Entity is not part of the grid, but its grid position was asked"); } } diff --git a/src/level/level_state.rs b/src/level/level_state.rs index 3ac7ce7..ceb4436 100644 --- a/src/level/level_state.rs +++ b/src/level/level_state.rs @@ -1,32 +1,51 @@ use amethyst::{ - assets::{AssetStorage, Loader}, + assets::AssetStorage, core::{ nalgebra::Vector3, timing::Time, transform::{GlobalTransform, Parent, Transform}, }, - ecs::Entity, + ecs::{Entity, WriteStorage}, input::{is_close_requested, is_key_down, InputHandler}, prelude::*, renderer::{ - ActiveCamera, Camera, Light, Mesh, MouseButton, ObjFormat, PngFormat, PointLight, Rgba, - ScreenDimensions, Texture, TextureMetadata, VirtualKeyCode, + ActiveCamera, Camera, Light, MaterialDefaults, Mesh, MouseButton, PointLight, Rgba, + ScreenDimensions, Texture, VirtualKeyCode, }, + shrev::EventChannel, ui::*, }; -use systems::OxygenBar; - -use assetmanagement::AssetManager; +use assetmanagement::{MeshManager, TextureManager}; use entities::{buildings::Base, RockRaider, Tile}; -use eventhandling::Clickable; -use level::LevelGrid; -use systems::RevealQueue; -use systems::{HoverHandler, Hovered, Oxygen, Path}; -use util::add_resource_soft; +use eventhandling::{ClickHandlerComponent, GameEvent, HoverEvent, HoverHandlerComponent, Hovered}; +use level::{LevelGrid, TileGrid}; +use systems::{Oxygen, OxygenBar, Path, RevealQueue}; +use ui::UiMap; +use util::add_resource_without_override; use GameScene; -use std::{cmp::Reverse, path::Path as OSPath}; +use std::{ + cmp::Reverse, + ops::{Deref, DerefMut}, + path::Path as OSPath, +}; + +pub struct SelectedRockRaider(pub Entity); + +impl Deref for SelectedRockRaider { + type Target = Entity; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for SelectedRockRaider { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} /// The `State` that is active, when a level runs pub struct LevelState { @@ -61,8 +80,8 @@ impl LevelState { } /// Loads the cave's model from disk. - fn load_tile_grid() -> Vec> { - let tile_grid = Vec::>::load(OSPath::new(&format!( + fn load_tile_grid() -> TileGrid { + let tile_grid = TileGrid::load(OSPath::new(&format!( "{}/assets/levels/1.ron", env!("CARGO_MANIFEST_DIR") ))); @@ -72,7 +91,7 @@ impl LevelState { } /// Converts the cave's model into a `LevelGrid` and adds it to the world. - fn initialize_level_grid(world: &mut World, tile_grid: Vec>) { + fn initialize_level_grid(world: &mut World, tile_grid: TileGrid) { let level_grid = LevelGrid::from_grid(tile_grid, world); let max_x = level_grid.x_len(); let max_y = level_grid.y_len(); @@ -80,7 +99,13 @@ impl LevelState { let tiles = world.read_storage::(); let mut transforms = world.write_storage::(); let dict = world.read_resource::(); - let mut storages = world.system_data(); + let mut tex_storages = world.system_data(); + let mut mesh_storages = world.system_data(); + let mut hover_storage = world.system_data::>(); + let mut click_storage = world.system_data::>(); + let mut hovered = world.write_resource::(); + let mut hover_channel = world.write_resource::>(); + let loader = world.read_resource(); for x in 0..max_x { for y in 0..max_y { @@ -90,7 +115,13 @@ impl LevelState { &dict, &mut transforms, &tiles, - &mut storages, + &mut tex_storages, + &mut mesh_storages, + &mut hovered, + &loader, + &mut hover_channel, + &mut hover_storage, + &mut click_storage, ); } } @@ -100,28 +131,16 @@ impl LevelState { /// Loads all assets that will presumably be used in the level into memory and `AssetManager`. fn load_initial_assets(world: &World) { - let mut mesh_manager = world.write_resource::>(); + let mut mesh_manager = world.write_resource::(); let mut mesh_storage = world.write_resource::>(); - let mut texture_manager = world.write_resource::>(); + let mut texture_manager = world.write_resource::(); let mut texture_storage = world.write_resource::>(); - let loader = world.read_resource::(); + let loader = world.read_resource(); for (_, asset) in world.read_resource::().iter() { debug!("loading asset: {}", asset); - mesh_manager.get_asset_handle_or_load( - asset, - ObjFormat, - Default::default(), - &mut mesh_storage, - &loader, - ); - texture_manager.get_asset_handle_or_load( - asset, - PngFormat, - TextureMetadata::srgb(), - &mut texture_storage, - &loader, - ); + mesh_manager.get_handle_or_load(asset, &loader, &mut mesh_storage); + texture_manager.get_handle_or_load(asset, &loader, &mut texture_storage); } } @@ -188,40 +207,46 @@ impl LevelState { } } -impl SimpleState for LevelState { +impl<'a, 'b> State, GameEvent> for LevelState { fn on_start(&mut self, data: StateData) { let world = data.world; world.register::(); world.register::(); world.register::(); - world.register::(); - world.register::>(); + world.register::(); + world.register::(); world.register::(); world.register::(); - let mesh_manager = AssetManager::::default(); - let texture_manager = AssetManager::::default(); + let mesh_manager = MeshManager::default(); + let texture_manager = TextureManager::default(); let tile_pattern_config = LevelState::load_tile_pattern_config(); let oxygen = Oxygen::new(100.); + let tile_grid = LevelState::load_tile_grid(); + + add_resource_without_override(world, tile_pattern_config); + add_resource_without_override(world, mesh_manager); + if add_resource_without_override(world, texture_manager) { + let mut texture_manager = world.write_resource::(); + texture_manager.initialize_with(world.read_resource::().0.clone()); + } + + let map = UiMap::from(tile_grid.clone(), world); world.exec(|mut creator: UiCreator| creator.create("ui/oxygen_bar/prefab.ron", ())); - /* more resources, that are initialized as default: - Option with: None - Option with: None - */ world.add_resource(Some(RevealQueue::new())); world.add_resource(Some(oxygen)); - - add_resource_soft(world, mesh_manager); - add_resource_soft(world, texture_manager); - add_resource_soft(world, tile_pattern_config); + world.add_resource::(Hovered::default()); + world.add_resource::>(None); + world.add_resource::>(None); + world.add_resource(Some(map)); LevelState::load_initial_assets(world); let cam = LevelState::initialize_camera(world); LevelState::initialize_light(world, cam); - LevelState::initialize_level_grid(world, LevelState::load_tile_grid()); + LevelState::initialize_level_grid(world, tile_grid); *world.write_resource() = LevelState::scene(); } @@ -230,53 +255,103 @@ impl SimpleState for LevelState { *data.world.write_resource() = LevelState::scene(); } - fn handle_event(&mut self, data: StateData, event: StateEvent) -> SimpleTrans { - if let StateEvent::Window(event) = &event { - if is_close_requested(&event) || is_key_down(&event, VirtualKeyCode::Escape) { - debug!("Quitting"); - return Trans::Quit; - } else if is_key_down(&event, VirtualKeyCode::Tab) { - debug!("Leaving Level State"); - return Trans::Pop; - } else if is_key_down(&event, VirtualKeyCode::Space) { - do_test_method(data); - - return Trans::None; + fn handle_event( + &mut self, + data: StateData, + event: GameEvent, + ) -> Trans, GameEvent> { + let world = data.world; + match &event { + // Dispatch the incoming event + GameEvent::Window(event) => { + if is_close_requested(&event) || is_key_down(&event, VirtualKeyCode::Escape) { + debug!("Quitting"); + return Trans::Quit; + } else if is_key_down(&event, VirtualKeyCode::Tab) { + debug!("Leaving Level State"); + return Trans::Pop; + } else if is_key_down(&event, VirtualKeyCode::Space) { + do_test_method(world); + + return Trans::None; + } + } + GameEvent::Hover(event) => { + // the following code may be a bit unintuitive: + // # remove handler + // # execute handler + // # add handler again + // This is required, because the handler itself may fetch the hoverhandler storage on execution, what would lead to a new borrow, while this method still borrows the storage to execute the handler. + // To bypass this, we remove the handler for the time of execution, so that no resource of the world is borrowed and there are no possible `Invalid Borrow` clashes from this side of the code. + let mut hover_handler = world + .write_storage::() + .remove(event.target) + .unwrap(); + if event.start { + // hover started + hover_handler.on_hover_start(event.target, world); + } else { + // hover ended + hover_handler.on_hover_stop(event.target, world); + } + world + .write_storage::() + .insert(event.target, hover_handler) + .unwrap(); } + _ => (), } - let mouse_button = data - .world + let mouse_button = world .read_resource::>() .mouse_button_is_down(MouseButton::Left); - if !self.mouse_button_was_down & &mouse_button { - if let Some(hovered) = &*data.world.read_resource::>() { - let entity = hovered.entity; - data.world - .read_storage::>() - .get(entity) - .map(|handler| handler.on_click(&entity, data.world)); + if !self.mouse_button_was_down && mouse_button { + if let Some(entity) = **(world.read_resource::()) { + // see hover event dispatching + let opt_handler = world + .write_storage::() + .remove(entity); + opt_handler.map(|handler| { + handler.on_click(entity, world); + world + .write_storage::() + .insert(entity, handler) + }); } } - self.mouse_button_was_down = mouse_button; + // reset selection on right click + if world + .read_resource::>() + .mouse_button_is_down(MouseButton::Right) + { + *world.write_resource::>() = None; + } Trans::None } fn on_stop(&mut self, data: StateData) { let world = data.world; - world.delete_all(); - + *world.write_resource() = GameScene::default(); + *world.write_resource::>() = None; + *world.write_resource::() = Hovered::default(); + *world.write_resource::>() = None; *world.write_resource::>() = None; *world.write_resource::>() = None; *world.write_resource::>() = None; - *world.write_resource::>() = None; + *world.write_resource::() = LevelGrid::default(); //Option? + + world.maintain(); + world.delete_all(); + } + fn update(&mut self, data: StateData) -> Trans, GameEvent> { + data.data.update(&data.world); + Trans::None } } -fn do_test_method(data: StateData) { - let world = data.world; +fn do_test_method(world: &mut World) { LevelState::initialize_base(world); } diff --git a/src/level/mod.rs b/src/level/mod.rs index f20b551..3ce7a61 100644 --- a/src/level/mod.rs +++ b/src/level/mod.rs @@ -1,6 +1,7 @@ mod level_grid; mod level_state; -pub use self::level_grid::LevelGrid; +pub use self::level_grid::{LevelGrid, TileGrid}; pub use self::level_state::LevelState; +pub use self::level_state::SelectedRockRaider; pub use self::level_state::TilePatternMap; diff --git a/src/main.rs b/src/main.rs index 40afde6..7d4bd8b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,7 @@ extern crate amethyst; #[macro_use] +extern crate amethyst_derive; +#[macro_use] extern crate log; extern crate serde; #[macro_use] @@ -12,8 +14,9 @@ mod assetmanagement; mod entities; mod eventhandling; mod level; -mod rockraiders; +mod main_state; mod systems; +mod ui; mod util; use amethyst::{ @@ -24,9 +27,11 @@ use amethyst::{ ui::{DrawUi, UiBundle}, }; +use eventhandling::{GameEvent, GameEventReader}; + fn main() -> amethyst::Result<()> { amethyst::start_logger(Default::default()); - use rockraiders::MainState; + use main_state::MainState; let path = format!("{}/resources/display.ron", env!("CARGO_MANIFEST_DIR")); @@ -48,17 +53,27 @@ fn main() -> amethyst::Result<()> { .with_bundle(TransformBundle::new())? .with_bundle(UiBundle::::new())? .with( - systems::MouseRaySystem.pausable(GameScene::Level), + eventhandling::MouseRaySystem.pausable(GameScene::Level), "mouse_ray_system", &[], ) + .with( + ui::UiMapUpdateSystem.pausable(GameScene::Level), + "ui_map_update_system", + &["ui_transform"], + ) + .with( + ui::UiRockRaiderSystem.pausable(GameScene::Level), + "ui_rr_update_system", + &["ui_transform", "ui_map_update_system"], + ) .with( systems::MovementSystem.pausable(GameScene::Level), "movement_system", &["transform_system"], ) .with( - systems::CameraMovementSystem.pausable(GameScene::Level), + eventhandling::CameraMovementSystem.pausable(GameScene::Level), "camera_movement_system", &[], ) @@ -73,11 +88,13 @@ fn main() -> amethyst::Result<()> { &["ui_transform"], ) .with( - systems::HoverInteractionSystem.pausable(GameScene::Level), + eventhandling::HoverInteractionSystem.pausable(GameScene::Level), "mouse_input_system", &["mouse_ray_system"], ); - let mut game = Application::new(assets_dir, MainState, game_data)?; + + let mut game = + CoreApplication::<_, GameEvent, GameEventReader>::new(assets_dir, MainState, game_data)?; game.run(); Ok(()) } diff --git a/src/rockraiders.rs b/src/main_state.rs similarity index 68% rename from src/rockraiders.rs rename to src/main_state.rs index 7d8e4de..207681b 100644 --- a/src/rockraiders.rs +++ b/src/main_state.rs @@ -3,6 +3,7 @@ use amethyst::{ prelude::*, renderer::VirtualKeyCode, }; +use eventhandling::GameEvent; use GameScene; use level::LevelState; @@ -16,7 +17,7 @@ impl MainState { } } -impl SimpleState for MainState { +impl<'a, 'b> State, GameEvent> for MainState { fn on_start(&mut self, data: StateData) { *data.world.write_resource() = MainState::scene(); } @@ -25,8 +26,12 @@ impl SimpleState for MainState { *data.world.write_resource() = MainState::scene(); } - fn handle_event(&mut self, _: StateData, event: StateEvent) -> SimpleTrans { - if let StateEvent::Window(event) = &event { + fn handle_event( + &mut self, + _: StateData, + event: GameEvent, + ) -> Trans, GameEvent> { + if let GameEvent::Window(event) = &event { if is_close_requested(&event) || is_key_down(&event, VirtualKeyCode::Escape) { return Trans::Quit; } else if is_key_down(&event, VirtualKeyCode::Tab) { @@ -37,4 +42,9 @@ impl SimpleState for MainState { } Trans::None } + + fn update(&mut self, data: StateData) -> Trans, GameEvent> { + data.data.update(&data.world); + Trans::None + } } diff --git a/src/systems/ground_reveal.rs b/src/systems/ground_reveal.rs index 63c7cdd..ade6bd9 100644 --- a/src/systems/ground_reveal.rs +++ b/src/systems/ground_reveal.rs @@ -1,14 +1,15 @@ use amethyst::{ - assets::{AssetStorage, Loader}, + assets::Loader, core::{timing::Time, transform::Transform}, ecs::prelude::{Entity, Read, ReadExpect, System, Write, WriteStorage}, - renderer::{Material, MaterialDefaults, Mesh, MeshHandle, Texture}, + shrev::EventChannel, }; use entities::Tile; +use eventhandling::{ClickHandlerComponent, HoverEvent, HoverHandlerComponent, Hovered}; use level::{LevelGrid, TilePatternMap}; -use assetmanagement::AssetManager; +use assetmanagement::util::{MeshStorages, TextureStorages}; use std::{cmp::Reverse, collections::BinaryHeap, time::Duration}; /// @@ -22,26 +23,37 @@ pub type RevealQueue = BinaryHeap>; impl<'a> System<'a> for GroundRevealSystem { type SystemData = ( Read<'a, Time>, + ReadExpect<'a, Loader>, + Write<'a, Hovered>, + WriteStorage<'a, HoverHandlerComponent>, + Write<'a, EventChannel>, + WriteStorage<'a, ClickHandlerComponent>, Read<'a, TilePatternMap>, Read<'a, LevelGrid>, Write<'a, Option>, WriteStorage<'a, Transform>, WriteStorage<'a, Tile>, - ( - ReadExpect<'a, Loader>, - Write<'a, AssetManager>, - WriteStorage<'a, MeshHandle>, - Write<'a, AssetStorage>, - Write<'a, AssetManager>, - WriteStorage<'a, Material>, - Write<'a, AssetStorage>, - ReadExpect<'a, MaterialDefaults>, - ), + MeshStorages<'a>, + TextureStorages<'a>, ); fn run( &mut self, - (time, dict, level_grid, mut ground_reveal_queue, mut transforms, mut tiles, mut storages): Self::SystemData, + ( + time, + loader, + mut hover, + mut hovers, + mut hover_channel, + mut clickers, + dict, + level_grid, + mut ground_reveal_queue, + mut transforms, + mut tiles, + mut mesh_storages, + mut texture_storages, + ): Self::SystemData, ) { if let Some(ref mut ground_reveal_queue) = *ground_reveal_queue { while !ground_reveal_queue.is_empty() @@ -97,7 +109,13 @@ impl<'a> System<'a> for GroundRevealSystem { &dict, &mut transforms, &tiles, - &mut storages, + &mut texture_storages, + &mut mesh_storages, + &mut hover, + &loader, + &mut hover_channel, + &mut hovers, + &mut clickers, ); } } diff --git a/src/systems/hover_interaction.rs b/src/systems/hover_interaction.rs deleted file mode 100644 index 11c6a91..0000000 --- a/src/systems/hover_interaction.rs +++ /dev/null @@ -1,111 +0,0 @@ -use amethyst::{ - core::{ - nalgebra::{try_convert, Isometry, Isometry3, Translation3}, - GlobalTransform, - }, - ecs::prelude::{ - Component, DenseVecStorage, Entities, Entity, Join, Read, ReadStorage, System, Write, - WriteStorage, - }, - renderer::{Material, TextureHandle}, -}; -use ncollide3d::shape::Shape; -use systems::MouseRay; - -pub struct HoverInteractionSystem; - -impl<'a> System<'a> for HoverInteractionSystem { - type SystemData = ( - Entities<'a>, - Read<'a, MouseRay>, - ReadStorage<'a, GlobalTransform>, - WriteStorage<'a, HoverHandler>, - WriteStorage<'a, Material>, - Write<'a, Option>, - ); - - fn run( - &mut self, - (entities, mouse_ray, transforms, mut hover_handlers, mut materials, mut hovered): Self::SystemData, - ) { - let mut nearest_dist = None; - let mut nearest_entity = None; - for (entity, hover_handler, transform) in - (&*entities, &mut hover_handlers, &transforms).join() - { - if let Some(collision_distance) = { - // the mesh model has its pivot point on the bottom, the collider have their pivots in the middle. - // to adjust the position we need to move the collision shapes a bit up - let offset: Translation3 = Translation3::new( - 0.0, - hover_handler - .bounding_box - .aabb(&Isometry::identity()) - .half_extents() - .y, - 0.0, - ); - let mut translation: Isometry3 = try_convert(transform.0).unwrap(); - translation.append_translation_mut(&offset); - - hover_handler - .bounding_box - .as_ray_cast() - .unwrap() - .toi_with_ray(&translation, &mouse_ray.ray, true) - } { - if let Some(ref mut dist) = nearest_dist { - if collision_distance < *dist { - *dist = collision_distance; - nearest_entity = Some(entity); - } - } else { - nearest_dist = Some(collision_distance); - nearest_entity = Some(entity); - } - } - } - - let old_hovered_entity = (*hovered).take(); - old_hovered_entity.map(|hovered| { - hover_handlers - .get_mut(hovered.entity) - .unwrap() - .change_materials(&hovered.entity, &mut materials) - }); - // we cannot use `map()` here, because map would move `hovered` while only only borrowed it from the world - - *hovered = nearest_entity.map(|entity| { - hover_handlers - .get_mut(entity) - .unwrap() - .change_materials(&entity, &mut materials); - Hovered { entity } - }); - } -} - -// Only entities with this Component can be hovered. Other Entities will be ignored -pub struct HoverHandler { - pub bounding_box: Box>, - // when hovered, the original `TextureHandle` will be stored here. - pub hover: TextureHandle, -} - -impl HoverHandler { - fn change_materials(&mut self, entity: &Entity, materials: &mut WriteStorage) { - let mat = materials.get_mut(*entity).unwrap(); - let texture_handle = mat.albedo.clone(); - mat.albedo = self.hover.clone(); - self.hover = texture_handle; - } -} - -impl Component for HoverHandler { - type Storage = DenseVecStorage; -} - -#[derive(Clone)] -pub struct Hovered { - pub entity: Entity, -} diff --git a/src/systems/mod.rs b/src/systems/mod.rs index 44bc80e..cb9a2de 100644 --- a/src/systems/mod.rs +++ b/src/systems/mod.rs @@ -1,13 +1,7 @@ -mod camera_movement; mod ground_reveal; -mod hover_interaction; -mod mouse_ray; mod movement; mod oxygen; -pub use self::camera_movement::CameraMovementSystem; pub use self::ground_reveal::{GroundRevealSystem, RevealQueue}; -pub use self::hover_interaction::*; -pub use self::mouse_ray::*; pub use self::movement::{MovementSystem, Path}; pub use self::oxygen::{Oxygen, OxygenBar, OxygenSystem}; diff --git a/src/ui/map.rs b/src/ui/map.rs new file mode 100644 index 0000000..53bc5a0 --- /dev/null +++ b/src/ui/map.rs @@ -0,0 +1,239 @@ +use amethyst::{ + assets::{AssetStorage, Loader}, + core::transform::{Parent, Transform}, + ecs::{ + Entities, Entity, Join, Read, ReadExpect, ReadStorage, System, World, Write, WriteStorage, + }, + prelude::Builder, + renderer::Texture, + ui::{Anchor, UiImage, UiTransform}, +}; + +use assetmanagement::TextureManager; +use entities::{RockRaider, Tile}; +use level::{LevelGrid, TileGrid}; +use util::find_ui_by_name; + +/// Green for Ground +const GROUND_COLOR: &'static str = "GREEN"; +const WALL_COLOR: &'static str = "BEIGE"; +const RR_COLOR: &'static str = "RED"; + +pub struct UiRockRaiderSystem; + +impl<'a> System<'a> for UiRockRaiderSystem { + type SystemData = ( + Entities<'a>, + ReadExpect<'a, Loader>, + Write<'a, TextureManager>, + Write<'a, Option>, + WriteStorage<'a, UiImage>, + WriteStorage<'a, UiTransform>, + ReadStorage<'a, Transform>, + Write<'a, AssetStorage>, + ReadStorage<'a, RockRaider>, + WriteStorage<'a, Parent>, + ); + + fn run( + &mut self, + ( + entities, + loader, + mut texture_manager, + mut map, + mut ui_image_storage, + mut ui_transform_storage, + transform_storage, + mut tex_storage, + rock_raider_storage, + mut parent_storage, + ): Self::SystemData, + ) { + let map = (*map).as_mut().unwrap(); + // delete all ui_rr + for entity in map.rr.drain(..) { + entities.delete(entity).unwrap(); + } + // add all ui_rr + for (_, transform) in (&rock_raider_storage, &transform_storage).join() { + // create a little quad in the map + let image = UiImage { + texture: texture_manager.get_handle_or_load(RR_COLOR, &loader, &mut tex_storage), + }; + let position = UiTransform::new( + "UiMapRR".to_string(), + Anchor::TopLeft, + transform.translation().x * 20., + -transform.translation().z * 20. + 6., + 3., + 12., + 12., + 0, + ); + let parent = find_ui_by_name("UiMap", &entities, &ui_transform_storage).unwrap(); + map.rr.push( + entities + .build_entity() + .with(Parent { entity: parent }, &mut parent_storage) + .with(position, &mut ui_transform_storage) + .with(image, &mut ui_image_storage) + .build(), + ); + } + } +} + +pub struct UiMapUpdateSystem; + +impl<'a> System<'a> for UiMapUpdateSystem { + type SystemData = ( + Read<'a, LevelGrid>, + ReadExpect<'a, Loader>, + Write<'a, TextureManager>, + Read<'a, Option>, + ReadStorage<'a, Tile>, + WriteStorage<'a, UiImage>, + Write<'a, AssetStorage>, + ); + + fn run( + &mut self, + ( + level_grid, + loader, + mut texture_manager, + map, + tile_storage, + mut ui_image_storage, + mut tex_storage, + ): Self::SystemData, + ) { + let map = (*map).as_ref().unwrap(); + for x in 0..level_grid.x_len() { + for y in 0..level_grid.y_len() { + let (x, y) = (x as i32, y as i32); + let tile = level_grid.get_tile(x, y, &tile_storage).unwrap(); + map.update_color( + x, + y, + &tile, + &loader, + &mut texture_manager, + &mut ui_image_storage, + &mut tex_storage, + ); + } + } + } +} + +/// Holds all entities of the UiMap. Those are either small images to represent the tiles or other small quads, that represent the RockRaider. +#[derive(Default)] +pub struct UiMap { + /// Like the LevelGrid, this stores the entities of the MapGrid + grid: Vec>, + /// A Vec of all RR on the Map + rr: Vec, +} + +impl UiMap { + /// Creates a new UiMap from a specification. Requires world access. + pub fn from(mut grid: TileGrid, world: &mut World) -> Self { + // x and y seem swizzled. They are, but this is intended, since the camera is rotated. Furthermore, y needs to be inverted, because the coordination system is inverted in the z direction (right handed)... + let max_y = grid[0].len() as f32; + let max_x = grid.len() as f32; + let position = UiTransform::new( + "UiMap".to_string(), + Anchor::TopLeft, + 200., + -200., + 1., + max_x * 20., + max_y * 20., + 0, + ); + let parent = world.create_entity().with(position).build(); + + let grid: Vec> = grid + .iter_mut() + .enumerate() + .map(|(x, tile_vec)| { + tile_vec + .iter_mut() + .enumerate() + .map(|(y, tile)| { + let texture = { + // We COULD move this out of the loop. but we don't want to. see the comment above `create_entity` + let loader = world.read_resource::(); + let mut texture_storage = + world.write_resource::>(); + let mut texture_manager = world.write_resource::(); + UiImage { + texture: texture_manager.get_handle_or_load( + Self::tile_color(tile), + &loader, + &mut texture_storage, + ), + } + }; + + let position = UiTransform::new( + "MapTile".to_string(), + Anchor::TopLeft, + x as f32 * 20., + max_y - y as f32 * 20., + 2., + 18., + 18., + 0, + ); + + // This method requires mutability over the world and thus prevents any other references to the world. + // It's a convenience method to not have active storage access from elsewhere( see above for examples^^), while creating an entity and inserting into the borrowed storages. + // We may change that aas soon as we have performance issues. + world + .create_entity() + .with(position) + .with(texture) + .with(Parent { entity: parent }) + .build() + }) + .collect() + }) + .collect(); + + UiMap { + grid, + rr: Vec::default(), + } + } + + pub fn update_color( + &self, + x: i32, + y: i32, + tile: &Tile, + loader: &Loader, + texture_manager: &mut Write, + ui_image_storage: &mut WriteStorage, + tex_storage: &mut Write>, + ) { + let image = UiImage { + texture: texture_manager.get_handle_or_load( + Self::tile_color(tile), + &loader, + &tex_storage, + ), + }; + let entity = self.grid[x as usize][y as usize]; + ui_image_storage.insert(entity, image).unwrap(); + } + + fn tile_color(tile: &Tile) -> &'static str { + match tile { + Tile::Ground { concealed: false } => GROUND_COLOR, + _ => WALL_COLOR, + } + } +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs new file mode 100644 index 0000000..b4d30ae --- /dev/null +++ b/src/ui/mod.rs @@ -0,0 +1,4 @@ +mod map; + +// TODO +pub use self::map::*; diff --git a/src/util.rs b/src/util.rs index bfd11ea..8d80901 100644 --- a/src/util.rs +++ b/src/util.rs @@ -35,13 +35,19 @@ pub fn find_ui_by_name<'a, T: Join>( } /// Updates a resource if the resource is Default or doesn't exist. -// TODO remove with merge of https://github.com/slide-rs/specs/pull/522 -pub fn add_resource_soft(world: &mut World, res: T) { +/// Returns true, if the resource was added to the Resources +pub fn add_resource_without_override( + world: &mut World, + res: T, +) -> bool { if !world.res.has_value::() { if (*world.read_resource::()) == T::default() { - *world.write_resource() = res + *world.write_resource() = res; + return true; } } else { - world.add_resource::(res) + world.add_resource::(res); + return true; } + false }