异星工厂的机械臂模块
最近在做一个类似异星工厂的以自动化生产为主题的游戏。虽然我们的设计中,虽然我们的物流系统和异星工厂有许多差异(暂时没有设计传送带),但是我们也有类似的机械臂系统。
机械臂以一个固定的方向,让物品短距离移动,从一端转移到另一端。它用来把物品在物流网络和生产机械之间转移。我们在实现这个系统时,修改了几次方案,我觉得有点意思,值得记录一下。
首先,我觉得应该把机械臂系统和物流系统、生产系统隔离开。单独抽象它有利于降低整体实现的复杂度,也适合以后单独做性能优化。所以,一开始,我们就抽象出了物品容器和机械臂,把它们放在一起,单独做成一个黑盒。
游戏逻辑的大框架是基于 ECS 的,但机械臂系统没有用 ECS 实现。每个物品容器都有一个唯一的 id ,主框架上,有物品容器的 Entity 都记录这个 id ,可供索引到具体的容器。比如箱子、机器的输入箱、输出箱、燃烧室的燃料仓库,都是物品容器。机械臂系统被 ECS 框架的一个特定环节更新,每个 tick 在更新之后,其它系统都可以审视自身物品容器中的物品,而不必理会容器中物品的扭转过程。
机械臂初看挺简单,就是从 A 容器转移一个(一叠相同)物品到 B 容器。但熟悉异星工厂的玩家都知道其逻辑并不简单。
对于箱子这样的容器,可以放入任意物品;但箱子中每个格子可以堆叠物品的数量,是由物品的属性决定的。
对于组装机这样的机器,它有一个输入箱、一个输出箱。机械臂可从外部放置物品到输入箱,同时可从输出箱把物品取出;这种情况下,机器作为一个 Entity ,它被机械臂当作输入端和当作输出端时,其实是两个容器。
组装机的输入输出箱会有物品限制,也就是只能放特定物品;而机械臂在连接两个带物品限制的容器时,需要根据限制来决定移动哪个物品。换句话说,机械臂需要有黑白名单;当然对于筛选机械臂来说,这是是一个显式的特性,只不过普通机械臂其实也有隐式的规则。
更复杂的是燃烧器和熔炉,它们还会对可抓的物品按类型做一个复杂的筛选。
机械臂的运动本身分成四个阶段:
- 在没有物品可抓,或缺少目标位置需要的物品类型时,处于待机阶段。
- 否则抓取一叠物品。
- 然后进入(旋转)移动阶段。
- 最后在到达目的地后,如果可以放置就把物品放下。
以上,2 阶段和 4 阶段是瞬时完成的,完成后在当前 tick 立刻转到下个阶段,或不满足条件挺在上个阶段;1 阶段和 3 阶段则会持续若干游戏 tick 。
机械臂系统就是对容器和机械臂的一个抽象。从玩家角度看,每个机械臂的完整小系统都包含了一个机械臂和两端的两个容器;但实际上并不只输入和输出两个容器。
因为一个 Entity 身上很可能有多个容器。比如带燃料箱的生产机器,我们在实现时,它是有独立的组件:燃烧室和组装机构成的,两者均有自己的容器。所以在机械臂系统看来,机械臂的每一端都应该是一个容器列表,而不是唯一容器。
考虑到这一点,我觉得与其在机械臂系统中抽象容器,不如抽象容器中的槽位。
每个容器都是由若干槽位(格子)构成的,我们可以用一个链表把一个容器中的所有槽位串起来。
槽位(slot)的数据结构可以定义为:
ID : dword NextID : dword Filter : word ItemType : word Count : word Capacity : word
一共 16 字节。我们可以把游戏中一切容器的槽位都管理在大数组中,用下标作为 ID 。用 freelist 回收再利用。绝大部分情况下,同一个容器中的槽位都会在连续内存中,对 Cache 非常友好。
当一个 Entity 创建时,它可能关联了多个容器,我们可以把所有关联容器的所有槽位都用 NextID 串起来,并首尾相接,形成一个循环链表。Entity 的每个组件都记录它自己拥有的那个容器的第一个槽位的 ID 以及槽位的数量(这个在原型表中,同种东西只有一份),这样就可以找到它自己拥有的那个容器的所有槽位了。
而对于机械臂系统来说,机械臂只记录它最近一次访问的两个(输入/输出)槽位的 ID ,而不需要有容器这个概念。
绝大部分情况,机械臂在需要抓取,或放置物品时,检查对应的槽位就够了。放置物品或抓取物品,只需要对 Count 字段做一次加减法。只有抓取时槽位为空、放置时槽位满,或物品不匹配(同一槽位只能相同物品叠加),才需要看其它关联槽位。这用 NextID 遍历循环链表即可。
关于 Filter ,它表示了槽位的过滤行为。大多数槽位是不需要过滤的,什么都可以放,这时 filter 为 0 ;部分槽位一开始就决定了只能放制定物品,filter 为 1 ,同时从 ItemType 可以查到具体是什么东西。剩下的情况会比较复杂,比如这个槽位可以放置燃料、矿石、等等。这种情况通常是游戏定义好的,有限种类的,我们可以实现对应的过滤器对象,并用这个 filter id 去索引到过滤器对象。
槽位的容量 Capacity 一般可以通过 filter 和 itemtype 查到。它通常就是物品的堆叠上限(配置在物品原型表中),但有时也有一些例外规则:比如组装机的输入箱的堆叠上限是有配方决定的。所以,对于独立系统来说,额外记录一个会性能友好一些,也可以隔绝系统间的复杂度。
Comments
Posted by: James | (7) December 25, 2021 01:55 PM
Posted by: 防水材料加盟 | (6) December 24, 2021 10:10 AM
Posted by: 毛毛虫 | (5) December 23, 2021 04:39 PM
Posted by: coco | (4) December 23, 2021 09:34 AM
Posted by: Cloud | (3) December 21, 2021 06:10 PM
Posted by: jxfzlmb | (2) December 20, 2021 09:02 PM
Posted by: bitetheddddt | (1) December 20, 2021 11:14 AM