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
#include <optional>
#include <QVector2D>
struct DynamicBodyComponent
@@ -20,6 +18,4 @@ struct DynamicBodyComponent
// --- written each tick by MovementIntentSystem, consumed by DynamicBodySystem ---
QVector2D linearAcceleration;
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
0.0f, // angularVelocity
QVector2D(0.0f, 0.0f), // linearAcceleration
0.0f, // angularAcceleration
std::nullopt // snapTarget
0.0f // angularAcceleration
});
add<SensorRange>(entity, SensorRange{sensorRange});
add<ShipIdentity>(entity, ShipIdentity{level, schematicId});

View File

@@ -1,5 +1,6 @@
#include "DynamicBodySystem.h"
#include <algorithm>
#include <cmath>
#include <QVector2D>
@@ -22,35 +23,27 @@ void DynamicBodySystem::tick(EntityAdmin& admin)
admin.forEach<Position, Facing, DynamicBodyComponent>(
[](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 = std::max(-body.maxRotationSpeedPerTick,
std::min(body.angularVelocity,
body.maxRotationSpeedPerTick));
facing.radians = wrapAngle(facing.radians + body.angularVelocity);
// Integrate linear velocity.
// Integrate linear velocity and cap to max speed.
body.velocity += body.linearAcceleration;
// Speed cap.
const float speed = body.velocity.length();
if (speed > body.maxSpeedPerTick)
{
body.velocity = body.velocity.normalized() * body.maxSpeedPerTick;
}
// Snap to target or advance position.
if (body.snapTarget.has_value())
{
pos.value = body.snapTarget.value();
body.velocity = QVector2D(0.0f, 0.0f);
}
else
{
// Advance position.
pos.value += body.velocity;
}
// Reset per-tick fields so stale values don't linger if the intent
// system is skipped for this entity in a future tick.
body.linearAcceleration = QVector2D(0.0f, 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)
{
body.velocity = QVector2D(0.0f, 0.0f);
body.angularVelocity = 0.0f;
body.linearAcceleration = QVector2D(0.0f, 0.0f);
body.angularAcceleration = 0.0f;
body.snapTarget = std::nullopt;
// No movement intent: brake using available thrust.
const float linearBraking = std::min(body.velocity.length(),
body.maneuveringAccelerationPerTick);
body.linearAcceleration = (body.velocity.length() > 0.0001f)
? -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;
}
@@ -40,9 +45,10 @@ void MovementIntentSystem::tick(EntityAdmin& admin)
if (dist < 0.001f)
{
body.velocity = QVector2D(0.0f, 0.0f);
// Already at target: no new thrust. The ship drifts; it will
// re-approach next tick once it has moved away.
body.linearAcceleration = QVector2D(0.0f, 0.0f);
body.snapTarget = std::nullopt;
body.angularAcceleration = 0.0f;
return;
}
@@ -55,9 +61,10 @@ void MovementIntentSystem::tick(EntityAdmin& admin)
std::min(angleDiff, body.angularAccelerationPerTick));
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);
if (sameSign && std::abs(newAngVel) > std::abs(angleDiff))
{
@@ -65,12 +72,13 @@ void MovementIntentSystem::tick(EntityAdmin& admin)
}
body.angularAcceleration = newAngVel - body.angularVelocity;
// DynamicBodySystem applies the clamp to maxRotationSpeedPerTick after
// integrating, so we do not clamp here.
// --- Linear acceleration ---
// Use the projected facing (after this tick's angular integration) so
// that the main thruster aligns with where the ship will actually be
// pointing when DynamicBodySystem applies the forces — matching the
// original single-pass ordering.
// pointing when DynamicBodySystem applies the forces.
const float projectedRadians = wrapAngle(facing.radians + newAngVel);
const QVector2D facingVec(std::cos(projectedRadians), std::sin(projectedRadians));
@@ -97,25 +105,5 @@ void MovementIntentSystem::tick(EntityAdmin& admin)
: remaining;
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;
}
});
}