~comcloudway/little_town

028b34a98d3515efd1daaed16d3bcb9beb487d39 — Jakob Meier 1 year, 8 months ago f4b01fc
Added basic map creator
5 files changed, 329 insertions(+), 22 deletions(-)

M README.md
M src/screens/build.rs
A src/screens/map_creator.rs
M src/screens/map_select.rs
M src/screens/mod.rs
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()
        }
    }
}