Giter Club home page Giter Club logo

npbehave's Issues

Null check on Condition node works but not on BlackboardCondition node

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.

BlackboardCondition with Operator.ON_CHANGE

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,

[Question] Clock timer with repetitions

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.

GC/Performance problems

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

Randomized Nodes

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.)

[Proposal] Making it more "unreal"

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.

  • Composites can have Decorators and Services, can have childs
  • Tasks can have Decorators, no childs
  • Services, child
  • Root is just a Composite that doesn't have anything attached to it and can only have one task child ( see it more as an entry point to the BT )
  • Decorators and Services are also nodes, but they can only be part of a composite child ( which is explained further below ).

Imagine if you could stack all nodes that share same scope.
Instead of :

  • (Root)
    • (->)
      • (Wait)
        • (Service)
          • (Service)
            • (Repeater)
              • (Succeeder)
                • (->)
                  • (Wait)
                    • (=>) // Paralell

You could have this:

  • (Root)
    • (Wait) [->] (Service)(Service)
      • (Repeater) (Succeeder) [->]
        • (Wait) [=>]

In the end the structure for node class becomes like this :

  • Node
    • Composite
    • Task
    • Decorator ( pre-condition/effect )
    • Service ( post-node-execution )

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:

  • CompositeChild
    • Decorators[]
    • Node
    • Services[]

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.

It is not possible to handle player input because the tree cannot wait for some action while execute other in parallel

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;"))
    )
);

Creation Utility class

Can you add class that handles creation for nodes? That way instead of bunch of "new"
image
You would have this
image
Or even this
image

Infinite loop when two BlackboardCondition fire at once

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);
}
  • Memory is just a normal Blackboard.

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,

NPBehave 2.0 Changes - Wishlist

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:

Base C# Library

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.

Stops Rules

IMO one of the biggest flaws NPBehave has, is the Stops Rules and the aborting of nodes.

  1. Aborted nodes can influence the way the Behaviour Tree continues processing by returning 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)
  2. I would like to introduce an “Aborting” state inside nodes. Nodes that are requested to be aborted shouldn’t be forced to immediately finish their work, but allowed to block the flow until they are finished cleaning up ( sometimes you want to wait for some animation for example ). This could be quite problematic in NPBehave, so I will have to see if I can actually make it happen.

Still maintained?

Hey, is this library still maintained? Unity 2018/2019 supported?

NavMoveTo always fails when stopOnTolerance==true

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.

Naming nodes

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
image
With naming we would be able to explicitly say "this part of tree is handling steering"

A question about the source code of Clock.cs and Blackborad.cs

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.

simpleAI example problem

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 ?

Pausing the behavior tree

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.

Question about order of timers

In

public void ShouldListenToEvents_WhenUsingChildBlackboard()

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?

how to use BlackboardQuery ? I get confused. I need example, please.

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.List1[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?

Assertion in Wait action

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

Debugger Window not scrollable on OSX

@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

BlackboardCondition seems can't stop lower priority repeater node when using a child blackboard

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.