DUC系统入门

指派单位控制(DUC, Direct Unit Control)是Userpatch 1.5中增加的一个功能,它允许你对AI的单位和建筑发出特定的命令,就像人类玩家点选控制自己的单位一样。


IDs和列表
首先要了解的是地图上的每个单位都有一个唯一的地图ID。要使用DUC选择一个单位,你需要告诉DUC你想要的单位ID。如何找到ID呢?UserPatch为我们提供了两个列表。我们可以告诉游戏去寻找特定的单位,当它找到它们时便会将它们的id放到列表中。这些列表称为本地列表和远程列表。本地列表包含属于我们自己的单位的id。它可以容纳240个id。远程列表可以保存任何玩家单位的id——我们自己的,我们的盟友和敌人的,甚至是盖亚的。远程列表一次最多可以容纳40个id。

当我们想要查找关于我们单位的信息或给他们指派任务时,我们使用本地列表。我们使用远程列表来查找任何单位的信息,或者使它成为我们给自己单位下的命令的目标。举个例子,如果我们玩游戏,我们希望我们所有的斥候攻击敌人的奇观,我们需要得到我们斥候ID 存入本地列表中(因为这是我们想指派的单位)和敌人的奇观的ID存入我们的远程列表(因为这是我们命令要攻击的目标。)如果我们是保护奇观并想要修复它的玩家,我们需要在本地列表中获取我们的村民ID,而在远程列表中获取我们的奇迹ID。在这种情况下,奇观是我们自己的单位,我们也使用远程列表,因为它是我们所给出的命令的目标。


第一个例子:本地搜索和目标点使用
:选择侦察兵并将其移动到地图的中间。我们要做的第一件事是找到侦察兵的ID。因为侦察兵是我们自己的单位,我们正在搜索它并给它一个指令,把它的ID放在本地列表中

我们要做的第一件事就是弄清楚我们要找什么样的单位。根据我们在游戏中选择的民族 分为有马民族和无马民族 我们在标准游戏中开始的侦察单位可以是骑兵侦察单位或者鹰侦察单位。定义民族该有什么类型的侦察单位

#load-if-defined AZTEC-CIV
(defconst scout-type -267)          ;阿兹特克,印加,玛雅 拥有Eagle warrior line 鹰斥候
#else
#load-if-defined INCAN-CIV
(defconst scout-type -267)
#else
#load-if-defined MAYAN-CIV
(defconst scout-type -267)
#else
(defconst scout-type -286)  ;其他民族拥有Scout cavalry line 斥候
#end-if
#end-if
#end-if

 

上面scout-type-XXX是classID,代表单位的整条生产线ID。民兵升级为装甲步兵,搜索民兵ID将不会再找到你的单位,因为他们不再是民兵了。搜索民兵线,-296,可以找到该单位类型升级之前和之后的单位

(defconst gl-state-1 100)       ;用于保存本地搜索总数
(defconst gl-state-2 101)       ;用于保存本地搜索中列表多少项存有对象的项数 从0索引开始
(defconst gl-state-3 102)       ;用于保存远程搜索总数
(defconst gl-state-4 103)       ;用于保存远程搜索中列表多少项存有对象的项数 从0索引开始

(defconst gl-point-x 104)       ;获取点需要连续两个序号大于40的目标值保存
(defconst gl-point-y 105)       ;获取点需要连续两个序号大于40的目标值保存

(defrule
(true)
=>
(up-full-reset-search)             ;这将从列表中删除所有以前的id并清除搜索中的任何过滤器 每次不同搜索之前都要清空
(up-find-local c: scout-type c: 1) ;当使用up-find-local代码时 就会在游戏搜索到对象加入到本地列表
(up-get-search-state gl-state-1)   ;获得搜索次数存入到定义常数大于40连续序号用来保存本地搜索总数 对象存入本地列表项数,远程搜索总数,对象存入远程列表项数
(disable-self)                     ;关闭循环
)

 

我们可以使用up-get-search-state提供的数据来验证我们确实找到了斥候。第一个goal告诉我们本地搜索总数。第二个goal给对象存入本地列表项数。因为我们使用up-full-reset-search从所有列表中删除了搜索所有id,第三和第四个goal与第一个和第二个goal相同,但是包含了关于远程列表而不是本地列表的信息。因此,第三个goal是远程搜索总数,第四个goal是对象存入远程列表项数。

如果我们的搜索找到了侦察兵,第一个goal和第二个goa值都会保存为1 假设我们找到了斥候,我们可以指派它移动到地图的中心

(defrule
(up-compare-goal gl-state-1 c:== 1)             ;判断本地列表是否找到单位
=>
(up-get-point position-center gl-point-x)       ;获取地图中心的(x,y)坐标。把x,y坐标存入连续序号gl-point x的值上,只需要写连续两个目标序号大于40就行了。
(up-target-point gl-point-x action-move -1 -1)  ;target在本地搜索结果中的单位(指的是我们的侦察兵)移动到地图中心(保存在gl-point x和gl-point y中的点)
(disable-self)
)

 

把上面的代码整合到一起:

#load-if-defined AZTEC-CIV
(defconst scout-type -267)
#else
#load-if-defined INCAN-CIV
(defconst scout-type -267)
#else
#load-if-defined MAYAN-CIV
(defconst scout-type -267)
#else
(defconst scout-type -286)
#end-if
#end-if
#end-if

(defconst gl-state-1 100)
(defconst gl-state-2 101)
(defconst gl-state-3 102)
(defconst gl-state-4 103)
(defconst gl-point-x 104)
(defconst gl-point-y 105)

(defrule
(true)
=>
(up-full-reset-search)
(up-find-local c: scout-type c: 1)
(up-get-search-state gl-state-1)
(disable-self)
)

(defrule
(up-compare-goal gl-state-1 c:== 1)
=>
(up-get-point position-center gl-point-x)
(up-target-point gl-point-x action-move -1 -1)
(disable-self)
)

 

目标单位up-target-objects和目标点up-target-point非常相似都有设定执行动作,设定阵型,姿态。唯一的区别是,指派执行动作单位放在我们的本地列表,指派执行目标单位放在远程列表并通过调用up-target-objects设定为目标对象。目标单位up-target-objects多一个参数 1只以用up-set-target-object设定为目标  0以远程列表对象为目标
可以通过过滤筛选出特定对象设定为目标。

性能考虑
除了up-path-distance和up-get-path-distance调用之外,搜索(以up-find-开头的命令)计划定期执行的搜索,建议您使用计时器来确保它们的执行频率不会超过每2秒一次。每次执行规则时都要进行一次又一次的搜索,这会导致游戏延迟。像up-target-point这样的命令也会导致游戏执行pathing调用(即使我们没有在代码中直接看到它),如果反复执行,这也会影响性能。disable-self可以防止代码执行多次


第二个例子:远程搜索和目标对象
想要检查我们在1v1中的敌对玩家是否研究织布机
首先,让我们定义我们想要如何做。作为一个人类玩家,对进入我们的视线的敌方村民检查是否研究了织布机,点选它,看到它的护甲或它的生命值。一个没研究织布机村民有25点生命值和0/0护甲,而一个有织布机的村民有40点生命值和0+1/0+2护甲,所以如果我们点击一个敌方的村民,看到它有40点生命值或有护甲,我们就知道敌人有织布机。我们将使用村民的生命值来确定他们是否有织机。

第一步是找到一个属于我们对手的村民。因为我们正在寻找其他玩家的单位,我们需要使用远程列表。每次只能搜索一个玩家的单位。通过使用up-find-player来筛选出特定玩家序号 然后通过设置sn-focus-player-number设定筛选出的玩家序号。因为使用远程列表需要用sn-focus-player-number来设定需要搜索哪个玩家对象。

;定义常数
(defconst gl-state-1 100)        ; 用于保存本地搜索总数
(defconst gl-state-2 101)        ; 用于保存本地搜索中列表多少项存有对象的项数 从0索引开始
(defconst gl-state-3 102)        ; 用于保存远程搜索总数
(defconst gl-state-4 103)        ; 用于保存远程搜索中列表多少项存有对象的项数 从0索引开始
(defconst gl-enemy 106)          ; 存储敌方玩家
(defconst gl-enemy-has-loom 107) ; 判断是否有织布机的开关(最终满足的条件)
(defconst gl-temp 108)

;计时器。因为需要计时器设定判断周期,不会导致频繁判断 造成游戏延迟
(defconst t-3-sec 1)

;Misc    ;判断满足条件血量
(defconst vil-loom-hp 40)

(defrule                                    ; 开局设定
(true)
=>
(up-find-player enemy find-closest gl-enemy)  ;搜索特定敌方(离自己最近敌方玩家)玩家序号
(set-goal gl-enemy-has-loom 0)          ; 设定没织布机 为0
(enable-timer t-3-sec 3)                ; 开启计时
(disable-self)                          ; 关闭循环 只运行一次
)

; 计时器反复开启
(defrule
(timer-triggered t-3-sec)               ; 计时到期
=>
(disable-timer t-3-sec)                 ; 关闭计时
(enable-timer t-3-sec 3)                ; 重启计时
)

(defrule
(up-compare-goal gl-enemy-has-loom c:== 1) ; 假设最终满足条件
=>
(chat-to-all "Our opponent has loom!")     ; 我们敌方村民有织布机
(disable-self)
)

 

因为我们希望这个检查重复发生,直到我们看到我们的敌人已经出现,所以我们将使用一个计时器来防止这个代码执行得太频繁。

当不满足最终条件 设定焦点玩家用远程列表搜索出一个敌方村民

(defrule
(timer-triggered t-3-sec)                       ;执行此规则的频率不超过每3秒一次
(up-compare-goal gl-enemy-has-loom c:== 0)      ;当最终条件不满足时候
=>
(up-modify-sn sn-focus-player-number g:= gl-enemy)   ;设定up-find-player找到的玩家序号
(up-full-reset-search)           ; 这将从列表中删除所有以前的id并清除搜索中的任何过滤器包括使用up-set-target-object up-get-point设置的对象都清空。
(up-find-remote c: 904 c: 1)     ; Unit ID 904是村民组
(up-get-search-state gl-state-1) ; 获得搜索次数存入到定义常数大于40连续序号用来保存本地列表搜索次数 上一次本地列表搜索次数,远程搜索次数,上次远程搜索次数
)

(defrule
(timer-triggered t-3-sec)
(up-compare-goal gl-enemy-has-loom c:== 0)
(up-compare-goal gl-state-3 c:== 1)             ; 没有检查搜索次数代码需要用保存goal值来保存再判断 这里是判断远程列表是否搜索到结果
=>
(up-set-target-object search-remote c: 0)       ; 设定远程列表搜索到的索引0号单位为目标对象
(up-get-object-data object-data-maxhp gl-temp)  ; gl-temp保存获取到对象数据-最大生命值 只能获得最后一个加入列表的单位数据
)

(defrule
(timer-triggered t-3-sec)
(up-compare-goal gl-enemy-has-loom c:== 0) ; 判断敌方村民是否研究织布机
(up-compare-goal gl-state-3 c:== 1)        ; 判断远程列表是否搜索到村民
(up-compare-goal gl-temp c:>= vil-loom-hp) ; 判断获得到单位数据生命值是大于40
=>
(set-goal gl-enemy-has-loom 1)             ; 敌方村民已经研究织布机了 gl-enemy-has-loom设定为1
)

 

上面例子 因为需要多个条件都满足了才能判断敌方村民有织布机  需要频繁使用计时器
设定一个开关 判断是否满足要求  当不满足当前要求再往下判断 直接逐步满足第一个要求。
goal值只能存储当前不变的数值 需要开启一个计时器频繁刷新goal里面的数值。如果不开启频繁计算
对于不断变化的属性会判断错误 导致条件永远满足


第三个例子:过滤距离
你的AI指派一个村民去建立一个伐木营地。建筑工人完成了伐木营地的建造,然后去城镇中心另一边采集浆果
我们这项任务的目标是确保在我们建造第一个木材营地后不会发生这种情况。(作为人类玩家当建造完某个采资源营地就自动在附近采集了 一般不会出现这个问题)

检测我们的第一个木材营地何时建成
看看在营地方圆一瓦范围内是否有一个不是伐木工的村民
如果有,找附近的树,指派该村民去伐木。

是否有伐木营地通过(building-type-count lumber-camp > 0)

;常量设定
(defconst gl-state-1 100)
(defconst gl-state-2 101)
(defconst gl-state-3 102)
(defconst gl-state-4 103)

(defconst gl-point-x 104)
(defconst gl-point-y 105)

(defrule
(true)
=>
(set-goal gl-state-1 0)    ; 设定一个初始值用来判断
(set-goal gl-state-2 0)    ; 设定一个初始值用来判断
(set-goal gl-state-3 0)    ; 设定一个初始值用来判断
)

(defrule
(building-type-count lumber-camp > 0)
=>
(up-full-reset-search)     ; 这将从列表中删除所有以前的id并清除搜索中的任何过滤器
(up-find-local c: lumber-camp c: 1)
(up-get-search-state gl-state-1)
(disable-self)
)

; 我们找到了木材营地。接下来我们要做的是确定它的确切位置,这样我们就知道去哪里寻找村民了
(defrule
(up-compare-goal gl-state-1 c:> 0)          ; 在本地列表找到单位
=>
(up-set-target-object search-local c: 0)    ; 设定目标对象为本地列表的对象 吧列表对象设定目标对象。用up-get-point和position-object引用它
(up-get-point position-object gl-point-x)   ; 将设定为目标对象的对象位置保存到一对goal上(gl-point-x)
;↑→等同于获取对象X,Y轴坐标 ↓
;(up-get-object-data object-data-point-x gl-point-x)
;(up-get-object-data object-data-point-y gl-point y)
(up-reset-search 1 1 0 0)               ; 已经获得对象位置保存了 清空本地列表所有对象。前面两个是本地列表记录,后面两个是远程列表记录   up-reset-search 1 1 1 1等同于up-full-reset-search
(up-set-target-point gl-point-x)        ; 将保存了目标对象位置的goal设为目标点
(up-filter-distance c: 0 c: 2)          ; 以这个目标点 筛选距离2格内 该代码需要与up-set-targer-point同时使用
(up-find-local c: 904 c: 1)             ; 904 = Villager group 寻找2格范围内是否有一个村民

(up-remove-objects search-local object-data-type c: 218)    ; 218 = 从本地列表移除对象类型Female lumberjack 男伐木工
(up-remove-objects search-local object-data-type c: 123)    ; 123 = 从本地列表移除对象类型Male lumberjack  女伐木工
(up-get-search-state gl-state-1)        ; 保存搜索计数
)

 

关于过滤需要注意,在我们对搜索应用过滤器之后,这个过滤器将会影响我们所有的搜索,需要我们使用完删除它。所以如果我们不删除现在再做一次搜索,它只会在我们第一个木材营地的两个瓦片内找到单位。我们可以通过使用up-reset-filters or up-full-reset-search从搜索中删除过滤器。

,我们的伐木营地的1格范围内寻找一个非伐木村民。如果本地列表的内容是空的,那么就没有闲着村民,我们的工作就完成了。但是如果有这样一个村民,我们想找棵树让他砍。

(defrule
(up-compare-goal gl-state-2 c:> 0)  ; 第二个目标值记录列表项数
=>
(up-filter-distance c: 1 c: 3)      ; 目标点仍然是伐木营地,所以这将限制搜索半径为3瓦。
(up-find-resource c: wood c: 1)     ; up-find-resource是一种特殊的搜索类型,它只查找资源。与up-find-remote一样,它存储在远程列表里。
(up-get-search-state gl-state-1)    ; 保存搜索次数
)

(defrule
(up-compare-goal gl-state-1 c:> 0)          ; 找到了村民
(up-compare-goal gl-state-3 c:> 0)          ; 找到树林资源
=>
(up-target-objects 0 action-default -1 -1)  ; 指派在本地列表找到村民去伐木,
; 0表示目标对象为远程列表  action-default执行动作  -1阵型  -1姿态
)
;注:同一时间 只能执行一个动作 临时执行完动作就清掉列表对象 加入新的对象执行动作  远程列表里对象是记录判断 不能指派远程列表对象执行动作指令。

前面示例中的代码有一个有趣的特性:它只会查找并锁定活的树,而忽略已经被砍伐的树。这是因为被砍倒的树与活着的树有着不同的地位。状态是每个单元拥有的属性。它将是下面UserPatch常量 – 对象状态中的一个数字。

许多搜索操作完全忽略状态,但是up-find-status-local、up-find-status-remote和up-find-resource(我们用来查找树的地方)都将其考虑在内。默认情况下,搜索将查找状态就绪的对象。可以使用up-filter-status修改它。

对象状态

(defconst status-pending 0)     ;状态未完成的建筑
(defconst status-ready 2)       ;正常(绝大部分活的单位。如建筑、部队、活着的野生动物/牲畜、树木)
(defconst status-resource 3)    ;资源块(砍倒的树木、果树丛、金矿、石矿、闪光、树桩、一堆石料)
(defconst status-down 4)        ;倒下(被杀死而正在倒下,但不包括砍倒的树木)
(defconst status-gather 5)      ;可采集(携带食物的已死动物、各种尸体、一堆食物/木材/黄金。其中尸体无法通过搜索找到)

 

请登录后发表评论

    请登录后查看回复内容