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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 14:34:58