Skip to content
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

Add a MinElement decorator #175

Open
Nordsoft91 opened this issue Jul 30, 2024 · 2 comments
Open

Add a MinElement decorator #175

Nordsoft91 opened this issue Jul 30, 2024 · 2 comments
Labels
enhancement New feature or request

Comments

@Nordsoft91
Copy link

Problem statement

It's quite frequent to iterate over arrays to find best element. For example, nearest target or weakest target. It's not quite convenient to do everything within one BTTask

Proposed solution

Introduce MinElement decorator, having one child task.

It accepts 3 variables

  • @export Array var: array to iterate
  • @export Element var: where to store element from array
  • @export Metric var: where child task must write a metric to optimize

Internal variables:

var _predicate_task: BTTask = get_child(0)
var _optimized_metric #metric to store
var _element_id: int #current array element
var _currently_best_element: int #best element

When decorator executed:

func _enter():
	_predicate_task = get_child(0)
	_element_id = 0
	_currently_best_element = 0

On tick

func _tick(delta) -> Status:
	var arr = blackboard.get_var(array_var)
	
	if _element_id >= arr.size(): #condition when all elements processed. 
		blackboard.set_var(element_var, arr[_currently_best_element])
		return SUCCESS
	
	blackboard.set_var(element_var, arr[_element_id]) #take next element
	
	var sub_status = _predicate_task.execute(delta) #execute predicate
	if sub_status == RUNNING:
		return RUNNING
		
	if sub_status == FAILURE: #stub
		return FAILURE
		
	var t = blackboard.get_var(metric_var) #get predicate output
	if _element_id == 0 or t < _optimized_metric: #compare metric
		_optimized_metric = t
		_currently_best_element = _element_id
	
	_element_id += 1
	return RUNNING

Alternatives

Could be also MaxElement added, or additional argument introduced to specify whether min or max element must be searched (in this case decorator can be called BestElement)

@Nordsoft91 Nordsoft91 added the enhancement New feature or request label Jul 30, 2024
@Nordsoft91
Copy link
Author

Realised, that it's actually needed to iterate over all elements within one tick

@limbonaut
Copy link
Owner

The purpose of this decorator is to combine it with a child task to find the "best" element in the array and store it on the blackboard, right? Not sure why it wouldn't be convenient to do this in a single task. It seems to me that this kind of a job would be best represented by something like array_reduce in Python. And Expression can be used to define the reduction logic (user-specified). To find the lowest value, the expression can be as simple as min(a, b).

ArrayReduce task specification:

  • Apply expression to elements of an array cumulatively, left to right, and store result on the blackboard
  • In other words, reduces an array to a single value (a sum, a minimum, a maximum, etc.)
  • Parameters
    • array_var -- specify blackboard variable that holds an array
    • expression -- Expression with a and b inputs; task is set as the base instance (so anything that can be used in a task, can also be used in this expression)
    • initial: BBVariant -- provide starting value for a (optional)
    • result_var -- variable to store the results of the reduction

To find the closest node in an Array[Node2D], the expression parameter could look like this:

agent.global_position.distance_squared_to(a.global_position) < agent.global_position.distance_squared_to(b.global_position) ? a : b

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants