内容将会持续更新,有错误的地方欢迎指正,谢谢!
拥有更好的学习体验 —— 不断努力,不断进步,不断探索 |
助力快速掌握 Serialized序列化 学习 为初学者节省宝贵的学习时间,避免困惑! |
文章目录
- 一、 引言
- 二、什么是序列化?
- 三、Unity游戏对象的序列化
- 四、脚本变量的序列化
- 1、 [SerializeField]:私有字段的序列化
- 2、 [System.Serializable]:自定义类的序列化
- 五、ScriptableObject序列化
- 六、常见序列化格式
- 1、 JSON序列化(Newtonsoft.Json)
- 2、CSV序列化(CsvHelper库)
- 3、Excel序列化 (EPPlus库)
- 4、XML序列化
一、 引言
在Unity开发中,序列化(Serialization) 是贯穿整个开发流程的核心技术。无论是游戏对象的持久化存储、Inspector面板的变量显示,还是跨平台数据交换,都离不开序列化的支持。对于编辑器扩展开发而言,深入理解序列化机制更是实现自定义工具和高效数据管理的关键。
为什么需要关注序列化?
-
数据持久化:保存玩家存档、配置数据、场景状态。
-
Inspector交互:在编辑器中直观调整脚本参数。
-
跨平台兼容:通过标准格式(如JSON)实现不同平台间的数据交换。
-
性能优化:合理的序列化策略可减少内存占用和加载时间。
二、什么是序列化?
序列化是将对象转换为可存储或传输的格式(如二进制、JSON等)的过程,使得数据能在不同系统或平台间共享。反向的反序列化则是将这些格式数据还原为原始对象的过程。
在Unity中,序列化的典型场景包括:
- 场景文件:将游戏对象层次结构和组件数据保存为.unity文件
- Prefab:保存预设对象的模板
- Inspector面板:显示并修改脚本的序列化字段
- 数据持久化:存档、配置文件或网络通信
三、Unity游戏对象的序列化
游戏对象与组件的自动序列化
Unity的GameObject和Component(如Transform、Renderer等)默认支持序列化。其数据保存在场景或Prefab文件中,包括层级关系、组件属性等。
场景文件示例
打开一个.unity场景文件,可以看到类似以下结构的序列化数据:
--- !u!1 &12345
GameObject:
m_Name: Player
m_Component:
- component: {fileID: 123456}
--- !u!4 &123456
Transform:
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
序列化的限制
并非所有类型都能被Unity自动序列化。以下情况会导致字段不被序列化:
- 静态字段(static)
- 非继承自UnityEngine.Object的类引用
- 标记为[NonSerialized]的字段
- 属性(Properties):仅支持字段序列化。
- 未标记的私有字段:需使用[SerializeField]。
- 某些引用类型:如Dictionary(需自定义序列化)。
四、脚本变量的序列化
通过特性标记,开发者可以控制脚本中哪些字段需要序列化并在Inspector中显示。
1、 [SerializeField]:私有字段的序列化
Unity默认只会序列化public字段。若需序列化私有或受保护字段,可使用[SerializeField]。
public class Player : MonoBehaviour {
[SerializeField]
private int health = 100; // Inspector中可见
}
2、 [System.Serializable]:自定义类的序列化
自定义类或结构体需添加[System.Serializable]属性,才能在Inspector中显示或被Unity序列化。
[System.Serializable]
public class Weapon {
public string name;
public int damage;
}
public class Inventory : MonoBehaviour {
public List<Weapon> weapons; // 列表中每个Weapon属性都可在Inspector编辑
}
✅ 最佳实践
避免过度序列化:仅序列化必要数据以减少内存开销
版本兼容性:修改序列化类时注意向后兼容性(如添加新字段不影响旧数据)
五、ScriptableObject序列化
ScriptableObject是Unity提供的一个功能强大的工具,通常用于保存游戏数据、配置数据等。ScriptableObject在Unity中是非常重要的,它不仅能够序列化数据,还能够让你避免创建多个实例化的对象。
ScriptableObject核心特性:
- 独立数据容器:不与场景绑定,保存于.asset文件
- 运行时修改持久化:编辑器模式下修改可保存到项目
- 高效引用系统:作为资产被多个对象引用
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
[System.Serializable]
public class SkillEffect {
public enum EffectType { Damage, Heal, Buff }
public EffectType type;
public float value;
public float duration;
}
[System.Serializable]
public class SkillLevel {
[Range(1, 100)]
public int requiredLevel = 1;
public float manaCost;
public SkillEffect[] effects;
}
// 主ScriptableObject类
[CreateAssetMenu(fileName = "New Skill", menuName = "RPG/Skill Config")]
public class SkillConfig : ScriptableObject {
public string skillName;
public Sprite icon;
public float baseCooldown;
[TextArea(3, 5)]
public string description;
[Space]
public SkillLevel[] levels = new SkillLevel[5]; // 最多5级技能
}
public class SkillSystemTest : MonoBehaviour {
void Start() {
// 创建临时技能配置
SkillConfig tempSkill = CreateSkill("雷电术",
new SkillLevel {
requiredLevel = 5,
manaCost = 30,
effects = new[] {
new SkillEffect {
type = SkillEffect.EffectType.Damage,
value = 50,
duration = 2
}
}
}
);
// 在编辑器模式下保存资产
#if UNITY_EDITOR
AssetDatabase.CreateAsset(tempSkill, "Assets/Skills/Lightning.asset");
AssetDatabase.SaveAssets();
#endif
}
SkillConfig CreateSkill(string name, params SkillLevel[] levels) {
SkillConfig config = ScriptableObject.CreateInstance<SkillConfig>();
config.skillName = name;
config.levels = levels;
return config;
}
}
六、常见序列化格式
1、 JSON序列化(Newtonsoft.Json)
为什么选择Newtonsoft?
- 原生JsonUtility不足:不支持字典、私有字段嵌套类
- Newtonsoft特性:高效灵活,支持复杂类型转换
- 安装方法:通过Unity Package Manager添加com.unity.nuget.newtonsoft-json
using Newtonsoft.Json;
using System.Collections.Generic;
using UnityEngine;
// 复杂数据结构示例(包含嵌套类和字典)
[System.Serializable]
public class Player {
public string name;
[SerializeField]
private int _health; //私有字段支持序列化
public Dictionary<string, int> skills = new Dictionary<string, int>();
// 自定义Vector3序列化(需转换)
[JsonIgnore]
public Vector3 position;
[JsonProperty("position")]
private Vector3Serializable PositionSerializable {
get => new Vector3Serializable(position);
set => position = value.ToVector3();
}
}
// Vector3序列化辅助类
[System.Serializable]
public struct Vector3Serializable {
public float x;
public float y;
public float z;
public Vector3Serializable(Vector3 v) {
x = v.x;
y = v.y;
z = v.z;
}
public Vector3 ToVector3() => new Vector3(x, y, z);
}
public class JsonExample : MonoBehaviour {
void Start() {
Player player = new Player {
name = "Arthur",
_health = 200,
position = new Vector3(10, 2, 5)
};
player.skills.Add("Attack", 85);
player.skills.Add("Defense", 60);
// 序列化
string json = JsonConvert.SerializeObject(player, Formatting.Indented);
Debug.Log("JSON Output:\n" + json);
// 反序列化
Player loadedPlayer = JsonConvert.DeserializeObject<Player>(json);
Debug.Log($"Position: {loadedPlayer.position}");
}
}
控制台输出:
{
"name": "Arthur",
"skills": {
"Attack": 85,
"Defense": 60
},
"position": {
"x": 10.0,
"y": 2.0,
"z": 5.0
}
}
关键注释:
- [JsonIgnore]:标记不参与序列化的字段
- [JsonProperty]:自定义字段映射名称
- Vector3Serializable:需要手动处理Unity引擎特有类型
2、CSV序列化(CsvHelper库)
CsvHelper核心功能
- 自动映射类属性到CSV列
- 处理标题行复杂数据类型
- 安装方法:通过NuGet安装CsvHelper
using CsvHelper;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using UnityEngine;
// CSV数据模型类
public class EnemyData {
public int Id { get; set; }
public string Name { get; set; }
public float Speed { get; set; }
public string PrefabPath { get; set; }
}
public class CsvExample : MonoBehaviour {
private string csvPath = "Enemies.csv";
void Start() {
WriteCSV();
ReadCSV();
}
//序列化 将数据写入CSV文件中
void WriteCSV() {
List<EnemyData> enemies = new List<EnemyData> {
new EnemyData { Id = 1, Name = "Goblin", Speed = 3.5f, PrefabPath = "Prefabs/Enemies/Goblin" },
new EnemyData { Id = 2, Name = "Dragon", Speed = 8.0f, PrefabPath = "Prefabs/Enemies/Dragon" }
};
using (var writer = new StreamWriter(csvPath))
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture)) {
csv.WriteRecords(enemies);
}
Debug.Log("CSV写入完成!");
}
//反序列化 将读取到的数据转换成类
void ReadCSV() {
using (var reader = new StreamReader(csvPath))
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture)) {
var records = csv.GetRecords<EnemyData>();
foreach (var enemy in records) {
Debug.Log($"ID: {enemy.Id}, Name: {enemy.Name}, Speed: {enemy.Speed}");
}
}
}
}
生成CSV内容:
Id,Name,Speed,PrefabPath
1,Goblin,3.5,Prefabs/Enemies/Goblin
2,Dragon,8,Prefabs/Enemies/Dragon
3、Excel序列化 (EPPlus库)
EPPlus库核心能力
- 创建、修改.xlsx文件
- 支持公式、样式、图表
- 安装:从NuGet安装EPPlus(注意Unity需使用兼容版本)
using OfficeOpenXml;
using System;
using System.IO;
using UnityEngine;
public class ExcelExample : MonoBehaviour {
private string excelPath = "GameData.xlsx";
void Start() {
WriteExcel();
ReadExcel();
}
private void WriteExcel() {
ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
// 序列化 写入数据 创建新Excel文件
using (ExcelPackage package = new ExcelPackage()) {
// 添加工作表
ExcelWorksheet sheet = package.Workbook.Worksheets.Add("玩家数据");
// 填充标题行
sheet.Cells[1, 1].Value = "ID";
sheet.Cells[1, 2].Value = "Name";
sheet.Cells[1, 3].Value = "Level";
sheet.Cells[1, 4].Value = "LastLogin";
// 填充数据
sheet.Cells[2, 1].Value = 101;
sheet.Cells[2, 2].Value = "Player1";
sheet.Cells[2, 3].Value = 15;
sheet.Cells[2, 4].Value = DateTime.Now;
// 保存文件
FileStream fileStream = new FileStream(excelPath, FileMode.Create);
package.SaveAs(fileStream);
fileStream.Close();
}
Debug.Log("Excel写入完成!");
}
//反序列化 读取数据
private void ReadExcel() {
using (ExcelPackage package = new ExcelPackage(new FileInfo(excelPath))) {
ExcelWorksheet sheet = package.Workbook.Worksheets["玩家数据"];
int rowCount = sheet.Dimension.Rows;
for (int row = 2; row <= rowCount; row++) { //跳过标题行
int id = int.Parse(sheet.Cells[row, 1].Text);
string name = sheet.Cells[row, 2].Text;
int level = int.Parse(sheet.Cells[row,3].Text);
DateTime lastLogin = DateTime.Parse(sheet.Cells[row,4].Text);
Debug.Log($"读取到玩家:{name} (等级{level}), 最后登录时间:{lastLogin}");
}
}
}
}
4、XML序列化
核心优势:
- 严格的树状数据格式,支持复杂嵌套结构
- 内置Schema验证(XSD)保证数据完整性
- 支持注释和CDATA块处理特殊字符
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;
using UnityEngine;
// 装备品类(嵌套定义)
[System.Serializable]
public class EquipmentItem {
public enum ItemRarity { Common, Rare, Epic }
[XmlAttribute("id")] // 设置为XML属性
public string itemID;
[XmlElement("displayName")] // 设置为XML元素
public string itemName;
[XmlIgnore] // 不参与XML序列化
public ItemRarity rarity;
[XmlElement("durability")]
public int currentDurability = 100;
// 自定义字段序列化转换器
[XmlElement("rarity")]
public string RarityString {
get => rarity.ToString();
set => rarity = (ItemRarity)System.Enum.Parse(typeof(ItemRarity), value);
}
}
// 角色装备数据容器
[System.Serializable]
public class CharacterEquipment {
[XmlArray("WeaponSlots")] // 定义数组包裹元素
[XmlArrayItem("Weapon")]
public List<EquipmentItem> weapons = new List<EquipmentItem>();
[XmlArray("ArmorSlots")]
[XmlArrayItem("Armor")]
public EquipmentItem[] armors = new EquipmentItem[4]; // 固定长度数组
}
public class XMLSerializeExample : MonoBehaviour {
private string xmlPath = "CharacterData.xml";
void Start() {
// 创建测试数据
CharacterEquipment equipment = new CharacterEquipment {
weapons = {
new EquipmentItem { itemID = "w_001", itemName = "Steel Sword", rarity = EquipmentItem.ItemRarity.Epic },
new EquipmentItem { itemID = "w_002", itemName = "Wooden Bow", rarity = EquipmentItem.ItemRarity.Common }
},
armors = {
new EquipmentItem { itemID = "a_001", itemName = "Iron Helmet" },
null, // 演示空元素处理方法
new EquipmentItem { itemID = "a_003", itemName = "Chainmail" }
}
};
// 序列化到文件
SerializeToXML(equipment, xmlPath);
// 从文件反序列化
CharacterEquipment loadedData = DeserializeFromXML<CharacterEquipment>(xmlPath);
Debug.Log($"加载的武器数量: {loadedData.weapons.Count}");
}
void SerializeToXML<T>(T data, string path) {
XmlSerializer serializer = new XmlSerializer(typeof(T));
using (StreamWriter stream = new StreamWriter(path)) {
serializer.Serialize(stream, data);
}
Debug.Log($"XML序列化完成,文件尺寸:{new FileInfo(path).Length} bytes");
}
T DeserializeFromXML<T>(string path) {
XmlSerializer serializer = new XmlSerializer(typeof(T));
using (StreamReader stream = new StreamReader(path)) {
return (T)serializer.Deserialize(stream);
}
}
}
XML输出结构:
<CharacterEquipment>
<WeaponSlots>
<Weapon id="w_001">
<displayName>Steel Sword</displayName>
<rarity>Epic</rarity>
<durability>100</durability>
</Weapon>
<Weapon id="w_002">
<displayName>Wooden Bow</displayName>
<rarity>Common</rarity>
<durability>100</durability>
</Weapon>
</WeaponSlots>
<ArmorSlots>
<Armor id="a_001">
<displayName>Iron Helmet</displayName>
<rarity>Common</rarity>
<durability>100</durability>
</Armor>
<Armor />
<Armor id="a_003">
<displayName>Chainmail</displayName>
<rarity>Common</rarity>
<durability>100</durability>
</Armor>
</ArmorSlots>
</CharacterEquipment>
关键注解:
- [XmlAttribute]:将字段序列化为XML属性(紧凑格式)
- [XmlElement]:自定义元素名称(默认使用字段名)
- [XmlArray]+[XmlArrayItem]:控制集合的嵌套结构
- 自定义属性转换器:处理枚举类型与字符串的转换
每一次跌倒都是一次成长 每一次努力都是一次进步 |
如果您喜欢本博客,请点赞和分享给更多的朋友,让更多人受益。同时,您也可以关注我的博客,以便及时获取最新的更新和文章。
在未来的写作中,我将继续努力,分享更多有趣、实用的内容。再次感谢大家的支持和鼓励,期待与您在下一篇博客再见!