From 015ec64771cf385a2687c6261dd51c17e819afde Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Mon, 14 Aug 2023 17:24:27 +0200 Subject: [PATCH] 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);