#Today I realized the state tree is seemingly a pretty subpar implementation

1 messages · Page 1 of 1 (latest)

obtuse sun
#

Today I realized the state tree is seemingly a pretty subpar implementation that can't facilitate a basic degree of prioritized substate selection. I'm posting here in hopes someone can show me something I'm missing, for how to do what is a pretty basic ask of a state tree.

The core issue is

The state tree offers this nice selection of state selection options to facilitate dynamic substate selection, such as TrySelectChildrenAtRandom, TrySelectChildrenWithHighestUtility, TrySelectChildrenAtRandomWeightedByUtility

Under the hood, these modes attempt to enter all available child states, in some particular order, until one of them is successfully selected.

The problem is this. The **only **conditional determination of whether a child state can be selected, are conditionals. Whether the tasks on the substate can be successfully entered is not part of state selection, which means it's not part of the ordered selection behavior.

This might seem fine at a glance, but this is deeply problematic.

Suppose you had a subtree like so

  • MeleeCombat(TrySelectChildrenAtRandomWeightedByUtility)
    • Charge Attack
    • Lunge Attack
    • Special Attack

The expectation here is that these child states are considered in a priority order whether to run, and if they can't, it will ask the next one, going down the line until one can run. Simple right?

Logic around whether certain AI abilities can be executed often involves semi complex logic that doesn't fit a conditional. Things like EQS queries(but EQS queries are inherently incompatible with state selection because they're async).

Abilities often may need to do some spatial/environmental analysis like an EQS query to determine a location to move to in order to execute on this ability. This analysis would be done in a state tree task, not a condition. Conditions can't share result state. Property binding doesn't work with conditionals. And since Task.Enter() is called completely outside the scope of state selection, the state tree cant provide you the functionality you're looking for, in this incredibly common situation.

This suggests that the decoupling of task Enter() result from state selection is an inherently bad design flaw of the state tree execution model. A crippling design flaw even, considering that there's no support for conditions to act as data providers as an alternative in situations like this.

So what are the possible workarounds?

  • Just make it a conditional
    • Truly the only way out of the box for precondition state to be calculated and take part in **state selection ** is to be a conditional. A task won't do. They are Entered after the state is selected, outside the scope where returning EStateTreeRunStatus::Failed will cause the system to move down the selection list and ask the next best ability
    • The conditional could do the work calculating an attack position in each of these circumstances, but conditionals have no support for propagating resulting data, outside the boolean they return. Property binding excludes conditionals
  • I could violate good software design principals(like single responsibility, encapsulation) and have the conditional modify mutable state on its parent state by calculating the mutable data and setting it via a TStateTreePropertyRef<FAttackPositionResult>
    • Then you run into a secondary problem. EQS-like queries often need to be run at intervals to account for the dynamic positioning of the participants.
    • Conditionals can't run with configurable periodic intervals
    • So presumably you'd need to clone all this logic into a task that could refresh the state post activation
    • This leaves you with a conditional and task that do the same things, to alter mutable state on a property that probably lives on the parameter list of each state. This is cringe too.
    • What your left with is a Conditional_CalculateAttackPosition, and a Task_CalculteAttackPosition, and the state that they both calculate living outside them. This is bananas.
#

Is there another approach that checks all the expected boxes?

I am hoping I am just overlooking something, because this seems like such an egregious oversight, that it's hard to believe it exists.

#

The use case here is an extremely common one

You expect this state tree to consider each child state in some order, but if each state needs to do a bit of additional state calculation to determine whether it can run, you can't do that where you need to do it, a Task, where you would expect your Task::Enter() to return failed and the next child state to be considered.

  • MeleeCombat(TrySelectChildrenAtRandomWeightedByUtility)
    • Charge Attack
    • Lunge Attack
    • Special Attack