meniku / npbehave Goto Github PK
View Code? Open in Web Editor NEWEvent Driven Behavior Trees for Unity 3D
License: MIT License
Event Driven Behavior Trees for Unity 3D
License: MIT License
Children[currentIndex].Stop();
should become
Children[randomizedOrder[currentIndex]].Stop();
Hello, i am making a space fight game and i am using NPBehave to implement AI, however i encountered this weird issue.
So basically there is a "Target" entry in my blackboard and if it is null i want to select a new target, which i implemented as these:
new BlackboardCondition("Target", Operator.IS_EQUAL, null, Stops.IMMEDIATE_RESTART,
new Action(() => SelectANewTarget())
)
and
new Condition(() => Target == null, Stops.IMMEDIATE_RESTART,
new Action(() => SelectANewTarget())
)
Target value is GameObject, and i am destroying the ships with Destroy(gameobject). So when target ship is destroyed these implementations suppose to stop the lower nodes and start itself. While implementation with Condition node works implementation with BlackboardCondition does not work.
My test scene starts with every ship having null value for Target so when scene starts every ship finds a target, the thing is BlackboardCondition actually does it's job once, meaning that ships can take find their first target, but once that target is destroyed BlackboardCondition node doesnt restart itself. So i am guessing BlackboardCondition node cant observe that the value has changed.
If I have something like this :
new BlackboardCondition ("SomeThing,,, Looking forward to be changed", Operator.ON_CHANGE, Stops.LOWER_PRIORITY,
new Action(() => Debug.Log("Well,,, that something has been changed"))
)
Would fire with every loop( even though the key hasn't changed )
private bool IsConditionMet()
{
if (op == Operator.ON_CHANGE)
{
return true;
}
...
}
The expected behavior : Not to Fire unless the key really changed.
I thought that something like that would fix the issue :
protected override void DoStart()
{
if (stopsOnChange != Stops.NONE)
{
this.RootNode.Blackboard.AddObserver(this.key, OnValueChanged);
}
if (this.op == Operator.ON_CHANGE || !IsConditionMet())
{
Stopped(false);
}
else
{
Decoratee.Start();
}
}
But now it don't fire at all.
I am feeling that may be I am the one who don't get it's intended use.
Thanks,
I have a question about the clock behavior when used with repetitions. Don't know if it's intended behavior or not.
If I add a timer with a time of X seconds with repeats, the first time it waits X seconds, but after that it repeats every tick. Seeing your NavMoveTo script, I thought the time parameter was meant to be the update frequency.
I could solve the problem by adding a timer with no repeats every time the action is called, but if this is an issue, I'd rather fix this.
I'm using Unity 2019.2.01f and NPBehave latest version.
Hello Author
I tried to put a method which calls another component function into the mutiframe action,like this:
new Action((bool _shouldCancel) =>
{ if (!_shouldCancel)
{
MoveInComp(true);
return Action.Result.PROGRESS;
} })
The caller method defined like this:
private void MoveInComp(bool isStart)
{
GetComponent().isStart = true;
GetComponent().enemyPos = new Vector3(21f, 2f, 2f);
}
And the really working function is in Update() of MoveComponent component
void Update()
{ if (isStart == true)
MoveTowardsEnemy(enemyPos); }
public void MoveTowardsEnemy(Vector3 targetPos)
{ gameObject.transform.position = Vector3.MoveTowards(gameObject.transform.position, targetPos, this.speed); }
The running result in Unity is gameObject moved to the targePos just in One frame. My intent is to let it move in multiframe slowly.
How to do it then ?
//////*******//////
So I tried another way.
I didn't use Update() in MoveComponent this time. I just call the plain MoveTowardsEnemy() method.
public void MoveTowardsEnemy() //in MoveComponent
{ if (isStart == true && enemyPos != Vector3.zero)
gameObject.transform.position = Vector3.MoveTowards(gameObject.transform.position, enemyPos, this.speed); }
In behavior tree caller is like this:
private void MoveInComp(bool isStart)
{
GetComponent().isStart = true;
GetComponent().enemyPos = new Vector3(21f, 2f, 2f);
GetComponent().MoveTowardsEnemy() ;
}
But the result is same, gameObject moved to the targePos just in One frame.
Please give me some advice. Thanks
It would be really handy to have nodes such as "Random Selector" or "Random Sequencer". They would be helpful for creating more non linear enemy behaviours. Especially for bosses.
(For clarity, by "Random Selector" I mean one where nodes are checked in random order and by "Random Sequencer" I mean one where nodes are sequenced in random order.)
Everytime i run my game NPBehave has the TimeScale on 0. is there a setting where i can set the start timescale?
One of the things I was trying to do myself is to replicate the way Unreal did their behaviour trees.
Since the structure was too different and I had the urge to use UniRx, I made my own repository and tried replicating it basing on how they did it in their SDK. I never managed to finish it but the structure and code is there, never got to implement UniRx either since I realized that it wouldn't benefit at all in the end.
What I found really interesting is the way they structure their trees.
Notice how service sticks underneath their composition node? Or how decorators are always above either composites and tasks?
Seeing how this looks:
https://camo.githubusercontent.com/40566eaf7697442c61b3f4079f70a83d479f51ab/687474703a2f2f6c6162732e6e6b7565626c65722e64652f6e706265686176652f696d616765732f4e504265686176652d416e696d2e676966
I can't help but to think it could be better, and by better I mean by changing the way it's structured.
The way they do it is not that hard.
Imagine if you could stack all nodes that share same scope.
Instead of :
You could have this:
In the end the structure for node class becomes like this :
The difference is that most logic is executed in the composites themselves, they're the ones who are active/inactive and know which child is currently active - not the task itself.
The children in the composite are stored with a struct CompositeChild
, which looks like this:
In order to execute the node you need to pass through its decorators, and in order to execute its services you need to execute the node. All this done by the composite parent.
Details aside, I really thought it through and it makes sense to structure it like that.
Seeing that Unreal kept it this way for so long with no other change can only mean that it works.
What do you think of it?
I know it's a major change but it would look totally awesome and the behavior trees will be more readable.
Anyhow, look up their docs and tell me if you agree with this. I think we could create something powerful.
I was trying to build a character controller script using NPBehave.
I have a blackboard with a key called "isMovePressed" that turns true or false depending on player input.
So i used the BlackboardCondition to set the animator key to true of false if the "isMovePressed" value is updated.
The problem is that the Parallel node only keeps executing its children if a SUCCESS or FAILED status is returned by the children,
if i return PROGRESS the other actions will keep waiting and the rest of the tree will not work.
But if i return SUCCESS or FAILED the action DoStop() will never be called on the BlackboardCondition class and is not possible to set the animator "isWalking" boolean back to false when the condition fails.
So based on this i believe it is not possible to handle player input on this library, because the tree cannot wait for some action while execute other in parallel.
My question is if this is right or do i miss something?
return new Root(
blackboard,
new Parallel(
Parallel.Policy.ALL,
Parallel.Policy.ALL,
new BlackboardCondition(
"isMovePressed",
Operator.IS_EQUAL,
true,
Stops.SELF,
new Action(
(abort) =>
{
if (abort)
{
animator.SetBool("isWalking", false);
return Action.Result.SUCCESS;
}
animator.SetBool("isWalking", true);
return Action.Result.PROGRESS;
}
)
),
new Action(() => Debug.Log("ONLY RUN WHEN THE FIRST RETURN SUCCESS OR FAILURE;"))
)
);
Hi, :)
Unfortunately, I got no time to investigate it more.
But I am sure I did something wrong.
I have something like this :
new BlackboardCondition (Attacking.SHOULD_ATTACK, Operator.IS_SET, true, Stops.BOTH,
new Repeater (
new Succeeder (
new Sequence (
new Attacking(Entity),
new Idle(Entity, Idle.Duration.Short)
)
)
)
),
new BlackboardCondition (Alerted.SHOULD_ALERT, Operator.IS_SET, true, Stops.BOTH,
new Sequence (
new Alerted(Entity)
)
),
and in update :
if(PlayerDistance > 6)
{
Entity.Memory.Unset (Attacking.SHOULD_ATTACK);
Entity.Memory.Unset (Alerted.SHOULD_ALERT);
}
else if(PlayerDistance > Entity.Weapons.MaxRange())
{
Entity.Memory.Unset (Attacking.SHOULD_ATTACK);
Entity.Memory.Set (Alerted.SHOULD_ALERT, true);
}
else
{
Entity.Memory.Set (Attacking.SHOULD_ATTACK, true);
Entity.Memory.Unset (Alerted.SHOULD_ALERT);
}
in a certain point, Unity Editor crash
through debug, I found an infinite loop
I think it is related to : DoParentCompositeStopped
But I am not sure about it,,,
will dig into it more, but thought to ask,
Thanks,
Looking at how things work in NPBehave two years after I wrote it, I realise it has some flaws in the aborting/Stops Rules, that cannot be resolved without major changes to the API. That’s why I aim for a new major version.
If you also have suggestions, feel free to comment on this ticket.
My Suggestions:
As I currently don’t work in Unity, but in Monogame, I would like to split the NPBehave in a base C# library and have separate git-projects for each of the specific engines: Unity, Monogame etc.
IMO one of the biggest flaws NPBehave has, is the Stops Rules
and the aborting of nodes.
Succeed
or Failed
. This is a property of NPBehave that I like over UE4, where aborting a node results in the whole branch to be aborted. You can get UE4s way by a combination of IMMEDIATE_RESTART
and returning the right result in the aborted node, however the way it works right now is not very obvious. I would like to get rid of any IMMEDIATE_RESTART
versions of the Stops Rules, and give the control over what “aborting” actually means completely to the aborted node. In case a node gets aborted, the node could control the flow by finish with: Succeed
, Failed
or Aborted
(the latter is a replacement for IMMEDIATE_RETART but will always guarantee that the aborting decorator is restarted)Hey, is this library still maintained? Unity 2018/2019 supported?
I traced the bug and have my own fix, but thought I'd post it here in case it causes any unintended consequences.
Here's the offending code from NavMoveTo.cs lines 103-118, in moveToBlackboardKey():
// set new destination
agent.destination = destination;
bool destinationChanged = (agent.destination - lastDestination).sqrMagnitude > (DESTINATION_CHANGE_THRESHOLD * DESTINATION_CHANGE_THRESHOLD); //(destination - agent.destination).sqrMagnitude > (DESTINATION_CHANGE_THRESHOLD * DESTINATION_CHANGE_THRESHOLD);
bool distanceChanged = Mathf.Abs(agent.remainingDistance - lastDistance) > DESTINATION_CHANGE_THRESHOLD;
// check if we are already at our goal and stop the task
if (lastDistance < this.tolerance)
{
if (stopOnTolerance || (!destinationChanged && !distanceChanged))
{
// reached the goal
stopAndCleanUp(true);
return;
}
}
...
lastDestination = agent.destination;
lastDistance = agent.remainingDistance;
When agent.destination
is changed, agent
doesn't immediately update the path. Instead, it takes a frame or so to generate the new result (see: NavMeshAgent.destination documentation).
But immediately after changing the destination, this code is querying parameters that rely on the path having been generated. In particular, agent.remainingDistance
will return 0 if the path hasn't been generated yet. And it will always return 0 after changing the destination because the destination is changed only two lines before it.
At the end of this method, the following line is called:
lastDistance = agent.remainingDistance;
Since agent.remainingDistance
always returns 0 after the destination is changed, lastDistance
is also always set to 0 after the destination is changed.
That's fine for the first pass through this method. It's when the method gets called on the second tick that the problem starts:
lastDistance
will equal 0, since it was set on the last tick, before the path was ready. Which means that the conditional statement if (lastDistance < this.tolerance)
will always evaluate to true on that second tick, because 0 is always smaller than any tolerance. Then the next conditional, if (stopOnTolerance || (!destinationChanged && !distanceChanged))
will also always evaluate to true if stopOnTolerance = true
. These conditions cause the code to exit, apparently assuming that since the tolerance was met, then agent
must have reached its destination (but in reality it hasn't even started to move yet).
To fix this problem, I changed the following line of code:
lastDistance = agent.remainingDistance;
to:
if (agent.pathPending)
{
lastDistance = 99999999.0f;
}
else
{
lastDistance = agent.remainingDistance;
}
agent.pathPending
will return true
if the path isn't yet set, meaning that we shouldn't trust the value of agent.remainingDistance
, since it will return a false value of 0. So instead of using the unreliable agent.remainingDistance
to set lastDistance
, we instead set lastDistance
to its default starting value of 99999999.0f
.
That means that when we come around to the second tick, the conditional if (lastDistance < this.tolerance)
does NOT trigger accidentally and we do NOT prematurely exit the navigation.
I touched this asset again and I noticed big flaw when it comes to debugging. Currently we cannot name nodes like selector or sequence in easy way. This is key feature in my opinion.
Without this debugging is basically pointless
With naming we would be able to explicitly say "this part of tree is handling steering"
I have encountered a question while reading the source code. I'm not sure whether it's a bug or designed like that intentionally, so I just list it out here. If it's the latter case, I would be pleased if someone could tell me the intention behind it.
In methods AddObserver
and RemoveObserver
in Blackboard.cs, before add/remove a observer, it will check whether there is already a add/ remove request in addObservers
/removeObservers
. If so, this request will be ignored.
But in methods AddUpdateObserver
and RemoveUpdateObserver
in Clock.cs, before add/remove a observer, it does not check the same thing, which means that there could be two remove requests for the same observer.
So, what's the intention of this difference?
Thanks.
new Sequence(
// Do once
new NBAction(() => {
SetColor(Color.red);
})
{ Label = "Change to Red" },
// go towards player as long as playerDistance is less than 7.5 ( in that case, _shouldCancel is false )
new NBAction((bool _shouldCancel) =>
{
//_shouldCancel turns TRUE when playerDiscance is more than 7.5 => means " do I have to abort"
if (!_shouldCancel)
{
MoveTowards(blackboard.Get<Vector3>("playerLocalPos"));
//inform that this action is being performed until the condition changes => waits for distance to be greater (it will run else statement)
return NBAction.Result.PROGRESS;
}
else
{
//upon returning false starts the next Node in the Node[]
return NBAction.Result.SUCCESS;
}
})
{ Label = "Follow" }
,
new NBAction(() => {
Debug.Log("yellow");
SetColor(Color.yellow);
})
{ Label = "Change to Yellow" }
);
It is from simpleAI example. It is run inside a condition with Stops.IMMEDIATE_RESTART flag.
I don't quite understand why when I return SUCCESS in the second action it never runs the Change to Yellow action. If I change PROGRESS to SUCCESS it runs all 3 Actions constantly. Why doesn't it run the last action before restarting if we stop returning PROGRESS and return SUCCESS instead? I thought Sequence should finish all its actions if we don't return FAILED ?
In other words how would I add another condition before we restart ?
Hi there,
is there a way to stop the execution of the behavior tree temporarily and then start it again.
I have different kind of enemies which behaviors are controlled by behavior trees.
When they get hurt, I want to stop the behavior tree for a moment so that a hurt animation can be played.
In
NPBehave/Editor/Tests/BlackboardTest.cs
Line 96 in a1bc967
In Unity, although child blackboard notify is added to timer first, root blackboard notify is called first (Set branch 1 true).
But As far as I know, C# have no guarantee the order of the Map's keys.
Why this order is reversed?
Test above can be failed if the order of the keys is changed. Am I misunderstanding something?
Hello author!
Thanks for your sharing program first.
I tried to modifyEnemyAI example. I tried BlackboardQuery in Selector, like this:
new BlackboardQuery(keys, Stops.IMMEDIATE_RESTART, MyBoardQuery, new Sequence());
keys assigned like this:
private string[] keys = new string[2];
keys[0] = "OrcDistance"; //if Orc is near, run away.
keys[1] = "goblinDistance"; //if goblin is near, attack. both conditions should query simultaneously.
MyBoardQuery() method is defined like this:
private bool MyBoardQuery()
{
object o1 = behaviorTree.Blackboard.Get(keys[0]);
object o2 = behaviorTree.Blackboard.Get(keys[1]);
float f1 = (float)o1;
float f2 = (float)o2;
if ((f1 > 0.99) && (f2 < 5.99f))
return true;
return false;
}
but I get errors in Unity :
Assertion failed. Value was False
Expected: True
UnityEngine.Assertions.Assert:IsTrue(Boolean)
NPBehave.Composite:.ctor(String, Node[]) (at Assets/NPBehaveTest01/NPBehave/Scripts/Composite/Composite.cs:12)
NPBehave.Sequence:.ctor(Node[])
NPBehaveExampleEnemyAI:CreateBehaviourTree() (at Assets/NPBehaveTest01/NPBehave/Examples/Scripts/NPBehaveExampleEnemyAI.cs:32)
NPBehaveExampleEnemyAI:Start() (at Assets/NPBehaveTest01/NPBehave/Examples/Scripts/NPBehaveExampleEnemyAI.cs:13)
ArgumentNullException: Argument cannot be null.
Parameter name: key
System.Collections.Generic.Dictionary2[System.String,System.Collections.Generic.List
1[System.Action`2[NPBehave.Blackboard+Type,System.Object]]].ContainsKey (System.String key) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Collections.Generic/Dictionary.cs:458)
can only stop active nodes, tried to stop Sequence! PATH: Root/Service/Selector/BlackboardCondition/Sequence
Assertion failed. Values are not equal.
Expected: ACTIVE == INACTIVE
UnityEngine.Assertions.Assert:AreEqual(State, State, String)
NPBehave.Node:Stop() (at Assets/NPBehaveTest01/NPBehave/Scripts/Node.cs:111)
IndexOutOfRangeException: Array index is out of range.
NPBehave.Sequence.DoStop () (at Assets/NPBehaveTest01/NPBehave/Scripts/Composite/Sequence.cs:29)
NPBehave.Node.Stop () (at Assets/NPBehaveTest01/NPBehave/Scripts/Node.cs:117)
What's wrong ? Can you kindly enough to give an example of BlackboardQuery?
Hi, what is the purpose of the boolean parameter to the second overload of Task/Action node?
I am getting an assertion exception from within the Wait node in the Stop() method:
Assert.AreEqual(this.currentState, State.ACTIVE, "can only stop active nodes, tried to stop");
The node is nested under Condition with Stops.IMMEDIATE_RESTART
@Xerios Do you have an idea about this? The scrollbar is visible but there is no way of scrolling it. When clicking the "down" arrow it scrolls for 1 frame but immediately snaps back again
At first using the default blackboard and BlackboardCondition, it works. And then try to move the code into a node factory and use a shared blackboard. After some trial and error, found that if use a BlackboardCondition node and with a none default child board, even if set it to IMMEDIATE_RESTART and the condition is meet, the state would stuck in a lower priority repeater node, and swtich BlackboardCondition node to condition node, it works agin.
Hello, I'm shocked by your NPBehave.So I want to do something for him.Among them, visual editing behavior, Chinese documents (because I am a developer from China), please stay tuned!
I will finish it soon。
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.