diff options
Diffstat (limited to 'scene/2d')
-rw-r--r-- | scene/2d/collision_object_2d.cpp | 4 | ||||
-rw-r--r-- | scene/2d/collision_object_2d.h | 3 | ||||
-rw-r--r-- | scene/2d/collision_polygon_2d.cpp | 2 | ||||
-rw-r--r-- | scene/2d/collision_shape_2d.cpp | 2 | ||||
-rw-r--r-- | scene/2d/node_2d.cpp | 15 | ||||
-rw-r--r-- | scene/2d/node_2d.h | 4 | ||||
-rw-r--r-- | scene/2d/physical_bone_2d.cpp | 303 | ||||
-rw-r--r-- | scene/2d/physical_bone_2d.h (renamed from scene/2d/y_sort.h) | 63 | ||||
-rw-r--r-- | scene/2d/physics_body_2d.cpp | 594 | ||||
-rw-r--r-- | scene/2d/physics_body_2d.h | 121 | ||||
-rw-r--r-- | scene/2d/position_2d.cpp | 39 | ||||
-rw-r--r-- | scene/2d/skeleton_2d.cpp | 531 | ||||
-rw-r--r-- | scene/2d/skeleton_2d.h | 53 | ||||
-rw-r--r-- | scene/2d/tile_map.cpp | 6 | ||||
-rw-r--r-- | scene/2d/tile_map.h | 1 | ||||
-rw-r--r-- | scene/2d/visibility_notifier_2d.cpp | 2 | ||||
-rw-r--r-- | scene/2d/y_sort.cpp | 52 |
17 files changed, 1420 insertions, 375 deletions
diff --git a/scene/2d/collision_object_2d.cpp b/scene/2d/collision_object_2d.cpp index de648d404c..a633923be7 100644 --- a/scene/2d/collision_object_2d.cpp +++ b/scene/2d/collision_object_2d.cpp @@ -408,6 +408,10 @@ void CollisionObject2D::set_only_update_transform_changes(bool p_enable) { only_update_transform_changes = p_enable; } +bool CollisionObject2D::is_only_update_transform_changes_enabled() const { + return only_update_transform_changes; +} + void CollisionObject2D::_update_pickable() { if (!is_inside_tree()) { return; diff --git a/scene/2d/collision_object_2d.h b/scene/2d/collision_object_2d.h index bb1a9dfcf5..e10f3097d9 100644 --- a/scene/2d/collision_object_2d.h +++ b/scene/2d/collision_object_2d.h @@ -62,7 +62,7 @@ class CollisionObject2D : public Node2D { int total_subshapes = 0; Map<uint32_t, ShapeData> shapes; - bool only_update_transform_changes = false; //this is used for sync physics in KinematicBody + bool only_update_transform_changes = false; //this is used for sync physics in CharacterBody2D protected: CollisionObject2D(RID p_rid, bool p_area); @@ -77,6 +77,7 @@ protected: void _mouse_exit(); void set_only_update_transform_changes(bool p_enable); + bool is_only_update_transform_changes_enabled() const; public: void set_collision_layer(uint32_t p_layer); diff --git a/scene/2d/collision_polygon_2d.cpp b/scene/2d/collision_polygon_2d.cpp index a69ef73a54..2a2fde80e2 100644 --- a/scene/2d/collision_polygon_2d.cpp +++ b/scene/2d/collision_polygon_2d.cpp @@ -244,7 +244,7 @@ TypedArray<String> CollisionPolygon2D::get_configuration_warnings() const { TypedArray<String> warnings = Node::get_configuration_warnings(); if (!Object::cast_to<CollisionObject2D>(get_parent())) { - warnings.push_back(TTR("CollisionPolygon2D only serves to provide a collision shape to a CollisionObject2D derived node. Please only use it as a child of Area2D, StaticBody2D, RigidBody2D, KinematicBody2D, etc. to give them a shape.")); + warnings.push_back(TTR("CollisionPolygon2D only serves to provide a collision shape to a CollisionObject2D derived node. Please only use it as a child of Area2D, StaticBody2D, RigidBody2D, CharacterBody2D, etc. to give them a shape.")); } int polygon_count = polygon.size(); diff --git a/scene/2d/collision_shape_2d.cpp b/scene/2d/collision_shape_2d.cpp index d9009ef85c..60780f1cc3 100644 --- a/scene/2d/collision_shape_2d.cpp +++ b/scene/2d/collision_shape_2d.cpp @@ -181,7 +181,7 @@ TypedArray<String> CollisionShape2D::get_configuration_warnings() const { TypedArray<String> warnings = Node::get_configuration_warnings(); if (!Object::cast_to<CollisionObject2D>(get_parent())) { - warnings.push_back(TTR("CollisionShape2D only serves to provide a collision shape to a CollisionObject2D derived node. Please only use it as a child of Area2D, StaticBody2D, RigidBody2D, KinematicBody2D, etc. to give them a shape.")); + warnings.push_back(TTR("CollisionShape2D only serves to provide a collision shape to a CollisionObject2D derived node. Please only use it as a child of Area2D, StaticBody2D, RigidBody2D, CharacterBody2D, etc. to give them a shape.")); } if (!shape.is_valid()) { warnings.push_back(TTR("A shape must be provided for CollisionShape2D to function. Please create a shape resource for it!")); diff --git a/scene/2d/node_2d.cpp b/scene/2d/node_2d.cpp index 8afc43ddc9..049d121213 100644 --- a/scene/2d/node_2d.cpp +++ b/scene/2d/node_2d.cpp @@ -391,6 +391,15 @@ Point2 Node2D::to_global(Point2 p_local) const { return get_global_transform().xform(p_local); } +void Node2D::set_y_sort_enabled(bool p_enabled) { + y_sort_enabled = p_enabled; + RS::get_singleton()->canvas_item_set_sort_children_by_y(get_canvas_item(), y_sort_enabled); +} + +bool Node2D::is_y_sort_enabled() const { + return y_sort_enabled; +} + void Node2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_position", "position"), &Node2D::set_position); ClassDB::bind_method(D_METHOD("set_rotation", "radians"), &Node2D::set_rotation); @@ -437,6 +446,9 @@ void Node2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_z_as_relative", "enable"), &Node2D::set_z_as_relative); ClassDB::bind_method(D_METHOD("is_z_relative"), &Node2D::is_z_relative); + ClassDB::bind_method(D_METHOD("set_y_sort_enabled", "enabled"), &Node2D::set_y_sort_enabled); + ClassDB::bind_method(D_METHOD("is_y_sort_enabled"), &Node2D::is_y_sort_enabled); + ClassDB::bind_method(D_METHOD("get_relative_transform_to_parent", "parent"), &Node2D::get_relative_transform_to_parent); ADD_GROUP("Transform", ""); @@ -454,7 +466,8 @@ void Node2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "global_scale", PROPERTY_HINT_NONE, "", 0), "set_global_scale", "get_global_scale"); ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "global_transform", PROPERTY_HINT_NONE, "", 0), "set_global_transform", "get_global_transform"); - ADD_GROUP("Z Index", ""); + ADD_GROUP("Ordering", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "z_index", PROPERTY_HINT_RANGE, itos(RS::CANVAS_ITEM_Z_MIN) + "," + itos(RS::CANVAS_ITEM_Z_MAX) + ",1"), "set_z_index", "get_z_index"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "z_as_relative"), "set_z_as_relative", "is_z_relative"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "y_sort_enabled"), "set_y_sort_enabled", "is_y_sort_enabled"); } diff --git a/scene/2d/node_2d.h b/scene/2d/node_2d.h index 358b7e6520..339efd9179 100644 --- a/scene/2d/node_2d.h +++ b/scene/2d/node_2d.h @@ -42,6 +42,7 @@ class Node2D : public CanvasItem { real_t skew = 0.0; int z_index = 0; bool z_relative = true; + bool y_sort_enabled = false; Transform2D _mat; @@ -117,6 +118,9 @@ public: void set_z_as_relative(bool p_enabled); bool is_z_relative() const; + virtual void set_y_sort_enabled(bool p_enabled); + virtual bool is_y_sort_enabled() const; + Transform2D get_relative_transform_to_parent(const Node *p_parent) const; Transform2D get_transform() const override; diff --git a/scene/2d/physical_bone_2d.cpp b/scene/2d/physical_bone_2d.cpp new file mode 100644 index 0000000000..0c1be16174 --- /dev/null +++ b/scene/2d/physical_bone_2d.cpp @@ -0,0 +1,303 @@ +/*************************************************************************/ +/* physical_bone_2d.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "physical_bone_2d.h" + +void PhysicalBone2D::_notification(int p_what) { + if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) { + // Position the RigidBody in the correct position + if (follow_bone_when_simulating) { + _position_at_bone2d(); + } + + // Keep the child joint in the correct position. + if (child_joint && auto_configure_joint) { + child_joint->set_global_position(get_global_position()); + } + } else if (p_what == NOTIFICATION_READY) { + _find_skeleton_parent(); + _find_joint_child(); + + // Configure joint + if (child_joint && auto_configure_joint) { + _auto_configure_joint(); + } + + // Simulate physics if set + if (simulate_physics) { + _start_physics_simulation(); + } else { + _stop_physics_simulation(); + } + + set_physics_process_internal(true); + } +} + +void PhysicalBone2D::_position_at_bone2d() { + // Reset to Bone2D position + if (parent_skeleton) { + Bone2D *bone_to_use = parent_skeleton->get_bone(bone2d_index); + ERR_FAIL_COND_MSG(bone_to_use == nullptr, "It's not possible to position the bone with ID: " + itos(bone2d_index)); + set_global_transform(bone_to_use->get_global_transform()); + } +} + +void PhysicalBone2D::_find_skeleton_parent() { + Node *current_parent = get_parent(); + + while (current_parent != nullptr) { + Skeleton2D *potential_skeleton = Object::cast_to<Skeleton2D>(current_parent); + if (potential_skeleton) { + parent_skeleton = potential_skeleton; + break; + } else { + PhysicalBone2D *potential_parent_bone = Object::cast_to<PhysicalBone2D>(current_parent); + if (potential_parent_bone) { + current_parent = potential_parent_bone->get_parent(); + } else { + current_parent = nullptr; + } + } + } +} + +void PhysicalBone2D::_find_joint_child() { + for (int i = 0; i < get_child_count(); i++) { + Node *child_node = get_child(i); + Joint2D *potential_joint = Object::cast_to<Joint2D>(child_node); + if (potential_joint) { + child_joint = potential_joint; + break; + } + } +} + +TypedArray<String> PhysicalBone2D::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); + + if (!parent_skeleton) { + warnings.push_back(TTR("A PhysicalBone2D only works with a Skeleton2D or another PhysicalBone2D as a parent node!")); + } + if (parent_skeleton && bone2d_index <= -1) { + warnings.push_back(TTR("A PhysicalBone2D needs to be assigned to a Bone2D node in order to function! Please set a Bone2D node in the inspector.")); + } + if (!child_joint) { + PhysicalBone2D *parent_bone = Object::cast_to<PhysicalBone2D>(get_parent()); + if (parent_bone) { + warnings.push_back(TTR("A PhysicalBone2D node should have a Joint2D-based child node to keep bones connected! Please add a Joint2D-based node as a child to this node!")); + } + } + + return warnings; +} + +void PhysicalBone2D::_auto_configure_joint() { + if (!auto_configure_joint) { + return; + } + + if (child_joint) { + // Node A = parent | Node B = this node + Node *parent_node = get_parent(); + PhysicalBone2D *potential_parent_bone = Object::cast_to<PhysicalBone2D>(parent_node); + + if (potential_parent_bone) { + child_joint->set_node_a(child_joint->get_path_to(potential_parent_bone)); + child_joint->set_node_b(child_joint->get_path_to(this)); + } else { + WARN_PRINT("Cannot setup joint without a parent PhysicalBone2D node."); + } + + // Place the child joint at this node's position. + child_joint->set_global_position(get_global_position()); + } +} + +void PhysicalBone2D::_start_physics_simulation() { + if (_internal_simulate_physics) { + return; + } + + // Reset to Bone2D position + _position_at_bone2d(); + + // Apply the layers and masks + PhysicsServer2D::get_singleton()->body_set_collision_layer(get_rid(), get_collision_layer()); + PhysicsServer2D::get_singleton()->body_set_collision_mask(get_rid(), get_collision_mask()); + + // Apply the correct mode + RigidBody2D::Mode rigid_mode = get_mode(); + if (rigid_mode == RigidBody2D::MODE_STATIC) { + PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), PhysicsServer2D::BodyMode::BODY_MODE_STATIC); + } else if (rigid_mode == RigidBody2D::MODE_DYNAMIC) { + PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), PhysicsServer2D::BodyMode::BODY_MODE_DYNAMIC); + } else if (rigid_mode == RigidBody2D::MODE_KINEMATIC) { + PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), PhysicsServer2D::BodyMode::BODY_MODE_KINEMATIC); + } else if (rigid_mode == RigidBody2D::MODE_DYNAMIC_LOCKED) { + PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), PhysicsServer2D::BodyMode::BODY_MODE_DYNAMIC_LOCKED); + } else { + // Default to Rigid + PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), PhysicsServer2D::BodyMode::BODY_MODE_DYNAMIC); + } + + _internal_simulate_physics = true; + set_physics_process_internal(true); +} + +void PhysicalBone2D::_stop_physics_simulation() { + if (_internal_simulate_physics) { + _internal_simulate_physics = false; + + // Reset to Bone2D position + _position_at_bone2d(); + + set_physics_process_internal(false); + PhysicsServer2D::get_singleton()->body_set_collision_layer(get_rid(), 0); + PhysicsServer2D::get_singleton()->body_set_collision_mask(get_rid(), 0); + PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), PhysicsServer2D::BodyMode::BODY_MODE_STATIC); + } +} + +Joint2D *PhysicalBone2D::get_joint() const { + return child_joint; +} + +bool PhysicalBone2D::get_auto_configure_joint() const { + return auto_configure_joint; +} + +void PhysicalBone2D::set_auto_configure_joint(bool p_auto_configure) { + auto_configure_joint = p_auto_configure; + _auto_configure_joint(); +} + +void PhysicalBone2D::set_simulate_physics(bool p_simulate) { + if (p_simulate == simulate_physics) { + return; + } + simulate_physics = p_simulate; + + if (simulate_physics) { + _start_physics_simulation(); + } else { + _stop_physics_simulation(); + } +} + +bool PhysicalBone2D::get_simulate_physics() const { + return simulate_physics; +} + +bool PhysicalBone2D::is_simulating_physics() const { + return _internal_simulate_physics; +} + +void PhysicalBone2D::set_bone2d_nodepath(const NodePath &p_nodepath) { + bone2d_nodepath = p_nodepath; + notify_property_list_changed(); +} + +NodePath PhysicalBone2D::get_bone2d_nodepath() const { + return bone2d_nodepath; +} + +void PhysicalBone2D::set_bone2d_index(int p_bone_idx) { + ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); + + if (!is_inside_tree()) { + bone2d_index = p_bone_idx; + return; + } + + if (parent_skeleton) { + ERR_FAIL_INDEX_MSG(p_bone_idx, parent_skeleton->get_bone_count(), "Passed-in Bone index is out of range!"); + bone2d_index = p_bone_idx; + + bone2d_nodepath = get_path_to(parent_skeleton->get_bone(bone2d_index)); + } else { + WARN_PRINT("Cannot verify bone index..."); + bone2d_index = p_bone_idx; + } + + notify_property_list_changed(); +} + +int PhysicalBone2D::get_bone2d_index() const { + return bone2d_index; +} + +void PhysicalBone2D::set_follow_bone_when_simulating(bool p_follow_bone) { + follow_bone_when_simulating = p_follow_bone; + + if (_internal_simulate_physics) { + _stop_physics_simulation(); + _start_physics_simulation(); + } +} + +bool PhysicalBone2D::get_follow_bone_when_simulating() const { + return follow_bone_when_simulating; +} + +void PhysicalBone2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_joint"), &PhysicalBone2D::get_joint); + ClassDB::bind_method(D_METHOD("get_auto_configure_joint"), &PhysicalBone2D::get_auto_configure_joint); + ClassDB::bind_method(D_METHOD("set_auto_configure_joint", "auto_configure_joint"), &PhysicalBone2D::set_auto_configure_joint); + + ClassDB::bind_method(D_METHOD("set_simulate_physics", "simulate_physics"), &PhysicalBone2D::set_simulate_physics); + ClassDB::bind_method(D_METHOD("get_simulate_physics"), &PhysicalBone2D::get_simulate_physics); + ClassDB::bind_method(D_METHOD("is_simulating_physics"), &PhysicalBone2D::is_simulating_physics); + + ClassDB::bind_method(D_METHOD("set_bone2d_nodepath", "nodepath"), &PhysicalBone2D::set_bone2d_nodepath); + ClassDB::bind_method(D_METHOD("get_bone2d_nodepath"), &PhysicalBone2D::get_bone2d_nodepath); + ClassDB::bind_method(D_METHOD("set_bone2d_index", "bone_index"), &PhysicalBone2D::set_bone2d_index); + ClassDB::bind_method(D_METHOD("get_bone2d_index"), &PhysicalBone2D::get_bone2d_index); + ClassDB::bind_method(D_METHOD("set_follow_bone_when_simulating", "follow_bone"), &PhysicalBone2D::set_follow_bone_when_simulating); + ClassDB::bind_method(D_METHOD("get_follow_bone_when_simulating"), &PhysicalBone2D::get_follow_bone_when_simulating); + + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "bone2d_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D"), "set_bone2d_nodepath", "get_bone2d_nodepath"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "bone2d_index", PROPERTY_HINT_RANGE, "-1, 1000, 1"), "set_bone2d_index", "get_bone2d_index"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_configure_joint"), "set_auto_configure_joint", "get_auto_configure_joint"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "simulate_physics"), "set_simulate_physics", "get_simulate_physics"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "follow_bone_when_simulating"), "set_follow_bone_when_simulating", "get_follow_bone_when_simulating"); +} + +PhysicalBone2D::PhysicalBone2D() { + // Stop the RigidBody from executing its force integration. + PhysicsServer2D::get_singleton()->body_set_collision_layer(get_rid(), 0); + PhysicsServer2D::get_singleton()->body_set_collision_mask(get_rid(), 0); + PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), PhysicsServer2D::BodyMode::BODY_MODE_STATIC); + + child_joint = nullptr; +} + +PhysicalBone2D::~PhysicalBone2D() { +} diff --git a/scene/2d/y_sort.h b/scene/2d/physical_bone_2d.h index 7d36ee3391..d650a0426f 100644 --- a/scene/2d/y_sort.h +++ b/scene/2d/physical_bone_2d.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* y_sort.h */ +/* physical_bone_2d.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,20 +28,61 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef Y_SORT_H -#define Y_SORT_H +#ifndef PHYSICAL_BONE_2D_H +#define PHYSICAL_BONE_2D_H -#include "scene/2d/node_2d.h" +#include "scene/2d/joints_2d.h" +#include "scene/2d/physics_body_2d.h" -class YSort : public Node2D { - GDCLASS(YSort, Node2D); - bool sort_enabled = true; +#include "scene/2d/skeleton_2d.h" + +class PhysicalBone2D : public RigidBody2D { + GDCLASS(PhysicalBone2D, RigidBody2D); + +protected: + void _notification(int p_what); static void _bind_methods(); +private: + Skeleton2D *parent_skeleton; + int bone2d_index = -1; + NodePath bone2d_nodepath; + bool follow_bone_when_simulating = false; + + Joint2D *child_joint; + bool auto_configure_joint = true; + + bool simulate_physics = false; + bool _internal_simulate_physics = false; + + void _find_skeleton_parent(); + void _find_joint_child(); + void _auto_configure_joint(); + + void _start_physics_simulation(); + void _stop_physics_simulation(); + void _position_at_bone2d(); + public: - void set_sort_enabled(bool p_enabled); - bool is_sort_enabled() const; - YSort(); + Joint2D *get_joint() const; + bool get_auto_configure_joint() const; + void set_auto_configure_joint(bool p_auto_configure); + + void set_simulate_physics(bool p_simulate); + bool get_simulate_physics() const; + bool is_simulating_physics() const; + + void set_bone2d_nodepath(const NodePath &p_nodepath); + NodePath get_bone2d_nodepath() const; + void set_bone2d_index(int p_bone_idx); + int get_bone2d_index() const; + void set_follow_bone_when_simulating(bool p_follow); + bool get_follow_bone_when_simulating() const; + + TypedArray<String> get_configuration_warnings() const override; + + PhysicalBone2D(); + ~PhysicalBone2D(); }; -#endif // Y_SORT_H +#endif // PHYSICAL_BONE_2D_H diff --git a/scene/2d/physics_body_2d.cpp b/scene/2d/physics_body_2d.cpp index 4f52f62e99..8f6e1c4695 100644 --- a/scene/2d/physics_body_2d.cpp +++ b/scene/2d/physics_body_2d.cpp @@ -38,10 +38,10 @@ #include "core/templates/rid.h" #include "scene/scene_string_names.h" -void PhysicsBody2D::_notification(int p_what) { -} - void PhysicsBody2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("move_and_collide", "rel_vec", "infinite_inertia", "exclude_raycast_shapes", "test_only", "safe_margin"), &PhysicsBody2D::_move, DEFVAL(true), DEFVAL(true), DEFVAL(false), DEFVAL(0.08)); + ClassDB::bind_method(D_METHOD("test_move", "from", "rel_vec", "infinite_inertia", "exclude_raycast_shapes", "collision", "safe_margin"), &PhysicsBody2D::test_move, DEFVAL(true), DEFVAL(true), DEFVAL(Variant()), DEFVAL(0.08)); + ClassDB::bind_method(D_METHOD("get_collision_exceptions"), &PhysicsBody2D::get_collision_exceptions); ClassDB::bind_method(D_METHOD("add_collision_exception_with", "body"), &PhysicsBody2D::add_collision_exception_with); ClassDB::bind_method(D_METHOD("remove_collision_exception_with", "body"), &PhysicsBody2D::remove_collision_exception_with); @@ -53,6 +53,56 @@ PhysicsBody2D::PhysicsBody2D(PhysicsServer2D::BodyMode p_mode) : set_pickable(false); } +PhysicsBody2D::~PhysicsBody2D() { + if (motion_cache.is_valid()) { + motion_cache->owner = nullptr; + } +} + +Ref<KinematicCollision2D> PhysicsBody2D::_move(const Vector2 &p_motion, bool p_infinite_inertia, bool p_exclude_raycast_shapes, bool p_test_only, real_t p_margin) { + PhysicsServer2D::MotionResult result; + + if (move_and_collide(p_motion, p_infinite_inertia, result, p_margin, p_exclude_raycast_shapes, p_test_only)) { + if (motion_cache.is_null()) { + motion_cache.instance(); + motion_cache->owner = this; + } + + motion_cache->result = result; + + return motion_cache; + } + + return Ref<KinematicCollision2D>(); +} + +bool PhysicsBody2D::move_and_collide(const Vector2 &p_motion, bool p_infinite_inertia, PhysicsServer2D::MotionResult &r_result, real_t p_margin, bool p_exclude_raycast_shapes, bool p_test_only) { + if (is_only_update_transform_changes_enabled()) { + ERR_PRINT("Move functions do not work together with 'sync to physics' option. Please read the documentation."); + } + Transform2D gt = get_global_transform(); + bool colliding = PhysicsServer2D::get_singleton()->body_test_motion(get_rid(), gt, p_motion, p_infinite_inertia, p_margin, &r_result, p_exclude_raycast_shapes); + + if (!p_test_only) { + gt.elements[2] += r_result.motion; + set_global_transform(gt); + } + + return colliding; +} + +bool PhysicsBody2D::test_move(const Transform2D &p_from, const Vector2 &p_motion, bool p_infinite_inertia, bool p_exclude_raycast_shapes, const Ref<KinematicCollision2D> &r_collision, real_t p_margin) { + ERR_FAIL_COND_V(!is_inside_tree(), false); + + PhysicsServer2D::MotionResult *r = nullptr; + if (r_collision.is_valid()) { + // Needs const_cast because method bindings don't support non-const Ref. + r = const_cast<PhysicsServer2D::MotionResult *>(&r_collision->result); + } + + return PhysicsServer2D::get_singleton()->body_test_motion(get_rid(), p_from, p_motion, p_infinite_inertia, p_margin, r, p_exclude_raycast_shapes); +} + TypedArray<PhysicsBody2D> PhysicsBody2D::get_collision_exceptions() { List<RID> exceptions; PhysicsServer2D::get_singleton()->body_get_collision_exceptions(get_rid(), &exceptions); @@ -83,12 +133,22 @@ void PhysicsBody2D::remove_collision_exception_with(Node *p_node) { void StaticBody2D::set_constant_linear_velocity(const Vector2 &p_vel) { constant_linear_velocity = p_vel; - PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_LINEAR_VELOCITY, constant_linear_velocity); + + if (kinematic_motion) { + _update_kinematic_motion(); + } else { + PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_LINEAR_VELOCITY, constant_linear_velocity); + } } void StaticBody2D::set_constant_angular_velocity(real_t p_vel) { constant_angular_velocity = p_vel; - PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_ANGULAR_VELOCITY, constant_angular_velocity); + + if (kinematic_motion) { + _update_kinematic_motion(); + } else { + PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_ANGULAR_VELOCITY, constant_angular_velocity); + } } Vector2 StaticBody2D::get_constant_linear_velocity() const { @@ -118,27 +178,74 @@ Ref<PhysicsMaterial> StaticBody2D::get_physics_material_override() const { return physics_material_override; } +void StaticBody2D::set_kinematic_motion_enabled(bool p_enabled) { + if (p_enabled == kinematic_motion) { + return; + } + + kinematic_motion = p_enabled; + + if (kinematic_motion) { + PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), PhysicsServer2D::BODY_MODE_KINEMATIC); + } else { + PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), PhysicsServer2D::BODY_MODE_STATIC); + } + + _update_kinematic_motion(); +} + +bool StaticBody2D::is_kinematic_motion_enabled() const { + return kinematic_motion; +} + +void StaticBody2D::_notification(int p_what) { + if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) { +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + return; + } +#endif + + ERR_FAIL_COND(!kinematic_motion); + + real_t delta_time = get_physics_process_delta_time(); + + Transform2D new_transform = get_global_transform(); + + new_transform.translate(constant_linear_velocity * delta_time); + new_transform.set_rotation(new_transform.get_rotation() + constant_angular_velocity * delta_time); + + PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_TRANSFORM, new_transform); + + // Propagate transform change to node. + set_block_transform_notify(true); + set_global_transform(new_transform); + set_block_transform_notify(false); + } +} + void StaticBody2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_constant_linear_velocity", "vel"), &StaticBody2D::set_constant_linear_velocity); ClassDB::bind_method(D_METHOD("set_constant_angular_velocity", "vel"), &StaticBody2D::set_constant_angular_velocity); ClassDB::bind_method(D_METHOD("get_constant_linear_velocity"), &StaticBody2D::get_constant_linear_velocity); ClassDB::bind_method(D_METHOD("get_constant_angular_velocity"), &StaticBody2D::get_constant_angular_velocity); + ClassDB::bind_method(D_METHOD("set_kinematic_motion_enabled", "enabled"), &StaticBody2D::set_kinematic_motion_enabled); + ClassDB::bind_method(D_METHOD("is_kinematic_motion_enabled"), &StaticBody2D::is_kinematic_motion_enabled); + ClassDB::bind_method(D_METHOD("set_physics_material_override", "physics_material_override"), &StaticBody2D::set_physics_material_override); ClassDB::bind_method(D_METHOD("get_physics_material_override"), &StaticBody2D::get_physics_material_override); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "physics_material_override", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial"), "set_physics_material_override", "get_physics_material_override"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "constant_linear_velocity"), "set_constant_linear_velocity", "get_constant_linear_velocity"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "constant_angular_velocity"), "set_constant_angular_velocity", "get_constant_angular_velocity"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "physics_material_override", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial"), "set_physics_material_override", "get_physics_material_override"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "kinematic_motion"), "set_kinematic_motion_enabled", "is_kinematic_motion_enabled"); } StaticBody2D::StaticBody2D() : PhysicsBody2D(PhysicsServer2D::BODY_MODE_STATIC) { } -StaticBody2D::~StaticBody2D() { -} - void StaticBody2D::_reload_physics_characteristics() { if (physics_material_override.is_null()) { PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_BOUNCE, 0); @@ -149,6 +256,23 @@ void StaticBody2D::_reload_physics_characteristics() { } } +void StaticBody2D::_update_kinematic_motion() { +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + return; + } +#endif + + if (kinematic_motion) { + if (!Math::is_zero_approx(constant_angular_velocity) || !constant_linear_velocity.is_equal_approx(Vector2())) { + set_physics_process_internal(true); + return; + } + } + + set_physics_process_internal(false); +} + void RigidBody2D::_body_enter_tree(ObjectID p_id) { Object *obj = ObjectDB::get_instance(p_id); Node *node = Object::cast_to<Node>(obj); @@ -262,14 +386,6 @@ struct _RigidBody2DInOut { int local_shape = 0; }; -bool RigidBody2D::_test_motion(const Vector2 &p_motion, bool p_infinite_inertia, real_t p_margin, const Ref<PhysicsTestMotionResult2D> &p_result) { - PhysicsServer2D::MotionResult *r = nullptr; - if (p_result.is_valid()) { - r = p_result->get_result_ptr(); - } - return PhysicsServer2D::get_singleton()->body_test_motion(get_rid(), get_global_transform(), p_motion, p_infinite_inertia, p_margin, r); -} - void RigidBody2D::_direct_state_changed(Object *p_state) { #ifdef DEBUG_ENABLED state = Object::cast_to<PhysicsDirectBodyState2D>(p_state); @@ -378,8 +494,8 @@ void RigidBody2D::_direct_state_changed(Object *p_state) { void RigidBody2D::set_mode(Mode p_mode) { mode = p_mode; switch (p_mode) { - case MODE_RIGID: { - PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), PhysicsServer2D::BODY_MODE_RIGID); + case MODE_DYNAMIC: { + PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), PhysicsServer2D::BODY_MODE_DYNAMIC); } break; case MODE_STATIC: { PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), PhysicsServer2D::BODY_MODE_STATIC); @@ -389,8 +505,8 @@ void RigidBody2D::set_mode(Mode p_mode) { PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), PhysicsServer2D::BODY_MODE_KINEMATIC); } break; - case MODE_CHARACTER: { - PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), PhysicsServer2D::BODY_MODE_CHARACTER); + case MODE_DYNAMIC_LOCKED: { + PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), PhysicsServer2D::BODY_MODE_DYNAMIC_LOCKED); } break; } @@ -666,8 +782,8 @@ TypedArray<String> RigidBody2D::get_configuration_warnings() const { TypedArray<String> warnings = CollisionObject2D::get_configuration_warnings(); - if ((get_mode() == MODE_RIGID || get_mode() == MODE_CHARACTER) && (ABS(t.elements[0].length() - 1.0) > 0.05 || ABS(t.elements[1].length() - 1.0) > 0.05)) { - warnings.push_back(TTR("Size changes to RigidBody2D (in character or rigid modes) will be overridden by the physics engine when running.\nChange the size in children collision shapes instead.")); + if ((get_mode() == MODE_DYNAMIC || get_mode() == MODE_DYNAMIC_LOCKED) && (ABS(t.elements[0].length() - 1.0) > 0.05 || ABS(t.elements[1].length() - 1.0) > 0.05)) { + warnings.push_back(TTR("Size changes to RigidBody2D (in dynamic modes) will be overridden by the physics engine when running.\nChange the size in children collision shapes instead.")); } return warnings; @@ -734,13 +850,11 @@ void RigidBody2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_can_sleep", "able_to_sleep"), &RigidBody2D::set_can_sleep); ClassDB::bind_method(D_METHOD("is_able_to_sleep"), &RigidBody2D::is_able_to_sleep); - ClassDB::bind_method(D_METHOD("test_motion", "motion", "infinite_inertia", "margin", "result"), &RigidBody2D::_test_motion, DEFVAL(true), DEFVAL(0.08), DEFVAL(Variant())); - ClassDB::bind_method(D_METHOD("get_colliding_bodies"), &RigidBody2D::get_colliding_bodies); BIND_VMETHOD(MethodInfo("_integrate_forces", PropertyInfo(Variant::OBJECT, "state", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsDirectBodyState2D"))); - ADD_PROPERTY(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Rigid,Static,Character,Kinematic"), "set_mode", "get_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Dynamic,Static,DynamicLocked,Kinematic"), "set_mode", "get_mode"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass", PROPERTY_HINT_EXP_RANGE, "0.01,65535,0.01"), "set_mass", "get_mass"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "inertia", PROPERTY_HINT_EXP_RANGE, "0.01,65535,0.01", 0), "set_inertia", "get_inertia"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "physics_material_override", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial"), "set_physics_material_override", "get_physics_material_override"); @@ -767,9 +881,9 @@ void RigidBody2D::_bind_methods() { ADD_SIGNAL(MethodInfo("body_exited", PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); ADD_SIGNAL(MethodInfo("sleeping_state_changed")); - BIND_ENUM_CONSTANT(MODE_RIGID); + BIND_ENUM_CONSTANT(MODE_DYNAMIC); BIND_ENUM_CONSTANT(MODE_STATIC); - BIND_ENUM_CONSTANT(MODE_CHARACTER); + BIND_ENUM_CONSTANT(MODE_DYNAMIC_LOCKED); BIND_ENUM_CONSTANT(MODE_KINEMATIC); BIND_ENUM_CONSTANT(CCD_MODE_DISABLED); @@ -778,7 +892,7 @@ void RigidBody2D::_bind_methods() { } RigidBody2D::RigidBody2D() : - PhysicsBody2D(PhysicsServer2D::BODY_MODE_RIGID) { + PhysicsBody2D(PhysicsServer2D::BODY_MODE_DYNAMIC) { PhysicsServer2D::get_singleton()->body_set_force_integration_callback(get_rid(), callable_mp(this, &RigidBody2D::_direct_state_changed)); } @@ -800,95 +914,13 @@ void RigidBody2D::_reload_physics_characteristics() { ////////////////////////// -Ref<KinematicCollision2D> KinematicBody2D::_move(const Vector2 &p_motion, bool p_infinite_inertia, bool p_exclude_raycast_shapes, bool p_test_only) { - Collision col; - - if (move_and_collide(p_motion, p_infinite_inertia, col, p_exclude_raycast_shapes, p_test_only)) { - if (motion_cache.is_null()) { - motion_cache.instance(); - motion_cache->owner = this; - } - - motion_cache->collision = col; - - return motion_cache; - } - - return Ref<KinematicCollision2D>(); -} - -bool KinematicBody2D::separate_raycast_shapes(bool p_infinite_inertia, Collision &r_collision) { - PhysicsServer2D::SeparationResult sep_res[8]; //max 8 rays - - Transform2D gt = get_global_transform(); - - Vector2 recover; - int hits = PhysicsServer2D::get_singleton()->body_test_ray_separation(get_rid(), gt, p_infinite_inertia, recover, sep_res, 8, margin); - int deepest = -1; - real_t deepest_depth; - for (int i = 0; i < hits; i++) { - if (deepest == -1 || sep_res[i].collision_depth > deepest_depth) { - deepest = i; - deepest_depth = sep_res[i].collision_depth; - } - } - - gt.elements[2] += recover; - set_global_transform(gt); - - if (deepest != -1) { - r_collision.collider = sep_res[deepest].collider_id; - r_collision.collider_metadata = sep_res[deepest].collider_metadata; - r_collision.collider_shape = sep_res[deepest].collider_shape; - r_collision.collider_vel = sep_res[deepest].collider_velocity; - r_collision.collision = sep_res[deepest].collision_point; - r_collision.normal = sep_res[deepest].collision_normal; - r_collision.local_shape = sep_res[deepest].collision_local_shape; - r_collision.travel = recover; - r_collision.remainder = Vector2(); - - return true; - } else { - return false; - } -} - -bool KinematicBody2D::move_and_collide(const Vector2 &p_motion, bool p_infinite_inertia, Collision &r_collision, bool p_exclude_raycast_shapes, bool p_test_only) { - if (sync_to_physics) { - ERR_PRINT("Functions move_and_slide and move_and_collide do not work together with 'sync to physics' option. Please read the documentation."); - } - Transform2D gt = get_global_transform(); - PhysicsServer2D::MotionResult result; - bool colliding = PhysicsServer2D::get_singleton()->body_test_motion(get_rid(), gt, p_motion, p_infinite_inertia, margin, &result, p_exclude_raycast_shapes); - - if (colliding) { - r_collision.collider_metadata = result.collider_metadata; - r_collision.collider_shape = result.collider_shape; - r_collision.collider_vel = result.collider_velocity; - r_collision.collision = result.collision_point; - r_collision.normal = result.collision_normal; - r_collision.collider = result.collider_id; - r_collision.collider_rid = result.collider; - r_collision.travel = result.motion; - r_collision.remainder = result.remainder; - r_collision.local_shape = result.collision_local_shape; - } - - if (!p_test_only) { - gt.elements[2] += result.motion; - set_global_transform(gt); - } - - return colliding; -} - //so, if you pass 45 as limit, avoid numerical precision errors when angle is 45. #define FLOOR_ANGLE_THRESHOLD 0.01 -Vector2 KinematicBody2D::move_and_slide(const Vector2 &p_linear_velocity, const Vector2 &p_up_direction, bool p_stop_on_slope, int p_max_slides, real_t p_floor_max_angle, bool p_infinite_inertia) { - Vector2 body_velocity = p_linear_velocity; - Vector2 body_velocity_normal = body_velocity.normalized(); - Vector2 up_direction = p_up_direction.normalized(); +void CharacterBody2D::move_and_slide() { + Vector2 body_velocity_normal = linear_velocity.normalized(); + + bool was_on_floor = on_floor; Vector2 current_floor_velocity = floor_velocity; if (on_floor && on_floor_body.is_valid()) { @@ -900,69 +932,71 @@ Vector2 KinematicBody2D::move_and_slide(const Vector2 &p_linear_velocity, const } // Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky - Vector2 motion = (current_floor_velocity + body_velocity) * (Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time()); + Vector2 motion = (current_floor_velocity + linear_velocity) * (Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time()); on_floor = false; on_floor_body = RID(); on_ceiling = false; on_wall = false; - colliders.clear(); + motion_results.clear(); floor_normal = Vector2(); floor_velocity = Vector2(); - while (p_max_slides) { - Collision collision; + int slide_count = max_slides; + while (slide_count) { + PhysicsServer2D::MotionResult result; bool found_collision = false; for (int i = 0; i < 2; ++i) { bool collided; if (i == 0) { //collide - collided = move_and_collide(motion, p_infinite_inertia, collision); + collided = move_and_collide(motion, infinite_inertia, result, margin); if (!collided) { motion = Vector2(); //clear because no collision happened and motion completed } } else { //separate raycasts (if any) - collided = separate_raycast_shapes(p_infinite_inertia, collision); + collided = separate_raycast_shapes(result); if (collided) { - collision.remainder = motion; //keep - collision.travel = Vector2(); + result.remainder = motion; //keep + result.motion = Vector2(); } } if (collided) { found_collision = true; - colliders.push_back(collision); - motion = collision.remainder; + motion_results.push_back(result); + motion = result.remainder; if (up_direction == Vector2()) { //all is a wall on_wall = true; } else { - if (Math::acos(collision.normal.dot(up_direction)) <= p_floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //floor + if (Math::acos(result.collision_normal.dot(up_direction)) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //floor on_floor = true; - floor_normal = collision.normal; - on_floor_body = collision.collider_rid; - floor_velocity = collision.collider_vel; + floor_normal = result.collision_normal; + on_floor_body = result.collider; + floor_velocity = result.collider_velocity; - if (p_stop_on_slope) { - if ((body_velocity_normal + up_direction).length() < 0.01 && collision.travel.length() < 1) { + if (stop_on_slope) { + if ((body_velocity_normal + up_direction).length() < 0.01 && result.motion.length() < 1) { Transform2D gt = get_global_transform(); - gt.elements[2] -= collision.travel.slide(up_direction); + gt.elements[2] -= result.motion.slide(up_direction); set_global_transform(gt); - return Vector2(); + linear_velocity = Vector2(); + return; } } - } else if (Math::acos(collision.normal.dot(-up_direction)) <= p_floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //ceiling + } else if (Math::acos(result.collision_normal.dot(-up_direction)) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //ceiling on_ceiling = true; } else { on_wall = true; } } - motion = motion.slide(collision.normal); - body_velocity = body_velocity.slide(collision.normal); + motion = motion.slide(result.collision_normal); + linear_velocity = linear_velocity.slide(result.collision_normal); } } @@ -970,36 +1004,28 @@ Vector2 KinematicBody2D::move_and_slide(const Vector2 &p_linear_velocity, const break; } - --p_max_slides; + --slide_count; } - return body_velocity; -} - -Vector2 KinematicBody2D::move_and_slide_with_snap(const Vector2 &p_linear_velocity, const Vector2 &p_snap, const Vector2 &p_up_direction, bool p_stop_on_slope, int p_max_slides, real_t p_floor_max_angle, bool p_infinite_inertia) { - Vector2 up_direction = p_up_direction.normalized(); - bool was_on_floor = on_floor; - - Vector2 ret = move_and_slide(p_linear_velocity, up_direction, p_stop_on_slope, p_max_slides, p_floor_max_angle, p_infinite_inertia); - if (!was_on_floor || p_snap == Vector2()) { - return ret; + if (!was_on_floor || snap == Vector2()) { + return; } - Collision col; + // Apply snap. Transform2D gt = get_global_transform(); - - if (move_and_collide(p_snap, p_infinite_inertia, col, false, true)) { + PhysicsServer2D::MotionResult result; + if (move_and_collide(snap, infinite_inertia, result, margin, false, true)) { bool apply = true; if (up_direction != Vector2()) { - if (Math::acos(col.normal.dot(up_direction)) <= p_floor_max_angle + FLOOR_ANGLE_THRESHOLD) { + if (Math::acos(result.collision_normal.dot(up_direction)) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { on_floor = true; - floor_normal = col.normal; - on_floor_body = col.collider_rid; - floor_velocity = col.collider_vel; - if (p_stop_on_slope) { + floor_normal = result.collision_normal; + on_floor_body = result.collider; + floor_velocity = result.collider_velocity; + if (stop_on_slope) { // move and collide may stray the object a bit because of pre un-stucking, // so only ensure that motion happens on floor direction in this case. - col.travel = up_direction * up_direction.dot(col.travel); + result.motion = up_direction * up_direction.dot(result.motion); } } else { @@ -1008,59 +1034,87 @@ Vector2 KinematicBody2D::move_and_slide_with_snap(const Vector2 &p_linear_veloci } if (apply) { - gt.elements[2] += col.travel; + gt.elements[2] += result.motion; set_global_transform(gt); } } - - return ret; } -bool KinematicBody2D::is_on_floor() const { - return on_floor; -} +bool CharacterBody2D::separate_raycast_shapes(PhysicsServer2D::MotionResult &r_result) { + PhysicsServer2D::SeparationResult sep_res[8]; //max 8 rays -bool KinematicBody2D::is_on_wall() const { - return on_wall; + Transform2D gt = get_global_transform(); + + Vector2 recover; + int hits = PhysicsServer2D::get_singleton()->body_test_ray_separation(get_rid(), gt, infinite_inertia, recover, sep_res, 8, margin); + int deepest = -1; + real_t deepest_depth; + for (int i = 0; i < hits; i++) { + if (deepest == -1 || sep_res[i].collision_depth > deepest_depth) { + deepest = i; + deepest_depth = sep_res[i].collision_depth; + } + } + + gt.elements[2] += recover; + set_global_transform(gt); + + if (deepest != -1) { + r_result.collider_id = sep_res[deepest].collider_id; + r_result.collider_metadata = sep_res[deepest].collider_metadata; + r_result.collider_shape = sep_res[deepest].collider_shape; + r_result.collider_velocity = sep_res[deepest].collider_velocity; + r_result.collision_point = sep_res[deepest].collision_point; + r_result.collision_normal = sep_res[deepest].collision_normal; + r_result.collision_local_shape = sep_res[deepest].collision_local_shape; + r_result.motion = recover; + r_result.remainder = Vector2(); + + return true; + } else { + return false; + } } -bool KinematicBody2D::is_on_ceiling() const { - return on_ceiling; +const Vector2 &CharacterBody2D::get_linear_velocity() const { + return linear_velocity; } -Vector2 KinematicBody2D::get_floor_normal() const { - return floor_normal; +void CharacterBody2D::set_linear_velocity(const Vector2 &p_velocity) { + linear_velocity = p_velocity; } -Vector2 KinematicBody2D::get_floor_velocity() const { - return floor_velocity; +bool CharacterBody2D::is_on_floor() const { + return on_floor; } -bool KinematicBody2D::test_move(const Transform2D &p_from, const Vector2 &p_motion, bool p_infinite_inertia) { - ERR_FAIL_COND_V(!is_inside_tree(), false); +bool CharacterBody2D::is_on_wall() const { + return on_wall; +} - return PhysicsServer2D::get_singleton()->body_test_motion(get_rid(), p_from, p_motion, p_infinite_inertia, margin); +bool CharacterBody2D::is_on_ceiling() const { + return on_ceiling; } -void KinematicBody2D::set_safe_margin(real_t p_margin) { - margin = p_margin; +Vector2 CharacterBody2D::get_floor_normal() const { + return floor_normal; } -real_t KinematicBody2D::get_safe_margin() const { - return margin; +Vector2 CharacterBody2D::get_floor_velocity() const { + return floor_velocity; } -int KinematicBody2D::get_slide_count() const { - return colliders.size(); +int CharacterBody2D::get_slide_count() const { + return motion_results.size(); } -KinematicBody2D::Collision KinematicBody2D::get_slide_collision(int p_bounce) const { - ERR_FAIL_INDEX_V(p_bounce, colliders.size(), Collision()); - return colliders[p_bounce]; +PhysicsServer2D::MotionResult CharacterBody2D::get_slide_collision(int p_bounce) const { + ERR_FAIL_INDEX_V(p_bounce, motion_results.size(), PhysicsServer2D::MotionResult()); + return motion_results[p_bounce]; } -Ref<KinematicCollision2D> KinematicBody2D::_get_slide_collision(int p_bounce) { - ERR_FAIL_INDEX_V(p_bounce, colliders.size(), Ref<KinematicCollision2D>()); +Ref<KinematicCollision2D> CharacterBody2D::_get_slide_collision(int p_bounce) { + ERR_FAIL_INDEX_V(p_bounce, motion_results.size(), Ref<KinematicCollision2D>()); if (p_bounce >= slide_colliders.size()) { slide_colliders.resize(p_bounce + 1); } @@ -1070,11 +1124,11 @@ Ref<KinematicCollision2D> KinematicBody2D::_get_slide_collision(int p_bounce) { slide_colliders.write[p_bounce]->owner = this; } - slide_colliders.write[p_bounce]->collision = colliders[p_bounce]; + slide_colliders.write[p_bounce]->result = motion_results[p_bounce]; return slide_colliders[p_bounce]; } -void KinematicBody2D::set_sync_to_physics(bool p_enable) { +void CharacterBody2D::set_sync_to_physics(bool p_enable) { if (sync_to_physics == p_enable) { return; } @@ -1085,7 +1139,7 @@ void KinematicBody2D::set_sync_to_physics(bool p_enable) { } if (p_enable) { - PhysicsServer2D::get_singleton()->body_set_force_integration_callback(get_rid(), callable_mp(this, &KinematicBody2D::_direct_state_changed)); + PhysicsServer2D::get_singleton()->body_set_force_integration_callback(get_rid(), callable_mp(this, &CharacterBody2D::_direct_state_changed)); set_only_update_transform_changes(true); set_notify_local_transform(true); } else { @@ -1095,11 +1149,11 @@ void KinematicBody2D::set_sync_to_physics(bool p_enable) { } } -bool KinematicBody2D::is_sync_to_physics_enabled() const { +bool CharacterBody2D::is_sync_to_physics_enabled() const { return sync_to_physics; } -void KinematicBody2D::_direct_state_changed(Object *p_state) { +void CharacterBody2D::_direct_state_changed(Object *p_state) { if (!sync_to_physics) { return; } @@ -1113,7 +1167,63 @@ void KinematicBody2D::_direct_state_changed(Object *p_state) { set_notify_local_transform(true); } -void KinematicBody2D::_notification(int p_what) { +void CharacterBody2D::set_safe_margin(real_t p_margin) { + margin = p_margin; +} + +real_t CharacterBody2D::get_safe_margin() const { + return margin; +} + +bool CharacterBody2D::is_stop_on_slope_enabled() const { + return stop_on_slope; +} + +void CharacterBody2D::set_stop_on_slope_enabled(bool p_enabled) { + stop_on_slope = p_enabled; +} + +bool CharacterBody2D::is_infinite_inertia_enabled() const { + return infinite_inertia; +} +void CharacterBody2D::set_infinite_inertia_enabled(bool p_enabled) { + infinite_inertia = p_enabled; +} + +int CharacterBody2D::get_max_slides() const { + return max_slides; +} + +void CharacterBody2D::set_max_slides(int p_max_slides) { + ERR_FAIL_COND(p_max_slides > 0); + max_slides = p_max_slides; +} + +real_t CharacterBody2D::get_floor_max_angle() const { + return floor_max_angle; +} + +void CharacterBody2D::set_floor_max_angle(real_t p_floor_max_angle) { + floor_max_angle = p_floor_max_angle; +} + +const Vector2 &CharacterBody2D::get_snap() const { + return snap; +} + +void CharacterBody2D::set_snap(const Vector2 &p_snap) { + snap = p_snap; +} + +const Vector2 &CharacterBody2D::get_up_direction() const { + return up_direction; +} + +void CharacterBody2D::set_up_direction(const Vector2 &p_up_direction) { + up_direction = p_up_direction.normalized(); +} + +void CharacterBody2D::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE) { last_valid_transform = get_global_transform(); @@ -1122,7 +1232,7 @@ void KinematicBody2D::_notification(int p_what) { on_floor_body = RID(); on_ceiling = false; on_wall = false; - colliders.clear(); + motion_results.clear(); floor_velocity = Vector2(); } @@ -1137,47 +1247,55 @@ void KinematicBody2D::_notification(int p_what) { } } -void KinematicBody2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("move_and_collide", "rel_vec", "infinite_inertia", "exclude_raycast_shapes", "test_only"), &KinematicBody2D::_move, DEFVAL(true), DEFVAL(true), DEFVAL(false)); - ClassDB::bind_method(D_METHOD("move_and_slide", "linear_velocity", "up_direction", "stop_on_slope", "max_slides", "floor_max_angle", "infinite_inertia"), &KinematicBody2D::move_and_slide, DEFVAL(Vector2(0, 0)), DEFVAL(false), DEFVAL(4), DEFVAL(Math::deg2rad((real_t)45.0)), DEFVAL(true)); - ClassDB::bind_method(D_METHOD("move_and_slide_with_snap", "linear_velocity", "snap", "up_direction", "stop_on_slope", "max_slides", "floor_max_angle", "infinite_inertia"), &KinematicBody2D::move_and_slide_with_snap, DEFVAL(Vector2(0, 0)), DEFVAL(false), DEFVAL(4), DEFVAL(Math::deg2rad((real_t)45.0)), DEFVAL(true)); - - ClassDB::bind_method(D_METHOD("test_move", "from", "rel_vec", "infinite_inertia"), &KinematicBody2D::test_move, DEFVAL(true)); - - ClassDB::bind_method(D_METHOD("is_on_floor"), &KinematicBody2D::is_on_floor); - ClassDB::bind_method(D_METHOD("is_on_ceiling"), &KinematicBody2D::is_on_ceiling); - ClassDB::bind_method(D_METHOD("is_on_wall"), &KinematicBody2D::is_on_wall); - ClassDB::bind_method(D_METHOD("get_floor_normal"), &KinematicBody2D::get_floor_normal); - ClassDB::bind_method(D_METHOD("get_floor_velocity"), &KinematicBody2D::get_floor_velocity); - - ClassDB::bind_method(D_METHOD("set_safe_margin", "pixels"), &KinematicBody2D::set_safe_margin); - ClassDB::bind_method(D_METHOD("get_safe_margin"), &KinematicBody2D::get_safe_margin); +void CharacterBody2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("move_and_slide"), &CharacterBody2D::move_and_slide); + + ClassDB::bind_method(D_METHOD("set_linear_velocity", "linear_velocity"), &CharacterBody2D::set_linear_velocity); + ClassDB::bind_method(D_METHOD("get_linear_velocity"), &CharacterBody2D::get_linear_velocity); + + ClassDB::bind_method(D_METHOD("set_safe_margin", "pixels"), &CharacterBody2D::set_safe_margin); + ClassDB::bind_method(D_METHOD("get_safe_margin"), &CharacterBody2D::get_safe_margin); + ClassDB::bind_method(D_METHOD("is_stop_on_slope_enabled"), &CharacterBody2D::is_stop_on_slope_enabled); + ClassDB::bind_method(D_METHOD("set_stop_on_slope_enabled", "enabled"), &CharacterBody2D::set_stop_on_slope_enabled); + ClassDB::bind_method(D_METHOD("is_infinite_inertia_enabled"), &CharacterBody2D::is_infinite_inertia_enabled); + ClassDB::bind_method(D_METHOD("set_infinite_inertia_enabled", "enabled"), &CharacterBody2D::set_infinite_inertia_enabled); + ClassDB::bind_method(D_METHOD("get_max_slides"), &CharacterBody2D::get_max_slides); + ClassDB::bind_method(D_METHOD("set_max_slides", "max_slides"), &CharacterBody2D::set_max_slides); + ClassDB::bind_method(D_METHOD("get_floor_max_angle"), &CharacterBody2D::get_floor_max_angle); + ClassDB::bind_method(D_METHOD("set_floor_max_angle", "floor_max_angle"), &CharacterBody2D::set_floor_max_angle); + ClassDB::bind_method(D_METHOD("get_snap"), &CharacterBody2D::get_snap); + ClassDB::bind_method(D_METHOD("set_snap", "snap"), &CharacterBody2D::set_snap); + ClassDB::bind_method(D_METHOD("get_up_direction"), &CharacterBody2D::get_up_direction); + ClassDB::bind_method(D_METHOD("set_up_direction", "up_direction"), &CharacterBody2D::set_up_direction); + + ClassDB::bind_method(D_METHOD("is_on_floor"), &CharacterBody2D::is_on_floor); + ClassDB::bind_method(D_METHOD("is_on_ceiling"), &CharacterBody2D::is_on_ceiling); + ClassDB::bind_method(D_METHOD("is_on_wall"), &CharacterBody2D::is_on_wall); + ClassDB::bind_method(D_METHOD("get_floor_normal"), &CharacterBody2D::get_floor_normal); + ClassDB::bind_method(D_METHOD("get_floor_velocity"), &CharacterBody2D::get_floor_velocity); + ClassDB::bind_method(D_METHOD("get_slide_count"), &CharacterBody2D::get_slide_count); + ClassDB::bind_method(D_METHOD("get_slide_collision", "slide_idx"), &CharacterBody2D::_get_slide_collision); + + ClassDB::bind_method(D_METHOD("set_sync_to_physics", "enable"), &CharacterBody2D::set_sync_to_physics); + ClassDB::bind_method(D_METHOD("is_sync_to_physics_enabled"), &CharacterBody2D::is_sync_to_physics_enabled); - ClassDB::bind_method(D_METHOD("get_slide_count"), &KinematicBody2D::get_slide_count); - ClassDB::bind_method(D_METHOD("get_slide_collision", "slide_idx"), &KinematicBody2D::_get_slide_collision); - - ClassDB::bind_method(D_METHOD("set_sync_to_physics", "enable"), &KinematicBody2D::set_sync_to_physics); - ClassDB::bind_method(D_METHOD("is_sync_to_physics_enabled"), &KinematicBody2D::is_sync_to_physics_enabled); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "linear_velocity"), "set_linear_velocity", "get_linear_velocity"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stop_on_slope"), "set_stop_on_slope_enabled", "is_stop_on_slope_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "infinite_inertia"), "set_infinite_inertia_enabled", "is_infinite_inertia_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "max_slides"), "set_max_slides", "get_max_slides"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "floor_max_angle"), "set_floor_max_angle", "get_floor_max_angle"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "snap"), "set_snap", "get_snap"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "up_direction"), "set_up_direction", "get_up_direction"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision/safe_margin", PROPERTY_HINT_RANGE, "0.001,256,0.001"), "set_safe_margin", "get_safe_margin"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "motion/sync_to_physics"), "set_sync_to_physics", "is_sync_to_physics_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision/safe_margin", PROPERTY_HINT_RANGE, "0.001,256,0.001"), "set_safe_margin", "get_safe_margin"); } -KinematicBody2D::KinematicBody2D() : +CharacterBody2D::CharacterBody2D() : PhysicsBody2D(PhysicsServer2D::BODY_MODE_KINEMATIC) { - margin = 0.08; - - on_floor = false; - on_ceiling = false; - on_wall = false; - sync_to_physics = false; } -KinematicBody2D::~KinematicBody2D() { - if (motion_cache.is_valid()) { - motion_cache->owner = nullptr; - } - +CharacterBody2D::~CharacterBody2D() { for (int i = 0; i < slide_colliders.size(); i++) { if (slide_colliders[i].is_valid()) { slide_colliders.write[i]->owner = nullptr; @@ -1188,39 +1306,39 @@ KinematicBody2D::~KinematicBody2D() { //////////////////////// Vector2 KinematicCollision2D::get_position() const { - return collision.collision; + return result.collision_point; } Vector2 KinematicCollision2D::get_normal() const { - return collision.normal; + return result.collision_normal; } Vector2 KinematicCollision2D::get_travel() const { - return collision.travel; + return result.motion; } Vector2 KinematicCollision2D::get_remainder() const { - return collision.remainder; + return result.remainder; } Object *KinematicCollision2D::get_local_shape() const { if (!owner) { return nullptr; } - uint32_t ownerid = owner->shape_find_owner(collision.local_shape); + uint32_t ownerid = owner->shape_find_owner(result.collision_local_shape); return owner->shape_owner_get_owner(ownerid); } Object *KinematicCollision2D::get_collider() const { - if (collision.collider.is_valid()) { - return ObjectDB::get_instance(collision.collider); + if (result.collider_id.is_valid()) { + return ObjectDB::get_instance(result.collider_id); } return nullptr; } ObjectID KinematicCollision2D::get_collider_id() const { - return collision.collider; + return result.collider_id; } Object *KinematicCollision2D::get_collider_shape() const { @@ -1228,7 +1346,7 @@ Object *KinematicCollision2D::get_collider_shape() const { if (collider) { CollisionObject2D *obj2d = Object::cast_to<CollisionObject2D>(collider); if (obj2d) { - uint32_t ownerid = obj2d->shape_find_owner(collision.collider_shape); + uint32_t ownerid = obj2d->shape_find_owner(result.collider_shape); return obj2d->shape_owner_get_owner(ownerid); } } @@ -1237,11 +1355,11 @@ Object *KinematicCollision2D::get_collider_shape() const { } int KinematicCollision2D::get_collider_shape_index() const { - return collision.collider_shape; + return result.collider_shape; } Vector2 KinematicCollision2D::get_collider_velocity() const { - return collision.collider_vel; + return result.collider_velocity; } Variant KinematicCollision2D::get_collider_metadata() const { @@ -1273,9 +1391,3 @@ void KinematicCollision2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "collider_velocity"), "", "get_collider_velocity"); ADD_PROPERTY(PropertyInfo(Variant::NIL, "collider_metadata", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT), "", "get_collider_metadata"); } - -KinematicCollision2D::KinematicCollision2D() { - collision.collider_shape = 0; - collision.local_shape = 0; - owner = nullptr; -} diff --git a/scene/2d/physics_body_2d.h b/scene/2d/physics_body_2d.h index 47d55d11fa..5a44d31cc2 100644 --- a/scene/2d/physics_body_2d.h +++ b/scene/2d/physics_body_2d.h @@ -42,17 +42,22 @@ class PhysicsBody2D : public CollisionObject2D { GDCLASS(PhysicsBody2D, CollisionObject2D); protected: - void _notification(int p_what); + static void _bind_methods(); PhysicsBody2D(PhysicsServer2D::BodyMode p_mode); - static void _bind_methods(); + Ref<KinematicCollision2D> motion_cache; + + Ref<KinematicCollision2D> _move(const Vector2 &p_motion, bool p_infinite_inertia = true, bool p_exclude_raycast_shapes = true, bool p_test_only = false, real_t p_margin = 0.08); public: + bool move_and_collide(const Vector2 &p_motion, bool p_infinite_inertia, PhysicsServer2D::MotionResult &r_result, real_t p_margin, bool p_exclude_raycast_shapes = true, bool p_test_only = false); + bool test_move(const Transform2D &p_from, const Vector2 &p_motion, bool p_infinite_inertia = true, bool p_exclude_raycast_shapes = true, const Ref<KinematicCollision2D> &r_collision = Ref<KinematicCollision2D>(), real_t p_margin = 0.08); + TypedArray<PhysicsBody2D> get_collision_exceptions(); void add_collision_exception_with(Node *p_node); //must be physicsbody void remove_collision_exception_with(Node *p_node); - PhysicsBody2D(); + virtual ~PhysicsBody2D(); }; class StaticBody2D : public PhysicsBody2D { @@ -63,7 +68,10 @@ class StaticBody2D : public PhysicsBody2D { Ref<PhysicsMaterial> physics_material_override; + bool kinematic_motion = false; + protected: + void _notification(int p_what); static void _bind_methods(); public: @@ -77,10 +85,14 @@ public: real_t get_constant_angular_velocity() const; StaticBody2D(); - ~StaticBody2D(); private: void _reload_physics_characteristics(); + + void _update_kinematic_motion(); + + void set_kinematic_motion_enabled(bool p_enabled); + bool is_kinematic_motion_enabled() const; }; class RigidBody2D : public PhysicsBody2D { @@ -88,9 +100,9 @@ class RigidBody2D : public PhysicsBody2D { public: enum Mode { - MODE_RIGID, + MODE_DYNAMIC, MODE_STATIC, - MODE_CHARACTER, + MODE_DYNAMIC_LOCKED, MODE_KINEMATIC, }; @@ -103,7 +115,7 @@ public: private: bool can_sleep = true; PhysicsDirectBodyState2D *state = nullptr; - Mode mode = MODE_RIGID; + Mode mode = MODE_DYNAMIC; real_t mass = 1.0; Ref<PhysicsMaterial> physics_material_override; @@ -163,8 +175,6 @@ private: void _body_inout(int p_status, const RID &p_body, ObjectID p_instance, int p_body_shape, int p_local_shape); void _direct_state_changed(Object *p_state); - bool _test_motion(const Vector2 &p_motion, bool p_infinite_inertia = true, real_t p_margin = 0.08, const Ref<PhysicsTestMotionResult2D> &p_result = Ref<PhysicsTestMotionResult2D>()); - protected: void _notification(int p_what); static void _bind_methods(); @@ -245,62 +255,70 @@ private: VARIANT_ENUM_CAST(RigidBody2D::Mode); VARIANT_ENUM_CAST(RigidBody2D::CCDMode); -class KinematicBody2D : public PhysicsBody2D { - GDCLASS(KinematicBody2D, PhysicsBody2D); - -public: - struct Collision { - Vector2 collision; - Vector2 normal; - Vector2 collider_vel; - ObjectID collider; - RID collider_rid; - int collider_shape = 0; - Variant collider_metadata; - Vector2 remainder; - Vector2 travel; - int local_shape = 0; - }; +class CharacterBody2D : public PhysicsBody2D { + GDCLASS(CharacterBody2D, PhysicsBody2D); private: - real_t margin; + real_t margin = 0.08; + + bool stop_on_slope = false; + bool infinite_inertia = true; + int max_slides = 4; + real_t floor_max_angle = Math::deg2rad((real_t)45.0); + Vector2 snap; + Vector2 up_direction = Vector2(0.0, -1.0); + + Vector2 linear_velocity; Vector2 floor_normal; Vector2 floor_velocity; RID on_floor_body; - bool on_floor; - bool on_ceiling; - bool on_wall; - bool sync_to_physics; + bool on_floor = false; + bool on_ceiling = false; + bool on_wall = false; + bool sync_to_physics = false; - Vector<Collision> colliders; + Vector<PhysicsServer2D::MotionResult> motion_results; Vector<Ref<KinematicCollision2D>> slide_colliders; - Ref<KinematicCollision2D> motion_cache; - - _FORCE_INLINE_ bool _ignores_mode(PhysicsServer2D::BodyMode) const; - Ref<KinematicCollision2D> _move(const Vector2 &p_motion, bool p_infinite_inertia = true, bool p_exclude_raycast_shapes = true, bool p_test_only = false); Ref<KinematicCollision2D> _get_slide_collision(int p_bounce); + bool separate_raycast_shapes(PhysicsServer2D::MotionResult &r_result); + Transform2D last_valid_transform; void _direct_state_changed(Object *p_state); + void set_safe_margin(real_t p_margin); + real_t get_safe_margin() const; + + bool is_stop_on_slope_enabled() const; + void set_stop_on_slope_enabled(bool p_enabled); + + bool is_infinite_inertia_enabled() const; + void set_infinite_inertia_enabled(bool p_enabled); + + int get_max_slides() const; + void set_max_slides(int p_max_slides); + + real_t get_floor_max_angle() const; + void set_floor_max_angle(real_t p_floor_max_angle); + + const Vector2 &get_snap() const; + void set_snap(const Vector2 &p_snap); + + const Vector2 &get_up_direction() const; + void set_up_direction(const Vector2 &p_up_direction); + protected: void _notification(int p_what); static void _bind_methods(); public: - bool move_and_collide(const Vector2 &p_motion, bool p_infinite_inertia, Collision &r_collision, bool p_exclude_raycast_shapes = true, bool p_test_only = false); - - bool test_move(const Transform2D &p_from, const Vector2 &p_motion, bool p_infinite_inertia = true); - - bool separate_raycast_shapes(bool p_infinite_inertia, Collision &r_collision); + void move_and_slide(); - void set_safe_margin(real_t p_margin); - real_t get_safe_margin() const; + const Vector2 &get_linear_velocity() const; + void set_linear_velocity(const Vector2 &p_velocity); - Vector2 move_and_slide(const Vector2 &p_linear_velocity, const Vector2 &p_up_direction = Vector2(0, 0), bool p_stop_on_slope = false, int p_max_slides = 4, real_t p_floor_max_angle = Math::deg2rad((real_t)45.0), bool p_infinite_inertia = true); - Vector2 move_and_slide_with_snap(const Vector2 &p_linear_velocity, const Vector2 &p_snap, const Vector2 &p_up_direction = Vector2(0, 0), bool p_stop_on_slope = false, int p_max_slides = 4, real_t p_floor_max_angle = Math::deg2rad((real_t)45.0), bool p_infinite_inertia = true); bool is_on_floor() const; bool is_on_wall() const; bool is_on_ceiling() const; @@ -308,21 +326,22 @@ public: Vector2 get_floor_velocity() const; int get_slide_count() const; - Collision get_slide_collision(int p_bounce) const; + PhysicsServer2D::MotionResult get_slide_collision(int p_bounce) const; void set_sync_to_physics(bool p_enable); bool is_sync_to_physics_enabled() const; - KinematicBody2D(); - ~KinematicBody2D(); + CharacterBody2D(); + ~CharacterBody2D(); }; class KinematicCollision2D : public Reference { GDCLASS(KinematicCollision2D, Reference); - KinematicBody2D *owner; - friend class KinematicBody2D; - KinematicBody2D::Collision collision; + PhysicsBody2D *owner = nullptr; + friend class PhysicsBody2D; + friend class CharacterBody2D; + PhysicsServer2D::MotionResult result; protected: static void _bind_methods(); @@ -339,8 +358,6 @@ public: int get_collider_shape_index() const; Vector2 get_collider_velocity() const; Variant get_collider_metadata() const; - - KinematicCollision2D(); }; #endif // PHYSICS_BODY_2D_H diff --git a/scene/2d/position_2d.cpp b/scene/2d/position_2d.cpp index 5c7d65e3e0..1019f85c8a 100644 --- a/scene/2d/position_2d.cpp +++ b/scene/2d/position_2d.cpp @@ -36,10 +36,41 @@ const real_t DEFAULT_GIZMO_EXTENTS = 10.0; void Position2D::_draw_cross() { - real_t extents = get_gizmo_extents(); - // Colors taken from `axis_x_color` and `axis_y_color` (defined in `editor/editor_themes.cpp`) - draw_line(Point2(-extents, 0), Point2(+extents, 0), Color(0.96, 0.20, 0.32)); - draw_line(Point2(0, -extents), Point2(0, +extents), Color(0.53, 0.84, 0.01)); + const real_t extents = get_gizmo_extents(); + + // Add more points to create a "hard stop" in the color gradient. + PackedVector2Array points_x; + points_x.push_back(Point2(+extents, 0)); + points_x.push_back(Point2()); + points_x.push_back(Point2()); + points_x.push_back(Point2(-extents, 0)); + + PackedVector2Array points_y; + points_y.push_back(Point2(0, +extents)); + points_y.push_back(Point2()); + points_y.push_back(Point2()); + points_y.push_back(Point2(0, -extents)); + + // Use the axis color which is brighter for the positive axis. + // Use a darkened axis color for the negative axis. + // This makes it possible to see in which direction the Position3D node is rotated + // (which can be important depending on how it's used). + // Axis colors are taken from `axis_x_color` and `axis_y_color` (defined in `editor/editor_themes.cpp`). + PackedColorArray colors_x; + const Color color_x = Color(0.96, 0.20, 0.32); + colors_x.push_back(color_x); + colors_x.push_back(color_x); + colors_x.push_back(color_x.lerp(Color(0, 0, 0), 0.5)); + colors_x.push_back(color_x.lerp(Color(0, 0, 0), 0.5)); + draw_multiline_colors(points_x, colors_x); + + PackedColorArray colors_y; + const Color color_y = Color(0.53, 0.84, 0.01); + colors_y.push_back(color_y); + colors_y.push_back(color_y); + colors_y.push_back(color_y.lerp(Color(0, 0, 0), 0.5)); + colors_y.push_back(color_y.lerp(Color(0, 0, 0), 0.5)); + draw_multiline_colors(points_y, colors_y); } #ifdef TOOLS_ENABLED diff --git a/scene/2d/skeleton_2d.cpp b/scene/2d/skeleton_2d.cpp index 22180797f0..c5ad3dde39 100644 --- a/scene/2d/skeleton_2d.cpp +++ b/scene/2d/skeleton_2d.cpp @@ -30,6 +30,69 @@ #include "skeleton_2d.h" +#include "scene/resources/skeleton_modification_2d.h" + +#ifdef TOOLS_ENABLED +#include "editor/editor_settings.h" +#include "editor/plugins/canvas_item_editor_plugin.h" +#endif //TOOLS_ENABLED + +bool Bone2D::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (path.begins_with("auto_calculate_length_and_angle")) { + set_autocalculate_length_and_angle(p_value); + } else if (path.begins_with("length")) { + set_length(p_value); + } else if (path.begins_with("bone_angle")) { + set_bone_angle(Math::deg2rad(float(p_value))); + } else if (path.begins_with("default_length")) { + set_length(p_value); + } + +#ifdef TOOLS_ENABLED + if (path.begins_with("editor_settings/show_bone_gizmo")) { + _editor_set_show_bone_gizmo(p_value); + } +#endif // TOOLS_ENABLED + + return true; +} + +bool Bone2D::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (path.begins_with("auto_calculate_length_and_angle")) { + r_ret = get_autocalculate_length_and_angle(); + } else if (path.begins_with("length")) { + r_ret = get_length(); + } else if (path.begins_with("bone_angle")) { + r_ret = Math::rad2deg(get_bone_angle()); + } else if (path.begins_with("default_length")) { + r_ret = get_length(); + } + +#ifdef TOOLS_ENABLED + if (path.begins_with("editor_settings/show_bone_gizmo")) { + r_ret = _editor_get_show_bone_gizmo(); + } +#endif // TOOLS_ENABLED + + return true; +} + +void Bone2D::_get_property_list(List<PropertyInfo> *p_list) const { + p_list->push_back(PropertyInfo(Variant::BOOL, "auto_calculate_length_and_angle", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + if (!autocalculate_length_and_angle) { + p_list->push_back(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_RANGE, "1, 1024, 1", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::FLOAT, "bone_angle", PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT)); + } + +#ifdef TOOLS_ENABLED + p_list->push_back(PropertyInfo(Variant::BOOL, "editor_settings/show_bone_gizmo", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); +#endif // TOOLS_ENABLED +} + void Bone2D::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE) { Node *parent = get_parent(); @@ -53,19 +116,54 @@ void Bone2D::_notification(int p_what) { skeleton->bones.push_back(bone); skeleton->_make_bone_setup_dirty(); } + + cache_transform = get_transform(); + copy_transform_to_cache = true; + +#ifdef TOOLS_ENABLED + // Only draw the gizmo in the editor! + if (Engine::get_singleton()->is_editor_hint() == false) { + return; + } + + update(); +#endif // TOOLS_ENABLED } - if (p_what == NOTIFICATION_LOCAL_TRANSFORM_CHANGED) { + + else if (p_what == NOTIFICATION_LOCAL_TRANSFORM_CHANGED) { if (skeleton) { skeleton->_make_transform_dirty(); } + if (copy_transform_to_cache) { + cache_transform = get_transform(); + } +#ifdef TOOLS_ENABLED + // Only draw the gizmo in the editor! + if (Engine::get_singleton()->is_editor_hint() == false) { + return; + } + + update(); + + if (get_parent()) { + Bone2D *parent_bone = Object::cast_to<Bone2D>(get_parent()); + if (parent_bone) { + parent_bone->update(); + } + } +#endif // TOOLS_ENABLED } - if (p_what == NOTIFICATION_MOVED_IN_PARENT) { + + else if (p_what == NOTIFICATION_MOVED_IN_PARENT) { if (skeleton) { skeleton->_make_bone_setup_dirty(); } + if (copy_transform_to_cache) { + cache_transform = get_transform(); + } } - if (p_what == NOTIFICATION_EXIT_TREE) { + else if (p_what == NOTIFICATION_EXIT_TREE) { if (skeleton) { for (int i = 0; i < skeleton->bones.size(); i++) { if (skeleton->bones[i].bone == this) { @@ -77,9 +175,200 @@ void Bone2D::_notification(int p_what) { skeleton = nullptr; } parent_bone = nullptr; + set_transform(cache_transform); } + + else if (p_what == NOTIFICATION_READY) { + if (autocalculate_length_and_angle) { + calculate_length_and_rotation(); + } + } +#ifdef TOOLS_ENABLED + else if (p_what == NOTIFICATION_EDITOR_PRE_SAVE || p_what == NOTIFICATION_EDITOR_POST_SAVE) { + Transform2D tmp_trans = get_transform(); + set_transform(cache_transform); + cache_transform = tmp_trans; + } + // Bone2D Editor gizmo drawing: +#ifndef _MSC_VER +#warning TODO Bone2D gizmo drawing needs to be moved to an editor plugin +#endif + else if (p_what == NOTIFICATION_DRAW) { + // Only draw the gizmo in the editor! + if (Engine::get_singleton()->is_editor_hint() == false) { + return; + } + + if (editor_gizmo_rid.is_null()) { + editor_gizmo_rid = RenderingServer::get_singleton()->canvas_item_create(); + RenderingServer::get_singleton()->canvas_item_set_parent(editor_gizmo_rid, get_canvas_item()); + RenderingServer::get_singleton()->canvas_item_set_z_as_relative_to_parent(editor_gizmo_rid, true); + RenderingServer::get_singleton()->canvas_item_set_z_index(editor_gizmo_rid, 10); + } + RenderingServer::get_singleton()->canvas_item_clear(editor_gizmo_rid); + + if (!_editor_show_bone_gizmo) { + return; + } + + // Undo scaling + Transform2D editor_gizmo_trans = Transform2D(); + editor_gizmo_trans.set_scale(Vector2(1, 1) / get_global_scale()); + RenderingServer::get_singleton()->canvas_item_set_transform(editor_gizmo_rid, editor_gizmo_trans); + + Color bone_color1 = EditorSettings::get_singleton()->get("editors/2d/bone_color1"); + Color bone_color2 = EditorSettings::get_singleton()->get("editors/2d/bone_color2"); + Color bone_ik_color = EditorSettings::get_singleton()->get("editors/2d/bone_ik_color"); + Color bone_outline_color = EditorSettings::get_singleton()->get("editors/2d/bone_outline_color"); + Color bone_selected_color = EditorSettings::get_singleton()->get("editors/2d/bone_selected_color"); + + bool Bone2D_found = false; + for (int i = 0; i < get_child_count(); i++) { + Bone2D *child_node = nullptr; + child_node = Object::cast_to<Bone2D>(get_child(i)); + if (!child_node) { + continue; + } + Bone2D_found = true; + + Vector<Vector2> bone_shape; + Vector<Vector2> bone_shape_outline; + + _editor_get_bone_shape(&bone_shape, &bone_shape_outline, child_node); + + Vector<Color> colors; + if (has_meta("_local_pose_override_enabled_")) { + colors.push_back(bone_ik_color); + colors.push_back(bone_ik_color); + colors.push_back(bone_ik_color); + colors.push_back(bone_ik_color); + } else { + colors.push_back(bone_color1); + colors.push_back(bone_color2); + colors.push_back(bone_color1); + colors.push_back(bone_color2); + } + + Vector<Color> outline_colors; + if (CanvasItemEditor::get_singleton()->editor_selection->is_selected(this)) { + outline_colors.push_back(bone_selected_color); + outline_colors.push_back(bone_selected_color); + outline_colors.push_back(bone_selected_color); + outline_colors.push_back(bone_selected_color); + outline_colors.push_back(bone_selected_color); + outline_colors.push_back(bone_selected_color); + } else { + outline_colors.push_back(bone_outline_color); + outline_colors.push_back(bone_outline_color); + outline_colors.push_back(bone_outline_color); + outline_colors.push_back(bone_outline_color); + outline_colors.push_back(bone_outline_color); + outline_colors.push_back(bone_outline_color); + } + + RenderingServer::get_singleton()->canvas_item_add_polygon(editor_gizmo_rid, bone_shape_outline, outline_colors); + RenderingServer::get_singleton()->canvas_item_add_polygon(editor_gizmo_rid, bone_shape, colors); + } + + if (!Bone2D_found) { + Vector<Vector2> bone_shape; + Vector<Vector2> bone_shape_outline; + + _editor_get_bone_shape(&bone_shape, &bone_shape_outline, nullptr); + + Vector<Color> colors; + if (has_meta("_local_pose_override_enabled_")) { + colors.push_back(bone_ik_color); + colors.push_back(bone_ik_color); + colors.push_back(bone_ik_color); + colors.push_back(bone_ik_color); + } else { + colors.push_back(bone_color1); + colors.push_back(bone_color2); + colors.push_back(bone_color1); + colors.push_back(bone_color2); + } + + Vector<Color> outline_colors; + if (CanvasItemEditor::get_singleton()->editor_selection->is_selected(this)) { + outline_colors.push_back(bone_selected_color); + outline_colors.push_back(bone_selected_color); + outline_colors.push_back(bone_selected_color); + outline_colors.push_back(bone_selected_color); + outline_colors.push_back(bone_selected_color); + outline_colors.push_back(bone_selected_color); + } else { + outline_colors.push_back(bone_outline_color); + outline_colors.push_back(bone_outline_color); + outline_colors.push_back(bone_outline_color); + outline_colors.push_back(bone_outline_color); + outline_colors.push_back(bone_outline_color); + outline_colors.push_back(bone_outline_color); + } + + RenderingServer::get_singleton()->canvas_item_add_polygon(editor_gizmo_rid, bone_shape_outline, outline_colors); + RenderingServer::get_singleton()->canvas_item_add_polygon(editor_gizmo_rid, bone_shape, colors); + } + } +#endif // TOOLS_ENALBED } +#ifdef TOOLS_ENABLED +bool Bone2D::_editor_get_bone_shape(Vector<Vector2> *p_shape, Vector<Vector2> *p_outline_shape, Bone2D *p_other_bone) { + int bone_width = EditorSettings::get_singleton()->get("editors/2d/bone_width"); + int bone_outline_width = EditorSettings::get_singleton()->get("editors/2d/bone_outline_size"); + + if (!is_inside_tree()) { + return false; //may have been removed + } + if (!p_other_bone && length <= 0) { + return false; + } + + Vector2 rel; + if (p_other_bone) { + rel = (p_other_bone->get_global_transform().get_origin() - get_global_transform().get_origin()); + rel = rel.rotated(-get_global_rotation()); // Undo Bone2D node's rotation so its drawn correctly regardless of the node's rotation + } else { + float angle_to_use = get_rotation() + bone_angle; + rel = Vector2(cos(angle_to_use), sin(angle_to_use)) * (length * MIN(get_global_scale().x, get_global_scale().y)); + rel = rel.rotated(-get_rotation()); // Undo Bone2D node's rotation so its drawn correctly regardless of the node's rotation + } + + Vector2 relt = rel.rotated(Math_PI * 0.5).normalized() * bone_width; + Vector2 reln = rel.normalized(); + Vector2 reltn = relt.normalized(); + + if (p_shape) { + p_shape->clear(); + p_shape->push_back(Vector2(0, 0)); + p_shape->push_back(rel * 0.2 + relt); + p_shape->push_back(rel); + p_shape->push_back(rel * 0.2 - relt); + } + + if (p_outline_shape) { + p_outline_shape->clear(); + p_outline_shape->push_back((-reln - reltn) * bone_outline_width); + p_outline_shape->push_back((-reln + reltn) * bone_outline_width); + p_outline_shape->push_back(rel * 0.2 + relt + reltn * bone_outline_width); + p_outline_shape->push_back(rel + (reln + reltn) * bone_outline_width); + p_outline_shape->push_back(rel + (reln - reltn) * bone_outline_width); + p_outline_shape->push_back(rel * 0.2 - relt - reltn * bone_outline_width); + } + return true; +} + +void Bone2D::_editor_set_show_bone_gizmo(bool p_show_gizmo) { + _editor_show_bone_gizmo = p_show_gizmo; + update(); +} + +bool Bone2D::_editor_get_show_bone_gizmo() const { + return _editor_show_bone_gizmo; +} +#endif // TOOLS_ENABLED + void Bone2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_rest", "rest"), &Bone2D::set_rest); ClassDB::bind_method(D_METHOD("get_rest"), &Bone2D::get_rest); @@ -90,8 +379,14 @@ void Bone2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_default_length", "default_length"), &Bone2D::set_default_length); ClassDB::bind_method(D_METHOD("get_default_length"), &Bone2D::get_default_length); + ClassDB::bind_method(D_METHOD("set_autocalculate_length_and_angle", "auto_calculate"), &Bone2D::set_autocalculate_length_and_angle); + ClassDB::bind_method(D_METHOD("get_autocalculate_length_and_angle"), &Bone2D::get_autocalculate_length_and_angle); + ClassDB::bind_method(D_METHOD("set_length", "length"), &Bone2D::set_length); + ClassDB::bind_method(D_METHOD("get_length"), &Bone2D::get_length); + ClassDB::bind_method(D_METHOD("set_bone_angle", "angle"), &Bone2D::set_bone_angle); + ClassDB::bind_method(D_METHOD("get_bone_angle"), &Bone2D::get_bone_angle); + ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "rest"), "set_rest", "get_rest"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "default_length", PROPERTY_HINT_RANGE, "1,1024,1"), "set_default_length", "get_default_length"); } void Bone2D::set_rest(const Transform2D &p_rest) { @@ -119,12 +414,14 @@ void Bone2D::apply_rest() { set_transform(rest); } -void Bone2D::set_default_length(real_t p_length) { - default_length = p_length; +void Bone2D::set_default_length(float p_length) { + WARN_DEPRECATED_MSG("set_default_length is deprecated. Please use set_length instead!"); + set_length(p_length); } -real_t Bone2D::get_default_length() const { - return default_length; +float Bone2D::get_default_length() const { + WARN_DEPRECATED_MSG("get_default_length is deprecated. Please use get_length instead!"); + return get_length(); } int Bone2D::get_index_in_skeleton() const { @@ -150,16 +447,121 @@ TypedArray<String> Bone2D::get_configuration_warnings() const { return warnings; } +void Bone2D::calculate_length_and_rotation() { + // if there is at least a single child Bone2D node, we can calculate + // the length and direction. We will always just use the first Bone2D for this. + bool calculated = false; + int child_count = get_child_count(); + if (child_count > 0) { + for (int i = 0; i < child_count; i++) { + Bone2D *child = Object::cast_to<Bone2D>(get_child(i)); + if (child) { + Vector2 child_local_pos = to_local(child->get_global_transform().get_origin()); + length = child_local_pos.length(); + bone_angle = Math::atan2(child_local_pos.normalized().y, child_local_pos.normalized().x); + calculated = true; + break; + } + } + } + if (calculated) { + return; // Finished! + } else { + WARN_PRINT("No Bone2D children of node " + get_name() + ". Cannot calculate bone length or angle reliably.\nUsing transform rotation for bone angle"); + bone_angle = get_transform().get_rotation(); + return; + } +} + +void Bone2D::set_autocalculate_length_and_angle(bool p_autocalculate) { + autocalculate_length_and_angle = p_autocalculate; + if (autocalculate_length_and_angle) { + calculate_length_and_rotation(); + } + notify_property_list_changed(); +} + +bool Bone2D::get_autocalculate_length_and_angle() const { + return autocalculate_length_and_angle; +} + +void Bone2D::set_length(float p_length) { + length = p_length; + +#ifdef TOOLS_ENABLED + update(); +#endif // TOOLS_ENABLED +} + +float Bone2D::get_length() const { + return length; +} + +void Bone2D::set_bone_angle(float p_angle) { + bone_angle = p_angle; + +#ifdef TOOLS_ENABLED + update(); +#endif // TOOLS_ENABLED +} + +float Bone2D::get_bone_angle() const { + return bone_angle; +} + Bone2D::Bone2D() { + skeleton = nullptr; + parent_bone = nullptr; + skeleton_index = -1; + length = 16; + bone_angle = 0; + autocalculate_length_and_angle = true; set_notify_local_transform(true); //this is a clever hack so the bone knows no rest has been set yet, allowing to show an error. for (int i = 0; i < 3; i++) { rest[i] = Vector2(0, 0); } + copy_transform_to_cache = true; +} + +Bone2D::~Bone2D() { +#ifdef TOOLS_ENABLED + if (!editor_gizmo_rid.is_null()) { + RenderingServer::get_singleton()->free(editor_gizmo_rid); + } +#endif // TOOLS_ENABLED } ////////////////////////////////////// +bool Skeleton2D::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (path.begins_with("modification_stack")) { + set_modification_stack(p_value); + return true; + } + return true; +} + +bool Skeleton2D::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (path.begins_with("modification_stack")) { + r_ret = get_modification_stack(); + return true; + } + return true; +} + +void Skeleton2D::_get_property_list(List<PropertyInfo> *p_list) const { + p_list->push_back( + PropertyInfo(Variant::OBJECT, "modification_stack", + PROPERTY_HINT_RESOURCE_TYPE, + "SkeletonModificationStack2D", + PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DEFERRED_SET_RESOURCE | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE)); +} + void Skeleton2D::_make_bone_setup_dirty() { if (bone_setup_dirty) { return; @@ -189,6 +591,8 @@ void Skeleton2D::_update_bone_setup() { } else { bones.write[i].parent_index = -1; } + + bones.write[i].local_pose_override = bones[i].bone->get_skeleton_rest(); } transform_dirty = true; @@ -257,19 +661,121 @@ void Skeleton2D::_notification(int p_what) { if (transform_dirty) { _update_transform(); } - request_ready(); } if (p_what == NOTIFICATION_TRANSFORM_CHANGED) { RS::get_singleton()->skeleton_set_base_transform_2d(skeleton, get_global_transform()); + } else if (p_what == NOTIFICATION_INTERNAL_PROCESS) { + if (modification_stack.is_valid()) { + execute_modifications(get_process_delta_time(), SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_process); + } + } else if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) { + if (modification_stack.is_valid()) { + execute_modifications(get_physics_process_delta_time(), SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_physics_process); + } } +#ifdef TOOLS_ENABLED + else if (p_what == NOTIFICATION_DRAW) { + if (Engine::get_singleton()->is_editor_hint()) { + if (modification_stack.is_valid()) { + modification_stack->draw_editor_gizmos(); + } + } + } +#endif // TOOLS_ENABLED } RID Skeleton2D::get_skeleton() const { return skeleton; } +void Skeleton2D::set_bone_local_pose_override(int p_bone_idx, Transform2D p_override, float p_amount, bool p_persistent) { + ERR_FAIL_INDEX_MSG(p_bone_idx, bones.size(), "Bone index is out of range!"); + bones.write[p_bone_idx].local_pose_override = p_override; + bones.write[p_bone_idx].local_pose_override_amount = p_amount; + bones.write[p_bone_idx].local_pose_override_persistent = p_persistent; +} + +Transform2D Skeleton2D::get_bone_local_pose_override(int p_bone_idx) { + ERR_FAIL_INDEX_V_MSG(p_bone_idx, bones.size(), Transform2D(), "Bone index is out of range!"); + return bones[p_bone_idx].local_pose_override; +} + +void Skeleton2D::set_modification_stack(Ref<SkeletonModificationStack2D> p_stack) { + if (modification_stack.is_valid()) { + modification_stack->is_setup = false; + modification_stack->set_skeleton(nullptr); + + set_process_internal(false); + set_physics_process_internal(false); + } + modification_stack = p_stack; + if (modification_stack.is_valid()) { + modification_stack->set_skeleton(this); + modification_stack->setup(); + + set_process_internal(true); + set_physics_process_internal(true); + +#ifdef TOOLS_ENABLED + modification_stack->set_editor_gizmos_dirty(true); +#endif // TOOLS_ENABLED + } +} + +Ref<SkeletonModificationStack2D> Skeleton2D::get_modification_stack() const { + return modification_stack; +} + +void Skeleton2D::execute_modifications(float p_delta, int p_execution_mode) { + if (!modification_stack.is_valid()) { + return; + } + + // Do not cache the transform changes caused by the modifications! + for (int i = 0; i < bones.size(); i++) { + bones[i].bone->copy_transform_to_cache = false; + } + + if (modification_stack->skeleton != this) { + modification_stack->set_skeleton(this); + } + + modification_stack->execute(p_delta, p_execution_mode); + + // Only apply the local pose override on _process. Otherwise, just calculate the local_pose_override and reset the transform. + if (p_execution_mode == SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_process) { + for (int i = 0; i < bones.size(); i++) { + if (bones[i].local_pose_override_amount > 0) { + bones[i].bone->set_meta("_local_pose_override_enabled_", true); + + Transform2D final_trans = bones[i].bone->cache_transform; + final_trans = final_trans.interpolate_with(bones[i].local_pose_override, bones[i].local_pose_override_amount); + bones[i].bone->set_transform(final_trans); + bones[i].bone->propagate_call("force_update_transform"); + + if (bones[i].local_pose_override_persistent) { + bones.write[i].local_pose_override_amount = 0.0; + } + } else { + // TODO: see if there is a way to undo the override without having to resort to setting every bone's transform. + bones[i].bone->remove_meta("_local_pose_override_enabled_"); + bones[i].bone->set_transform(bones[i].bone->cache_transform); + } + } + } + + // Cache any future transform changes + for (int i = 0; i < bones.size(); i++) { + bones[i].bone->copy_transform_to_cache = true; + } + +#ifdef TOOLS_ENABLED + modification_stack->set_editor_gizmos_dirty(true); +#endif // TOOLS_ENABLED +} + void Skeleton2D::_bind_methods() { ClassDB::bind_method(D_METHOD("_update_bone_setup"), &Skeleton2D::_update_bone_setup); ClassDB::bind_method(D_METHOD("_update_transform"), &Skeleton2D::_update_transform); @@ -279,6 +785,13 @@ void Skeleton2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_skeleton"), &Skeleton2D::get_skeleton); + ClassDB::bind_method(D_METHOD("set_modification_stack", "modification_stack"), &Skeleton2D::set_modification_stack); + ClassDB::bind_method(D_METHOD("get_modification_stack"), &Skeleton2D::get_modification_stack); + ClassDB::bind_method(D_METHOD("execute_modifications", "execution_mode", "execution_mode"), &Skeleton2D::execute_modifications); + + ClassDB::bind_method(D_METHOD("set_bone_local_pose_override", "bone_idx", "override_pose", "strength", "persistent"), &Skeleton2D::set_bone_local_pose_override); + ClassDB::bind_method(D_METHOD("get_bone_local_pose_override", "bone_idx"), &Skeleton2D::get_bone_local_pose_override); + ADD_SIGNAL(MethodInfo("bone_setup_changed")); } diff --git a/scene/2d/skeleton_2d.h b/scene/2d/skeleton_2d.h index fd62b87bde..59bd711960 100644 --- a/scene/2d/skeleton_2d.h +++ b/scene/2d/skeleton_2d.h @@ -32,6 +32,7 @@ #define SKELETON_2D_H #include "scene/2d/node_2d.h" +#include "scene/resources/skeleton_modification_2d.h" class Skeleton2D; @@ -46,15 +47,32 @@ class Bone2D : public Node2D { Bone2D *parent_bone = nullptr; Skeleton2D *skeleton = nullptr; Transform2D rest; - real_t default_length = 16.0; + + bool autocalculate_length_and_angle = true; + float length = 16; + float bone_angle = 0; int skeleton_index = -1; + void calculate_length_and_rotation(); + +#ifdef TOOLS_ENABLED + RID editor_gizmo_rid; + bool _editor_get_bone_shape(Vector<Vector2> *p_shape, Vector<Vector2> *p_outline_shape, Bone2D *p_other_bone); + bool _editor_show_bone_gizmo = true; +#endif // TOOLS ENABLED + protected: void _notification(int p_what); static void _bind_methods(); + bool _set(const StringName &p_path, const Variant &p_value); + bool _get(const StringName &p_path, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; public: + Transform2D cache_transform; + bool copy_transform_to_cache = true; + void set_rest(const Transform2D &p_rest); Transform2D get_rest() const; void apply_rest(); @@ -65,11 +83,26 @@ public: void set_default_length(real_t p_length); real_t get_default_length() const; + void set_autocalculate_length_and_angle(bool p_autocalculate); + bool get_autocalculate_length_and_angle() const; + void set_length(float p_length); + float get_length() const; + void set_bone_angle(float p_angle); + float get_bone_angle() const; + int get_index_in_skeleton() const; +#ifdef TOOLS_ENABLED + void _editor_set_show_bone_gizmo(bool p_show_gizmo); + bool _editor_get_show_bone_gizmo() const; +#endif // TOOLS_ENABLED + Bone2D(); + ~Bone2D(); }; +class SkeletonModificationStack2D; + class Skeleton2D : public Node2D { GDCLASS(Skeleton2D, Node2D); @@ -86,6 +119,11 @@ class Skeleton2D : public Node2D { int parent_index = 0; Transform2D accum_transform; Transform2D rest_inverse; + + //Transform2D local_pose_cache; + Transform2D local_pose_override; + float local_pose_override_amount = 0; + bool local_pose_override_persistent = false; }; Vector<Bone> bones; @@ -100,15 +138,28 @@ class Skeleton2D : public Node2D { RID skeleton; + Ref<SkeletonModificationStack2D> modification_stack; + protected: void _notification(int p_what); static void _bind_methods(); + bool _set(const StringName &p_path, const Variant &p_value); + bool _get(const StringName &p_path, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; public: int get_bone_count() const; Bone2D *get_bone(int p_idx); RID get_skeleton() const; + + void set_bone_local_pose_override(int p_bone_idx, Transform2D p_override, float p_amount, bool p_persistent = true); + Transform2D get_bone_local_pose_override(int p_bone_idx); + + Ref<SkeletonModificationStack2D> get_modification_stack() const; + void set_modification_stack(Ref<SkeletonModificationStack2D> p_stack); + void execute_modifications(float p_delta, int p_execution_mode); + Skeleton2D(); ~Skeleton2D(); }; diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 24b907fe6c..e79dfb019c 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -334,6 +334,12 @@ TileMap::VisibilityMode TileMap::get_navigation_visibility_mode() { return show_navigation; } +void TileMap::set_y_sort_enabled(bool p_enable) { + Node2D::set_y_sort_enabled(p_enable); + _recreate_quadrants(); + emit_signal("changed"); +} + void TileMap::update_dirty_quadrants() { if (!pending_update) { return; diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index f02455a84b..3001e6b471 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -279,6 +279,7 @@ public: int get_effective_quadrant_size() const; void update_dirty_quadrants(); + virtual void set_y_sort_enabled(bool p_enable) override; Vector2 map_to_world(const Vector2i &p_pos) const; Vector2i world_to_map(const Vector2 &p_pos) const; diff --git a/scene/2d/visibility_notifier_2d.cpp b/scene/2d/visibility_notifier_2d.cpp index 8feb47f1cc..c85b2c85a4 100644 --- a/scene/2d/visibility_notifier_2d.cpp +++ b/scene/2d/visibility_notifier_2d.cpp @@ -176,7 +176,7 @@ void VisibilityEnabler2D::_find_nodes(Node *p_node) { { RigidBody2D *rb2d = Object::cast_to<RigidBody2D>(p_node); - if (rb2d && ((rb2d->get_mode() == RigidBody2D::MODE_CHARACTER || rb2d->get_mode() == RigidBody2D::MODE_RIGID))) { + if (rb2d && ((rb2d->get_mode() == RigidBody2D::MODE_DYNAMIC || rb2d->get_mode() == RigidBody2D::MODE_DYNAMIC_LOCKED))) { add = true; meta = rb2d->get_mode(); } diff --git a/scene/2d/y_sort.cpp b/scene/2d/y_sort.cpp deleted file mode 100644 index 7e7bc27cc2..0000000000 --- a/scene/2d/y_sort.cpp +++ /dev/null @@ -1,52 +0,0 @@ -/*************************************************************************/ -/* y_sort.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "y_sort.h" - -void YSort::set_sort_enabled(bool p_enabled) { - sort_enabled = p_enabled; - RS::get_singleton()->canvas_item_set_sort_children_by_y(get_canvas_item(), sort_enabled); -} - -bool YSort::is_sort_enabled() const { - return sort_enabled; -} - -void YSort::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_sort_enabled", "enabled"), &YSort::set_sort_enabled); - ClassDB::bind_method(D_METHOD("is_sort_enabled"), &YSort::is_sort_enabled); - - ADD_GROUP("Sort", "sort_"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sort_enabled"), "set_sort_enabled", "is_sort_enabled"); -} - -YSort::YSort() { - RS::get_singleton()->canvas_item_set_sort_children_by_y(get_canvas_item(), true); -} |