From 028b34a98d3515efd1daaed16d3bcb9beb487d39 Mon Sep 17 00:00:00 2001 From: Jakob Meier Date: Mon, 27 Feb 2023 21:34:34 +0100 Subject: [PATCH] Added basic map creator --- README.md | 38 +++-- src/screens/build.rs | 7 +- src/screens/map_creator.rs | 277 +++++++++++++++++++++++++++++++++++++ src/screens/map_select.rs | 12 +- src/screens/mod.rs | 17 ++- 5 files changed, 329 insertions(+), 22 deletions(-) create mode 100644 src/screens/map_creator.rs diff --git a/README.md b/README.md index 99d96a4..cfa4633 100644 --- a/README.md +++ b/README.md @@ -9,14 +9,16 @@ with this 2D game ## Screenshot ![img](./screenshot.png) -You can find this example world [here](./example.world). -To try it out, rename the file to `local.data` (and put it into a new folder). -Now launch the binary in that folder and click `Build`, -the world should launch. +To try out the example world on your device, +copy the `example.world` to the applications data dir. + +On Linux, this is `~/.local/share/little_town/`, +if you are not running Linux, +have a look at the [ProjectDir Value Table](https://github.com/xdg-rs/dirs/tree/master/directories#projectdirs). + +After copying the file, +launch the game and the world should appear in the world list -NOTE: There are plans to add multi-world support, -at that point this guide has to be updated, -because the file will also be moved. ## Roadmap - [x] Placing & Removing blocks @@ -25,7 +27,9 @@ because the file will also be moved. - [x] Basic block list - [ ] Inventory categories - [x] p2p local multiplayer (using mdns discovery) -- [ ] world selection & better storage +- [x] world selection & better storage + - [x] World selection screen & loading from seperate files + - [x] World creation screen - [ ] (Maybe) campaign mode (build & expand a city with limited ressources) - [ ] Touch control - [ ] Settings menu (i.e for Keybindings) @@ -56,10 +60,26 @@ because the file will also be moved. | Left | `` | | Right | `` | - ### Inventory +| Action | Key(s) / MouseButton | +|--------|----------------------| +| Select Item & close inventory | Left mouse button (on item) | +| Deselect Item | Left mouse button (on item) | +| Close Inventory | `Q` or `` | ### Level Select screen +| Action | Key(s) / MouseButton | +|--------|----------------------| +| Page down | `` | +| Page up | `` | +| Close Screen | `Q` or `` | + +### Level Cration Screen +| Action | Key(s) / MouseButton | +|--------|----------------------| +| Close Screen | `` | +| Input | `A` - `Z` or `a` - `z` (will be lower case either way) | + ## Building We are currently building binaries diff --git a/src/screens/build.rs b/src/screens/build.rs index 1b0b362..7fc4c97 100644 --- a/src/screens/build.rs +++ b/src/screens/build.rs @@ -11,7 +11,6 @@ use crate::blocks::Block; use super::{ inventory::Inventory, Screen, - welcome::WelcomeScreen }; use crate::storage::save_map; use nanoserde::{DeJson, SerJson}; @@ -83,14 +82,14 @@ pub struct BuildScreen { file_name: Option } impl BuildScreen { - pub fn new(file_name: String) -> Self { + pub fn new(file_name: &str) -> Self { let mut this = Self { grid: HashMap::new(), cam: Camera::new(), show_inv: false, inv: Inventory::new(), mouse_position: None, - file_name: Some(file_name), + file_name: Some(file_name.to_string()), #[cfg(feature="multiplayer")] multiplayer: None }; @@ -510,7 +509,7 @@ impl GameComponent for BuildScreen { } // close game - return GameEvent::ChangeScreen(Screen::Welcome(WelcomeScreen::new())); + return GameEvent::ChangeScreen(Screen::default()); } } diff --git a/src/screens/map_creator.rs b/src/screens/map_creator.rs new file mode 100644 index 0000000..d0f4447 --- /dev/null +++ b/src/screens/map_creator.rs @@ -0,0 +1,277 @@ +use macroquad::prelude::*; +use crate::types::{ + GameComponent, + GameEvent +}; +use crate::textures::AssetStore; +use super::map_select::SelectScreen; +use super::build::BuildScreen; +use super::Screen; + +use crate::storage::{ + get_world_list, + save_map +}; + +use crate::draw::{ + TextButton, + Widget, + ButtonEvent, + SelectableText +}; + +/// The welcome screen +pub struct MapCreatorScreen { + /// back button widget + /// (goes back to WelcomeScreen) + widget_back: TextButton, + /// widget to display the world name + widget_container: SelectableText, + /// button to create world + /// and return to world list + widget_create: TextButton, + /// String contianing the world name + name: String +} +impl MapCreatorScreen { + /// create a new World creation Screen instance + pub fn new() -> Self { + Self { + widget_back: TextButton::new("Back", 0.1, 0.1) + .with_font_size(50) + .with_font_color(LIGHTGRAY), + widget_container: SelectableText::new("type here...", 0.5, 0.5), + widget_create: TextButton::new("Create", 0.7, 0.7) + .with_font_size(40), + name: String::new() + } + } +} +impl GameComponent for MapCreatorScreen { + async fn draw(&self, assets: &AssetStore) { + + { + // draw background image + let dim: f32 = if screen_width() < screen_height() { + screen_width() * 2.0/3.0 + } else { screen_height() * 2.0/3.0 }; + draw_texture_ex( + assets.icon, + screen_width()/2.0 - dim/2.0, + screen_height()/2.0 - dim/2.0, + WHITE, + DrawTextureParams { + dest_size: Some(Vec2::new(dim, dim)), + ..Default::default() + } + ); + draw_rectangle(0.0, 0.0, screen_width(), screen_height(), Color::from_rgba(0, 0, 0, 100)); + } + + self.widget_back.draw(&assets).await; + self.widget_create.draw(&assets).await; + self.widget_container.draw(&assets).await; + } + fn ev_loop(&mut self) -> GameEvent { + + { + // process text input + if is_key_pressed(KeyCode::Delete) { + self.name.pop(); + + if self.name.len() == 0 { + self.widget_container.set_text("type here"); + } else { + self.widget_container.set_text(&self.name); + } + } + + + if self.name.len() < 10 { + if is_key_pressed(KeyCode::A) { + self.name.push('a'); + self.widget_container.set_text(&self.name); + } + } + if self.name.len() < 10 { + if is_key_pressed(KeyCode::B) { + self.name.push('b'); + self.widget_container.set_text(&self.name); + } + } + if self.name.len() < 10 { + if is_key_pressed(KeyCode::C) { + self.name.push('c'); + self.widget_container.set_text(&self.name); + } + } + if self.name.len() < 10 { + if is_key_pressed(KeyCode::D) { + self.name.push('d'); + self.widget_container.set_text(&self.name); + } + } + if self.name.len() < 10 { + if is_key_pressed(KeyCode::E) { + self.name.push('e'); + self.widget_container.set_text(&self.name); + } + } + if self.name.len() < 10 { + if is_key_pressed(KeyCode::F) { + self.name.push('f'); + self.widget_container.set_text(&self.name); + } + } + if self.name.len() < 10 { + if is_key_pressed(KeyCode::G) { + self.name.push('g'); + self.widget_container.set_text(&self.name); + } + } + if self.name.len() < 10 { + if is_key_pressed(KeyCode::H) { + self.name.push('h'); + self.widget_container.set_text(&self.name); + } + } + if self.name.len() < 10 { + if is_key_pressed(KeyCode::I) { + self.name.push('i'); + self.widget_container.set_text(&self.name); + } + } + if self.name.len() < 10 { + if is_key_pressed(KeyCode::J) { + self.name.push('j'); + self.widget_container.set_text(&self.name); + } + } + if self.name.len() < 10 { + if is_key_pressed(KeyCode::K) { + self.name.push('k'); + self.widget_container.set_text(&self.name); + } + } + if self.name.len() < 10 { + if is_key_pressed(KeyCode::L) { + self.name.push('l'); + self.widget_container.set_text(&self.name); + } + } + if self.name.len() < 10 { + if is_key_pressed(KeyCode::M) { + self.name.push('m'); + self.widget_container.set_text(&self.name); + } + } + if self.name.len() < 10 { + if is_key_pressed(KeyCode::N) { + self.name.push('n'); + self.widget_container.set_text(&self.name); + } + } + if self.name.len() < 10 { + if is_key_pressed(KeyCode::O) { + self.name.push('o'); + self.widget_container.set_text(&self.name); + } + } + if self.name.len() < 10 { + if is_key_pressed(KeyCode::P) { + self.name.push('p'); + self.widget_container.set_text(&self.name); + } + } + if self.name.len() < 10 { + if is_key_pressed(KeyCode::Q) { + self.name.push('q'); + self.widget_container.set_text(&self.name); + } + } + if self.name.len() < 10 { + if is_key_pressed(KeyCode::R) { + self.name.push('r'); + self.widget_container.set_text(&self.name); + } + } + if self.name.len() < 10 { + if is_key_pressed(KeyCode::S) { + self.name.push('s'); + self.widget_container.set_text(&self.name); + } + } + if self.name.len() < 10 { + if is_key_pressed(KeyCode::T) { + self.name.push('t'); + self.widget_container.set_text(&self.name); + } + } + if self.name.len() < 10 { + if is_key_pressed(KeyCode::U) { + self.name.push('u'); + self.widget_container.set_text(&self.name); + } + } + if self.name.len() < 10 { + if is_key_pressed(KeyCode::V) { + self.name.push('v'); + self.widget_container.set_text(&self.name); + } + } + if self.name.len() < 10 { + if is_key_pressed(KeyCode::W) { + self.name.push('w'); + self.widget_container.set_text(&self.name); + } + } + if self.name.len() < 10 { + if is_key_pressed(KeyCode::X) { + self.name.push('x'); + self.widget_container.set_text(&self.name); + } + } + if self.name.len() < 10 { + if is_key_pressed(KeyCode::Y) { + self.name.push('y'); + self.widget_container.set_text(&self.name); + } + } + if self.name.len() < 10 { + if is_key_pressed(KeyCode::Z) { + self.name.push('z'); + self.widget_container.set_text(&self.name); + } + } + } + + match self.widget_create.ev_loop() { + ButtonEvent::LeftClick => { + // TODO check text + if self.name.len() > 0 && self.name.len() <= 10 { + let name = format!("{}.world", self.name); + if !get_world_list().contains(&name) { + let bscr = BuildScreen::new(&name); + save_map(&name, &bscr); + return GameEvent::ChangeScreen(Screen::Select(SelectScreen::new())); + } + } + }, + _ => () + } + + match self.widget_back.ev_loop() { + ButtonEvent::LeftClick => { + return GameEvent::ChangeScreen(Screen::default()) + } + _ => () + } + + // return to welcome screen + if is_key_pressed(KeyCode::Escape) { + return GameEvent::ChangeScreen(Screen::default()) + } + + GameEvent::None + } +} diff --git a/src/screens/map_select.rs b/src/screens/map_select.rs index f20aea5..b199c23 100644 --- a/src/screens/map_select.rs +++ b/src/screens/map_select.rs @@ -16,7 +16,7 @@ use crate::storage::{ }; use super::{ Screen, - welcome::WelcomeScreen + map_creator::MapCreatorScreen }; /// The map select screen @@ -108,7 +108,7 @@ impl GameComponent for SelectScreen { match self.widget_back.ev_loop() { ButtonEvent::LeftClick => { // return to welcome screen - return GameEvent::ChangeScreen(Screen::Welcome(WelcomeScreen::new())) + return GameEvent::ChangeScreen(Screen::default()) }, _ => () } @@ -129,7 +129,7 @@ impl GameComponent for SelectScreen { } match self.widget_new.ev_loop() { ButtonEvent::LeftClick => { - + return GameEvent::ChangeScreen(Screen::Create(MapCreatorScreen::new())) }, _ => () } @@ -171,14 +171,14 @@ impl GameComponent for SelectScreen { // return to welcome screen if is_key_pressed(KeyCode::Q) || is_key_pressed(KeyCode::Escape) { - return GameEvent::ChangeScreen(Screen::Welcome(WelcomeScreen::new())) + return GameEvent::ChangeScreen(Screen::default()) } if is_key_pressed(KeyCode::Down) { if self.page < self.list.len() - self.level_select_widgets.len() { self.page += 1; - // TODO: update widget text + // update widget text for (i, slot) in self.level_select_widgets.iter_mut().enumerate() { if let Some(text) = self.list.get(i+self.page) { slot.set_visibility(true); @@ -194,7 +194,7 @@ impl GameComponent for SelectScreen { self.page -= 1; - // TODO: update widget text + // update widget text for (i, slot) in self.level_select_widgets.iter_mut().enumerate() { if let Some(text) = self.list.get(i+self.page) { slot.set_visibility(true); diff --git a/src/screens/mod.rs b/src/screens/mod.rs index 43b31f2..2446bf4 100644 --- a/src/screens/mod.rs +++ b/src/screens/mod.rs @@ -2,10 +2,12 @@ mod welcome; pub mod build; mod inventory; mod map_select; +mod map_creator; use welcome::WelcomeScreen; use build::BuildScreen; use map_select::SelectScreen; +use map_creator::MapCreatorScreen; use crate::types::{ GameComponent, @@ -13,24 +15,33 @@ use crate::types::{ }; use crate::textures::AssetStore; +/// Possible screens +/// that can be shown pub enum Screen { + /// The initial welcome screen Welcome(WelcomeScreen), + /// In-game canvas screen Build(BuildScreen), - Select(SelectScreen) + /// Map selection screen + Select(SelectScreen), + /// Map creation screen + Create(MapCreatorScreen) } impl GameComponent for Screen { async fn draw(&self, assets: &AssetStore) { match self { Screen::Welcome(w) => w.draw(&assets).await, Screen::Build(b) => b.draw(&assets).await, - Screen::Select(s) => s.draw(&assets).await + Screen::Select(s) => s.draw(&assets).await, + Screen::Create(c) => c.draw(&assets).await } } fn ev_loop(&mut self) -> GameEvent { match self { Screen::Welcome(w) => w.ev_loop(), Screen::Build(b) => b.ev_loop(), - Screen::Select(s) => s.ev_loop() + Screen::Select(s) => s.ev_loop(), + Screen::Create(c) => c.ev_loop() } } } -- 2.38.5