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

如何使用C#在MonoGame框架中实现2D网格

MonoGame 通用2D网格实现方案(适配生命游戏/寻路/沙盒模拟)

这套实现和具体业务逻辑解耦,底层结构写完可以直接复用到所有2D网格类项目,基于C# + MonoGame编写,不需要额外依赖。


分步实现

1. 先写纯数据层核心结构(和渲染逻辑解耦)

不要一开始就绑定渲染逻辑,先把网格、格子的纯数据定义写清楚,这部分换任何C#游戏框架都能复用。
首先定义通用格子基类,预留扩展空间适配不同项目需求:

// 可按需扩展的通用格子基类
public class GridCell
{
    // 网格内的整数坐标,从网格左上角(0,0)开始计数
    public int GridX { get; set; }
    public int GridY { get; set; }
    // 对应MonoGame世界坐标系下的格子左上角坐标,渲染、交互检测直接调用
    public Vector2 WorldPosition { get; set; }
    // 通用状态字段:生命游戏存活/死标记,寻路存通行代价/障碍标记,沙盒存材料ID
    public int State { get; set; }
}

再定义网格核心类,存基础参数和格子容器:

public class BaseGrid
{
    // 单个格子的像素边长,正方形网格统一用单值即可,长方形网格可拆为CellWidth/CellHeight
    public int CellSize { get; private set; }
    // 网格横向总格子数(列数)、纵向总格子数(行数)
    public int Columns { get; private set; }
    public int Rows { get; private set; }
    // 网格整体在世界坐标系下的偏移值,要调整网格在屏幕上的位置改这个参数即可
    public Vector2 GridOrigin { get; private set; }
    // 存储所有格子的二维数组,规整网格场景下性能优于交错数组
    private GridCell[,] _cells;
}

2. 实现网格初始化逻辑

在网格类的构造方法里完成所有格子的生成,提前算好每个格子的世界坐标,避免后续每帧重复计算:

public BaseGrid(int cellSize, int columns, int rows, Vector2 gridOrigin)
{
    CellSize = cellSize;
    Columns = columns;
    Rows = rows;
    GridOrigin = gridOrigin;
    _cells = new GridCell[columns, rows];

    // 遍历生成所有格子
    for (int x = 0; x < Columns; x++)
    {
        for (int y = 0; y < Rows; y++)
        {
            _cells[x, y] = new GridCell
            {
                GridX = x,
                GridY = y,
                // 世界坐标计算公式:网格原点 + 格子序号 * 单格尺寸
                WorldPosition = new Vector2(
                    GridOrigin.X + x * CellSize,
                    GridOrigin.Y + y * CellSize
                ),
                State = 0 // 可按需修改默认初始状态
            };
        }
    }
}

3. 实现所有项目通用的工具方法

这部分逻辑写完不用再改,所有网格类项目都能直接用:

  • 坐标转换方法:实现世界坐标(比如鼠标位置)到对应格子的转换,做交互绘制、点击检测必用,内置边界校验避免数组越界
// 输入世界坐标,返回对应位置的格子,坐标不在网格范围内返回null
public GridCell? GetCellAtWorldPosition(Vector2 worldPos)
{
    int gridX = (int)Math.Floor((worldPos.X - GridOrigin.X) / CellSize);
    int gridY = (int)Math.Floor((worldPos.Y - GridOrigin.Y) / CellSize);

    if (gridX < 0 || gridX >= Columns || gridY < 0 || gridY >= Rows)
        return null;
    
    return _cells[gridX, gridY];
}

// 按网格整数坐标取格子,内置边界校验
public GridCell? GetCell(int gridX, int gridY)
{
    if (gridX < 0 || gridX >= Columns || gridY < 0 || gridY >= Rows)
        return null;
    return _cells[gridX, gridY];
}
  • 邻接格子获取方法:生命游戏统计存活邻居、寻路遍历相邻节点、沙盒模拟元素扩散/反应都要用到,支持切换4邻接(上下左右)、8邻接(加四个斜角)
public List<GridCell> GetNeighbors(GridCell cell, bool includeDiagonals = false)
{
    List<GridCell> neighbors = new List<GridCell>();
    // 上下左右四个方向的坐标偏移
    int[] dx = { 0, 0, -1, 1 };
    int[] dy = { -1, 1, 0, 0 };

    if (includeDiagonals)
    {
        // 追加斜角四个方向的偏移
        dx = dx.Concat(new[] { -1, -1, 1, 1 }).ToArray();
        dy = dy.Concat(new[] { -1, 1, -1, 1 }).ToArray();
    }

    for (int i = 0; i < dx.Length; i++)
    {
        var neighbor = GetCell(cell.GridX + dx[i], cell.GridY + dy[i]);
        if (neighbor != null) neighbors.Add(neighbor);
    }

    return neighbors;
}

4. MonoGame渲染层实现

不要每帧新建纹理,提前在Game类的LoadContent阶段创建1x1白色纯色纹理,所有矩形、线条渲染都复用这一个纹理,能大幅降低Draw Call,几百乘几百的大网格也能流畅跑:

private Texture2D _pixelTexture;
protected override void LoadContent()
{
    _pixelTexture = new Texture2D(GraphicsDevice, 1, 1);
    _pixelTexture.SetData(new[] { Color.White });
}
  • 填充单个格子的示例:
// 传入SpriteBatch、要绘制的格子、填充颜色即可
spriteBatch.Draw(
    _pixelTexture, 
    cell.WorldPosition, 
    null, 
    drawColor, 
    0f, 
    Vector2.Zero, 
    new Vector2(CellSize), 
    SpriteEffects.None, 
    0f
);
  • 画网格线:不要逐格画边框,直接按行、列画直线性能更好,横向线从网格左边缘画到右边缘,纵向线从上边缘画到下边缘即可。

不同项目的适配方式

  • 康威生命游戏:不需要修改底层结构,每帧遍历所有格子,调用GetNeighbors(cell, true)统计8邻接内存活细胞数量,按规则计算下一代状态即可。注意用双缓冲存储状态:不要在遍历过程中直接修改当前帧的格子状态,先把下一代所有格子的状态存在单独的数组里,全部计算完成后再统一更新到网格上,避免同帧状态互相干扰导致结果错误。
  • 路径寻路(A*/BFS/DFS):给GridCell扩展GCost、HCost、ParentNode三个字段即可,State字段用来标记可通行、障碍、起点、终点,邻接方法默认用4邻接,直接在网格上写寻路逻辑即可。
  • 沙盒模拟(粉末类游戏):把State字段和不同材料ID做映射(0=空气、1=沙子、2=水、3=石头等),每帧按规则顺序遍历格子,调用邻接方法判断周围格子状态,实现重力、流动、元素反应逻辑即可。

常见避坑

  • 不要混用网格整数坐标和世界浮点坐标,所有坐标转换统一用封装好的GetCellAtWorldPosition方法,避免手动计算出现坐标偏移。
  • 所有取格子、取邻接的逻辑必须做边界校验,否则鼠标点到网格外、取网格边缘格子的邻接时会直接抛出数组越界异常。
  • 大尺寸网格不要逐格新建纹理,复用提前创建的1x1白纹理改颜色即可,否则会出现严重的性能卡顿。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 16:18:21