接第一篇的话题,这次来说说AI bug不可重现的问题。确实,对于AI调试来说,很多bug(或者说行为异常),很难找到一个切实的重现的方法,经常是看到后,加好断点,想刻意再玩一下却很难再玩出来了。所以,如何抓住现场,是在AI调试中的一个很值得去考虑问题。
我们很自然的会想到一个解决方案,那就是“回放”。当看到问题时,马上“录像”,然后把这个场景再回放一遍,甚至是可以回放任意遍。这确实是一个自然而完美的方法。一般来说,游戏中的回放分两种:
- 逻辑回放(logical playback)
- 结果回放(result playback)/ 画面回放(screen playback)
逻辑回放是指,能回到过去的任意时刻,运行游戏的整个逻辑,保证相同的输出结果。结果回放(画面回放)是指,能回到过去的任意时刻,将逻辑运行的结果重新显示出来。简单来说,逻辑回放中所记录的游戏的数据是输入,而结果回放(画面回放)记录的数据是输出,见下图:
这两种的回放的实现都依赖于引擎的结构,和引擎实现的耦合度很高,很难做到独立和通用的模块,对于AI调试来说,显然逻辑回放是最好的,因为它能让我们再运行一次游戏逻辑,这样就可以通过设置断点来调试了,但是,现在的引擎一般都是多线程引擎,既然是多线程,就存在一定的时序问题,要做到同样的输入和上下文,两次结果完全一致会相当的困难,除非引擎从设计之初就考虑逻辑回放的问题,如果是改造这样的多线程引擎,工作量会比较大。如果是单线程的引擎,实现起来会容易很多,一般而言,逻辑回放主要记录几个内容:
- 时间信息
- 设备输入信息(比如,手柄,键盘,鼠标等)
- 随机数信息
- 当时的游戏世界上下文
对于第4点是针对从任意点回放而言的,如果当前的上下文信息不是很容易获取,可以考虑不支持任意点回放,而是每次都从头开始回放,这样实现起来更容易一点。
就如我前面所说,对于多线程引擎来说,逻辑回放的实现比较困难,那退而求其次,我们可以选择结果回放,结果回放记录的信息就比较“单纯”,就是每次画面需要画的那些Object信息,比如位置等等,回放时,我们只需把记录的结果再重新给渲染器,让它画出来即可。也许大家会问,我们没法重新调试AI的逻辑,这样的回放有什么意义呢?当然,如果只是单纯的把画面重画一遍,是没什么意义,结果回放需要结合另一个AI调试的方法一起来使用。那就是“调试信息”(Debug Draw)。一般引擎都提供一套可以在屏幕上画点圈,画点叉,写点字的接口,AI的调试,就是可以使用这样的接口在屏幕上打印出关键的信息,来帮助我们查看逻辑的“走向”,举个例子来说,我以前写过一篇博文,介绍用分数系统来做AI,如果要调试这样的AI,我们就可以利用调试信息,在屏幕上,将分数的情况都打印出来。由于结果回放是将所有输出到渲染器的信息都保存了下来,所以我们就可以通过回放来观察这些分数的变化,以此来调试AI的行为。结果回放的另一个优势是,它可以轻松的实现任意点的回放,包括后退,前进,暂停等等,因为结果数据和上下文是无关。
不管有没有回放的机制,很多时候AI的调试,都需要调试信息的帮助,可以让我们不用设置断点就知道逻辑的计算,我们甚至需要制作一些工具来获取当前AI的相关状态,比如前几篇说到的那个观察器。这也就是AI调试比较难的地方,一个字,“猜”,当然是有依据的猜,更准确的说,应该是推测吧,:)
不知不觉,写了3篇了,总结下吧,要有好的AI调试体验,我们需要有好的AI架构,这是一切的基础,比如脚本,比如回放,其次要有好的配套工具,来辅助那个“猜”,并能形成独立可复用的模块,然后就是对于标准的遵守,比如不要用“魔数”的问题,当然,还有很多值得我们去想的,这一系列也希望能抛砖引玉,引起大家的思考吧。
相关:
—> 关于调试AI的闲话(1)
—> 关于调试AI的闲话(2)
————————————————————————
作者:Finney
Blog:AI分享站(http://www.aisharing.com/)
Email:finneytang@gmail.com
本文欢迎转载和引用,请保留本说明并注明出处
————————————————————————