前面我们设计的物品栏和物品引用信息,其中,利用物品引用信息,也即物品条目,我们可以建立物品栏。物品栏不仅包括玩家的背包,还包括了各种箱子、储具、生产设施等一切存储物品条目(而非实体)的实体
然而,物品栏不是物品条目的简单堆砌,它还需要包括:
1.物品顺序信息,对于有些游戏,物品的排列可以是不连续的,如《minecraft》和《饥荒》,这些项目允许不同物品栏中间出现空位;而像《上古卷轴》等游戏的物品栏中是不允许出现空位的,也就数说,每次物品条目发生改变,物品栏内部就需要重新填补空缺;对于一些更复杂的系统,出于UI的美观或者资源管理玩法上的调整,物品可能还会占用不同的二维格子,例如《Kenshi》以及《巫师3》。对于第一种来说对玩家体验要提起重视;第二种当物品栏中条目过多时,由于涉及UI刷新、数组长度变更等可能影响性能的操作会比较需要考虑;并且这类游戏往往只针对物品的重量,而不是物品所占空间进行一维的限制,容易造成玩家出现“屯屯鼠综合征”;第三种则可能会造成UI杂乱、存储算法复杂,以及需要使用动态规划算法进行物品栏整理。这里我们着重考虑第二种
2.特殊的物品位置:例如,玩家装备的物品;生产设备的输入以及输出,这些位置只能存放个别物品,对于物品的存储和取出也有要求。《上古卷轴》等游戏中,“装备物品”这一动作是改变了特定的物品ID,例如装备了第9号槽位的斧头,换成装备第3号位的剑;这一装备过程中,物品的排列信息没有改变;有些例如《巫师3》,它的装备操作是将物品栏中的物品与特定槽位的物品进行了交换;有些是不同场合使用不同方式,例如《minecraft》中的工具和盔甲,还有些是一种“先选,后换”的混合式物品栏,例如《艾尔登法环》中的武器魔法的选择
2.1.快捷栏:游戏过程中,为了快速地使用药水等物品,我们会需要把物品放进一些比较显眼的、数量相对有限的栏位中;在有些游戏中,这些栏位中的物品不能随意更换(如黑魂中的魔法)。快捷栏的实现接近一般武器装备的实现
3.物品的综合信息:物品的总重量,占用空间等限制会改变玩家对道具取舍的策略;当涉及的物品项目过多时,如果我们能事先获知某些(类,个)物品存在与否、数量多少(例如剩余食物的量,是否存在远程武器等),并动态地修改和提供这些数据,不仅可以给玩家形成便利,也可以给AI管理的过程中提高性能
4.物品整理算法:将物品按照一定原则,例如无空位、价格优先等原则,整理成新的物品栏顺序;对于某些游戏,这些算法应当是在获得或损失物品的同时自动运行的。
5.物品获得、损失时的以及例外情况:当获得物品时,如何添加物品,是否需要重新排列;当获得物品时,背包空间不够时,如何处理;如何从背包中移除物品;如何检测物品是否损耗完毕;当物品栏发生改变时,如何通知AI重新装备物品;这些都是我们需要考虑的内容。
public class BasicStorage:monobehevior
{
//之所以继承了mono,是因为如果考虑到实现物品腐烂等功能实现时,需要mono来实现计时
public visual ItemSlot[] AllItem{get;}//所有物品
public visual ItemSlot[] Items;//普通的物品;
public int itemCount = 0//物品的数量
//一些可选的功能
//public float rotRate//腐烂速率,用于冰箱等效果
......
public void TryRemoveItem(int id)
{
//失去物品
//此物品栏设计不允许出现空位,在每个物品进入和移除时进行判断和重排
//这里只需要将移除槽位的物品依次
items[id] = null;
for(int i=id;i<itemCount;i++)
{
items[id].itemRef == items[id + 1].itemRef;//上提
}
items[id + 1] = null;//将最后一个物品栏清除
itemCount -=1;
}
public bool TryAddItem(ItemRef itemRef)
{
bool canAdd = itemSlot[slotID].IsAllowedItem(itemRef);
if(!canAdd)
{
Debug.logError("Unexcepted item kind");
return false;//当对应槽位不允许对应物品时,返回false
//对物品种类的限制更应该放在UI/AI相关脚本中分别完成,这里仅仅是作为一个备用功能
}
else
{
if(itemCount<items.length)
{
items[itemCount].itemRef = itemRef;
}
}
}
public void SwampItem(int slotID, itemRef item2)
{
//交换物品
}
public void SwampItem(int slotID1, int slotID2)
{
//一个用于交换通用槽位物品的重载
}
}
public class ItemSlot
{
//需要一个物品槽位类来限制存取
public itemRef itemref;//此槽位存放的物品
public class ItemDef {get{return itemRef == null?null:itemRef.itemDef}};
public bool allowPutIn;//是否限制放入
public bool allowTakeOut;//拿出
public itemClass[] allowItemKind;//限制存储类型
public int[] allowItemID;//限制存储物品
public bool IsAllowedItem()//判断是否允许某物品
{
....
}
}
//箱子
public class Box : BasicStorage
{
public int slotAmount = 16;
public ItemKind itemKindAllowed;//可以装入的物品;
public overwrite ItemSlot{get{return Items}};
void Awake
{
//初始化Items栏位,生成slotAmount长度、限制装入itemKindAllowed类物品的ItemSlot数组
}
}
//角色
public class CharacterInventory : BasicStorage
{
public itemSlot[] quickSlots;
public itemSlot weaponSlots;
public itemSlot ArmorSlots;
public override AllItems{ get {//将角色的装备与背包物品合并并重新排序}}
void Awake()
{
//初始化Items栏位,生成slotAmount长度、限制装入itemKindAllowed类物品的ItemSlot数组
//初始化装备槽位
}
}
实际的代码比上述复杂,例如,完整的characterInventory类涉及了很多角色属性管理的内容;使用update()函数和Time.deltatime动态更新物品耐久度的功能也没有体现。上述代码仅仅提供了框架和示意的作用。