JarkkoPar / Utility_AI_GDExtension

This repository contains the binaries and example project for the Utility AI GDExtension.
MIT License
72 stars 3 forks source link

Add signals to behaviors and actions #12

Open madeleineostoja opened 8 months ago

madeleineostoja commented 8 months ago

It would be really helpful if individual actions and behaviors emitted signals like entered, exited, action_processing, etc so that they could be dealt with in a decoupled fashion, rather than a big switch statement in the top level action_changed / behavior_changed signal

madeleineostoja commented 8 months ago

Realised I can extend UtilityAISTNode to emit signals on enter, exit, and tick to patch this pattern. Feels like this kind of basic signalling could be unified between all the different nodes? And in general could lean more on signals vs methods on inherited classes to be more godot-like

JarkkoPar commented 8 months ago

I can add these signals to the agent behaviours, I'll have to do a bit of thinking regarding the behaviour tree leaf nodes. The difference to the State Tree nodes will be that currently I'm not passing any user_data.

There's also an example scene, "making use of actions", where the action-node is extended with a script has methods start_action(actor) and end_action(actor) and also the method execute_action(actor, delta). The AI update code is then like this:

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
    # 1. Sense
    var dist_to_cursor = (get_viewport().get_mouse_position() - position).length()
    sensor_distance_to_cursor.sensor_value = dist_to_cursor / 1000.0
    sensor_wait_time.sensor_value = wait_time / 10.0

    # 2. Think
    ai.evaluate_options(delta)
    ai.update_current_behaviour()

    # 3. Act
    # This time we have extended the Action nodes with the logic,
    # and let them handle the entity update. Action is switched
    # in the signal handler below.
    if current_action != null:
        current_action.execute_action(self, delta)

# This is called when an action is chosen. We simply
# end the current action (if any) and start the new.
# The start does preparations needed to execute the
# new action and end any cleanup for the previous one.
func _on_utility_ai_agent_action_changed(action_node):
    if current_action != null:
        current_action.end_action(self)
    current_action = action_node
    if current_action != null:
        current_action.start_action(self)
        current_action_label.text = current_action.get_name()

This also achieves the decoupling. In the example scene I've used inherited scenes for the actions but that isn't really necessary, you just need to extend the action node with a script.

madeleineostoja commented 8 months ago

The difference to the State Tree nodes will be that currently I'm not passing any user_data.

That wouldn’t be needed, because the signals would be connected to the actor so you’d already have all the context you need. Which is another reason I think this pattern is cleaner than extending individual nodes

Fwiw an implementation of signals for state I really like is state charts (docs on state node events). Basically what I described above, leads to nice decoupled logic without anything dirtying up your core _process, lots of node scripts that need context passed around, or any huge switch statements

madeleineostoja commented 8 months ago

Also a seperate bug I can file an issue for if I get a chance to debug a little and provide more context — when trying to use the ST setup (for extending those leaf nodes with signals) my game crashed on startup if I had considerations under STNodes and evaluation method set to utility considerations. If I changed considerations to resources it stopped crashing but didn’t actually work.

I didn’t go any further because I just switched back to the standard agent, since it can do everything I would have the ST setup do. All on Mac if it helps

JarkkoPar commented 8 months ago

I've now added the following signals to the Agent Behaviours:

There are also behaviour_group/behaviour/action_exited-signals added on the agent.

For the STNodes I had a pointer I hadn't initialized to null, which caused the problems. That is now fixed. Thanks for letting me know about the bug!

I've attached MacOS builds, could you try it out and see if the signals work as you expected? Please do take a backup of your project first, and then you just need to copy the folders "libutilityai.macos.template_debug.framework" and "libutilityai.macos.template_release.framework" over the folders of the same name in the /addons/utility_ai/bin folder.

libutilityai.macos.template_20240114.zip

madeleineostoja commented 7 months ago

Hey sorry for the radio silence, work got crazy in the new year and I haven’t had time for gamedev. I’ll pull down the latest builds soon and let you know.

Great to see all those signals added in v1.6, makes structuring logic for behaviors and actions much more flexible. Also had a read of the updated documentation, much easier to follow 🙌🏼