diff options
Diffstat (limited to 'scene/2d/physics_body_2d.cpp')
-rw-r--r-- | scene/2d/physics_body_2d.cpp | 297 |
1 files changed, 186 insertions, 111 deletions
diff --git a/scene/2d/physics_body_2d.cpp b/scene/2d/physics_body_2d.cpp index be619ed60d..5b12da8b57 100644 --- a/scene/2d/physics_body_2d.cpp +++ b/scene/2d/physics_body_2d.cpp @@ -76,21 +76,21 @@ 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 p_cancel_sliding) { +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, const Set<RID> &p_exclude) { 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); + bool colliding = PhysicsServer2D::get_singleton()->body_test_motion(get_rid(), gt, p_motion, p_infinite_inertia, p_margin, &r_result, p_exclude_raycast_shapes, p_exclude); // 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) { + if (p_cancel_sliding) { + real_t motion_length = p_motion.length(); real_t precision = 0.001; - if (colliding && p_cancel_sliding) { + if (colliding) { // 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); @@ -101,16 +101,21 @@ bool PhysicsBody2D::move_and_collide(const Vector2 &p_motion, bool p_infinite_in } if (p_cancel_sliding) { + // When motion is null, recovery is the resulting motion. + Vector2 motion_normal; + if (motion_length > CMP_EPSILON) { + motion_normal = p_motion / motion_length; + } + // 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 projected_length = r_result.motion.dot(motion_normal); + Vector2 recovery = r_result.motion - motion_normal * projected_length; 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. + // because 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.motion = motion_normal * projected_length; r_result.remainder = p_motion - r_result.motion; } } @@ -224,6 +229,13 @@ void StaticBody2D::set_kinematic_motion_enabled(bool p_enabled) { set_body_mode(PhysicsServer2D::BODY_MODE_STATIC); } +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + update_configuration_warnings(); + return; + } +#endif + _update_kinematic_motion(); } @@ -231,8 +243,75 @@ bool StaticBody2D::is_kinematic_motion_enabled() const { return kinematic_motion; } +void StaticBody2D::set_sync_to_physics(bool p_enable) { + if (sync_to_physics == p_enable) { + return; + } + + sync_to_physics = p_enable; + +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + update_configuration_warnings(); + return; + } +#endif + + if (kinematic_motion) { + _update_kinematic_motion(); + } +} + +bool StaticBody2D::is_sync_to_physics_enabled() const { + return sync_to_physics; +} + +void StaticBody2D::_direct_state_changed(Object *p_state) { + if (!sync_to_physics) { + return; + } + + PhysicsDirectBodyState2D *state = Object::cast_to<PhysicsDirectBodyState2D>(p_state); + ERR_FAIL_NULL_MSG(state, "Method '_direct_state_changed' must receive a valid PhysicsDirectBodyState2D object as argument"); + + last_valid_transform = state->get_transform(); + set_notify_local_transform(false); + set_global_transform(last_valid_transform); + set_notify_local_transform(true); +} + +TypedArray<String> StaticBody2D::get_configuration_warnings() const { + TypedArray<String> warnings = PhysicsBody2D::get_configuration_warnings(); + + if (sync_to_physics && !kinematic_motion) { + warnings.push_back(TTR("Sync to physics works only when kinematic motion is enabled.")); + } + + return warnings; +} + void StaticBody2D::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + last_valid_transform = get_global_transform(); + } break; + + case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { + // Used by sync to physics, send the new transform to the physics... + Transform2D new_transform = get_global_transform(); + + real_t delta_time = get_physics_process_delta_time(); + 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); + + // ... but then revert changes. + set_notify_local_transform(false); + set_global_transform(last_valid_transform); + set_notify_local_transform(true); + } break; + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint()) { @@ -242,19 +321,23 @@ void StaticBody2D::_notification(int p_what) { ERR_FAIL_COND(!kinematic_motion); - real_t delta_time = get_physics_process_delta_time(); - Transform2D new_transform = get_global_transform(); + real_t delta_time = get_physics_process_delta_time(); 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); + if (sync_to_physics) { + // Propagate transform change to node. + set_global_transform(new_transform); + } else { + 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); + // Propagate transform change to node. + set_block_transform_notify(true); + set_global_transform(new_transform); + set_block_transform_notify(false); + } } break; } } @@ -271,10 +354,14 @@ void StaticBody2D::_bind_methods() { 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); + ClassDB::bind_method(D_METHOD("set_sync_to_physics", "enable"), &StaticBody2D::set_sync_to_physics); + ClassDB::bind_method(D_METHOD("is_sync_to_physics_enabled"), &StaticBody2D::is_sync_to_physics_enabled); + 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::BOOL, "kinematic_motion"), "set_kinematic_motion_enabled", "is_kinematic_motion_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync_to_physics"), "set_sync_to_physics", "is_sync_to_physics_enabled"); } StaticBody2D::StaticBody2D() : @@ -298,14 +385,24 @@ void StaticBody2D::_update_kinematic_motion() { } #endif + if (kinematic_motion && sync_to_physics) { + PhysicsServer2D::get_singleton()->body_set_force_integration_callback(get_rid(), callable_mp(this, &StaticBody2D::_direct_state_changed)); + set_only_update_transform_changes(true); + set_notify_local_transform(true); + } else { + PhysicsServer2D::get_singleton()->body_set_force_integration_callback(get_rid(), Callable()); + set_only_update_transform_changes(false); + set_notify_local_transform(false); + } + + bool needs_physics_process = false; if (kinematic_motion) { if (!Math::is_zero_approx(constant_angular_velocity) || !constant_linear_velocity.is_equal_approx(Vector2())) { - set_physics_process_internal(true); - return; + needs_physics_process = true; } } - set_physics_process_internal(false); + set_physics_process_internal(needs_physics_process); } void RigidBody2D::_body_enter_tree(ObjectID p_id) { @@ -955,11 +1052,14 @@ void RigidBody2D::_reload_physics_characteristics() { void CharacterBody2D::move_and_slide() { Vector2 body_velocity_normal = linear_velocity.normalized(); - bool was_on_floor = on_floor; + // Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky + float delta = Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time(); + Vector2 current_floor_velocity = floor_velocity; - if (on_floor && on_floor_body.is_valid()) { + + if ((on_floor || on_wall) && on_floor_body.is_valid()) { //this approach makes sure there is less delay between the actual body velocity and the one we saved PhysicsDirectBodyState2D *bs = PhysicsServer2D::get_singleton()->body_get_direct_state(on_floor_body); if (bs) { @@ -967,19 +1067,30 @@ void CharacterBody2D::move_and_slide() { } } - // 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 + linear_velocity) * (Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time()); - + motion_results.clear(); on_floor = false; - on_floor_body = RID(); on_ceiling = false; on_wall = false; - motion_results.clear(); floor_normal = Vector2(); floor_velocity = Vector2(); - // No sliding on first attempt to keep floor motion stable when possible. - bool sliding_enabled = false; + if (current_floor_velocity != Vector2()) { + PhysicsServer2D::MotionResult floor_result; + Set<RID> exclude; + exclude.insert(on_floor_body); + if (move_and_collide(current_floor_velocity * delta, infinite_inertia, floor_result, true, false, false, false, exclude)) { + motion_results.push_back(floor_result); + _set_collision_direction(floor_result); + } + } + + on_floor_body = RID(); + Vector2 motion = linear_velocity * delta; + + // No sliding on first attempt to keep floor motion stable when possible, + // when stop on slope is enabled. + bool sliding_enabled = !stop_on_slope; + for (int iteration = 0; iteration < max_slides; ++iteration) { PhysicsServer2D::MotionResult result; bool found_collision = false; @@ -1003,31 +1114,19 @@ void CharacterBody2D::move_and_slide() { found_collision = true; motion_results.push_back(result); - - if (up_direction == Vector2()) { - //all is a wall - on_wall = true; - } else { - if (Math::acos(result.collision_normal.dot(up_direction)) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //floor - - on_floor = true; - floor_normal = result.collision_normal; - on_floor_body = result.collider; - floor_velocity = result.collider_velocity; - - if (stop_on_slope) { - 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); - linear_velocity = Vector2(); - return; - } + _set_collision_direction(result); + + if (on_floor && stop_on_slope) { + if ((body_velocity_normal + up_direction).length() < 0.01) { + Transform2D gt = get_global_transform(); + if (result.motion.length() > margin) { + gt.elements[2] -= result.motion.slide(up_direction); + } else { + gt.elements[2] -= result.motion; } - } else if (Math::acos(result.collision_normal.dot(-up_direction)) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //ceiling - on_ceiling = true; - } else { - on_wall = true; + set_global_transform(gt); + linear_velocity = Vector2(); + return; } } @@ -1047,6 +1146,11 @@ void CharacterBody2D::move_and_slide() { } } + if (!on_floor && !on_wall) { + // Add last platform velocity when just left a moving platform. + linear_velocity += current_floor_velocity; + } + if (!was_on_floor || snap == Vector2()) { return; } @@ -1054,7 +1158,7 @@ void CharacterBody2D::move_and_slide() { // Apply snap. Transform2D gt = get_global_transform(); PhysicsServer2D::MotionResult result; - if (move_and_collide(snap, infinite_inertia, result, margin, false, true)) { + if (move_and_collide(snap, infinite_inertia, result, margin, false, true, false)) { bool apply = true; if (up_direction != Vector2()) { if (Math::acos(result.collision_normal.dot(up_direction)) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { @@ -1065,9 +1169,12 @@ void CharacterBody2D::move_and_slide() { 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. - result.motion = up_direction * up_direction.dot(result.motion); + if (result.motion.length() > margin) { + result.motion = up_direction * up_direction.dot(result.motion); + } else { + result.motion = Vector2(); + } } - } else { apply = false; } @@ -1080,6 +1187,29 @@ void CharacterBody2D::move_and_slide() { } } +void CharacterBody2D::_set_collision_direction(const PhysicsServer2D::MotionResult &p_result) { + on_floor = false; + on_ceiling = false; + on_wall = false; + if (up_direction == Vector2()) { + //all is a wall + on_wall = true; + } else { + if (Math::acos(p_result.collision_normal.dot(up_direction)) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //floor + on_floor = true; + floor_normal = p_result.collision_normal; + on_floor_body = p_result.collider; + floor_velocity = p_result.collider_velocity; + } else if (Math::acos(p_result.collision_normal.dot(-up_direction)) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //ceiling + on_ceiling = true; + } else { + on_wall = true; + on_floor_body = p_result.collider; + floor_velocity = p_result.collider_velocity; + } + } +} + bool CharacterBody2D::separate_raycast_shapes(PhysicsServer2D::MotionResult &r_result) { PhysicsServer2D::SeparationResult sep_res[8]; //max 8 rays @@ -1168,45 +1298,6 @@ Ref<KinematicCollision2D> CharacterBody2D::_get_slide_collision(int p_bounce) { return slide_colliders[p_bounce]; } -void CharacterBody2D::set_sync_to_physics(bool p_enable) { - if (sync_to_physics == p_enable) { - return; - } - sync_to_physics = p_enable; - - if (Engine::get_singleton()->is_editor_hint()) { - return; - } - - if (p_enable) { - 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 { - PhysicsServer2D::get_singleton()->body_set_force_integration_callback(get_rid(), Callable()); - set_only_update_transform_changes(false); - set_notify_local_transform(false); - } -} - -bool CharacterBody2D::is_sync_to_physics_enabled() const { - return sync_to_physics; -} - -void CharacterBody2D::_direct_state_changed(Object *p_state) { - if (!sync_to_physics) { - return; - } - - PhysicsDirectBodyState2D *state = Object::cast_to<PhysicsDirectBodyState2D>(p_state); - ERR_FAIL_NULL_MSG(state, "Method '_direct_state_changed' must receive a valid PhysicsDirectBodyState2D object as argument"); - - last_valid_transform = state->get_transform(); - set_notify_local_transform(false); - set_global_transform(last_valid_transform); - set_notify_local_transform(true); -} - void CharacterBody2D::set_safe_margin(real_t p_margin) { margin = p_margin; } @@ -1266,8 +1357,6 @@ void CharacterBody2D::set_up_direction(const Vector2 &p_up_direction) { void CharacterBody2D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { - last_valid_transform = get_global_transform(); - // Reset move_and_slide() data. on_floor = false; on_floor_body = RID(); @@ -1276,16 +1365,6 @@ void CharacterBody2D::_notification(int p_what) { motion_results.clear(); floor_velocity = Vector2(); } break; - - case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { - // Used by sync to physics, send the new transform to the physics. - Transform2D new_transform = get_global_transform(); - PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_TRANSFORM, new_transform); - // But then revert changes. - set_notify_local_transform(false); - set_global_transform(last_valid_transform); - set_notify_local_transform(true); - } break; } } @@ -1318,9 +1397,6 @@ void CharacterBody2D::_bind_methods() { 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); - 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"); @@ -1329,7 +1405,6 @@ void CharacterBody2D::_bind_methods() { 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::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"); } |