From a92446d02464e5694afd08fa036b91590f0c1c02 Mon Sep 17 00:00:00 2001 From: Jakob Meier Date: Sat, 25 Feb 2023 10:56:13 +0100 Subject: [PATCH] Added inventory rendering --- src/blocks.rs | 33 ++++++-- src/draw.rs | 41 ++++++++++ src/main.rs | 4 + src/screens/build.rs | 50 +++++++------ src/screens/inventory.rs | 157 +++++++++++++++++++++++++++++++++++++-- src/screens/welcome.rs | 47 +++--------- src/textures.rs | 55 +++++++++++--- src/types.rs | 2 +- 8 files changed, 307 insertions(+), 82 deletions(-) create mode 100644 src/draw.rs diff --git a/src/blocks.rs b/src/blocks.rs index 2531452..ea8495f 100644 --- a/src/blocks.rs +++ b/src/blocks.rs @@ -1,17 +1,34 @@ -use macroquad::prelude::*; -use crate::textures::AssetStore; -use crate::types::Direction; +use crate::textures::{ + AssetStore, + DirectionalTexture +}; #[derive(Eq, Hash, Clone, PartialEq)] pub enum Block { - Dirt(Direction), - GrassCenter(Direction) + Dirt, + GrassCenter } impl Block { - pub fn get_texture(&self, assets: &AssetStore) -> Texture2D { + /// returns the texture for the block + /// with the direction the block is facing + pub fn get_texture<'a>(&self, assets: &'a AssetStore) -> &'a DirectionalTexture { match self { - Block::Dirt(d) => assets.blocks.dirt.get_dir(d), - Block::GrassCenter(d) => assets.blocks.grass_center.get_dir(d) + Block::Dirt => &assets.blocks.dirt, + Block::GrassCenter => &assets.blocks.grass_center + } + } + /// returns a list of all blocks + pub fn all() -> Vec { + vec![ + Block::Dirt, + Block::GrassCenter + ] + } + + pub fn get_name(&self) -> &str { + match self { + Block::Dirt => "Dirt", + Block::GrassCenter => "Grass", } } } diff --git a/src/draw.rs b/src/draw.rs new file mode 100644 index 0000000..d4392b4 --- /dev/null +++ b/src/draw.rs @@ -0,0 +1,41 @@ +use macroquad::prelude::*; +use crate::textures::AssetStore; + +/// draws centered text +/// with the given font and font size +pub fn draw_centered_text(text: &str, x: f32, y: f32, font: Option, font_size: u16, color: Color) { + let mut title_params = TextParams { + font_size, + color, + ..Default::default() + }; + if let Some(font) = font { + title_params.font = font; + } + let title_center = get_text_center( + text, + font, + font_size, + 1.0, + 0.0); + draw_text_ex( + text, + x - title_center.x, + y - title_center.y, + title_params); +} + +/// draws a wide button with text +pub fn draw_wide_button(text: &str, pressed: bool, x: f32, y: f32, assets: &AssetStore) { + draw_texture( + if pressed { + assets.ui.long_button.1 + } else { assets.ui.long_button.0 }, + x - 190.0 / 2.0, + y, + WHITE); + + draw_centered_text(text, + x, y + 49.0 / 2.0 - 2.0, + Some(assets.font), 60, LIGHTGRAY); +} diff --git a/src/main.rs b/src/main.rs index 565bd85..d431ab0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,13 @@ +#![feature(int_roundings)] +#![feature(let_chains)] + use macroquad::prelude::*; mod screens; mod textures; mod types; mod blocks; +mod draw; use types::{ GameComponent, diff --git a/src/screens/build.rs b/src/screens/build.rs index 8d11930..dd00301 100644 --- a/src/screens/build.rs +++ b/src/screens/build.rs @@ -4,7 +4,8 @@ use crate::textures::AssetStore; use crate::types::{ GameComponent, GameEvent, - Pos3 + Pos3, + Direction }; use crate::blocks::Block; use super::inventory::Inventory; @@ -27,20 +28,20 @@ impl Camera { } /// image width -const TEXTURE_WIDTH: f32 = 256.0; +pub const TEXTURE_WIDTH: f32 = 256.0; /// actual width inside ob cube const TEXTURE_INNER_WIDTH: f32 = 223.0; // 216 /// height of image -const TEXTURE_HEIGHT: f32 = 352.0; +pub const TEXTURE_HEIGHT: f32 = 352.0; /// empty space above texture -const TEXTURE_Y_WHITESPACE: f32 = 130.0; +pub const TEXTURE_Y_WHITESPACE: f32 = 130.0; /// actual height of cube const TEXTURE_INNER_HEIGHT: f32 = 108.0; // 110 /// height of top surface const TEXTURE_DEPTH: f32 = 100.0; pub struct BuildScreen { - grid: HashMap, + grid: HashMap, cam: Camera, mouse_down: bool, show_inv: bool, @@ -56,7 +57,7 @@ impl BuildScreen { inv: Inventory::new() }; - this.grid.insert(Pos3::new(0, 0, 0), Block::Dirt(crate::types::Direction::North)); + this.grid.insert(Pos3::new(0, 0, 0), (Block::Dirt, Direction::North)); this } @@ -155,15 +156,16 @@ impl Face { impl GameComponent for BuildScreen { fn draw(&self, assets: &AssetStore) { if self.show_inv { - // TODO draw inventory + // draw inventory + self.inv.draw(&assets); return; } // order: x (inc) -> y (inc) -> z (inc) - let mut render_order: Vec<(&Pos3, &Block)> = self.grid.iter().collect(); + let mut render_order: Vec<(&Pos3, &(Block, Direction))> = self.grid.iter().collect(); render_order.sort_by_key(|(pos, _)| pos.x + pos.y*2 + pos.z*3 ); - for (pos, block) in render_order.iter() { - let texture = block.get_texture(&assets); + for (pos, (block, dir)) in render_order.iter() { + let texture = block.get_texture(&assets).get_dir(dir); let width = TEXTURE_WIDTH * self.cam.scale; let height = TEXTURE_HEIGHT * self.cam.scale; @@ -189,6 +191,20 @@ impl GameComponent for BuildScreen { } } fn ev_loop(&mut self) -> GameEvent { + + if is_key_down(KeyCode::I) { + self.show_inv = true; + } + if self.show_inv { + match self.inv.ev_loop() { + GameEvent::Quit => { + self.show_inv = false; + } + _ => return GameEvent::None + } + } + + // mouse input if is_mouse_button_down(MouseButton::Left) { // currently holding button down @@ -200,7 +216,7 @@ impl GameComponent for BuildScreen { let (mx, my) = mouse_position(); // virtual render cycle - let render_order: Vec<(&Pos3, &Block)> = self.grid.iter().collect(); + let render_order: Vec<(&Pos3, &(Block, Direction))> = self.grid.iter().collect(); // list of positions in render que for given pixel // (mx, my) @@ -248,7 +264,7 @@ impl GameComponent for BuildScreen { } if let Some(block) = self.inv.place() { - self.grid.insert(pos, block); + self.grid.insert(pos, (block, self.inv.direction.clone())); } } @@ -280,16 +296,6 @@ impl GameComponent for BuildScreen { self.cam.center.x += 1.0 * (1.0/self.cam.scale); } - if is_key_down(KeyCode::I) { - self.show_inv = true; - match self.inv.ev_loop() { - GameEvent::Quit => { - self.show_inv = false; - } - _ => () - } - } - GameEvent::None } } diff --git a/src/screens/inventory.rs b/src/screens/inventory.rs index 55d372e..163ea94 100644 --- a/src/screens/inventory.rs +++ b/src/screens/inventory.rs @@ -1,10 +1,17 @@ use macroquad::prelude::*; use crate::types::{ GameComponent, - GameEvent + GameEvent, + Direction +}; +use super::build::{ + TEXTURE_WIDTH, + TEXTURE_HEIGHT, + TEXTURE_Y_WHITESPACE }; use crate::textures::AssetStore; use crate::blocks::Block; +use crate::draw::draw_centered_text; use std::collections::HashMap; pub struct Inventory { @@ -16,14 +23,33 @@ pub struct Inventory { /// if infinite_items is true, the amount wont decrease contents: HashMap, /// which block is currently selected - pub selected: Option + pub selected: Option, + /// in which direction the block should be facing + pub direction: Direction, + /// current page + /// set to zero when closing inventory + /// the page count depends on the amount of items + /// and on how many slots to draw per page + /// see SLOTS_PER_PAGE for more + page: usize } impl Inventory { pub fn new() -> Self { + let mut cont = HashMap::new(); + let infinite_items = true; + + if infinite_items { + for block in Block::all() { + cont.insert(block, 1); + } + } + Self { - infinite_items: true, - contents: HashMap::new(), - selected: None + infinite_items, + contents: cont, + selected: Some(Block::Dirt), + direction: Direction::North, + page: 0 } } pub fn place(&mut self) -> Option { @@ -46,10 +72,131 @@ impl Inventory { return None; } } + +/// how many slots to draw per inventory page +const SLOTS_PER_PAGE: usize = 9; + + impl GameComponent for Inventory { fn draw(&self, assets: &AssetStore) { + let mid_x = screen_width()/2.0; + let mid_y = screen_height()/2.0; + + let smallest = if screen_width() < screen_height() { + screen_width() + } else { + screen_height() + }; + // use 70% of screen width for slots + let width = smallest * 0.7; + let hslot_count = ((SLOTS_PER_PAGE as f32).sqrt()) as usize; + let slot_dim = width / hslot_count as f32; + // only use 90% of slot width for the actual item + // ensures enough space around item + let slot_render_dim = slot_dim * 0.9; + + let mut list: Vec<(&Block, &usize)> = self.contents.iter().collect(); + list.sort_by(|a,b| a.0.get_name().cmp(b.0.get_name())); + + let ox = mid_x - (hslot_count as f32 /2.0) * slot_dim; + let oy = mid_y - (hslot_count as f32 /2.0) * slot_dim; + for x in 0..hslot_count { + for y in 0..hslot_count { + let tx = ox + slot_dim * x as f32 + (slot_dim - slot_render_dim) / 2.0; + let ty = oy + slot_dim * y as f32 + (slot_dim - slot_render_dim) / 2.0; + + let mut bg = assets.ui.panel_brown.0; + if let Some(block) = list.get(self.page * SLOTS_PER_PAGE + x + y*hslot_count) + && let Some(sb) = &self.selected && sb == block.0 { + // selected block + // draw blue background + bg = assets.ui.panel_blue.0; + } + // draw background + draw_texture_ex( + bg, + tx, + ty, + WHITE, + DrawTextureParams { + dest_size: Some(Vec2::new(slot_render_dim, slot_render_dim)), + ..Default::default() + } + ); + + // draw brown foreground + draw_texture_ex( + assets.ui.panel_brown.1, + tx + 4.0 + 2.0/2.0, + ty + 4.0 + 2.0/2.0, + WHITE, + DrawTextureParams { + dest_size: Some(Vec2::new(slot_render_dim - 4.0 * 2.0 - 4.0, slot_render_dim - 4.0 * 2.0 - 4.0)), + ..Default::default() + } + ); + + if let Some((block, amount)) = list.get(self.page * SLOTS_PER_PAGE + x + y * hslot_count) { + // draw block + draw_texture_ex( + block.get_texture(&assets).get_dir(&self.direction), + tx + slot_render_dim / 6.0, + ty + slot_dim / 2.0 - TEXTURE_HEIGHT / 2.0 + TEXTURE_Y_WHITESPACE / 2.0, + WHITE, + DrawTextureParams { + dest_size: Some(Vec2::new( + slot_render_dim * 2.0/3.0, + slot_render_dim * 2.0/3.0 * TEXTURE_HEIGHT / TEXTURE_WIDTH)), + ..Default::default() + } + ); + + // draw name + draw_centered_text( + block.get_name(), + tx + slot_render_dim / 2.0, + ty + 4.0 * 7.0, + Some(assets.font), + 40, + WHITE + ); + + // draw amount + let text = if self.infinite_items { "inf.".to_string() } else { amount.to_string() }; + draw_centered_text( + &text, + tx + slot_render_dim / 6.0 + slot_render_dim * 2.0/3.0, + ty + slot_dim / 2.0 + - TEXTURE_HEIGHT / 2.0 + TEXTURE_Y_WHITESPACE / 2.0 + + slot_render_dim * 2.0/3.0 * TEXTURE_HEIGHT / TEXTURE_WIDTH + - 4.0, + Some(assets.font), + 30, + WHITE + ); + } + } + } + + let page_count = self.contents.len().div_ceil(SLOTS_PER_PAGE); + + draw_centered_text( + &format!("{}/{}", self.page+1, page_count), + mid_x, + mid_y + hslot_count as f32 /2.0 * slot_dim + slot_dim/3.0, + Some(assets.font), + 60, + WHITE + ); } fn ev_loop(&mut self) -> GameEvent { + + // close inventory + if is_key_down(KeyCode::Q) { + self.page = 0; + return GameEvent::Quit; + } + GameEvent::None } } diff --git a/src/screens/welcome.rs b/src/screens/welcome.rs index 0ded508..fa0b436 100644 --- a/src/screens/welcome.rs +++ b/src/screens/welcome.rs @@ -4,47 +4,24 @@ use crate::types::{ GameEvent }; use crate::textures::AssetStore; +use crate::draw::{ + draw_wide_button, + draw_centered_text +}; -fn draw_centered_text(text: &str, x: f32, y: f32, font: Option, font_size: u16, color: Color) { - let mut title_params = TextParams { - font_size, - color, - ..Default::default() - }; - if let Some(font) = font { - title_params.font = font; - } - let title_center = get_text_center( - text, - font, - font_size, - 1.0, - 0.0); - draw_text_ex( - text, - x - title_center.x, - y - title_center.y, - title_params); -} -fn draw_wide_button(text: &str, pressed: bool, x: f32, y: f32, assets: &AssetStore) { - draw_texture( - if pressed { - assets.long_button.1 - } else { assets.long_button.0 }, - x - 190.0 / 2.0, - y, - WHITE); - - draw_centered_text(text, - x, y + 49.0 / 2.0 - 2.0, - Some(assets.font), 60, LIGHTGRAY); -} - +/// The welcome screen pub struct WelcomeScreen { + /// true when mouse is above the build button + /// and the left button is held + /// used for press-on-release-mechanism button_build_down: bool, + /// true when mouse is above the exit button + /// and the left button is held + /// used for press-on-release-mechanism button_exit_down: bool } impl WelcomeScreen { + /// create a new WelcomeScreen instance pub fn new() -> Self { Self { button_build_down: false, diff --git a/src/textures.rs b/src/textures.rs index 06fc8ad..ea9b06c 100644 --- a/src/textures.rs +++ b/src/textures.rs @@ -27,9 +27,17 @@ fn in_font_folder(path: &str) -> String { /// Collection of textures required by game pub struct AssetStore { pub font: Font, - pub long_button: (Texture2D, Texture2D), + pub ui: UIAssetCollection, pub blocks: BlockAssetCollection } +pub struct UIAssetCollection { + pub long_button: (Texture2D, Texture2D), + pub panel_blue: (Texture2D, Texture2D), + pub panel_brown: (Texture2D, Texture2D), + pub arrow_left: Texture2D, + pub arrow_right: Texture2D, + pub close_button: Texture2D +} pub struct DirectionalTexture { pub north: Texture2D, pub south: Texture2D, @@ -41,16 +49,16 @@ impl DirectionalTexture { Self { north: load_texture( &in_base_folder(&format!("{}_N.png", name))) - .await.expect("Unable to load texture N"), + .await.expect("Unable to load texture N"), south: load_texture( &in_base_folder(&format!("{}_S.png", name))) - .await.expect("Unable to load texture S"), + .await.expect("Unable to load texture S"), west: load_texture( &in_base_folder(&format!("{}_W.png", name))) - .await.expect("Unable to load texture W"), + .await.expect("Unable to load texture W"), east: load_texture( &in_base_folder(&format!("{}_E.png", name))) - .await.expect("Unable to load texture E"), + .await.expect("Unable to load texture E"), } } pub fn get_dir(&self, dir: &Direction) -> Texture2D { @@ -73,13 +81,38 @@ impl AssetStore { Self { font: load_ttf_font(&in_font_folder("Fonts/Kenney Pixel.ttf")) .await.expect("Unable to load font"), - long_button: ( - load_texture( - &in_ui_folder("PNG/buttonLong_brown.png")) + ui: UIAssetCollection { + long_button: ( + load_texture( + &in_ui_folder("PNG/buttonLong_brown.png")) + .await.expect("Unable to load texture"), + load_texture( + &in_ui_folder("PNG/buttonLong_brown_pressed.png")) + .await.expect("Unable to load texture")), + panel_blue: ( + load_texture( + &in_ui_folder("PNG/panel_blue.png")) + .await.expect("Unable to load texture"), + load_texture( + &in_ui_folder("PNG/panelInset_blue.png")) + .await.expect("Unable to load texture")), + panel_brown: ( + load_texture( + &in_ui_folder("PNG/panel_brown.png")) + .await.expect("Unable to load texture"), + load_texture( + &in_ui_folder("PNG/panelInset_brown.png")) + .await.expect("Unable to load texture")), + arrow_left: load_texture( + &in_ui_folder("PNG/arrowBrown_left.png")) + .await.expect("Unable to load texture"), + arrow_right: load_texture( + &in_ui_folder("PNG/arrowBrown_right.png")) .await.expect("Unable to load texture"), - load_texture( - &in_ui_folder("PNG/buttonLong_brown_pressed.png")) - .await.expect("Unable to load texture")), + close_button: load_texture( + &in_ui_folder("PNG/iconCross_brown.png")) + .await.expect("Unable to load texture") + }, blocks: BlockAssetCollection { dirt: DirectionalTexture::from_auto_png("Tiles/dirt_center").await, grass_center: DirectionalTexture::from_auto_png("Tiles/grass_center").await diff --git a/src/types.rs b/src/types.rs index 5ac73df..7802168 100644 --- a/src/types.rs +++ b/src/types.rs @@ -25,7 +25,7 @@ impl Pos3 { } /// Directions -#[derive(Eq, Hash, Clone, PartialEq)] +#[derive(Clone)] pub enum Direction { /// Left /// (left edge of screen) -- 2.38.5