KI des Roboters programmieren

Hi Roland,

ich werde später nochmal auf de running Geschichte zurück kommen, jetzt nur kurz hierzu:

Roland schrieb:
JEDE Node, also auch CCompositeNode oder CDecoratorNode, ist von CTreeNode abgeleitet und speichert sich ebenfalls auf dem Stack ab.

Nicht, wenn sie im running state ist und running nodes ausgeschlossen werden:

Code:
if (!IsRunning())
	{
		bb.AddRunningNode(this);


Die Sequence einer CCompositeNode node xxx -> running wäre:

> CCompositeNode->Tick()
+ CCompositeNode::CTreeNode->OnInitialize()
+ CCompositeNode->bb.AddRunningNode()
> CCompositeNode::OnUpdate()
> CCompositeNode::mChild(x)->Tick()
+ CCompositeNode::mChild(x)::CTreeNode->OnInitialize()
+ CCompositeNode::mChild(x)->bb.AddRunningNode()
> CCompositeNode::mChild(x)::OnUpdate()
< CCompositeNode::mChild(x)::OnUpdate()
< CCompositeNode::mChild(x)->Tick()
< CCompositeNode::OnUpdate()
< CCompositeNode->Tick()

Beide Nodes befinden sich auf dem Stack. Die Sequence Running -> Running überspringe ich mal weil sich da am Stack nichts ändert.

Die Sequence einer CCompositeNode node running -> xxx wäre:

> CCompositeNode->Tick()
> CCompositeNode::OnUpdate()
> CCompositeNode::mChild(xRun)->Tick()
> CCompositeNode::mChild(xRun)::OnUpdate()
< CCompositeNode::mChild(xRun)::OnUpdate()
+ CCompositeNode::mChild(xRun)->bb.RemoveRunningNode()
+ CCompositeNode::mChild(xRun)::CTreeNode->OnTerminate()
< CCompositeNode::mChild(xRun)->Tick()
< CCompositeNode::OnUpdate()
+ CCompositeNode->bb.RemoveRunningNode()
+ CCompositeNode::CTreeNode->OnTerminate()
< CCompositeNode->Tick()

Hoffe ich konnte das etwas erklären. Last Running Child ist dabei immer an oberster Stelle im Stack.

Gruss
Rajiva
 
Zuletzt bearbeitet von einem Moderator:
Hallo Rajiva,

Die Sequence Running -> Running überspringe ich mal weil sich da am Stack nichts ändert.

Der Stack bb.runningNodes wird nach jedem Durchlauf des BHTrees gelöscht und ist damit leer. Beim nächsten Durchlauf wird dieser wieder von root an bis zum letzten node aufgebaut und enthält somit den Pfad von root zur running node.
Da ich bb.runningNodes vor Löschen nach lastRunningNodes Stack kopiert habe, kann ich den Unterschied welcher Pfad beim letzten Durchlauf und gerade vollzogenem Durchlauf genommen wurde erkennen. Wenn sich der Pfad geändert hat, werden die nicht mehr aufgerufenen running nodes des letzten Durchlaufs beendet mit abort und bb.runningNodes werden dann wieder nach lastRunningNodes kopiert. Dann bb.runningNodes löschen und tree wieder aufrufen.

Da sich die nodes vom Stack entfernen, wenn onUpdate mit success oder failure beendet wird, sind im Stack immer nur die nodes des direkten pfades von root zur running node enthalten.

Da der Stack bb.runningNodes leer ist, verhindert folgende Zeile, dass eine running node wieder in den Stack bb.runningNodes aufgenommen wird.

Code:
if (!IsRunning())
	{
		bb.AddRunningNode(this);



Oder hast du die Routine irgendwie geändert, die dafür sorgt, dass nicht mehr aufgerufenen running nodes aborted werden?
 
Hi Roland,

Roland schrieb:
Der Stack bb.runningNodes wird nach jedem Durchlauf des BHTrees gelöscht und ist damit leer. Beim nächsten Durchlauf wird dieser wieder von root an bis zum letzten node aufgebaut und enthält somit den Pfad von root zur running node.
so ist es wohl implementiert, aber ich frage mich ob das so sein muss? Bei dieser Implementierung kann es 1. keine running Nodes geben die einen Durchlauf überleben und 2. kann auch immer nur eine Node den Status running haben. Genau diese Möglichkeiten machen den BHT doch aber so mächtig! In Deinen verlinkten Seiten steht hierzu:

The third means that success or failure is not yet determined, and the node is still running. The node will be ticked again next time the tree is ticked, at which point it will again have the opportunity to succeed, fail or continue running.

This functionality is key to the power of behaviour trees, since it allows a node's processing to persist for many ticks of the game.
Bezogen auf unser Elektroschaf bedeutet das, sofern ich hier nicht auf der falschen Spur bin, dass z.B. die Node "Beschleunigung" solange running sein kann bis die Zielgeschwindigkeit erreicht ist, oder eben ein anders Behavior dazu führt dass "Beschleunigung" in den failed Zustand fällt. Oder hab ich hier einen Gedankenfehler?

For example a Walk node would offer up the Running status during the time it attempts to calculate a path, as well as the time it takes the character to walk to the specified location. If the pathfinding failed for whatever reason, or some other complication arisen during the walk to stop the character reaching the target location, then the node returns failure to the parent.

Wenn ich das Verhalten in Deinem BHT nicht falsch verstehe wird jede running Node mit Abort abgebrochen, was IMHO doch nur bedeuten kann das auch der gestartete Vorgang beendet werden muss. Bezogen auf unser Schaf folgt der abgebrochenen "Beschleunigung" ein "Geschwidigkeit halten" oder gar "abbremsen", richtig?

Da ich bb.runningNodes vor Löschen nach lastRunningNodes Stack kopiert habe, kann ich den Unterschied welcher Pfad beim letzten Durchlauf und gerade vollzogenem Durchlauf genommen wurde erkennen. Wenn sich der Pfad geändert hat, werden die nicht mehr aufgerufenen running nodes des letzten Durchlaufs beendet mit abort und bb.runningNodes werden dann wieder nach lastRunningNodes kopiert.
Ja, das hatte ich mit "Liefert eine Node < der Running Node != BH_SUCCESS muss die später getickte Node abgebrochen werden weil die CSequenceNode ja auch abricht" zu beschreiben versucht. Hier braucht es IMHO aber keinen gesonderten Stack da die nicht mehr getickte running Node in dem Augenblick bekannt ist wo ein anderer Weg (Tree) eingeschlagen wird. Rein theoretisch müsste man die running Node noch nicht mal kennen weil einfach alles was danach nicht mehr getickt wird abgebrochen werden muss. Aus Performanzgründen macht es aber durchaus Sinn zu wissen ob sich im Restzweig ein running Child befindet.

Oder hast du die Routine irgendwie geändert, die dafür sorgt, dass nicht mehr aufgerufenen running nodes aborted werden?
Ich bin die ganze Zeit dabei den BHT umzudesignen :oops: , aber ja, wie oben beschrieben werden running nodes die wegen eines Richtungswechsels nicht mehr zum Zug kommen beendet. Das stimmt m.E. auch weil die Bedingung für running ja nicht mehr erfüllt sein kann.

VG
Rajiva
 
Zuletzt bearbeitet von einem Moderator:
Bei dieser Implementierung kann es 1. keine running Nodes geben die einen Durchlauf überlebe
Wenn ein running node erneut aufgerufen wird, sind beide Stacks identisch und es wird keine node aborted.

2. kann auch immer nur eine Node den Status running haben
Richtig. Parallel laufende running nodes werden nach meinem Kenntnisstand mit einer composite node realisiert, die die Zweige dann parallel aufrufen. Diese habe ich nicht programmiert, da ich keinen Anwendugsfall sah. Beispiel wäre im Spiel, Fahrstuhl fahren und parallel Musik laufen lassen.

Siehe auch: http://guineashots.com/2014/08/10/an-introduction-to-behavior-trees-part-2/ parallelization: BT can specify parallel nodes which run all children at the same time without losing the control of the model execution. This is possible because the parallelization is locally contained to the parallel node.


Bezogen auf unser Elektroschaf bedeutet das, sofern ich hier nicht auf der falschen Spur bin, dass z.B. die Node "Beschleunigung" solange running sein kann bis die Zielgeschwindigkeit erreicht ist, oder eben ein anders Behavior dazu führt dass "Beschleunigung" in den failed Zustand fällt.
Eine anders node führt nicht dazu, dass "Beschleunigung" in den failed Zustand fällt ausser es wird eine BlackBoard variable gesetzt, die "Beschleunigung" dazu veranlasst in den status faild zu gehen. Wenn die nun aktuelle node in den status running geht, wird zurück nach root gegangen und "Beschleunigung" einfach nicht mehr aufgerufen und verbleibt im status running. Daher setzte ich dieses zurück.

Wenn ich das Verhalten in Deinem BHT nicht falsch verstehe wird jede running Node mit Abort abgebrochen
Eine running node wird nur dann mit abort abgebrochen, wenn der Tree einen anderen Zweig aufruft und dann nach root zurückkehrt ohne nochmal die running node aufgerufen zu haben. Ansonsten beendet sich die running node selber wenn sie mit der Arbeit fertig ist.

Bezogen auf unser Schaf folgt der abgebrochenen "Beschleunigung" ein "Geschwidigkeit halten" oder gar "abbremsen"
Node "Beschleunigung" beendet sich selber, "Geschwidigkeit halten" geht in running und bleibt in running. Wenn nun der Bumper betätigt wird, aktiviert der Tree einen anderen Weg und "Geschwidigkeit halten" wird nicht mehr aufgerufen.

Hier braucht es IMHO aber keinen gesonderten Stack da die nicht mehr getickte running Node in dem Augenblick bekannt ist wo ein anderer Weg (Tree) eingeschlagen wird.
Wie findest du im Programm raus, welche node im runnig state ist, wenn ein andere Zweig aufgerufen wurde? Du musst dir das ja irgendwie merken. Normalerweise kannst du die letzte running node in eine variable abspeichern. Doch es ist nicht nur die eine node im running state, sondern alle nodes des direkten Pfades von root zur running node. Daher der Stack.

Wie bereits gesagt, wenn du einen einfacheren Weg findest als dies mit dem Stack zu machen wäre echt super. :) :) B) Ggf. könnte man auch den Nachteil umgehen, dass die running node beendet wird nachdem der neue Zweig aufgerufen wurde.
 
Hi Roland,

Roland schrieb:
Wie findest du im Programm raus, welche node im runnig state ist, wenn ein andere Zweig aufgerufen wurde?
ich glaube dass musst Du gar nicht rausfinden denn alle Nodes bis Root befinden sich in running und jede Parentnode kennt den Zustand seiner Childs. Ein Abort auf den Parent, sofern die Nodes über Ableitungen verknüpft sind, führt automatisch dazu dass auch die Child die Running sind aborted werden.

Als Beispiel:
Eine SequenceNode kam mit running zurück weil Child 5 mit running beendet wurde. Die SequenceNode weiß wie die MemSeqennceNode welcher Child als letztes getickt wurde. Sollte im nächsten Durchgang Child 3 mit failed beenden, Child 5 wird also gar nicht mehr getickt, weiß die SequenceNode selbst was zu tun ist, da braucht es IMHO keinen Stack da alle Objekte in sich seine childs kennen. Ich meine dass müsste im ganzen Tree so funktionieren und es reicht das Parent zu Aborten da der Parent weiß welcher Child usw. ebenfalls abortet werden muss.

sondern alle nodes des direkten Pfades von root zur running node. Daher der Stack.
Ich glaube eben dass der Stack schon durch die Nodes selbst vorhanden ist. Die Intelligenz steckt im Tree selbst und braucht keine externe Instanz.

Wie bereits gesagt, wenn du einen einfacheren Weg findest als dies mit dem Stack zu machen wäre echt super. :) :) B) Ggf. könnte man auch den Nachteil umgehen, dass die running node beendet wird nachdem der neue Zweig aufgerufen wurde.
Gib mir noch etwas Zeit um meine Erkenntnisse in die Tat umzusetzen. Mein Tree wird durch eine XML Datei aufgebaut und ich hab auch ein paar mehr Nodetypen definiert. Ob ich die am Ende alle brauche weiß ich noch gar nicht, aber da der Tree später über eine XML Datei beschrieben wird schaded das erstmal nicht. Ich hab leider auch nicht unbegrenzt Zeit für das Schaf, geht alles sehr gemäßigt vorwärts.

Grüße
Rajiva
 
Zuletzt bearbeitet von einem Moderator:
Hallo Rajiva,

die Idee die Intelligenz auf diese Art in den Tree zu Implementieren ist super. Vielleicht werde ich das auch umsetzen, wenn mein Robbi denn endlich mal mäht. Aktuell funktioniert meine Umsetzung ja einwandfrei, aber man ist ja immer bestrebt etwas zu verbessern und dazuzulernen.
Ich freue mich schon zu sehen wie du das umgesetzt hast.

Viel Erfolg dabei
Roland
 
Sorry, die PN werden scheinbar nur durch eine 1 auf der Webseite angezeigt. Die übersehe ich immer gerne. Besser wäre da ein Popup. Meine Mail gucke ich auch nicht ständig nach da diese ein Pseudonym ist. Daher habe ich dies nicht gesehen.
 
Hi Roland,

Roland schrieb:
Sorry, die PN werden scheinbar nur durch eine 1 auf der Webseite angezeigt. Die übersehe ich immer gerne. Besser wäre da ein Popup.
ja das ist hier sehr unglücklich gelöst. Meine das war im alten Board besser.

VG
Rajiva
 
Zuletzt bearbeitet von einem Moderator:
Hallo Rajiva,

anbei meine Umsetzung des BHT.
Der Tree wird in behaviour.cpp aufgebaut.
EdisonBehaviourTree.zip


Viele Grüße
Roland
Attachment: https://forum.ardumower.de/data/media/kunena/attachments/2936/EdisonBehaviourTree.zip/
 
Zuletzt bearbeitet von einem Moderator:
Danke Roland!
Sobald ich meinen NodeJs MowerIO Devicetreiber fertig habe werfe ich da mal einen Blick drauf. :)

VG
Rajiva
 
Oben