— AI分享站

Archive
Tag "引擎"

在上次博文《关于调试AI的闲话(1)》中,提到一个利用共享内存实现的参数调试模块,我整理了一下代码,现在放出来,给大家参考,

下载地址:

GoogleCode下载点

也可用svn通过以下地址来得:

http://tsiu.googlecode.com/svn/branches/blogver/

编译方法:

用VS2005以上打开,选择Debug NoDx或者Release NoDx, 在AITest工程,ATInit.cpp中,有一个宏

#define APP_ONE

打开这个宏,编译一次生成一个可执行文件AITest.exe(在exe\aitest\中),重命名后(比如,AITestA.exe),再关闭这个宏,重新编译一次。

测试方式:

打开编译好的两个exe文件,在其中一个窗口中(有文字提示),按住鼠标左键移动鼠标,就可以看到在另一个窗口中有一个红色的圈在同时移动,另外绿色的圈也是通过这个模块来通信的。

参数调试模块:这次提到的模块代码主要是这两个文件,

TAI_RefValue.h
TAI_RefValue.cpp

说明:

  1. 使用方法可以参考AITest中的例子,注意,需要每帧调用Flush来刷新。
  2. 删除后内存回收的功能还没做,这个需要改变内存的组织方式来做,现在是用数组的方式,可能需要改成链表的方式来组织
  3. 不建议将RefValue用在临时变量里,因为观察一个临时变量的某个值没有什么意义

顺便说一下,整个分享出来的代码是基于一个叫“TsiU”的框架来做的,它是我一直在维护的一个自己用的小型的框架,我平时做的一些AI的sample,或者一些工具,都会基于这个框架,TsiU有一些基本的UI控件库,网络模块库,GDI绘图模块,D3D绘图模块等等,可以快速的做成一个小型的示例程序,很方便(具体可参考AITest),并且整个架构是用Object的方式来组织,非常容易理解和扩展。整个框架很轻量化,基本就是做了一些底层的基本的功能,这样我在平时做东西的时候,就不需要重新写底层了,把精力都放在高层的实现了。以后分享代码都会基于这个框架,大家也可以通过svn来随时update到我最新的改动。下图就是TsiU里的几个工程介绍,代码不多,大家想看的也可以自己看一下 :)

tsiu-project

————————————————————————
作者:Finney
Blog:AI分享站(http://www.aisharing.com/)
Email:finneytang@gmail.com
本文欢迎转载和引用,请保留本说明并注明出处
————————————————————————

Read More

接第一篇的话题,这次来说说AI bug不可重现的问题。确实,对于AI调试来说,很多bug(或者说行为异常),很难找到一个切实的重现的方法,经常是看到后,加好断点,想刻意再玩一下却很难再玩出来了。所以,如何抓住现场,是在AI调试中的一个很值得去考虑问题。

我们很自然的会想到一个解决方案,那就是“回放”。当看到问题时,马上“录像”,然后把这个场景再回放一遍,甚至是可以回放任意遍。这确实是一个自然而完美的方法。一般来说,游戏中的回放分两种:

  1. 逻辑回放(logical playback)
  2. 结果回放(result playback)/ 画面回放(screen playback)

逻辑回放是指,能回到过去的任意时刻,运行游戏的整个逻辑,保证相同的输出结果。结果回放(画面回放)是指,能回到过去的任意时刻,将逻辑运行的结果重新显示出来。简单来说,逻辑回放中所记录的游戏的数据是输入,而结果回放(画面回放)记录的数据是输出,见下图:

ai_debug_1

这两种的回放的实现都依赖于引擎的结构,和引擎实现的耦合度很高,很难做到独立和通用的模块,对于AI调试来说,显然逻辑回放是最好的,因为它能让我们再运行一次游戏逻辑,这样就可以通过设置断点来调试了,但是,现在的引擎一般都是多线程引擎,既然是多线程,就存在一定的时序问题,要做到同样的输入和上下文,两次结果完全一致会相当的困难,除非引擎从设计之初就考虑逻辑回放的问题,如果是改造这样的多线程引擎,工作量会比较大。如果是单线程的引擎,实现起来会容易很多,一般而言,逻辑回放主要记录几个内容:

  1. 时间信息
  2. 设备输入信息(比如,手柄,键盘,鼠标等)
  3. 随机数信息
  4. 当时的游戏世界上下文

对于第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
本文欢迎转载和引用,请保留本说明并注明出处
————————————————————————

Read More

【游戏篇】

0. 我的游戏引擎核阅读心得:找到Update,就找到了一切!

1. 找到游戏主循环:游戏主循环是游戏的核心所在,所以找到主循环至关重要,有几个办法,如果代码是可运行的(一些时候,代码连编都编不过),那比较简单的就是加断点(其实很多时候,都可以用加断点的方式来辅助阅读),这样callstack一看就找到了。如果不能运行,一个办法是从Main函数开始找,还有一个办法,是先找到一个模块,比如input的update函数,然后再顺着往上找。还有一个讨巧的办法,就是搜关键字,比如MainLoop。

2. 熟悉游戏的模块:游戏引擎总有一些常见的模块,比如input,animation,AI,network,sound,render,physics等等,这些模块就是游戏引擎的核心,可以先在引擎里找到这些模块的Tick或者Update函数,由update开始逐渐深入到各个模块的具体运作。看游戏引擎,一般只会挑自己感兴趣的看,因为引擎的东西实在太多,太杂,不可能都去了解。所以一定要有所取舍。

3. 要不求甚解:游戏引擎里面包含了各种技术,每个部分都有很多精彩的东西,但在通篇阅读的时候,有些可以跳过,也就是,只要知道他干吗的就可以了。最好的例子就是math,一些引擎的math实现相当厉害,但对我们来说,只要知道,vector,matrix等等怎么用就可以了,其内部实现,优化方法等完全不用去了解(除非你想学习)。我想到一些游戏中可以不用去了解的部分:基本数据结构(array,list等),math,thread部分,time,memory(但如果要优化内存,或者有bug,就需要看),一些类型的封装(smartpointer等),ScriptSystem(游戏中有时会自己实现一套虚拟机,可以无视)等等

4. 数据的重要性:游戏中的数据十分重要,比如你看AI部分代码,很有可能很多定义都在数据中,如果单看代码会十分难以理解,比较好的就是结合引擎提供的editor来看,先用editor改一下数据,然后再在代码里看相应的代码。还有,现在的游戏引擎很多都用了Script,比如lua,或者游戏自己的Script,比如Unreal,这时候,就要在script和引擎两头转,如果引擎没有提供很好的工具,确实相当麻烦,我到现在还没找到一个很好的办法来解决这样的问题,

5. :阅读游戏引擎需要一点经验,经常会边猜边读,猜的过程,就是一个使用以往经验的过程。总的来说,游戏中用的技术有一定的通用性,虽然名字或叫法可能不同,但基本思想还是一样的,又要举一个我比较熟悉的AI的例子,比如,FSM,有限状态机,看到代码里有类似的名字,那我就能大致了猜到这段代码的实现方法了。

好啦,写了这么多,基本把我想到的,学到的都做了一个梳理,我一方面上班会看一些代码,现在在业余时间也在研究doom的源码(以后可以写个Doom阅读笔记系列,再次崇拜一下伟大的Carmack),还在维护一个游戏引擎(TsiU),可以说,对游戏引擎的积累也慢慢加深,这里share一些东西,也算一个总结,我也是个Game界的newbie,欢迎大家讨论,批评指正。

相关:

---> 阅读代码的一些心得(1)(工具篇)
---> 阅读代码的一些心得(2)(通用篇)

————————————————————————
作者:Finney
Blog:AI分享站(http://www.aisharing.com/)
Email:finneytang@gmail.com
本文欢迎转载和引用,请保留本说明并注明出处
————————————————————————

Read More

【通用篇】

0. 我的一句话心得:“看代码有时候和阅读外文文章一样,先泛读,了解文章大意,再精读,了解单词语法。”

1. 了解代码的命名规则:代码一般都有自己的命名规范,比如cl打头表示类,E打头表示枚举,函数名前会带返回值类型等等,掌握这些命名规则,可以很方便的知道这个变量或函数的信息,加快看代码的速度,另一方面,对再开发也有很多好处,因为再开发一定要遵守代码中原有的命名规范,我想这也是程序员不成文的准则之一吧

2. 遍历工程结构:在实际看代码前,最好先看看他的solution结构和工程属性,包括哪些工程是lib,哪个是startup proj,工程和工程间的关系是什么。另外,看工程结构也可以大致了解代码中包括了哪些部分,有的项目是以工程来分的,有些是在一个工程中,用folder filter来分的。

3. 以main函数为开始点:和程序运行一下,看代码也可以从main函数开始,一般main函数会分为几个部分:初始化,主逻辑,销毁。初始化可以快速的了解代码中有哪些模块,主逻辑为今后阅读重点,销毁部分基本可以不care。另外,一些库的源代码,可能没有main函数,那就可以结合lib提供的Sample逐渐深入。

4. 拆分代码:一般代码是一个有多个部件组成的,阅读时需要拆分,比如win32窗口程序,一般是控件驱动,大部分的feature代码都可以在处理控件的消息中找到相应的线索,所以就可以按照控件的功能来阅读,逐步到具体实现方法,再比如stl源码,因为本来各个部件就比较独立,就可以按照头文件的分类逐步阅读。

5. 关心值得关心的:代码中有很多无用的信息,比如打印debug消息,profile,assert等等,代码中可能还有很多被称之为guard的代码,就是保护代码,也可以无视。当然,要从一大堆代码中取其精华,去其糟粕,需要一定的经验积累。

6. 用写者的角度思考:阅读代码,必须一直想几个问题,“如果是我,我会怎么实现?”,“作者为什么要这样写”等等。因为看代码如同阅读别人的逻辑思维,只有投入和思考后,才能产生共鸣。

(待续)

相关:

---> 阅读代码的一些心得(1)(工具篇)

————————————————————————
作者:Finney
Blog:AI分享站(http://www.aisharing.com/)
Email:finneytang@gmail.com
本文欢迎转载和引用,请保留本说明并注明出处
————————————————————————

Read More

一般游戏代码的量都是相当惊人的,在开始做项目的时候,特别是porting项目,都会阅读引擎代码,所以阅读代码的能力确实不可或缺。在进公司后做的第一个项目,我看代码的能力有了很大的提高,记得第一个项目的引擎代码有180万行,我和一些新进的同事,足足研究的了3个月,收获颇丰,后来也陆陆续续的看了多种引擎的代码,有一些心得,和大家分享,有些有可能和游戏相关,不过大体都比较通用。我将分为工具篇,通用代码篇,游戏专用篇来写。

【工具篇】

0. 我选工具的宗旨:尽量使用开源或者免费的工具,工具尽量轻量化。

1. 找一个好的阅读代码的工具:好的工具的基本标准是可以支持类,函数,变量的跳转等,现在的项目大多都用VS创建,所以VS一般就是首选,2003的话需要辅助VAssist,2005本身功能就比较强,就不太需要另外的插件。如果有两个显示器,或者显示器比较大,可以推荐SourceInsight。

2. 找一个好的UML的工具:现在代码大多是OOP,很多类的层次结构相当复杂,所以画UML图的方式就可以非常方便的理清各个类之间的关系,UML有很多内容,我一般就画一下类图,UML的工具也很多,推荐starUML,这个是开源的,而且非常轻量化,相当好用,其他诸如,Visio,RationalRose,也都可以。很多人也许习惯用纸和笔来画,虽然这样很快,但我觉得,这种方法比较不利于文档化和共享。

3. 用wiki来记录:wiki对于阅读代码来说是一个非常好的share和文档化工具,特别是有很多人一起看一个代码时,优点尤其明显。在阅读代码的过程中,可以在wiki上写一点notes,这样其他人在看到这部分代码时,就不用重新去理解代码内容,而可以结合你写的notes来辅助阅读,这样就大大节省了时间,到最后,整个wiki里就成了这个项目的文档,一举两得啊。我在一个项目里用过这个方法,效果相当不错。推荐几个pmwiki(开源,并且不需要额外的数据库),mediawiki(开源)。

(待续)

————————————————————————
作者:Finney
Blog:AI分享站(http://www.aisharing.com/)
Email:finneytang@gmail.com
本文欢迎转载和引用,请保留本说明并注明出处
————————————————————————

Read More