flame
flame copied to clipboard
feat: Added RouterComponent
Description
This PR adds the RouterComponent (see the docs for the description of its functionality).
https://user-images.githubusercontent.com/4231472/175832885-bcd1b821-c263-4d83-a4a6-daedf8e5f79f.mov
Checklist
- [x] The title of my PR starts with a Conventional Commit prefix (
fix:,feat:,docs:etc). - [x] I have read the Contributor Guide and followed the process outlined for submitting PRs.
- [x] I have updated/added tests for ALL new/updated/fixed functionality.
- [x] I have updated/added relevant documentation in
docsand added dartdoc comments with///. - [x] I have updated/added relevant examples in
examples.
Breaking Change
- [-] Yes, this is a breaking change.
- [x] No, this is not a breaking change.
Related Issues
~~Prerequisite: #1781~~ Closes #1375
Hello, Is there an option to add a loading screen? If you have a scene having many objects can be useful to have it.
I realise this is in draft but I want to raise some discussion around this.
I like the idea of navigator inside Flame, but I wonder if this isn't falling a little bit into over engineering.
Flutter already provides a navigation system, it already provides a way to display dialogs, etc. We recommend users that build their UI using flutter, as flutter is a way more powerful tool for building UIs than Flame. So I am failing to see why someone would write dialogs, title screens, stage selection screens with Flame components rather than just going with Flutter.
With the content I am seeing in the docs, I fear that users will be "directed" to think that Flame can't be integrated inside a Flutter app, this is actually something that users always ask on our discord.
With that said, I still think we could have some sort of navigator for Flame as I see clear use cases for it, a fine example is a Pokémon/RPG like game, where you have the over world which is a top down game, and the battle screen, which is a turn based game. We could implement this with Flutter Widgets, but I definitely think it would be nicer and smoother if such transition is done directly on Flame, so a small navigation system, or component stacking system would be good here.
So my thinkings here are this:
- We can still have a full blown navigation system on Flame, but we need a super thorough documentation that mentions flutter navigation system, how flame is just a widget, how we recommend using Flutter to build pages, widgets, navigation etc.
- Simplifying this to be a simpler system, Like something that just stack components and allow them to be changed with nice transitions.
IMO the second option is the one I would go. Looking forward to hear your thoughts about this.
I realise this is in draft but I want to raise some discussion around this.
I like the idea of navigator inside Flame, but I wonder if this isn't falling a little bit into over engineering.
Interesting, I thought the other way around, that it will be very nice to handle screens and scenes in the same way in Flame no matter if it is a component or an overlay with Flutter components and from the review I've done previously I don't think that it was over engineered and that the user facing API was very simplistic.
With the content I am seeing in the docs, I fear that users will be "directed" to think that Flame can't be integrated inside a Flutter app, this is actually something that users always ask on our discord.
Is it? They usually ask the other way around right, whether a Flutter widget can be added to Flame?
* Simplifying this to be a simpler system, Like something that just stack components and allow them to be changed with nice transitions.
What is it that you'd want to remove from the current solution? The possibility to handle overlays from the navigator?
Agree that we should have good docs describing what the difference would be with this compared to using navigation from Flutter.
Interesting, I thought the other way around, that it will be very nice to handle screens and scenes in the same way in Flame no matter if it is a component or an overlay with Flutter components and from the review I've done previously I don't think that it was over engineered and that the user facing API was very simplistic.
The over engineer thing for me is that we have implemented a full blown navigation system for Flame, in a way, it feels to me that we are reinventing the wheel, as this is already ready on Flutter and we are adding it to Flame.
Is it? They usually ask the other way around right, whether a Flutter widget can be added to Flame?
Yeah, but that is part of the broad subject right? Where we end up explaining that you can't use widgets directly on Flame, but since Flame is a widget in the end, it can be composed into your Flutter App.
What is it that you'd want to remove from the current solution? The possibility to handle overlays from the navigator?
I think we could have a simpler API actually, like, a StackComponent, where you can Push and pop it, and it would change between the components with nice transitions and such, and let the navigation thing to Flutter itself.
The over engineer thing for me is that we have implemented a full blown navigation system for Flame, in a way, it feels to me that we are reinventing the wheel, as this is already ready on Flutter and we are adding it to Flame.
Flutter can't handle Flame "scenes", which is a concept that people ask all the time about and I think having scenes consistent with how you handle screens would be very nice for user experience instead of having to handle them separately. We are already today handling screens without interacting with the Flutter navigation, through the overlays API.
Yeah, but that is part of the broad subject right? Where we end up explaining that you can't use widgets directly on Flame, but since Flame is a widget in the end, it can be composed into your Flutter App.
Maybe, I've never seen a question that way around though and I don't think this will increase that confusion, because as soon as they have written GameWidget they should™️ know that they can use it in Flutter. 😄
I think we could have a simpler API actually, like, a StackComponent, where you can Push and pop it, and it would change between the components with nice transitions and such, and let the navigation thing to Flutter itself.
Isn't that pretty much what this is currently (with the exception that it'll be able to handle overlays too)? Navigator is a component already.
Flutter can't handle Flame "scenes", which is a concept that people ask all the time about and I think having scenes consistent with how you handle screens would be very nice for user experience instead of having to handle them separately.
Yes! That is why on my original comment I said that I like the idea of having this concept, and even gave the example of the pokemon use case, what I don't like is to suggest the user to do UI on Flame instead of Flutter.
Maybe, I've never seen a question that way around though and I don't think this will increase that confusion, because as soon as they have written GameWidget they should™️ know that they can use it in Flutter. 😄
I disagree, many people still asks about that even after writing their first game. I agree with you that it should be obvious, but I would still thread carefully on this.
Isn't that pretty much what this is currently (with the exception that it'll be able to handle overlays too)? Navigator is a component already.
I don't think so, looking at the Navigator component we have a lot of code managing routes, managing the concept of named routes, we also have a class to just represent a route, I am thinking on something more like this:
SceneManager( // I liked the name concept of Scene that you mentioned 😅
children: [
Overworld(),
],
);
Then from the Overworld component one could (assuming it has the parent mixin): parent.push(Battle()), from the Battle, one could just call parent.pop().
For scene transitions, the push and pop methods could receive an effect for example.
I think this API will cover most of the use cases we wold need on Flame and is waaaay more simple.
Yes! That is why on my original comment I said that I like the idea of having this concept, and even gave the example of the pokemon use case, what I don't like is to suggest the user to do UI on Flame instead of Flutter.
Maybe I misunderstood you then, but that only sounds like a documentation issue. :)
I disagree, many people still asks about that even after writing their first game. I agree with you that it should be obvious, but I would still thread carefully on this.
I don't know what secret communication channel you have with our users, but I haven't seen this on discord at least. 😄
(About whether the GameWidget can be used in the Flutter widget tree that is)
I don't think so, looking at the Navigator component we have a lot of code managing routes
It's less than 200 lines (without comments), I don't think that is a lot. 🤷
I think this API will cover most of the use cases we wold need on Flame and is waaaay more simple.
Let's skip the overlays discussion for now, since that isn't implemented in this PR anyways. But other than that, the suggested solution will only cover the most basic usecase right? What if you want to go directly from a battle to an already initialized inventory screen in the main game for example? The way you navigate to routes shouldn't have to be linear.
Maybe I misunderstood you then, but that only sounds like a documentation issue. :)
I don't think it is only a documentation issue, only the existence of the navigation system on flame could cause some sort of confusion. But I definitely think that documentation is a way to mitigate it (or at least most of it). That is why I added that as an option on my suggestion on my original comment.
I don't know what secret communication channel you have with our users, but I haven't seen this on discord at least. 😄 (About whether the GameWidget can be used in the Flutter widget tree that is)
I don't have a secrete communication channel 🧐 , the doubts I am mentioning is not specific about GameWidget in the flutter widget, but how Flutter widgets can be used alongside a FlameGame
It's less than 200 lines (without comments), I don't think that is a lot. 🤷
That isn't a lot, but the whole system is more complicated right? You need to have a route, that route will create the component, etc, I am not saying it is a complicated code to ready, but rather a complicated system. Or even better, I don't even think this is hard to use or hard to understand, I just think that for our use case, it could be even more simpler.
The way you navigate to routes shouldn't have to be linear.
Hmm, With the way I suggested, you don't need to have things linear, you could add components to the SceneManager on whatever order you like, didn't followed you on this one.
Hmm, With the way I suggested, you don't need to have things linear, you could add components to the
SceneManageron whatever order you like, didn't followed you on this one.
In your suggestion you say that we should remove named routes right?
Consider the following scenes; world, battle and inventory, where the inventory screen can be accessed from any route.
How would you go from the battle route to inventory when world was the last showed scene before battle? inventory has already been initialized in the usecase and should not have to be re-created.
What I mean is that with only push (without a named push) and pop you can only move linearly, which I guess is why the Route concept was introduced here.
What I mean is that with only push (without a named push) and pop you can only move linearly, which I guess is why the Route concept was introduced here.
I only mentioned pop and push because that was enough for my exemplification. We could have a replace that would be enough for that maybe?
But we could keep the idea of named routes, I am not against that, but I think it could be simpler, and it could be one of the options. I will try to write a small example of this.
I only mentioned
popandpushbecause that was enough for my exemplification. We could have a replace that would be enough for that maybe?
With replace only, you'd have to add the battle scene again when you want to go back from the inventory. And even with replace, how would you nicely use an already initialized component without having to go through the hassle of querying or saving the component to a variable?
But we could keep the idea of named routes, I am not against that, but I think it could be simpler, and it could be one of the options. I will try to write a small example of this.
If we have named routes, then it is pretty much like the implementation in the PR but without the route class?
I think the route class is a pretty good layer here, because then you can easily define filters and transitions for each route. Without the route class, one would have to pass in all that information when a new component is pushed as a route right?
I will try to write a small example of this.
Great, this will be much easier to discuss with some example code I think! :)
Alright, while I was thinking about the example, I think I finally managed to think this through better.
If we have named routes, then it is pretty much like the implementation in the PR but without the route class?
True, so here is my thoughts:
- We could change
NavigatorandRoutetoSceneManagerandScene. I feel that most of my issues if that how this would be confusing to users on when the user would use Flutter Navigation of the Flame ones. I think that Scene describes better on the flame context, Route reminds me of Page, and that is not really the case on the game. - We can keep named routes, I got your point and it makes sense, it is a nice idea to have them in a way that they can be kept alive. But I also like the idea to push "fresh" created components in the stack, so we could have a pushNamed, as well as push.
- On documentation we should make it clear about the difference between Flame's Scene Manager and Flutter's navigator, also I wouldn't use Title screen and things like that as an example for a scene on the docs, that fits better as a Flutter Page.
- (Optional but nicely to have): Named scenes could be a generic type instead on string, so people can use enums.
Thoughts?
Wow, that's a big discussion! I will be able to read it carefully later tonight, but for now here's my general thinking about why I thought the Navigator is needed.
So, let's suppose I'm making a some kind of an RPG game. At the outset, I'm sure there will be the following top-level elements there:
- a loading screen (not sure why, but it seems all games have a loading screen);
- the "main menu" where you can start a new game, or check out settings/credits/etc;
- the character creation screen, where you select the class, give your character a name and choose their look;
- maybe some kind of a cut-scene;
- the main world, where the character is going to kill monster and do quests;
- some inventory UI;
- a dialogue with NPCs UI;
- a trade UI;
- the world map where I can see my current whereabouts;
- etc.
Now, that's already quite a lot to handle, so how can I handle all of it?
Let's say, the world map - how can I implement it? The first instinct is to make an overlay. But overlay displays a widget, and I feel that the map will have a complicated visual that I would really rather custom-render with the Flame's toolset. Ok then, I could make it into a component and then just mount on top of the world when needed. Alright, but now what if my character gets attacked by monsters while looking at the map? I want the world to pause while the map UI is open (and pauseEngine is not gonna work, because it will pause everything, including the map). One way to accomplish this is to have the world as a child of another component, and that other component will control the passage of time for the world.
What about all those other screens? Let's take the "main menu" screen. Sure, doing layout in Flutter is very powerful, but the screen is more than just about the layout. I'd want the menu screen to have consistent custom look-and-feel, different from what Flutter's MaterialApp offers. I may need to use my custom buttons, custom text renderers, sprinkle everything with lots of animation and effects. In short, I'd prefer even the main menu to be done with Flame, even if it means that I need to do the layout manually.
Now, I understand that there could be different opinions about this and different preferences. But does not force its way of organization on anybody -- the developers are free to either use it or not use it. We'd definitely want to have good documentation outlining various approaches for organizing the high-level structure of the game, perhaps mentioning the pros and cons of each one.
Take your time @st-pasha , But like I mentioned on my comments, I definitely see value on this! I think my biggest concern is about the mix of contexts here. If you want you can just skip to my last comment. That one basically just adds some suggestions on what the PR already implements with just a few name changes and tweaks.
Just some thoughts about this:
different from what Flutter's MaterialApp offers
But you can totally customise it, that is one of the reason that we have SpriteButton widget for example :)
But I also understand people preferring to go with the UI totally done on Flame as there may be some cases where doing it on Flame will be easier, I just think that those will not be the common ones.
Alright, while I was thinking about the example, I think I finally managed to think this through better.
If we have named routes, then it is pretty much like the implementation in the PR but without the route class?
True, so here is my thoughts:
- We could change
NavigatorandRoutetoSceneManagerandScene. I feel that most of my issues if that how this would be confusing to users on when the user would use Flutter Navigation of the Flame ones. I think that Scene describes better on the flame context, Route reminds me of Page, and that is not really the case on the game.- We can keep named routes, I got your point and it makes sense, it is a nice idea to have them in a way that they can be kept alive. But I also like the idea to push "fresh" created components in the stack, so we could have a pushNamed, as well as push.
- On documentation we should make it clear about the difference between Flame's Scene Manager and Flutter's navigator, also I wouldn't use Title screen and things like that as an example for a scene on the docs, that fits better as a Flutter Page.
- (Optional but nicely to have): Named scenes could be a generic type instead on string, so people can use enums.
Thoughts?
Reworking this into a form of scene manager totally makes sense! That is quite often what you see in other game engines as well and in the context of what Pasha just mentioned that makes the most sense for me as well.
After reading the discussion more carefully, I'll try to summarize the points and suggestions raised:
-
There is a general consensus that this is a useful feature to have, though few questions / points of contention remain.
-
Are we reinventing the wheel here?
Yes, we are, though it's not that hard since we have a first-hand account of how this wheel is supposed to look like :) But really, there's nothing wrong with Flutter's
Navigator, except that it cannot navigate within the Flame's environment. So, we need our own navigator to do that.Note also that Flame's
Navigatoris supposed to be nestable -- one Navigator can be placed inside another. Thus, we're just continuing the tradition here, with our own specialized Navigator being used to navigate within the GameWidget's internal environment. -
Would the users be confused by this new Navigator?
There is a certain degree of truth to this. However, there is a second side to the same coin: once you realize that Flame has its own Navigator that is almost the same as the Flutter's Navigator, you also realize that you don't have to learn this new system because the knowledge of the Flutter's Navigator is perfectly transferable here. (Well, at least to people who know Flutter). Which is why, I think, it is good that we follow the Flutter's example here instead of trying to mimic Unity or UnrealEngine.
Still, a case could be made for renaming
Navigator->NavigatorComponent, so that the users would be able to easier distinguish the two navigators (esp. if they are used at the same time). -
What about renaming Route -> Scene?
To me, this would feel more confusing. To me, the word "scene" just sounds very BIG. When I hear "scene", I'm thinking along the lines of Scene = Page = Screen = World. However, the Route object is supposed to be much smaller: its job is just to build the corresponding scene/page, and possibly configure the transition affordances or some special effects. There is also the
Sceneobject in Flutter, but it's related to canvas rendering. -
Unnamed routes
At first, I didn't think these would be needed since we can just give names to all the routes. But then, thinking of Erick's example, I kinda see their usefulness too. For example, if a character engages in a battle with a monster and we want to bring up the battle UI, then that UI needs to be passed the argument which is the monster properties. One way to accomplish this would be to create the battle route on the fly and pass the
monsterargument to it. But there could be other solutions too, need to check what Flutter is doing in cases like this. -
Documentation
I feel there's a consensus that we ought to have a good documentation about which other navigation systems can be used and how, and also the comparison of their relative advantages and drawbacks.
you don't have to learn this new system because the knowledge of the Flutter's Navigator is perfectly transferable here ... Which is why, I think, it is good that we follow the Flutter's example here instead of trying to mimic Unity or UnrealEngine.
The system will be the same, just with a different name, so knowledge is still applicable, and we can mitigate any possible confusions with a good doc explaining that the scene system is heavily inspired by Flutter's navigation system.
The point of the Scene term is not to mimic Unity or Unreal, the point is that this is a term more suitable for a game engine. Godot also uses this term.
I'm thinking along the lines of Scene = Page = Screen = World.
The same goes for route imo, Route = Page = Screen = world. But for me, Screen we have one less ting actually, because I don't relate Scene to Page, btw Page is something that I find difficult to relate to game engine at all.
So I still feel that Scene would be a better name for this system. I can see that it can cause some confusions for Flutter devs (which can be mitigated with docs) coming to the engine but on the other hands, game devs coming to Flame from other engines may feel less confused and none of them will ever confuse it with the Flutter's navigation.
I'm not against the Scene term per se, it's a great term. But AFAIK, in all game engine the term Scene refers to a large arrangements of game objects. To be more concrete, this is how the Navigator is used in the example in this PR:
navigator = Navigator(
routes: {
'splash': Route(builder: SplashScreenPage.new),
'home': Route(builder: StartPage.new),
'level1': Route(builder: Level1Page.new),
'level2': Route(builder: Level2Page.new),
'pause': PauseRoute(),
},
initialRoute: 'splash',
),
);
So, it's reasonable to say that the start page is a scene, or that a level page is a scene. Thus, we could rename StartPage -> StartScene, Level1Page -> Level1Scene, and so on. However, a Route is not a scene: it's a descriptor of how to get to the scene.
Note also that I didn't want to store the destination pages (or Scenes) within the navigator, because it would have caused all those pages (scenes) to be instantiated when the Navigator is created, which is potentially costly. So instead we just store a list of routes -- sort of like an address book -- allowing each destination page (scene) to be instantiated only when and if it is actually needed.
Maybe there is a better term than "Route", but I don't think it's the "Scene".
Maybe there is a better term than "Route", but I don't think it's the "Scene".
I think that Route is fine, and I think that we can probably introduce a scene concept later (which basically is just an empty component at the moment).
I think this would benefit from #1799 . Where the Navigator would be an InheritedComponent. Similar to how Flutter deals with navigation.
I think this PR has no action items left, @spydon WDYT?
I think this PR has no action items left, @spydon WDYT?
If I remember correctly, there was a discussion about adding generics to the routes so that enums for example could be used, and then have a default implementation that just handled strings?
If I remember correctly, there was a discussion about adding generics to the routes so that enums for example could be used, and then have a default implementation that just handled strings?
My understanding was that because enums/strings have no common super-type, then using generics here would be counter-productive as it will lead for less type-safety (dynamic as the inferred type).
An additional problem with generic types is that it wouldn't be able to accommodate some of the existing features like generated route names.
My understanding was that because enums/strings have no common super-type, then using generics here would be counter-productive as it will lead for less type-safety (
dynamicas the inferred type).An additional problem with generic types is that it wouldn't be able to accommodate some of the existing features like generated route names.
Right, that was it, now I remember.
@st-pasha is there a plan for how to support routes opening overlays? I feel that is pretty important so that users aren't locked in to create menus etc as flame components.
Yeah, we have issue #1762 for that.
I am still not sure about the
Navigatorname, internally me and @spydon discussed a little bit and theRouterComponentname could also be a good fit and I think I like it more.Either way, gonna leave my approval in here.
For me both RouterComponent and NavigatorComponent are fine, but if you prefer RouterComponent maybe we should go with that then so that we can get this merged. :)