rimworld吧 关注:277,965贴子:5,144,762

泰南行为学:理论篇

只看楼主收藏回复

众所周知,泰南原版游戏的部分逻辑总是莫名其妙的,其中最令我难绷的就是一着火角色就乱跑。所以有一点C#基础的楼主准备写一个mod修改一下。但我改的时候发现对这一块的介绍少之又少,所以我准备分两个帖子分别介绍泰南小人的行为代码和mod编写的过程(算是编写CSharp与使用Harmony的教程吧)。因为本人也没怎么深入分析,如有错误请多多包涵,欢迎指正。


IP属地:广东1楼2025-12-26 20:31回复
    二楼放mod效果


    IP属地:广东2楼2025-12-26 20:32
    回复
      2026-01-21 17:00:00
      广告
      不感兴趣
      开通SVIP免广告
      角色或者说棋子(Pawn)是通过布置任务,和实现任务两个环节来实现的。其中布置任务有两种方法,第一种是通过玩家(player)强制指定任务。玩家在游戏中通过右键点击出的浮窗(FloatMenu)进而给与任务。第二种是通过Pawn每固定帧的更新来指定任务。最后都由布置任务对应的驱动器(Driver)驱动任务的完成。由于楼主本人懒和泰南的代码有点多,我不会详细讲解全部类,只会举一些例子,提到一些重点的方法。


      IP属地:广东3楼2025-12-26 20:35
      回复
        大体流程如下


        IP属地:广东4楼2025-12-26 20:38
        回复
          小人自主行为的触发是先通过Tick()函数以及后面相关函数进行触发,主要是调用的JobDriver驱动动作与完成JobDriver相应的工作后去寻找新的工作。Tick()相关我感觉不是很重要,毕竟这玩意太基础了,应该没什么补丁会打到这里。


          IP属地:广东6楼2025-12-26 20:42
          回复
            有mod来着,会原地灭火,也就是把原版的前面那段试图找水的逻辑删了只保留最后的自我灭火


            IP属地:山东来自Android客户端7楼2025-12-26 20:43
            收起回复
              下面就是泰南行为学的重头戏:思考树(ThinkTree)。着火乱跑就是因为它。思考树的根是ThinkTreeDef,下面是ThinkNode和它子类的实例(我下面就叫子节点了)。这些子节点我分成四类,分别是遍历子节点,条件子节点,子树节点以及工作子节点,整个树会通过TryIssueJobPackage()函数进行树由上至下执行触发,理论上应该思考树会返回一个ThinkResult实例,里面有相关的job内容。


              IP属地:广东8楼2025-12-26 20:46
              回复
                遍历子节点主要是ThinkNode_Priority和它的子类,主要与节点的遍历优先级相关。比如ThinkNode_Priority就是按照子节点序列加入的顺序进行遍历,ThinkNode_ChancePerHour是每小时检测触发随机触发某个节点,ThinkNode_PrioritySorter依照优先级进行排序触发,而有一些节点如ThinkNode_Priority_GetJoy用于调节行为(组)的优先级。


                IP属地:广东9楼2025-12-26 20:49
                回复
                  2026-01-21 16:54:00
                  广告
                  不感兴趣
                  开通SVIP免广告
                  条件子节点主要是ThinkNode_Conditional和他的子类。顾名思义,是通过重写 Satisfied()函数检测是否符合条件,符合条件则继续执行子节点,不符合就不执行。如ThinkNode_ConditionalDrafted就是检测是否角色被征召如果征召则执行子节点,没有被征召则不执行。ThinkNode_Conditional及其子类有一个invert属性可以使达成条件相反。


                  IP属地:广东10楼2025-12-26 20:53
                  回复
                    子树节点就是用于树的复用,对于相同流程的思考树打个包。主要是ThinkNode_Subtree和ThinkNode_SubtreesByTag。主要区别就是一个是直接执行子树另一个是按tag找。


                    IP属地:广东11楼2025-12-26 20:55
                    回复
                      工作子节点主要是ThinkNode_JobGiver和它的子类以及JobGiver_Work。ThinkNode_JobGiver的子类一般都以(JobGiver_事情名字)来命名。JobGiver这类节点是修改判定是否做某事的主要节点。主要关注的方法是子类的TryGiveJob(),在这个方法中会尝试返回一个Job,即为如果满足一定的条件则会返一个以JobDefOf为基类的Job,后续会在其父类ThinkNode_JobGiver的TryIssueJobPackage()中被包装成一个ThinkResult返回。而JobGiver_Work则会试图通过WorkGiver与其子类分配Job,该类会分配更复杂的任务(JobGiver_Work.TryIssueJobPackage()函数通过工作优先级查找Job)。


                      IP属地:广东12楼2025-12-26 20:56
                      回复
                        楼上应该说的是Stop drop and roll这个mod,这个mod通过直接返回Job(ExtinguishSelf)直接命令小人在着火的时候立刻自我灭火,同时禁掉了ThinkNode_JumpInWater.TryGiveJob()这个方法从而防止小人乱找水


                        IP属地:广东13楼2025-12-26 21:01
                        回复
                          泰南是通过xml记录思考树,比如在Core\Defs\ThinkTreeDefs\Humanlike.xml中定义了人类的通用思考树。


                          IP属地:广东14楼2025-12-26 21:05
                          收起回复
                            玩家可以通过浮窗进行强制任务派遣,征召时玩家的操作也通过这个方式。主要是通过FloatMenuOptionProvider提供FloatMenuOption实例(每个FloatMenuOption实例代表对应选窗)。在FloatMenuOption中写了浮窗的字与点击后的反应(回调函数)。基本上在FloatMenuOption中是通过委托添加入Job的方法。label是浮窗选项的字,action是回调函数。


                            IP属地:广东15楼2025-12-26 21:10
                            回复
                              2026-01-21 16:48:00
                              广告
                              不感兴趣
                              开通SVIP免广告
                              在Pawn_JobTracker.StartJob()中通过Pawn_JobTracker.curJob的MakeDriver()函数创造JobDriver(工作驱动器)。所有的JobDriver都存储则在每个Pawn的Pawn_JobTracker的属性里(Pawn.Pawn_JobTracker.curDriver)。每个Job对应的JobDriver可以在xml文件里面找到。
                              JobDriver中最重点的就是MakeNewToils()这个函数。每个Toil指一个步骤。如JobDriver_Goto类的MakeNewToils()中返回两个Toil。第一个Toil是负责的是否可以到达目标(不能到达就直接标记直接结束,取消后续任务)以及走达目标的过程。第二个Toil是到达后发出信号。比如安排小人去参加仪式时,标记修改人数,人满了就开始。MakeNewToils()这个函数返回为IEnumerable<Toil>的迭代器。


                              IP属地:广东16楼2025-12-26 21:16
                              回复