-
Notifications
You must be signed in to change notification settings - Fork 244
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Discussing Behavior Trees #12
Comments
Ok pushed the btree branch. I'm not fully convinced by the METADATA static field, but it's what I've come up with so far. |
Now all branch nodes have an optional attribute "deterministic" which defaults to true. // Copy BranchNode.METADATA, add no attribute and remove deterministic attribute
public static final Metadata METADATA = new Metadata(BranchNode.METADATA, new String[]{}, "deterministic"); or like that: // Create a new METADATA with no limit on the number of children and with no attributes
public static final Metadata METADATA = new Metadata(-1); |
If we are thinking about making an editor, XML will be fine with me. If we are using an XML syntax here, I don't think we should do
or even
How can we validate our XML then?
for attribute
With XML, everything is string, so how we can tell it's a "3" string and not a number with value of 3. Writing something like
is ugly 😄 About METADATA, I'm not sure what you are using it for here and why it is static.
The node will have a parameter map and set get method
Parser will call The time this node will be repeated is init param but the time the node did run is state. When we clone the tree, we clone only the params, not the data. |
Yes, we miss XML validation (which is always optional, anyways) but we can still verify if the document is well-formed.
Yep, it's really terrible :)
I think that the problem with JSON, is that you have to give everything a name and you're forced to use arrays. For example in
you must create an array named children and all those "Unfortunately" I have a meeting now. I'm going to reply the other stuff later. Anyways, at the moment my major intent is to find the best compromise among simplicity, usability and performance for such a transitory where users have to write trees by hand since no editor is (yet) available. And thanks for your valuable considerations, really. :) |
With JSON, I agree with you about the array notation being error prone and less readable. But it isn't true that you have to give everything a name
I still prefer something we can write by hands
I think it is easy enough to read, to write a parser or even to export from tools. |
Well, yes I meant that with JSON you're forced to use an array when the order is relevant since properties are unordered. Back to the inline format then!
By the way, the "times" attribute in BarkTask was just an example. I know it's recommended to use a limit decorator for things like that. :) |
I'm not sure about the "deterministic" attribute you use for the branch nodes. |
@implicit-invocation If the branch is non-deterministic the EDIT: |
I did look at the implementation. |
Hmmm.... why? Suppose you want to burn something.
The order you get matches and gasoline is irrelevant but you need both. Am I missing something? |
Oh, I missed that case.
I usually use sequence in cases that the entity checks for a condition before executing an action. (I use task for condition) And in your case where the order is less relevant, I'm thinking of a Probability selector
We can even rank the node for a soft order, GetMatches and GetGasoline will have a same weight then. |
Yeah the use of a deterministic sequence whose first child is a condition task in the most common case. Anyways, in order to make the file format easier for both the parser and the user I'm thinking of something like that:
This way all the lines of the file have the same format:
Currently I'm rewriting the parser with Ragel which is a bit hard to learn when you're new to it, but it's really cool because is very fast and most importantly is cross-platform, meaning that external non Java tools can easily use similar code to load behavior trees as long as Ragel supports that particular programming language. |
@implicit-invocation
where:
Sample tree: https://github.com/libgdx/gdx-ai/blob/btree/tests/data/dog.tree Currently METADATA is still used, but I'm open to use a different approach. :) |
Wow, you are fast. |
Eh I had just to learn how to use Ragel and add JSON-like values, the rest is a mix of your old parser and my xml parser. |
Added clone capability. |
Added behavior tree library, subtree reference nodes and tests. @implicit-invocation |
Ooh, Ragel looks fun to use. I should keep it in my back pocket. |
@davebaol |
@implicit-invocation
Yes, METADATA is static because contains no runtime information. Actually, it is only used by the parser to emit errors in case an attribute doesn't exist or the number of children in a node is wrong. |
Oh, I misunderstood it. So there will be no logic concerning the METADATA. |
Yeah, METADATA contains the name of the attributes, not their value. So it's static final and conceptually immutable too (no setters, and its fields are package private). |
@implicit-invocation |
Last release for gdx-ai was 1.3.1, so you'd only need to go to 1.3.2. |
Yeah @Tom-Ski, but a lot of stuff has been added since 1.3.1, namely Steering Behaviors and Behavior Trees. |
@davebaol please go ahead, I don't have anything to add yet. |
@implicit-invocation BTW, I'm thinking of renaming
Hope you don't mind :) |
Well, I think renaming |
TBH I don't see the problem. Usually the one who creates a tree (not necessarily the developer) is used to think in term of actions and conditions which are the leaf tasks. The same is true for the developer. |
Also if I got it right the static priority
acts like the more verbose tree below
with the difference that the latter always returns |
I've just realized that, at the API level, we can take into consideration the idea to add a guard to the For instance, in absence of a priority branch parent, the tree below
can be expressed simply by
Also, task's guards are optional, meaning that they implicity evaluate to What do you think? @implicit-invocation |
BTW, since guards are nothing more than good old tasks, guarding the guard would come for free.
or maybe
This would provide a simple inline syntax for guard sequences, which are a viable alternative to a guard tree whose root is a sequence. As long as you use short guard sequences (for instance, 0 to 4 guards) this construct remains pretty readable. But there's nothing - apart from common-sense, of course - stopping you from guarding dozens of guards. To keep the logic simple for the user (and the implementation simple for me LOL) I'd just impose the limitation that guards can't run over multiple ticks, meaning that they must terminate with |
I got task guards and dynamic priorities working at the API level just like described above. Also, since any task can have a guard, I think that a specific task for
Since guards can be either something simple like a single task or something complex like a whole tree, I'm convinced that AI designers and developers will really benefit from such a feature. |
Dropping by from the Reddit thread ... Interesting that you got the guards working already. I'm not sure I explained the priorities idea properly though, so let's try again, this time with an example. Boring old Java code stuff. First, a leaf task which supplies its own priority.
This one calculates some priority for another task (or branch)
Let's put it all together. Priorities are in parentheses before the tasks. Default priority is 0.
Now, if the priorities for food or shelter search are above those for playing a happy animation, the one with the bigger need (= priority) gets used. If the need for both isn't that great, the happy animation plays, and the whole task succeeds. If they are great, but fail, PlayHappyAnimation gets played anyway. The AI might be hungry and without a roof above their head, but it can at least still smile. |
I see your point and I think that my proposal is a superset of yours :) class CheckPriorityCondition extends LeafTask<MyBlackboard> {
public enum Need {
Food, Shelter
};
@TaskAttribute(required=true) public Need need;
public CheckPriorityCondition () {
}
@Override public Status execute() {
return needs(getObject(), need) ? Status.SUCCEEDED : Status.FAILED;
}
// No @Override; this method only exists for this task
public boolean needs(MyBlackboard blackboard, Need need) {
// Use whatever formula you want and determine whether the given need is actually needed or not
}
}
What do you think? |
Ok, so ... The idea with the guards is roughly the same for a static tree, though the And when you add another need, the formula needs to get updated as well, to pick whichever need is the strongest and if it is the one we're checking for, and if all the needs together are strong enough to not rather play a happy animation instead. And then you try to add a need at runtime, which means that you suddenly have to write And then you decide to add another bunch of potential subtrees, each of them evaluating the needs and other information to decide on priority, and each needing to know the exact formula for priority all the other, potentially many and changing at runtime branches use. Essentially: Replacing priorities (which each subtree can calculate for itself not caring what other subtrees are doing, or which subtrees there are in the first place) with guards requires that the guards are way more complicated and tightly coupled with each other. |
Here's an idea how guards and priorities can work together to their strengths: A "planning selector". Assume the selector has a bunch (more than 10, possibly more than 100) different possible branches. Assume we can also estimate the cost of executing each branch (either by querying each of them for the estimated cost, or via some external cost estimation class). The algorithm is thus:
|
To deal with changes at runtime, should we just allow referencing callbacks/functors for attributes (with an additional paramater in the annotation as well)? @davebaol great work 👍 I've been through some kind of crisis 😄 fortunately I'm alive again |
Sounds like a nice idea for future extensions.
Nice to hear it. Welcome back! 😃 @MartinSojka
|
Done! |
@davebaol, sorry to hijack the thread, but how's the best way to do a "while" loop using behavior trees? I want to iterate over a list of possible targets and apply a selector over them. UntilFail and UntilSuccess won't resolve the problem since they can possibly end on a infinite loop. |
@scooterman
where
|
@davebaol thanks for the reply. If processItem does run and I return success (meaning that, for example, I can execute an attack on the selected item, hence making sequence return true) untilFail will exit? I can let the iteration end but the ideal (in my case) would be to stop right after a successful sequence. |
@scooterman |
great, thanks for the clarification. |
in fact, it won't work @davebaol. take a look on this snippet:
Suppose I have iterated over all my enemies in range and haven't attacked any of them. How I'm supposed to exit this loop since selectEnemyInRange will return false and the condition too? Also it would be interesting have something like a "nop" task that wouldn't interfere in the current processing result, so I could use nextEnemyInRange on both selectors and sequences without having to invert it according to the logic. |
Well, you have to distinguish between end-of-iteration condition and in-range condition:
BTW, in your tree the first |
@davebaol sorry for annoying, if you prefere another conversation mechanism like irc just say 😄 The problem with this approach is that I lose information on one thing: if my attack succeed or not for that specific entity. My blackboard is the brain for one entity, and this tree is to check if I can attack another one in range. The ideal for this subtree was to shortcut or break the result of attackEnemy returning SUCCESS if one of it managed to attack, otherwise iterate over the next entity until exhausted. If exhausted and no one managed to attack, the subtree should return FAIL meaning that no enemy was found to attack. I could add a check after alwaysSucceed and then one after the untilFail but that's not composable, say I decide to implement a new verification for a different kind of operation. Imo this is looking like I'm trying to program procedurally which it's not desirable, but I can't see another way to do that without iteration, specially when starting to compose behaviors. This may be related to the priority discussion above, imo. |
|
@davebaol I ended implementing an "exhaust" branch that simplified this case. Do you have any interest on an pull request for this? |
@scooterman |
I don't know if it's the right place to post but I have some suggestions : First of all I think current API is complete specially with guards and dynamic guard selector, I personally always found a way to implement logic with provided generic tasks without hacking a re-code things (nice job BTW). My concern right now is about pooling strategy :
My conclusion is that pooling strategy is game specific : recycle whole trees, recycle tasks, don't recycle is a design choice. But we need a mechanism to allow individual tasks recycling though and I think using Poolable interface is the best choice we have. @davebaol What do you think ? Did you faced this problem ? I could provide a PR if you don't have time to implement it but I need your approval first. |
I've never needed to instantiate/destroy trees in game. I always do it during the initialization phase of the level, but I do recognize your use case. So, yes, PR is welcome. 😄
Sounds good to me
Yeah, this would be an interesting feature. Just notice that when you remove a child the parent task MUST update his own internal status accordingly. This operation is task-specific, of course.
Couldn't agree more |
I've opened this to discuss behavior trees API enhancements.
@implicit-invocation
Resuming discussion #4 (comment) ...
Not sure why you don't like the XML format, but I think that it has some advantages:
So I'm playing with the XML format just to see what can be done.
Currently I can successfully load a behavior tree from this file:
I added the "Import" tag to improve readability. It allows you to use the given alias in place of the fully qualified class name of the task. Actually Selector, Parallel, etc.. are predefined imports. Also, the "as" attribute is optional, meaning that the simple class name is used as the alias, i.e.
creates the task alias "BarkTask".
Also, I added task parameters, see urgentProb in CareTask and times in BarkTask.
The attribute value is parsed according to the type of the corresponding field of the task class. For example, urgentProb is a float and times is an int. Supported types are: int, Integer, float, Float, boolean, Boolean, long, Long, double, Double, short, Short, char, Character, byte, Byte, and String.
Of course, we can maintain both formalisms as long as they have the same structural features. I mean, unlike task parameters, imports are just a syntactic sugar so they are not mandatory for the inline tab-based formalism.
I think we can use a "btree" branch in order to experiment with BT improvements while keeping the master branch clean.
The text was updated successfully, but these errors were encountered: