Hey all,
I thought I understood callables, but a use case I implemented is not working as I thought it would.
The idea is this; a base Ability class has a function, execute(), which takes a static set of arguments. When I want to make a new ability, I can extend that class into another, say MoveAbility, and make any number of execution loops which I can pass back as a callable to get that executed. The ability loop is called by its parent class in the _physics_process(), but is not relevant to the problem.
Ability Execute method;
func executing(_delta: float, _tile_grid: TileGrid, _pawn_spawner: PawnSpawner) -> void:
if is_executing:
current_state_callable.call(_delta, _tile_grid, _pawn_spawner)
When the execution starts, it get this one time initialization code;
func try_execute(_tile_grid: TileGrid, _pawn_spawner: PawnSpawner) -> Ability:
if can_execute(_tile_grid, _pawn_spawner):
is_executing = true
current_state_callable = targeting_move
return self
return null
If the ability can execute, it sets itself executing, points the current_state_callable to the target_move method, and it starts executing. Thus far, this all works; I get an execution look that works. A simple print("targeting move" in the targeting_move method showed this working.
Here is the targeting_move function, notice the signature is identical to the executing and all other function I intend to pass as a callable;
func targeting_move(_delta: float, _tile_grid: TileGrid, _pawn_spawner: PawnSpawner):
print("targeting move")
if _tile_grid.picked_tile:
var _target_path := _tile_grid.pathfinder.get_navigation_path(Tools.vec3_to_vec2(pawn.global_position), _tile_grid.picked_tile.map_position)
if Input.is_action_just_pressed("pawn_click_confirm"):
if _target_path:
path = _target_path
next_node = 0
# Check movement speed
if path.size() > pawn.stat_handler.get_movement():
final_node = pawn.stat_handler.get_movement()
else:
final_node = path.size() - 1
# Set execution callable
current_state_callable = executing_move
The final line here is the problem; I'm setting the current_state_callable (which at the time of this execution is "targeting_move", to a different function in the script. The whole function is not relevant, but here's the signature;
func executing_move(_delta: float, _tile_grid: TileGrid, _pawn_spawner: PawnSpawner):
It does not work. Regardless of triggering the state, or even manually changing it in the targeting_move method, the executing loop never swaps to the new callable. If I print out the callable at the end of the targeting_move function, it even prints the proper response;
Node(move_ability_script.gd)::executing_move
If I set the method in the original one-time set and put current_state_callable = targeting_move, then it works.
I am not understanding why switching the callable in the script does nothing. Any help appreciated!
Put the most simply, here's what I want to do;
var a_callable: Callable = do_something
func _process(delta: float) -> void:
a_callable.call()
func do_something():
print("doing something")
a_callable = do_something_else
func do_something_else():
print("doing something else")
a_callable = do_something
This should, in theory, print then vacillate between the two functions. No?
Here's the entire script unredacted for reference;
class_name MoveAbility
extends Ability
## --- Movement Flags ---
var path: PackedVector2Array
var next_node: int = 0
var final_node: int = 0
"""
Ability Checks
"""
func can_execute(_tile_grid: TileGrid, _pawn_spawner: PawnSpawner) -> bool:
return pawn.stat_handler.get_has_move()
func try_execute(_tile_grid: TileGrid, _pawn_spawner: PawnSpawner) -> Ability:
if can_execute(_tile_grid, _pawn_spawner):
is_executing = true
current_state_callable = targeting_move
return self
return null
"""
Action Loop
"""
func executing(_delta: float, _tile_grid: TileGrid, _pawn_spawner: PawnSpawner) -> void:
if is_executing:
current_state_callable.call(_delta, _tile_grid, _pawn_spawner)
func resolved() -> void:
reset_ability_state()
# Consume pawn move
## TODO make sure move is consumed
RESOLVED.emit()
func canceled() -> void:
reset_ability_state()
CANCELED.emit()
func reset_ability_state() -> void:
is_executing = false
current_state_callable = callable_template
func callable_template(_delta: float, _tile_grid: TileGrid, _pawn_spawner: PawnSpawner):
print("Null Callable Template Running")
func request_tile_effect(_tile: Tile, _effect: Effect) -> void:
REQUEST_EFFECT_APPLICATION_TO_TILE.emit(_tile, _effect)
func request_pawn_effect(_pawn: Pawn, _effect: Effect) -> void:
REQUEST_EFFECT_APPLICATION_TO_PAWN.emit(_pawn, _effect)
"""
Move Ability Functions
"""
func targeting_move(_delta: float, _tile_grid: TileGrid, _pawn_spawner: PawnSpawner):
print("targeting move")
if _tile_grid.picked_tile:
var _target_path := _tile_grid.pathfinder.get_navigation_path(Tools.vec3_to_vec2(pawn.global_position), _tile_grid.picked_tile.map_position)
if Input.is_action_just_pressed("pawn_click_confirm"):
if _target_path:
path = _target_path
next_node = 0
# Check movement speed
if path.size() > pawn.stat_handler.get_movement():
final_node = pawn.stat_handler.get_movement()
else:
final_node = path.size() - 1
# Set execution callable
current_state_callable = executing_move
print(current_state_callable)
func executing_move(_delta: float, _tile_grid: TileGrid, _pawn_spawner: PawnSpawner):
print("executing move")
var new_position: Vector3 = Vector3(
lerpf(pawn.global_position.x, path[next_node].x, 5.0 * _delta),
0,
lerpf(pawn.global_position.z, path[next_node].y, 5.0 * _delta)
)
pawn.global_position = new_position
if pawn.global_position.distance_to(Tools.vec2_to_vec3(path[next_node])) <= .02:
if next_node == final_node:
# Snap to final position
pawn.global_position = Tools.vec2_to_vec3(path[next_node])
# Move to resolving
current_state_callable = resolving_move
else:
next_node += 1
func resolving_move(_delta: float, _tile_grid: TileGrid, _pawn_spawner: PawnSpawner) -> void:
print("resolving move")
pawn.stat_handler.consume_move()
resolved()
current_state_callable = callable_template