feat: added oversteer, understeer and handbraking
Implementation is still messy so some cleanup is needed
This commit is contained in:
parent
5c728b1b39
commit
00df86e63f
|
@ -1,4 +1,6 @@
|
|||
#include "car_physics.hpp"
|
||||
#include "godot_cpp/classes/collision_object3d.hpp"
|
||||
#include "godot_cpp/variant/utility_functions.hpp"
|
||||
#include "utils/godot_macros.h"
|
||||
#include <godot_cpp/classes/curve.hpp>
|
||||
|
||||
|
@ -6,35 +8,166 @@ namespace godot {
|
|||
void CarPhysics::_bind_methods() {
|
||||
#define CLASSNAME CarPhysics
|
||||
GDPROPERTY_HINTED(oversteer_curve, Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "Curve");
|
||||
GDPROPERTY(oversteer_curve_x_scale, Variant::FLOAT);
|
||||
GDPROPERTY_HINTED(understeer_curve, Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "Curve");
|
||||
GDPROPERTY(understeer_curve_x_scale, Variant::FLOAT);
|
||||
}
|
||||
|
||||
void CarPhysics::_enter_tree() {
|
||||
this->set_use_custom_integrator(true);
|
||||
this->front_wheels = this->get_node<CollisionShape3D>("FrontWheels");
|
||||
this->back_wheels = this->get_node<CollisionShape3D>("BackWheels");
|
||||
this->set_contact_monitor(true);
|
||||
this->set_max_contacts_reported(128);
|
||||
|
||||
GDGAMEONLY();
|
||||
this->connect("body_shape_entered", callable_mp(this, &CarPhysics::on_body_shape_entered));
|
||||
this->connect("body_shape_exited", callable_mp(this, &CarPhysics::on_body_shape_exited));
|
||||
}
|
||||
|
||||
void CarPhysics::_physics_process(double delta_time) {
|
||||
this->process_oversteer(delta_time);
|
||||
this->process_understeer(delta_time);
|
||||
UtilityFunctions::print("true target speed: ", this->get_true_target_speed());
|
||||
UtilityFunctions::print("speed: ", this->local_velocity.z);
|
||||
UtilityFunctions::print("grounded: ", this->grounded_objects.size());
|
||||
UtilityFunctions::print("brakes: ", this->brake);
|
||||
UtilityFunctions::print("acceleration: ", this->get_current_acceleration());
|
||||
UtilityFunctions::print("-----------------------------------------------------------");
|
||||
}
|
||||
|
||||
void CarPhysics::process_oversteer(double delta_time) {
|
||||
float const target = this->current_steering == 0.f
|
||||
? 0.f
|
||||
: (this->evaluate_oversteer_curve(this->local_velocity.z) + (this->brake ? 1.f : 0.f)) * Math::sign(this->current_steering);
|
||||
if(Math::abs(target) >= Math::abs(this->current_oversteer) && Math::sign(target) == Math::sign(this->current_oversteer))
|
||||
this->current_oversteer = target;
|
||||
else
|
||||
this->current_oversteer = Math::move_toward(this->current_oversteer, target, float(delta_time * 100.0));
|
||||
UtilityFunctions::print("current oversteer: ", this->current_oversteer, " target: ", target);
|
||||
}
|
||||
|
||||
void CarPhysics::process_understeer(double delta_time) {
|
||||
float const target = this->evaluate_understeer_curve(this->local_velocity.z);
|
||||
if(target >= this->current_understeer)
|
||||
this->current_understeer = target;
|
||||
else
|
||||
this->current_understeer = Math::move_toward(this->current_understeer, target, (float)delta_time);
|
||||
UtilityFunctions::print("current understeer: ", this->current_understeer, " target: ", target);
|
||||
}
|
||||
|
||||
void CarPhysics::_integrate_forces(PhysicsDirectBodyState3D *state) {
|
||||
Basis const basis = this->get_global_basis();
|
||||
if(this->is_grounded()) {
|
||||
this->integrate_engine_acceleration(state);
|
||||
this->integrate_steering(state);
|
||||
this->integrate_oversteer(state);
|
||||
}
|
||||
this->last_velocity = state->get_linear_velocity();
|
||||
state->set_linear_velocity(
|
||||
(this->is_grounded()
|
||||
? (this->local_to_world_velocity() + Vector3{0, float(-9.8f * state->get_step()), 0})
|
||||
: (this->get_linear_velocity() + Vector3{0, float(-9.8f * state->get_step()), 0})));
|
||||
}
|
||||
|
||||
this->local_velocity.z = Math::move_toward(this->local_velocity.z, this->target_speed, float(ACCELERATION * state->get_step()));
|
||||
void CarPhysics::integrate_steering(PhysicsDirectBodyState3D *state) {
|
||||
float target_x_velocity = 0.f;
|
||||
if(this->local_velocity.z != 0.f) {
|
||||
state->set_angular_velocity(basis.get_column(1) * -this->current_steering * Math::sign(this->local_velocity.z));
|
||||
this->local_velocity.x = -this->current_steering * 0.1f;
|
||||
Vector3 const steering_angle_vel = this->get_global_basis().get_column(1) * (
|
||||
this->current_steering * Math::sign(this->local_velocity.z + this->current_oversteer * 2.f));
|
||||
state->set_angular_velocity(steering_angle_vel * (1.0f-this->current_understeer));
|
||||
target_x_velocity = this->current_steering * 0.1f;
|
||||
} else {
|
||||
state->set_angular_velocity(Vector3(0,0,0));
|
||||
this->local_velocity.x = 0;
|
||||
}
|
||||
this->local_velocity.x = Math::move_toward(this->local_velocity.x, target_x_velocity, float(20.f * (1.f-this->current_oversteer) * state->get_step()));
|
||||
}
|
||||
|
||||
void CarPhysics::integrate_engine_acceleration(PhysicsDirectBodyState3D *state) {
|
||||
Vector3 const jolt = state->get_linear_velocity() - this->last_velocity;
|
||||
float const z_jolt = jolt.dot(this->get_global_basis().get_column(2));
|
||||
|
||||
if(Math::abs(this->local_velocity.z) > 1.f && Math::abs(z_jolt) >= Math::abs(this->local_velocity.z) / 2.f && Math::sign(z_jolt) != Math::sign(this->local_velocity.z)) {
|
||||
this->local_velocity.z = 0.f;
|
||||
}
|
||||
|
||||
this->local_velocity.z = Math::move_toward(
|
||||
this->local_velocity.z,
|
||||
this->get_true_target_speed(),
|
||||
float(this->get_current_acceleration() * state->get_step()));
|
||||
}
|
||||
|
||||
void CarPhysics::integrate_oversteer(PhysicsDirectBodyState3D *state) {
|
||||
this->local_velocity.x = Math::clamp(
|
||||
this->local_velocity.x - this->local_velocity.z * this->current_oversteer * 0.05f,
|
||||
-20.f,
|
||||
+20.f
|
||||
);
|
||||
}
|
||||
|
||||
void CarPhysics::on_body_shape_entered(RID body_rid, Node *body, int body_shape_index, int local_shape_index) {
|
||||
CollisionShape3D *shape = Object::cast_to<CollisionShape3D>(this->shape_owner_get_owner(this->shape_find_owner(local_shape_index)));
|
||||
bool const was_grounded = this->is_grounded();
|
||||
if(shape == this->front_wheels || shape == this->back_wheels)
|
||||
this->grounded_objects.insert(body);
|
||||
if(!was_grounded && this->is_grounded()) {
|
||||
this->local_velocity = this->world_to_local_velocity();
|
||||
}
|
||||
}
|
||||
|
||||
void CarPhysics::on_body_shape_exited(RID body_rid, Node *body, int body_shape_index, int local_shape_index) {
|
||||
if(this->grounded_objects.has(body))
|
||||
this->grounded_objects.erase(body);
|
||||
}
|
||||
|
||||
Vector3 CarPhysics::local_to_world_velocity() const {
|
||||
Basis const basis = this->get_global_basis();
|
||||
Vector3 world_velocity{
|
||||
this->local_velocity.x * basis.get_column(0) +
|
||||
this->local_velocity.y * basis.get_column(1) +
|
||||
this->local_velocity.z * basis.get_column(2)
|
||||
};
|
||||
world_velocity.y = 0;
|
||||
if(state->get_linear_velocity().y > this->gravity_velocity && state->get_linear_velocity().y <= 0.f)
|
||||
this->gravity_velocity = 0.f;
|
||||
else
|
||||
this->gravity_velocity -= 15.f * state->get_step();
|
||||
state->set_linear_velocity(world_velocity + Vector3{0, this->gravity_velocity, 0});
|
||||
return world_velocity;
|
||||
}
|
||||
|
||||
Vector3 CarPhysics::world_to_local_velocity() const {
|
||||
Basis const basis = this->get_global_basis();
|
||||
Vector3 const velocity = this->get_linear_velocity();
|
||||
return Vector3{
|
||||
basis.get_column(0).dot(velocity),
|
||||
0.f,
|
||||
basis.get_column(2).dot(velocity)
|
||||
};
|
||||
}
|
||||
|
||||
float CarPhysics::evaluate_oversteer_curve(float speed) const {
|
||||
return this->current_steering != 0.f
|
||||
? this->oversteer_curve->sample(speed / this->oversteer_curve_x_scale)
|
||||
: 0.f;
|
||||
}
|
||||
|
||||
float CarPhysics::evaluate_understeer_curve(float speed) const {
|
||||
return this->current_steering != 0.f
|
||||
? this->understeer_curve->sample(speed / this->understeer_curve_x_scale)
|
||||
: 0.f;
|
||||
}
|
||||
|
||||
float CarPhysics::get_true_target_speed() const {
|
||||
return (this->brake ? 0.f
|
||||
: (this->target_speed > 0
|
||||
? this->target_speed - (Math::abs(this->current_oversteer) * 5.f + this->current_understeer * 3.f)
|
||||
: this->target_speed));
|
||||
}
|
||||
|
||||
bool CarPhysics::is_grounded() const {
|
||||
return !this->grounded_objects.is_empty();
|
||||
}
|
||||
|
||||
float CarPhysics::get_current_acceleration() const {
|
||||
return Math::abs(this->brake
|
||||
? HANDBRAKE
|
||||
: ((Math::abs(this->get_true_target_speed()) >= Math::abs(this->local_velocity.z))
|
||||
? (ACCELERATION * (1.f-Math::abs(this->current_oversteer)))
|
||||
: (CarPhysics::ENGINE_BRAKING * (1.f-Math::abs(this->current_oversteer * 0.25f)))));
|
||||
}
|
||||
|
||||
void CarPhysics::set_target_speed(float target) {
|
||||
|
@ -69,6 +202,14 @@ Ref<Curve> CarPhysics::get_oversteer_curve() const {
|
|||
return this->oversteer_curve;
|
||||
}
|
||||
|
||||
void CarPhysics::set_oversteer_curve_x_scale(float scale) {
|
||||
this->oversteer_curve_x_scale = scale;
|
||||
}
|
||||
|
||||
float CarPhysics::get_oversteer_curve_x_scale() const {
|
||||
return this->oversteer_curve_x_scale;
|
||||
}
|
||||
|
||||
void CarPhysics::set_understeer_curve(Ref<Curve> curve) {
|
||||
this->understeer_curve = curve;
|
||||
}
|
||||
|
@ -77,8 +218,17 @@ Ref<Curve> CarPhysics::get_understeer_curve() const {
|
|||
return this->understeer_curve;
|
||||
}
|
||||
|
||||
void CarPhysics::set_understeer_curve_x_scale(float scale) {
|
||||
this->understeer_curve_x_scale = scale;
|
||||
}
|
||||
|
||||
float CarPhysics::get_understeer_curve_x_scale() const {
|
||||
return this->understeer_curve_x_scale;
|
||||
}
|
||||
|
||||
const float CarPhysics::ACCELERATION{20.f};
|
||||
const float CarPhysics::BRAKE_FORCE{5.f};
|
||||
const float CarPhysics::ENGINE_BRAKING{30.f};
|
||||
const float CarPhysics::HANDBRAKE{40.f};
|
||||
const float CarPhysics::UNDERSTEER_SPEED_MUL{0.f};
|
||||
const float CarPhysics::OVERSTEER_SPEED_MUL{0.f};
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
#ifndef CAR_PHYSICS_HPP
|
||||
#define CAR_PHYSICS_HPP
|
||||
|
||||
#include "godot_cpp/classes/collision_shape3d.hpp"
|
||||
#include "godot_cpp/templates/hash_set.hpp"
|
||||
#include "godot_cpp/variant/rid.hpp"
|
||||
#include <godot_cpp/classes/curve.hpp>
|
||||
#include <godot_cpp/classes/physics_direct_body_state3d.hpp>
|
||||
#include <godot_cpp/classes/rigid_body3d.hpp>
|
||||
|
@ -11,8 +14,32 @@ class CarPhysics : public RigidBody3D {
|
|||
static void _bind_methods();
|
||||
public:
|
||||
virtual void _enter_tree() override;
|
||||
virtual void _integrate_forces(PhysicsDirectBodyState3D *state) override;
|
||||
|
||||
virtual void _physics_process(double delta_time) override;
|
||||
void process_oversteer(double delta_time);
|
||||
void process_understeer(double delta_time);
|
||||
virtual void _integrate_forces(PhysicsDirectBodyState3D *state) override;
|
||||
protected:
|
||||
// Integration functions, state can be gotten via the _integrate_forces method.
|
||||
// Only intended to be called from the _integrate_forces methdo
|
||||
void integrate_steering(PhysicsDirectBodyState3D *state);
|
||||
void integrate_engine_acceleration(PhysicsDirectBodyState3D *state);
|
||||
void integrate_oversteer(PhysicsDirectBodyState3D *state);
|
||||
void on_body_shape_entered(RID body_rid, Node *node, int body_shape_index, int local_shape_index);
|
||||
void on_body_shape_exited(RID body_rid, Node *node, int body_shape_index, int local_shape_index);
|
||||
// convert the local_velocity to world coordinates
|
||||
Vector3 local_to_world_velocity() const;
|
||||
Vector3 world_to_local_velocity() const;
|
||||
// evaluate the oversteer curve with a speed, taking *_x_scale into account
|
||||
float evaluate_oversteer_curve(float speed) const;
|
||||
// evaluate the understeer curve with a speed, taking *_x_scale into account
|
||||
float evaluate_understeer_curve(float speed) const;
|
||||
|
||||
// returns the maximum speed modified by under/oversteer
|
||||
float get_true_target_speed() const;
|
||||
bool is_grounded() const;
|
||||
float get_current_acceleration() const;
|
||||
public:
|
||||
void set_target_speed(float target);
|
||||
float get_target_speed() const;
|
||||
void set_current_steering(float steering);
|
||||
|
@ -21,19 +48,35 @@ public:
|
|||
bool get_brake() const;
|
||||
void set_oversteer_curve(Ref<Curve> curve);
|
||||
Ref<Curve> get_oversteer_curve() const;
|
||||
void set_oversteer_curve_x_scale(float scale);
|
||||
float get_oversteer_curve_x_scale() const;
|
||||
void set_understeer_curve(Ref<Curve> curve);
|
||||
Ref<Curve> get_understeer_curve() const;
|
||||
void set_understeer_curve_x_scale(float scale);
|
||||
float get_understeer_curve_x_scale() const;
|
||||
private:
|
||||
Vector3 last_velocity{0.f, 0.f, 0.f};
|
||||
Vector3 local_velocity{0.f, 0.f, 0.f};
|
||||
|
||||
HashSet<Node*> grounded_objects{};
|
||||
|
||||
float target_speed{0.f};
|
||||
float current_steering{0.f};
|
||||
Vector3 local_velocity{};
|
||||
float gravity_velocity{-1.f};
|
||||
bool brake{false};
|
||||
Ref<Curve> oversteer_curve{};
|
||||
float oversteer_curve_x_scale{50.f};
|
||||
Ref<Curve> understeer_curve{};
|
||||
float understeer_curve_x_scale{50.f};
|
||||
|
||||
CollisionShape3D *front_wheels{nullptr};
|
||||
CollisionShape3D *back_wheels{nullptr};
|
||||
|
||||
float current_understeer{0.f};
|
||||
float current_oversteer{0.f};
|
||||
|
||||
static const float ACCELERATION;
|
||||
static const float BRAKE_FORCE;
|
||||
static const float ENGINE_BRAKING;
|
||||
static const float HANDBRAKE;
|
||||
static const float UNDERSTEER_SPEED_MUL;
|
||||
static const float OVERSTEER_SPEED_MUL;
|
||||
};
|
||||
|
|
|
@ -9,7 +9,7 @@ void CarPlayer::_bind_methods() {
|
|||
}
|
||||
|
||||
void CarPlayer::setup_player_input(PlayerInput *input) {
|
||||
input->listen_to(PlayerInput::Listener("steer_right", "steer_left", callable_mp(this, &CarPlayer::on_steer)));
|
||||
input->listen_to(PlayerInput::Listener("steer_left", "steer_right", callable_mp(this, &CarPlayer::on_steer)));
|
||||
input->listen_to(PlayerInput::Listener("accelerate", callable_mp(this, &CarPlayer::on_accelerate)));
|
||||
input->listen_to(PlayerInput::Listener("brake", callable_mp(this, &CarPlayer::on_brake)));
|
||||
}
|
||||
|
|
|
@ -21,9 +21,9 @@ public:
|
|||
void on_brake(Ref<InputEvent> event, float value);
|
||||
void on_accelerate(Ref<InputEvent> event, float value);
|
||||
private:
|
||||
const float max_speed{34.f};
|
||||
const float max_speed{40.f};
|
||||
const float max_speed_reverse{10.f};
|
||||
const float steering_factor{1.f};
|
||||
const float steering_factor{2.f};
|
||||
};
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue