黔东南苗族侗族自治州网站建设_网站建设公司_在线商城_seo优化
2025/12/18 18:57:36 网站建设 项目流程

模糊逻辑初识

本文内容算是对 《Game Programming Gems》《Game Programming Gems 2》 模糊逻辑相关章节的“复述”。

文中会提及一些数学公式和函数、图表等,但请放心,它们都很简单,你一定能看懂!

模糊集合

传统的布尔逻辑用于表达 「非黑即白」,但在游戏开发中有些概念往往需要 量化。像是何谓“远”的问题,它可以是“有些远”,也可以是“遥不可及”……像这类需要描述所处状态的程度,就可以用 模糊逻辑

模糊逻辑使用 模糊集合 来描述这类情况。与之相对的是「经典集合」,经典集合中事物只能属于或不属于,而不能部分属于。

模糊集合中包含哪些元素,是通过设计好的隶属函数计算出的。隶属函数会接受一个输入值并返回一个介于0~1之间的返回值来表达这个状态的 隶属程度,这也就是「模糊化」过程。例如,我们可以这样表达“远距离”:

image

从100m开始摸到了“远”的边,150m表示“距离有点远”,200m及以后就是完全的“远距离”了。这就是 模糊集合,它不仅像传统集合一样包含完全“远距离”,还包含了其他程度的“远”。

隶属函数的设计并不受限制,像这个三角形一样的隶属函数是很常见的(三角模糊数),后面我们都用它来举例,它的表达也很简单:

image

我们知道,传统集合可以进行 and/or/not (与/或/非)操作。模糊集合一样也可以,例如我们还有一个模糊集合,表示“大约150m距离”:

image

对于“与”逻辑:"远且大约150m"的距离 就可以是下图红色部分的集合。从图像上看,是取了二者的重叠部分;从数值上看,是取了二者中 隶属度较小的部分

image

对于“或”逻辑:“远或大约150m”的距离 就可以是下图红色部分的集合。从图像上看,是二者最终的合并;从数值上看,是取了二者中 隶属度较大的部分

image

P.S: 上述两者就是 扎德算子 的表达方式。

image

对于“非”逻辑:“不远的距离”如下图所示,也比较好理解,都变成了 1-y 的值:

image

模糊计算

知道了这些操作后,我们就可以来进行 「模糊计算」 了。来看看这种情况,依据对方血量、自身血量、敌我距离来设计一个敌人的攻击决策方式,它总共有3种行为:全力进攻、且战且退、逃跑。所依据的3种条件用模糊逻辑表示。

对方血量、自身血量,都可以分为3种情况:血量健康、血量良好、血量危险,由于都是血量,这两个条件可以用同种表达方式:

image

敌我距离也分3种情况:近、中、远,表示如下:

image

敌人的攻击行为与这3类变量的模糊逻辑规则如下,意为同时满足三种指定条件后,攻击行为的形式:

自身血量 敌人血量 距离 攻击性
健康 健康 且战且退
健康 健康 且战且退
健康 健康 全力进攻
健康 良好 且战且退
健康 良好 全力进攻
健康 良好 全力进攻
健康 危险 全力进攻
健康 危险 全力进攻
健康 危险 全力进攻
…… …… …… ……
良好 健康 且战且退
良好 危险 且战且退
…… …… …… ……
危险 健康 逃跑
危险 健康 逃跑
危险 健康 且战且退

这就是「模糊规则库」,其中的每一行都是一条 「模糊规则」。上表只列出了部分规则,完整的规则数量应该是 \(3^3 = 27\) 个。现在我们给出一个具体情况:自身血量 77、敌人血量 20、敌我距离 8.5,并将其代入到模糊集合里看看具体各情况的隶属度。

  1. 自身血量的3种情况隶属度大致取值:血量危险0%、血量良好20%、血量健康50%
image
  1. 敌人血量的3种情况隶属度大致取值:血量危险60%、血量良好10%、血量健康0%
image
  1. 敌我距离的3种情况的隶属度大致取值:近80%、中0%、远0%
image

忽略0%的情况,共有 \(2 \times 2 \times 1 = 4\) 种规则被触发:

自身血量 敌人血量 距离 攻击性
良好(20%) 良好(10%) 近(80%) 且战且退
良好(20%) 危险(60%) 近(80%) 且战且退
健康(50%) 良好(10%) 近(80%) 且战且退
健康(50%) 危险(60%) 近(80%) 全力进攻

同时满足三种条件就是进行了 逻辑与,以第一行触发的规则为例,我们可以计算出「且战且退」的隶属度为10%。为什么?因为 扎德与算子,模糊逻辑的与运算就是取最小隶属度,20%、10%、80%中最小的自然就是10%了。我们同样处理下表格种剩下的3种规则:

自身血量 敌人血量 距离 攻击性
良好(20%) 良好(10%) 近(80%) 且战且退(10%)
良好(20%) 危险(60%) 近(80%) 且战且退(20%)
健康(50%) 良好(10%) 近(80%) 且战且退(10%)
健康(50%) 危险(60%) 近(80%) 全力进攻(50%)

模糊逻辑推理中,多条规则若输出相同结果,它们之间是逻辑或,需按 扎德或算子(取最大值 max) 合并隶属度 —— 意思是 “只要有一条规则触发该输出,最终输出强度取所有触发规则中的最强值”。

所有输出「且战且退」的规则有 3 条,隶属度分别是 10%、20%、10%;或运算合并:max (10%, 20%, 10%) = 20%。这是「且战且退」的最终综合隶属度;

输出「全力进攻」的规则仅 1 条,隶属度 50%,直接保留为最终综合隶属度。

最后,我们可以通过去模糊化来算出具体的攻击性的数值,假设攻击性的隶属度函数如下:

image

去模糊化的方法有很多,这类采用一种简单好用的质心法(加权平均)。在上图中,逃跑所构成的三角形函数三个顶点所在x轴上的值为[0, 20, 45],且战且退为[30, 50, 70],全力进攻为[55, 75, 100]

\[攻击性 = \frac{0.2 \times \frac{30+50+70}{3} + 0.5 \times \frac{55+75+100}{3}}{0.2+0.5} ≈ 69 \]

在上述式子中,利用到了三角形质心的计算方式,三角形质心在 (a+b+c)/3。但其实当三角形近似等腰时,我们可以直接取 b 所在位置,例如上述的 \(\frac{55+75+100}{3}\) 可以直接用 75 代替,计算出的结果差距不大。


现在我们回顾一下整个过程,我们起初根据具体的自身血量、敌人血量、敌我距离,算出了这三种变量在各自模糊集合的隶属度,然后结合模糊规则得出了攻击性的模糊集合,最后去模糊化得到了具体的攻击性的数值。

至于这个数值的具体运用,我们可以将它作为多行为的加权融合权重,行为混合、动画混合、决策树加权等。


Combs方法

可能大家隐约注意到了模糊逻辑的规则数量随着变量的增加可能会急剧增长,先前我们举的例子中,有3种变量,每个变量有3种集合,就有 \(3^3\) 种可能规则,这是指数增长变化。好在使用 「Combs方法」 能将其转回线性增长。

在游戏AI中,用于决策的变量之间强相关的情况并不常见,以之前的例子来说,自身血量、敌人血量、敌我距离可以看作是相互独立的,这也就满足了 Combs方法 使用条件。它的思想基于这样一则逻辑命题:

\[“如果 (p 且 q) 则 r” 等价于 “(如果 p 则 r )或(如果 q 则 r)” \]

Combs方法不通过多变量之间的组合,而是假设其他变量处于良好水平(不极端),设计当前变量对行为的独立影响。因此,我只需要为每种变量的每种集合设计对应行为规则就行:

自身血量 攻击性
健康 全力进攻
良好 且战且退
危险 逃跑
敌人血量 攻击性
健康 逃跑
良好 且战且退
危险 全力进攻
敌我距离 攻击性
且战且退
且战且退
全力进攻

现在我们试试用Combs方法设计的规则处理我们之前的例子。回顾下,我们假设了具体情况:自身血量 77、敌人血量 20、敌我距离 8.5。并代入各自隶属度函数中,算得了如下结果:

  1. 自身血量:血量危险0%、血量良好20%、血量健康50%
  2. 敌人血量:血量危险60%、血量良好10%、血量健康0%
  3. 敌我距离:近80%、中0%、远0%

在Combs方法下就是这样:

自身血量 攻击性
健康(50%) 全力进攻(50%)
良好(20%) 且战且退(20%)
敌人血量 攻击性
良好(10%) 且战且退(10%)
危险(60%) 全力进攻(60%)
敌我距离 攻击性
近(80%) 且战且退(80%)

那最后攻击性的情况如何呢?要注意,这时Combs方法与通常方法的不一样的地方,它将同类型的结果进行逻辑或。因为Combs方法把规则解释为“独立证据的累积”,而非传统方法的“联合条件必须同时满足”。

  1. 且战且退 = max(20%, 10%, 80%) = 80%
  2. 全力进攻 = max(50%, 60%) = 60%

\[攻击性 = \frac{0.8 \times \frac{30+50+70}{3} + 0.6 \times \frac{55+75+100}{3}}{0.8+0.6} ≈ 61 \]

对比传统方法计算出的69,二者结果是相近的,不会对决策带来太大影响。考虑到Combs方法大大减少了规则数量,这点是可以接受的。

示例:模糊状态机(FuSM)

众所周知,有限状态机同时只能处于一个状态中。而模糊状态机则允许同时处于多个当前状态中,并允许根据所处状态的不同隶属度作出不同的行为,这尤为适合于表示NPC的情绪状态等。

模糊状态机的实现方式多种多样,任何包含了模糊逻辑的状态机都可以称为模糊状态机。模糊状态机可以看作是有限状态机的一个分化,并不是它的上位替代,二者功能上其实有一定差距,模糊状态机并不适合像有限状态机一样在内部直接触发具体动作,而是适合内部决策、外部执行。

接下来,用一个使用模糊状态的状态机来描述一个NPC的愤怒情绪。

首先,先定义模糊状态的接口,它的内容十分简单:

public interface IFuSMState
{/// <summary>/// 隶属值范围下限/// </summary>float LowRange { get; set; }/// <summary>/// 隶属值范围上限/// </summary>	   float HighRange { get; set; }/// <summary>/// 隶属度(0-100百分比)/// </summary>float MembershipDegree { get; set; }/// <summary>/// 判断输入值是否属于当前状态的隶属值范围,并计算隶属度/// </summary>/// <param name="inputValue">输入值</param>/// <returns>是否属于当前状态</returns>bool DoTransition(float inputValue);void OnUpdate();
}

然后是模糊状态机类,由于它有多个当前状态,因此用列表记录当前状态。我们采用 <System.Type, IFuSMState> 作为键值对的字典来记录所有的状态:

using System.Collections.Generic;public class FuSM<T> where T : IFuSMState
{public Dictionary<System.Type, T> StateTable{ get; protected set; } //状态表public List<T> curStates; //当前状态public FuSM(){StateTable = new Dictionary<System.Type, T>();curStates = new List<T>();}public void AddState(T state){StateTable.Add(state.GetType(), state);}/// <summary>/// 处理输入值,更新活跃状态列表/// </summary>public void StateTransition(float inputValue){// 清空当前活跃状态curStates.Clear();// 遍历所有状态,判断是否属于当前输入的隶属范围foreach (var state in StateTable.Values){if (state.DoTransition(inputValue)){curStates.Add(state);}}}public void OnUpdate(){for(int i = 0; i < curStates.Count; ++i){curStates[i].OnUpdate();}}
}

我们拟定用状态机描述这样的情绪,总共5种模糊集合,其中3种使用三角模糊数:

image

现在将它们分别用模糊状态表示,可以先设计一个模糊状态基类:

public class Fus_BaseState : IFuSMState
{public float LowRange { get; set; }public float HighRange { get; set; }public float MembershipDegree { get; set; }// 传入隶属度函数的区间范围public Fus_BaseState(float lowLimit, float hgihLimit){LowRange = lowLimit;HighRange = hgihLimit;}// 默认置零且无法转换public virtual bool DoTransition(float inputValue){MembershipDegree = 0;return false;}public virtual void OnUpdate(){;}
}

对于第一个模糊集合「不在意」,其实很简单,就让传入值在\([0,10]\)时,隶属度设为100%,其他情况设为0就行:

using UnityEngine;public class FuS_Uncaring : Fus_BaseState
{public FuS_Uncaring(float lowLimit, float hgihLimit) : base(lowLimit, hgihLimit){}public override bool DoTransition(float inputValue){if(inputValue < LowRange || inputValue > HighRange){return base.DoTransition(inputValue);}MembershipDegree = 1;return true;}public override void OnUpdate(){Debug.Log("不在意");}
}

第二个「恼火」是三角模糊数,之前我们已经知道了它的数学表达,实现起来也很简单:

using UnityEngine;public class FuS_Annoyed : Fus_BaseState
{protected float topX;protected float leftRange, rightRange;public FuS_Annoyed(float a, float b, float c) : base(a, c){//LowRange 和 HighRange 分别记录了a和c的值,再用一个成员变量记录b的值topX = b;//预计算 b - a 以及 c - b 的结果leftRange = b - a; rightRange = c - b;}public override bool DoTransition(float inputValue){if(inputValue <= LowRange || inputValue >= HighRange){return base.DoTransition(inputValue);}if(inputValue < topX){MembershipDegree = (inputValue - LowRange) / leftRange;}else{MembershipDegree = (HighRange - inputValue) / rightRange;}return true;}public override void OnUpdate(){Debug.Log($"恼火:{MembershipDegree}");}
}

剩下的状态就简单了,分别对应继承「恼火」或「不在意」就行(毕竟是同类型的函数

using UnityEngine;public class FuS_Mad : FuS_Annoyed
{public FuS_Mad(float a, float b, float c) : base(a, b, c){}public override void OnUpdate(){Debug.Log($"生气:{MembershipDegree}");}
}public class FuS_Rage : FuS_Annoyed
{public FuS_Rage(float a, float b, float c) : base(a, b, c){}public override void OnUpdate(){Debug.Log($"愤怒:{MembershipDegree}");}
}public class FuS_Berserk : FuS_Uncaring
{public FuS_Berserk(float lowLimit, float hgihLimit) : base(lowLimit, hgihLimit){}public override void OnUpdate(){Debug.Log("怒不可遏");}
}

可以看到,所有状态的 OnUpdate 逻辑都很简单,只是打印了当前状态名和隶属度而已,所以接下来的实际调用也就没额外进行外部处理了。

最后再用一个继承了 MonoBehaviour 的类写调用逻辑并在Unity中运行:

using UnityEngine;public class FuSM_Test : MonoBehaviour
{[Range(0, 100f)]public float inputValue = 0; //运行时修改该值观察变化private FuSM<Fus_BaseState> fuSM;private void Awake() {fuSM = new FuSM<Fus_BaseState>();fuSM.AddState(new FuS_Uncaring(0, 10f));fuSM.AddState(new FuS_Annoyed(10f, 25, 40f));fuSM.AddState(new FuS_Mad(35f, 55f, 75f));fuSM.AddState(new FuS_Rage(60f, 75f, 90f));fuSM.AddState(new FuS_Berserk(90f, 100f));}private void Update() {fuSM.StateTransition(inputValue);fuSM.OnUpdate();	}
}

尾声

模糊逻辑填补了传统逻辑的短板。人们决策常基于 “有点 / 比较 / 很” 等模糊描述,模糊逻辑用 “隶属度” 量化这些描述,让系统决策更符合人类直觉。

其“多集合同时隶属”的性质也有利于控制的连续平滑过渡。《Game Programming Gems 6》中还提到利用模糊逻辑控制LOD以降低场景几何复杂度的方法。

模糊逻辑还能大大简化规则的设计复杂度,像是用 “冷 / 凉爽 / 温暖”3 个模糊集合配合各自的隶属度就足以替代数十个温度区间的离散规则(利用Combs方法甚至可以更简洁)。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询