diff options
Diffstat (limited to 'scene/2d')
-rw-r--r-- | scene/2d/physics_body_2d.cpp | 57 | ||||
-rw-r--r-- | scene/2d/physics_body_2d.h | 2 |
2 files changed, 48 insertions, 11 deletions
diff --git a/scene/2d/physics_body_2d.cpp b/scene/2d/physics_body_2d.cpp index 6c1cdc2129..2f552af3d9 100644 --- a/scene/2d/physics_body_2d.cpp +++ b/scene/2d/physics_body_2d.cpp @@ -76,13 +76,46 @@ Ref<KinematicCollision2D> PhysicsBody2D::_move(const Vector2 &p_motion, bool p_i 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) { +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, bool p_cancel_sliding) { 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); + // Restore direction of motion to be along original motion, + // in order to avoid sliding due to recovery, + // but only if collision depth is low enough to avoid tunneling. + real_t motion_length = p_motion.length(); + if (motion_length > CMP_EPSILON) { + real_t precision = 0.001; + + if (colliding && p_cancel_sliding) { + // Can't just use margin as a threshold because collision depth is calculated on unsafe motion, + // so even in normal resting cases the depth can be a bit more than the margin. + precision += motion_length * (r_result.collision_unsafe_fraction - r_result.collision_safe_fraction); + + if (r_result.collision_depth > (real_t)p_margin + precision) { + p_cancel_sliding = false; + } + } + + if (p_cancel_sliding) { + // Check depth of recovery. + Vector2 motion_normal = p_motion / motion_length; + real_t dot = r_result.motion.dot(motion_normal); + Vector2 recovery = r_result.motion - motion_normal * dot; + real_t recovery_length = recovery.length(); + // Fixes cases where canceling slide causes the motion to go too deep into the ground, + // Becauses we're only taking rest information into account and not general recovery. + if (recovery_length < (real_t)p_margin + precision) { + // Apply adjustment to motion. + r_result.motion = motion_normal * dot; + r_result.remainder = p_motion - r_result.motion; + } + } + } + if (!p_test_only) { gt.elements[2] += r_result.motion; set_global_transform(gt); @@ -942,15 +975,16 @@ void CharacterBody2D::move_and_slide() { floor_normal = Vector2(); floor_velocity = Vector2(); - int slide_count = max_slides; - while (slide_count) { + // No sliding on first attempt to keep floor motion stable when possible. + bool sliding_enabled = false; + for (int iteration = 0; iteration < max_slides; ++iteration) { 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, infinite_inertia, result, margin); + collided = move_and_collide(motion, infinite_inertia, result, margin, true, false, !sliding_enabled); if (!collided) { motion = Vector2(); //clear because no collision happened and motion completed } @@ -966,7 +1000,6 @@ void CharacterBody2D::move_and_slide() { found_collision = true; motion_results.push_back(result); - motion = result.remainder; if (up_direction == Vector2()) { //all is a wall @@ -980,7 +1013,7 @@ void CharacterBody2D::move_and_slide() { floor_velocity = result.collider_velocity; if (stop_on_slope) { - if ((body_velocity_normal + up_direction).length() < 0.01 && result.motion.length() < 1) { + if ((body_velocity_normal + up_direction).length() < 0.01) { Transform2D gt = get_global_transform(); gt.elements[2] -= result.motion.slide(up_direction); set_global_transform(gt); @@ -995,16 +1028,20 @@ void CharacterBody2D::move_and_slide() { } } - motion = motion.slide(result.collision_normal); - linear_velocity = linear_velocity.slide(result.collision_normal); + if (sliding_enabled || !on_floor) { + motion = result.remainder.slide(result.collision_normal); + linear_velocity = linear_velocity.slide(result.collision_normal); + } else { + motion = result.remainder; + } } + + sliding_enabled = true; } if (!found_collision || motion == Vector2()) { break; } - - --slide_count; } if (!was_on_floor || snap == Vector2()) { diff --git a/scene/2d/physics_body_2d.h b/scene/2d/physics_body_2d.h index f084a247aa..b1d48a2cc9 100644 --- a/scene/2d/physics_body_2d.h +++ b/scene/2d/physics_body_2d.h @@ -50,7 +50,7 @@ protected: 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 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 p_cancel_sliding = true); 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(); |