don't touch velocity in other systems and don't use snapTarget

This commit is contained in:
2026-05-25 07:50:33 +02:00
parent 0cd0529468
commit fa714335dc
4 changed files with 33 additions and 57 deletions

View File

@@ -1,7 +1,5 @@
#pragma once #pragma once
#include <optional>
#include <QVector2D> #include <QVector2D>
struct DynamicBodyComponent struct DynamicBodyComponent
@@ -20,6 +18,4 @@ struct DynamicBodyComponent
// --- written each tick by MovementIntentSystem, consumed by DynamicBodySystem --- // --- written each tick by MovementIntentSystem, consumed by DynamicBodySystem ---
QVector2D linearAcceleration; QVector2D linearAcceleration;
float angularAcceleration; float angularAcceleration;
std::optional<QVector2D> snapTarget; // set when snap-to-target will fire this tick
}; };

View File

@@ -44,8 +44,7 @@ entt::entity EntityAdmin::spawnShip(QVector2D position, float hp, float maxHp,
QVector2D(0.0f, 0.0f), // velocity QVector2D(0.0f, 0.0f), // velocity
0.0f, // angularVelocity 0.0f, // angularVelocity
QVector2D(0.0f, 0.0f), // linearAcceleration QVector2D(0.0f, 0.0f), // linearAcceleration
0.0f, // angularAcceleration 0.0f // angularAcceleration
std::nullopt // snapTarget
}); });
add<SensorRange>(entity, SensorRange{sensorRange}); add<SensorRange>(entity, SensorRange{sensorRange});
add<ShipIdentity>(entity, ShipIdentity{level, schematicId}); add<ShipIdentity>(entity, ShipIdentity{level, schematicId});

View File

@@ -1,5 +1,6 @@
#include "DynamicBodySystem.h" #include "DynamicBodySystem.h"
#include <algorithm>
#include <cmath> #include <cmath>
#include <QVector2D> #include <QVector2D>
@@ -22,35 +23,27 @@ void DynamicBodySystem::tick(EntityAdmin& admin)
admin.forEach<Position, Facing, DynamicBodyComponent>( admin.forEach<Position, Facing, DynamicBodyComponent>(
[](entt::entity /*e*/, Position& pos, Facing& facing, DynamicBodyComponent& body) [](entt::entity /*e*/, Position& pos, Facing& facing, DynamicBodyComponent& body)
{ {
// Integrate angular velocity and advance facing. // Integrate angular velocity, clamp to max rotation speed, then advance facing.
body.angularVelocity += body.angularAcceleration; body.angularVelocity += body.angularAcceleration;
body.angularVelocity = std::max(-body.maxRotationSpeedPerTick,
std::min(body.angularVelocity,
body.maxRotationSpeedPerTick));
facing.radians = wrapAngle(facing.radians + body.angularVelocity); facing.radians = wrapAngle(facing.radians + body.angularVelocity);
// Integrate linear velocity. // Integrate linear velocity and cap to max speed.
body.velocity += body.linearAcceleration; body.velocity += body.linearAcceleration;
// Speed cap.
const float speed = body.velocity.length(); const float speed = body.velocity.length();
if (speed > body.maxSpeedPerTick) if (speed > body.maxSpeedPerTick)
{ {
body.velocity = body.velocity.normalized() * body.maxSpeedPerTick; body.velocity = body.velocity.normalized() * body.maxSpeedPerTick;
} }
// Snap to target or advance position. // Advance position.
if (body.snapTarget.has_value()) pos.value += body.velocity;
{
pos.value = body.snapTarget.value();
body.velocity = QVector2D(0.0f, 0.0f);
}
else
{
pos.value += body.velocity;
}
// Reset per-tick fields so stale values don't linger if the intent // Reset per-tick fields so stale values don't linger if the intent
// system is skipped for this entity in a future tick. // system is skipped for this entity in a future tick.
body.linearAcceleration = QVector2D(0.0f, 0.0f); body.linearAcceleration = QVector2D(0.0f, 0.0f);
body.angularAcceleration = 0.0f; body.angularAcceleration = 0.0f;
body.snapTarget = std::nullopt;
}); });
} }

View File

@@ -27,11 +27,16 @@ void MovementIntentSystem::tick(EntityAdmin& admin)
{ {
if (intent.priority == 0) if (intent.priority == 0)
{ {
body.velocity = QVector2D(0.0f, 0.0f); // No movement intent: brake using available thrust.
body.angularVelocity = 0.0f; const float linearBraking = std::min(body.velocity.length(),
body.linearAcceleration = QVector2D(0.0f, 0.0f); body.maneuveringAccelerationPerTick);
body.angularAcceleration = 0.0f; body.linearAcceleration = (body.velocity.length() > 0.0001f)
body.snapTarget = std::nullopt; ? -body.velocity.normalized() * linearBraking
: QVector2D(0.0f, 0.0f);
const float angBraking = std::min(std::abs(body.angularVelocity),
body.angularAccelerationPerTick);
body.angularAcceleration = (body.angularVelocity >= 0.0f) ? -angBraking : angBraking;
return; return;
} }
@@ -40,9 +45,10 @@ void MovementIntentSystem::tick(EntityAdmin& admin)
if (dist < 0.001f) if (dist < 0.001f)
{ {
body.velocity = QVector2D(0.0f, 0.0f); // Already at target: no new thrust. The ship drifts; it will
body.linearAcceleration = QVector2D(0.0f, 0.0f); // re-approach next tick once it has moved away.
body.snapTarget = std::nullopt; body.linearAcceleration = QVector2D(0.0f, 0.0f);
body.angularAcceleration = 0.0f;
return; return;
} }
@@ -55,9 +61,10 @@ void MovementIntentSystem::tick(EntityAdmin& admin)
std::min(angleDiff, body.angularAccelerationPerTick)); std::min(angleDiff, body.angularAccelerationPerTick));
float newAngVel = body.angularVelocity + rotDelta; float newAngVel = body.angularVelocity + rotDelta;
newAngVel = std::max(-body.maxRotationSpeedPerTick,
std::min(newAngVel, body.maxRotationSpeedPerTick));
// Overshoot prevention: if the accumulated angular velocity already
// exceeds the remaining angle, snap it to exactly that angle so the
// ship doesn't rotate past its heading.
const bool sameSign = (newAngVel >= 0.0f) == (angleDiff >= 0.0f); const bool sameSign = (newAngVel >= 0.0f) == (angleDiff >= 0.0f);
if (sameSign && std::abs(newAngVel) > std::abs(angleDiff)) if (sameSign && std::abs(newAngVel) > std::abs(angleDiff))
{ {
@@ -65,12 +72,13 @@ void MovementIntentSystem::tick(EntityAdmin& admin)
} }
body.angularAcceleration = newAngVel - body.angularVelocity; body.angularAcceleration = newAngVel - body.angularVelocity;
// DynamicBodySystem applies the clamp to maxRotationSpeedPerTick after
// integrating, so we do not clamp here.
// --- Linear acceleration --- // --- Linear acceleration ---
// Use the projected facing (after this tick's angular integration) so // Use the projected facing (after this tick's angular integration) so
// that the main thruster aligns with where the ship will actually be // that the main thruster aligns with where the ship will actually be
// pointing when DynamicBodySystem applies the forces — matching the // pointing when DynamicBodySystem applies the forces.
// original single-pass ordering.
const float projectedRadians = wrapAngle(facing.radians + newAngVel); const float projectedRadians = wrapAngle(facing.radians + newAngVel);
const QVector2D facingVec(std::cos(projectedRadians), std::sin(projectedRadians)); const QVector2D facingVec(std::cos(projectedRadians), std::sin(projectedRadians));
@@ -82,40 +90,20 @@ void MovementIntentSystem::tick(EntityAdmin& admin)
? std::sqrt(2.0f * manAccel * dist) ? std::sqrt(2.0f * manAccel * dist)
: body.maxSpeedPerTick; : body.maxSpeedPerTick;
const QVector2D desiredVel = delta.normalized() * desiredSpeed; const QVector2D desiredVel = delta.normalized() * desiredSpeed;
const QVector2D velError = desiredVel - body.velocity; const QVector2D velError = desiredVel - body.velocity;
const float mainAligned = std::max(0.0f, const float mainAligned = std::max(0.0f,
QVector2D::dotProduct(velError, facingVec)); QVector2D::dotProduct(velError, facingVec));
const float mainApplied = std::min(mainAligned, body.mainAccelerationPerTick); const float mainApplied = std::min(mainAligned, body.mainAccelerationPerTick);
const QVector2D mainDelta = facingVec * mainApplied; const QVector2D mainDelta = facingVec * mainApplied;
const QVector2D remaining = velError - mainDelta; const QVector2D remaining = velError - mainDelta;
const float remainLen = remaining.length(); const float remainLen = remaining.length();
const QVector2D maneuverDelta = (remainLen > manAccel) const QVector2D maneuverDelta = (remainLen > manAccel)
? remaining.normalized() * manAccel ? remaining.normalized() * manAccel
: remaining; : remaining;
body.linearAcceleration = mainDelta + maneuverDelta; body.linearAcceleration = mainDelta + maneuverDelta;
// --- Snap detection ---
// Compute the prospective velocity after integration + speed cap to
// determine whether the ship will reach (or pass) the target this tick.
QVector2D prospectiveVel = body.velocity + body.linearAcceleration;
const float prospectiveSpeed = prospectiveVel.length();
if (prospectiveSpeed > body.maxSpeedPerTick)
{
prospectiveVel = prospectiveVel.normalized() * body.maxSpeedPerTick;
}
if (dist <= prospectiveVel.length())
{
body.snapTarget = intent.target;
}
else
{
body.snapTarget = std::nullopt;
}
}); });
} }