Unity中ScriptableObject传递对象引用后修改同步变更问题
问题根因说明
- 当
Terrain定义为class时,C#中class属于引用类型,你从SO中传递出去的是指向同一块内存的引用,修改引用指向的实例内容,自然会同步修改SO内的原始数据。 - 就算你把
Terrain改为struct(值类型)也会出问题,是因为你用Terrain[]数组存储数据:数组本身是引用类型,直接通过数组索引修改struct元素的属性/字段时,操作的是数组内的原始值,而非值副本,所以修改仍然会同步到SO数据中。
可行解决方案
方案1:传递实例前手动做深拷贝
无论Terrain是class还是struct,对外传递时都先复制一份独立副本,后续修改副本不会影响原始SO数据:
适用于class类型的Terrain
给Terrain新增Clone方法,示例代码如下:
public class Terrain { public int terrainId; public float height; // 其余自定义属性 public Terrain Clone() { return new Terrain { terrainId = this.terrainId, height = this.height, // 逐字段拷贝所有自定义属性 }; } }
取实例时调用克隆方法即可:Terrain terrainCopy = mapSO.tiles[index].Clone();
适用于struct类型的Terrain
取元素时显式赋值给本地变量,不要直接操作数组内的元素:
// 错误写法:直接修改数组内的struct,会同步修改SO原始数据 mapSO.tiles[0].height = 3.5f; // 正确写法:先拷贝到本地变量,修改的是独立副本 Terrain localTerrain = mapSO.tiles[0]; localTerrain.height = 3.5f;
如果后续需要把修改同步回SO,再手动赋值:mapSO.tiles[0] = localTerrain;即可。
方案2:初始化时全局拷贝SO数据,运行时只操作副本
可以在游戏启动时把SO的所有数据一次性深拷贝到运行时内存中,后续所有业务逻辑都只操作这份副本,完全不接触原始SO,从根源上避免误改原始配置:
public class MapRuntimeData : MonoBehaviour { // 编辑器中赋值原始SO public Map originalMapSO; // 运行时使用的独立数据副本 private Terrain[] _runtimeTiles; private void Awake() { // 初始化时全量拷贝 _runtimeTiles = new Terrain[originalMapSO.tiles.Length]; for (int i = 0; i < originalMapSO.tiles.Length; i++) { _runtimeTiles[i] = originalMapSO.tiles[i].Clone(); } } // 对外只提供副本的访问接口 public Terrain GetTerrain(int index) { return _runtimeTiles[index].Clone(); } }
该方案还可以避免编辑器模式下运行时修改SO,导致原始配置被永久改动的问题。
方案3:将Terrain改为只读结构
如果业务上没有修改Terrain属性的需求,可以直接把Terrain所有属性改为只读,从语法层面禁止意外修改:
public class Terrain { // 仅开放get访问器,构造时赋值后不可修改 public int TerrainId { get; } public float Height { get; } public Terrain(int terrainId, float height) { TerrainId = terrainId; Height = height; } }
内容的提问来源于stack exchange,提问作者Yoaquin
相关产品推荐
相关产品推荐

