bevyengine / bevy

A refreshingly simple data-driven game engine built in Rust
https://bevyengine.org
Apache License 2.0
35.23k stars 3.47k forks source link

Add an interface for `State` to easily define and enforce legal state transitions #12177

Open maxrdz opened 6 months ago

maxrdz commented 6 months ago

What problem does this solve or what need does it fill?

Currently, you can define a finite state machine like the following:

#[derive(States, Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
enum NewFSM {
    #[default]
    Idle,
    Walk,
    Walk2Swim,
    Swim,
    Swim2Walk,
    Drowning
}

When using this new State resource, transitions to all states defined are legal by default.

Below is a simple diagram of our new FSM. (excluding idle): image

The arrows in the diagram represent the legal transitions between states. In this example, you would not want to ever transition from the Walk state to the Drowning state, or vice versa.

Currently, to enforce these transition rules, you have to implement this logic manually at all states' enter/exit systems. This leads to the developer writing more of an "all over the place" solution to enforce these transition rules.

What solution would you like?

I'm actually still getting my feet wet in Rust, so I do not have a clear solution in mind.

Although, when thinking of a solution, a certain feature from strum does come to mind. The following sample shows how you can add custom properties to enum variants using strum:

use strum::EnumProperty;

#[derive(PartialEq, Eq, Debug, EnumProperty)]
enum Class {
    #[strum(props(Teacher="Ms.Frizzle", Room="201"))]
    History,
    #[strum(props(Teacher="Mr.Smith"))]
    #[strum(props(Room="103"))]
    Mathematics,
    #[strum(props(Time="2:30"))]
    Science,
}

Possibly could have a similar solution for declaring which enum variant a certain state can transition to. Then, when requesting to transition states, Bevy can enforce these rules by checking if the transition is legal based on the rules defined by the developer.

What alternative(s) have you considered?

As mentioned above, the current alternative solution is to manually write the necessary logic to enforce transition rules at all states' enter/exit systems.

maxrdz commented 6 months ago

I also wanted to suggest allowing initializing a new State without wrapping it in a resource.

Currently, the only way to initialize a FSM is through app.add_state::<StatesEnum>(); which is stored globally within the app and can only be initialized once. This makes it not possible to use Bevy's States to keep state of entities in the ECS per entity instance. Even though an alternative solution would be to use a third party crate, it would be a good component to provide to the developer within Bevy.