flame-engine / flame

A Flutter based game engine.
https://flame-engine.org
MIT License
9.06k stars 892 forks source link

Implement "Loader" component #1776

Open st-pasha opened 2 years ago

st-pasha commented 2 years ago

Problem to solve

Often, a game needs some time to load its resources/assets. In this case some kind of a loading screen is necessary.

Currently, the only support from Flame for this functionality comes from the loadingBuilder parameter of the GameWidget. However, this solution may be insufficient for the following reasons:

Proposal

How exactly to solve this is TBD. Should this be a specialized Route within the Navigator? Or a mixin to a page? Or a page wrapper? Or a child?

I'd be interested to hear how people solve this problem right now.

nmarafo commented 2 years ago

Hello, I find your proposal very interesting. I currently use LoadingBuilder dividing the game into several phases loading the assets at the beginning of each one of them.

On the other hand, there is another question in Flutter regarding the delay that exists when loading the images inside a Widget, such as a Scroll. For this there is the precacheImage() function that has the problem that can cause the system to crash. https://twitter.com/NorbertoMartnAf/status/1295749275243216896

rivella50 commented 1 year ago

I'm not sure if a dedicated loading screen would be a good solution since it forces the developers to use that screen-based approach only. What about a fully styleable component which can be used (if wanted) on a loading screen (created and navigated to like all other screens with the new routing system) or anywhere else?

For not having tight coupling and therefore possibly having problems passing the loading progress to that new component i would use some event- or state-based approach (event_bus, ValueNotifier) if i had to develop that feature on my own.

ASGAlex commented 1 year ago

@st-pasha I just made a draft implementation ☝🏽 It now works only with Flutter widgets but could be extended to work with widgets too. This is pure stream and subscriber in proxy widget with user-friendly notifier for user-made widget... a bit tricky inside but allows to hide all mess under hood =) Is the approach is ok, I will make PR and some code refactoring of course.

it is unclear how to use the loadingBuilder (or whether it is even possible) to show loading progress when loading internal components, such as a particular level within the game.

In my case loading a new game level means creating and loading new game class instance with new parameters, so loadingBuilder is called automatically as it was at first time

st-pasha commented 1 year ago

So, the idea here was to specifically make a loader Component, as opposed to a loader widget. We already support loading widget -- via the loadingBuilder parameter -- though it doesn't accept progress indicator right now.

There are several reasons why we would want to have the LoaderComponent instead of (or in addition to) a loading widget:

In my case loading a new game level means creating and loading new game class instance with new parameters, so loadingBuilder is called automatically as it was at first time

This would not work in general: it requires everything before the loading screen to not be a Game, which may be applicable in some limited cases, but not in general. Ideally, we would want the solutions included in core Flame to be as general as possible.

ASGAlex commented 1 year ago

I just add here my own experience with loading progress with heavy calculations. In general you can't implement this feature and to keep backward compatibility because onLoad function going to become more and more blocking while amount of it's operations grows. No matter if you use sync API or something async like streams - widget's tree just do not rebuilds.

The thing that helps to solve the problem is moving initialization logic from onLoad to something like update. And dividing you loading process to small chunks that lasts no longer than 16ms. After every loading operation you check Stopwatch timer and execute next operation, if you have enough time. Otherwise you save the progress and allow Flame to complete game's lifecycle, Flutter rebuilds widget's tree then and in new update cycle you restores the progress and continue loading.