You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android(Java)太阳系2D视图:基于恒星点的缩放平移异常求助

Hey there! Let's sort out this view transformation problem for your top-down solar system game. The core issue right now is that you're tying all celestial body drawing directly to the star's position, which means your pan and zoom are modifying the objects themselves instead of acting on the view/camera layer. Here's how to fix this properly, step by step:

1. Separate World Coordinates from Screen Coordinates

First, you need to split your system into two distinct spaces:

  • World Coordinates: The actual position of each celestial body in your solar system (e.g., the star at (0,0), a planet at (100, 0) relative to the star in your game's unit system).
  • Screen Coordinates: The final position where each object gets drawn on the Android canvas.

To bridge these, you'll add a camera/view layer that handles pan and zoom. This camera will track three key values:

  • cameraX, cameraY: The focus point of the camera in world coordinates (default to the star's world position initially).
  • zoomLevel: The scale factor (1.0 = 1:1 world-to-screen ratio, 2.0 = doubled size, etc.).
2. Add Coordinate Conversion Logic

Create helper methods to translate between world and screen coordinates. This keeps your drawing code clean and reusable:

// Convert world position to screen position
private PointF worldToScreen(float worldX, float worldY, Canvas canvas) {
    int screenCenterX = canvas.getWidth() / 2;
    int screenCenterY = canvas.getHeight() / 2;

    // Shift world coordinates relative to the camera
    float offsetX = worldX - cameraX;
    float offsetY = worldY - cameraY;

    // Apply zoom
    offsetX *= zoomLevel;
    offsetY *= zoomLevel;

    // Convert to screen's top-left origin
    return new PointF(screenCenterX + offsetX, screenCenterY + offsetY);
}

// Convert screen touch position back to world position (for input handling)
private PointF screenToWorld(float screenX, float screenY, Canvas canvas) {
    int screenCenterX = canvas.getWidth() / 2;
    int screenCenterY = canvas.getHeight() / 2;

    // Shift to screen-centered coordinates
    float offsetX = screenX - screenCenterX;
    float offsetY = screenY - screenCenterY;

    // Reverse zoom
    offsetX /= zoomLevel;
    offsetY /= zoomLevel;

    // Convert to world coordinates
    return new PointF(cameraX + offsetX, cameraY + offsetY);
}
3. Rewrite Your Drawing Code

Instead of calculating positions relative to the star every time, use the world coordinates of each body and convert them to screen coordinates:

// Old (broken) approach: Tied to star position
// canvas.drawCircle(starX + planetOffsetX, starY + planetOffsetY, planetRadius * zoom, paint);

// New (fixed) approach: Use world coordinates + camera conversion
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    // Draw star first
    PointF starScreenPos = worldToScreen(starWorldX, starWorldY, canvas);
    canvas.drawCircle(starScreenPos.x, starScreenPos.y, starRadius * zoomLevel, starPaint);

    // Draw each planet using its own world coordinates
    for (Planet planet : planets) {
        PointF planetScreenPos = worldToScreen(planet.worldX, planet.worldY, canvas);
        canvas.drawCircle(planetScreenPos.x, planetScreenPos.y, planet.radius * zoomLevel, planetPaint);
    }
}

Notice we multiply the celestial radii by zoomLevel too—this ensures all objects scale uniformly with the view.

4. Fix Pan and Zoom Input Handling

Now update your touch logic to modify the camera instead of the objects:

Pan (Drag to Move View)

private float lastTouchX, lastTouchY;

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            lastTouchX = event.getX();
            lastTouchY = event.getY();
            return true;
        case MotionEvent.ACTION_MOVE:
            float deltaScreenX = event.getX() - lastTouchX;
            float deltaScreenY = event.getY() - lastTouchY;

            // Convert screen drag distance to world space offset
            cameraX -= deltaScreenX / zoomLevel;
            cameraY -= deltaScreenY / zoomLevel;

            lastTouchX = event.getX();
            lastTouchY = event.getY();
            invalidate(); // Trigger redraw
            return true;
    }
    return super.onTouchEvent(event);
}

Zoom (Double-Finger Scale)

Use Android's ScaleGestureDetector to handle smooth zooming:

private ScaleGestureDetector scaleDetector;

@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    scaleDetector = new ScaleGestureDetector(requireContext(), new ScaleGestureDetector.SimpleOnScaleGestureListener() {
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            zoomLevel *= detector.getScaleFactor();
            // Add bounds to prevent extreme zoom
            zoomLevel = Math.max(0.1f, Math.min(zoomLevel, 10.0f));
            invalidate();
            return true;
        }
    });
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    scaleDetector.onTouchEvent(event);
    // Keep your pan handling code here too
    return super.onTouchEvent(event);
}
5. Optional: Simplify with Canvas Matrix

For even cleaner code, use an Android Matrix to encapsulate all view transformations. This lets you draw directly in world coordinates, with the matrix handling the conversion:

private Matrix viewMatrix = new Matrix();

private void updateViewMatrix(Canvas canvas) {
    viewMatrix.reset();
    // Move origin to screen center
    viewMatrix.postTranslate(canvas.getWidth() / 2, canvas.getHeight() / 2);
    // Apply zoom
    viewMatrix.postScale(zoomLevel, zoomLevel);
    // Shift to camera's world position (negative because we're moving the world, not the camera)
    viewMatrix.postTranslate(-cameraX, -cameraY);
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    updateViewMatrix(canvas);

    // Save canvas state, apply the matrix
    canvas.save();
    canvas.concat(viewMatrix);

    // Now draw everything using world coordinates directly!
    canvas.drawCircle(starWorldX, starWorldY, starRadius, starPaint);
    for (Planet planet : planets) {
        canvas.drawCircle(planet.worldX, planet.worldY, planet.radius, planetPaint);
    }

    // Restore original canvas state
    canvas.restore();
}

This approach is more efficient and reduces boilerplate in your drawing code.


内容的提问来源于stack exchange,提问作者Nieminen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 10:57:40