Unity 的“序列化规则“:哪些数据能“活下来“,藏着一本明明白白的“账“

Unity 的“序列化规则“:哪些数据能“活下来“,藏着一本明明白白的“账“
引子小李的时灵时不灵困惑上回说到小李借着一桩探案搞懂了序列化的魔法也学会了用[SerializeField]让私有变量现身。他以为自己已经摸透了序列化可没过几天又被一连串时灵时不灵的怪现象搞得晕头转向怎么回事啊?同样是想让数据’显示在面板、能存下来’——有的变量,我啥都没干,它自己就乖乖显示了;有的变量,我加了[SerializeField],它也现身了;可有的变量,我明明也加了[SerializeField],它就是死活不显示**!**还有的,我用了字典 Dictionary** 想存一组数据,结果面板上啥也没有,存了个寂寞!**更怪的是,我自己写的一个小类,有时候能在面板里展开、有时候又不行……这’能不能被序列化’,到底是凭什么**?难道是看心情、靠运气?它背后,是不是有一套我还没摸清的**‘规矩’?小李这一连串困惑问到了点子上——序列化绝不是看心情、靠运气它背后有一套清清楚楚、明明白白的规则账本。摸不清这本账就会觉得它时灵时不灵一旦摸清了便豁然开朗、再无意外。老师傅点点头“你上次学的是’序列化是什么’今天该学’序列化的规则’了。Unity 到底愿意帮你保存哪些数据、不愿保存哪些背后有一本记得明明白白的账。今天我就把这本账一页一页翻给你看。”第一章先立总纲——序列化要同时满足两个条件要看懂这本账得先抓住一条总纲一个字段能不能被 Unity 序列化取决于它同时满足两个条件——访问权限对且类型也对。┌────────────────────────────────────────────────┐ │ 能被序列化的总纲:两道关卡,缺一不可! │ │ │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ 第一关:权限 │ │ 第二关:类型 │ │ │ │ 这个字段 │ AND │ 这个字段的 │ │ │ │ 够格被序列 │ │ 类型支持 │ │ │ │ 化吗? │ │ 被序列化吗? │ │ │ └─────────────┘ └─────────────┘ │ │ ↓ ↓ │ │ 两关都过 → ✅ 才能被序列化、显示、保存! │ │ 任一关没过 → ❌ 抱歉,存不了! │ └────────────────────────────────────────────────┘一语道破小李那些时灵时不灵的怪现象根源就在这里——他只盯着第一关权限“加了[SerializeField]就以为万事大吉却忘了还有第二关类型在把关只要有一关没过数据就存了个寂寞”。下面咱们就把这两道关卡逐一看透。小李恍然“原来要过两道关!不是加了[SerializeField]就一定行——还得看这个变量的类型本身,Unity 答不答应序列化它!我之前光顾着第一关,忘了还有第二关——难怪时灵时不灵!”第二章第一关·权限——“谁够格被序列化”先看第一道关卡字段的访问权限够不够格被序列化。┌────────────────────────────────────────────────┐ │ 第一关·权限规则: │ │ │ │ ✅ 够格(权限这关过): │ │ · public 字段 │ │ → 默认就够格,自动显示 │ │ · private/protected [SerializeField] │ │ → 加了标签,破格录取,也够格! │ │ │ │ ❌ 不够格(权限这关没过): │ │ · private/protected 字段(没加[SerializeField])│ │ → 默认不够格,Unity 不理它 │ │ · static 静态字段 │ │ → 永远不够格!(它属于类不属于对象, │ │ 加[SerializeField]也没用!) │ │ · const / readonly 常量 │ │ → 不够格 │ │ · 属性 Property(带 get/set 的) │ │ → 默认不够格 │ └────────────────────────────────────────────────┘划重点第一关其实就是上一篇学的延伸——public默认够格private加上[SerializeField]也能够格。但要特别注意几个**加了[SerializeField]也没用的顽固分子**static静态字段它属于整个类而不属于某个具体对象而序列化保存的是对象的数据所以它天生就在序列化的范围之外贴什么标签都没用属性Property、常量默认也不在序列化之列。 这就解释了小李的一桩怪事——他给一个static字段加了[SerializeField]却死活不显示。真相是static 根本过不了第一关标签贴得再多也是徒劳小李点头“懂了!第一关看权限:public默认够格,private加标签也能够格。但static是个顽固分子——它属于’类’不属于’对象’,序列化存的是对象数据,所以它天生出局,加标签也救不回来!”第三章第二关·类型——“什么类型撑得起序列化”过了权限关还有更关键、也最容易被忽视的第二关这个字段的类型Unity 到底支不支持序列化。┌────────────────────────────────────────────────┐ │ 第二关·类型规则: │ │ │ │ ✅ Unity【支持】序列化的类型: │ │ · 基本类型:int float bool string char 等 │ │ · 常用结构:Vector2/3 Color Quaternion │ │ Rect Bounds 等 Unity 内置类型 │ │ · 枚举 enum │ │ · Unity对象引用:GameObject Transform │ │ 各种Component、ScriptableObject、Texture等 │ │ · 数组 T[] 和 ListT │ │ (但里面装的T,自己也得是可序列化的!) │ │ · 加了 [System.Serializable] 的自定义类/结构体 │ │ │ │ ❌ Unity【不支持】(默认)序列化的类型: │ │ · 字典 DictionaryK,V ← 超高频踩坑! │ │ · 多维数组 int[,]、嵌套ListListT │ │ · 没加[System.Serializable]的自定义类 │ │ · 接口interface、object、委托delegate等 │ └────────────────────────────────────────────────┘⚠️最大的坑Dictionary 不能被序列化小李用字典存了个寂寞的怪事真相就在这——Unity 默认压根不支持序列化Dictionary哪怕你加了[SerializeField]面板上也不会显示、数据也存不下来。这是无数开发者踩过的头号大坑。替代方案想存键值对可以用两个 List 配合一个存 key、一个存 value或者用一些第三方/自定义的可序列化字典方案。小李倒吸一口凉气“原来Dictionary这么坑!我加了标签还以为稳了,结果它类型这关就没过——存了个寂寞’的真相找到了!记住了:想存键值对,得绕道用两个List!”第四章让自定义类也能被序列化——[System.Serializable]小李还有一桩怪事没破为什么我自己写的小类有时能在面板展开、有时不行答案藏在另一个关键标签里——[System.Serializable]// ① 想让自定义类能被序列化,必须加这个标签![System.Serializable]publicclassWeapon{publicstringweaponName铁剑;publicintdamage10;publicfloatattackSpeed1.5f;}publicclassPlayer:MonoBehaviour{// ② 因为Weapon加了[System.Serializable],// 它就能显示在面板、能展开编辑了!publicWeaponmainWeapon;// ③ 装着可序列化类型的List,也能完美显示!publicListWeaponbackpack;}┌────────────────────────────────────────────────┐ │ ️ 两个序列化标签,别搞混! │ │ │ │ [SerializeField] │ │ → 加在【字段】前 │ │ → 作用:让 private 字段也能被序列化 │ │ → 解决权限关(第一关) │ │ │ │ [System.Serializable] │ │ → 加在【自定义类/结构体】定义前 │ │ → 作用:让这个类型本身变得可序列化 │ │ → 解决类型关(第二关) │ │ │ │ 一个管字段够不够格,一个管类型撑不撑得起! │ └────────────────────────────────────────────────┘真相大白小李的自定义类有时能展开、有时不行——区别就在于那个类有没有加[System.Serializable]加了它就成了Unity 支持的可序列化类型能在面板里漂亮地展开、嵌套编辑不加它就过不了类型关面板上一片空白。别把两个标签搞混[SerializeField]管的是字段的权限第一关[System.Serializable]管的是类型本身可不可序列化第二关。一个修字段一个修类型各司其职。小李茅塞顿开“最后一桩也破了!我那自定义类,加了[System.Serializable]就能展开、不加就一片空白!原来这俩标签分工明确:[SerializeField]修第一关的’权限’,[System.Serializable]修第二关的’类型’——再也不会搞混了!”第五章还有些隐藏规矩——别让序列化坑了你老师傅补充道这本规则账里还有几条容易被忽视、却很要命的隐藏条款┌────────────────────────────────────────────────┐ │ ⚠️ 几条容易踩的隐藏规矩: │ │ │ │ 1️⃣ 序列化【不存 null】的概念: │ │ 一个可序列化的自定义类字段, │ │ 就算你没赋值,反序列化时Unity也会 │ │ 给你造一个默认实例出来(而非null) │ │ │ │ 2️⃣ 不支持多态: │ │ 用基类字段装子类对象,序列化后 │ │ 可能丢失子类的真实类型,变回基类! │ │ (这是普通[Serializable]类的局限) │ │ │ │ 3️⃣ 循环引用会出问题: │ │ A引用B、B又引用A……普通类序列化 │ │ 会陷入死循环或报错 │ │ (用对象引用如GameObject则没事) │ │ │ │ 4️⃣ 改了字段名,存的值会丢: │ │ 序列化是按字段名对应存取的, │ │ 你把health改名成hp,旧存档里 │ │ 那个health的值就对不上、丢了! │ │ (可用[FormerlySerializedAs]补救) │ └────────────────────────────────────────────────┘划重点这几条隐藏规矩是项目做大后最容易暗中坑你的地方。尤其第 4 条——改字段名会导致旧数据丢失——很多人改了个变量名发现辛苦配好的一堆数据全没了却找不到原因。记住序列化是认字段名的随意改名等于切断了新旧数据的对应关系。小李感慨“原来水这么深!不存null、不支持多态、循环引用会爆、改名字会丢数据……这些’隐藏条款’要是不知道,迟早被坑得莫名其妙!这本’规则账’,真得记牢啊!”第六章终极总结——序列化规则到底是怎样一本账小李把这本规则账浓缩成一张表┌────────────────┬──────────────────────────────────┐ │ 序列化规则 │ 要点 │ ├────────────────┼──────────────────────────────────┤ │ 总纲 │ 权限关 类型关,两关都过才行 │ │ 第一关·权限 │ public够格;private[SerializeField]│ │ │ 也够格;static/const/属性不够格 │ │ 第二关·类型 │ 基本类型/Vector/枚举/对象引用/ │ │ │ 数组List/[Serializable]类 → 支持 │ │ │ Dictionary/接口/多维数组 → 不支持 │ │ [SerializeField]│ 修权限关——让private字段够格 │ │[System.Serializable]│ 修类型关——让自定义类可序列化│ │ 隐藏规矩 │ 不存null/无多态/怕循环引用/改名丢值│ │ 一句话 │ 能不能存,有本明明白白的账,非看运气│ └────────────────┴──────────────────────────────────┘小李摸着这张表悟出了序列化规则的题眼我总算把这本’规则账’看明白了——原来数据’能不能被保存’,从来不是看心情、靠运气,而是有一套清清楚楚、明明白白的规则在背后默默运作:权限要够格、类型要支持,两关都过才行;还有那些不显眼的’隐藏条款’,条条都有它的道理!我之前觉得它’时灵时不灵’,不是它真的随机,而是我没把规则摸全**——只盯着一关,漏了另一关,自然处处意外!**原来,世上那些让你觉得’时好时坏、捉摸不定’的事,十有八九不是真的无常,而是你还没看清它背后那本’明明白白的账’——把规则摸全了,意外就变成了意料之中!尾声一本明明白白的规则账亦是人生的智慧小李这场对Unity 序列化规则的钻研从时灵时不灵、像看心情靠运气的困惑出发看清了权限关类型关的总纲、辨清了两个标签的分工、记下了那些隐藏条款——终于把一团乱麻般的玄学理成了一本条理分明的明白账。但当我们合上书会发现这本明明白白的规则账背后竟也舒展着几分耐人寻味的人生哲理。第一你以为的时灵时不灵、捉摸不定往往只是规则还没摸全。小李最大的转变是从觉得序列化看心情、靠运气到看清它背后有一套清清楚楚、明明白白的规则——所谓的无常不过是规则还没摸全的错觉罢了。这何尝不是一记对人生的深刻点拨我们生活里有太多事初看上去时好时坏、捉摸不定、全凭运气——为什么这次成了、下次又砸了?为什么同样的努力,结果却天差地别?于是我们焦虑、迷信、归因于命。可序列化规则告诉我们:绝大多数看似随机的事,背后都藏着一套你尚未看清的规律;你觉得它无常,往往只是因为你只看到了一关,漏掉了另一关。与其把捉摸不定归咎于运气不如沉下心去探究——它成功时同时满足了哪些条件?失败时又是哪一关没过?当你把背后的规律一条条摸全,曾经的玄学,就变成了意料之中。真正的高手从不信运气,只信规律——因为他们懂得,把规则摸透了,命运就大半握在了自己手里。第二做成一件事常常要多个条件同时满足缺一不可。序列化的总纲是——权限关、类型关两关都过才行任一关没过全盘皆输。这道破了一个朴素却极易被忽视的真理世上许多事的成功从来不是单一因素决定的而是多个条件同时满足的结果——它是一道与门(AND)“而非或门(OR)”。我们常常犯一个错把全部心力押在某一个条件上,以为只要把这一点做到极致,就一定能成——结果某一关确实做得无可挑剔,却因为忽略了另一关,功亏一篑。一个产品光有好技术不够还得有市场、有时机一个人成事光有才华不够还得有勤勉、有机遇、有德行。真正成熟的做事,是先看清这件事到底需要同时满足哪几个条件,然后一关一关都不落下地去打通——而不是偏执地死磕一点、却对其他关卡视而不见。成事如过关关关都得过木桶能装多少水,从来取决于最短的那块板。第三把隐藏的规矩提前摸清才不会在关键处被暗中坑掉。老师傅特意叮嘱小李那几条隐藏条款——改名会丢数据、不支持多态、怕循环引用……这些平时不显眼,却会在项目做大后暗中坑你于无形。这道破了一个老练者才懂的智慧真正让人栽大跟头的,往往不是那些显而易见的难关,而是那些藏在角落、平时不起眼、却在关键时刻突然发难的隐藏规矩。没经验的人只看得见摆在明面上的规则,对那些潜规则、暗条款、容易被忽略的细节浑然不觉,于是总在意想不到的地方翻车。而真正老练的人会主动去打探、去搞清那些没人明说、却真实存在的隐藏规矩——把暗处的坑提前标记出来,绕过去,才能走得稳、走得远。凡事预则立多花一分心思去摸清那些隐藏的规矩,就少一分在关键处被暗中坑掉的风险——这份看见暗处的清醒,正是经验最宝贵的馈赠。下次当你把某件事的成败归咎于运气或偏执地死磕单一条件又或在不起眼的地方栽了莫名其妙的跟头时请记得这本规则账的智慧——像看透序列化规则那样相信捉摸不定的背后藏着尚未摸全的规律沉下心去把它一条条看清像那两关都过的总纲那样懂得成事需要多个条件同时满足一关一关都不落下地去打通更像那几条隐藏条款那样提前去摸清那些不起眼却要命的暗规矩别在关键处被暗中坑掉。于是曾经让你觉得全凭运气的事渐渐都变成了尽在掌握的明白——你不再被无常裹挟而成了那个看清规律、稳稳过关的人。“Unity 的序列化规则”就是这门关于无常背后有规律、成事需诸缘俱足、暗处规矩要提前看清的、朴素而深刻的智慧。它告诉我们你以为的捉摸不定往往只是规则没摸全做成一件事常需多个条件同时满足把隐藏的规矩提前摸清才不会在关键处被暗中坑掉。它像一句朴素的箴言提醒着我们——别把捉摸不定归咎于运气沉下心去摸清背后那本明明白白的账玄学便成了意料之中别偏执地死磕单一条件看清成事需要同时满足的诸般关卡一关一关都打通别只盯着明面上的规则提前摸清那些不起眼却要命的隐藏条款方能走得稳、走得远——一个懂得看清规律、诸缘俱足、洞悉暗处的人才能像那本明明白白的规则账纵使面对再纷繁、再像全凭运气的世事也总能拨开无常的迷雾看清背后的规律打通每一道关卡标记每一处暗坑于是捉摸不定者尽在掌握诸缘俱足者水到渠成暗处明察者步步为安活成一个不信运气、只信规律、把命运稳稳握在自己手里的清醒之人。这就是藏在Unity 的序列化规则背后那本明明白白的账最深、也最美的浪漫。