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

Monogame中如何为相机边界限制集成Zoom缩放并修复显示问题

问题:Monogame相机缩放后的边界适配

我正在使用Monogame开发一款2D游戏,现有Camera类、基础渲染方法和玩家相机边界限制方法如下:

Camera类代码

class Camera
{
    private Matrix transform;
    private Vector2 centre;
    private Viewport viewport;
    private int mapWidthInPx;
    private int mapHeightInPx;
    private float zoom;
    private float rotation;
    private Rectangle oldViewArea;
    private Rectangle viewArea;

    public Camera(Viewport nViewport)
    {
        viewport = nViewport;
        zoom = 1f;
        rotation = 0f;
    }

    public Viewport Viewport
    {
        get { return viewport; }
    }
    public Matrix Transform
    {
        get { return transform; }
    }
    public int MapWidthInPx
    {
        get { return mapWidthInPx; }
        set { mapWidthInPx = value; }
    }
    public int MapHeightInPx
    {
        get { return mapHeightInPx; }
        set { mapHeightInPx = value; }
    }
    public float X
    {
        get { return centre.X; }
        set { centre.X = value; }
    }
    public float Y
    {
        get { return centre.Y; }
        set { centre.Y = value; }
    }
    public float Zoom
    {
        get { return zoom; }
        set
        {
            zoom = value;

            if (zoom < 0.1f)
            {
                zoom = 0.1f;
            }
        }
    }
    public float Rotation
    {
        get { return rotation; }
        set { rotation = value; }
    }
    public Rectangle OldViewArea
    {
        get { return oldViewArea; }
        set { oldViewArea = value; }
    }
    public Rectangle ViewArea
    {
        get { return viewArea; }
    }

    public void Update(Vector2 position)
    {
        SetCentrePosition(position);

        transform = GetTransformMatrix();

        oldViewArea = viewArea;
        viewArea = GetViewArea(Zoom);
    }

    private void SetCentrePosition(Vector2 position)
    {
        if (position.X < viewport.Width / 2)
        {
            centre.X = viewport.Width / 2;
        }
        else if (position.X > mapWidthInPx - (viewport.Width / 2))
        {
            centre.X = mapWidthInPx - (viewport.Width / 2);
        }
        else
        {
            centre.X = position.X;
        }

        if (position.Y < viewport.Height / 2)
        {
            centre.Y = viewport.Height / 2;
        }
        else if (position.Y > mapHeightInPx - (viewport.Height / 2))
        {
            centre.Y = mapHeightInPx - (viewport.Height / 2);
        }
        else
        {
            centre.Y = position.Y;
        }
    }

    private Matrix GetTransformMatrix()
    {
        Matrix matrix = Matrix.CreateTranslation(
            new Vector3(-centre.X, -centre.Y, 0)) *
                        Matrix.CreateRotationZ(Rotation) *
                        Matrix.CreateScale(new Vector3(Zoom, Zoom, 0)) *
                        Matrix.CreateTranslation(new Vector3(viewport.Width / 2, viewport.Height / 2, 0));

        return matrix;
    }

    private Rectangle GetViewArea(float zoom)
    {

        Rectangle rectangle = new Rectangle(
           Convert.ToInt32(X - ((Viewport.Width / 2) / zoom)),
           Convert.ToInt32(Y - ((Viewport.Height / 2) / zoom)),
           Convert.ToInt32(Viewport.Width / zoom),
           Convert.ToInt32(Viewport.Height / zoom));

        return rectangle;
    }
}

基础渲染方法

public static void Render()
{
    transform = camera.Transform;
    gDevice.SetRenderTarget(renderTarget);

    spriteBatch.Begin(
         SpriteSortMode.Deferred, BlendState.AlphaBlend, samplerState, null, null,    null, transformMatrix: transform);

    currentScreen.Render();
    spriteBatch.End();

    gDevice.SetRenderTarget(null);

    spriteBatch.Begin();
    spriteBatch.Draw(renderTarget, renderDestination, Color.White);
    spriteBatch.End();
}

玩家类相机边界限制方法

private void ClampCamBounds()
{
    if (position.X < 0)
    {
        position.X = 0;
    }
    if (position.X > Basic.camera.MapWidthInPx - boundingBox.Width)
    {
        position.X = Basic.camera.MapWidthInPx - boundingBox.Width;
    }
    if (position.Y < 0)
    {
        position.Y = 0;
    }
    if (position.Y > Basic.camera.MapHeightInPx - boundingBox.Height)
    {
        position.Y = Basic.camera.MapHeightInPx - boundingBox.Height;
    }
}

当前启用缩放后,玩家初始不可见,相机仍按无缩放逻辑限制边界。需要调整代码实现:移动时玩家保持屏幕中央,缩放后相机触达地图边缘时停止,同时玩家可以走到地图边缘。


解决方案

核心问题

原相机边界逻辑未考虑缩放因子,导致缩放后可视区域计算错误,相机与玩家的位置适配失效。

1. 修复Camera类的SetCentrePosition方法

缩放后,相机的实际可视半宽/半高需要结合缩放比例计算,替换原固定视口尺寸的逻辑:

private void SetCentrePosition(Vector2 position)
{
    // 计算缩放后的可视半宽/半高
    float halfVisibleWidth = (viewport.Width / 2f) / Zoom;
    float halfVisibleHeight = (viewport.Height / 2f) / Zoom;

    // X轴边界限制
    if (position.X < halfVisibleWidth)
    {
        centre.X = halfVisibleWidth;
    }
    else if (position.X > mapWidthInPx - halfVisibleWidth)
    {
        centre.X = mapWidthInPx - halfVisibleWidth;
    }
    else
    {
        centre.X = position.X;
    }

    // Y轴边界限制
    if (position.Y < halfVisibleHeight)
    {
        centre.Y = halfVisibleHeight;
    }
    else if (position.Y > mapHeightInPx - halfVisibleHeight)
    {
        centre.Y = mapHeightInPx - halfVisibleHeight;
    }
    else
    {
        centre.Y = position.Y;
    }
}

2. 简化玩家的ClampCamBounds方法

玩家的边界限制是地图坐标系内的逻辑,与相机缩放无关,用MathHelper.Clamp简化代码:

private void ClampCamBounds()
{
    position.X = MathHelper.Clamp(position.X, 0, Basic.camera.MapWidthInPx - boundingBox.Width);
    position.Y = MathHelper.Clamp(position.Y, 0, Basic.camera.MapHeightInPx - boundingBox.Height);
}

效果验证

  • 缩放时,相机可视区域随缩放比例调整,边界限制基于缩放后的范围计算,确保不超出地图边缘;
  • 玩家未到达地图边缘时,相机始终将玩家保持在屏幕中央;
  • 玩家靠近地图边缘时,相机先停止移动,玩家可继续走到地图边界;
  • 初始状态下,相机基于缩放后的可视范围正确定位,玩家不会出现不可见问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.11 13:05:55