Unity背包系统笔记

张开发
2026/4/10 16:31:51 15 分钟阅读

分享文章

Unity背包系统笔记
一个物品在游戏里需要几个类配置数据类详细数据玩家数据类玩家拥有的这个物品的数据剩余耐久度、数量格子类只显示图标和边角的一些信息但点击需要显示各类物品的详情所以需要记录物品类型详情页类不同类物品详情页版式可能不一样也可能一样1、2使用继承而3各类物品的格子看起来基本一样再继承会造成类爆炸所以不如使用一个格子类用type枚举标记物品类型。4的种类通常少于物品类也适合用type。功能需求查看玩家拥有的所有物品可以放下其中的任意物品查看已死的他人背包的物品可以拿起其中的任意物品查看附近的物品可以拿起其中的任意物品拿起物品时如果已达到背包容量上限无法放进背包并弹出提示背包里的物品数据要能json序列化能带出场景背包里能放不同类的物品弹匣、医疗包、手榴弹等对于可以使用的物品可以选择使用数据结构设计玩家从场景拿起一个物品放进背包时需要把该物品销毁物品的数据要记入背包脚本这个数据不在场景里存在不能继承Monobehaviour。从背包放下物品时需要把物品实例化能挂在场景里物体上的脚本一定继承Monobehaviour。所以一种物品一个有继承MonoBehavior和不继承MonoBehavior的脚本分别对应场景里和背包里的情况放入和拿出背包时要在两种脚本之间转换。物品的详细信息记录在配表里物品实例记录一个能找到自己详细信息的id和这个物品实例独立的数据一个弹匣里剩余的子弹数。因为每类物品的详情字段不同所以每类物品一个配表。详情页对于背包外物品可用操作都是拾取对背包内物品都可以丢弃有的能使用有的不能使用。有效物品种类可以共用详情页有些不能。背包都面临的一个问题是物品有不同类的不同类的物品有共同属性如名字、重量、图标、预制体每类物品又有独有的属性如医疗包的治疗值、手榴弹的伤害。对于背包不同类物品1.用一个列表放还是每类一个列表2.格子除了按物品类分还分为玩家背包的格子、附近物品格子、别人背包格子。用一种处理所有类物品还是制作多种格子我看了一下地铁逃生的仓库面板物品出现的顺序是按分类出现的说明用户数据文件就是每类物品用了一个列表存储配表也是每类物品一个配表。虽然这个面板上用看起来一样的格子但它们链接的数据所在的列表不同。显示面板时也确实把仓库里所有类物品的列表都读了一遍。我突然发现这里两个三级头也各占了一个格子也就是说这些物品右下角的数字永远是1这个数字也就没有用和平精英的背包面板。无论捡起东西的顺序怎么样物品在这个列表里是按分类排列的而且同一类里显示的顺序是定死的。显示背包物品的时候可能是按配表的顺序读取物品的。配表设计使用ScriptableObject做配表ScriptableObject可记录Assets里各种类型的资源如GameObject、Sprite、AudioClip无需记录预制体路径然后Resources.Load()。需要一个资源管理器得到这个Asset。//以预制体为中心通过预制体路径找到预制体再通过它身上脚本的字段找到ScriptableObject里的详细信息 class ItemData:ScriptableObject{//记录物品详细信息的 public ListItemDetail() itemsDetail; } class ItemDetail{ int id; string name; int load; GameObject prefab; Sprte icon; } class ItemInPack{//放在背包里的 int id; } //因为背包里可能装各种类型的物品所以用总的类ItemInPack装 class MagInPack:ItemInPack{//放在背包里的弹匣 int roundNum; } class Item:MonoBehaviour{//挂在物品上的 int id; } class Mag:Item{ int roundNum; }使用Json做配表ItemInPack通过一个id从json反序列化结构获得详细信息。//以json反序列化的结构体为中心通过id查找详细信息 public class ItemData{ public int id; public string name; public string prefabPath; public string iconPath; public int load1; } class ItemInPack{ int id; } class MagInPack:ItemInPack{ int roundNum; } 挂在物品上的class Item:MonoBehaviour{ int id; } class Mag:Item{ int roundNum; }父类装子类遇到json持久化的问题弹匣、医疗包、饮料、手榴弹属于不同类物品一开始我试图用它们的基类Item列表记录它们列表元素为不同子类用LitJson序列化成Json文件也没问题。问题在于把这个Json文件反序列化时LitJson不会把它们识别为对应的子类只会读出来一个全是Item的列表子类多出的信息全部丢失。除非自己写读Json的程序。然后这个设计就放弃了。要持久化的列表必须每个元素结构相同。[Serializable] public class ItemsInPack{ public ListMagInPack magsInPack; public ListMedicInPack medicsInPack; public ItemsInPack(){ magsInPack new ListMagInPack(); medicsInPacknew ListMedicInPack(); } }附近物品列表及其维护背包脚本声明了一个物品基类列表记录周围可拾取的物品Physics.OverlapSphere()检测附近物品打开背包界面时用Physics.OverlapSphere()得到一个球形区域的碰撞体筛选出可拾取的、没有主人的物品加入附近物品列表。加之前要先把附近物品列表清空。public float pickUpRange2; public LayerMask packCheckLayerMask; public void CheckItemsAround(){ itemsAround.Clear(); packsAround.Clear(); Collider[] thingsAroundPhysics.OverlapSphere(transform.position,pickUpRange,packCheckLayerMask);//检测周围球形区域里的物品 Item item; Weapon gun; MyCharacter other; for(int i0;ithingsAround.Length;i){ if(thingsAround[i].TryGetComponent(out item)){//检测到是物品 if(item is Weapon){ gunitem as Weapon; if(gun!myCharacter.rifleScriptgun!myCharacter.pistolScriptgun.ownernull){//不是人物自己的枪枪没有主人 itemsAround.Add(item); } } } else if(thingsAround[i].TryGetComponent(out other)){//检测到是人 if(other.life0other.backpack){//人已经死了人有背包 for(int j0;jother.backpack.magsInPack.Count;j){ packsAround.Add(other.backpack); } } } } }类的继承关系物品格子可以按在背包里、在别人背包、在场景里分为3类也可以按物品种类分然后就形成了一个“类矩阵”脚本数量会比较恐怖。实用经验 87 切记继承过度滥用_过度继承-CSDN博客首先提取所有物品格子的最大共性只有图标和名字。因为同一类物品3个地方的格子有相同的详细信息所以适合继承一个基类。写到这里发现在其他游戏里不同类物品一般都在背包的不同页签显示吃鸡背包存数据也必须按类型分开存但是混在一起显示对程序员不太友好但是对玩家友好。需要做的测试1.点开背包界面显示的物品和人物拥有的一致2.走近其他物品点开背包界面其他物品出现在附近物品栏3.点拥有的枪或物品把它放下此物品出现在附近物品栏如果是弹匣这样在背包里没有实体的物品附近应该出现它的实体4.捡起身上没有的物品此物品从附近物品栏消失加入拥有的物品如果是弹匣此物品在场景里的实体消失5.和附近的枪交换6.打开背包界面查看附近的物品按F拾取再打开背包界面该物品从附近物品栏消失7.打开背包界面查看附近的物品按F放下枪再打开背包界面该物品出现在附近物品栏交互系统在交互检测的拾取物品的部分增加对医疗物品类型的判断通过场景里的医疗物品身上的脚本记录的id去物品管理器查询这种医疗物品的名字显示在交互选项里。执行拾取时增加对医疗物品类型的判断在背包的医疗物品列表增加此物品的数据。手榴弹和背包的关系根据设计玩家可以在背包界面点手榴弹然后点使用来装备手榴弹。然后这个手榴弹还应该在背包里吗如果还在背包里再次点击要么不显示使用要么点击使用无效。如果不显示使用那么手榴弹格子显示详情页的代码就要加判断而且不显示使用会让玩家迷惑。如果点击使用无效则是在点击使用的回调里判断这个格子指向的数据对象和正在使用的手榴弹数据对象如果是同一个就return。如果不在背包里使用手榴弹的时候要把这份数据移出背包收回手榴弹时要放回背包。如果此时背包界面开着要刷新界面。不同类物品拥有不同属性时的设计继承vs全包含背包系统想支持弹匣、医疗物品、手榴弹等几类物品。基于不同类物品分配表、用户数据分列表存储的设计架构每类物品需要准备的数据类有配表元素配表ScriptableObject预制体类用户背包、仓库数据类ui类有背包内物品脚本场景物品脚本他人背包物品脚本仓库界面的背包物品脚本仓库界面的仓库物品脚本。需要增加的数据结构有配表背包里的物品列表仓库里的物品列表配表管理器里这类物品的配表字典需要增加的代码有配表管理器根据id得到详细信息的方法拾取物品时对相应类型的判定把这类物品放进背包的方法从背包取出这类物品的方法在显示附近物品方法里增加显示这类物品的代码在显示背包内物品方法里增加显示这类物品的代码在显示附近其他背包内物品方法里增加显示这类物品的代码想到原神的背包界面按类型分成很多栏肉眼可见工作量很大而吃鸡虽然只有一个界面但不同类物品的字段不同点击行为不同也不可能用同一个脚本处理。二者的程序工作量都很大只是有没有用UI表现出来。反思为什么这套架构这么恶心因为支持一类物品的背包系统需要的数据、代码量已经很大又把不同类物品作为不同类形成一个“子类矩阵”。因为不同类物品详细信息的字段不同配表无法共用。同理配表管理器也不行。配表部分是不能不同类物品共用的只能期望背包和UI部分共用。然后背包因为弹匣每个实例有独特的数据必须单独用一个列表。那么其他每个实例都相同的物品在背包里就可以共用public class ItemInPack{ public int type; public int id; public int num; }的类存。接下来修改放入背包的方法输入一个ItemInPack现在查询配表数据需要type和id两个信息需要给配表和type定一个规则写一个用type和id得到详细信息的方法。然后发现这个方法查到的物品可能是不同类型那就返回它们的共同基类。然后放入背包就要改成搜索type、id都相同的元素num1没有则创建这个元素。拿出背包格子脚本有一个字段指向ItemInPack列表里的元素直接把这个元素的num-1减到0显示背包物品各类物品的共性是图标、名称可能还有描述。但是详情页就根据不同类物品有不同字段了可采取的行动也不同。下面是逆境重生不同类物品的详情页几种详情页很明显是不同的预制体。现在我们只能期望格子分成两种一个物品占一格的和一种物品占一格的。一种格子不同类物品的格子共用同一个脚本。不同类物品的格子能打开详情页这些详情页不同那么共用的格子脚本就太可能存详细信息了除非它把所有类物品的详细信息类都存一份。那么它应该是根据type和id去相应的配表查得详细信息。预制体上挂载的脚本也可以共用一个。因为预制体的作用就是让玩家捡起玩家能识别这是哪种物品还不涉及使用。搞清了设计后尝试把之前写的医疗包和手榴弹也改成使用这个架构。需要改的部分有背包数据结构删掉医疗包、手榴弹列表人物的拾取方法使用放入共用列表的方法预制体挂的脚本使用共用脚本记录type和id的如上面所说不同类物品的详情页和可执行操作不一样格子脚本里势必要有一个switch判定点击格子后的行为。然后我们不如把各类物品的详情页和操作按钮放在一起做成一个页面像上面的图一样。然后因为详情页有了按钮不能再鼠标进入时显示离开时消失了否则按钮会点不到背包格子类精细分类还是全包含用同一个类就需要有玩家背包物品、场景物品、他人背包物品需要的字段玩家背包物品列表的索引、场景物品变量、玩家背包物品列表的索引会有一些字段、代码多余需要有一个type变量记录这个格子的来源类型。实例化格子时。写入相应字段。用不同子类不会有多余代码但是格子按物品来源分自己背包、场景、他人背包还能按物品类分脚本和类数量会巨多每种子类预制体都要有变量记录都要拖一次预制体。写了这么久背包还是感觉到格子用一个大而全的类用一个type记录类型比较好用。这里看似先进的继承反而造成很多细碎的脚本、同样多的格子预制体、同样多的记录预制体的字段。这是只分两种格子弹匣和其他物品时显示详情页的代码用is判断弹匣和其他物品两层switch先判断格子来源对于背包内其他物品再根据是否有使用按钮加载相应的详情页。背包要处理的情况就是这么多要么用这种分支判断巨长的设计要么用子类、脚本、预制、预制体字段巨多的设计。相比之下后者更恶心一点。public void ShowDetail(){ HideDetail(); if(itemUISelected is Cell_Mag){ Cell_Mag cellitemUISelected as Cell_Mag; switch(cell.type){ case CellType.PlayerPack: detailPanelInstantiate(detail_Mag_In,detailPageAnchor); Detail_Mag_In detail_IndetailPanel as Detail_Mag_In; detail_In.textNum.textcell.ammoNum.ToString(); detail_In.buttonDrop.onClick.AddListener(cell.Drop); break; case CellType.Scene: detailPanelInstantiate(detail_Mag_Out,detailPageAnchor); Detail_Mag_Out detail_OutdetailPanel as Detail_Mag_Out; detail_Out.textNum.textcell.ammoNum.ToString(); detail_Out.buttonPick.onClick.AddListener(cell.PickFromScene); break; case CellType.OtherPack: detailPanelInstantiate(detail_Mag_Out,detailPageAnchor); detail_OutdetailPanel as Detail_Mag_Out; detail_Out.textNum.textcell.ammoNum.ToString(); detail_Out.buttonPick.onClick.AddListener(cell.PickFromOtherPack); break; } detailPanel.textName.textcell.magDataBin.itemName; detailPanel.textLoad.textcell.magDataBin.load.ToString(); } else if(itemUISelected is Cell_Item){ Cell_Item cell itemUISelected as Cell_Item; switch(cell.type){//格子来源 case CellType.PlayerPack: ItemDataBin dataItemDataManager.Instance.GetOtherItemFromSO (cell.otherItemInPack.type,cell.otherItemInPack.id); switch(cell.otherItemInPack.type){ case ItemDataManager.medicine: detailPanelInstantiate(detail_Item_Useful,detailPageAnchor); Detail_Item_Useful detail_IndetailPanel as Detail_Item_Useful; MedicineDataBin medicineDatadata as MedicineDataBin; detail_In.textName.textmedicineData.itemName; detail_In.textDesc.textstring.Concat(回复,medicineData.healNum,生命值); detail_In.textLoad.textmedicineData.load.ToString(); detail_In.buttonDrop.onClick.AddListener(cell.Drop); detail_In.buttonUse.onClick.AddListener((){ cell.UseMedicine(); HideDetail(); }); break; case ItemDataManager.grenade: detailPanelInstantiate(detail_Item_Useful,detailPageAnchor); detail_In detailPanel as Detail_Item_Useful; GrenadeDataBin grenadeDatadata as GrenadeDataBin; detail_In.textName.textgrenadeData.itemName; detail_In.textLoad.textgrenadeData.load.ToString(); detail_In.textDesc.textgrenadeData.desc; detail_In.buttonDrop.onClick.AddListener(cell.Drop); detail_In.buttonUse.onClick.AddListener((){ if(MyInput.Instance.player.grenadeInHandData!cell.otherItemInPack){ MyInput.Instance.player.UseGrenade(); } HideDetail(); }); break; case ItemDataManager.other: detailPanelInstantiate(detail_Item_Useless,detailPageAnchor); Detail_Item_Useless detail2detailPanel as Detail_Item_Useless; detail2.textName.textdata.itemName; detail2.textLoad.textdata.load.ToString(); detail2.textDesc.textdata.desc; detail2.buttonDrop.onClick.AddListener(cell.Drop); break; } break; //背包外物品格子没有使不使用只有拾取。也就不分能使用的药品、手榴弹和不能使用的其他 case CellType.Scene: detailPanelInstantiate(detail_Item_Out,detailPageAnchor); Detail_Item_Out detaildetailPanel as Detail_Item_Out; dataItemDataManager.Instance.GetOtherItemFromSO (cell.itemInstance.type,cell.itemInstance.id); detail.textName.textdata.itemName; detail.textLoad.textdata.load.ToString(); detail.textDesc.textdata.desc; detail.buttonPick.onClick.AddListener(cell.PickFromScene); break; case CellType.OtherPack: detailPanelInstantiate(detail_Item_Out,detailPageAnchor); detaildetailPanel as Detail_Item_Out; dataItemDataManager.Instance.GetOtherItemFromSO (cell.otherItemInPack.type,cell.otherItemInPack.id); detail.textName.textdata.itemName; detail.textLoad.textdata.load.ToString(); detail.textDesc.textdata.desc; detail.buttonPick.onClick.AddListener(cell.PickFromOtherPack); break; } detailPanel.transform.positionitemUISelected.transform.position; } }详情页预制体可选操作不同没法大而全不同类物品可选操作按钮的数量、功能、名称不同无法共用一个面板。详情页预制体显示控件精细分类还是大而全详情页的文本显示控件可以根据物品类型拥有的字段定制或是只包含名称和描述甚至只用一个Text各类物品不同的信息药品的治疗量、弹匣容量都通过在代码里给描述写“治疗量”xxx这样硬编码写。前一种做法同样会导致详情页预制体泛滥后一种则可以大大减少详情页预制体。详情页脚本精细分类还是大而全详情页预制体已经必然每类物品一个详情页脚本如果大而全脚本和子类就少一点坏处如果一些字段留空不算坏处就没有坏处了。改进物品类包含所有类物品的属性但是特定类的属性在成员对象里可以是空的可以让物品类有所有类物品的属性但是把比如武器的属性放在一个类药品属性一个类物品拥有武器属性类字段但如果不是武器则武器属性字段是空。但是我们知道Unity里如果类序列化到检查器了里面的空成员对象也会被实例化一个包含默认值的对象不能用一类属性成员是否为空判断物品类型还是要用种类枚举。也就是如果想在检查器方便查看背包里的物品就不能指望通过把一些类成员设空来省内存还是会退化成全包含设计。经过反复折腾、查看一些项目源码我觉得全包含设计还是比继承多类设计好一些。武器待拾取的预制体和人物手里的预制体把武器分为待拾取的预制体和人物手里的预制体。待拾取的预制体有触发器、记录详情的脚本可以被人物检测到、拾取。没有射击相关功能如发射子弹、播放枪声、播放枪口火焰、抛壳等。人物手里的预制体有射击的脚本声源、animator。虽然之前一直用一种预制体也没什么问题但考虑到枪的脚本里有Update()在一直检测射击输入还有一大堆射击相关参数枪在地上放着时都用不到用两种预制体应该能明显改善性能。然后需要考虑两种预制体衔接的问题。拾取枪时销毁待拾取预制体在人身上实例化人物手里的预制体。背包类的设计是做成独立的类还是作为人物的一部分是否继承MonoBehaviour一开始我做成继承MonoBehavior和人物分开的类每个人物和背包都要相互引用后来觉得每个人物都必有背包就把背包代码作为人物的partial class。再后来为了优化性能想做人物死后成盒要把人物销毁背包数据转移到盒里又要背包人物分开。这时候想到如果做成不继承MonoBehavior的类人物和盒加一个背包字段死时直接把这个背包实例赋值给盒最方便。总结起来如果除了人物还想做弹药箱那么应该把背包和人物分离继承MonoBehavior人物要多挂一个脚本且把背包数据转移到另一个对象很麻烦而且又看了一眼继承MonoBehavior的背包发现它没有用任何生命周期函数、触发检测函数、协程完全没有必要继承MonoBehavior。弹药箱和背包的区别背包不能装枪弹药箱可以背包有主人弹药箱没有总结玩家通过UI界面操作改变脚本里的数据时为了保持脚本里和界面上的数据一致UI界面的数据刷新时必须总是从脚本读取而不能直接修改UI界面比如从场景拿一个弹匣到背包里如果用代码把附近物品栏的这个弹匣删掉再在背包物品栏加上这个弹匣实际上进行了脚本数据和UI界面的两线操作造成了两边数据不一致的可能性。不要试图用一个脚本处理很多种情况比如玩家背包里的弹匣、场景里的弹匣、别人背包里的弹匣共用一个脚本再在里面做不同处理会很难读。然后我发现让一个预制体的脚本里一个字段指向预制体它自己预制体实例化后这个字段指向的也是场景里的实例它自己了。不能让一个预制体脚本里的字段指向它自己来记录预制体。关于格子和相似面板脚本的继承问题带来的教训是在背包系统里会有大量样子相似或一模一样功能相似但是又有细微差别的面板、格子这时候很容易陷入一个误区就是面板、格子有一点不同就继承结果多了大量脚本这些面板、格子还需要预制体文件又多了一倍。反思继承的设计初衷应该还是在类的成员、行为有较大不同时才适合继承。唐老狮的背包系统教程总结总结几个要点不同类物品使用同一个配表用type记录类型有所有物品可能的属性允许有些属性对有些类物品无效数据管理器用以id为键的字典存配表有一个把列表转换成字典的步骤。避免搜索列表的操作玩家拥有的物品按类型分成几个列表存不同类物品格子共用一个脚本用枚举标记类型避免过度继承尽量用同一个类支持不同类物品使用type枚举标记和我走过弯路后得到的结论一样。正在使用的武器是放在拥有武器列表里用index记录还是不放在拥有武器列表专门用一个字段对于原神那样使用中的武器不可能损失的游戏毫无疑问用index记录使用的武器修改使用的武器只用改一个index很方便暗区突围那样带入武器可能损失的游戏毫无疑问入局时使用的武器已经离开仓库了使用的武器应该不属于仓库。这种设计修改使用的武器时如果原来有武器把原来使用的武器放进武器列表把选择的武器拿出武器列表。比较简单的写法是用一个中间变量把原来使用的武器和新武器交换。但是界面上这2把枪显示的顺序就会交换一下体验会差一点。如果原来没有武器把选择的武器移出列表然后我们明白原神和暗区突围的武器选择机制和它们的数据结构、游戏机制是适配的。deepseek问答游戏物品系统不同类物品的数据类是用type标记种类还是用继承在游戏物品系统的设计中使用继承Inheritance是更优于使用Type标记Tagging的方案。这两种范式代表了面向对象编程OOP与数据导向设计DOD的核心思想碰撞。以下是深度分析和最佳实践---一、核心对比继承 vs Type标记特性 继承方案 Type标记方案 胜出方数据组织 类型安全结构清晰 所有数据挤在同一类需switch判断 ✅ 继承内存效率 可能内存碎片化 数据连续存储缓存友好 ✅ Type扩展性 需修改继承树 添加新类型只需改枚举和数据表 ✅ Type行为多态 天然支持虚函数 需手动switch或函数指针 ✅ 继承序列化 复杂需工厂模式 简单可直接反射/序列化 ✅ Type---二、架构示意图1. 继承方案OOP范式· 优点类型安全行为高内聚符合直觉· 缺点物品类型固定后难以动态添加2. Type标记方案DOD范式· 优点灵活性极高适合数据驱动设计· 缺点需大量类型判断容易破坏封装---三、最佳实践混合方案现代游戏主流结合两种优势采用 “数据组件化 行为接口” 设计1. 基础物品结构csharp// 基础物品数据所有物品共有public struct Item {public uint32 GUID;public ItemType Type; // 枚举Weapon, Consumable...public string Name;public IItemBehavior Behavior; // 行为接口}// 数据组件按需附加public struct WeaponData {public int Damage;public int Durability;}public struct ConsumableData {public int HealthRestore;}2. 行为接口化csharp// 行为接口public interface IItemBehavior {void Use(Player user);void OnEquip(Player owner);}// 具体实现public class WeaponBehavior : IItemBehavior {private WeaponData _data;public void Use(Player user) {user.Attack(_data.Damage);_data.Durability--;}}3. 混合方案架构mermaidgraph TDA[Item基础结构] -- B[Type字段]A -- C[IItemBehavior接口]B -- D[获取对应数据组件]C -- E[执行多态行为]D -- F[WeaponData]D -- G[ConsumableData]E -- H[WeaponBehavior]E -- I[ConsumableBehavior]---四、各方案性能对比操作 继承方案 Type标记 混合方案遍历所有物品 ❌ 缓存不友好 ✅ 最优 ✅ 优调用Use() ✅ 虚表调用最快 ❌ switch开销 ✅ 接口调用快内存占用 ❌ 碎片化 ✅ 紧凑 ✅ 较紧凑添加新类型 ❌ 需重新编译 ✅ 热重载支持 ✅ 热重载支持---五、行业应用案例1. 《魔兽世界》继承方案cpp// 暴雪早期采用深继承树class Item {};class Weapon : public Item {};class Sword : public Weapon {};class LegendarySword : public Sword {}; // 问题类爆炸2. 《Minecraft》组件方案java// 现代版采用组件化public class ItemStack {private ItemType type;private ComponentMap components; // NBT数据}3. 《星际战甲》混合方案csharp// 使用组合模式public class Weapon {public WeaponData Data;public IWeaponBehavior[] Behaviors; // 多重行为}---六、选择决策树mermaidgraph TDA[选择物品系统方案] -- B{项目规模};B --|小型项目| C[继承方案br/简单直接];B --|中大型项目| D{是否需要热更新?};D --|是| E[Type标记方案br/数据驱动];D --|否| F[混合方案br/性能与灵活性兼顾];E -- G[推荐: ScriptableObject];F -- H[推荐: ECS架构];---七、最终建议1. 原型阶段直接用继承快速验证Consumable/Weapon基类2. 正式生产转向混合方案数据组件 行为接口3. 大型MMO采用纯数据驱动Type标记 脚本系统黄金法则· 如果物品行为差异大于数据差异 → 优先继承· 如果物品数据差异大于行为差异 → 优先Type标记· 永远不要用switch(item.Type)处理行为逻辑

更多文章