New Inventory System Plugin
通过 Item的SphereComponent + LineTrace结合起来 检测物体
原理:先进行SphereOverlap检测所有重叠的Item并存储到OverlappingItems,
然后再进行射线检测,因为射线检测可以穿墙,所以射线检测只检测OverlappingItems里面的物品,
如果检测到则将CurrentItem设置为检测到的值,否则设置为OverlappingItems[0]的值
创建InteractComponent,放置在PlayerController上:

创建USceneComponent,放置在Item上,USceneComponent可以设置SphereComponent:

创建HUD,用于设置中心圆点和PickupMessage:

在ProjectSetting中创建ItemTraceChannel和Item的Preset,用于射线检测:


创建Component:

配置InteractComponent:

将BPC_Inv_ItemComponent添加到Item中:

将Item的StaticMesh的CollisionPreset设置为Item:

将BPC_Inv_InteractComponent添加到PlayerController中:

添加PostProcessVolume并设置Material从而达到高亮效果:

Inv_InteractComponent.h:

Inv_InteractComponent.cpp:




Inv_ItemComponent.h:

Inv_ItemComponent.cpp:

Inv_HUD.h:

Inv_HUD.cpp:

创建SpatialInventory,InventoryGrid,GridSlot三个Widget并通过创建InventoryComponent实现开关库存操作
创建BaseInventory用作父类:

创建子类SpatialInventory并继承BaseInventory:

创建UUSerWidget类的InventoryGrid:

创建UUSerWidget类的GridSlot:

创建InventoryComponent用于切换库存:

InventoryComponent.h:
初始化SpatialInventory并设置ToggleInventory函数用于切换库存

InventoryComponent.cpp:


InventoryGrid.h:
在InventoryGrid中创建具体行数和列数的GridSlot:

InventoryGrid.cpp:

GridSlot.h:
在GridSlot中存储当前的Index:

InteractComponent.cpp:
在InteractComponent中创建ToggleInventory动作并存储InventoryComponent然后调用InventoryComponent中的ToggleInventory:

创建三个Widget:

设置SpatialInventory:

设置InventoryGrid:

设置GridSlot:

设置InventoryComponent:

将InventoryComponent添加到PlayerController上:

创建UObject的抽象类用于存储数据

创建Fast Array Serialize

根据FastArraySerializer的步骤来建立FastArray:

将InventoryItem作为存储数据放在Entry的结构体中
在FastArray结构体中声明Entries用于存储所有Entry,
添加AddEntry和RemoveEntry的函数
添加自带的PostReplicateAdd和PreReplicatedRemove函数,用于在复制新添加/新删除的元素前调用:

在PostReplicateAdd和PreReplicatedRemove函数中调用InventoryComponent的委托:

在InventoryComponent中创建InventoryItemChange和NoRoomInInventory的委托,并添加TryAddItem的函数:

创建TryAddItem函数并调用OnNoRoomInInventory的委托:

在InteractComponent中调用TryAddItem函数:

创建InfoMessage用于显示NoRoom的Message:


创建FadeAnimation:

在蓝图中实现ShowInfoMessage和HideInfoMessage的函数:

将InfoMessage添加到HUD中:

在HUD中初始化委托并设置InfoMessage:


创建SlotAvailability和GridAvailabilityResult来判断库存是否有空间
GridAvailabilityResult用来判断整个库存还能放下多少个该物品(TotalRoomToFill)、是否可堆叠、还有多少放不下(Remainder)并列出具体要放置的槽位SlotAvailabilities
SlotAvailability计算不同的格子要放多少个该物品,指出目标索引、是否已有物品、还能填多少(AmountToFill)
FInv_SlotAvailability 加起来的放置数量就等于GridAvailabilityResult的总的AmountToFill数量

在BaseInventory中创建判断库存中是否有空间:

在SpatialInventory中继承父类函数:

创建ItemManifest描述Item的属性
拾取物 (UInv_ItemComponent) 在蓝图中设置 FInv_ItemManifest
当要把物品放进玩家 UInv_InventoryComponent 时,会通过 FInv_FastArray::AddEntry 新建 UInv_InventoryItem,复制 Manifest 数据 并 注册成可复制子对象
在ItemComponent中创建ItemManifest并在蓝图中设置:

在InventoryItem中创建用来保存 FInv_ItemManifest 的实例,并添加设置该实例的函数,以及获取ItemManifest的函数:

在ItemManifest中创建CreateInventoryItem的函数,创建InventoryItem 并 把当前的ItemManifest数据复制进新对象:

在InventoryComponent中创建FastArray并添加AddRepSubObject函数和Server函数:

将子对象加入复制队列,确保客户端能收到该子对象

完善FastArray中的AddEntry(UInv_ItemComponent ItemComponent)函数,通过ItemComponent中的Manifest中的CreateInventoryItem函数来新建 UInv_InventoryItem,并在该函数中复制 Manifest 数据,并注册成可复制子对象:*

创建FGameplayTag类型的ItemType



在ItemManifest中创建FGameplayTag类型的变量并限制Categories只能为Items:

在ItemComponent中的Manifest中设置ItemType:

创建AddItem函数绑定委托,并让其在客户端和服务器都能被调用
由于FastArray的PostReplicatedAdd和PreReplicatedRemove只会在客户端调用并调用OnInventoryItemAdded委托的Broadcast,所以需要在服务器上单独Broadcast:

在AddEntry之后,只在服务器单独调用Broadcast:

在InventoryComponent开头初始化InventoryList的OwnerComponent为InventoryComponent:

在InventoryGrid中创建用于绑定的委托函数AddItem:


创建ItemFragment用于设置加入网格的物品的属性
添加BaseClass和ChildClass,由于Struct存在继承,所以在BaseClass中设置析构函数
创建FragmentTag用于确定当前的Fragment类型
GridFragment用于设置物品添加到网格的大小是1x1还是2x3
ImageFramgent用于设置物品添加到网格的图标:



在Manifest中创建ItemFragments的实例化数组并排除父类选项:

在Manifest中添加Fragments:

设置HasRoomForItem函数
在Grid中创建执行具体逻辑的HasRoomForItem函数,然后在SpatialInventory中创建具体的Grid并调用该Grid的HasRoomForItem函数:
创建3个重载函数,其中以Manifest为参数的函数作为执行具体逻辑的函数,其他两个函数直接调用Manifest函数:

在AddItem中调用HasRoomForItem函数来获取Result从而获取物品应该放在网格位置的具体信息:

在SpatialInventory中创建具体的Grid:

HasRoomForItem函数调用具体Grid的HasRoomForItem函数:

Add SlottedItem To Canvas
在Manifest中创建模板函数,用于通过Tag来获取具体的Fragment(例如GridFragment,ImageFragment):

创建SlottedItem Widget,用于将物品图标添加到Canvas:



在InventoryGrid中创建SlottedItemClass和用于存储的SlottedItems:

在AddItem函数中调用AddItemToIndices,将物品添加到需要添加的每个索引(堆叠物品分散到不同槽位,则会有多个Availability)
如果是多格物品,只会在左上角索引创建一个SlottedItem
在AddItemAtIndex函数中通过Manifest获取GridFragment和ImageFragment
通过Fragment来设置SlottedItem的位置和图标
调用AddSlottedItemToCanvas将SlottedItem添加到Canvas:

在InventoryGrid中的HasRoomForItem函数中暂时创建Index为0的SlotAvailability用于测试:

在蓝图中创建SlottedItem类的Widget:

在InventoryGrid中设置SlotteItem类:

效果:

添加GridSlot占用Texture
在GridSlot中创建背景图片,并创建GridSlotState的枚举类:

对于不同的State,设置不同的背景Brush:

在AddItemToIndices中创建UpdateGridSlotsState函数用于更新Slot的状态
若为多格物品,则设置每个格子索引的State为占用:

效果:

创建StackCount
创建StackableFragment,用于设置物品的 单格最大堆叠数 和 捡起一个物品的堆叠数:

创建用于StackableFragment的Tags:


在SlottedItem中创建Text用于显示堆叠数:

创建更新堆叠数的函数:

在创建SlottedItem的函数中调用UpdateStackCount函数:

在HasRoomForItem函数中暂时硬编码创建GridAvailabilityResult:

在蓝图中创建Stackable Fragment:

在蓝图中添加Text_StackCount:

效果:

Update GridSlot
由于一个物品可能会占用多个网格,
需要将被占用的网格标记为不可用
并且左上角格子记录堆叠数:
在GridSlot中添加StackCount、UpperLeftIndex、bAvailable:

在UpdateGridSlotsState中设置对应的Slot:

完善HasRoomForItem函数
完善HasRoomForItem的检查:

在HasRoomForItem中先判断是否存在可堆叠的同类物品,如果有,直接填充:

Result中的InventoryItem是用于判断当前库存中是否存在相同类型的物品才会设置这个变量:

在FastArray中添加查找第一个类型匹配的物品的函数:

通过FindByPredicate查找:

在TryAddItem函数中,如果在FastArray中找到相同类型的物品,则设置Result中的InventoryItem,并执行Server_AddStacksToItem,否则执行Server_AddNewItem:

完善AddStackToItem函数
在InventoryItem中创建TotalStackCount用于记录库存中当前Item的堆叠总数:


在ItemComponent中创建DestroyItem函数用于销毁Item:

在InventoryComponent中创建委托,用于当StackChange时,改变库存Widget:

在添加StacksToItem之前调用委托:

更新TotalStackCount,如果有Remainder,则更新剩余物品的StackableFragment,否则销毁Item:

绑定委托,遍历SlotAvailabilities,如果当前格子有物品,则直接更新SlottedItem的StackCount,否则添加新的SlottedItem:

将物品的Replicates设置为True:

设置拖拽、合并
在GridSlot中添加鼠标进入和离开事件,用于鼠标移动到对应格子时,格子会高亮:


在SlottedItem中添加鼠标按下,进入和离开事件,用于拖拽SlottedItem,创建委托并在InventoryGrid中设置委托:

鼠标按下时调用检测拖拽,在拖拽时创建用于视觉效果的SlottedItem:

InventoryGrid中创建用于委托的函数:

拖拽委托被触发时,将bIsDragging设置为true,用于在Tick中实现拖拽:
取消拖拽委托被触发时,将判断是否可以放置或交换:

交换物品:

移除Inventory中的SlottedItem:

在Tick中执行拖拽逻辑,先通过鼠标所在象限和物品尺寸计算物品左上角起始坐标,然后判断是否可以放置或交换:


判断是否可以放置或交换:


通过鼠标所在象限和物品尺寸计算物品左上角起始坐标:

计算鼠标在哪个格子并且计算在当前格子的左半边和上半边:

创建PopUpMenu和SplitMenu
创建PopUpMenu的Widget:

绑定Consume和Drop的函数:

创建Split的Widget:

绑定Split和Cancel的函数:

再SlottedItem中创建RightButtonDown的函数,用于判断是否在SlottedItem上按下右键:


在InventoryComponent中创建DropItem和ConsumeItem的服务器函数,并创建SpawnDroppedItem函数:

在服务器更新Consume和Drop后的库存变化:

在Manifest中声明PickupActorClass用于Drop到世界:

在ItemFragment中创建Consume的Fragment:


创建ConsumableFragment的Tag:


在SpatialInventory中创建CanvasPanel,创建关闭PopUpMenu和SplitMenu的函数:


在Grid中获取Consumable的Tag用于判断物品是否可以Consume,创建PopUpMenu和SplitMenu:


绑定SlottedItem的RightButtonDown,创建PopUpMenu并判断是否是Consumable

按键按下时,调用Server函数,并在本地更新网格占用:

放置物品时,先判断是否可以Split:

如果Shift按下,则可以Split:

Split和Cancel的按钮按下:

将ItemComponent设置为可复制的:

创建PopUpMenu:

创建SplitMenu:

设置Grid:

为可以Consume的物品添加具体的Consumable Fragment ———— Heal Consumable Fragment:

设置ItemComponent为可复制的:

添加ItemDescription
创建ItemDescription Wiget类:


在SlottedItem中创建鼠标进入和离开事件:

在鼠标事件中调用BaseInventory的函数:

在BaseInventory中创建虚函数:

在SpatialInvventory中实现虚函数:

在SpatialInventory中的Canvas中创建ItemDescription,并根据鼠标位置和Canvas的大小来设置Widget的位置,确保Widget不会超过边界:

当Hover时,延迟出现,当UnHover时,隐藏:

完善ItemDescription
复合模式:
1.CompositeBase(复合基类)
·定义所有组件的公共接口
·声明 DoWork() 等操作
2.Composite(复合类)
·包含子复合类Composite 或 叶子节点Leaf(Children)
·DoWork() 遍历子组件并调用它们的 DoWork()
3.Leaf(叶子节点)
·实现基础操作,不包含子组件
·DoWork() 执行实际工作

创建所有的复合Widget的类:



在CompositeBase中创建关闭和显示Widget的函数,并创建ApplyFunction函数:


在Composite中创建Children数组,并继承Base中所有的函数:

通过WidgetTree来设置Children,ApplyFunction和Collapse让所有的Children调用对应的函数:

在Leaf中创建对应的FragmentTag,以及ApplyFunction:

真正调用Function的位置:

将ItemDescription改为继承自Composite:


浙公网安备 33010602011771号