You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

CLIPS规则系统多伤害与抗性事实匹配的最佳实践咨询

CLIPS游戏伤害抗性逻辑的最佳实践

我正在用CLIPS实现游戏中的伤害抗性逻辑,物理伤害会按物理抗性的系数减免。单伤害、单抗性的场景下代码能正常跑,但多伤害事实时,原规则会撤回抗性事实导致后续伤害无法触发减免;如果不撤回抗性,规则又会无限循环重复触发。

需求是:多个抗性事实与多个伤害事实共存时,适用的抗性需对每个伤害仅应用一次——单个伤害可匹配多个抗性,单个抗性也可匹配多个伤害。

初始代码如下:

(deftemplate damage
  (slot source (type STRING))
  (slot target (type STRING))
  (slot amount (type FLOAT))
  (slot type (type STRING)))

(deftemplate resist
  (slot type (type STRING))
  (slot factor (type FLOAT))
  (slot player_id (type STRING)))

(defrule damage-resistance
   ?d <- (damage (source ?source) (target ?target) (amount ?amount) (type ?type))
   ?r <- (resist (type ?type) (factor ?factor) (player_id ?target))
   =>
   (bind ?reduced-amount (* ?amount ?factor))
   (retract ?r)
   (modify ?d (amount ?reduced-amount))
)

(assert (damage (source "1") (target "2") (amount 10.0) (type "physical")))
(assert (resist (type "physical") (factor 0.9) (player_id "2")))

我试过给每个伤害事实加唯一ID,让抗性事实通过多槽记录已应用的伤害ID,这个方案能满足需求,但不确定是不是最优解,代码如下:

(deftemplate damage
  (slot id (type INTEGER))
  (slot source (type STRING))
  (slot target (type STRING))
  (slot amount (type FLOAT))
  (slot type (type STRING)))

(deftemplate resist
  (slot type (type STRING))
  (slot factor (type FLOAT))
  (slot player_id (type STRING))
  (multislot applied_to)) ; 记录该抗性已应用到哪些伤害事实

(defrule damage-resistance
   ?d <- (damage (source ?source) (target ?target) (amount ?amount) (type ?type) (id ?id))
   ?r <- (resist (type ?type) (factor ?factor) (player_id ?target) (applied_to $?applied))
   (test (not (member$ ?id ?applied)))
   =>
   (bind ?reduced-amount (* ?amount ?factor))
   (modify ?r (applied_to $?applied ?id)) ; 标记该抗性已应用此伤害
   (modify ?d (amount ?reduced-amount))
)

(assert (damage (source "1") (target "2") (amount 10.0) (type "physical") (id 0)))
(assert (damage (source "1") (target "2") (amount 5.0) (type "physical") (id 1)))
(assert (resist (type "physical") (factor 0.9) (player_id "2")))

方案分析与优化建议

你的方案是完全可行的,也是CLIPS中处理这类"避免重复匹配"场景的常规思路。除此之外,还有两种更简洁的优化方向:

1. 标记伤害为"已处理对应抗性"

不需要在抗性事实里追踪伤害ID,转而给伤害模板加一个多槽,记录已经应用过的抗性类型或ID,从伤害端控制重复匹配:

(deftemplate damage
  (slot source (type STRING))
  (slot target (type STRING))
  (slot amount (type FLOAT))
  (slot type (type STRING))
  (multislot processed_resists)) ; 记录已应用的抗性类型

(deftemplate resist
  (slot type (type STRING))
  (slot factor (type FLOAT))
  (slot player_id (type STRING)))

(defrule damage-resistance
   ?d <- (damage (source ?source) (target ?target) (amount ?amount) (type ?d_type) (processed_resists $?processed))
   ?r <- (resist (type ?r_type) (factor ?factor) (player_id ?target))
   (test (eq ?d_type ?r_type))
   (test (not (member$ ?r_type ?processed)))
   =>
   (bind ?reduced-amount (* ?amount ?factor))
   (modify ?d (amount ?reduced-amount) (processed_resists $?processed ?r_type))
)

这种方式更贴合"伤害是被作用对象"的逻辑,尤其是当抗性类型唯一时,不需要额外维护伤害ID,代码更简洁。

2. 生成新事实而非修改原事实

如果不需要保留原伤害数值,可以创建一个reduced-damage事实来存储最终结果,原damage和resist事实保留(或处理后撤回),从根源避免循环触发:

(deftemplate damage
  (slot source (type STRING))
  (slot target (type STRING))
  (slot amount (type FLOAT))
  (slot type (type STRING)))

(deftemplate resist
  (slot type (type STRING))
  (slot factor (type FLOAT))
  (slot player_id (type STRING)))

(deftemplate reduced-damage
  (slot source (type STRING))
  (slot target (type STRING))
  (slot original-amount (type FLOAT))
  (slot final-amount (type FLOAT))
  (slot type (type STRING)))

(defrule apply-resistance
   ?d <- (damage (source ?source) (target ?target) (amount ?amount) (type ?type))
   ?r <- (resist (type ?type) (factor ?factor) (player_id ?target))
   ; 确保该伤害还没生成过减免结果
   (not (reduced-damage (source ?source) (target ?target) (original-amount ?amount)))
   =>
   (bind ?reduced-amount (* ?amount ?factor))
   (assert (reduced-damage (source ?source) (target ?target) (original-amount ?amount) (final-amount ?reduced-amount) (type ?type)))
   ; 如果是一次性抗性,这里可以撤回?r;常驻抗性则保留
   ; (retract ?r)
   (retract ?d) ; 撤回原伤害事实,避免重复处理
)

这种方式适合不需要修改原数据的场景,规则触发后直接生成最终结果,逻辑更清晰,也不会出现循环问题。


总结

  • 如果抗性是常驻型(比如角色永久拥有的物理抗性),推荐用你的原始方案或优化方案1,通过标记机制避免重复匹配;
  • 如果抗性是一次性消耗型(比如临时护盾),可以考虑方案2,处理后撤回对应抗性事实,同时保证每个伤害只生成一次减免结果。

内容的提问来源于stack exchange,提问作者Timothy O'Brien

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 20:52:07