Exposed additional functions to GDScript to implement a GDScript based EditorPlugin.

This commit is contained in:
Martin Felis 2026-01-24 15:38:27 +01:00
parent 5d0bf10ce7
commit fd13c53e52
4 changed files with 132 additions and 54 deletions

View File

@ -51,7 +51,7 @@ void BLTAnimationGraph::_update_properties_for_node(const String &p_base_path, R
p_node->get_child_nodes(&children);
for (const Ref<BLTAnimationNode> &child_node : children) {
_update_properties_for_node(p_base_path + child_node->name + "/", child_node);
_update_properties_for_node(p_base_path + child_node->get_name() + "/", child_node);
}
}

View File

@ -5,9 +5,17 @@
#include "blendalot_animation_node.h"
void BLTAnimationNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_position", "position"), &BLTAnimationNode::set_position);
ClassDB::bind_method(D_METHOD("get_position"), &BLTAnimationNode::get_position);
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position"), "set_position", "get_position");
ADD_SIGNAL(MethodInfo("tree_changed"));
ADD_SIGNAL(MethodInfo("animation_node_renamed", PropertyInfo(Variant::INT, "object_id"), PropertyInfo(Variant::STRING, "old_name"), PropertyInfo(Variant::STRING, "new_name")));
ADD_SIGNAL(MethodInfo("animation_node_removed", PropertyInfo(Variant::INT, "object_id"), PropertyInfo(Variant::STRING, "name")));
ClassDB::bind_method(D_METHOD("get_input_names"), &BLTAnimationNode::get_input_names_as_typed_array);
ClassDB::bind_method(D_METHOD("get_input_count"), &BLTAnimationNode::get_input_count);
ClassDB::bind_method(D_METHOD("get_input_index"), &BLTAnimationNode::get_input_index);
}
void BLTAnimationNode::get_parameter_list(List<PropertyInfo> *r_list) const {
@ -42,8 +50,13 @@ void BLTAnimationNode::_animation_node_removed(const ObjectID &p_oid, const Stri
void BLTAnimationNodeBlendTree::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_node", "animation_node"), &BLTAnimationNodeBlendTree::add_node);
ClassDB::bind_method(D_METHOD("get_node", "node_name"), &BLTAnimationNodeBlendTree::get_node);
ClassDB::bind_method(D_METHOD("get_output_node"), &BLTAnimationNodeBlendTree::get_output_node);
ClassDB::bind_method(D_METHOD("get_node_names"), &BLTAnimationNodeBlendTree::get_node_names_as_typed_array);
ClassDB::bind_method(D_METHOD("is_connection_valid", "source_node", "target_node", "target_port_name"), &BLTAnimationNodeBlendTree::is_connection_valid);
ClassDB::bind_method(D_METHOD("add_connection", "source_node", "target_node", "target_port_name"), &BLTAnimationNodeBlendTree::add_connection);
ClassDB::bind_method(D_METHOD("get_connections"), &BLTAnimationNodeBlendTree::get_connections_as_array);
BIND_CONSTANT(CONNECTION_OK);
BIND_CONSTANT(CONNECTION_ERROR_GRAPH_ALREADY_INITIALIZED);
@ -57,7 +70,7 @@ void BLTAnimationNodeBlendTree::_bind_methods() {
void BLTAnimationNodeBlendTree::_get_property_list(List<PropertyInfo> *p_list) const {
for (const Ref<BLTAnimationNode> &node : tree_graph.nodes) {
String prop_name = node->name;
String prop_name = node->get_name();
if (prop_name != "Output") {
p_list->push_back(PropertyInfo(Variant::OBJECT, "nodes/" + prop_name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationNode", PROPERTY_USAGE_NO_EDITOR));
}
@ -93,9 +106,9 @@ bool BLTAnimationNodeBlendTree::_get(const StringName &p_name, Variant &r_value)
int idx = 0;
for (const BLTBlendTreeConnection &connection : tree_graph.connections) {
conns[idx * 3 + 0] = connection.target_node->name;
conns[idx * 3 + 0] = connection.target_node->get_name();
conns[idx * 3 + 1] = connection.target_node->get_input_index(connection.target_port_name);
conns[idx * 3 + 2] = connection.source_node->name;
conns[idx * 3 + 2] = connection.source_node->get_name();
idx++;
}
@ -115,7 +128,7 @@ bool BLTAnimationNodeBlendTree::_set(const StringName &p_name, const Variant &p_
if (what == "node") {
Ref<BLTAnimationNode> anode = p_value;
if (anode.is_valid()) {
anode->name = node_name;
anode->set_name(node_name);
add_node(anode);
}
return true;
@ -138,8 +151,7 @@ bool BLTAnimationNodeBlendTree::_set(const StringName &p_name, const Variant &p_
int source_node_index = find_node_index_by_name(conns[i + 2]);
Ref<BLTAnimationNode> target_node = tree_graph.nodes[target_node_index];
Vector<StringName> target_input_names;
target_node->get_input_names(target_input_names);
Vector<StringName> target_input_names = target_node->get_input_names();
add_connection(tree_graph.nodes[source_node_index], target_node, target_input_names[target_node_port_index]);
}
@ -255,11 +267,13 @@ void AnimationDataAllocator::register_track_values(const Ref<Animation> &animati
}
bool BLTAnimationNodeSampler::initialize(GraphEvaluationContext &context) {
BLTAnimationNode::initialize(context);
if (!BLTAnimationNode::initialize(context)) {
return false;
}
animation = context.animation_player->get_animation(animation_name);
if (!animation.is_valid()) {
print_error(vformat("Cannot initialize node %s: animation '%s' not found in animation player.", name, animation_name));
print_error(vformat("Cannot initialize node %s: animation '%s' not found in animation player.", get_name(), animation_name));
return false;
}
@ -412,7 +426,7 @@ bool BLTAnimationNodeBlend2::_set(const StringName &p_name, const Variant &p_val
BLTAnimationNodeBlendTree::BLTBlendTreeGraph::BLTBlendTreeGraph() {
Ref<BLTAnimationNodeOutput> output_node;
output_node.instantiate();
output_node->name = "Output";
output_node->set_name("Output");
add_node(output_node);
}
@ -432,7 +446,7 @@ int BLTAnimationNodeBlendTree::BLTBlendTreeGraph::find_node_index(const Ref<BLTA
int BLTAnimationNodeBlendTree::BLTBlendTreeGraph::find_node_index_by_name(const StringName &name) const {
for (int i = 0; i < nodes.size(); i++) {
if (nodes[i]->name == name) {
if (nodes[i]->get_name() == name) {
return i;
}
}
@ -441,15 +455,15 @@ int BLTAnimationNodeBlendTree::BLTBlendTreeGraph::find_node_index_by_name(const
}
void BLTAnimationNodeBlendTree::BLTBlendTreeGraph::add_node(const Ref<BLTAnimationNode> &node) {
StringName node_base_name = node->name;
StringName node_base_name = node->get_name();
if (node_base_name.is_empty()) {
node_base_name = node->get_class_name();
}
node->name = node_base_name;
node->set_name(node_base_name);
int number_suffix = 1;
while (find_node_index_by_name(node->name) != -1) {
node->name = vformat("%s %d", node_base_name, number_suffix);
while (find_node_index_by_name(node->get_name()) != -1) {
node->set_name(vformat("%s %d", node_base_name, number_suffix));
number_suffix++;
}
@ -546,8 +560,7 @@ BLTAnimationNodeBlendTree::ConnectionError BLTAnimationNodeBlendTree::BLTBlendTr
return CONNECTION_ERROR_NO_TARGET_NODE;
}
Vector<StringName> target_inputs;
target_node->get_input_names(target_inputs);
Vector<StringName> target_inputs = target_node->get_input_names();
if (!target_inputs.has(target_port_name)) {
print_error("Cannot connect nodes: target port not found.");

View File

@ -262,7 +262,6 @@ public:
NodeTimeInfo node_time_info;
bool active = false;
StringName name;
Vector2 position;
virtual ~BLTAnimationNode() override = default;
@ -285,6 +284,7 @@ public:
node_time_info.loop_mode = input_nodes[0]->node_time_info.loop_mode;
}
}
virtual void update_time(double p_time) {
if (node_time_info.is_synced) {
node_time_info.sync_position = p_time;
@ -293,6 +293,7 @@ public:
node_time_info.position += p_time;
}
}
virtual void evaluate(GraphEvaluationContext &context, const LocalVector<AnimationData *> &input_datas, AnimationData &output_data) {
// By default, use the AnimationData of the first input.
if (input_datas.size() > 0) {
@ -300,16 +301,32 @@ public:
}
}
virtual void get_input_names(Vector<StringName> &inputs) const {}
void set_position(const Vector2 &p_position) {
position = p_position;
}
Vector2 get_position() const {
return position;
}
virtual Vector<StringName> get_input_names() const { return {}; }
TypedArray<StringName> get_input_names_as_typed_array() const {
TypedArray<StringName> typed_arr;
Vector<StringName> vec = get_input_names();
typed_arr.resize(vec.size());
for (uint32_t i = 0; i < vec.size(); i++) {
typed_arr[i] = vec[i];
}
return typed_arr;
}
int get_input_index(const StringName &port_name) const {
Vector<StringName> inputs;
get_input_names(inputs);
Vector<StringName> inputs = get_input_names();
return inputs.find(port_name);
}
int get_input_count() const {
Vector<StringName> inputs;
get_input_names(inputs);
Vector<StringName> inputs = get_input_names();
return inputs.size();
}
@ -341,8 +358,8 @@ class BLTAnimationNodeOutput : public BLTAnimationNode {
GDCLASS(BLTAnimationNodeOutput, BLTAnimationNode);
public:
void get_input_names(Vector<StringName> &inputs) const override {
inputs.push_back("Input");
Vector<StringName> get_input_names() const override {
return { "Output" };
}
};
@ -353,20 +370,21 @@ public:
float blend_weight = 0.0f;
bool sync = true;
void get_input_names(Vector<StringName> &inputs) const override {
inputs.push_back("Input0");
inputs.push_back("Input1");
Vector<StringName> get_input_names() const override {
return { "Input0", "Input1" };
}
bool initialize(GraphEvaluationContext &context) override {
bool result = BLTAnimationNode::initialize(context);
if (!BLTAnimationNode::initialize(context)) {
return false;
}
if (sync) {
// TODO: do we always want looping in this case or do we traverse the graph to check what's reasonable?
node_time_info.loop_mode = Animation::LOOP_LINEAR;
}
return result;
return true;
}
void activate_inputs(const Vector<Ref<BLTAnimationNode>> &input_nodes) override {
input_nodes[0]->active = true;
@ -549,10 +567,6 @@ public:
};
LocalVector<NodeRuntimeData> _node_runtime_data;
Ref<BLTAnimationNode> get_output_node() const {
return tree_graph.nodes[0];
}
int find_node_index(const Ref<BLTAnimationNode> &node) const {
return tree_graph.find_node_index(node);
}
@ -561,14 +575,6 @@ public:
return tree_graph.find_node_index_by_name(name);
}
Ref<BLTAnimationNode> get_node(int node_index) {
if (node_index < 0 || node_index > tree_graph.nodes.size()) {
return nullptr;
}
return tree_graph.nodes[node_index];
}
void add_node(const Ref<BLTAnimationNode> &node) {
if (tree_initialized) {
print_error("Cannot add node to BlendTree: BlendTree already initialized.");
@ -578,6 +584,51 @@ public:
tree_graph.add_node(node);
}
TypedArray<StringName> get_node_names_as_typed_array() const {
Vector<StringName> vec;
for (const Ref<BLTAnimationNode> &node : tree_graph.nodes) {
vec.push_back(node->get_name());
}
TypedArray<StringName> typed_arr;
typed_arr.resize(vec.size());
for (uint32_t i = 0; i < vec.size(); i++) {
typed_arr[i] = vec[i];
}
return typed_arr;
}
Ref<BLTAnimationNode> get_node(const StringName &node_name) const {
int node_index = tree_graph.find_node_index_by_name(node_name);
if (node_index >= 0) {
return tree_graph.nodes[node_index];
}
return nullptr;
}
Ref<BLTAnimationNode> get_node_by_index(int node_index) const {
if (node_index < 0 || node_index > tree_graph.nodes.size()) {
return nullptr;
}
return tree_graph.nodes[node_index];
}
Ref<BLTAnimationNode> get_output_node() const {
return tree_graph.nodes[0];
}
ConnectionError is_connection_valid(const Ref<BLTAnimationNode> &source_node, const Ref<BLTAnimationNode> &target_node, const StringName &target_port_name) {
if (tree_initialized) {
print_error("Cannot add connection to BlendTree: BlendTree already initialized.");
return CONNECTION_ERROR_GRAPH_ALREADY_INITIALIZED;
}
return tree_graph.is_connection_valid(source_node, target_node, target_port_name);
}
ConnectionError add_connection(const Ref<BLTAnimationNode> &source_node, const Ref<BLTAnimationNode> &target_node, const StringName &target_port_name) {
if (tree_initialized) {
print_error("Cannot add connection to BlendTree: BlendTree already initialized.");
@ -587,8 +638,23 @@ public:
return tree_graph.add_connection(source_node, target_node, target_port_name);
}
Array get_connections_as_array() const {
Array result;
for (const BLTBlendTreeConnection &connection : tree_graph.connections) {
result.push_back(connection.source_node);
result.push_back(connection.target_node);
result.push_back(connection.target_port_name);
}
return result;
}
// overrides from SyncedAnimationNode
bool initialize(GraphEvaluationContext &context) override {
if (!BLTAnimationNode::initialize(context)) {
return false;
}
sort_nodes();
setup_runtime_data();

View File

@ -146,27 +146,27 @@ TEST_CASE("[SyncedAnimationGraph] Test BlendTree construction") {
Ref<BLTAnimationNodeSampler> animation_sampler_node0;
animation_sampler_node0.instantiate();
animation_sampler_node0->name = "Sampler0";
animation_sampler_node0->set_name("Sampler0");
tree_constructor.add_node(animation_sampler_node0);
Ref<BLTAnimationNodeSampler> animation_sampler_node1;
animation_sampler_node1.instantiate();
animation_sampler_node1->name = "Sampler1";
animation_sampler_node1->set_name("Sampler1");
tree_constructor.add_node(animation_sampler_node1);
Ref<BLTAnimationNodeSampler> animation_sampler_node2;
animation_sampler_node2.instantiate();
animation_sampler_node2->name = "Sampler2";
animation_sampler_node2->set_name("Sampler2");
tree_constructor.add_node(animation_sampler_node2);
Ref<BLTAnimationNodeBlend2> node_blend0;
node_blend0.instantiate();
node_blend0->name = "Blend0";
node_blend0->set_name("Blend0");
tree_constructor.add_node(node_blend0);
Ref<BLTAnimationNodeBlend2> node_blend1;
node_blend1.instantiate();
node_blend1->name = "Blend1";
node_blend1->set_name("Blend1");
tree_constructor.add_node(node_blend1);
// Tree
@ -201,7 +201,7 @@ TEST_CASE("[SyncedAnimationGraph] Test BlendTree construction") {
CHECK(tree_constructor.node_connection_info[blend1_index].input_subtree_node_indices.has(blend0_index));
// Perform remaining connections
CHECK(BLTAnimationNodeBlendTree::CONNECTION_OK == tree_constructor.add_connection(node_blend1, tree_constructor.get_output_node(), "Input"));
CHECK(BLTAnimationNodeBlendTree::CONNECTION_OK == tree_constructor.add_connection(node_blend1, tree_constructor.get_output_node(), "Output"));
CHECK(BLTAnimationNodeBlendTree::CONNECTION_OK == tree_constructor.add_connection(animation_sampler_node2, node_blend1, "Input1"));
// Output node must have all nodes in its subtree:
@ -287,7 +287,7 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph
animation_sampler_node->animation_name = "animation_library/TestAnimationA";
synced_blend_tree_node->add_node(animation_sampler_node);
REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == synced_blend_tree_node->add_connection(animation_sampler_node, synced_blend_tree_node->get_output_node(), "Input"));
REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == synced_blend_tree_node->add_connection(animation_sampler_node, synced_blend_tree_node->get_output_node(), "Output"));
synced_blend_tree_node->initialize(synced_animation_graph->get_context());
@ -329,18 +329,17 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph
// Blend2
Ref<BLTAnimationNodeBlend2> blend2_node;
blend2_node.instantiate();
blend2_node->name = "Blend2";
blend2_node->set_name("Blend2");
blend2_node->blend_weight = 0.5;
blend2_node->sync = false;
synced_blend_tree_node->add_node(blend2_node);
// Connect nodes
Vector<StringName> blend2_inputs;
blend2_node->get_input_names(blend2_inputs);
Vector<StringName> blend2_inputs = blend2_node->get_input_names();
REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == synced_blend_tree_node->add_connection(animation_sampler_node_a, blend2_node, blend2_inputs[0]));
REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == synced_blend_tree_node->add_connection(animation_sampler_node_b, blend2_node, blend2_inputs[1]));
REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == synced_blend_tree_node->add_connection(blend2_node, synced_blend_tree_node->get_output_node(), "Input"));
REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == synced_blend_tree_node->add_connection(blend2_node, synced_blend_tree_node->get_output_node(), "Output"));
synced_blend_tree_node->initialize(synced_animation_graph->get_context());
@ -437,7 +436,7 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph
Ref<BLTAnimationNodeBlendTree> loaded_synced_blend_tree = ResourceLoader::load("synced_blend_tree_node.tres");
REQUIRE(loaded_synced_blend_tree.is_valid());
Ref<BLTAnimationNodeBlend2> loaded_blend2_node = loaded_synced_blend_tree->get_node(loaded_synced_blend_tree->find_node_index_by_name("Blend2"));
Ref<BLTAnimationNodeBlend2> loaded_blend2_node = loaded_synced_blend_tree->get_node_by_index(loaded_synced_blend_tree->find_node_index_by_name("Blend2"));
REQUIRE(loaded_blend2_node.is_valid());
CHECK(loaded_blend2_node->sync == false);
CHECK(loaded_blend2_node->blend_weight == blend2_node->blend_weight);