Question about Few Things and Thank you for supporting this! <3 #13
Replies: 3 comments 19 replies
-
First of all, thanks for the kind words! I'm glad to see this tool is helping someone and I'm doing my best to improve it and address everything. As for you first question, in the next version I'm going to switch to the _physics_process and _process functions, because what I did there doesn't make much sense :) About your problem, right now the way to leave a node running is not to call run(), which is called automatically at each tick(by the way, running() is to check whether it is running), but to yield() for something. With that being said, here are a few alternatives to make this work: In this case, we consider reaching the goal a success, and not reaching it a failure. go_to_position.gd extends BTLeaf
var ag: Agent # Useless optimization
var destination: Vector2 = Vector2(85, 190)
func _tick(agent: Node, blackboard: Blackboard) -> bool:
ag = agent
ag.move_towards_position(destination, blackboard.get_data('delta'))
if ag.global_position.distance_to(destination) <= 50:
return succeed()
else:
return fail() EDIT: The above example allows you to do something cool. You can put this leaf under a "bt_repeat_until" and set it until success. or, let's change the way we think of it a little bit. The action is successful if you move, not if you reach the goal. agent.gd func move_towards_position(target_position: Vector2, delta: float) -> Vector2:
print(target_position)
var difference = (target_position - position).normalized()
velocity = move_and_slide(difference * speed, Vector2.UP)
#velocity.normalized() # I'm not sure what the purpose of this is, it return a new Vector2 but it is not assigned to anything.
return velocity go_to_position.gd extends BTLeaf
var ag: Agent # Useless optimization
var destination: Vector2 = Vector2(85, 190)
func _tick(agent: Node, blackboard: Blackboard) -> bool:
ag = agent
var velocity = ag.move_towards_position(destination, blackboard.get_data('delta'))
blackboard.set_data("distance_to_destination", agent.global_position.distance_to(destination))
if velocity.length() > 0.1: # If we moved
return succeed()
else:
return fail() So what? Now you know the distance to the destination, so after the agent moved (successfully) you can have a conditional node (a decorator) where you say if distance <= 50 execute whatever you have to execute. I think the last option makes the most sense, because why setting a leaf to a running state if it's not running? You are gonna call it every frame, so it is gonna run and complete successfully at each frame. The action is to "take one step", not to reach the goal, so it is successful at each step :) Moreover, another reason why your leaf cannot be running, is that you are doing a move_and_slide() call at each frame. By definition, if the leaf is running(), it will not be ticked, because the tree suspends execution while it is running. This is wrong because you do wanna tick it at every frame, and thus you wanna either succeed() or fail() at each tick() call. (I'm assuming you know how to use yield, but if you don't I suggest you to check the documentation in the GDScript Basics page, at 'Yield and Coroutines'. On top of that, I suggest you to check the two script templates I included, where you find an example on how to do all the things you might wanna do with leaves and decorators.) This whole process is gonna change in 1.2.0, where I plan to use the run() method for consistency and to even allow aborting running states, but until then this is how it works. I will try to make the future update so that it will not break your previous trees too much. I hope I clarified your doubts and if you need more support feel free to ask :) I'm open to suggestions and change proposals if they are requested! |
Beta Was this translation helpful? Give feedback.
-
First of all, I would like to thank you so much for replying as soon as possible. You are a life savior for real and yes I am aware of yields (pretty powerful coroutines really). So, I did read the whole thing and honestly the last one works pretty well to me due to separation of distance check and the whole move_towards_position thing. Happy to say, it's working great here. My bad about velocity.normalized() thing, it was from my main project and I had to cut out most of the code to paste it here for easy debugging - sadly forgot to take that off haha Honestly, I do like this idle_frame and physics_frame thing. It looks nicer but that is no surprise that many here are used to physics_process and process instead. However, I am happy either way and loving the fact that you are catering towards user experience more. Coming to think of it, how would you go about implementing patrol between x number of points?
Why the awful suggestion for the second AssignAnotherPoint was because the first AssignPoint keeps on repeating itself, so the current point keeps on changing. Let me quickly type out basic code for choosing a point from list change_points.gd extends BTLeaf
export (Array, NodePath) var waypoint
var curr_point = 0
func _tick(agent: Node, blackboard: Blackboard) -> bool:
curr_point = (curr_point + 1) % waypoint.size()
blackboard.set_data('current_point', get_node(waypoint[curr_point]))
return succeed() Also, is it weird to say I am hyped about 1.2.0 than Godot 4.0? - you are killing it with the updates. When do you think that's going to come and will it change fundamentally when supporting nodes? Although, that's not an issue. Logic anyways in every node stays the same for sure. So, it's hardly any problem haha. |
Beta Was this translation helpful? Give feedback.
-
The first thing I can suggest is to think of this in the opposite order, first check if it's reached, and move if it's not. _BTSelector (instead of sequence) If you look carefully I said reached or "null". You have a lot of freedom on this, but in general you may want to have some additional information (in this case, when the point is null) to know when to start the whole process. I've been developing this for months and sometimes I forget the reason why I did things. There reason why I didn't use process is because I wanted to stop execution, and process is called at every frame. But that can easily be fixed with set_process(false), which, incredibly, I didn't know of at the time lol. The visual editor is just gonna be on a higher level and won't change the actual logic (it will dynamically create a SceneTree). I'm flattered that you are waiting for the update, ha! Godot 4 will bring lots of crazy improvements that I'm looking forward to to improve the Behavior Tree. But I have a whole LOT of improvements and future versions planned, as I'm actively using this in my game and sharing the improvements as I go. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
So, I have been looking into different behavior trees and everything to jumpstart. This is probably the best I have seen so far and loving the constant support. I tried few behaviors with timer and it's pretty fun to see the flow right on the tree.
I will start with the questions:
Why did you opt for physics_frame and idle_frame approach than normal process and physics_process? Not going to lie, I am loving this and did learn a new thing. Are they even different from godot's own built in functions (idle = process, physics = physics_process)?
I created this custom patrol behaviour (it's barebones right now) but I keep getting assertion failed because of "not running()". The code is below.
go_to_position.gd
agent.gd
I am pretty sure it's my mistake, if so.. I would love to know fix and few pointers based on the script. You have been massive help to my workflow, I would love to continue using this.
Beta Was this translation helpful? Give feedback.
All reactions