M README.md => README.md +29 -9
@@ 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 | `<ArrowLeft>` |
| Right | `<ArrowRight>` |
-
### 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 `<Escape>` |
### Level Select screen
+| Action | Key(s) / MouseButton |
+|--------|----------------------|
+| Page down | `<ArrowDown>` |
+| Page up | `<ArrowUp>` |
+| Close Screen | `Q` or `<Escape>` |
+
+### Level Cration Screen
+| Action | Key(s) / MouseButton |
+|--------|----------------------|
+| Close Screen | `<Escape>` |
+| Input | `A` - `Z` or `a` - `z` (will be lower case either way) |
+
## Building
We are currently building binaries
M src/screens/build.rs => src/screens/build.rs +3 -4
@@ 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<String>
}
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());
}
}
A src/screens/map_creator.rs => src/screens/map_creator.rs +277 -0
@@ 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
+ }
+}
M src/screens/map_select.rs => src/screens/map_select.rs +6 -6
@@ 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);
M src/screens/mod.rs => src/screens/mod.rs +14 -3
@@ 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()
}
}
}