Unity运行时创建/保存自定义关卡数据的最佳方案咨询
关于Unity自定义关卡存档的方案建议
是否推荐用Scriptable Objects做运行时持久化?
不推荐。Scriptable Objects(SO)的设计定位是编辑器内的配置容器,虽然运行时可以实例化,但它并不适合做运行时的持久化存储:
- SO内部包含Unity的对象引用元数据,
JsonUtility无法正确序列化这些非纯数据的部分,导致序列化/反序列化后数据丢失或实例无法还原; - 运行时创建的SO实例在跨平台(尤其是移动端)的持久化兼容性上存在风险,相关调试资料少,后期维护成本高。
更优的存档系统实现方案
1. 纯数据类(POCO)+ JSON序列化(最适合当前需求)
直接定义仅包含关卡数据的普通C#类,配合JSON序列化工具保存到本地文件,完全不需要依赖MonoBehaviour或SO,轻量且优雅。
示例代码:
首先定义数据类:
[System.Serializable] public class LevelData { public string levelName; public string authorName; } // 用于包裹列表(解决JsonUtility无法直接序列化顶级数组的问题) [System.Serializable] public class LevelDataCollection { public List<LevelData> levels; }
然后实现保存/加载逻辑:
using UnityEngine; using System.IO; using System.Collections.Generic; public class LevelSaveManager : MonoBehaviour { private readonly string _saveFilePath = Path.Combine(Application.persistentDataPath, "custom_levels.json"); // 保存关卡列表 public void SaveCustomLevels(List<LevelData> levels) { var levelCollection = new LevelDataCollection { levels = levels }; string jsonContent = JsonUtility.ToJson(levelCollection, prettyPrint: true); File.WriteAllText(_saveFilePath, jsonContent); } // 加载关卡列表 public List<LevelData> LoadCustomLevels() { if (!File.Exists(_saveFilePath)) { return new List<LevelData>(); } string jsonContent = File.ReadAllText(_saveFilePath); var levelCollection = JsonUtility.FromJson<LevelDataCollection>(jsonContent); return levelCollection?.levels ?? new List<LevelData>(); } }
如果觉得JsonUtility的数组限制麻烦,可以使用Unity官方支持的**Newtonsoft Json(Json.NET)**包(通过Package Manager安装),它可以直接序列化/反序列化列表,代码更简洁:
using Newtonsoft.Json; // 保存方法 public void SaveCustomLevels(List<LevelData> levels) { string jsonContent = JsonConvert.SerializeObject(levels, Formatting.Indented); File.WriteAllText(_saveFilePath, jsonContent); } // 加载方法 public List<LevelData> LoadCustomLevels() { if (!File.Exists(_saveFilePath)) { return new List<LevelData>(); } string jsonContent = File.ReadAllText(_saveFilePath); return JsonConvert.DeserializeObject<List<LevelData>>(jsonContent) ?? new List<LevelData>(); }
2. 本地数据库(适合未来需求扩展)
如果后续关卡数据会变得复杂(比如增加关卡ID、创建时间、关卡内容数据等),或者需要频繁查询、修改单条关卡数据,推荐使用SQLite本地数据库。
示例代码(基于sqlite-net插件):
首先定义数据模型:
public class LevelData { [PrimaryKey, AutoIncrement] public int Id { get; set; } public string LevelName { get; set; } public string AuthorName { get; set; } }
然后实现数据库操作:
using SQLite; using UnityEngine; using System.IO; using System.Collections.Generic; public class LevelDbManager : MonoBehaviour { private SQLiteConnection _dbConnection; void Awake() { string dbPath = Path.Combine(Application.persistentDataPath, "custom_levels.db"); _dbConnection = new SQLiteConnection(dbPath); _dbConnection.CreateTable<LevelData>(); } // 保存/更新单条关卡数据 public void SaveLevel(LevelData level) { _dbConnection.InsertOrReplace(level); } // 加载所有关卡 public List<LevelData> LoadAllLevels() { return _dbConnection.Table<LevelData>().ToList(); } // 根据作者查询关卡 public List<LevelData> LoadLevelsByAuthor(string authorName) { return _dbConnection.Table<LevelData>().Where(l => l.AuthorName == authorName).ToList(); } void OnDestroy() { _dbConnection?.Close(); } }
3. PlayerPrefs(仅适合极少量数据)
如果自定义关卡数量极少,也可以用PlayerPrefs将序列化后的关卡列表字符串存储,但这种方式仅适合临时存储或数据量极小的场景,不推荐作为长期存档方案。
内容的提问来源于stack exchange,提问作者user8482543
相关产品推荐
相关产品推荐

