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
相关产品推荐
相关产品推荐

