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

Unity中Scriptable Object存储的对象引用运行时失效问题求助

问题描述

我在Unity里用Context Menu功能把Mesh的顶点(Vertex)和三角形(Tri)数据存入自定义ScriptableObject(LevelData),想要实现修改AllVerts里的顶点时,AllTris中对应的顶点同步变化。编辑模式下测试正常,但运行时这种引用关联会失效。在Start方法里重新生成数据能正常工作,但我想通过预计算来提升性能,求问题原因、解决办法,以及用ID实现关联是否可行?


问题原因

Unity运行时加载ScriptableObject时,会对其中的引用类型做深度序列化重建:

  • 编辑模式下用ContextMenu生成的Tri里的Vertex引用,是直接指向AllVerts里的内存实例;
  • 但运行时加载LevelData时,AllVerts和AllTris里的Vertex会被重新创建成独立的内存实例,原来的引用关系被切断,修改AllVerts的顶点自然不会同步到AllTris。

而Start里重新生成数据时,是在运行时内存里直接创建并关联实例,引用关系是实时生效的,所以能正常工作。


解决办法

方案1:修复运行时引用关联(低开销,保留预计算数据)

在LevelData里加一个初始化方法,运行时调用一次重新建立Tri和Vertex的引用,不用重复生成整个数据集:

修改LevelData类:

[CreateAssetMenu(fileName = "LevelData", menuName = "Tactics/LevelData", order = 0)]
public class LevelData : ScriptableObject {
    public List<Tri> AllTris = new List<Tri>();
    public List<Vertex> AllVerts = new List<Vertex>();
   
    public Vertex GetVertexFromVector3(Vector3 position)
    {
        foreach (Vertex v in AllVerts)
        {
            if (v.pos == position)
            {
                return v;
            }
        }
        return null;
    }

    // 运行时重建Tri与Vertex的引用关系
    public void RebuildTriVertexReferences()
    {
        // 用字典缓存顶点ID和实例的映射,比遍历查找快很多
        Dictionary<int, Vertex> vertexIdMap = new Dictionary<int, Vertex>();
        foreach(var vert in AllVerts)
        {
            vertexIdMap[vert.id] = vert;
        }

        foreach(var tri in AllTris)
        {
            tri.First = vertexIdMap[tri.First.id];
            tri.Second = vertexIdMap[tri.Second.id];
            tri.Third = vertexIdMap[tri.Third.id];
        }
    }
}

然后在Start里只调用初始化方法:

private void Start() {
    levelData.RebuildTriVertexReferences();
    MoveVertexAndLogTriangle();
}

这个方案保留预计算的顶点和三角形数据,仅重建引用,性能开销极小。


方案2:用ID关联(序列化友好,彻底避免引用问题)

如果觉得引用依赖容易出问题,可以把Tri里的Vertex实例改成存储顶点ID,每次需要获取顶点时通过ID从AllVerts里查找。这种方式完全规避序列化导致的引用断裂问题,适合长期存储。

1. 修改Tri类

// 替换原来的Tri类
[System.Serializable]
public class Tri
{
    public int id;
    public int FirstId;
    public int SecondId;
    public int ThirdId;

    public Tri(int id, int v1Id, int v2Id, int v3Id)
    {
        this.id = id;
        FirstId = v1Id;
        SecondId = v2Id;
        ThirdId = v3Id;
    }

    // 从LevelData获取对应顶点的方法
    public Vertex GetFirst(LevelData levelData) => levelData.AllVerts.Find(v => v.id == FirstId);
    public Vertex GetSecond(LevelData levelData) => levelData.AllVerts.Find(v => v.id == SecondId);
    public Vertex GetThird(LevelData levelData) => levelData.AllVerts.Find(v => v.id == ThirdId);
}

2. 修改生成Tri的代码(ContextMenu和Start方法通用)

[ContextMenu("Find All Triangles")]
void GetAllTris(){
    mesh = GetComponent<MeshFilter>().sharedMesh;
    levelData.AllTris = new List<Tri>();
    List<Vertex> verts = levelData.AllVerts;
    for (int t = 0; t < mesh.triangles.Length; t+=3)
    {
        int v1Id = verts[mesh.triangles[t+0]].id;
        int v2Id = verts[mesh.triangles[t+1]].id;
        int v3Id = verts[mesh.triangles[t+2]].id;
        levelData.AllTris.Add(new Tri( (t+3)/3 - 1, v1Id, v2Id, v3Id ));
    }
}

3. 修改测试方法

[ContextMenu("Test relationship between AllVerts and AllTris")]
void MoveVertexAndLogTriangle(){
    Tri firstTri = levelData.AllTris.First();
    Vertex v1 = firstTri.GetFirst(levelData);
    Debug.Log(v1.pos);
    v1.pos += Vector3.up;
    // 再次获取验证同步效果
    Debug.Log(firstTri.GetFirst(levelData).pos);
}

如果觉得每次查找顶点效率低,可以在LevelData初始化时提前建立ID->Vertex的字典缓存,进一步提升性能。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 22:18:10