GOAP的实现已经告于段落,但是仍然并不能应用于我的游戏。因为我目前所能找到的关于GOAP的探讨和资源都是关于单一角色(agent)的任务规划,然而我这种建设管理的游戏,是不可避免要由多名角色各司其职,合作完成任务。于是,我构思了一系列对GOAP的改造,以实现多人合作。
示例
环境设置
出于简化目的,坐标轴仅保留单轴,表示各物体的间距。
- agent_a 角色a, 位于0点,烹饪2级,采集2级,移动速度1
- agent_b 角色b,位于0点,烹饪1级,采集1级,移动速度2
- cooker 炉子,位于2点,烹饪基础时长4s
- collector 采集站,位于4点,采集操作基础时长4s
- apple_tree 苹果树,位于5点,在采集站的采集范围内,采集可产出生苹果(raw_apple)
还有一些定义:
- 配方:生苹果(raw_apple)可在炉子(cooker)处被加工为烤苹果(roast_apple)
- 角色执行加工行为所需时间 = 基础时间/角色对应技能等级。例如角色a进行烹饪的时间 = 4s/2 = 2s
目标
假设玩家在炉子处下达了一个目标(goal): 生产1个烤苹果,以状态定义(state)表示为:
单角色实现
以经典的GOAP实现方式,会是这样的规划结果:
角色前往采集站→采集生苹果→携带苹果前往炉子→生产烤苹果
十分直白简单。
角色需要具有的Action包括:Collect, Pick, Drop, Cook。
这里还需要特别注意一点,角色从拿到生苹果后,就一直携带,并不放下。因此,所谓生产烤苹果的过程,其实是“把自身携带的生苹果”转化为“烤苹果”。但而我的游戏设计并不能这样,后详。
像这种每个goal都由一个角色规划并完全由他执行一整串action的方式,对于很多游戏来说是足够的,每个角色只需要能够与环境交互并动态完成他的需求即可,例如FPS游戏里敌方的行为,或者RPG里的NPC。但是我这种建设规划的游戏里,是必须要有角色配合的,尤其如果是较为复杂步数较多的action串,如果一个人从头忙到尾,其他人都歇着,是肯定不合理的。
多角色合作实现
Action改造
- 角色不能再持续携带物品来进行生产工作,因为要多人合作,那么必须得有物品的“接头地点”,并且由于配方中会有多个原料,单人来回拿取不可行。因此采集站和炉子除了是生产设施以外,还必须兼任“物品接收容器”和“物品提供容器”,用以作为原材料放置和产物放置。这样角色不再持续携带物品,而是在各种容器之间取放。那么在Action层面就是原本的PickAction和DropAction改为一个TransferAction,表示从Source到Destination两个容器之间的物品运输。
- 那么,CookAction和CollectAction的Precondition和Effect也从 agent本身具有原料/产物 转变为了 设施具有原料/产物
世界状态改造
原本,在判断CookAction是否适合一个State的时候,是在Action Expand时才检查配方里是否有对应产出。出于优化的目的,现在提前遍历配方把其产出写入Cooker设施的世界状态。而Collector设施以及未来其他 潜在物品提供者 也都如此处理。
规划时机改变
原本是在每当一个agent闲下来时,进行一次plan。但现在由于是多人合作模式,以某个agent闲置为时机点就不合理了。因此plan过程是以goal为主导,在每当出现一个新goal时,进行规划。
各自Plan → 整体Plan
与上一条相辅相成的,原本每个人的各自plan,合并为了一个大plan。但是在Action Expand时,每个action中都会把不同agent的执行作为一个setting,展开成多个node。这样有内存使用暴涨的担忧,但由于每人1次的plan合并为了整体1次,从而显著减少了内存使用,所以目前认为可行。
增加个人工作队列
既然要考虑到所有agent一起合作,那么就会有这样的可能:“由于某人极其擅长烹饪,因此即使等他完成手头工作后再去做饭,也比让外行立刻去做饭来得快”。那么为了实现这种优化,就需要在规划时把“正在忙碌”的agent也考虑进来,并且需要有个人任务队列,来支持对他后续工作的保存。
Cost → Time
在标准的GOAP里,计量action的方式是cost,agent总是要选择cost最小的方式去规划。而在我的游戏里,玩家唯一在意的事情其实就是时间,也就是一切都是越早完工越好。因此Cost被明确化为Time。Action需要能够提供其执行所需时间。
但是这里有个问题,除了Action的执行时间以外,还需要考虑角色前往目的地的行走时间。并且由于不再是单一角色执行整个action串,在尚未expand完全的情况下,并不知道某个角色的起点position是哪里,因此无法预知角色行走时间。好在,PathFinding是在整个Expand完成之后,那么我们可以在PathFinding时,询问到各个Node的Neighbour的Cost(Time)的时候,由这个Neighbour Node从Action Graph中查询他前置Action是什么,从而查到移动过来所需时间。这样就可以用Time替代Cost,寻找到时间上的最优解了。
增加Action依赖关系
由于action串被分派到多人执行了,那么为了避免后续任务被提前执行而失败,需要明确记录action之间的关联关系。
人工实现示例
最后,建立在以上的改造之下,我尝试着人工进行了一下Plan如下,首先是展开:
这里记录的事件,就仅仅是Action本身的执行时间。然后是寻路
路径应该有很多种,我只人工进行了2种以示效果。这里的MoveTo部分就是移动到目的地所需时间了。
这里还展示了一项优化,也就是在A执行收集任务将尽时,B作为预定的搬运者,要提前MoveTo收集站,从而保证在A收集完成时,B刚好可以把东西拿走。
另外我还尝试了在第一次Plan确认完毕之后,立即进行第二次相同Plan。在这种情况下,计算各Action的Time的时候要建立在各个Agent"当前队列全部完成之后”的基础上。
遗留问题
- 在PathFinding中还有一个可优化的余地,如上图所示,agent_a的两次cook之间有5s空窗期。如果这段时间可以利用起来做别的事情就更好了,不过这个优化感觉已经超过了我现在能构思的脑力了,留待这一版本的工作结束以后再考虑吧。
- 这种“预规划”的设计带来了一个新问题,就是如果规划后执行前,相关的世界状态发生变动了怎么办,例如预定要去摘取的苹果腐烂了,或者玩家取消了任务。那么我目前的构思是只能让他执行失败,并把这个goal重新退回目标池,移除每个相关人员的后续相关action,并且前移其后续任务。但是为了减少这种情况的发生,不应当过于超前的进行任务规划,目前我认为可以设置限制为“每人有3项任务队列”时即停止规划。
- 优先级,暂不考虑新的高优先级任务直接插队,而只是被优先规划。那么为了能尽早执行,前项的减小队列的措施也是必须的。
怀疑
- GOAP是否大材小用了?
- 如果注意观察一下的话,这个示例其实并不会出现在真正的游戏里,通常不会有哪个游戏里当玩家下达一个做饭的任务时,NPC竟然会主动去采集食物再做饭这么的自动化。这样做反而剥夺了玩家的操作权利,一般来说应该是玩家尝试下达做饭指令,然后发现原料不够,于是再从收集站下达收集原料的任务。
- 诚然,在我的游戏里,我也不会让角色在早期就自动化到这个地步,但是由于这类游戏的玩点是逐渐螺旋上升的,不能让玩家总是进行手工的早期生产选择,因此我会在后期玩点出现后,科技树中逐渐开启各种早期生产活动的自动化,例如当玩家拥有一名“簿记员”NPC时,就可以表现为玩家直接下达了做饭命令,而簿记员则将其扩展为“收集原料+做饭“指令,这在后台的规划自然是通过GOAP来做了。
- 另一方面,虽然玩家下达的目标大部分是直接明确并且一两步行为就能完成的,但角色自己的需求所下达的目标却不总是这么直接,例如当一个角色感受到寒冷时,他既有可能去多找件衣服穿,又有可能额外往火盆里添加点柴,再或者去吃顿热乎的饭菜,如果有这样多种应变能力,对游戏而言无疑是更加生动的。要实现这种东西,使用GOAP是很合适的了。
讨论群: 827072601
爱发电: https://afdian.net/@taohuayuan
任务板: https://trello.com/b/StForyw7/taohuayuan
twitter: https://twitter.com/zephyr1125
wiki: https://taohuayuanwiki.a2hosted.com
discord: https://discord.gg/sMuKYE6
dalao什么时候能放出您的GOAP实例项目学习下呢