Redefine camera scroll as view center

m_scrollXTiles now stores the world-X (tiles) at the center of the
viewport instead of its left edge. A new viewLeftTiles() helper derives
the left edge for the world<->widget conversions and tile culling, so
the half-viewport math lives in one place.

The view now opens centered on the asteroid/space edge and pans left
until the buildable edge is centered and right until the enemy stations
are centered (revealing a little space beyond them).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DZR44tA8sn4dPqDzAVXyps
This commit is contained in:
2026-07-13 21:28:49 +02:00
parent 7757bf4d48
commit 25cb23f66c
2 changed files with 20 additions and 8 deletions

View File

@@ -293,7 +293,7 @@ void GameWorldView::onFrame()
// Apply held scroll
{
// Pan speed depends on where the view is centered (REQ-UI-SCROLL-SPEED).
const float viewCenterX = m_scrollXTiles + viewportWidthTiles() / 2.0f;
const float viewCenterX = m_scrollXTiles;
const float delta = panSpeedTilesPerSecondAt(viewCenterX)
* static_cast<float>(elapsed) / 1000.0f;
const float scrollBefore = m_scrollXTiles;
@@ -435,10 +435,15 @@ float GameWorldView::viewportWidthTiles() const
return static_cast<float>(width()) / tilePx();
}
float GameWorldView::viewLeftTiles() const
{
return m_scrollXTiles - viewportWidthTiles() / 2.0f;
}
QPointF GameWorldView::worldToWidget(QVector2D worldPos) const
{
return QPointF(
static_cast<qreal>((worldPos.x() - m_scrollXTiles) * tilePx()),
static_cast<qreal>((worldPos.x() - viewLeftTiles()) * tilePx()),
static_cast<qreal>(worldPos.y() * tilePx()));
}
@@ -450,14 +455,14 @@ QPointF GameWorldView::tileToWidget(QPoint tile) const
QPoint GameWorldView::widgetToTile(QPoint widgetPt) const
{
const float wx = static_cast<float>(widgetPt.x()) / tilePx() + m_scrollXTiles;
const float wx = static_cast<float>(widgetPt.x()) / tilePx() + viewLeftTiles();
const float wy = static_cast<float>(widgetPt.y()) / tilePx();
return QPoint(static_cast<int>(std::floor(wx)), static_cast<int>(std::floor(wy)));
}
QVector2D GameWorldView::widgetToWorld(QPoint widgetPt) const
{
const float wx = static_cast<float>(widgetPt.x()) / tilePx() + m_scrollXTiles;
const float wx = static_cast<float>(widgetPt.x()) / tilePx() + viewLeftTiles();
const float wy = static_cast<float>(widgetPt.y()) / tilePx();
return QVector2D(wx, wy);
}
@@ -471,9 +476,9 @@ QRectF GameWorldView::tileRect(QPoint tile) const
QRect GameWorldView::viewportRect() const
{
const int left = static_cast<int>(std::floor(m_scrollXTiles)) - 1;
const int left = static_cast<int>(std::floor(viewLeftTiles())) - 1;
const int top = 0;
const int right = static_cast<int>(std::ceil(m_scrollXTiles + viewportWidthTiles())) + 1;
const int right = static_cast<int>(std::ceil(viewLeftTiles() + viewportWidthTiles())) + 1;
const int bottom = m_config->world.heightTiles;
return QRect(left, top, right - left, bottom - top);
}
@@ -546,8 +551,11 @@ float GameWorldView::panSpeedTilesPerSecondAt(float viewCenterXTiles) const
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 = asteroidLeftEdge();
const float rightBound = enemyStationRightEdge() - viewportWidthTiles();
const float rightBound = enemyStationRightEdge();
m_scrollXTiles = std::max(leftBound, std::min(m_scrollXTiles, rightBound));
}
@@ -906,7 +914,7 @@ void GameWorldView::drawPortGlyph(QPainter& painter, QPoint bodyTile,
void GameWorldView::drawTiles(QPainter& painter)
{
const int leftTile = static_cast<int>(std::floor(m_scrollXTiles)) - 1;
const int leftTile = static_cast<int>(std::floor(viewLeftTiles())) - 1;
const int rightTile = leftTile + static_cast<int>(std::ceil(viewportWidthTiles())) + 2;
const int bottomTile = m_config->world.heightTiles;

View File

@@ -134,6 +134,9 @@ private:
float tilePx() const;
float viewportWidthTiles() const;
// World-X (tiles) at the left edge of the viewport. m_scrollXTiles stores the
// view center; this derives the left edge the world<->widget conversions need.
float viewLeftTiles() const;
QPointF worldToWidget(QVector2D worldPos) const;
QPointF tileToWidget(QPoint tile) const;
QPoint widgetToTile(QPoint widgetPt) const;
@@ -218,6 +221,7 @@ private:
std::mt19937 m_rng;
double m_gameSpeedMultiplier;
double m_prevNonZeroSpeed;
// World-X (tiles) at the center of the viewport (see viewLeftTiles()).
float m_scrollXTiles;
QTimer* m_renderTimer;