From 45cf74dca95f40ad83f5a828a74eb1cbc99f68cb Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Sun, 13 Aug 2023 17:34:34 +0200 Subject: [PATCH] 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);