Fix motion direction in slope for CharacterBody3D

- More accurate sliding in slopes to keep input direction correct
- More accurate constant speed calculation
- Renamed linear_velocity to motion_velocity for clarity
- General code cleaning and simplifications
This commit is contained in:
PouleyKetchoupp 2021-09-23 18:30:06 -07:00
parent f72419d152
commit eec3f3ec12
3 changed files with 101 additions and 73 deletions

View File

@ -32,7 +32,7 @@
<method name="get_last_motion" qualifiers="const">
<return type="Vector3" />
<description>
Returns the last motion applied to the [CharacterBody3D] during the last call to [method move_and_slide]. The movement can be split if needed into multiple motion, this method return the last one, it's useful to retrieve the current direction of the movement.
Returns the last motion applied to the [CharacterBody3D] during the last call to [method move_and_slide]. The movement can be split into multiple motions when sliding occurs, and this method return the last one, which is useful to retrieve the current direction of the movement.
</description>
</method>
<method name="get_last_slide_collision">
@ -56,7 +56,7 @@
<method name="get_real_velocity" qualifiers="const">
<return type="Vector3" />
<description>
Returns the current real velocity since the last call to [method move_and_slide]. For example, when you climb a slope, you will move diagonally even though the velocity is horizontal. This method returns the diagonal movement, as opposed to [member linear_velocity] which returns the requested velocity.
Returns the current real velocity since the last call to [method move_and_slide]. For example, when you climb a slope, you will move diagonally even though the velocity is horizontal. This method returns the diagonal movement, as opposed to [member motion_velocity] which returns the requested velocity.
</description>
</method>
<method name="get_slide_collision">
@ -117,9 +117,9 @@
<method name="move_and_slide">
<return type="bool" />
<description>
Moves the body based on [member linear_velocity]. If the body collides with another, it will slide along the other body rather than stop immediately. If the other body is a [CharacterBody3D] or [RigidDynamicBody3D], it will also be affected by the motion of the other body. You can use this to make moving and rotating platforms, or to make nodes push other nodes.
Moves the body based on [member motion_velocity]. If the body collides with another, it will slide along the other body rather than stop immediately. If the other body is a [CharacterBody3D] or [RigidDynamicBody3D], it will also be affected by the motion of the other body. You can use this to make moving and rotating platforms, or to make nodes push other nodes.
This method should be used in [method Node._physics_process] (or in a method called by [method Node._physics_process]), as it uses the physics step's [code]delta[/code] value automatically in calculations. Otherwise, the simulation will run at an incorrect speed.
Modifies [member linear_velocity] if a slide collision occurred. To get the latest collision call [method get_last_slide_collision], for more detailed information about collisions that occurred, use [method get_slide_collision].
Modifies [member motion_velocity] if a slide collision occurred. To get the latest collision call [method get_last_slide_collision], for more detailed information about collisions that occurred, use [method get_slide_collision].
When the body touches a moving platform, the platform's velocity is automatically added to the body motion. If a collision occurs due to the platform's motion, it will always be first in the slide collisions.
Returns [code]true[/code] if the body collided, otherwise, returns [code]false[/code].
</description>
@ -147,10 +147,8 @@
As long as the snapping vector is in contact with the ground and the body moves against `up_direction`, the body will remain attached to the surface. Snapping is not applied if the body moves along `up_direction`, so it will be able to detach from the ground when jumping.
</member>
<member name="floor_stop_on_slope" type="bool" setter="set_floor_stop_on_slope_enabled" getter="is_floor_stop_on_slope_enabled" default="true">
If [code]true[/code], the body will not slide on slopes when you include gravity in [code]linear_velocity[/code] when calling [method move_and_slide] and the body is standing still.
</member>
<member name="linear_velocity" type="Vector3" setter="set_linear_velocity" getter="get_linear_velocity" default="Vector3(0, 0, 0)">
Current velocity vector (typically meters per second), used and modified during calls to [method move_and_slide].
If [code]true[/code], the body will not slide on slopes when calling [method move_and_slide] when the body is standing still.
If [code]false[/code], the body will slide on floor's slopes when [member motion_velocity] applies a downward force.
</member>
<member name="max_slides" type="int" setter="set_max_slides" getter="get_max_slides" default="6">
Maximum number of times the body can change direction before it stops when calling [method move_and_slide].
@ -158,6 +156,9 @@
<member name="motion_mode" type="int" setter="set_motion_mode" getter="get_motion_mode" enum="CharacterBody3D.MotionMode" default="0">
Sets the motion mode which defines the behaviour of [method move_and_slide]. See [enum MotionMode] constants for available modes.
</member>
<member name="motion_velocity" type="Vector3" setter="set_motion_velocity" getter="get_motion_velocity" default="Vector3(0, 0, 0)">
Current velocity vector (typically meters per second), used and modified during calls to [method move_and_slide].
</member>
<member name="moving_platform_apply_velocity_on_leave" type="int" setter="set_moving_platform_apply_velocity_on_leave" getter="get_moving_platform_apply_velocity_on_leave" enum="CharacterBody3D.MovingPlatformApplyVelocityOnLeave" default="0">
Sets the behaviour to apply when you leave a moving platform. By default, to be physically accurate, when you leave the last platform velocity is applied. See [enum MovingPlatformApplyVelocityOnLeave] constants for available behaviour.
</member>
@ -185,10 +186,10 @@
Apply when there is no notion of floor or ceiling. All collisions will be reported as [code]on_wall[/code]. In this mode, when you slide, the speed will always be constant. This mode is suitable for games without ground like space games.
</constant>
<constant name="PLATFORM_VEL_ON_LEAVE_ALWAYS" value="0" enum="MovingPlatformApplyVelocityOnLeave">
Add the last platform velocity to the [member linear_velocity] when you leave a moving platform.
Add the last platform velocity to the [member motion_velocity] when you leave a moving platform.
</constant>
<constant name="PLATFORM_VEL_ON_LEAVE_UPWARD_ONLY" value="1" enum="MovingPlatformApplyVelocityOnLeave">
Add the last platform velocity to the [member linear_velocity] when you leave a moving platform, but any downward motion is ignored. It's useful to keep full jump height even when the platform is moving down.
Add the last platform velocity to the [member motion_velocity] when you leave a moving platform, but any downward motion is ignored. It's useful to keep full jump height even when the platform is moving down.
</constant>
<constant name="PLATFORM_VEL_ON_LEAVE_NEVER" value="2" enum="MovingPlatformApplyVelocityOnLeave">
Do nothing when leaving a platform.

View File

@ -1046,13 +1046,16 @@ void RigidDynamicBody3D::_reload_physics_characteristics() {
bool CharacterBody3D::move_and_slide() {
// Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky
double delta = Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time();
previous_position = get_global_transform().origin;
for (int i = 0; i < 3; i++) {
if (locked_axis & (1 << i)) {
linear_velocity[i] = 0.0;
motion_velocity[i] = 0.0;
}
}
Transform3D gt = get_global_transform();
previous_position = gt.origin;
Vector3 current_platform_velocity = platform_velocity;
if ((collision_state.floor || collision_state.wall) && platform_rid.is_valid()) {
@ -1066,7 +1069,6 @@ bool CharacterBody3D::move_and_slide() {
//this approach makes sure there is less delay between the actual body velocity and the one we saved
PhysicsDirectBodyState3D *bs = PhysicsServer3D::get_singleton()->body_get_direct_state(platform_rid);
if (bs) {
Transform3D gt = get_global_transform();
Vector3 local_position = gt.origin - bs->get_transform().origin;
current_platform_velocity = bs->get_velocity_at_local_position(local_position);
}
@ -1080,6 +1082,8 @@ bool CharacterBody3D::move_and_slide() {
bool was_on_floor = collision_state.floor;
collision_state.state = 0;
last_motion = Vector3();
if (!current_platform_velocity.is_equal_approx(Vector3())) {
PhysicsServer3D::MotionResult floor_result;
Set<RID> exclude;
@ -1107,20 +1111,15 @@ bool CharacterBody3D::move_and_slide() {
if (moving_platform_apply_velocity_on_leave == PLATFORM_VEL_ON_LEAVE_UPWARD_ONLY && current_platform_velocity.dot(up_direction) < 0) {
current_platform_velocity = current_platform_velocity.slide(up_direction);
}
linear_velocity += current_platform_velocity;
motion_velocity += current_platform_velocity;
}
}
// Reset the gravity accumulation when touching the ground.
if (collision_state.floor && linear_velocity.dot(up_direction) <= 0) {
linear_velocity = linear_velocity.slide(up_direction);
}
return motion_results.size() > 0;
}
void CharacterBody3D::_move_and_slide_grounded(double p_delta, bool p_was_on_floor) {
Vector3 motion = linear_velocity * p_delta;
Vector3 motion = motion_velocity * p_delta;
Vector3 motion_slide_up = motion.slide(up_direction);
Vector3 prev_floor_normal = floor_normal;
@ -1135,12 +1134,13 @@ void CharacterBody3D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo
// Constant speed can be applied only the first time sliding is enabled.
bool can_apply_constant_speed = sliding_enabled;
bool first_slide = true;
bool vel_dir_facing_up = linear_velocity.dot(up_direction) > 0;
bool vel_dir_facing_up = motion_velocity.dot(up_direction) > 0;
Vector3 total_travel;
for (int iteration = 0; iteration < max_slides; ++iteration) {
PhysicsServer3D::MotionResult result;
bool collided = move_and_collide(motion, result, margin, false, 4, !sliding_enabled);
last_motion = result.travel;
if (collided) {
motion_results.push_back(result);
@ -1150,20 +1150,20 @@ void CharacterBody3D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo
CollisionState result_state;
_set_collision_direction(result, result_state);
if (collision_state.floor && floor_stop_on_slope && (linear_velocity.normalized() + up_direction).length() < 0.01) {
if (collision_state.floor && floor_stop_on_slope && (motion_velocity.normalized() + up_direction).length() < 0.01) {
Transform3D gt = get_global_transform();
if (result.travel.length() <= margin + CMP_EPSILON) {
gt.origin -= result.travel;
}
set_global_transform(gt);
linear_velocity = Vector3();
motion_velocity = Vector3();
motion = Vector3();
last_motion = Vector3();
break;
}
if (result.remainder.is_equal_approx(Vector3())) {
motion = Vector3();
last_motion = result.travel;
break;
}
@ -1212,7 +1212,7 @@ void CharacterBody3D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo
Vector3 forward = wall_normal.slide(up_direction).normalized();
motion = motion.slide(forward);
// Avoid accelerating when you jump on the wall and smooth falling.
linear_velocity = linear_velocity.slide(forward);
motion_velocity = motion_velocity.slide(forward);
// Allow only lateral motion along previous floor when already on floor.
// Fixes slowing down when moving in diagonal against an inclined wall.
@ -1241,7 +1241,7 @@ void CharacterBody3D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo
if (stop_all_motion) {
motion = Vector3();
linear_velocity = Vector3();
motion_velocity = Vector3();
}
}
}
@ -1252,7 +1252,7 @@ void CharacterBody3D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo
real_t motion_angle = Math::abs(Math::acos(-horizontal_normal.dot(motion_slide_up.normalized())));
if (motion_angle < wall_min_slide_angle) {
motion = up_direction * motion.dot(up_direction);
linear_velocity = up_direction * linear_velocity.dot(up_direction);
motion_velocity = up_direction * motion_velocity.dot(up_direction);
apply_default_sliding = false;
}
@ -1263,19 +1263,33 @@ void CharacterBody3D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo
// Regular sliding, the last part of the test handle the case when you don't want to slide on the ceiling.
if ((sliding_enabled || !collision_state.floor) && (!collision_state.ceiling || slide_on_ceiling || !vel_dir_facing_up)) {
const PhysicsServer3D::MotionCollision &collision = result.collisions[0];
Vector3 slide_motion = result.remainder.slide(collision.normal);
if (slide_motion.dot(linear_velocity) > 0.0) {
if (collision_state.floor && !collision_state.wall) {
// Slide using the intersection between the motion plane and the floor plane,
// in order to keep the direction intact.
real_t motion_length = slide_motion.length();
slide_motion = up_direction.cross(result.remainder).cross(floor_normal);
// Keep the length from default slide to change speed in slopes by default,
// when constant speed is not enabled.
slide_motion.normalize();
slide_motion *= motion_length;
}
if (slide_motion.dot(motion_velocity) > 0.0) {
motion = slide_motion;
} else {
motion = Vector3();
}
if (slide_on_ceiling && result_state.ceiling) {
// Apply slide only in the direction of the input motion, otherwise just stop to avoid jittering when moving against a wall.
if (vel_dir_facing_up) {
linear_velocity = linear_velocity.slide(collision.normal);
motion_velocity = motion_velocity.slide(collision.normal);
} else {
// Avoid acceleration in slope when falling.
linear_velocity = up_direction * up_direction.dot(linear_velocity);
motion_velocity = up_direction * up_direction.dot(motion_velocity);
}
}
}
@ -1283,18 +1297,19 @@ void CharacterBody3D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo
else {
motion = result.remainder;
if (result_state.ceiling && !slide_on_ceiling && vel_dir_facing_up) {
linear_velocity = linear_velocity.slide(up_direction);
motion_velocity = motion_velocity.slide(up_direction);
motion = motion.slide(up_direction);
}
}
}
// Apply Constant Speed.
if (p_was_on_floor && floor_constant_speed && collision_state.floor && !motion.is_equal_approx(Vector3())) {
motion = motion.normalized() * MAX(0, (motion_slide_up.length() - result.travel.slide(up_direction).length() - total_travel.slide(up_direction).length()));
}
total_travel += result.travel;
// Apply Constant Speed.
if (p_was_on_floor && floor_constant_speed && can_apply_constant_speed && collision_state.floor && !motion.is_equal_approx(Vector3())) {
Vector3 travel_slide_up = total_travel.slide(up_direction);
motion = motion.normalized() * MAX(0, (motion_slide_up.length() - travel_slide_up.length()));
}
}
// When you move forward in a downward slope you dont collide because you will be in the air.
// This test ensures that constant speed is applied, only if the player is still on the ground after the snap is applied.
@ -1305,34 +1320,34 @@ void CharacterBody3D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo
gt.origin = gt.origin - result.travel;
set_global_transform(gt);
Vector3 motion_slide_norm = motion.slide(prev_floor_normal).normalized();
// Slide using the intersection between the motion plane and the floor plane,
// in order to keep the direction intact.
Vector3 motion_slide_norm = up_direction.cross(motion).cross(prev_floor_normal);
motion_slide_norm.normalize();
motion = motion_slide_norm * (motion_slide_up.length());
collided = true;
}
can_apply_constant_speed = !can_apply_constant_speed && !sliding_enabled;
sliding_enabled = true;
first_slide = false;
if (!motion.is_equal_approx(Vector3())) {
last_motion = motion;
}
if (!collided || motion.is_equal_approx(Vector3())) {
break;
}
can_apply_constant_speed = !can_apply_constant_speed && !sliding_enabled;
sliding_enabled = true;
first_slide = false;
}
_snap_on_floor(p_was_on_floor, vel_dir_facing_up);
// Reset the gravity accumulation when touching the ground.
if (collision_state.floor && !vel_dir_facing_up) {
linear_velocity = linear_velocity.slide(up_direction);
motion_velocity = motion_velocity.slide(up_direction);
}
}
void CharacterBody3D::_move_and_slide_free(double p_delta) {
Vector3 motion = linear_velocity * p_delta;
Vector3 motion = motion_velocity * p_delta;
platform_rid = RID();
floor_normal = Vector3();
@ -1343,6 +1358,7 @@ void CharacterBody3D::_move_and_slide_free(double p_delta) {
PhysicsServer3D::MotionResult result;
bool collided = move_and_collide(motion, result, margin, false, 1, false);
last_motion = result.travel;
if (collided) {
motion_results.push_back(result);
@ -1350,7 +1366,12 @@ void CharacterBody3D::_move_and_slide_free(double p_delta) {
CollisionState result_state;
_set_collision_direction(result, result_state);
if (wall_min_slide_angle != 0 && Math::acos(wall_normal.dot(-linear_velocity.normalized())) < wall_min_slide_angle + FLOOR_ANGLE_THRESHOLD) {
if (result.remainder.is_equal_approx(Vector3())) {
motion = Vector3();
break;
}
if (wall_min_slide_angle != 0 && Math::acos(wall_normal.dot(-motion_velocity.normalized())) < wall_min_slide_angle + FLOOR_ANGLE_THRESHOLD) {
motion = Vector3();
if (result.travel.length() < margin + CMP_EPSILON) {
Transform3D gt = get_global_transform();
@ -1364,16 +1385,16 @@ void CharacterBody3D::_move_and_slide_free(double p_delta) {
motion = result.remainder.slide(wall_normal);
}
if (motion.dot(linear_velocity) <= 0.0) {
if (motion.dot(motion_velocity) <= 0.0) {
motion = Vector3();
}
}
first_slide = false;
if (!collided || motion.is_equal_approx(Vector3())) {
break;
}
first_slide = false;
}
}
@ -1525,12 +1546,12 @@ real_t CharacterBody3D::get_safe_margin() const {
return margin;
}
Vector3 CharacterBody3D::get_linear_velocity() const {
return linear_velocity;
const Vector3 &CharacterBody3D::get_motion_velocity() const {
return motion_velocity;
}
void CharacterBody3D::set_linear_velocity(const Vector3 &p_velocity) {
linear_velocity = p_velocity;
void CharacterBody3D::set_motion_velocity(const Vector3 &p_velocity) {
motion_velocity = p_velocity;
}
bool CharacterBody3D::is_on_floor() const {
@ -1557,15 +1578,15 @@ bool CharacterBody3D::is_on_ceiling_only() const {
return collision_state.ceiling && !collision_state.floor && !collision_state.wall;
}
Vector3 CharacterBody3D::get_floor_normal() const {
const Vector3 &CharacterBody3D::get_floor_normal() const {
return floor_normal;
}
Vector3 CharacterBody3D::get_wall_normal() const {
const Vector3 &CharacterBody3D::get_wall_normal() const {
return wall_normal;
}
Vector3 CharacterBody3D::get_last_motion() const {
const Vector3 &CharacterBody3D::get_last_motion() const {
return last_motion;
}
@ -1573,19 +1594,23 @@ Vector3 CharacterBody3D::get_position_delta() const {
return get_transform().origin - previous_position;
}
Vector3 CharacterBody3D::get_real_velocity() const {
const Vector3 &CharacterBody3D::get_real_velocity() const {
return real_velocity;
};
}
real_t CharacterBody3D::get_floor_angle(const Vector3 &p_up_direction) const {
ERR_FAIL_COND_V(p_up_direction == Vector3(), 0);
return Math::acos(floor_normal.dot(p_up_direction));
}
Vector3 CharacterBody3D::get_platform_velocity() const {
const Vector3 &CharacterBody3D::get_platform_velocity() const {
return platform_velocity;
}
Vector3 CharacterBody3D::get_linear_velocity() const {
return get_real_velocity();
}
int CharacterBody3D::get_slide_collision_count() const {
return motion_results.size();
}
@ -1740,8 +1765,8 @@ void CharacterBody3D::_notification(int p_what) {
void CharacterBody3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("move_and_slide"), &CharacterBody3D::move_and_slide);
ClassDB::bind_method(D_METHOD("set_linear_velocity", "linear_velocity"), &CharacterBody3D::set_linear_velocity);
ClassDB::bind_method(D_METHOD("get_linear_velocity"), &CharacterBody3D::get_linear_velocity);
ClassDB::bind_method(D_METHOD("set_motion_velocity", "motion_velocity"), &CharacterBody3D::set_motion_velocity);
ClassDB::bind_method(D_METHOD("get_motion_velocity"), &CharacterBody3D::get_motion_velocity);
ClassDB::bind_method(D_METHOD("set_safe_margin", "pixels"), &CharacterBody3D::set_safe_margin);
ClassDB::bind_method(D_METHOD("get_safe_margin"), &CharacterBody3D::get_safe_margin);
@ -1794,7 +1819,7 @@ void CharacterBody3D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "motion_mode", PROPERTY_HINT_ENUM, "Grounded,Free", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_motion_mode", "get_motion_mode");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "up_direction"), "set_up_direction", "get_up_direction");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "slide_on_ceiling"), "set_slide_on_ceiling_enabled", "is_slide_on_ceiling_enabled");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "linear_velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_linear_velocity", "get_linear_velocity");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "motion_velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_motion_velocity", "get_motion_velocity");
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_slides", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_max_slides", "get_max_slides");
ADD_GROUP("Free Mode", "free_mode_");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wall_min_slide_angle", PROPERTY_HINT_RANGE, "0,180,0.1,radians", PROPERTY_USAGE_DEFAULT), "set_wall_min_slide_angle", "get_wall_min_slide_angle");

View File

@ -318,8 +318,8 @@ public:
};
bool move_and_slide();
virtual Vector3 get_linear_velocity() const override;
void set_linear_velocity(const Vector3 &p_velocity);
const Vector3 &get_motion_velocity() const;
void set_motion_velocity(const Vector3 &p_velocity);
bool is_on_floor() const;
bool is_on_floor_only() const;
@ -327,13 +327,15 @@ public:
bool is_on_wall_only() const;
bool is_on_ceiling() const;
bool is_on_ceiling_only() const;
Vector3 get_last_motion() const;
const Vector3 &get_last_motion() const;
Vector3 get_position_delta() const;
Vector3 get_floor_normal() const;
Vector3 get_wall_normal() const;
Vector3 get_real_velocity() const;
const Vector3 &get_floor_normal() const;
const Vector3 &get_wall_normal() const;
const Vector3 &get_real_velocity() const;
real_t get_floor_angle(const Vector3 &p_up_direction = Vector3(0.0, 1.0, 0.0)) const;
Vector3 get_platform_velocity() const;
const Vector3 &get_platform_velocity() const;
virtual Vector3 get_linear_velocity() const override;
int get_slide_collision_count() const;
PhysicsServer3D::MotionResult get_slide_collision(int p_bounce) const;
@ -369,7 +371,7 @@ private:
bool floor_block_on_wall = true;
bool slide_on_ceiling = true;
int max_slides = 6;
int platform_layer;
int platform_layer = 0;
RID platform_rid;
uint32_t moving_platform_floor_layers = UINT32_MAX;
uint32_t moving_platform_wall_layers = 0;
@ -377,7 +379,7 @@ private:
real_t floor_max_angle = Math::deg2rad((real_t)45.0);
real_t wall_min_slide_angle = Math::deg2rad((real_t)15.0);
Vector3 up_direction = Vector3(0.0, 1.0, 0.0);
Vector3 linear_velocity;
Vector3 motion_velocity;
Vector3 floor_normal;
Vector3 wall_normal;
Vector3 last_motion;