Skip to content

Commit

Permalink
Merge branch 'release'
Browse files Browse the repository at this point in the history
  • Loading branch information
Inspiaaa committed May 28, 2024
2 parents 5f0f7fd + eb84af6 commit d1b8659
Show file tree
Hide file tree
Showing 19 changed files with 1,019 additions and 124 deletions.
167 changes: 119 additions & 48 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,77 @@

---

## In Progress (2.1)

### Added

- **Remember last state**: This is a new parameter in the constructor of the `StateMachine` class that is interesting for nested state machines. When set to true, it makes the state machine return to its last active state when it enters, instead of its original start state. You can also use this feature in the `HybridStateMachine` class.

- **Run states in parallel**: The new `ParallelStates` class allows you to run multiple states in parallel. If `needsExitTime` is set to true, it will wait until any one of the child states calls `StateCanExit` before it exits. This behaviour can be overridden by providing a custom `canExit` function.

E.g.
```csharp
var attackFsm = new StateMachine();
attackFsm.AddState("Idle");
attackFsm.AddState("Attack");
// ...
fsm.AddState("A", new ParallelStates(
new State(onLogic: s => MoveTowardsPlayer()),
new State(onLogic: s => Animate()),
attackFsm
));
```

With a custom `canExit` function:

```csharp
fsm.AddState("A", new ParallelStates(
canExit: s => IsPlayerInRange(),
needsExitTime: true,

new State(onLogic: s => MoveTowardsPlayer()),
new State(onLogic: s => Animate())
));
```

- **Active State Changed Event**: The `StateMachine` class now has a new event that you can subscribe to that is triggered when its active state is changed:

E.g.
```csharp
fsm.StateChanged += state => print(state.name);

fsm.AddState("A");
fsm.AddState("B");
fsm.AddTransition("A", "B");

fsm.Init(); // prints "A"
fsm.OnLogic(); // prints "B"
```

### Improved

- Improved the performance of the `OnLogic` and the `Trigger` methods of the `StateMachine` class when states have multiple outgoing transitions. Depending on the number of transitions, when using string state names, this can make the `OnLogic` method up to 15% faster.

- The naming of the key / mouse transition classes has been improved by following the C# naming convention for events.
- `TransitionOnKey.Press` is now `TransitionOnKey.Pressed`
- `TransitionOnKey.Release` is now `TransitionOnKey.Released`
- `TransitionOnMouse.Press` is now `TransitionOnMouse.Pressed`
- `TransitionOnMouse.Release` is now `TransitionOnMouse.Released`

- Improved documentation.

### Fixed

- Fix incorrect execution order (timing) bug concerning the `canExit` feature of the `State` class.

- Fix `Time.time` access exception bug that occurred during the deserialisation of `State` and `State`-derived classes shown in the inspector.

- Fix incorrect output of `GetActiveHierarchyPath()` in the `StateWrapper.WrappedState` class.

---

## 2.0.1

### Fixed
Expand All @@ -17,53 +88,53 @@
### Added

- **Ghost states**: Ghost states are states that the state machine does not want to remain in and will try to exit as soon as possible. This means that the fsm can do multiple transitions in one `OnLogic` call. The "ghost state behaviour" is supported by all state types by setting the `isGhostState` field.

E.g.

```csharp
fsm.AddState("A", onEnter: s => print("A"));
fsm.AddState("B", new State(onEnter: s => print("B"), isGhostState: true));
fsm.AddState("C", onEnter: s => print("C");

fsm.AddTransition("A", "B");
fsm.AddTransition("B", "C");

fsm.Init(); // Prints "A"
fsm.OnLogic(); // Prints "B" and then "C"
```

- **Exit transitions**: Exit transitions finally provide an easy and powerful way to define the exit conditions for nested state machines, essentially levelling up the mechanics behind hierarchical state machines. Previously, the rule that determined when a nested state machine that `needsExitTime` can exit, was implicit, not versatile, and not in the control of the developer.

```csharp
var nested = new StateMachine(needsExitTime: true);
nested.AddState("A");
nested.AddState("B");
// ...
// The nested fsm can only exit when it is in the "B" state and
// the variable x equals 0.
move.AddExitTransition("B", t => x == 0);
```

Exit transitions can also be defined for all states (`AddExitTransitionFromAny`), as trigger transitions (`AddExitTriggerTransition`), or as both (`AddExitTriggerTransitionFromAny`).

- **Transition callbacks**: New feature that lets you define a function that is called when a transition succeeds. It is supported by all transition types (e.g. trigger transitions, transitions from any, exit transitions, ...).

```csharp
fsm.AddTransition(
new Transition("A", "B", onTransition: t => print("Transition"))
);
```

This feature is also supported when using the shortcut methods:

```csharp
// Can be shortened using shortcut methods:
fsm.AddTransition("A", "B", onTransition: t => print("Transition"));
```

The print function will be called just before the transition. You can also define a callback that is called just after the transition:

```csharp
fsm.AddTransition("A", "B",
onTransition: t => print("Before"),
Expand All @@ -72,18 +143,18 @@
```

- Support for **custom actions** in `HybridStateMachine`, just like in the normal `State` class:

```csharp
var hybrid = new HybridStateMachine();
hybrid.AddState("A", new State().AddAction("Action", () => print("A")));
hybrid.AddAction("Action", () => print("Hybrid"));

hybrid.Init();
hybrid.OnAction("Action"); // Prints "Hybrid" and then "A"
```

- Option in `HybridStateMachine` to **run custom code before and after** the `OnEnter` / `OnLogic` / ... of its active sub state. Previously, you could only add a custom callback that was run *after* the respective methods of the sub state. When migrating to this version simply replace the `onEnter` parameter with `afterOnEnter` in the constructor. For example

```csharp
var hybrid = new HybridStateMachine(
beforeOnEnter: fsm => print("Before OnEnter"),
Expand All @@ -93,28 +164,28 @@
```

- Feature for getting the **active path in the state hierarchy**: When debugging it is often useful to not only see what the active state of the root state machine is (using `ActiveStateName`) but also which state is active in any nested state machine. This path of states can now be retrieved using the new `GetActiveHierarchyPath()` method:

```csharp
var fsm = new StateMachine();
var move = new StateMachine();
var jump = new StateMachine();

fsm.AddState("Move", move);
move.AddState("Jump", jump);
jump.AddState("Falling");

fsm.Init();
print(fsm.GetActiveHierarchyPath()); // Prints "/Move/Jump/Falling"
```

- Option in `CoState` to **only run the coroutine once**. E.g.

```csharp
var state = new CoState(mono, myCoroutine, loop: false);
```

- Option in `TransitionAfterDynamic` to only evaluate the dynamic delay when the `from` state enters. This is useful, e.g. when the delay of a transition should be random. E.g.

```csharp
fsm.AddTransition(new TransitionAfterDynamic(
"A", "B", t => Random.Range(2, 10), onlyEvaluateDelayOnEnter: true
Expand Down Expand Up @@ -148,40 +219,40 @@
### Added

- Action system to allow for adding and calling custom functions apart from `OnLogic`.

E.g.

```csharp
var state = new State()
.AddAction("OnGameOver", () => print("Good game"))
.AddAction<Collision2D>("OnCollision", collision => print(collision));

fsm.AddState("State", state);
fsm.Init();

fsm.OnAction("OnGameOver"); // prints "Good game"
fsm.OnAction<Collision2D>("OnCollision", new Collision2D());
```

- Two way transitions: New feature that lets the state machine transition from a source to a target state when a condition is true, and from the target to the source state when the condition is false:

```csharp
fsm.AddTwoWayTransition("Idle", "Shoot", t => isInRange);

// Same as
fsm.AddTransition("Idle", "Shoot", t => isInRange);
fsm.AddTransition("Shoot", "Idle", t => ! isInRange);
```

```csharp
fsm.AddTwoWayTransition(transition);
fsm.AddTwoWayTriggerTransition(transition);
```

- `TransitionOnMouse` classes for readable transitions that should occur when a certain mouse button has been pressed / released / ... It is analogous to `TransitionOnKey`.

E.g.:

```csharp
fsm.AddTransition(new TransitionOnMouse.Down("Idle", "Shoot", 0));
```
Expand All @@ -197,36 +268,36 @@
- The "shortcut methods" of the state machine have been moved to a dedicated class as extension methods. This does not change the API or usage in any way, but makes the internal code cleaner. -> This change reduces the coupling between the base StateMachine class and the State / Transition classes. Instead, the StateMachine only depends on the StateBase and TransitionBase classes. This especially shows that the extension methods are optional and not necessary in a fundamental way.

- To allow for better testing and more customisation, references to the Timer class have been replaced with the ITimer interface. This allows you to write a custom timer for your use case and allows for time-based transitions to be tested more easily.

```csharp
// Previously
if (timer > 2) { }

// Now
if (timer.Elapsed > 2) { }
```

- As a consequence of the way the action system was implemented, generic datatype of the input parameter of `onEnter` / `onLogic` / `onExit` for `State` and `CoState` has changed. The class `State` now requires two generic type parameters: One for the type of its ID and one for the type of the IDs of the actions.

Previously:

```csharp
void FollowPlayer(State<string> state)
{
// ...
}

fsm.AddState("FollowPlayer", onLogic: FollowPlayer);
```

Now:

```csharp
void FollowPlayer(State<string, string> state)
{
// ...
}

fsm.AddState("FollowPlayer", onLogic: FollowPlayer);
```

Expand All @@ -247,21 +318,21 @@ Version 1.8 of UnityHFSM adds support for generics. Now the datatype of state id
- Support for generics for the state identifiers and event names

- "Shortcut methods" for reduced boilerplate and automatic optimisation

```csharp
fsm.AddState("FollowPlayer", new State(
onLogic: s => MoveTowardsPlayer()
));
// Now
fsm.AddState("FollowPlayer", onLogic: s => MoveTowardsPlayer());
```

```csharp
fsm.AddState("ExtractIntel", new State());
// Now
fsm.AddState("ExtractIntel");
```

```csharp
fsm.AddTransition(new Transition("A", "B"));
// Now
Expand All @@ -275,33 +346,33 @@ Version 1.8 of UnityHFSM adds support for generics. Now the datatype of state id
### Changed

- The datatype of the input parameter of `onEnter` / `onLogic` / `onExit` for `State` has changed. This is due to the inheritance hierarchy and the way generic support was added to the codebase while still trying to retain the ease of use of the string versions.

Previously:

```csharp
void FollowPlayer(State state)
{
// ...
}

fsm.AddState("FollowPlayer", new State(onLogic: FollowPlayer));
```

Now:

```csharp
void FollowPlayer(State<string> state)
{
// ...
}

fsm.AddState("FollowPlayer", new State(onLogic: FollowPlayer));
```

- States and transitions no longer carry a reference to the MonoBehaviour by default.

- Now the constructor of `StateMachine` does not require mono anymore => `new StateMachine()` instead of `new StateMachine(this)`

- The reference to mono has to be passed into the `CoState` constructor => `new CoState(this, ...)`

### Fixed
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ You can also add it directly from GitHub on Unity 2019.4+. Note that you won't b
- Select <kbd>Add from Git URL</kbd>
- Paste
- `https://github.com/Inspiaaa/UnityHFSM.git#upm` for the latest stable release (**recommended**)
- `https://github.com/Inspiaaa/UnityHFSM.git` for the development version
- `https://github.com/Inspiaaa/UnityHFSM.git#release` for the development version
- `https://github.com/Inspiaaa/UnityHFSM.git#v1.8.0` for a specific version (`v1.8.0` here)
- Click <kbd>Add</kbd>
- Tip: If you're using VSCode and you're not getting any IntelliSense, you may have to regenerate the `.csproj` project files (<kbd>Edit</kbd> > <kbd>Preferences</kbd> > <kbd>External Tools</kbd> > <kbd>Regenerate project files</kbd>)
Expand Down Expand Up @@ -258,6 +258,8 @@ fsm.AddState("FollowPlayer", onLogic: state => MoveTowardsPlayer(1));

Although this example is using lambda expressions for the states' logic, you can of course also just pass normal functions.

> **Side note:** To keep things simple, we're using strings for the state identifiers. Just keep in mind that UnityHFSM is not limited to this, as it allows you to use any custom type (e.g. enums) for the state identifiers. See the [generics](#generics) chapter for more information.
#### Adding transitions

```csharp
Expand Down
Loading

0 comments on commit d1b8659

Please sign in to comment.