Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

game_menu example: pull common patterns into methods #12008

Open
wants to merge 12 commits into
base: main
Choose a base branch
from

Conversation

awwsmm
Copy link
Contributor

@awwsmm awwsmm commented Feb 20, 2024

Objective

  • examples/games/game_menu has a decent amount of boilerplate
  • this hides the logic and bloats this simple example to > 800 lines

Solution

  • pull common patterns into methods for reusability, better adherence to DRY
  • allows newbies to focus on the differences between different menus, ignoring similarities

@awwsmm
Copy link
Contributor Author

awwsmm commented Feb 20, 2024

I also made a FSM diagram for this example, though I'm unsure of where the best place for this would be in the repo

diagram

@@ -45,10 +45,62 @@ fn setup(mut commands: Commands) {
commands.spawn(Camera2dBundle::default());
}

// This outermost node encompasses the entire Window (100% width and height)
// and centers all content vertically (align_items) and horizontally (justify_content)
fn outer_node(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a pattern common to every screen of this example, and was previously repeated verbatim in 6 different places

}

// This node is always a direct child of the `outer` node, and arranges contents in a column, from top to bottom
fn inner_node<T: Into<BackgroundColor>>(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pattern was previously used on every single screen except for the Splash screen, and so was repeated in five places

fn main_menu_setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// Common style for all buttons on the screen
let button_style = Style {
fn button_style() -> Style {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pattern was previously repeated verbatim in four different places. The only difference between the Main menu and the Main Settings menu was that the buttons were 50px wider on the Main menu. I've made the Main Settings menu buttons 50px wider in order to not have to repeat this configuration

}
}

fn button_text_style() -> TextStyle {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pattern was previously repeated verbatim in six places

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you considered extracting it on the root as a function? A similar pattern is used in game_setup, we could re-use the same code.

fn default_font(font_size: f32) -> TextStyle {
    TextStyle {
        font_size,
        color: TEXT_COLOR,
        ..default()
    }
}

}

// In the main menu and in the main settings menu, we create a column of buttons
fn column_of_buttons(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pattern was previously repeated in two places

@awwsmm
Copy link
Contributor Author

awwsmm commented Feb 20, 2024

Net -160 lines makes it easier to see what's important and what's not on each screen, without having to mentally filter out all of the boilerplate

@nicopap nicopap added C-Examples An addition or correction to our examples A-UI Graphical user interfaces, styles, layouts, and widgets labels Feb 20, 2024
Copy link
Contributor

@nicopap nicopap left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like this set of changes! It suddenly makes the example much less overwhelming.

I suggest a few changes to even reduce more the number of lines, but also to make the code a bit more in line with rust conventions. (The curly braces surrounding closures are especially problematic)

You don't need to manually change anything! I opened a PR on your bevy fork. See the link: awwsmm#1

examples/games/game_menu.rs Outdated Show resolved Hide resolved
examples/games/game_menu.rs Outdated Show resolved Hide resolved
examples/games/game_menu.rs Outdated Show resolved Hide resolved
examples/games/game_menu.rs Outdated Show resolved Hide resolved
examples/games/game_menu.rs Outdated Show resolved Hide resolved
examples/games/game_menu.rs Outdated Show resolved Hide resolved
examples/games/game_menu.rs Outdated Show resolved Hide resolved
examples/games/game_menu.rs Outdated Show resolved Hide resolved
}
}

fn button_text_style() -> TextStyle {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you considered extracting it on the root as a function? A similar pattern is used in game_setup, we could re-use the same code.

fn default_font(font_size: f32) -> TextStyle {
    TextStyle {
        font_size,
        color: TEXT_COLOR,
        ..default()
    }
}

examples/games/game_menu.rs Outdated Show resolved Hide resolved
@nicopap
Copy link
Contributor

nicopap commented Feb 20, 2024

Regarding the state machine, I think it would work as ASCII art as module doc. Here is a wonderful draft

      Start
        v
GameState::Splash
        |
  |---------------------------------------------------------------------|
  |     |                       GameState::Menu                         |
  |     |         v------back-------<                                   |
  |     v         v                 |                                   |
  |  MenuState::Main >-settings-> MenuState::Settings <--------------<  |
  |   v    ^                        |       |                        |  |
  |   |    |                      sound  display                     |  |
  |  play  |                        v       v                        |  |
  |   |    |   MenuState::SettingsSound   MenuState::SettingsDisplay |  |
  |   |    |                     |              |                    |  |
  |   |    |                     >-------------->--------------------^  |
  |---+----+------------------------------------------------------------|
      |    |
      |  timer
      v    ^
   GameState::Game

Copy link
Contributor

@nicopap nicopap left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the one thing to ask is: does this make the example harder to read? And I think it doesn't. In fact, it introduces some nice hints at good practices for UI dev for the current bevy UI state.

@awwsmm
Copy link
Contributor Author

awwsmm commented Feb 21, 2024

So the one thing to ask is: does this make the example harder to read? And I think it doesn't. In fact, it introduces some nice hints at good practices for UI dev for the current bevy UI state.

I also considered this. These changes definitely introduce a bit more indirection, but they also eliminate quite a lot of duplication. I think the net effect is improved readability and understandability.

@benfrankel
Copy link
Contributor

benfrankel commented Feb 26, 2024

I made another version of the diagram. There are no edge labels, but they should be easy to infer hopefully.

              START                                                             
                │                                                               
      ┌─────────▼─────────┐                                                     
      │GameState::Splash  │                                                     
      │MenuState::Disabled│  ┌───────────────────┐  ┌──────────────────────────┐
      └─────────┬─────────┘  │                   ├─►│GameState::Menu           │
      ┌─────────▼─────────┐  │                   │◄─┤MenuState::SettingsDisplay│
END ◄─┤GameState::Menu    ├─►│GameState::Menu    │  └──────────────────────────┘
      │MenuState::Main    │◄─┤MenuState::Settings│  ┌──────────────────────────┐
      └────────┬─▲────────┘  │                   ├─►│GameState::Menu           │
      ┌────────▼─┴────────┐  │                   │◄─┤MenuState::SettingsSound  │
      │GameState::Game    │  └───────────────────┘  └──────────────────────────┘
      │MenuState::Disabled│                                                     
      └───────────────────┘                                                     

Copy link
Contributor

@benfrankel benfrankel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving because this is an overall improvement. Just left a few nits.

examples/games/game_menu.rs Outdated Show resolved Hide resolved
examples/games/game_menu.rs Outdated Show resolved Hide resolved
examples/games/game_menu.rs Outdated Show resolved Hide resolved
examples/games/game_menu.rs Outdated Show resolved Hide resolved
@nicopap nicopap added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it and removed S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it labels Feb 26, 2024
@nicopap
Copy link
Contributor

nicopap commented Feb 26, 2024

FYI, to rebase, you just need to rename Color to LegacyColor.

To avoid some headache, I would rebase and ignore the upstream changes. Here is how it would look like:

  1. Rename Color to LegacyColor
  2. Commit the change
  3. Run git checkout -b example-game_menu-backup && git checkout - (to avoid nasty surprises)
  4. Run git fetch && git rebase upstream/main
  5. Run git checkout --theirs . && git add -u && git rebase --continue
  6. Make sure everything works by running cargo run --example game_menu

@awwsmm
Copy link
Contributor Author

awwsmm commented Mar 12, 2024

I made another version of the diagram. There are no edge labels, but they should be easy to infer hopefully.

              START                                                             
                │                                                               
      ┌─────────▼─────────┐                                                     
      │GameState::Splash  │                                                     
      │MenuState::Disabled│  ┌───────────────────┐  ┌──────────────────────────┐
      └─────────┬─────────┘  │                   ├─►│GameState::Menu           │
      ┌─────────▼─────────┐  │                   │◄─┤MenuState::SettingsDisplay│
END ◄─┤GameState::Menu    ├─►│GameState::Menu    │  └──────────────────────────┘
      │MenuState::Main    │◄─┤MenuState::Settings│  ┌──────────────────────────┐
      └────────┬─▲────────┘  │                   ├─►│GameState::Menu           │
      ┌────────▼─┴────────┐  │                   │◄─┤MenuState::SettingsSound  │
      │GameState::Game    │  └───────────────────┘  └──────────────────────────┘
      │MenuState::Disabled│                                                     
      └───────────────────┘                                                     

Remade this using labels, and making the diagram tall, rather than wide.

//!          START                                 
//!            │                                   
//! ┌──────────▼──────────┐ ┌─────────────────────┐
//! │  GameState::Splash  │ │   GameState::Game   │
//! │ MenuState::Disabled │ │ MenuState::Disabled │
//! └───┬─────────────────┘ └─▲─────┬─────────────┘
//!     │                     │     │              
//! ┌───┼─────────────────────┼─────┼────────┐     
//! │   │                     │     │        │     
//! │ (timer)               Play  (timer)    │     
//! │   │                     │     │        │     
//! │ ┌─▼─────────────────────┴─────▼─┐      │     
//! │ │     MenuState::Main           ├─Quit─┼─►END
//! │ └─┬───────────────────────────▲─┘      │     
//! │   │                           │        │     
//! │ Settings           BackToMainMenu      │     
//! │   │                           │        │     
//! │ ┌─▼───────────────────────────┴──────┐ │     
//! │ │     MenuState::Settings            │ │     
//! │ └─┬───┬─────────────────────────▲──▲─┘ │     
//! │   │   │                         │  │   │     
//! │   │ SettingsDisplay     BackToSettings │     
//! │   │   │                         │  │   │     
//! │   │ ┌─▼─────────────────────────┴┐ │   │     
//! │   │ │ MenuState::SettingsDisplay │ │   │     
//! │   │ └────────────────────────────┘ │   │     
//! │   │                                │   │     
//! │ SettingsSound                      │   │     
//! │   │                                │   │     
//! │ ┌─▼────────────────────────────────┴─┐ │     
//! │ │     MenuState::SettingsSound       │ │     
//! │ └────────────────────────────────────┘ │     
//! │                                        │     
//! │            GameState::Menu             │     
//! │                                        │     
//! └────────────────────────────────────────┘     

Wide diagrams are sometimes soft-wrapped in IDEs (RustRover, for example).

Diagram is available here

https://asciiflow.com/#/share/eJzFVt1KwzAUfpWYK4XBQPCvd46JVxO1vQxIdGEG22ysKWyMvYXsYYZP45OYtiZL16ZJ1cxwYFlOvnPyfeckdAUZTggM4ET8PCWEZbAHY7wkc7G2QnCBYHB1dt5DcClmp5cXYsbJgos%2FCPb7R0CNMLp%2BjIBtIMQqKAA%2B3z%2BsKIUTmy222Vr3OERxsN2JALgV4oUccxIE4SzG6WtJq6SmOfNpSVlDj4To3%2F4hTfFzTMYKbfA5q%2BFgMoqDcHY12goq12v%2Bv2TzcxmqJ%2BnGowF%2FzGlC5id72HtxtYDu85BZ1tODTBtdLEPuYknr2xGmrMpB2ENGuRZ2e3M3rDPwYbLLjQxsD1K79iHhnLJJqgEG%2BOUtmuYq5KJ4yrvH7gDmWP0GQYAZ7deUOqoJWurQVo3CV91WiyCJi2d7Vlx61QpKEo%2FZ%2FfWDQ%2Bam%2BksZ3CJ4MltmyzCiJclwmrFxR%2BzvMvurs0VD13uvS2JGH9ia6%2BA02pHap97usXdCdsv5j1aeBK7h%2Bgu9D%2FUw)

@awwsmm awwsmm force-pushed the example-game_menu-DRY branch 2 times, most recently from 2b2df07 to 8775459 Compare March 12, 2024 13:09
@awwsmm
Copy link
Contributor Author

awwsmm commented Mar 12, 2024

Tested the version on this branch locally and it works now as it does before. All button colours and actions are as expected.

@awwsmm
Copy link
Contributor Author

awwsmm commented Mar 12, 2024

Ready for re-review @nicopap , @benfrankel

Thanks in advance!

examples/games/game_menu.rs Outdated Show resolved Hide resolved
examples/games/game_menu.rs Outdated Show resolved Hide resolved
examples/games/game_menu.rs Outdated Show resolved Hide resolved
@awwsmm awwsmm force-pushed the example-game_menu-DRY branch from 221ef5a to 4dc25de Compare March 12, 2024 21:49
@awwsmm
Copy link
Contributor Author

awwsmm commented Mar 12, 2024

☝🏻 updated main on my fork and rebased

Copy link
Contributor

@benfrankel benfrankel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me :)

@mockersf
Copy link
Member

I'm mostly opposed to those changes. This example is on purpose boilerplate heavy as UI in Bevy is boilerplate heavy, and is a good way to show potential improvements.

Solving it in the example rather than in Bevy is not the good way, and will make it harder to showcase those improvements later. It's also a very good candidate for scene driven UI, and that's made harder by having functions around.

@awwsmm
Copy link
Contributor Author

awwsmm commented Mar 26, 2024

I'm mostly opposed to those changes. This example is on purpose boilerplate heavy as UI in Bevy is boilerplate heavy, and is a good way to show potential improvements.

Solving it in the example rather than in Bevy is not the good way, and will make it harder to showcase those improvements later. It's also a very good candidate for scene driven UI, and that's made harder by having functions around.

Thanks for your feedback, François. How would you use scene-driven UI here? Is there an example of that in the Bevy repo?

Anyway, here are my counterpoints:

Going through the examples/ "is currently the best way to learn Bevy's features and how to use them", and the recommended method for beginners to learn Bevy. This example is 800+ lines and repetitive. It is not very approachable for beginners. You can read how I (a beginner) struggled with it over 4+ hours here, here, and here.

Not improving this example because that would make future UI improvements appear less impactful is not, in my opinion, a good reason to leave an example in its current state. If anything, that biases any future comparison, because you started with an example which was purposefully not streamlined when it could have been.

Even if you do not wish to accept these changes wholesale, surely there are some sections which you would approve of including in the example, like the diagram (or the functions which return styles, rather than the ones which spawn things). If you let me know which improvements, I'd be happy to extract only those out and trash the other ones.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-UI Graphical user interfaces, styles, layouts, and widgets C-Examples An addition or correction to our examples
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants