Play - 角色匹配规则

在运行过脚本后,我们深入探索一下角色的匹配规则。

动机

对于小型足球这样的多智能体场景中,我们在最上层进行简单的状态调转后,最终会执行一个由多个任务组成的特定状态对应的任务包,在这个任务包中,包含了不定数量的角色以及每个角色的具体任务,但在此时,我们并不知道每个角色对应的机器人编号是多少,有时我们只希望由距离最近的机器人去执行该任务(代价最小),有时我们又希望由某个特定编号的机器人去执行该任务,这时匹配规则就会起到作用。

让我们现在书写一个简单的单状态的Play,使用四种不同的匹配规则,分别是①[ALDM]、②[A][LDM]、③[AL][DM]、④[ALD][M]来比较匹配规则的实际作用。

简单的角色匹配测试
 1local runPos  = {
 2    CGeoPoint:new_local(300, 300), 
 3    CGeoPoint:new_local(-300, 300),
 4    CGeoPoint:new_local(300,-300),
 5    CGeoPoint:new_local(-300,-300)
 6}
 7
 8gPlayTable.CreatePlay{
 9    firstState = 'run',
10    ["run"] = {
11        switch = function()
12        end,
13        Assister = task.goSimplePos(runPos[1],0),
14        Leader   = task.goSimplePos(runPos[2],0),
15        Defender = task.goSimplePos(runPos[3],0),
16        Middle   = task.goSimplePos(runPos[4],0),
17        match = "[ALDM]" -- ①
18        -- match = "[A][LDM]" -- ②
19        -- match = "[AL][DM]" -- ③
20        -- match = "[ALD][M]" -- ④
21    },
22    name = "TestRun",
23}

让四个机器人运行到一个特殊的初始位置后分别以不同匹配规则运行上述状态,可以得到如下结果:

上述的匹配规则中使用了[]中括号,这代表了实时匹配,每个角色在匹配规则中使用首字母表示,例如A代表AssisterL代表Leader。匹配采用从左到右的规则,例如:

  • [ALDM]代表将四个任务角色AssisterLeaderDefenderMiddle直接进行匹配,由于测试时共有四台机器人,所以每个角色会匹配到一台机器人,并保证四个机器人运动的总代价最小。

  • [A][LDM]表示,在四台机器人中,先选择执行Assister任务代价最小(例如距离最近)的机器人优先匹配A任务,再将剩下三台机器人与三个任务进行分配。

上面用到了匹配规则中的两个功能:分组匹配实时匹配。为了应对更复杂的场景,我们还设计了其他匹配,完整的描述如下:

  • { } : 保持匹配 - 保持之前机器人匹配,除非当前帧刚经历过脚本跳转(新脚本所以无匹配号码)。

  • ( ) : 单次匹配 - 保持之前机器人匹配,除非当前帧刚经历过状态跳转(switch函数有返回值即可)。

  • [ ] : 实时匹配 - 在任何状态下都进行匹配。

在具体匹配算法中,我们使用二分图匹配的经典方法-匈牙利算法来实现,算法细节不做展开,但对于N个机器人执行M个任务时,可以勾选面板的Debug/RoleMatch来查看用于匹配运算的代价矩阵。下图中每个黄色的数字代表了一个机器人执行一个任务的代价。

在实际使用时,可能由于各种原因导致机器人数量与任务数量不等的情况:

  • 如果机器人数量比任务数量多,会出现有些机器人没有角色可用,现象是机器人原地不动,Client软件上机器人没有类似于L,A这样的角色标记。

  • 如果机器人数量少于任务数量,会存在部分角色任务没有对应的执行机器人,具体哪个任务缺失由匹配规则决定。所以在书写匹配规则时也需要考虑缺少机器人的情况,有时会使用闭包函数来动态生成匹配规则。