Store And Equipment System In Unity3D
Introduction
物品元素可以说是所有RPG的必备系统,毕竟,如果没有物品系统就没有消耗,没有消耗哪能骗玩家充钱呢?最简单的物品系统应该是所谓的商店、背包加装备的结构,而这个Demo则主要基于UGUI实现一个简单的上述系统
UI
制作系统的第一步便是UI的绘制了。无论美术资源来自何方,画风如何,应该基本包括如下几个层面:
- 角色信息界面,用于显示主角的装备槽位以及正在穿戴中的装备
- 背包界面,用于显示主角拥有的物品集合
- 金钱标签,用于显示主角当前拥有的钱币
- 商店界面,用于显示当前售卖的物品集合
- (可选)快速物品栏,通常与技能栏合用,让玩家能够快速使用物品
在我的Demo中也基本实现了上述模块
Bag System
一个背包系统的组成很简单,即面板+很多的格子,再贴一个金钱的文本框和几个图标就能够完成,我的背包也是基于上述想法来实现的:
- 通过Unity3D的Scroll View作为背包系统的主面板
- 创建Grid对象,并设置其Image为相应的Spirit作为物品图标
- 实现物品拖动逻辑,主要是在C#脚本实现动画表现,Lua更新数据,即注册onDrag事件,当鼠标拖动一个Grid时,创建一个当前Grid的副本,并在onDraging中不断获取当前鼠标所在的屏幕坐标赋值给副本的Transform,最后进行鼠标弹起位置的判断是否放在有效的区域,若位置合法则移动物品,并将数据写入Lua类,同步到服务器
// copy current grid
public static InventoryUIItemWrapperBase CreateDragObject(InventoryUIItemWrapperBase from)
{
var copy = GameObject.Instantiate<InventoryUIItemWrapperBase>(from);
copy.index = from.index;
copy.itemCollection = from.itemCollection;
copy.item = from.item;
var copyComp = copy.GetComponent<RectTransform>();
copyComp.SetParent(InventorySettingsManager.instance.guiRoot.transform);
copyComp.transform.localPosition = new Vector3(copyComp.transform.localPosition.x, copyComp.transform.localPosition.y, 0.0f);
copyComp.sizeDelta = from.GetComponent<RectTransform>().sizeDelta;
group.blocksRaycasts = false;
group.interactable = false;
return copy;
}
//draging event
public static void OnDrag(PointerEventData eventData)
{
if (currentDragHandler != null)
{
currentDragHandler.OnDrag(eventData);
if (OnDragging != null) OnDragging(currentDragHandler.dragLookup, currentDragHandler.currentlyDragging, eventData);
}
}
//finish drag
public static InventoryUIDragLookup OnEndDrag(PointerEventData eventData)
{
if (currentDragHandler == null)
{
return null;
}
var lookup = currentDragHandler.OnEndDrag(InventoryUIUtility.currentlyHoveringWrapper, eventData);
if (OnEndDragging != null)
{
OnEndDragging(lookup, currentDragHandler.currentlyDragging, eventData);
AppFacade.instance.CallLua("pg.global.bag.moveItem", curItemId, eventData.getTargetGrid());
}
return lookup;
}
- 在初步实现中发现缺少了一个考虑的点,即拖拽到相同的物品应该合并,因此应当加入判断
- 此外,能合并物品理应能拆分,于是加入左Shift键的监听事件,物品的拆分逻辑可重用拖动逻辑,即直接调用DragItem(curItemId, targetGrid)将物品移入下一格,并创建一个相应的克隆对象,改变其下标的数量。当然,当背包满时直接Return即可
- 在Lua中应当调用服务器方法将数据写入数据库以保证数据的持久化
function onEndMoveItem(curItemId, grid)
self.server.MoveItem(curItemId, grid)
end
- 最后实现右键使用物品的逻辑,分消耗品和装备来进行不同的逻辑处理
Advandace Bag System
在上述的第一版实现完成后,其实还存在着一些不足:
- 背包的格子很多,一次性加载会造成卡顿
- 没必要每次移动物品后都写入数据库,可以在服务器内存里保留一个Map映射,当玩家下线后再根据此映射将整个背包的数据写入
对于第一个问题,我采用类型WOW里的背包模式,即增加一个仅能装备背包的装备栏,并加入“背包”这个物品,根据玩家选择的背包动态加载格子而非一次性全部加载。第二个问题则在Bag.xml里新增一个属性ItemBagMap保存物品id与格子id的映射
Store and PlayerInfo
商店界面和个人信息界面和背包类似,只不过在每个Grid里加上了价格标签而已,并且点击物品可以弹出购买界面,这只是UI与逻辑的不同罢了,在这里就不详谈了。出于美观,还可以在个人信息界面的中间做一个人物立绘,通过设定相机渲染到一个Reneder Texture便可以实现了
Pick Up Item
有了物品系统,相应的拾取逻辑也是必不可少的。在我的实现中主要基于碰撞检测的回调函数:
- 人物和掉落在地上的物品具有碰撞检测
- 当产生碰撞时判断背包是否已满,未满创建物品对应的grid对象并销毁地上物品的GameObject
- 物品存入背包成功则写入Lua
public virtual void OnTriggerEnter(Collider col)
{
TryPickup(col.gameObject);
}
public virtual void OnTriggerEnter2D(Collider2D col)
{
TryPickup(col.gameObject);
}
protected virtual void TryPickup(GameObject obj)
{
if (obj.layer == InventorySettingsManager.instance.equipmentLayer)
{
return;
}
if (InventorySettingsManager.instance.itemTriggerOnPlayerCollision || CanPickupGold(obj))
{
var item = obj.GetComponent<ObjectTriggererItem>();
if (item != null)
{
item.Use(this);
}
}
}
protected virtual bool CanPickupGold(GameObject obj)
{
return InventorySettingsManager.instance.alwaysTriggerGoldItemPickupOnPlayerCollision && obj.GetComponent<CurrencyInventoryItem>() != null;
}
protected virtual void PickUpSucc(itemId, gridId)
{
AppFacade.instance.CallLua("pg.global.bag.addItem", curItemId, gridId)
}