don't touch velocity in other systems and don't use snapTarget
This commit is contained in:
@@ -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
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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});
|
||||||
|
|||||||
@@ -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;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user