« C 中访问 Lua 配置表的优化 | 返回首页 | 难产的 Lua 5.4.4 »

异星工厂的机械臂模块

最近在做一个类似异星工厂的以自动化生产为主题的游戏。虽然我们的设计中,虽然我们的物流系统和异星工厂有许多差异(暂时没有设计传送带),但是我们也有类似的机械臂系统。

机械臂以一个固定的方向,让物品短距离移动,从一端转移到另一端。它用来把物品在物流网络和生产机械之间转移。我们在实现这个系统时,修改了几次方案,我觉得有点意思,值得记录一下。

首先,我觉得应该把机械臂系统和物流系统、生产系统隔离开。单独抽象它有利于降低整体实现的复杂度,也适合以后单独做性能优化。所以,一开始,我们就抽象出了物品容器和机械臂,把它们放在一起,单独做成一个黑盒。

游戏逻辑的大框架是基于 ECS 的,但机械臂系统没有用 ECS 实现。每个物品容器都有一个唯一的 id ,主框架上,有物品容器的 Entity 都记录这个 id ,可供索引到具体的容器。比如箱子、机器的输入箱、输出箱、燃烧室的燃料仓库,都是物品容器。机械臂系统被 ECS 框架的一个特定环节更新,每个 tick 在更新之后,其它系统都可以审视自身物品容器中的物品,而不必理会容器中物品的扭转过程。

机械臂初看挺简单,就是从 A 容器转移一个(一叠相同)物品到 B 容器。但熟悉异星工厂的玩家都知道其逻辑并不简单。

对于箱子这样的容器,可以放入任意物品;但箱子中每个格子可以堆叠物品的数量,是由物品的属性决定的。

对于组装机这样的机器,它有一个输入箱、一个输出箱。机械臂可从外部放置物品到输入箱,同时可从输出箱把物品取出;这种情况下,机器作为一个 Entity ,它被机械臂当作输入端和当作输出端时,其实是两个容器。

组装机的输入输出箱会有物品限制,也就是只能放特定物品;而机械臂在连接两个带物品限制的容器时,需要根据限制来决定移动哪个物品。换句话说,机械臂需要有黑白名单;当然对于筛选机械臂来说,这是是一个显式的特性,只不过普通机械臂其实也有隐式的规则。

更复杂的是燃烧器和熔炉,它们还会对可抓的物品按类型做一个复杂的筛选。

机械臂的运动本身分成四个阶段:

  1. 在没有物品可抓,或缺少目标位置需要的物品类型时,处于待机阶段。
  2. 否则抓取一叠物品。
  3. 然后进入(旋转)移动阶段。
  4. 最后在到达目的地后,如果可以放置就把物品放下。

以上,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

感觉传送带和机械臂是这类游戏当中最消耗资源的部分
感谢分享 赞一个
大佬什么时候进军3A游戏
厉害,有梦想的游戏制作人。
从传送带抓有过程,从箱子抓则没有。
阶段2.否则抓取一叠物品。 异形工厂的抓取不是瞬时完成的,2个机械臂抓同一个位置会出现争抢,有一个明显的抓空动画
异星工场感觉后期液体管道太恶心了 远远没有传送带机械臂好玩

Post a comment

非这个主题相关的留言请到:留言本