Unity 2D非瓦片开放世界纹理遮挡剔除内存与性能优化求助
问题描述
我要做一个非瓦片式(不用Tilemap)的2D开放世界,用单场景加大量方形平面的方案替代不适用的场景式系统。脚本会自动给每个平面生成纹理和材质,但世界达到预期规模时纹理会占约1GB内存,引发内存问题。我想实现类似遮挡剔除的功能,只加载相机附近的纹理,其余不加载,但自行实现的脚本性能极差,挂载在Camera上的代码如下:
void Update() { if (playerRB.velocity == Vector2.zero) return; var coords = Vector2Int.RoundToInt(transform.position / 10); for (var x = -1; x <= 1; x++) { for (var y = -1; y <= 1; y++) { var chunk = _terrainRoot.GetChunk(coords + new Vector2Int(x, y)); _terrainRoot.ActivateChunk(chunk); _activeChunks.Add(chunk); } } for (var i = _activeChunks.Count - 1; i >= 0; i--) { if (_activeChunks[i].isVisible) continue; _terrainRoot.DeactivateChunk(_activeChunks[i]); _activeChunks.RemoveAt(i); } Resources.UnloadUnusedAssets(); }
我知道在Update里执行大量操作不好,求更优的实现方案。
优化方案
- 取消高频Update执行:不要每帧都触发chunk检查,改用两种方式控制触发时机:
- 固定时间间隔:用
InvokeRepeating每隔0.5-1秒检查一次,避免每帧开销; - 位置阈值触发:记录玩家上一次检查的坐标,只有当玩家移动超过半个chunk的距离(比如5单位)时才重新计算,减少无效检查。
- 固定时间间隔:用
- 优化活跃Chunk的管理:把
List<TerrainChunk>换成HashSet<TerrainChunk>,判断Chunk是否已加载的效率从O(n)提升到O(1),避免重复添加相同Chunk。 - 禁用每帧调用
Resources.UnloadUnusedAssets:这个方法开销极大,改用协程延迟调用,比如在Chunk加载/卸载完成后延迟1-2秒调用一次,或者玩家静止一段时间后触发,不要每帧执行。 - 简化可见性判断逻辑:不要依赖
Renderer.isVisible(每帧判断有额外开销),直接通过坐标距离判断:只保留玩家周围N格内的Chunk,超出范围直接卸载,计算比视锥体交集更高效。 - 改用异步加载与对象池:如果
ActivateChunk是实例化操作,改成对象池复用Chunk对象;加载纹理时用异步接口(比如AssetBundle.LoadAssetAsync),避免同步加载导致主线程卡顿。 - 优化Chunk加载/卸载流程:先卸载超出范围的Chunk,再加载新范围内未加载的Chunk,避免先全量添加再过滤的冗余操作。
改进后的代码示例
private Vector2Int _lastCheckedCoords; private float _checkInterval = 0.5f; // 可根据需求调整检查间隔 private HashSet<TerrainChunk> _activeChunks = new HashSet<TerrainChunk>(); void Start() { // 初始化加载初始区域的Chunk _lastCheckedCoords = Vector2Int.RoundToInt(transform.position / 10); LoadSurroundingChunks(_lastCheckedCoords); // 启动固定间隔检查 InvokeRepeating(nameof(CheckChunkLoadState), 0f, _checkInterval); } void CheckChunkLoadState() { var currentCoords = Vector2Int.RoundToInt(transform.position / 10); // 坐标未变化则直接返回 if (currentCoords == _lastCheckedCoords) return; // 收集需要卸载的Chunk(超出周围1格范围) var chunksToUnload = new List<TerrainChunk>(); foreach (var chunk in _activeChunks) { var chunkCoords = chunk.Coords; // 假设每个Chunk有存储自身坐标的属性 var distance = Vector2Int.Distance(currentCoords, chunkCoords); if (distance > 1) { chunksToUnload.Add(chunk); } } // 执行卸载 foreach (var chunk in chunksToUnload) { _terrainRoot.DeactivateChunk(chunk); _activeChunks.Remove(chunk); } // 加载新范围内的Chunk LoadSurroundingChunks(currentCoords); _lastCheckedCoords = currentCoords; // 延迟调用资源卸载,避免频繁触发 StartCoroutine(UnloadUnusedAssetsDelayed()); } void LoadSurroundingChunks(Vector2Int centerCoords) { for (var x = -1; x <= 1; x++) { for (var y = -1; y <= 1; y++) { var targetCoords = centerCoords + new Vector2Int(x, y); var chunk = _terrainRoot.GetChunk(targetCoords); // 跳过已加载的Chunk if (chunk == null || _activeChunks.Contains(chunk)) continue; _terrainRoot.ActivateChunk(chunk); _activeChunks.Add(chunk); } } } IEnumerator UnloadUnusedAssetsDelayed() { yield return new WaitForSeconds(1f); Resources.UnloadUnusedAssets(); }
内容的提问来源于stack exchange,提问作者d1squit
相关产品推荐
相关产品推荐

