iyes_progress
got a rewrite/redesign with version 0.13 (for Bevy 0.15)
and there are significant changes to the API and semantics/behavior.
The main aspects of the redesign are as follows:
- Global progress does not clear every frame. It is now persistent by default.
- This plays nice with systems that don't run every frame (such as observers and systems with run conditions).
- Everything is now tracked per-state-type.
- This plays nice with complex state hierarchies where you might want progress tracking in multiple states at the same time.
- Unfortunately, it means you have to specify the state type as a generic everywhere (
::<MyState>
). It's not as bad as it sounds, I promise. ;)
iyes_progress
now takes responsibility for tracking the individual progress of each system (as well as custom entries), not just the overall value.- Every system's last reported values are stored and remembered, and the system can modify them whenever it likes.
- New syntax for managing progress:
- Systems can now take a special
ProgressEntry<State>
system param, which gives you access to a stored progress value you can update at any time. - The old way of returning a
Progress
/HiddenProgress
value from your systemfn
is still supported.
- Systems can now take a special
In 0.12, the ProgressPlugin
is per-state. You add an instance of the plugin
for every individual state you want to track progress in.
In 0.13, the ProgressPlugin
is per-state-type. You add one instance of
the plugin for your state type, and then you can configure progress tracking
and state transitions for individual states within it.
#[derive(States, ...)]
enum MyAppState {
#[default]
InitialLoading,
MainMenu,
GameLoading,
InGame,
}
#[derive(States, ...)]
enum MyNetworkState {
#[default]
Disconnected,
Connecting,
Connected,
}
// 0.12
app.init_state::<MyState>();
app.add_plugins(
ProgressPlugin::new(MyAppState::InitialLoading)
.continue_to(MyAppState::MainMenu)
);
app.add_plugins(
ProgressPlugin::new(MyAppState::GameLoading)
.continue_to(MyAppState::InGame)
);
app.add_plugins(
ProgressPlugin::new(MyNetworkState::Connecting)
.continue_to(MyNetworkState::Connected)
);
// 0.13
app.init_state::<MyState>();
app.add_plugins(
ProgressPlugin::<MyState>::new()
.with_state_transition(MyAppState::InitialLoading, MyAppState::MainMenu)
.with_state_transition(MyAppState::GameLoading, MyAppState::InGame)
);
app.add_plugins(
ProgressPlugin::<MyNetworkState>::new()
.with_state_transition(MyNetworkState::Connecting, MyNetworkState::Connected)
);
In 0.12, the overall progress was stored in a ProgressCounter
resource. You
could read the accumulated total progress from there. That resource was
inserted/removed when entering/exiting the state for which the ProgressPlugin
was configured.
In 0.13, the ProgressTracker<State>
resource stores all progress information
for states of the given type. You can read the accumulated total progress
from there. This resource is always available. It should not be removed. Upon
entering a state that was configured in the ProgressPlugin
, the values
stored inside are cleared.
// 0.12
fn get_progress(v: Res<ProgressCounter>) {
let visible_progress = v.progress();
let all_progress = v.progress_complete();
}
// 0.13
fn get_progress_my_state(v: Res<ProgressTracker<MyState>>) {
let visible_progress = v.get_global_progress();
let hidden_progress = v.get_global_hidden_progress();
let all_progress = v.get_global_combined_progress();
}
fn get_progress_any_state<S: FreelyMutableState>(v: Res<ProgressTracker<S>>) {
let visible_progress = v.get_global_progress();
let hidden_progress = v.get_global_hidden_progress();
let all_progress = v.get_global_combined_progress();
}
In 0.12, the usual way was to return a Progress
or HiddenProgress
value
from your systems.
// 0.12
fn my_system(/* ... */) -> Progress {
// do stuff ...
Progress {
done, total
}
}
And add it as follows:
// 0.12
app.add_systems(Update, my_system.track_progress());
Doing it this way is still supported in 0.13. The syntax for the system fn
is the same, but when adding it to the app, you must specify the state type
the progress should count towards:
// 0.13
app.add_systems(Update, my_system.track_progress::<MyState>());
The stored progress is no longer cleared every frame. The previous value you return is remembered and overwritten the next time your system runs. Thus, it is now OK to add run conditions to your progress-tracked systems.
We also provide a new method:
// 0.13
app.add_systems(Update, my_system.track_progress_and_stop::<MyState>());
This will add an internal run condition to stop your system from running any more after it has returned a progress value that indicates completion.
This is new in 0.13.
You can take a ProgressEntry<State>
system param, which gives you direct
access to a value stored in the ProgressTracker<State>
. This can give you
more flexibility, compared to returning progress values.
Each instance of this system param will create its own separate entry
in the [ProgressTracker
] resource. You can have multiple if you want
to manage multiple progress values from one system.
Note: this is a special system param type. It is not a resource (no need for
Res
/ResMut
).
// 0.13
fn my_system(
mut entry: ProgressEntry<MyState>,
// ...
) {
// Overwrite any previously stored values
// (this is what the old way of returning `Progress` would do)
entry.set_progress(7, 20);
entry.set_hidden_progress(1, 2);
// You can also set only the done/total individually
entry.set_done(7);
entry.set_total(20);
entry.set_hidden_done(1);
entry.set_hidden_total(2);
// For your convenience, there are also methods to add to the
// existing value.
entry.add_done(1); // we completed 1 more item, yay!
entry.add_total(1); // one more item pending to do...
entry.add_hidden_done(1);
entry.add_hidden_total(2);
}
You can just add these systems normally, no need for any special syntax:
// 0.13
app.add_systems(Update, my_system);
In 0.12, you could add some progress to the global ProgressCounter
via
the manually_track
method.
// 0.12
progress_counter.manually_track(Progress { done, total });
In 0.13, you can get an entry in the ProgressTracker
, where you can record
your progress values (using a ProgressEntryId
).
This is exactly what the previously-shown APIs do internally, but you can do it manually, if you want to store progress that is not tied to a specific system or system param.
Note: ProgressTracker
does not need mut
access.
// 0.13
fn thing(tracker: Res<ProgressTracker<MyState>>) {
// create a new unique ID
let entry_id = ProgressEntryId::new();
// we can now use our ID to manage an entry in the tracker
tracker.set_progress(entry_id, 7, 11);
tracker.set_hidden_progress(entry_id, 9, 11);
// Make sure you store your ID somewhere!
// If you lose it, you will not be able to update the
// associated progress values anymore!
}
In 0.12, there was the TrackedProgressSet
, which was automatically assigned
to all systems you add to your app with .track_progress()
. This made sense,
because returning Progress
from your fn
was the expected way of reporting
progress.
In 0.13, there is no such set. There is no way to automatically assign
a system set to systems using the ProgressEntry
system param or other
methods, so it would not make sense. If you want to have a system set to
identify all your systems that use progress tracking, you will have to make
one yourself manually.
The CheckProgressSet
(for the system where we check the overall progress and
trigger the state transition) is still available and works the same as before.
The general idea is the same. It works the same as before.
But you have to specify the states type as a generic type parameter.
// 0.12
fn add_assets(mut loading: ResMut<AssetsLoading>) {
// ...
}
// 0.13
fn add_assets(mut loading: ResMut<AssetsLoading<MyState>>) {
// ...
}