即时战略游戏的 AI 是怎样实现的?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了即时战略游戏的 AI 是怎样实现的?相关的知识,希望对你有一定的参考价值。

自己可以打开地图的jass脚本去看的,星际里也有script可以看。  War3时代,底层寻路是地图分Tile之后的A *寻路,上层逻辑估计就是FSM有限状态机,经过这些年发展,现在游戏里寻路还是基于导航网格(NavigationMesh)的A*,上层AI很多是Behavior Tree来实现的。  游戏和工程的AI目标完全不同的,游戏的AI是看起来聪明、表现多样;工程上我熟悉的多是为了解决组合爆炸问题,通过AI算法求解。但工程上的AI算法也有各种限制,例如遗传算法的过度收敛、收敛到局部解、神经网络的权重训练出来人不可理解、多少个神经节点能解决特定问题的没有定义,等等等等还有大量问题,这些对于游戏开发这种需要控制开发周期和确定性结果的工程设计都不利啊!所以游戏这种密集开发的软件工程,一是AI不需要这么复杂的算法就能实现,二是AI算法很多不稳定和难理解控制并不适合游戏快速迭代开发。

参考技术A

国内真正做过游戏AI的很少,说概念的人很多,所以看了半天离实际编码还是很远,不知道该怎么入手,因为国内游戏主要以MMO和卡牌为主,RTS比较少,体育竞技类游戏更少,没几个真正写过强AI代码的。而从AI的难度上来看,是:MMO < FPS < RTS < 体育竞技。作为实际开发过AI的人,拿一份五年前的代码,以最难的体育竞技类游戏为例,来科普一下,什么叫做游戏团队策略,什么叫做分层状态机?具体该如何落地到代码?如果你能实现体育竞技的AI,那即时战略只是小事一桩。  硬派游戏AI,不是虚无缥缈的神经网络,用神经网络其实是一个黑洞,把问题一脚踢给计算机,认为我只要训练它,它就能解决一切问题的懒人想法。更不是遗传算法和模糊逻辑,你想想以前8位机,16位机上就能有比较激烈对抗的足球游戏、篮球游戏,那么差的处理器能做这些计算么?  硬派游戏AI,就是状态机和行为树。状态机是基本功,行为树可选(早年AI没行为树这东西,大家都是hard code的)。大部分人说到这里也就没了,各位读完还是无法写代码。因为没有把最核心的三个问题讲清楚,即:分层状态机、决策支持系统、以及团队角色分配。每个人物身上,有三层状态机:基础层状态机、行为层状态机、角色层状态机。每一层状态机解决一个层次的复杂度,并对上层提供接口,上层状态机通过设置下层状态机的目标实现更复杂的逻辑。

参考技术B

每一层状态机为下一层设定一个目标,让下层自动工作,顶层角色层的目标则由最高层的团队ai进行战术指导。团队状态机跟据当前的游戏情况确定当前首要目标(进攻或者防守),又根据当前的势力图等信息,确定进攻或者防守的具体战略(比如中路突破、盘路包抄、下底传中等),最终为当前己方的所有角色分配一个新的任务,即设定角色层状态机的新目标,确定他是做主攻还是做助攻,还是联防还是策应。具体该怎么联防,怎么策应,那就是角色层状态机的事情了。其实团队AI没那么玄乎,任何问题就是一个编程的建模问题,而最复杂的体育竞技类游戏的AI策略,上文已经给出模型,相信各位略加修改即可使用。写状态机是游戏AI的硬功夫,如果状态机逻辑经常改变或者项目规模大了以后可以考虑引入决策树来控制状态机,程序提供一系列接口,然后用可视化的编辑器进行更改,感兴趣的人可以参考决策树相关文章。

游戏自动化测试——局内战斗

在这里插入图片描述
利用AI,使用正常的玩家客户端来打游戏是一种怎样的体验?

直接人都不用玩游戏了,放那,让AI帮你玩,过一会就看到自己分上去了。多爽,请代练的钱都省了,妥妥的。然而,我们今天是来讨论正经的技术问题。如何利用AI,实现我们的自动化测试!这才是我们的目的。

一场竞技游戏,我们在这里稍微划分一下,将其分为局外界面和局内战斗。举个例子,比如LOL,在大厅、组队、选英雄界面这些都是局外界面。当我们等待读完长长的进度条,进入到游戏,听到"咚咚咚咚咚"的声音后英雄出现到我们眼前,这个时候,你就已经到了局内战斗的部分里了。

1. 概述

本文主要对局内战斗的自动化进行讲解。包含如何实现战斗自动化的流程和脚本运行本身框架进行介绍,读者应具备一些python的编程经验和UI自动化的开发经验。特别的,有利用公司内部团队提供的AI进行决策操作。简单的来说就是我们把当前的局内情况告诉给AI,AI来想,返回决策,我们用自动化脚本,执行决策。但没有AI也一样是可以做自动化测试的,需要我们自己构建期望的流程,调用AI就像是条件反射,自我构建对应流程脚本就像是非条件反射。
对于游戏的UI自动化这里采用poco的unitysdk形式进行实现。

稍微介绍一下poco。这是一个类似appium的客户端UI自动化框架,利用界面元素来进行UI自动化操作。

官方文档:poco-chinese.readthedocs.io/zh_CN/lates…

元素来源于UITree,UITree源于接入unitysdk的游戏客户端。通过与外部挂载agent进行socket通信,从而将使用unity引擎游戏的UITree返回给外部供自动化相关。

UITree里面包含对应Tree中元素的所有属性信息,其中包含元素的position属性(位置信息),这也是我们得以锁定对应元素进行操作的核心依据。

通过pocosdk获取游戏客户端UI层信息,对UI信息中的具体内容做对应处理。

最后反馈对应数据给AI,由AI进行对应决策,将决策信息返回给本地后执行对应的UI自动化操作代码,从而完成决策,通过大量决策的实施,完成整局的实时操作。

再引入多轮重开检测机制,进而完成局内战斗的自动化对战。

  • 一些简单的演示:

在unity项目中,需要在unity中安装Poco的SDK Poco调用方法

import time
from poco.drivers.unity3d import  UnityPoco

poco = UnityPoco()

poco('btn_start').click()
time.sleep(1.5)

shell = poco('shell').focus('center')
for star in poco('star'):
    star.drag_to(shell)
time.sleep(1)

assert poco('scoreVal').get_text() == "100","score correct."
poco('btn_back',type = 'Button').click()

## Poco调用举例

poco = UnityPoco()    #1、通过接口调用unity中的pocosdk,SDK对整个ui树进行遍历,将dump后的json信息传出以供调用。

poco('btn_start').click()    #2、在得到的UI树中找到‘btn_start’的元素位置信息,通过adb进行点击操作。

2. 具体流程

在这里插入图片描述

  • 流程描述:

通过每次对当前UI数据的检查,判断其在局内还是局外。局外会进入到局内对局,处于局内则会检测当前局内状态,将当前的局内UI信息做整合,提取指定信息发送给AI决策服务,再根据AI的返回来进行对应的UI自动化代码操作。

3. 实现过程

3.1 UI层数据获取

1、使用poco.freeze,利用里面的agent dump把当前的UI树保存下来(因为要用到root节点,root节点下的内容无法用children获取)。

2、其他的数据使用poco.freeze调用正常的poco方法进行提取即可。

3.2 指定数据提取

1、确认好AI一共需要我们提供哪些数据,这些数据能不能从游戏的UI信息中读取出来。确认好后,我们就可以开始进行对应项的提取了。

2、要注意提取的UI元素是不是在特定UI中才会有,他能不能被容易的获取,也就是对提取指定信息的难易度和是否需要条件构建进行判断。

3、以自走棋游戏举例,对于棋子的提取会复杂一些,因为没有对应棋盘上每个格子的坐标,只有放在根目录下的美术资源。这部分的数据一个是需要存起来自己写方法提取,一个是要根据里面属性包含的信息判断其在场上还是在手牌。

目前这一部分的判断经历过三个阶段:

a. 靠pos属性判断。

b. 靠对pocosdk接入unity的c++源码进行修改添加的rawpos属性进行判断。

c. 靠这两者综合判断,保证不会有判断错的情况出现 。

3.3 数据接口请求过程

请求数据示范样本:

{“round_drawscount”: 0, “round_actionscount”:2, “round”:17,“level”:6, “experience”:2, “money”:33, “blood”:73,}

也就是包含一些基本玩家信息的json数据。

AI接口返回数据:

{‘eventId’: ACTION_UPGRADE}{‘eventId’: ACTION_DRAW}{‘eventId’:
ACTION_PICK,‘params’: {‘draw_cardIndex’: 0}}{‘eventId’:
ACTION_DROP,‘params’: {“hand_cardIndex”:0}}{‘eventId’: ACTION_SWAP,
‘params’:{“hand_cardIndex”:0,“play_listIndex”:1}}{‘eventId’:
ACTION_SWAP,
‘params’:{“hand_cardIndex”:2,“play_listIndex”:1}}{‘eventId’:
ACTION_SWAP, ‘params’:{“hand_cardIndex”:0,“play_listIndex”:2}}

大致是一些游戏AI的决策内容,通过不同的事件ID告知你应该做什么样的事情。这里是以目前的自走棋游戏为例进行展示。

具体返回内容就根据实际的业务需求去制定了。

接口须知:

  • 所有发送的参数需要向开发人员确认。
  • 会存在后期的变动维护,例如传参要求和增删参数。
  • 要注意发送的数据是经过处理为标准格式。

3.4 返回决策实施

决策实施这里以自走棋类游戏的操作为例:

  • 刷棋盘:保证商店处于打开状态后刷新。
  • 买棋子:保证商店处于打开后购买对应索引位置棋子。
  • 升人口:随时都会有的按钮,直接用点就可以。
  • 上下交换棋子:根据对应index有无棋子来判断对应上下棋子的决定。
  • 卖棋子:点击对应index位置,再点击sell按钮。
#对应返回eventID执行不同操作

        eventid = self.reqs_data["eventId"]
        if eventid == 0:
            self.round_over([])
        elif eventid == 1:
            self.read_chessbook()
        elif eventid == 2:
            self.refresh()
        elif eventid == 3:
            self.buy_chess(self.reqs_data["params"]["draw_cardIndex"])
        elif eventid == 4:
            self.sell_chess(self.reqs_data["params"]["hand_cardIndex"])
        elif eventid == 5:
            self.up_down_change(self.reqs_data["params"])

3.5 脚本运行框架介绍

在这里插入图片描述

其实这个框架归类的比较简单,就四个大块。

  • runner:

包含触发检测的触发器,用于传入指定包名,选择对应服务器等操作进行持续集成相关操作。
包含动作调用,用来执行对应返回值的操作。
包含日志记录,记录过程中的游戏运行日志和adb日志等。
包含屏幕录制,记录游戏过程中的运行视频,提交视频数据给UI检测接口判断有无UI异常出现(多为花屏白屏和其他明显UI异常情况)。

  • datas:

游戏运行数据的记录文件,包含日志的存放和游戏对应战斗结果的存放(例如当前回合,本局战斗的排名胜负)。

  • common_cls:

一些公共模块
包含与AI的通信,数据的发送和返回
包含UI的通用操作,如自动安装对应版本游戏客户端后启动游戏,过关新手引导,生成新账号等操作
包含通过对AI决策调用对应UI操作方法的函数,也就是动作函数

  • requirements:

整个工程是通过python完成的,这个就是帮你一键安装所有所需库的文件,其中涉及到了一些对poco框架的二次开发,需要本地修改后使用大致的runner流程:

确保你的设备已与电脑开通调试连接,再启动poco与adb的本地通信,对当前UI进行判断是否在局内,局内走提取指定信息,发送给ai进行决策,再执行AI的决策。

通过每次检测有无战斗结束进行重开实现多轮流程,runner的流程图与具体实现流程图基本一致。

3.6 相关优化问题

关于黑名单:比较大型的游戏,局内战斗的元素和UI的复杂程度也是很高的,保存一次UI树的信息也有2M以上的数据,而获取时间会随着UI复杂度的上升而上升。从最终效果来看,战斗从前期到后期,获取时间会由1秒到4秒逐渐上升,这样的速度是不能满足实现业务需求的,因此建议从poco的unitySDK上添加黑名单机制,以提高获取UITree的效率。

建议利用freeze函数,多次提取会重复的获取UI元素浪费时间,目前已经解决这个问题可以只获取一次就提取出所有参数。目前较大型游戏因局内战斗UI结构复杂,获取一次UI信息在一秒到三秒内不等。

UI层操作时间问题:需要避的坑有两个 1.minicap截图:这个要关掉,实际会采用录屏行为,不需要截图。截图服务启动会消耗时间且截图本身也消耗性能 2.adb本地socket通信建立:确保一次建立,这是poco click swipe等方式进行指令的必要条件,需要提前自己启动起来,让他们时刻保持通信,这样会大大降低单次操作时间。现在单次实行操作的时间已经可以控制在最快0.2秒最慢0.8秒。

获取UITree时间问题:通过观察发现,UITree的获取时间跟界面的复杂程度是正相关的。一个比较简单的局外界面可以在0.5秒内就获取完毕,而较为复杂的局内战斗就要约2秒多的时间。对于实际操作而言,这样的过程都太长了。目前探索出的解决方法有两个:1.利用黑名单2.只dump一次,更新后的数据不做重新获取,而是采用利用预期结果的数据本地维护。

有关poco的c++层sdk修改文章请尝试自行百度…

3.7 注意事项

1、获取一次UI信息的时间会随着UI复杂度的提升而提升,局内较为复杂,到后期尤甚。通常在两秒多三秒左右,实际使用要根据项目做具体优化。

2、处理获取出的原始数据到提取对应数据会用到0.2秒左右时间。

3、操作间要保证存在足够时间间隔,一个是利于等待操作后UI确实刷新,一个是保证UI切换展示出内容需要时间 。

4、双击功能需要观察能不能实现现有客户端的操作(adb和poco double click都不行),有的客户端要求双击的两次点击时间间隔太短,不一定能通过自动化实现。

4. 结论

本文主要描述了通过局内调用AI的方式实现自动打游戏。

利用这个框架可以进行局内的多轮流程检测(甚至上分),从而帮助测试同学免去重复去测局内战斗的皮肉之苦。同时可以记录过程视频,交由AI或其他api判断有无花屏白屏的情况,adb报错,unity报错信息都可以进行实时收集。

这是我们迈出AI自动化测试的第一步,接下来我们也会对局外的AI探索性遍历展开研究。

下方资料对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你~

在这里插入图片描述

在这里插入图片描述
凡事要趁早,特别是技术行业,一定要提升技术功底。技术成长的每一个阶段都会遇到一个与之匹配的、难以跨越的,技术瓶颈期!这个阶段没有一次能解决的神药,只有自己不断的积累、沉淀、破局,到最后的爆发。而这些知识可能最开始都是枯燥的,就像看了大A不会小a,看了小a又牵扯出小b,没办法只能一层层的扒,一层层的学。

关注我的微信公众号:【伤心的辣条】免费获取~

我的学习交流群:902061117 群里有技术大牛一起交流分享~

如果我的博客对你有帮助、如果你喜欢我的博客内容,请 “点赞” “评论” “收藏” 一键三连哦,我们下篇文章见!

好文推荐:

那个准点下班的人,比我先升职了…

包装成1年工作经验的测试工程师,我给他的面试前的建议如下

面试官:工作三年,还来面初级测试?恐怕你的软件测试工程师的头衔要加双引号…

自动化测试大总结

以上是关于即时战略游戏的 AI 是怎样实现的?的主要内容,如果未能解决你的问题,请参考以下文章

总结游戏AI人工智能

行业资讯当游戏遇上人工智能,会碰撞出怎样的火花?

AI+游戏:高效利用样本的强化学习 | 腾讯AI Lab学术论坛演讲

万众瞩目下,AI能与游戏擦出怎样的火花?

游戏自动化测试——局内战斗

[SDOI2018]战略游戏 圆方树,树链剖分