实测分享“微乐天津麻将为什么老是一直输”原来确实有挂
tqq005
2025-10-11 02:04:50
您好:微乐天津麻将这款游戏可以开挂,确实是有挂的,需要软件加微信【69174242】,很多玩家在微乐天津麻将这款游戏中打牌都会发现很多用户的牌特别好,总是好牌,而且好像能看到-人的牌一样。所以很多小伙伴就怀疑这款游戏是不是有挂,实际上这款游戏确实是有挂的


1.微乐天津麻将
这款游戏可以开挂,确实是有挂的,通过添加客服微信【69174242


2.在"设置DD功能DD微信手麻工具"里.点击"开启".


3.打开工具加微信【69174242】.在"设置DD新消息提醒"里.前两个选项"设置"和"连接软件"均勾选"开启"(好多人就是这一步忘记做了)


4.打开某一个微信组.点击右上角.往下拉."消息免打扰"选项.勾选"关闭"(也就是要把"群消息的提示保持在开启"的状态.这样才能触系统发底层接口。)


【央视新闻客户端】

【USparkle专栏】如果你深怀绝技,爱“搞点研究”,乐于分享也博采众长,我们期待你的加入,让智慧的火花碰撞交织,让知识的传递生生不息!

这是侑虎科技第1889篇文章,感谢作者狐王驾虎供稿。欢迎转发分享,未经作者授权请勿转载。如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:793972859)

一、前言

像先前提到的有限状态机、行为树、HTN,它们实现的AI行为,虽说能针对不同环境作出不同反应,但应对方法是写死了的。有限状态机终究是在几个状态间进行切换、行为树也是根据提前设计好的树来搜索……你会发现,游戏AI角色表现出的智能程度,终究与开发者的设计结构有关,就有限状态机而言,各个状态如何切换很大程度上就影响了AI智能的表现。

那有没有什么决策方法,能够仅需设计好角色需要的动作,而它自己就能合理决定要选择哪些动作完成目标呢?这样的话,角色AI的行为智能程度会更上一层楼,毕竟它不再被写死的决策结构束缚;我们在添加更多AI行为时,也可以简单地直接将它放在角色需要的动作集里就好,减少了工作量,不必像行为树那样,还要考虑节点间的连接。

没错,GOAP(目标导向型行为规划)就可以做到。但请注意,并不是说GOAP就比其它决策方法好,后面也会提到它的缺点。选择何种决策方法还得根据实际项目和自身需求。

2. 知道A星寻路的流程,如不了解可看此视频[1]。

3. 基本的位运算与位存储(能做到理解Unity中的Layer和LayerMask的程度就行)。

二、运行逻辑

我们来看个简单的寻路问题:你能找到从A到B的最短路线吗?注意,道路是单向的。

现在,加大难度,假设每条道路口都有一个门,红色表示门关上了,蓝色表示门开着,你还能找出可达成的最短A到B路线吗?

这样就足够了,GOAP的规划就是这么一个过程。只是把每个节点都当成一个状态,每条道路都当作一个动作、道路长度作为动作代价、路口的门作为动作执行条件,然后像你这样寻找出一条可以执行的最短「路线」,并记录下途径的道路(注意,不是节点),这样就得到了「动作序列」,再让AI角色逐一执行。GOAP中的图会长成下面这样(只画出了一条路的样子,但相信你们能举一反三的):

GOAP就是在不断执行「从现有状态到目标状态」,上图中的「现有状态」和「目标状态」分别就是「饿」和「饱」。请注意,虽说用了不同形状,但中间的那些椭圆节点,比如「在上网」,也是和「饿」、「饱」同类别的存在。也就是说「在上网」也可以作为现有状态或目标状态。

可想而知,只要状态够多,动作够多,AI就能做出更复杂的动作。虽说这对其它决策方法也成立,但GOAP不需要我们手动设置各动作、状态之间的关系,它能自行规划出要做的一系列动作,更省事且更智能,甚至可以规划出超出原本设想但又合理的动作序列。

希望我讲明白了它的运作(如果还是感觉有点不懂,可以看看这个视频[2]),下面一起来实现一个简单的GOAP进一步了解吧!顺带提一下,在Unity资源商店有免费的GOAP插件,并且做了可视化处理以及多线程优化,各位真的想将GOAP运用于项目的话,更推荐去学习使用成熟的插件。

三、代码实现

本文「世界状态」的实现参考了GitHub上一C语言版本的GOAP[3]。

所谓「世界状态」其实就是存储所有的状态放在一块儿的合集。而状态其实还有一个隐藏身份——动作条件。是的,状态也充当了动作的执行条件,比如之前图中的条件「有流量」,它其实也是一个状态。

世界状态会因自然因素变化,比如「饱」会随着时间流逝而变「饿」;也会因角色自身的一些动作导致变化,比如一个角色多运动,也会使「饱」变「饿」。

1. GOAP规划需要时时获取最新的状态,才能保证规划结果的合理性(否则饿晕了还想着运动);

2. 「世界状态」中有些状态是「共享」的,比如之前说的时间,但还有一些状态是私有的,比如「饱」,是我饱、你饱还是他饱?在一个合集里该如何区分?

如果你看过上一篇关于HTN的文章的话,你会发现这是如此的眼熟。不过没看过也没关系,我们将采取一种新的实现「世界状态」的方法——原子表示。

PS:在传统人工智能Agent中,对于环境的表示方式有三种:

1. 原子表示(Atomic):就是单纯描述某个状态有无,通常每个状态都只用布尔值(True/False)表示就可以,比如「有流量」。

2. 要素化表示(Factored):进一步描述状态的具体数值,这时,状态可以有不同的类型,可以是字符串、整数、布尔值……在HTN中,我们就是用这种方式实现的。

3. 结构化表示(Structured):再进一步,每个状态不但描述具体数值,还存储于其它数据的连接关系,就像数据结构中「图」的节点那样。

接下来将采用位存储的方式进行原子表示,因为借助位运算可以方便且高效地实现比较,还省空间。缺点就是有些难懂,所以,我希望你了解如int、long的二进制存储方式或者Unity中LayerMask,再来看以下内容。当然,这段代码之后我也会做些举例说明,这个类还继承了三个接口,其用意也会在后面解释:

我们以添加两个状态为例,相信看了这个,你会更容易理解相关函数的内容。虽说总共有64位世界状态,但这里只看4位:

将世界状态分为「私有」和「共享」,我们就可以让角色更新「私有」部分,而全局系统更新「共享」部分。当需要角色规划时,我们就用位运算将该角色的「私有」与世界的「共享」进行整合,得到对于这个角色而言的当前世界状态。这样对于不同角色,它们就能得到对各自的而言的世界状态啦!

如果去除注释,这个类的内容其实并不多,在使用时几乎只要用到SetAtomValue函数,像这样:

worldState = new GoapWorldState(); worldState.SetAtomValue("血量健康", true); worldState.SetAtomValue("大半夜", false, true);

接下来就是那三个接口了,首先是IAStarNode ,前文稍提过:「世界状态」是图中的结点,「动作」都是图中的边,这是我用以辅助「泛用A星搜索器」的结点接口,本文就不赘述了,只要知道:继承了这个类,都可以作为A星搜索中的结点,从而参与搜索。完整代码如下:

这段代码的注释也说明了另外两个接口的用意。

我们之前说过,动作包含一个「前提条件」,其实和HTN一样,它还包含一个「行为影响」,相当于之前图中道路指向的椭圆表示的状态。它们也都是世界状态,注意是世界状态,而不是单个状态!

为什么不设置成单个?首先,「前提条件」和「行为影响」本身就可能是多个状态组合成的,用单个不合适;其次,将它们也设置成世界状态(64位的long类型),方便进行统一处理与位运算。Unity中的Layer也是这样的。

只有当前世界状态与「前提条件」对应位的值相同时,才算满足前提条件,这个动作才有被选择的机会。而动作一旦执行成功,世界状态就会发送变化,对应位上的值会被赋值为「行为影响」所设置的值。

/// /// Goap动作,也是Goap图中的边 /// publicclassGoapAction { publicint Cost{ get; privateset; } //动作代价,作为AI规划的依据 public GoapWorldState Precondition = precondition; public GoapWorldState Effect = effect; privatereadonly GoapWorldState precondition; //动作得以执行的前提条件 privatereadonly GoapWorldState effect; //动作成功执行后带来的影响,体现在对世界状态的改变 /// /// 根据给定世界状态样式创建「前提条件」和「行为影响」, /// 这为了让它们的位与世界状态保持一致,方便进行位运算 /// /// 作为基准的世界状态 /// 动作代价 public GoapAction(GoapWorldState baseState, int cost = 1) { Cost = cost; precondition = new GoapWorldState(baseState); effect = new GoapWorldState(baseState); } /// /// 判断是否满足动作执行的前提条件 /// /// 当前世界状态 /// 是否满足前提 public bool MetCondition(GoapWorldState worldState) { var care = ~precondition.DontCare; return (precondition.Values care) == (worldState.Values care); } //--------------------------------------------------------------- /// /// 判断世界状态是否可由执行影响导致 /// /// 当前世界状态 /// 是否能导致 public bool MetEffect(GoapWorldState worldState) { var care = ~effect.DontCare; return (effect.Values care) == (worldState.Values care); } //---------------------------------------------------------------- /// /// 动作实际执行成功的影响 /// /// 实际世界状态 public void Effect_OnRun(GoapWorldState worldState) { worldState.Values = ((worldState.Values effect.DontCare) | (effect.Values ~effect.DontCare)); } /// /// 设置动作前提条件,利用元组,方便一次性设置多个 /// public GoapAction SetPrecontidion(params (string, bool)[] atomName) { foreach(var atom in atomName) { precondition.SetAtomValue(atom.Item1, atom.Item2); } returnthis; } /// /// 设置动作影响 /// public GoapAction SetEffect(params (string, bool)[] atomName) { foreach (var atom in atomName) { effect.SetAtomValue(atom.Item1, atom.Item2); } returnthis; } public void Clear() { precondition.Clear(); effect.Clear(); } }

你可能发现了这个动作类的奇怪之处——它没有像OnRunning或OnUpdate之类的动作执行函数,这样一来要如何执行动作?是的,这个类主要是用来充当图的边,来连接各个状态,它会作为 字典中的值,并于一个动作名字符串绑定。我们会通过动作名,再查找另一个同样以动作名为键、但值为事件的字典,找到对应的事件,这个事件才是真正运行的动作函数。

这样岂不多此一举?其实这是为了提高GOAP图的重用性。如果GOAP中的道路并不是真正的动作函数,而是用了动作名来标记。那么我们可以为多个角色设计同一种动作,但不同的表现。比如「攻击」动作,在弓箭手中就是射击函数,枪手中就是开火函数……这样一来,即便不同角色都可以使用同一张GOAP图,不用重复创建(除非有特殊需求)。

这样是GOAP的一般做法,只用少数GOAP图,而不同角色可以共同使用一张GOAP图来进行互不干扰的规划。这可以省很多代码量,试想在有限状态机中,不做特殊处理你都无法让不同敌人共用「攻击」状态,就得不断写大同小异的代码。GOAP的这种将结构与逻辑分离的做法,就可以很方便地复用结构或进行定制化设计,也是其优势之一。

PS:GOAP图也得用「图」这一数据结果存储,而这种数据结构在C# 中是没有提供的,得自己实现,这里我给个简单的,方便后续其他功能(如果你有自己的一套,也可以用自己的,只是后续文章中相应的函数要进行替换):

接下来要实现的就是那三个接口所需的函数了,这三个接口其实都是为了方便寻找「路径」,GOAP会采用启发式搜索,就像A星寻路所用的那样。所谓「启发式搜索」就是有按照一定「启发值」进行的搜索,它的反面就是「盲目搜索」,如深度优先搜索、广度优先搜索。启发式搜索需要设计「启发函数」来计算「启发值」。

在A星寻路中,我们通过计算「当前位置离起点的距离 + 当前位置离终点的距离」做为启发值来寻找最短路径;类似的,在我们实现的这个GOAP中,我们会通过计算「起点状态至当前状态累计的动作代价+ 当前状态与目标状态的相关度」作为启发值。

累计代价,也相当于与起始状态的「距离」;与目标状态的相关度,在世界状态类中已经说明了,就是比较当前状态与目标状态的有效位的值有多少是相同的,通常相同的越多就越接近。当然,思路不唯一,可以搜索《数据挖掘》相关的文章,了解更多关于数据相关度的计算。

PS:在寻路时,常需要选取已探索过的节点中具有最小启发值的节点。用遍历倒也能做到,但总归效率不高,故可以用「堆」,也就是「优先队列」:

照理说,动作集不过是动作的合集,单独将它也制成一个类,是为了方便「动作序列」规划,主要体现在GetPossibleTrans函数,根据传入的节点的世界状态,在合集中遍历出「前提条件」满足的动作:

一切条件都准备好了,现在实现下用来「寻路」的类。首先,我们会进行反向搜索,意思是说,我们不会「起始状态--目标状态」,而是「目标状态--起始状态」,如果成功找到,就将得到的动作序列逆向执行。

为什么这么麻烦?其实恰恰相反,这还是一种简化。如果真的「起始状态--目标状态」,未必最终会找到目标状态(因为有可能能抵达的动作暂时条件不满足);但反向搜索,必定会包含目标状态,也一定会找到一条路(因为总会抵达一个当前已经符合的世界状态,否则就是设计的有问题了),只不过可能不是最短的。

我们也能接受这种结果,虽说非最优解,但这种不确定因素,也变相让AI增加了点随机性,更接近真实决策情况。

它的整体搜索过程和A星寻路是一样的,直接用「泛用A星搜索器」即可:

我们最后创建一个「代理器」,它用来整合了上述内容,并统筹运行:

注意,代码里的这个部分,因为A星搜索得到的是结点——也就是状态,但我们所需要的是链接状态的动作,所以要再「加工」一下:

goapAStar.FindPath(goal, curSelfState, path); //通过状态序列得到动作序列 path.TryPop(out var cur); while(path.Count != 0) { actionPlan.Push(actionSet.GetTransAction(cur, path.Peek())); cur = path.Pop(); }

这个类中,RunPlan函数与上一期的HTN中的基本一样。但我想可能有些人还不大明白UpdateSelfState函数是如何融合自身状态与世界状态的,我就简单举个例:

可以看到得到的值,恰好保留了世界状态的共享部分和自身状态的私有部分。其实这也并非「恰好」,这样的位运算理应得到这样的结果才是。你也可以自己动手尝试一些值或者用更多位的数来验证。

四、尾声

GOAP的缺点主要是在设计难度上,它的设计相较FSM、行为树那些不那么直接,你需要把控好动作的条件和影响对应的状态,比其它决策方法更费脑子些。因为GOAP没有显示的结构,如何定义好一个状态,使它能在逻辑层面合理地成为一个动作的前提条件,又能成为另一个动作条件的影响结果(比如「有流量」,想想看,将其做为条件可以设计什么动作?作为影响结果又应该怎么设计呢?)是比较考验开发人员的架构设计的。但毋庸置疑的是,在面对较复杂的AI时,它的代码量一定是小于FSM、行为树和HTN的。而且添加和减少动作也不需要进行过多代码修改,只要将新行动加入到动作集或将欲剔除的动作从动作集中删去就可以,这也是它没有显式结构的好处。

这里也简单用上文所学内容做一个简单的太空射击飞船敌人的AI:gitee项目[4]

在EnemyConfig中为敌人指定了GOAP图并共用,一个非常简单的敌人逻辑(只是用GOAP实现了而已):当敌人健康时会尝试瞄准玩家后射击,当玩家弱势(无力)时,敌人追击玩家;当敌人自身不安全时会退避并以较低命中率的方式射击:

goal = new GoapWorldState(WarSpaceManager.worldState); goal.SetAtomValue("击杀玩家", true); actionSet = new GoapActionSet(); actionSet .AddAction("低命中射击", new GoapAction(WarSpaceManager.worldState, 1) .SetPrecontidion(("安全区内", true)) .SetEffect(("击杀玩家", true))) .AddAction("追击", new GoapAction(WarSpaceManager.worldState, 4) .SetPrecontidion(("弹药充足", true), ("玩家无力", true)) .SetEffect(("击杀玩家", true))) .AddAction("瞄准玩家", new GoapAction(WarSpaceManager.worldState, 3) .SetPrecontidion(("瞄准就绪", false)) .SetEffect(("瞄准就绪", true))) .AddAction("射击", new GoapAction(WarSpaceManager.worldState, 2) .SetPrecontidion(("瞄准就绪", true)) .SetEffect(("击杀玩家", true))) .AddAction("躲避", new GoapAction(WarSpaceManager.worldState, 1) .SetPrecontidion(("安全", false)) .SetEffect(("安全区内", true)));

https://www.bilibili.com/video/BV147411u7r5?p=1vd_source=c9a1131d04faacd4a397411965ea21f4

https://www.bilibili.com/video/BV1iG4y1i78Q/?spm_id_from=333.1007.top_right_bar_window_history.content.clickvd_source=c9a1131d04faacd4a397411965ea21f4

https://github.com/stolk/GPGOAP

https://gitee.com/OwlCat/some-projects-in-tutorials/tree/master/GOAP

文末,再次感谢狐王驾虎 的分享, 作者主页:https://home.cnblogs.com/u/OwlCat, 如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群: 793972859 )。

【万象更新】

【万象更新】

【万象更新】

【万象更新】

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关内容

热门资讯

重大通报“多乐跑胡子其实有透视... 您好:多乐跑胡子这款游戏可以开挂,确实是有挂的,需要软件加微信【6355786】,很多玩家在多乐跑胡...
玩家实测“新乐游其实有透视辅助... 您好:新乐游这款游戏可以开挂,确实是有挂的,需要软件加微信【6355786】,很多玩家在新乐游这款游...
分享实测“518互游其实有透视... 您好:518互游这款游戏可以开挂,确实是有挂的,需要软件加微信【5902455】,很多玩家在518互...
重磅.通报(红龙扑克辅助神器)... 亲,红龙扑克这个游戏其实有挂的,确实是有挂的,需要了解加客服微信【2278274】很多玩家在这款游戏...
轻松掌握碧海麻将有没有挂吗,原... 轻松掌握碧海麻将有没有挂吗,原来确实有挂、【无需打开直接搜索微信【5382285】操作使用教程:1....