extract the scroll position into WorldCamera

This commit is contained in:
2026-08-05 19:48:06 +02:00
parent fa9dbd62ad
commit f6df95abb2
8 changed files with 423 additions and 58 deletions

View File

@@ -199,7 +199,7 @@ GameWorldView::GameWorldView(Simulation* sim, const GameConfig* config,
, m_commandManager(*sim)
, m_gameSpeedMultiplier(1.0)
, m_prevNonZeroSpeed(1.0)
, m_scrollXTiles(0.0f)
, m_camera(config->world.scroll, config->world.regions)
, m_ghostRotation(Rotation::East)
, m_ghostValid(false)
, m_dragging(false)
@@ -366,18 +366,19 @@ void GameWorldView::onFrame()
// Apply held scroll
{
// Pan speed depends on where the view is centered (REQ-UI-SCROLL-SPEED).
const float viewCenterX = m_scrollXTiles;
const float delta = panSpeedTilesPerSecondAt(viewCenterX)
* static_cast<float>(elapsed) / 1000.0f;
const float scrollBefore = m_scrollXTiles;
if (m_scrollLeft) { m_scrollXTiles -= delta; }
if (m_scrollRight) { m_scrollXTiles += delta; }
clampScroll();
// Holding both keys cancels out, as it did when each key moved the view
// independently.
PanDirection direction = PanDirection::None;
if (m_scrollLeft != m_scrollRight)
{
direction = m_scrollLeft ? PanDirection::Left : PanDirection::Right;
}
const bool viewMoved = m_camera.advance(direction, elapsed, getScrollBounds());
// While the view scrolls, the tile under a stationary cursor changes,
// so refresh the box-select rectangle even though no mouse move fires.
if (m_boxSelecting && m_scrollXTiles != scrollBefore)
if (m_boxSelecting && viewMoved)
{
m_boxCurrentTile =
getCoordinates().widgetToTile(mapFromGlobal(QCursor::pos()));
@@ -526,7 +527,7 @@ void GameWorldView::paintGL()
WorldCoordinates GameWorldView::getCoordinates() const
{
return WorldCoordinates::scrolling(size(), m_config->world.heightTiles,
m_scrollXTiles);
m_camera.getViewCenterXTiles());
}
float GameWorldView::getAsteroidLeftEdge() const
@@ -562,47 +563,13 @@ float GameWorldView::getEnemyStationRightEdge() const
return rightX;
}
namespace
ScrollBounds GameWorldView::getScrollBounds() const
{
// Linearly blend from valueAt0 (for x <= x0) to valueAt1 (for x >= x1), clamped
// outside [x0, x1]. A zero- or negative-width band collapses to a hard step at x1.
float lerpClamped(float valueAt0, float valueAt1, float x0, float x1, float x)
{
if (x1 <= x0) { return x < x1 ? valueAt0 : valueAt1; }
const float t = std::max(0.0f, std::min(1.0f, (x - x0) / (x1 - x0)));
return valueAt0 + (valueAt1 - valueAt0) * t;
}
}
float GameWorldView::panSpeedTilesPerSecondAt(float viewCenterXTiles) const
{
// Slow near the asteroid/player buffer, fast across the contest zone, with a
// linear ramp straddling each contest-zone boundary (REQ-UI-SCROLL-SPEED). The
// contest zone spans from the player buffer's right edge to the enemy stations,
// the latter tracked live so the ramp follows the front line as it is pushed.
const float slow = static_cast<float>(m_config->world.scroll.panSpeedSlow_tps);
const float fast = static_cast<float>(m_config->world.scroll.panSpeedFast_tps);
const float half = static_cast<float>(m_config->world.scroll.panRampBandWidth_tiles) / 2.0f;
const float leftEdge = static_cast<float>(m_config->world.regions.playerBufferWidth_tiles);
const float rightEdge = getEnemyStationRightEdge();
// Rising ramp at the left boundary (slow -> fast) and falling ramp at the right
// boundary (fast -> slow); their minimum yields flat-slow outside, flat-fast in
// the middle, and — if the bands overlap in a narrow contest zone — a single peak
// below the fast speed where the two ramps cross.
const float leftRamp = lerpClamped(slow, fast, leftEdge - half, leftEdge + half, viewCenterXTiles);
const float rightRamp = lerpClamped(fast, slow, rightEdge - half, rightEdge + half, viewCenterXTiles);
return std::min(leftRamp, rightRamp);
}
void GameWorldView::clampScroll()
{
// m_scrollXTiles is the view center, so the pan limits are the edges themselves:
// the view can pan left until the buildable/asteroid edge is centered, and right
// until the enemy stations are centered (revealing a little space beyond them).
const float leftBound = getAsteroidLeftEdge();
const float rightBound = getEnemyStationRightEdge();
m_scrollXTiles = std::max(leftBound, std::min(m_scrollXTiles, rightBound));
// The camera clamps its view center to these, so the pan limits are the edges
// themselves: the view can pan left until the buildable/asteroid edge is
// centered, and right until the enemy stations are centered (revealing a little
// space beyond them).
return ScrollBounds{getAsteroidLeftEdge(), getEnemyStationRightEdge()};
}
// ---------------------------------------------------------------------------
@@ -3102,7 +3069,7 @@ void GameWorldView::resetForNewGame()
m_copiedConfig = std::nullopt;
m_copyConfigFlashes.clear();
m_boxSelecting = false;
m_scrollXTiles = 0.0f;
m_camera.reset();
m_scrollLeft = false;
m_scrollRight = false;
m_gameOverShown = false;