From 45cf74dca95f40ad83f5a828a74eb1cbc99f68cb Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Sun, 13 Aug 2023 17:34:34 +0200 Subject: [PATCH 01/16] Add BTPlayAnimation action --- bt/actions/bt_play_animation.cpp | 138 +++++++++++++++++++++++++++++++ bt/actions/bt_play_animation.h | 66 +++++++++++++++ config.py | 1 + register_types.cpp | 2 + 4 files changed, 207 insertions(+) create mode 100644 bt/actions/bt_play_animation.cpp create mode 100644 bt/actions/bt_play_animation.h diff --git a/bt/actions/bt_play_animation.cpp b/bt/actions/bt_play_animation.cpp new file mode 100644 index 0000000..d0d0a50 --- /dev/null +++ b/bt/actions/bt_play_animation.cpp @@ -0,0 +1,138 @@ +/** + * bt_play_animation.cpp + * ============================================================================= + * Copyright 2021-2023 Serhii Snitsaruk + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + * ============================================================================= + */ + +#include "bt_play_animation.h" + +#include "core/math/math_funcs.h" + +//**** Setters / Getters + +void BTPlayAnimation::set_animation_player(Ref p_animation_player) { + animation_player_param = p_animation_player; + emit_changed(); + if (Engine::get_singleton()->is_editor_hint() && animation_player_param.is_valid()) { + animation_player_param->connect(SNAME("changed"), Callable(this, SNAME("emit_changed"))); + } +} + +void BTPlayAnimation::set_animation_name(StringName p_animation_name) { + animation_name = p_animation_name; + emit_changed(); +} + +void BTPlayAnimation::set_await_completion(double p_await_completion) { + await_completion = p_await_completion; + emit_changed(); +} + +void BTPlayAnimation::set_blend(double p_blend) { + blend = p_blend; + emit_changed(); +} + +void BTPlayAnimation::set_speed(double p_speed) { + speed = p_speed; + emit_changed(); +} + +void BTPlayAnimation::set_from_end(bool p_from_end) { + from_end = p_from_end; + emit_changed(); +} + +//**** Task Implementation + +String BTPlayAnimation::get_configuration_warning() const { + String warning = BTAction::get_configuration_warning(); + if (!warning.is_empty()) { + warning += "\n"; + } + + if (animation_player_param.is_null()) { + warning += "Animation Player parameter is not set.\n"; + } else { + if (animation_player_param->get_value_source() == BBParam::SAVED_VALUE && animation_player_param->get_saved_value().is_zero()) { + warning += "Path to AnimationPlayer node is not set.\n"; + } else if (animation_player_param->get_value_source() == BBParam::BLACKBOARD_VAR && animation_player_param->get_variable().is_empty()) { + warning += "AnimationPlayer blackboard variable is not set.\n"; + } + } + if (animation_name == StringName()) { + warning += "Animation Name is not set.\n"; + } + + return warning; +} + +String BTPlayAnimation::_generate_name() const { + if (animation_name == StringName() || animation_player_param.is_null()) { + return "PlayAnimation ???"; + } + return vformat("PlayAnimation \"%s\"", animation_name) + + (blend >= 0.0 ? vformat(" blend: %ss", Math::snapped(blend, 0.001)) : "") + + (speed != 1.0 ? vformat(" speed: %s", Math::snapped(speed, 0.001)) : "") + + (from_end != false ? vformat(" from_end: %s", from_end) : "") + + (await_completion > 0.0 ? vformat(" await_completion: %ss", Math::snapped(await_completion, 0.001)) : ""); +} + +void BTPlayAnimation::_setup() { + setup_failed = true; + ERR_FAIL_COND_MSG(animation_player_param.is_null(), "BTPlayAnimation: AnimationPlayer parameter is not set."); + animation_player = Object::cast_to(animation_player_param->get_value(get_agent(), get_blackboard())); + ERR_FAIL_COND_MSG(animation_player == nullptr, "BTPlayAnimation: Failed to get AnimationPlayer."); + ERR_FAIL_COND_MSG(animation_name == StringName(), "BTPlayAnimation: Animation name is not set."); + ERR_FAIL_COND_MSG(!animation_player->has_animation(animation_name), vformat("BTPlayAnimation: Animation not found: %s", animation_name)); + setup_failed = false; +} + +void BTPlayAnimation::_enter() { + if (!setup_failed) { + animation_player->play(animation_name, blend, speed, from_end); + } +} + +int BTPlayAnimation::_tick(double p_delta) { + ERR_FAIL_COND_V_MSG(setup_failed == true, FAILURE, "BTPlayAnimation: _setup() failed - returning FAILURE."); + + // ! Doing this check instead of using signal due to a bug in Godot: https://github.com/godotengine/godot/issues/76127 + if (animation_player->is_playing() && animation_player->get_assigned_animation() == animation_name) { + if (get_elapsed_time() < await_completion) { + return RUNNING; + } else if (await_completion > 0.0) { + WARN_PRINT(vformat("BTPlayAnimation: Waiting time for the \"%s\" animation exceeded the allocated %s sec.", animation_name, await_completion)); + } + } + return SUCCESS; +} + +//**** Godot + +void BTPlayAnimation::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_animation_player", "p_anim_player"), &BTPlayAnimation::set_animation_player); + ClassDB::bind_method(D_METHOD("get_animation_player"), &BTPlayAnimation::get_animation_player); + ClassDB::bind_method(D_METHOD("set_animation_name", "p_anim_name"), &BTPlayAnimation::set_animation_name); + ClassDB::bind_method(D_METHOD("get_animation_name"), &BTPlayAnimation::get_animation_name); + ClassDB::bind_method(D_METHOD("set_await_completion", "p_time_sec"), &BTPlayAnimation::set_await_completion); + ClassDB::bind_method(D_METHOD("get_await_completion"), &BTPlayAnimation::get_await_completion); + ClassDB::bind_method(D_METHOD("set_blend", "p_blend"), &BTPlayAnimation::set_blend); + ClassDB::bind_method(D_METHOD("get_blend"), &BTPlayAnimation::get_blend); + ClassDB::bind_method(D_METHOD("set_speed", "p_speed"), &BTPlayAnimation::set_speed); + ClassDB::bind_method(D_METHOD("get_speed"), &BTPlayAnimation::get_speed); + ClassDB::bind_method(D_METHOD("set_from_end", "p_from_end"), &BTPlayAnimation::set_from_end); + ClassDB::bind_method(D_METHOD("get_from_end"), &BTPlayAnimation::get_from_end); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "await_completion", PROPERTY_HINT_RANGE, "0.0,100.0"), "set_await_completion", "get_await_completion"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "animation_player", PROPERTY_HINT_RESOURCE_TYPE, "BBNode"), "set_animation_player", "get_animation_player"); + ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "animation_name"), "set_animation_name", "get_animation_name"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "blend"), "set_blend", "get_blend"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed"), "set_speed", "get_speed"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "from_end"), "set_from_end", "get_from_end"); +} diff --git a/bt/actions/bt_play_animation.h b/bt/actions/bt_play_animation.h new file mode 100644 index 0000000..e20f015 --- /dev/null +++ b/bt/actions/bt_play_animation.h @@ -0,0 +1,66 @@ +/** + * bt_play_animation.h + * ============================================================================= + * Copyright 2021-2023 Serhii Snitsaruk + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + * ============================================================================= + */ + +#ifndef BT_PLAY_ANIMATION_H +#define BT_PLAY_ANIMATION_H + +#include "bt_action.h" + +#include "modules/limboai/blackboard/bb_param/bb_node.h" + +#include "core/object/object.h" +#include "scene/animation/animation_player.h" + +class BTPlayAnimation : public BTAction { + GDCLASS(BTPlayAnimation, BTAction); + +private: + Ref animation_player_param; + StringName animation_name; + double await_completion = 0.0; + double blend = -1.0; + double speed = 1.0; + bool from_end = false; + + AnimationPlayer *animation_player = nullptr; + bool setup_failed = false; + +protected: + static void _bind_methods(); + + virtual String _generate_name() const override; + virtual void _setup() override; + virtual void _enter() override; + virtual int _tick(double p_delta) override; + +public: + void set_animation_player(Ref p_animation_player); + Ref get_animation_player() const { return animation_player_param; } + + void set_animation_name(StringName p_animation_name); + StringName get_animation_name() const { return animation_name; } + + void set_await_completion(double p_await_completion); + double get_await_completion() const { return await_completion; } + + void set_blend(double p_blend); + double get_blend() const { return blend; } + + void set_speed(double p_speed); + double get_speed() const { return speed; } + + void set_from_end(bool p_from_end); + bool get_from_end() const { return from_end; } + + virtual String get_configuration_warning() const override; +}; + +#endif // BT_PLAY_ANIMATION \ No newline at end of file diff --git a/config.py b/config.py index 8ddd132..416f191 100644 --- a/config.py +++ b/config.py @@ -76,6 +76,7 @@ def get_doc_classes(): "BTInvert", "BTNewScope", "BTParallel", + "BTPlayAnimation", "BTPlayer", "BTProbability", "BTRandomSelector", diff --git a/register_types.cpp b/register_types.cpp index 80a3074..f684233 100644 --- a/register_types.cpp +++ b/register_types.cpp @@ -47,6 +47,7 @@ #include "bt/actions/bt_action.h" #include "bt/actions/bt_console_print.h" #include "bt/actions/bt_fail.h" +#include "bt/actions/bt_play_animation.h" #include "bt/actions/bt_random_wait.h" #include "bt/actions/bt_set_agent_property.h" #include "bt/actions/bt_set_var.h" @@ -142,6 +143,7 @@ void initialize_limboai_module(ModuleInitializationLevel p_level) { GDREGISTER_CLASS(BTConsolePrint); GDREGISTER_CLASS(BTFail); GDREGISTER_CLASS(BTNewScope); + GDREGISTER_CLASS(BTPlayAnimation); GDREGISTER_CLASS(BTRandomWait); GDREGISTER_CLASS(BTSetAgentProperty); GDREGISTER_CLASS(BTSetVar); From 9eb912816f0a94401b933197ee1c4ed9c4eedca8 Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Sun, 13 Aug 2023 19:38:14 +0200 Subject: [PATCH 02/16] Add BTPlayAnimation class doc --- doc_classes/BTPlayAnimation.xml | 34 +++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 doc_classes/BTPlayAnimation.xml diff --git a/doc_classes/BTPlayAnimation.xml b/doc_classes/BTPlayAnimation.xml new file mode 100644 index 0000000..c082822 --- /dev/null +++ b/doc_classes/BTPlayAnimation.xml @@ -0,0 +1,34 @@ + + + + BT action that plays an animation on the specified AnimationPlayer node. + + + BTPlayAnimation action plays an animation on the specified AnimationPlayer node. If the [member await_completion] is greater than [code]0[/code], the action will wait for the animation to complete, with a maximum waiting time equal to [member await_completion]. + Returns [code]SUCCESS[/code] if the animation finishes playing or if the elapsed time exceeds [member await_completion]. When [member await_completion] is set to [code]0[/code], BTPlayAnimation doesn't wait for the animation to finish and immediately returns [code]SUCCESS[/code]. + Returns [code]RUNNING[/code] if the animation is still playing and the elapsed time is less than [member await_completion]. + Returns [code]FAILURE[/code] if the action fails to play the requested animation. + + + + + + Animation's key within the AnimationPlayer node. + + + Parameter that specifies the AnimationPlayer node. + + + Duration to wait for the animation to finish (in seconds). + + + Custom blend time (in seconds). + + + Play animation from the end. Used in combination with negative [member speed] to play animation in reverse. + + + Custom playback speed scaling ratio. + + + From fb7ea31a49ade3f6c257876dfb5b7aae46b585b5 Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Mon, 14 Aug 2023 12:32:34 +0200 Subject: [PATCH 03/16] Add BTPlayAnimation icon --- icons/BTPlayAnimation.svg | 1 + 1 file changed, 1 insertion(+) create mode 100644 icons/BTPlayAnimation.svg diff --git a/icons/BTPlayAnimation.svg b/icons/BTPlayAnimation.svg new file mode 100644 index 0000000..d387bf6 --- /dev/null +++ b/icons/BTPlayAnimation.svg @@ -0,0 +1 @@ + \ No newline at end of file From abe1117055b6246e07d2f4b2a3a66351e6e2d3f6 Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Mon, 14 Aug 2023 12:43:12 +0200 Subject: [PATCH 04/16] Add BTStopAnimation action --- bt/actions/bt_play_animation.h | 1 - bt/actions/bt_stop_animation.cpp | 86 ++++++++++++++++++++++++++++++++ bt/actions/bt_stop_animation.h | 48 ++++++++++++++++++ config.py | 1 + register_types.cpp | 2 + 5 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 bt/actions/bt_stop_animation.cpp create mode 100644 bt/actions/bt_stop_animation.h diff --git a/bt/actions/bt_play_animation.h b/bt/actions/bt_play_animation.h index e20f015..eb25908 100644 --- a/bt/actions/bt_play_animation.h +++ b/bt/actions/bt_play_animation.h @@ -16,7 +16,6 @@ #include "modules/limboai/blackboard/bb_param/bb_node.h" -#include "core/object/object.h" #include "scene/animation/animation_player.h" class BTPlayAnimation : public BTAction { diff --git a/bt/actions/bt_stop_animation.cpp b/bt/actions/bt_stop_animation.cpp new file mode 100644 index 0000000..f0d415e --- /dev/null +++ b/bt/actions/bt_stop_animation.cpp @@ -0,0 +1,86 @@ +/** + * bt_stop_animation.cpp + * ============================================================================= + * Copyright 2021-2023 Serhii Snitsaruk + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + * ============================================================================= + */ + +#include "modules/limboai/bt/actions/bt_stop_animation.h" + +//**** Setters / Getters + +void BTStopAnimation::set_animation_player(Ref p_animation_player) { + animation_player_param = p_animation_player; + emit_changed(); + if (Engine::get_singleton()->is_editor_hint() && animation_player_param.is_valid()) { + animation_player_param->connect(SNAME("changed"), Callable(this, SNAME("emit_changed"))); + } +} + +void BTStopAnimation::set_animation_name(StringName p_animation_name) { + animation_name = p_animation_name; + emit_changed(); +} + +//**** Task Implementation + +String BTStopAnimation::get_configuration_warning() const { + String warning = BTAction::get_configuration_warning(); + if (!warning.is_empty()) { + warning += "\n"; + } + + if (animation_player_param.is_null()) { + warning += "Animation Player parameter is not set.\n"; + } else { + if (animation_player_param->get_value_source() == BBParam::SAVED_VALUE && animation_player_param->get_saved_value().is_zero()) { + warning += "Path to AnimationPlayer node is not set.\n"; + } else if (animation_player_param->get_value_source() == BBParam::BLACKBOARD_VAR && animation_player_param->get_variable().is_empty()) { + warning += "AnimationPlayer blackboard variable is not set.\n"; + } + } + + return warning; +} + +String BTStopAnimation::_generate_name() const { + if (animation_name == StringName() || animation_player_param.is_null()) { + return "StopAnimation"; + } + return vformat("StopAnimation \"%s\"", animation_name); +} + +void BTStopAnimation::_setup() { + setup_failed = true; + ERR_FAIL_COND_MSG(animation_player_param.is_null(), "BTStopAnimation: AnimationPlayer parameter is not set."); + animation_player = Object::cast_to(animation_player_param->get_value(get_agent(), get_blackboard())); + ERR_FAIL_COND_MSG(animation_player == nullptr, "BTStopAnimation: Failed to get AnimationPlayer."); + if (animation_name != StringName()) { + ERR_FAIL_COND_MSG(!animation_player->has_animation(animation_name), vformat("BTStopAnimation: Animation not found: %s", animation_name)); + } + setup_failed = false; +} + +int BTStopAnimation::_tick(double p_delta) { + ERR_FAIL_COND_V_MSG(setup_failed == true, FAILURE, "BTStopAnimation: _setup() failed - returning FAILURE."); + if (animation_player->is_playing() && (animation_name == StringName() || animation_name == animation_player->get_assigned_animation())) { + animation_player->stop(); + } + return SUCCESS; +} + +//**** Godot + +void BTStopAnimation::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_animation_player", "p_anim_player"), &BTStopAnimation::set_animation_player); + ClassDB::bind_method(D_METHOD("get_animation_player"), &BTStopAnimation::get_animation_player); + ClassDB::bind_method(D_METHOD("set_animation_name", "p_anim_name"), &BTStopAnimation::set_animation_name); + ClassDB::bind_method(D_METHOD("get_animation_name"), &BTStopAnimation::get_animation_name); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "animation_player", PROPERTY_HINT_RESOURCE_TYPE, "BBNode"), "set_animation_player", "get_animation_player"); + ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "animation_name"), "set_animation_name", "get_animation_name"); +} diff --git a/bt/actions/bt_stop_animation.h b/bt/actions/bt_stop_animation.h new file mode 100644 index 0000000..2ec939c --- /dev/null +++ b/bt/actions/bt_stop_animation.h @@ -0,0 +1,48 @@ +/** + * bt_stop_animation.h + * ============================================================================= + * Copyright 2021-2023 Serhii Snitsaruk + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + * ============================================================================= + */ + +#ifndef BT_STOP_ANIMATION_H +#define BT_STOP_ANIMATION_H + +#include "bt_action.h" + +#include "modules/limboai/blackboard/bb_param/bb_node.h" + +#include "scene/animation/animation_player.h" + +class BTStopAnimation : public BTAction { + GDCLASS(BTStopAnimation, BTAction); + +private: + Ref animation_player_param; + StringName animation_name; + + AnimationPlayer *animation_player = nullptr; + bool setup_failed = false; + +protected: + static void _bind_methods(); + + virtual String _generate_name() const override; + virtual void _setup() override; + virtual int _tick(double p_delta) override; + +public: + void set_animation_player(Ref p_animation_player); + Ref get_animation_player() const { return animation_player_param; } + + void set_animation_name(StringName p_animation_name); + StringName get_animation_name() const { return animation_name; } + + virtual String get_configuration_warning() const override; +}; + +#endif // BT_STOP_ANIMATION \ No newline at end of file diff --git a/config.py b/config.py index 416f191..f4db882 100644 --- a/config.py +++ b/config.py @@ -91,6 +91,7 @@ def get_doc_classes(): "BTSetAgentProperty", "BTSetVar", "BTState", + "BTStopAnimation", "BTSubtree", "BTTask", "BTTimeLimit", diff --git a/register_types.cpp b/register_types.cpp index f684233..3b8b8fe 100644 --- a/register_types.cpp +++ b/register_types.cpp @@ -51,6 +51,7 @@ #include "bt/actions/bt_random_wait.h" #include "bt/actions/bt_set_agent_property.h" #include "bt/actions/bt_set_var.h" +#include "bt/actions/bt_stop_animation.h" #include "bt/actions/bt_wait.h" #include "bt/actions/bt_wait_ticks.h" #include "bt/behavior_tree.h" @@ -147,6 +148,7 @@ void initialize_limboai_module(ModuleInitializationLevel p_level) { GDREGISTER_CLASS(BTRandomWait); GDREGISTER_CLASS(BTSetAgentProperty); GDREGISTER_CLASS(BTSetVar); + GDREGISTER_CLASS(BTStopAnimation); GDREGISTER_CLASS(BTSubtree); GDREGISTER_CLASS(BTWait); GDREGISTER_CLASS(BTWaitTicks); From 0b1d249ebe1212fa47a0dfdda536b23cfc9b2a97 Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Mon, 14 Aug 2023 13:06:00 +0200 Subject: [PATCH 05/16] BTStopAnimation: Add `keep_state` and fixes --- bt/actions/bt_stop_animation.cpp | 17 ++++++++++++----- bt/actions/bt_stop_animation.h | 4 ++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/bt/actions/bt_stop_animation.cpp b/bt/actions/bt_stop_animation.cpp index f0d415e..60da2ec 100644 --- a/bt/actions/bt_stop_animation.cpp +++ b/bt/actions/bt_stop_animation.cpp @@ -26,6 +26,11 @@ void BTStopAnimation::set_animation_name(StringName p_animation_name) { emit_changed(); } +void BTStopAnimation::set_keep_state(bool p_keep_state) { + keep_state = p_keep_state; + emit_changed(); +} + //**** Task Implementation String BTStopAnimation::get_configuration_warning() const { @@ -48,10 +53,9 @@ String BTStopAnimation::get_configuration_warning() const { } String BTStopAnimation::_generate_name() const { - if (animation_name == StringName() || animation_player_param.is_null()) { - return "StopAnimation"; - } - return vformat("StopAnimation \"%s\"", animation_name); + return "StopAnimation" + + (animation_name != StringName() ? vformat(" \"%s\"", animation_name) : "") + + (keep_state ? " keep_state: true" : ""); } void BTStopAnimation::_setup() { @@ -68,7 +72,7 @@ void BTStopAnimation::_setup() { int BTStopAnimation::_tick(double p_delta) { ERR_FAIL_COND_V_MSG(setup_failed == true, FAILURE, "BTStopAnimation: _setup() failed - returning FAILURE."); if (animation_player->is_playing() && (animation_name == StringName() || animation_name == animation_player->get_assigned_animation())) { - animation_player->stop(); + animation_player->stop(keep_state); } return SUCCESS; } @@ -80,7 +84,10 @@ void BTStopAnimation::_bind_methods() { ClassDB::bind_method(D_METHOD("get_animation_player"), &BTStopAnimation::get_animation_player); ClassDB::bind_method(D_METHOD("set_animation_name", "p_anim_name"), &BTStopAnimation::set_animation_name); ClassDB::bind_method(D_METHOD("get_animation_name"), &BTStopAnimation::get_animation_name); + ClassDB::bind_method(D_METHOD("set_keep_state", "p_keep_state"), &BTStopAnimation::set_keep_state); + ClassDB::bind_method(D_METHOD("get_keep_state"), &BTStopAnimation::get_keep_state); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "animation_player", PROPERTY_HINT_RESOURCE_TYPE, "BBNode"), "set_animation_player", "get_animation_player"); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "animation_name"), "set_animation_name", "get_animation_name"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keep_state"), "set_keep_state", "get_keep_state"); } diff --git a/bt/actions/bt_stop_animation.h b/bt/actions/bt_stop_animation.h index 2ec939c..2f9b7cd 100644 --- a/bt/actions/bt_stop_animation.h +++ b/bt/actions/bt_stop_animation.h @@ -24,6 +24,7 @@ class BTStopAnimation : public BTAction { private: Ref animation_player_param; StringName animation_name; + bool keep_state = false; AnimationPlayer *animation_player = nullptr; bool setup_failed = false; @@ -42,6 +43,9 @@ public: void set_animation_name(StringName p_animation_name); StringName get_animation_name() const { return animation_name; } + void set_keep_state(bool p_keep_state); + bool get_keep_state() const { return keep_state; } + virtual String get_configuration_warning() const override; }; From 92b0f24dcb07da297106608cd9af867bedfc22ec Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Mon, 14 Aug 2023 13:14:06 +0200 Subject: [PATCH 06/16] Add BTStopAnimation icon --- icons/BTStopAnimation.svg | 1 + 1 file changed, 1 insertion(+) create mode 100644 icons/BTStopAnimation.svg diff --git a/icons/BTStopAnimation.svg b/icons/BTStopAnimation.svg new file mode 100644 index 0000000..973b542 --- /dev/null +++ b/icons/BTStopAnimation.svg @@ -0,0 +1 @@ + \ No newline at end of file From 404c9274f93d4641f1f7c710168dc6831b23db19 Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Mon, 14 Aug 2023 13:25:58 +0200 Subject: [PATCH 07/16] Add BTStopAnimation class docs --- doc_classes/BTStopAnimation.xml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 doc_classes/BTStopAnimation.xml diff --git a/doc_classes/BTStopAnimation.xml b/doc_classes/BTStopAnimation.xml new file mode 100644 index 0000000..f3abf17 --- /dev/null +++ b/doc_classes/BTStopAnimation.xml @@ -0,0 +1,23 @@ + + + + BT action that stops playback of an animation on the specified AnimationPlayer node. + + + BTStopAnimation action stops playback of an animation on the specified AnimationPlayer node and returns [code]SUCCESS[/code]. If [member animation_name] is set, it will only stop playback if the specified animation is currently playing. + Returns [code]FAILURE[/code] if the action fails to acquire AnimationPlayer node. + + + + + + Animation's key within the AnimationPlayer node. If not empty, BTStopAnimation will only stop playback if the specified animation is currently playing. + + + Parameter that specifies the AnimationPlayer node. + + + If [code]true[/code], the animation state is not updated visually. + + + From f776779175e3d4d046a617fb1ffb6cc97f659788 Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Mon, 14 Aug 2023 13:29:16 +0200 Subject: [PATCH 08/16] Update waypoints demo --- demo/ai/tasks/play_animation.gd | 46 -------------------------------- demo/ai/tasks/start_animation.gd | 31 --------------------- demo/ai/trees/waypoints.tres | 15 ++++++----- 3 files changed, 9 insertions(+), 83 deletions(-) delete mode 100644 demo/ai/tasks/play_animation.gd delete mode 100644 demo/ai/tasks/start_animation.gd diff --git a/demo/ai/tasks/play_animation.gd b/demo/ai/tasks/play_animation.gd deleted file mode 100644 index ca833da..0000000 --- a/demo/ai/tasks/play_animation.gd +++ /dev/null @@ -1,46 +0,0 @@ -#* -#* play_animation.gd -#* ============================================================================= -#* Copyright 2021-2023 Serhii Snitsaruk -#* -#* Use of this source code is governed by an MIT-style -#* license that can be found in the LICENSE file or at -#* https://opensource.org/licenses/MIT. -#* ============================================================================= -#* - -@tool -extends BTAction - -@export var animation_name: String -@export var animation_player: NodePath - -var _player: AnimationPlayer -var _finished: bool - - -func _generate_name() -> String: - return "PlayAnimation \"%s\"" % animation_name - - -func _setup() -> void: - _player = agent.get_node(animation_player) - - -func _enter() -> void: - if _player.has_animation(animation_name): - _finished = false - _player.play(animation_name) - _player.animation_finished.connect(_on_animation_finished, CONNECT_ONE_SHOT) - else: - _finished = true - - -func _tick(_delta: float) -> int: - if _finished: - return SUCCESS - return RUNNING - - -func _on_animation_finished(_anim): - _finished = true diff --git a/demo/ai/tasks/start_animation.gd b/demo/ai/tasks/start_animation.gd deleted file mode 100644 index a109ff1..0000000 --- a/demo/ai/tasks/start_animation.gd +++ /dev/null @@ -1,31 +0,0 @@ -#* -#* start_animation.gd -#* ============================================================================= -#* Copyright 2021-2023 Serhii Snitsaruk -#* -#* Use of this source code is governed by an MIT-style -#* license that can be found in the LICENSE file or at -#* https://opensource.org/licenses/MIT. -#* ============================================================================= -#* - -@tool -extends BTAction - -@export var animation_name: String -@export var animation_player: NodePath - -var _player: AnimationPlayer - - -func _generate_name() -> String: - return "StartAnimation \"%s\"" % animation_name - - -func _setup() -> void: - _player = agent.get_node(animation_player) - - -func _tick(p_delta: float) -> int: - _player.play(animation_name) - return SUCCESS diff --git a/demo/ai/trees/waypoints.tres b/demo/ai/trees/waypoints.tres index c056b64..ecabd99 100644 --- a/demo/ai/trees/waypoints.tres +++ b/demo/ai/trees/waypoints.tres @@ -1,7 +1,6 @@ [gd_resource type="BehaviorTree" load_steps=10 format=3 uid="uid://cjkqi41oagagd"] [ext_resource type="Script" path="res://ai/tasks/arrive_pos.gd" id="1_rhs33"] -[ext_resource type="Script" path="res://ai/tasks/play_animation.gd" id="2_dg0ss"] [sub_resource type="BTAction" id="BTAction_3xal7"] script = ExtResource("1_rhs33") @@ -9,16 +8,20 @@ target_position_var = "wp" speed_var = "speed" tolerance = 50.0 -[sub_resource type="BTAction" id="BTAction_yq1vl"] -script = ExtResource("2_dg0ss") -animation_name = "bounce" -animation_player = NodePath("AnimationPlayer") +[sub_resource type="BBNode" id="BBNode_0t2vk"] +resource_name = "AnimationPlayer" +saved_value = NodePath("AnimationPlayer") + +[sub_resource type="BTPlayAnimation" id="BTPlayAnimation_s01ov"] +await_completion = 1.0 +animation_player = SubResource("BBNode_0t2vk") +animation_name = &"bounce" [sub_resource type="BTWait" id="BTWait_qs55a"] duration = 0.1 [sub_resource type="BTSequence" id="BTSequence_a2ng0"] -children = [SubResource("BTAction_3xal7"), SubResource("BTAction_yq1vl"), SubResource("BTWait_qs55a")] +children = [SubResource("BTAction_3xal7"), SubResource("BTPlayAnimation_s01ov"), SubResource("BTWait_qs55a")] [sub_resource type="BTForEach" id="BTForEach_0cp04"] children = [SubResource("BTSequence_a2ng0")] From 8e3c4c757069e8eff1659be2c2f67724c0c7bfd4 Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Mon, 14 Aug 2023 14:08:10 +0200 Subject: [PATCH 09/16] Add BTPauseAnimation action --- bt/actions/bt_pause_animation.cpp | 70 +++++++++++++++++++++++++++++++ bt/actions/bt_pause_animation.h | 44 +++++++++++++++++++ config.py | 1 + register_types.cpp | 2 + 4 files changed, 117 insertions(+) create mode 100644 bt/actions/bt_pause_animation.cpp create mode 100644 bt/actions/bt_pause_animation.h diff --git a/bt/actions/bt_pause_animation.cpp b/bt/actions/bt_pause_animation.cpp new file mode 100644 index 0000000..ee1b142 --- /dev/null +++ b/bt/actions/bt_pause_animation.cpp @@ -0,0 +1,70 @@ +/** + * bt_pause_animation.cpp + * ============================================================================= + * Copyright 2021-2023 Serhii Snitsaruk + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + * ============================================================================= + */ + +#include "bt_pause_animation.h" + +//**** Setters / Getters + +void BTPauseAnimation::set_animation_player(Ref p_animation_player) { + animation_player_param = p_animation_player; + emit_changed(); + if (Engine::get_singleton()->is_editor_hint() && animation_player_param.is_valid()) { + animation_player_param->connect(SNAME("changed"), Callable(this, SNAME("emit_changed"))); + } +} + +//**** Task Implementation + +String BTPauseAnimation::get_configuration_warning() const { + String warning = BTAction::get_configuration_warning(); + if (!warning.is_empty()) { + warning += "\n"; + } + + if (animation_player_param.is_null()) { + warning += "Animation Player parameter is not set.\n"; + } else { + if (animation_player_param->get_value_source() == BBParam::SAVED_VALUE && animation_player_param->get_saved_value().is_zero()) { + warning += "Path to AnimationPlayer node is not set.\n"; + } else if (animation_player_param->get_value_source() == BBParam::BLACKBOARD_VAR && animation_player_param->get_variable().is_empty()) { + warning += "AnimationPlayer blackboard variable is not set.\n"; + } + } + + return warning; +} + +String BTPauseAnimation::_generate_name() const { + return "PauseAnimation"; +} + +void BTPauseAnimation::_setup() { + setup_failed = true; + ERR_FAIL_COND_MSG(animation_player_param.is_null(), "BTPauseAnimation: AnimationPlayer parameter is not set."); + animation_player = Object::cast_to(animation_player_param->get_value(get_agent(), get_blackboard())); + ERR_FAIL_COND_MSG(animation_player == nullptr, "BTPauseAnimation: Failed to get AnimationPlayer."); + setup_failed = false; +} + +int BTPauseAnimation::_tick(double p_delta) { + ERR_FAIL_COND_V_MSG(setup_failed == true, FAILURE, "BTPauseAnimation: _setup() failed - returning FAILURE."); + animation_player->pause(); + return SUCCESS; +} + +//**** Godot + +void BTPauseAnimation::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_animation_player", "p_anim_player"), &BTPauseAnimation::set_animation_player); + ClassDB::bind_method(D_METHOD("get_animation_player"), &BTPauseAnimation::get_animation_player); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "animation_player", PROPERTY_HINT_RESOURCE_TYPE, "BBNode"), "set_animation_player", "get_animation_player"); +} diff --git a/bt/actions/bt_pause_animation.h b/bt/actions/bt_pause_animation.h new file mode 100644 index 0000000..a54a436 --- /dev/null +++ b/bt/actions/bt_pause_animation.h @@ -0,0 +1,44 @@ +/** + * bt_pause_animation.h + * ============================================================================= + * Copyright 2021-2023 Serhii Snitsaruk + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + * ============================================================================= + */ + +#ifndef BT_PAUSE_ANIMATION_H +#define BT_PAUSE_ANIMATION_H + +#include "bt_action.h" + +#include "modules/limboai/blackboard/bb_param/bb_node.h" + +#include "scene/animation/animation_player.h" + +class BTPauseAnimation : public BTAction { + GDCLASS(BTPauseAnimation, BTAction); + +private: + Ref animation_player_param; + + AnimationPlayer *animation_player = nullptr; + bool setup_failed = false; + +protected: + static void _bind_methods(); + + virtual String _generate_name() const override; + virtual void _setup() override; + virtual int _tick(double p_delta) override; + +public: + void set_animation_player(Ref p_animation_player); + Ref get_animation_player() const { return animation_player_param; } + + virtual String get_configuration_warning() const override; +}; + +#endif // BT_PAUSE_ANIMATION diff --git a/config.py b/config.py index f4db882..b94b37a 100644 --- a/config.py +++ b/config.py @@ -76,6 +76,7 @@ def get_doc_classes(): "BTInvert", "BTNewScope", "BTParallel", + "BTPauseAnimation", "BTPlayAnimation", "BTPlayer", "BTProbability", diff --git a/register_types.cpp b/register_types.cpp index 3b8b8fe..25d36cc 100644 --- a/register_types.cpp +++ b/register_types.cpp @@ -47,6 +47,7 @@ #include "bt/actions/bt_action.h" #include "bt/actions/bt_console_print.h" #include "bt/actions/bt_fail.h" +#include "bt/actions/bt_pause_animation.h" #include "bt/actions/bt_play_animation.h" #include "bt/actions/bt_random_wait.h" #include "bt/actions/bt_set_agent_property.h" @@ -144,6 +145,7 @@ void initialize_limboai_module(ModuleInitializationLevel p_level) { GDREGISTER_CLASS(BTConsolePrint); GDREGISTER_CLASS(BTFail); GDREGISTER_CLASS(BTNewScope); + GDREGISTER_CLASS(BTPauseAnimation); GDREGISTER_CLASS(BTPlayAnimation); GDREGISTER_CLASS(BTRandomWait); GDREGISTER_CLASS(BTSetAgentProperty); From 94f6d289a86318ed21cad8938511b0a5d0f6efca Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Mon, 14 Aug 2023 14:15:01 +0200 Subject: [PATCH 10/16] Allow BTPlayAnimation with `animation_name` empty to resume playback after pause --- bt/actions/bt_play_animation.cpp | 16 ++++++++-------- doc_classes/BTPlayAnimation.xml | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/bt/actions/bt_play_animation.cpp b/bt/actions/bt_play_animation.cpp index d0d0a50..c37e0d1 100644 --- a/bt/actions/bt_play_animation.cpp +++ b/bt/actions/bt_play_animation.cpp @@ -65,18 +65,16 @@ String BTPlayAnimation::get_configuration_warning() const { warning += "AnimationPlayer blackboard variable is not set.\n"; } } - if (animation_name == StringName()) { - warning += "Animation Name is not set.\n"; + if (animation_name == StringName() && await_completion > 0.0) { + warning += "Animation Name is required in order to wait for the animation to finish.\n"; } return warning; } String BTPlayAnimation::_generate_name() const { - if (animation_name == StringName() || animation_player_param.is_null()) { - return "PlayAnimation ???"; - } - return vformat("PlayAnimation \"%s\"", animation_name) + + return "PlayAnimation" + + (animation_name != StringName() ? vformat(" \"%s\"", animation_name) : "") + (blend >= 0.0 ? vformat(" blend: %ss", Math::snapped(blend, 0.001)) : "") + (speed != 1.0 ? vformat(" speed: %s", Math::snapped(speed, 0.001)) : "") + (from_end != false ? vformat(" from_end: %s", from_end) : "") + @@ -88,8 +86,10 @@ void BTPlayAnimation::_setup() { ERR_FAIL_COND_MSG(animation_player_param.is_null(), "BTPlayAnimation: AnimationPlayer parameter is not set."); animation_player = Object::cast_to(animation_player_param->get_value(get_agent(), get_blackboard())); ERR_FAIL_COND_MSG(animation_player == nullptr, "BTPlayAnimation: Failed to get AnimationPlayer."); - ERR_FAIL_COND_MSG(animation_name == StringName(), "BTPlayAnimation: Animation name is not set."); - ERR_FAIL_COND_MSG(!animation_player->has_animation(animation_name), vformat("BTPlayAnimation: Animation not found: %s", animation_name)); + ERR_FAIL_COND_MSG(animation_name != StringName() && !animation_player->has_animation(animation_name), vformat("BTPlayAnimation: Animation not found: %s", animation_name)); + if (animation_name == StringName() && await_completion > 0.0) { + WARN_PRINT("BTPlayAnimation: Animation Name is required in order to wait for the animation to finish."); + } setup_failed = false; } diff --git a/doc_classes/BTPlayAnimation.xml b/doc_classes/BTPlayAnimation.xml index c082822..f4f20dd 100644 --- a/doc_classes/BTPlayAnimation.xml +++ b/doc_classes/BTPlayAnimation.xml @@ -13,7 +13,7 @@ - Animation's key within the AnimationPlayer node. + Animation's key within the AnimationPlayer node. If empty, BTPlayAnimation will resume the last played animation if [class AnimationPlayer] was paused. Parameter that specifies the AnimationPlayer node. From b293d52fbb831dd61db420302885fcf7cd5c4a2e Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Mon, 14 Aug 2023 14:29:32 +0200 Subject: [PATCH 11/16] Add BTPauseAnimation icon --- icons/BTPauseAnimation.svg | 1 + 1 file changed, 1 insertion(+) create mode 100644 icons/BTPauseAnimation.svg diff --git a/icons/BTPauseAnimation.svg b/icons/BTPauseAnimation.svg new file mode 100644 index 0000000..d0878cd --- /dev/null +++ b/icons/BTPauseAnimation.svg @@ -0,0 +1 @@ + \ No newline at end of file From 0c4a98cf352d734f24eba7e39886001e9eaa9725 Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Mon, 14 Aug 2023 14:35:44 +0200 Subject: [PATCH 12/16] Add class doc for BTPauseAnimation --- doc_classes/BTPauseAnimation.xml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 doc_classes/BTPauseAnimation.xml diff --git a/doc_classes/BTPauseAnimation.xml b/doc_classes/BTPauseAnimation.xml new file mode 100644 index 0000000..0fa31fc --- /dev/null +++ b/doc_classes/BTPauseAnimation.xml @@ -0,0 +1,17 @@ + + + + BT action that pauses playback of an animation on the specified AnimationPlayer node. + + + BTPauseAnimation action pauses playback of an animation on the specified [class AnimationPlayer] node and returns [code]SUCCESS[/code]. + Returns [code]FAILURE[/code] if the action fails to acquire AnimationPlayer node. + + + + + + Parameter that specifies the AnimationPlayer node. + + + From 694eebcf6df8eef10d4b2bdd9d790639960cee8b Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Mon, 14 Aug 2023 14:42:46 +0200 Subject: [PATCH 13/16] Fixes for class docs --- doc_classes/BTPauseAnimation.xml | 8 ++++---- doc_classes/BTPlayAnimation.xml | 8 ++++---- doc_classes/BTStopAnimation.xml | 10 +++++----- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/doc_classes/BTPauseAnimation.xml b/doc_classes/BTPauseAnimation.xml index 0fa31fc..0f9b767 100644 --- a/doc_classes/BTPauseAnimation.xml +++ b/doc_classes/BTPauseAnimation.xml @@ -1,17 +1,17 @@ - BT action that pauses playback of an animation on the specified AnimationPlayer node. + BT action that pauses playback of an animation on the specified [AnimationPlayer] node. - BTPauseAnimation action pauses playback of an animation on the specified [class AnimationPlayer] node and returns [code]SUCCESS[/code]. - Returns [code]FAILURE[/code] if the action fails to acquire AnimationPlayer node. + BTPauseAnimation action pauses playback of an animation on the specified [AnimationPlayer] node and returns [code]SUCCESS[/code]. + Returns [code]FAILURE[/code] if the action fails to acquire [AnimationPlayer] node. - Parameter that specifies the AnimationPlayer node. + Parameter that specifies the [AnimationPlayer] node. diff --git a/doc_classes/BTPlayAnimation.xml b/doc_classes/BTPlayAnimation.xml index f4f20dd..97bd599 100644 --- a/doc_classes/BTPlayAnimation.xml +++ b/doc_classes/BTPlayAnimation.xml @@ -1,10 +1,10 @@ - BT action that plays an animation on the specified AnimationPlayer node. + BT action that plays an animation on the specified [AnimationPlayer] node. - BTPlayAnimation action plays an animation on the specified AnimationPlayer node. If the [member await_completion] is greater than [code]0[/code], the action will wait for the animation to complete, with a maximum waiting time equal to [member await_completion]. + BTPlayAnimation action plays an animation on the specified [AnimationPlayer] node. If the [member await_completion] is greater than [code]0[/code], the action will wait for the animation to complete, with a maximum waiting time equal to [member await_completion]. Returns [code]SUCCESS[/code] if the animation finishes playing or if the elapsed time exceeds [member await_completion]. When [member await_completion] is set to [code]0[/code], BTPlayAnimation doesn't wait for the animation to finish and immediately returns [code]SUCCESS[/code]. Returns [code]RUNNING[/code] if the animation is still playing and the elapsed time is less than [member await_completion]. Returns [code]FAILURE[/code] if the action fails to play the requested animation. @@ -13,10 +13,10 @@ - Animation's key within the AnimationPlayer node. If empty, BTPlayAnimation will resume the last played animation if [class AnimationPlayer] was paused. + Animation's key within the [AnimationPlayer] node. If empty, BTPlayAnimation will resume the last played animation if [AnimationPlayer] was paused. - Parameter that specifies the AnimationPlayer node. + Parameter that specifies the [AnimationPlayer] node. Duration to wait for the animation to finish (in seconds). diff --git a/doc_classes/BTStopAnimation.xml b/doc_classes/BTStopAnimation.xml index f3abf17..c0962ff 100644 --- a/doc_classes/BTStopAnimation.xml +++ b/doc_classes/BTStopAnimation.xml @@ -1,20 +1,20 @@ - BT action that stops playback of an animation on the specified AnimationPlayer node. + BT action that stops playback of an animation on the specified [AnimationPlayer] node. - BTStopAnimation action stops playback of an animation on the specified AnimationPlayer node and returns [code]SUCCESS[/code]. If [member animation_name] is set, it will only stop playback if the specified animation is currently playing. - Returns [code]FAILURE[/code] if the action fails to acquire AnimationPlayer node. + BTStopAnimation action stops playback of an animation on the specified [AnimationPlayer] node and returns [code]SUCCESS[/code]. If [member animation_name] is set, it will only stop playback if the specified animation is currently playing. + Returns [code]FAILURE[/code] if the action fails to acquire [AnimationPlayer] node. - Animation's key within the AnimationPlayer node. If not empty, BTStopAnimation will only stop playback if the specified animation is currently playing. + Animation's key within the [AnimationPlayer] node. If not empty, BTStopAnimation will only stop playback if the specified animation is currently playing. - Parameter that specifies the AnimationPlayer node. + Parameter that specifies the [AnimationPlayer] node. If [code]true[/code], the animation state is not updated visually. From 015ec64771cf385a2687c6261dd51c17e819afde Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Mon, 14 Aug 2023 17:24:27 +0200 Subject: [PATCH 14/16] Add BTAwaitAnimation action --- bt/actions/bt_await_animation.cpp | 104 ++++++++++++++++++++++++++++++ bt/actions/bt_await_animation.h | 52 +++++++++++++++ config.py | 1 + register_types.cpp | 2 + 4 files changed, 159 insertions(+) create mode 100644 bt/actions/bt_await_animation.cpp create mode 100644 bt/actions/bt_await_animation.h diff --git a/bt/actions/bt_await_animation.cpp b/bt/actions/bt_await_animation.cpp new file mode 100644 index 0000000..67873db --- /dev/null +++ b/bt/actions/bt_await_animation.cpp @@ -0,0 +1,104 @@ +/** + * bt_await_animation.cpp + * ============================================================================= + * Copyright 2021-2023 Serhii Snitsaruk + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + * ============================================================================= + */ + +#include "bt_await_animation.h" + +//**** Setters / Getters + +void BTAwaitAnimation::set_animation_player(Ref p_animation_player) { + animation_player_param = p_animation_player; + emit_changed(); + if (Engine::get_singleton()->is_editor_hint() && animation_player_param.is_valid()) { + animation_player_param->connect(SNAME("changed"), Callable(this, SNAME("emit_changed"))); + } +} + +void BTAwaitAnimation::set_animation_name(StringName p_animation_name) { + animation_name = p_animation_name; + emit_changed(); +} + +void BTAwaitAnimation::set_max_time(double p_max_time) { + max_time = p_max_time; + emit_changed(); +} + +//**** Task Implementation + +String BTAwaitAnimation::get_configuration_warning() const { + String warning = BTAction::get_configuration_warning(); + if (!warning.is_empty()) { + warning += "\n"; + } + + if (animation_player_param.is_null()) { + warning += "Animation Player parameter is not set.\n"; + } else { + if (animation_player_param->get_value_source() == BBParam::SAVED_VALUE && animation_player_param->get_saved_value().is_zero()) { + warning += "Path to AnimationPlayer node is not set.\n"; + } else if (animation_player_param->get_value_source() == BBParam::BLACKBOARD_VAR && animation_player_param->get_variable().is_empty()) { + warning += "AnimationPlayer blackboard variable is not set.\n"; + } + } + if (animation_name == StringName()) { + warning += "Animation Name is required in order to wait for the animation to finish.\n"; + } + if (max_time <= 0.0) { + warning += "Max time should be greater than 0.0.\n"; + } + + return warning; +} + +String BTAwaitAnimation::_generate_name() const { + return "AwaitAnimation" + + (animation_name != StringName() ? vformat(" \"%s\"", animation_name) : " ???") + + vformat(" max_time: %ss", Math::snapped(max_time, 0.001)); +} + +void BTAwaitAnimation::_setup() { + setup_failed = true; + ERR_FAIL_COND_MSG(animation_player_param.is_null(), "BTAwaitAnimation: AnimationPlayer parameter is not set."); + animation_player = Object::cast_to(animation_player_param->get_value(get_agent(), get_blackboard())); + ERR_FAIL_COND_MSG(animation_player == nullptr, "BTAwaitAnimation: Failed to get AnimationPlayer."); + ERR_FAIL_COND_MSG(animation_name == StringName(), "BTAwaitAnimation: Animation Name is not set."); + ERR_FAIL_COND_MSG(!animation_player->has_animation(animation_name), vformat("BTAwaitAnimation: Animation not found: %s", animation_name)); + setup_failed = false; +} + +int BTAwaitAnimation::_tick(double p_delta) { + ERR_FAIL_COND_V_MSG(setup_failed == true, FAILURE, "BTAwaitAnimation: _setup() failed - returning FAILURE."); + + // ! Doing this check instead of using signal due to a bug in Godot: https://github.com/godotengine/godot/issues/76127 + if (animation_player->is_playing() && animation_player->get_assigned_animation() == animation_name) { + if (get_elapsed_time() < max_time) { + return RUNNING; + } else if (max_time > 0.0) { + WARN_PRINT(vformat("BTAwaitAnimation: Waiting time for the \"%s\" animation exceeded the allocated %s sec.", animation_name, max_time)); + } + } + return SUCCESS; +} + +//**** Godot + +void BTAwaitAnimation::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_animation_player", "p_anim_player"), &BTAwaitAnimation::set_animation_player); + ClassDB::bind_method(D_METHOD("get_animation_player"), &BTAwaitAnimation::get_animation_player); + ClassDB::bind_method(D_METHOD("set_animation_name", "p_anim_name"), &BTAwaitAnimation::set_animation_name); + ClassDB::bind_method(D_METHOD("get_animation_name"), &BTAwaitAnimation::get_animation_name); + ClassDB::bind_method(D_METHOD("set_max_time", "p_time_sec"), &BTAwaitAnimation::set_max_time); + ClassDB::bind_method(D_METHOD("get_max_time"), &BTAwaitAnimation::get_max_time); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "animation_player", PROPERTY_HINT_RESOURCE_TYPE, "BBNode"), "set_animation_player", "get_animation_player"); + ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "animation_name"), "set_animation_name", "get_animation_name"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_time", PROPERTY_HINT_RANGE, "0.0,100.0"), "set_max_time", "get_max_time"); +} diff --git a/bt/actions/bt_await_animation.h b/bt/actions/bt_await_animation.h new file mode 100644 index 0000000..7eb34eb --- /dev/null +++ b/bt/actions/bt_await_animation.h @@ -0,0 +1,52 @@ +/** + * bt_await_animation.h + * ============================================================================= + * Copyright 2021-2023 Serhii Snitsaruk + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + * ============================================================================= + */ + +#ifndef BT_AWAIT_ANIMATION_H +#define BT_AWAIT_ANIMATION_H + +#include "bt_action.h" + +#include "modules/limboai/blackboard/bb_param/bb_node.h" + +#include "scene/animation/animation_player.h" + +class BTAwaitAnimation : public BTAction { + GDCLASS(BTAwaitAnimation, BTAction); + +private: + Ref animation_player_param; + StringName animation_name; + double max_time = 1.0; + + AnimationPlayer *animation_player = nullptr; + bool setup_failed = false; + +protected: + static void _bind_methods(); + + virtual String _generate_name() const override; + virtual void _setup() override; + virtual int _tick(double p_delta) override; + +public: + void set_animation_player(Ref p_animation_player); + Ref get_animation_player() const { return animation_player_param; } + + void set_animation_name(StringName p_animation_name); + StringName get_animation_name() const { return animation_name; } + + void set_max_time(double p_max_time); + double get_max_time() const { return max_time; } + + virtual String get_configuration_warning() const override; +}; + +#endif // BT_AWAIT_ANIMATION \ No newline at end of file diff --git a/config.py b/config.py index b94b37a..bee70be 100644 --- a/config.py +++ b/config.py @@ -60,6 +60,7 @@ def get_doc_classes(): "BTAction", "BTAlwaysFail", "BTAlwaysSucceed", + "BTAwaitAnimation", "BTCheckAgentProperty", "BTCheckTrigger", "BTCheckVar", diff --git a/register_types.cpp b/register_types.cpp index 25d36cc..ce49ceb 100644 --- a/register_types.cpp +++ b/register_types.cpp @@ -45,6 +45,7 @@ #include "blackboard/bb_param/bb_vector4i.h" #include "blackboard/blackboard.h" #include "bt/actions/bt_action.h" +#include "bt/actions/bt_await_animation.h" #include "bt/actions/bt_console_print.h" #include "bt/actions/bt_fail.h" #include "bt/actions/bt_pause_animation.h" @@ -142,6 +143,7 @@ void initialize_limboai_module(ModuleInitializationLevel p_level) { GDREGISTER_CLASS(BTForEach); GDREGISTER_CLASS(BTAction); + GDREGISTER_CLASS(BTAwaitAnimation); GDREGISTER_CLASS(BTConsolePrint); GDREGISTER_CLASS(BTFail); GDREGISTER_CLASS(BTNewScope); From d642dbedc57b919ac518c0cfa0507d48452485a6 Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Mon, 14 Aug 2023 17:33:54 +0200 Subject: [PATCH 15/16] Add class docs for BTAwaitAnimation --- doc_classes/BTAwaitAnimation.xml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 doc_classes/BTAwaitAnimation.xml diff --git a/doc_classes/BTAwaitAnimation.xml b/doc_classes/BTAwaitAnimation.xml new file mode 100644 index 0000000..886262f --- /dev/null +++ b/doc_classes/BTAwaitAnimation.xml @@ -0,0 +1,24 @@ + + + + BT action that waits for an animation to finish playing. + + + BTAwaitAnimation waits for an animation on the specified [AnimationPlayer] node to finish playing and returns [code]SUCCESS[/code]. + Returns [code]SUCCESS[/code] if the specified animation finished playing or if the specified animation is not currently playing. + Returns [code]FAILURE[/code] if the specified animation doesn't exist or the action failed to get [AnimationPlayer] instance. + + + + + + Animation's key within the [AnimationPlayer] node. + + + Parameter that specifies the [AnimationPlayer] node. + + + Maximum duration to wait for the animation to finish (in seconds). + + + From 4b57c8fd99ac0622d31a11e13635e975fa6cdd36 Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Mon, 14 Aug 2023 17:34:12 +0200 Subject: [PATCH 16/16] Add icon for BTAwaitAnimation --- icons/BTAwaitAnimation.svg | 1 + 1 file changed, 1 insertion(+) create mode 100644 icons/BTAwaitAnimation.svg diff --git a/icons/BTAwaitAnimation.svg b/icons/BTAwaitAnimation.svg new file mode 100644 index 0000000..6bb4cd1 --- /dev/null +++ b/icons/BTAwaitAnimation.svg @@ -0,0 +1 @@ + \ No newline at end of file