diff --git a/spine-godot/example/examples/01-helloworld/helloworld.tscn b/spine-godot/example/examples/01-helloworld/helloworld.tscn index 463463de1..fae49da0c 100644 --- a/spine-godot/example/examples/01-helloworld/helloworld.tscn +++ b/spine-godot/example/examples/01-helloworld/helloworld.tscn @@ -6,10 +6,14 @@ [node name="Node2D" type="Node2D"] [node name="Spineboy" type="SpineSprite" parent="."] -position = Vector2( 496.207, 477.185 ) +position = Vector2( 512, 324 ) scale = Vector2( 0.466832, 0.466832 ) skeleton_data_res = ExtResource( 3 ) -preview_animation = "-- Empty --" -preview_frame = false -preview_time = 0.0 +bones = true +bones_color = Color( 0.968627, 1, 0, 0.501961 ) +paths_color = Color( 1, 0.498039, 0, 0.466667 ) +paths_clipping = Color( 0.8, 0, 0, 0.5 ) +preview_animation = "portal" +preview_frame = true +preview_time = 2.18 script = ExtResource( 1 ) diff --git a/spine-godot/example/project.godot b/spine-godot/example/project.godot index 9716ff8a6..d1b46851c 100644 --- a/spine-godot/example/project.godot +++ b/spine-godot/example/project.godot @@ -11,7 +11,7 @@ config_version=4 [application] config/name="spine-godot-examples" -run/main_scene="res://examples/11-bone-node/bone-node.tscn" +run/main_scene="res://examples/01-helloworld/helloworld.tscn" run/low_processor_mode=true config/icon="res://icon.png" diff --git a/spine-godot/example/tests/ragdoll.tscn b/spine-godot/example/tests/ragdoll.tscn new file mode 100644 index 000000000..9f9abfb11 --- /dev/null +++ b/spine-godot/example/tests/ragdoll.tscn @@ -0,0 +1,53 @@ +[gd_scene format=2] + +[node name="Node2D" type="Node2D"] + +[node name="A" type="RigidBody2D" parent="."] +position = Vector2( 483, 158 ) + +[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="A"] +position = Vector2( 6, 17 ) +polygon = PoolVector2Array( -38, 6, 28, 6, 28, -45, -40, -45 ) +__meta__ = { +"_edit_lock_": true +} + +[node name="B" type="RigidBody2D" parent="."] +position = Vector2( 484, 228 ) + +[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="B"] +position = Vector2( 6, 17 ) +polygon = PoolVector2Array( -38, 6, 28, 6, 28, -45, -40, -45 ) +__meta__ = { +"_edit_lock_": true +} + +[node name="C" type="RigidBody2D" parent="."] +position = Vector2( 485, 296 ) + +[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="C"] +position = Vector2( 6, 17 ) +polygon = PoolVector2Array( -38, 6, 28, 6, 28, -45, -40, -45 ) +__meta__ = { +"_edit_lock_": true +} + +[node name="PinJoint2D" type="PinJoint2D" parent="."] +position = Vector2( 484, 189 ) +node_a = NodePath("../A") +node_b = NodePath("../B") +bias = 0.9 +disable_collision = false + +[node name="PinJoint2D2" type="PinJoint2D" parent="."] +position = Vector2( 486, 257 ) +node_a = NodePath("../B") +node_b = NodePath("../C") +bias = 0.9 +disable_collision = false + +[node name="Ground" type="StaticBody2D" parent="."] +position = Vector2( 489, 478 ) + +[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="Ground"] +polygon = PoolVector2Array( -116, -4, 128, -100, 204, 34, -156, 48 ) diff --git a/spine-godot/spine_godot/SpineSprite.cpp b/spine-godot/spine_godot/SpineSprite.cpp index 4350831cc..1343c8853 100644 --- a/spine-godot/spine_godot/SpineSprite.cpp +++ b/spine-godot/spine_godot/SpineSprite.cpp @@ -33,6 +33,12 @@ #include "SpineSkeleton.h" #include "SpineRendererObject.h" #include "SpineSlotNode.h" +#include "core/engine.h" +#include "scene/gui/control.h" +#include "scene/main/viewport.h" +#if TOOLS_ENABLED +#include "editor/editor_plugin.h" +#endif Ref SpineSprite::default_materials[4] = {}; static int sprite_count = 0; @@ -65,6 +71,33 @@ void SpineSprite::_bind_methods() { ClassDB::bind_method(D_METHOD("set_screen_material", "material"), &SpineSprite::set_screen_material); ClassDB::bind_method(D_METHOD("get_screen_material"), &SpineSprite::get_screen_material); + ClassDB::bind_method(D_METHOD("set_debug_bones", "v"), &SpineSprite::set_debug_bones); + ClassDB::bind_method(D_METHOD("get_debug_bones"), &SpineSprite::get_debug_bones); + ClassDB::bind_method(D_METHOD("set_debug_bones_color", "v"), &SpineSprite::set_debug_bones_color); + ClassDB::bind_method(D_METHOD("get_debug_bones_color"), &SpineSprite::get_debug_bones_color); + ClassDB::bind_method(D_METHOD("set_debug_bones_thickness", "v"), &SpineSprite::set_debug_bones_thickness); + ClassDB::bind_method(D_METHOD("get_debug_bones_thickness"), &SpineSprite::get_debug_bones_thickness); + ClassDB::bind_method(D_METHOD("set_debug_regions", "v"), &SpineSprite::set_debug_regions); + ClassDB::bind_method(D_METHOD("get_debug_regions"), &SpineSprite::get_debug_regions); + ClassDB::bind_method(D_METHOD("set_debug_regions_color", "v"), &SpineSprite::set_debug_regions_color); + ClassDB::bind_method(D_METHOD("get_debug_regions_color"), &SpineSprite::get_debug_regions_color); + ClassDB::bind_method(D_METHOD("set_debug_meshes", "v"), &SpineSprite::set_debug_meshes); + ClassDB::bind_method(D_METHOD("get_debug_meshes"), &SpineSprite::get_debug_meshes); + ClassDB::bind_method(D_METHOD("set_debug_meshes_color", "v"), &SpineSprite::set_debug_meshes_color); + ClassDB::bind_method(D_METHOD("get_debug_meshes_color"), &SpineSprite::get_debug_meshes_color); + ClassDB::bind_method(D_METHOD("set_debug_bounding_boxes", "v"), &SpineSprite::set_debug_bounding_boxes); + ClassDB::bind_method(D_METHOD("get_debug_bounding_boxes"), &SpineSprite::get_debug_bounding_boxes); + ClassDB::bind_method(D_METHOD("set_debug_bounding_boxes_color", "v"), &SpineSprite::set_debug_bounding_boxes_color); + ClassDB::bind_method(D_METHOD("get_debug_bounding_boxes_color"), &SpineSprite::get_debug_bounding_boxes_color); + ClassDB::bind_method(D_METHOD("set_debug_paths", "v"), &SpineSprite::set_debug_paths); + ClassDB::bind_method(D_METHOD("get_debug_paths"), &SpineSprite::get_debug_paths); + ClassDB::bind_method(D_METHOD("set_debug_paths_color", "v"), &SpineSprite::set_debug_paths_color); + ClassDB::bind_method(D_METHOD("get_debug_paths_color"), &SpineSprite::get_debug_paths_color); + ClassDB::bind_method(D_METHOD("set_debug_clipping", "v"), &SpineSprite::set_debug_clipping); + ClassDB::bind_method(D_METHOD("get_debug_clipping"), &SpineSprite::get_debug_clipping); + ClassDB::bind_method(D_METHOD("set_debug_clipping_color", "v"), &SpineSprite::set_debug_clipping_color); + ClassDB::bind_method(D_METHOD("get_debug_clipping_color"), &SpineSprite::get_debug_clipping_color); + ClassDB::bind_method(D_METHOD("update_skeleton", "delta"), &SpineSprite::update_skeleton); ClassDB::bind_method(D_METHOD("new_skin", "name"), &SpineSprite::new_skin); @@ -87,7 +120,24 @@ void SpineSprite::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "additive_material", PROPERTY_HINT_RESOURCE_TYPE, "Material"), "set_additive_material", "get_additive_material"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "multiply_material", PROPERTY_HINT_RESOURCE_TYPE, "Material"), "set_multiply_material", "get_multiply_material"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "screen_material", PROPERTY_HINT_RESOURCE_TYPE, "Material"), "set_screen_material", "get_screen_material"); + + ADD_GROUP("Debug", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bones"), "set_debug_bones", "get_debug_bones"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "bones_color"), "set_debug_bones_color", "get_debug_bones_color"); + ADD_PROPERTY(PropertyInfo(VARIANT_FLOAT, "bones_thickness"), "set_debug_bones_thickness", "get_debug_bones_thickness"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "regions"), "set_debug_regions", "get_debug_regions"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "regions_color"), "set_debug_regions_color", "get_debug_regions_color"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "meshes"), "set_debug_meshes", "get_debug_meshes"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "meshes_color"), "set_debug_meshes_color", "get_debug_meshes_color"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bounding_boxes"), "set_debug_bounding_boxes", "get_debug_bounding_boxes"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "bounding_boxes_color"), "set_debug_bounding_boxes_color", "get_debug_bounding_boxes_color"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "paths"), "set_debug_paths", "get_debug_paths"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "paths_color"), "set_debug_paths_color", "get_debug_paths_color"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clipping"), "set_debug_clipping", "get_debug_clipping"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "paths_clipping"), "set_debug_clipping_color", "get_debug_clipping_color"); + ADD_GROUP("Preview", ""); + // Filled in in _get_property_list() } SpineSprite::SpineSprite() : update_mode(SpineConstant::UpdateMode_Process), preview_animation("-- Empty --"), preview_frame(false), preview_time(0), skeleton_clipper(nullptr), modified_bones(false) { @@ -123,6 +173,22 @@ SpineSprite::SpineSprite() : update_mode(SpineConstant::UpdateMode_Process), pre quad_indices[5] = 0; scratch_vertices.ensureCapacity(1200); } + + // Default debug settings + debug_bones = false; + debug_bones_color = Color(1, 0, 0, 0.5); + debug_bones_thickness = 5; + debug_regions = false; + debug_regions_color = Color(0, 0, 1, 0.8); + debug_meshes = false; + debug_meshes_color = Color(0, 0, 1, 0.8); + debug_bounding_boxes = false; + debug_bounding_boxes_color = Color(0, 1, 0, 0.8); + debug_paths = false; + debug_paths_color = Color::hex(0xff7f0077); + debug_clipping = false; + debug_clipping_color = Color(0.8, 0, 0, 0.8); + sprite_count++; } @@ -188,6 +254,8 @@ void SpineSprite::generate_meshes_for_slots(Ref skeleton_ref) { auto mesh_instance = memnew(MeshInstance2D); mesh_instance->set_position(Vector2(0, 0)); mesh_instance->set_material(default_materials[spine::BlendMode_Normal]); + // Needed so that debug drawables are rendered in front of attachments + mesh_instance->set_draw_behind_parent(true); add_child(mesh_instance); mesh_instances.push_back(mesh_instance); slot_nodes.add(spine::Vector()); @@ -254,6 +322,10 @@ void SpineSprite::_notification(int what) { update_skeleton(get_physics_process_delta_time()); break; } + case NOTIFICATION_DRAW: { + draw(); + break; + } default: break; } @@ -541,6 +613,161 @@ void SpineSprite::update_meshes(Ref skeleton_ref) { skeleton_clipper->clipEnd(); } +void SpineSprite::draw() { + if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_collisions_hint()) return; + if (!animation_state.is_valid() && !skeleton.is_valid()) return; + + auto mouse_position = get_local_mouse_position(); + + if (debug_regions) { + draw_set_transform(Vector2(0, 0), 0, Vector2(1, 1)); + auto &draw_order = skeleton->get_spine_object()->getDrawOrder(); + for (int i = 0; i < (int)draw_order.size(); i++) { + auto *slot = draw_order[i]; + if (!slot->getBone().isActive()) continue; + auto *attachment = slot->getAttachment(); + if (!attachment) continue; + if (!attachment->getRTTI().isExactly(spine::RegionAttachment::rtti)) continue; + auto *region = (spine::RegionAttachment*)attachment; + auto *vertices = &scratch_vertices; + vertices->setSize(8, 0); + region->computeWorldVertices(*slot, *vertices, 0); + scratch_points.resize(0); + for (int i = 0, j = 0; i < 4; i++, j += 2) { + float x = vertices->buffer()[j]; + float y = vertices->buffer()[j + 1]; + scratch_points.push_back(Vector2(x, y)); + } + scratch_points.push_back(Vector2(vertices->buffer()[0], vertices->buffer()[1])); + draw_polyline(scratch_points, debug_meshes_color, 2); + } + } + + if (debug_meshes) { + draw_set_transform(Vector2(0, 0), 0, Vector2(1, 1)); + auto &draw_order = skeleton->get_spine_object()->getDrawOrder(); + for (int i = 0; i < (int)draw_order.size(); i++) { + auto *slot = draw_order[i]; + if (!slot->getBone().isActive()) continue; + auto *attachment = slot->getAttachment(); + if (!attachment) continue; + if (!attachment->getRTTI().isExactly(spine::MeshAttachment::rtti)) continue; + auto *mesh = (spine::MeshAttachment*)attachment; + auto *vertices = &scratch_vertices; + vertices->setSize(mesh->getWorldVerticesLength(), 0); + mesh->computeWorldVertices(*slot, *vertices); + scratch_points.resize(0); + for (int i = 0, j = 0; i < mesh->getHullLength(); i++, j += 2) { + float x = vertices->buffer()[j]; + float y = vertices->buffer()[j + 1]; + scratch_points.push_back(Vector2(x, y)); + } + scratch_points.push_back(Vector2(vertices->buffer()[0], vertices->buffer()[1])); + draw_polyline(scratch_points, debug_meshes_color, 2); + } + } + + if (debug_bounding_boxes) { + draw_set_transform(Vector2(0, 0), 0, Vector2(1, 1)); + auto &draw_order = skeleton->get_spine_object()->getDrawOrder(); + for (int i = 0; i < (int)draw_order.size(); i++) { + auto *slot = draw_order[i]; + if (!slot->getBone().isActive()) continue; + auto *attachment = slot->getAttachment(); + if (!attachment) continue; + if (!attachment->getRTTI().isExactly(spine::BoundingBoxAttachment::rtti)) continue; + auto *bounding_box = (spine::BoundingBoxAttachment*)attachment; + auto *vertices = &scratch_vertices; + vertices->setSize(bounding_box->getWorldVerticesLength(), 0); + bounding_box->computeWorldVertices(*slot, *vertices); + size_t num_vertices = vertices->size() / 2; + scratch_points.resize((int)num_vertices); + memcpy(scratch_points.ptrw(), vertices->buffer(), num_vertices * 2 * sizeof(float)); + scratch_points.push_back(Vector2(vertices->buffer()[0], vertices->buffer()[1])); + draw_polyline(scratch_points, debug_bounding_boxes_color, 2); + } + } + + if (debug_clipping) { + draw_set_transform(Vector2(0, 0), 0, Vector2(1, 1)); + auto &draw_order = skeleton->get_spine_object()->getDrawOrder(); + for (int i = 0; i < (int)draw_order.size(); i++) { + auto *slot = draw_order[i]; + if (!slot->getBone().isActive()) continue; + auto *attachment = slot->getAttachment(); + if (!attachment) continue; + if (!attachment->getRTTI().isExactly(spine::ClippingAttachment::rtti)) continue; + auto *clipping = (spine::ClippingAttachment*)attachment; + auto *vertices = &scratch_vertices; + vertices->setSize(clipping->getWorldVerticesLength(), 0); + clipping->computeWorldVertices(*slot, *vertices); + size_t num_vertices = vertices->size() / 2; + scratch_points.resize((int)num_vertices); + memcpy(scratch_points.ptrw(), vertices->buffer(), num_vertices * 2 * sizeof(float)); + scratch_points.push_back(Vector2(vertices->buffer()[0], vertices->buffer()[1])); + draw_polyline(scratch_points, debug_clipping_color, 2); + } + } + + + spine::Bone *hovered_bone = nullptr; + if (debug_bones) { + float hovered_bone_distance = FLT_MAX; + auto &bones = skeleton->get_spine_object()->getBones(); + for (int i = 0; i < (int)bones.size(); i++) { + auto *bone = bones[i]; + if (!bone->isActive()) continue; + draw_bone(bone, debug_bones_color); + + float bone_length = bone->getData().getLength(); + if (bone_length == 0) bone_length = debug_bones_thickness * 2; + + scratch_points.resize(5); + scratch_points.set(0, Vector2(-debug_bones_thickness, 0)); + scratch_points.set(1, Vector2(0, debug_bones_thickness)); + scratch_points.set(2, Vector2(bone_length, 0)); + scratch_points.set(3, Vector2(0, -debug_bones_thickness)); + scratch_points.set(4, Vector2(-debug_bones_thickness, 0)); + Transform2D bone_transform(Math::deg2rad(bone->getWorldRotationX()), Vector2(bone->getWorldX(), bone->getWorldY())); + bone_transform.scale_basis(Vector2(bone->getWorldScaleX(), bone->getWorldScaleY())); + auto mouse_local_position = bone_transform.affine_inverse().xform(mouse_position); + if (Geometry::is_point_in_polygon(mouse_local_position, scratch_points)) { + hovered_bone = bone; + } + } + } + +#if TOOLS_ENABLED + if (hovered_bone) { + Ref default_font; + auto control = memnew(Control); + default_font = control->get_font("font", "Label"); + memfree(control); + float thickness = debug_bones_thickness; + debug_bones_thickness *= 1.1; + draw_bone(hovered_bone, Color(debug_bones_color.r, debug_bones_color.g, debug_bones_color.b, 1)); + debug_bones_thickness = thickness; + float editor_scale = EditorInterface::get_singleton()->get_editor_scale(); + float inverse_zoom = 1 / get_viewport()->get_global_canvas_transform().get_scale().x * editor_scale * 2.5; + draw_set_transform(Vector2(hovered_bone->getWorldX(), hovered_bone->getWorldY()), 0, Vector2(inverse_zoom, inverse_zoom)); + draw_string(default_font, Vector2(11, 1), hovered_bone->getData().getName().buffer(), Color(0, 0, 0, 1)); + draw_string(default_font, Vector2(10, 0), hovered_bone->getData().getName().buffer()); + } +#endif +} + +void SpineSprite::draw_bone(spine::Bone* bone, const Color &color) { + draw_set_transform(Vector2(bone->getWorldX(), bone->getWorldY()), Math::deg2rad(bone->getWorldRotationX()), Vector2(bone->getWorldScaleX(), bone->getWorldScaleY())); + float bone_length = bone->getData().getLength(); + if (bone_length == 0) bone_length = debug_bones_thickness * 2; + Vector points; + points.push_back(Vector2(-debug_bones_thickness, 0)); + points.push_back(Vector2(0, debug_bones_thickness)); + points.push_back(Vector2(bone_length, 0)); + points.push_back(Vector2(0, -debug_bones_thickness)); + draw_colored_polygon(points, color); +} + void SpineSprite::callback(spine::AnimationState *state, spine::EventType type, spine::TrackEntry *entry, spine::Event *event) { Ref entry_ref = Ref(memnew(SpineTrackEntry)); entry_ref->set_spine_object(this, entry); diff --git a/spine-godot/spine_godot/SpineSprite.h b/spine-godot/spine_godot/SpineSprite.h index cb2a8983b..386e85fed 100644 --- a/spine-godot/spine_godot/SpineSprite.h +++ b/spine-godot/spine_godot/SpineSprite.h @@ -33,7 +33,6 @@ #include "SpineAnimationState.h" #include "scene/2d/node_2d.h" #include "scene/2d/mesh_instance_2d.h" -#include "scene/resources/texture.h" class SpineSlotNode; @@ -52,6 +51,20 @@ protected: bool preview_frame; float preview_time; + bool debug_bones; + Color debug_bones_color; + float debug_bones_thickness; + bool debug_regions; + Color debug_regions_color; + bool debug_meshes; + Color debug_meshes_color; + bool debug_bounding_boxes; + Color debug_bounding_boxes_color; + bool debug_paths; + Color debug_paths_color; + bool debug_clipping; + Color debug_clipping_color; + spine::Vector > slot_nodes; Vector mesh_instances; static Ref default_materials[4]; @@ -73,6 +86,8 @@ protected: void sort_slot_nodes(); void update_meshes(Ref skeleton_ref); void set_modified_bones() { modified_bones = true; } + void draw(); + void draw_bone(spine::Bone *bone, const Color &color); void callback(spine::AnimationState *state, spine::EventType type, spine::TrackEntry *entry, spine::Event *event); @@ -118,6 +133,58 @@ public: void set_screen_material(Ref material); + bool get_debug_bones() { return debug_bones; } + + void set_debug_bones (bool bones) { debug_bones = bones; } + + Color get_debug_bones_color() { return debug_bones_color; } + + void set_debug_bones_color(const Color &color) { debug_bones_color = color; } + + float get_debug_bones_thickness() { return debug_bones_thickness; } + + void set_debug_bones_thickness(float thickness) { debug_bones_thickness = thickness; } + + bool get_debug_regions() { return debug_regions; } + + void set_debug_regions(bool regions) { debug_regions = regions; } + + Color get_debug_regions_color() { return debug_regions_color; } + + void set_debug_regions_color(const Color &color) { debug_regions_color = color; } + + bool get_debug_meshes() { return debug_meshes; } + + void set_debug_meshes(bool meshes) { debug_meshes = meshes; } + + Color get_debug_meshes_color() { return debug_meshes_color; } + + void set_debug_meshes_color(const Color &color) { debug_meshes_color = color; } + + bool get_debug_paths() { return debug_paths; } + + void set_debug_paths(bool paths) { debug_paths = paths; } + + Color get_debug_paths_color() { return debug_paths_color; } + + void set_debug_paths_color(const Color &color) { debug_paths_color = color; } + + bool get_debug_bounding_boxes() { return debug_bounding_boxes; } + + void set_debug_bounding_boxes(bool paths) { debug_bounding_boxes = paths; } + + Color get_debug_bounding_boxes_color() { return debug_bounding_boxes_color; } + + void set_debug_bounding_boxes_color(const Color &color) { debug_bounding_boxes_color = color; } + + bool get_debug_clipping() { return debug_clipping; } + + void set_debug_clipping(bool clipping) { debug_clipping = clipping; } + + Color get_debug_clipping_color() { return debug_clipping_color; } + + void set_debug_clipping_color(const Color &color) { debug_clipping_color = color; } + #ifdef TOOLS_ENABLED virtual Rect2 _edit_get_rect() const; virtual bool _edit_use_rect() const;