在游戏开发中,数据配置是绕不开的话题。想象一下,一个RPG游戏里可能有上百个角色、几十种武器、数百个任务,这些数据如果硬编码在代码里,不仅难以维护,还会让策划和程序的工作完全耦合。这就是为什么我们需要Json这样的数据格式。
我做过一个中型RPG项目,最初把所有角色属性都写在代码里,结果每次策划想调整数值,都需要程序员重新编译打包。后来改用Json配置后,策划可以直接在Excel里编辑,导出Json文件,游戏运行时动态加载,效率提升了至少3倍。
Json在游戏中的典型应用场景包括:
JsonUtility是Unity自带的Json解析工具,最大的优势是不需要引入任何第三方库。我经常用它来处理简单的配置数据。比如下面这个角色配置:
json复制{
"characters": [
{
"id": 1001,
"name": "战士",
"hp": 150,
"attack": 25
}
]
}
对应的C#类需要加上[Serializable]特性:
csharp复制[System.Serializable]
public class Character {
public int id;
public string name;
public int hp;
public int attack;
}
[System.Serializable]
public class CharacterData {
public Character[] characters;
}
解析代码非常简单:
csharp复制TextAsset jsonFile = Resources.Load<TextAsset>("characterData");
CharacterData data = JsonUtility.FromJson<CharacterData>(jsonFile.text);
我在一个包含1000个角色数据的Json文件上测试,JsonUtility的解析速度确实很快,平均只要3ms左右。但它有几个明显的限制:
有次我尝试用JsonUtility解析一个复杂的技能树配置,结果因为嵌套层级太多,直接解析失败了。这时候就需要考虑第三方库了。
LitJson是一个轻量级的Json库,很多Unity开发者都喜欢用它。安装很简单,直接把LitJson.dll放到Plugins文件夹就行。
还是用上面的角色数据,用LitJson解析的代码略有不同:
csharp复制using LitJson;
TextAsset jsonFile = Resources.Load<TextAsset>("characterData");
CharacterData data = JsonMapper.ToObject<CharacterData>(jsonFile.text);
LitJson最大的特点是支持动态类型,这在处理不确定结构的数据时特别有用:
csharp复制JsonData data = JsonMapper.ToObject(jsonFile.text);
int hp = (int)data["characters"][0]["hp"];
在我最近的一个项目中,我们同时使用了JsonUtility和LitJson:
实测下来,LitJson解析同样1000条数据大约需要8ms,虽然比JsonUtility慢一些,但灵活性高很多。内存占用方面,LitJson会比JsonUtility多消耗约20%的内存。
有个坑要注意:LitJson对中文支持需要特殊处理,否则可能会乱码。解决方案是在保存Json文件时使用UTF-8编码,并在解析时指定编码:
csharp复制JsonMapper.ToObject(jsonFile.text, new JsonReader {
SkipNonMembers = true
});
Newtonsoft.Json(现在叫Json.NET)是.NET生态中最强大的Json库,功能非常全面。在Unity中使用需要下载Newtonsoft.Json.dll,建议使用12.0.3版本,兼容性最好。
解析代码和其他两种方式类似:
csharp复制using Newtonsoft.Json;
TextAsset jsonFile = Resources.Load<TextAsset>("characterData");
CharacterData data = JsonConvert.DeserializeObject<CharacterData>(jsonFile.text);
Newtonsoft.Json最强大的地方在于它支持几乎所有你能想到的Json特性:
我做了一个性能对比测试(1000次解析平均值):
| 特性 | JsonUtility | LitJson | Newtonsoft.Json |
|---|---|---|---|
| 简单结构解析时间 | 3ms | 8ms | 15ms |
| 复杂结构解析时间 | 失败 | 25ms | 30ms |
| 内存占用 | 最低 | 中等 | 最高 |
| 功能完整性 | 基础 | 中等 | 全面 |
选择建议:
基于以上分析,我设计了一个混合使用的配置系统架构:
具体实现时,我封装了一个配置管理器:
csharp复制public class ConfigManager {
private static Dictionary<Type, object> _configs = new Dictionary<Type, object>();
public static T Load<T>(string path) where T : class {
if (_configs.TryGetValue(typeof(T), out var config)) {
return config as T;
}
TextAsset textAsset = Resources.Load<TextAsset>(path);
if (textAsset == null) return null;
// 根据类型选择解析方式
if (typeof(T).GetCustomAttributes(typeof(SerializableAttribute), false).Length > 0) {
var data = JsonUtility.FromJson<T>(textAsset.text);
_configs.Add(typeof(T), data);
return data;
}
else {
var data = JsonMapper.ToObject<T>(textAsset.text);
_configs.Add(typeof(T), data);
return data;
}
}
}
在处理大型Json文件时,我总结了几条优化经验:
有次我们游戏的角色配置Json文件达到了5MB,直接导致移动端加载卡顿。后来我们把它拆分成多个小文件,并按职业分类加载,性能立即提升了70%。
游戏开发中经常需要处理继承关系的数据。比如不同类型的武器可能有不同的属性:
json复制{
"weapons": [
{
"type": "sword",
"damage": 20,
"sharpness": 0.9
},
{
"type": "gun",
"damage": 30,
"ammo": 10
}
]
}
用Newtonsoft.Json可以这样处理:
csharp复制[JsonConverter(typeof(WeaponConverter))]
public abstract class Weapon {
public string type;
public int damage;
}
public class Sword : Weapon {
public float sharpness;
}
public class Gun : Weapon {
public int ammo;
}
public class WeaponConverter : JsonConverter {
public override bool CanConvert(Type objectType) {
return objectType == typeof(Weapon);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
JObject jo = JObject.Load(reader);
switch (jo["type"].Value<string>()) {
case "sword": return jo.ToObject<Sword>();
case "gun": return jo.ToObject<Gun>();
default: throw new Exception();
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
throw new NotImplementedException();
}
}
游戏更新时,旧存档可能需要兼容新版本的Json结构。我常用的方法是:
csharp复制public class GameSave {
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
public int version = 1;
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
public string playerName = "Unknown";
// 新版本新增的字段
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
public int playerLevel = 1;
}
在移动设备上处理Json需要特别注意性能问题。我在一个Android项目中发现的问题:
解决方案:
实测下来,在低端Android设备上,将Json文件压缩后再加载,解析时间可以减少40%,内存占用降低35%。