Monogame 2D瓦片平台游戏帧率下降问题及代码优化求助
问题描述
我用MonoGame开发2D瓦片平台游戏,全屏模式下显示较多瓦片时帧率下降卡顿。通过FrameCounter确认卡顿源于UpdateTiles方法,若强制每次更新都重建瓦片列表(把if (viewArea != oldViewArea)改成if (true)),帧率会暴跌至2帧左右。
当前实现细节:
- 每个瓦片对应独立
Tile类,存储动画、位置、类型等信息 Level类维护瓦片列表,相机视口变化时清空列表并重新创建视口内的所有Tile实例- 渲染时遍历列表调用每个Tile的
Render(底层是spriteBatch.Draw) - 碰撞检测遍历视口内瓦片做矩形相交判断
核心优化:解决
UpdateTiles的性能瓶颈 频繁重建Tile实例是性能暴跌的核心原因,每次创建新对象都会触发GC(垃圾回收),累积后导致卡顿。以下是针对性优化方案:
1. 改用对象池复用Tile实例
预先创建足够的Tile对象存入对象池,视口更新时仅复用闲置实例,避免重复初始化:
// Level类中新增对象池与活跃瓦片列表 private List<Tile> _tilePool = new List<Tile>(); private List<Tile> _activeTiles = new List<Tile>(); // 记录上次视口对应的瓦片范围,减少触发更新的频率 private int _lastXStart, _lastYStart, _lastXEnd, _lastYEnd; private void UpdateTiles() { Rectangle viewArea = Basic.camera.ViewArea; // 计算当前视口对应的瓦片范围 int xStart = viewArea.X / Consts.TileWidth; int yStart = viewArea.Y / Consts.TileHeight; int xEnd = ((int)Math.Ceiling(viewArea.X + viewArea.Width) / Consts.TileWidth) + 1; int yEnd = ((int)Math.Ceiling(viewArea.Y + viewArea.Height) / Consts.TileHeight) + 1; // 仅当瓦片范围变化时才更新(避免像素级移动就触发重建) if (xStart != _lastXStart || yStart != _lastYStart || xEnd != _lastXEnd || yEnd != _lastYEnd) { // 标记所有活跃瓦片为闲置 foreach (var tile in _activeTiles) { tile.IsActive = false; } // 钳位边界,避免越界访问地图 xStart = Math.Max(0, xStart); yStart = Math.Max(0, yStart); xEnd = Math.Min(currentMap.GetLength(1), xEnd); yEnd = Math.Min(currentMap.GetLength(0), yEnd); for (int y = yStart; y < yEnd; ++y) { for (int x = xStart; x < xEnd; ++x) { int id = currentMap[y, x]; if (id > 0) { // 从对象池取闲置Tile,无闲置则新建 Tile tile = _tilePool.FirstOrDefault(t => !t.IsActive) ?? new Tile(Textures.tiles, Rectangle.Empty, TileType.None, 0, 0); // 更新Tile属性,复用实例 tile.Location = new Rectangle(x * Consts.TileWidth, y * Consts.TileHeight, Consts.TileWidth, Consts.TileHeight); tile.Type = (TileType)id; tile.GridX = x; tile.GridY = y; tile.IsActive = true; // 新创建的Tile加入对象池 if (!_tilePool.Contains(tile)) _tilePool.Add(tile); // 闲置Tile重新加入活跃列表 if (!_activeTiles.Contains(tile)) _activeTiles.Add(tile); } } } // 清理活跃列表中的闲置Tile _activeTiles.RemoveAll(t => !t.IsActive); // 更新记录的瓦片范围 _lastXStart = xStart; _lastYStart = yStart; _lastXEnd = xEnd; _lastYEnd = yEnd; } }
同时在Tile类中添加IsActive字段,用于标记实例状态:
public bool IsActive { get; set; }
2. 降低视口更新触发频率
原代码按像素级变化触发更新,相机轻微移动就会重建列表。改成按瓦片格子粒度判断,只有当视口对应的瓦片范围变化时才更新,大幅减少更新次数。
其他辅助优化
1. 渲染优化:批量绘制
避免每个Tile单独调用spriteBatch.Draw,统一在Level类中批量绘制,减少spriteBatch.Begin/End的调用开销:
private void RenderTiles() { // 确保spriteBatch已处于Begin状态(在Level.Render方法开头调用) foreach (var tile in _activeTiles) { Basic.spriteBatch.Draw(tile.Texture, tile.Location, tile.SourceRect, Color.White); } }
如果所有Tile使用同一图集纹理,批量绘制的性能提升会更明显。
2. 碰撞检测优化
原代码遍历所有活跃瓦片做碰撞判断,改成只检查玩家周围的瓦片,减少遍历数量:
private void Move(Vector2 v) { // ... 原有代码 ... // 计算玩家所在的瓦片格子,扩大2格范围避免漏检 int playerGridX = (int)(position.X / Consts.TileWidth); int playerGridY = (int)(position.Y / Consts.TileHeight); int checkXStart = Math.Max(0, playerGridX - 2); int checkXEnd = Math.Min(currentMap.GetLength(1), playerGridX + 2); int checkYStart = Math.Max(0, playerGridY - 2); int checkYEnd = Math.Min(currentMap.GetLength(0), playerGridY + 2); // 仅遍历玩家周围的碰撞瓦片 List<Entity> collidingTiles = new List<Entity>(); for (int y = checkYStart; y < checkYEnd; y++) { for (int x = checkXStart; x < checkXEnd; x++) { int id = currentMap[y, x]; if (id > 0) { Tile tile = _activeTiles.FirstOrDefault(t => t.GridX == x && t.GridY == y); if (tile != null && tile.Collision && tile.BoundingBox.Intersects(collider)) { collidingTiles.Add(tile); } } } } // ... 后续碰撞处理代码 ... }
内容的提问来源于stack exchange,提问作者Canox
相关产品推荐
相关产品推荐

