cloud

  C++博客 :: 首页 :: 联系 :: 聚合  :: 管理
  29 Posts :: 1 Stories :: 4 Comments :: 0 Trackbacks

常用链接

留言簿(5)

我参与的团队

搜索

  •  

最新评论

阅读排行榜

评论排行榜

#

HWND hWnd  =  FindWindowEx(GetDesktopWindow() ,  0  ,  " Progman "  ,  " Program Manager " );
hWnd 
=  FindWindowEx(hWnd ,  0  ,  " SHELLDLL_DefView "  ,  0 );
hWnd 
=  FindWindowEx(hWnd ,  0  ,  " SysListView32 "  ,  " FolderView " );
//  初始化 D3D 设备 
InitD3D(hWnd);

显示的时候,如果想渲染在桌面的一角,则可以这样写:
// 显示在左上角,128×128宽
RECT rect;
rect.left 
= 0;
rect.right 
= 128;
rect.top 
= 0;
rect.bottom 
= 128;
// 显示
g_pd3dDevice->Present(0 , &rect , 0 , 0);

posted @ 2008-03-21 10:53 cloud 阅读(810) | 评论 (0)编辑 收藏

在游戏引擎中,Entity通常被翻译成实体,也常用诸如GameObjectActorSimulationObjectUnitCharacter等名字。相比于对图像声音引擎的热情,Entity层多年来一直备受冷遇,但最近几年随着大型游戏的发展,Entity层设计的重要性已经达到和图像声音的同等水平,而且已经出现了多种通用型Entity架构。当然,这里伴随着争议和分歧。

直接模式(The C Approach

这是最早、最简单、也是任何人都能直接想到的模式。这种方式下一个Entity就是一个简单的struct:

struct Mob
{
int level, hp, mp, attack, …;
};

这种情况下往往需要对不同类型的Entity定义不同的struct,比如PlayerMobItemDoodad等。Entity的数据库可以直接使用现成的数据库系统或者从数据文件读取,比如csv文件。一般会选择Excel编辑数据库,然后导出csvExcel的强大表格和统计计算功能是调节游戏数据平衡的得力工具。以致这种最古老而简单的Entity模式以强大的生命力一直活到现在。

那么为什么Developers会要去探索其他方式呢?最大的问题是这种方式下各种Entity完全不同质。比如PlayerMobDoodad都有位置,都要做碰撞检测,但他们却是不同的类型,不能使用同一份代码;PlayerMob都有hpmp,也有基本相同的处理逻辑……当然,这个可以用hack的方法解决:只要各个struct前若干个成员类型和顺序完全相同,就可以将指针cast成统一的一个Entity类型来处理。不过,任何人都能想到更好的办法,用类!

继承模式(Inheritage

这也是EntityGameObject等这些名字的由来,他们就是基类。于是我们可以得到一颗继承关系树,例如:

Entity

       Character

              Player

              Mob

       Missile

              Laser

              GuidedMissile

       Item

      

Entity对象的逻辑大多也是直接包含在类里的。但也有人采用了数据对象和逻辑对象的分离,其好处是对相同的数据可以替换不同的逻辑。不过,个人比较怀疑这种分离是否值得,因为类的派生本身就是可以拥有不同的逻辑。

另外,Entity间的交互除了用标准的直接访问方式外,经常使用消息模式。消息模式和Windows的消息模式类似,Entity间通过消息交互。虽说窗口编程画了多年时间才摆脱了当年肥大的switch的消息处理模式,在Entity层使用消息模式还是有意义的,因为消息很容易广播且可以直接在网络上发送。执着于OO的人会将消息写成Command对象,但原理仍然是一样的。

同时,联网游戏出现,指针不能在不同的机器上标识对象,我们必须用稳定的ID来标识对象,于是我们有了EntityManager来分配ID和管理对象集合,或者叫GameObjectManagerObjectManager等。这在一段时期内几乎成了完美的方案。

随着游戏内容的丰富性的迅速膨胀,传统的由程序员来实现游戏逻辑功能的模式也越来越力不从心。脚本语言的集成将大部分创意性工作从程序员的担子上拿了下来,交还给游戏设计人员。为了给脚本提供足够的控制权,Entity结构上必须有充分的灵活性。

数据驱动(Data-Driven

现在有句很流行的话,“唯一不变的是变化。(The only constant is change.)”数据驱动使得变化对引擎的影响最小化。数据驱动不能算是一种独立的Entity模式,不如说它是一种设计思想,其目的就是将内容制作和游戏引擎的制作分离开来。与上面所说的填充Entity属性数据库的不同之处在于,它还要能通过数据来设计游戏逻辑。

游戏设计人员需要的一项最基本功能就是自定义人物属性,所以与其在类里写死属性成员,不如使用属性表(Attributes/Properties)。通常使用一个哈希表(Hashtable),或者std::map,或者Dictionary,或者就是个数组,随个人喜好,其实就是要一个key-value的映射表。然后为脚本和编辑器提供对属性表的操作。

动态的逻辑主要就靠脚本了,必须为脚本提供足够的事件和方法。个人推荐用Lua脚本,因为它是在游戏领域内用得最多的通用脚本语言,其引擎很小、速度很快、易于集成,尽管语法过于松散。不要迷信宣传去用庞大、极慢、难于集成的Python。为脚本提供事件,其实也就是调用脚本里的函数,这里如果使用了前面所述的消息模式,那么就只要调用一个脚本方法,传递不同的消息参数就行了。当然也有人觉得这样很丑陋而更愿意为不同的事件注册不同的函数。

当有了数据驱动后,Entity的继承树就基本失去意义了,因为一个Entity是什么已经不是程序里决定的了,而是通过数据和脚本设计出来的。但数据和脚本又不是全部,一个Entity的核心内容和需要高效处理的部分,如碰撞检测,还是要程序来完成。于是我们需要重新设计Entity类,困难的局面也就由此开始。

一个直观的想法是一个统一且唯一的Entity类,它包含了所有的基本功能,如显示、碰撞检测、运动、背包等,然后由数据决定哪些组件被启用。比如一个玩家角色可能会启用绝大部分组件,而一颗树只启用显示和碰撞检测组件。但也伴随着缺点:一、这个类太大了;二、对于树木等一些简单的Entity也要浪费其他组件的私有数据所占的内存。那么一个简单的折中是部分使用继承、部分使用数据定制。例如Entity只提供最基本的组件,再派生出CharactorEntity提供足够人物角色使用的组件。

组件模式(Component-Based Entity

提到组件,那么很自然的就过渡到组件模式,就是把显示、运动、攻击、背包、队伍、声音等基本功能都做成独立的组件,由数据来决定向Entity里添加哪些组件。由此可以得到另外一个扩展,就是既然可以有引擎内置的组件,那就也可以有脚本制作的组件,实现脚本模块的复用。这种模式在GDC2002正式提出,到现在主流的引擎都有这种设计。

这种模式在理论上很完美,但实践上还是有不少疑问。最常见的问题就是组件间的依赖关系。理想情况下,各个组件是完全独立的,但实践中必然有所依赖。比如运动速度、攻击强度等和角色的基本属性有关,运动组件需要角色的包围盒来测试是否碰撞,AI组件需要分析角色当前状态和发出运动、攻击命令,角色动作状态变化时改变显示组件属性,攻击组件需要访问队伍信息组件以禁止攻击队友等等。处理这种依赖关系主要要解决两个问题:

<!--[if !supportLists]-->一、              <!--[endif]-->谁依赖谁。比如是敏捷属性改变而去修改移动速度,还是运动组件读取敏捷属性来计算移动速度。如果要游戏设计人员自由定义基本属性的话,就要选择前者,因为基本属性组件会是脚本组件。

<!--[if !supportLists]-->二、              <!--[endif]-->如何取得另一组件的指针/引用。常见的方法是给每个组件类型一个唯一ID,然后用该IDEntity上查询注册了的组件,如果找到返回其指针/引用,否则返回null。当然,每次访问都做这个查询会很浪费CPU,如果Entity的组件不会在运行时动态添加删除的话(除非在编辑器里,否则很少有人会这么做),可以在Entity初始化后让每个组件缓存它所要用的其他组件指针。那么当所依赖的组件不存在怎么办,一般情况下都是无声地忽略。

Entity由很多组件组成后,交互的消息需要发给每一个组件。这里又一次体现出消息机制的优势,你不需要在Entity里为每一个事件函数写一个loop来调用组件的相应事件函数。但这里也出现了一个问题,消息到达各个组件的顺序。很多时候这个顺序不会有什么影响,但个别时候不同的顺序会导致完全不同的逻辑发展方向。

此外,Entity的序列化存储也变得比较复杂,经典的Excel导出csv的模式难以奏效,因为这里需要结构化的存储,所以需要结构化的数据文件如XML来存储,或者完全用脚本来包含所有数据和构造Entity

据个人经验,使用数据驱动的继承模式时很是向往组件模式,感觉上它一个非常自然的扩展方向,但顾忌其引入的额外的复杂性,尤其是需要游戏设计人员具有一定的编程能力,所以一直不敢全盘接过使用。但退一步,在引擎里仍然使用组件思想,但Entity的组件构成在编译时固定,可以达到某种妥协,这和采用继承与数据驱动的折中类似。

混入模式(Mix-in

这是又一种常见的折中模式,即使用C++的多重继承,将各个组件类混入一个Entity类。如:

class Mob: public GameObject, public Renderable, public Movable, public Attackable
{

}

这种方法因其简单而且非常符合多重继承设计思想而被很多引擎采用。当然缺点也是只能在支持多重继承的语言里使用,而且当组件很丰富时,dynamic_cast就变成一个代价高昂的操作。

功能性与复杂性(Functionality vs Complexity

编程领域最古老的原则之一就是要“简单快速”。但随着问题的复杂化,程序也随之变得越来越复杂。好的方法应该能有效的降低或隐藏复杂性。但是,没有不带副作用的药(No silver bullet.),在取得更强大的功能的同时总会带来额外的复杂性。我们需要做出权衡,在必要时牺牲一些功能,也就是要估算性价比。

一般游戏内容制作人员不会或不太会编程,编程人员也不善于游戏的内容创造和数值平衡,过于复杂的系统会导致需要两面兼顾的人才,会大大增加做出一款游戏的难度。

posted @ 2008-01-09 15:14 cloud 阅读(535) | 评论 (0)编辑 收藏

     摘要: 免费引擎 Agar - 一个高级图形应用程序框架,用于2D和3D游戏。 Allegro library - 基于 C/C++ 的游戏引擎,支持图形,声音,输入,游戏时钟,浮点,压缩文件以及GUI。 Axiom 引擎 - OGRE的衍生引擎。 Baja 引擎 - 专业品质的图像引擎,用于The Lost Mansion。 Boom - Doom代...  阅读全文
posted @ 2007-12-25 13:09 cloud 阅读(12243) | 评论 (0)编辑 收藏

vc++2005中的异常捕获try...catch... 和以往的版本有很大的不同, 默认的vc++2005的工程只能捕获由throw抛出的异常, 当然, 如果是用windows的__try...__except, 可以捕获到结构化异常的, 如果想让try...catch...捕获结构化异常, 那么就需要设置为/EHA, 而不是/EHS
posted @ 2007-11-29 20:41 cloud 阅读(398) | 评论 (0)编辑 收藏

这几天一直在找lua的远程调试, 终于找到一个LuaPlus和remdebug, 在此做个标记
posted @ 2007-10-20 12:18 cloud 阅读(699) | 评论 (0)编辑 收藏

http://www.cppblog.com/nacci/archive/2005/11/08/974.html

posted @ 2007-07-12 11:29 cloud 阅读(227) | 评论 (0)编辑 收藏

软件开发是一个动态的过程,在多人开发过程中经常会发送代码混乱和代码失控,如为了扩展功能经常在他人的代码中加入调用自己的模块,在地层函数中加入自己的业务处理逻辑等,经常造成多人同时维护一段代码的情况,容易造成模块间的耦合性太高,代码难以理解和修改,稍微做修改却在不相关的地方出现问题。轻微的一处修改特别是地层头文件的修改会引起程序大规模的编译和连接等。解决此类问题的关键在于需要将程序按功能接口化、组件化 
1.多人开发的基础---组件化编程,仿COM篇
2.多人开发基础:组件化编程的简化实现
posted @ 2007-07-12 11:15 cloud 阅读(214) | 评论 (0)编辑 收藏

这几天服务器突发一个bug, 登陆的人员会发生串号, 就是A玩家登陆后, 得到B玩家的角色.
  这个bug触发的主要原因是网络底层在得到一个客户端的连接请求时, 会分配一个id,而这个id是用栈在保存的,当有新的连接, id就出栈, 当连接断开时,id入栈, 这样就导致了id被重复利用的次数非常高,尤其是某些id. 当这些id生成后,会进入登陆队列,等待登陆结果, 如果这个时候登陆认证模块的速度很慢, 或者说这个时候客户端退出了, 这个时候的id就是一个无效id了, 可是这个只有网络底层知道, 这就造成引用层编写的难度.  所以最好的解决方案就是每次的连接id都是不一样的,类似操作系统的HANDLE
posted @ 2007-06-18 17:37 cloud 阅读(269) | 评论 (0)编辑 收藏

插件式设计近年来非常流行,其中eclipse起了推波助澜的作用,提到插件式就会不由自主的想到eclipse。其实插件式设计并不是什么新事物,早在几十年前就有了。像X Server就是基于插件式设计的,除了核心功能外,它所有的扩展功能和设备驱动都是以插件方式加入进来的。

  基于插件的设计好处很多:把扩展功能从框架中剥离出来,降低了框架的复杂度,让框架更容易实现。扩展功能与框架以一种很松的方式耦合,两者在保持接口不变的情况下,可以独立变化和发布。公开插件接口,让第三方有机会扩展应用程序的功能,有财大家一起发。另外,还可以让开源与闭源共存于一套软件,你的插件是开源还是闭源,完全由你自己决定。

  基于插件设计并不神秘,相反它比起一团泥的设计更简单,更容易理解。各种基于插件设计的架构都有自己的特色,但从总体架构上看,其模型都大同小异。这里我们介绍一个简单的模型,并给出几个实例,希望对新手有所启发。

  1. 基本架构

plugin.jpg

  插件式设计的应用程序,基本上可以用上图来表示。当然,此图是一种较高层次的表示,实际的设计会更复杂一些。我们在这里为了阐述方便,不用故意搞得那么复杂。

  应用程序由应用程序框架、插件接口、插件和公共函数库四部分组成。

  应用程序框架负责应用程序的整体运作,它清楚程序整个流程,但并不知道每个过程具体要做什么。它在适当的时候调用一些插件,来完成真正的功能。

  插件接口是一个协议,可能用IDL描述,可能是头文件,也可能一段文字说明。插件按照这个协议实现出来,就可以加入到应用程序中来。当然,对于复杂的系统,插件接口可能有多个,各自具有独立的功能。

  插件是完成实际功能的实体,实现了要求的插件接口。尽管实现什么以及怎么实现,完全是插件自己的自由。在实际情况来,一般还是有些限制,因为插件接口本身可能就是一个限制。如,实现编译功能的插件,自然不能实现成一个聊天功能的插件。

  公共函数库是一组函数或者类,应用程序框架和插件都可以调用。它通常是一个独立的动态库(DLL)。应用程序框架本身是公用的,是代码复用的一种方式。但并不是所有可复用代码都可以放在框架中,特别是插件会用到的公共代码,那会造成插件对框架的依赖。把这些公共代码提取到一个独立的库中,是一种好的方法。

  另外,值得补充说明一下的是插件接口。插件接口通常有两种:

  通用插件接口:这一类插件接口是通用的,你无法从接口函数看出这个插件的功能。它的接口函数通常有这些函数:

  init : 用于初始化插件,通常在插件被加载时调用。

  deinit:用于反初始化插件,通常在插件被卸载时调用。

  run:让插件起动。

  stop:让插件停止。

  至于插件要完成什么功能,要插到哪里,在init函数里决定,它调用公共函数库里的函数把自己注册到框架中某个位置。

  专用插件接口:这一类插件接口是专用的,看到它的接口函数说明,你就可以大致了解它的功能了。

  加入插件的方式通常采用配置信息来实现,配置信息可以是注册表,也可以配置文件。也可以动态注册进来,或者把插件放到指定的位置。

  下面我们来看几个实例:

  2. 桌面设计

  最近一段时间完成了桌面模块的设计和实现。按照以往的经验,桌面模块通常是变化最多的一个模块,SPEC总是在不断的调整的效果,不同客户要求实现具有个性化的桌面,直到产品快发布了,桌面的SPEC还在不停的修改。另外,在智能手机中,桌面占有特殊的地位,很多东西都可能往桌面里塞,桌面不但是各种功能的大杂烩,还是一些系统消息的中转站。

  这个任务比较棘手,所以在设计时就分外小心。首先想到的就是采用插件式设计,把外围功能独立出来,尽量简化框架的实现。

  插件:每一个最小功能单元都是一个插件,它可以是可见的,也可以是不可的,也可以是动态变化的。比如时间、电池电量、网络连接、信号强弱、新事件(如SMS、MMS、EMAL、ALARM和未接电话等)、应用程序快捷方式、左右操作按钮和其它处理系统事件的功能单元。每个插件都用一个.desktop来描述,这是遵循freedesktop.org的标准的。

  桌面框架包括:状态栏、开始菜单、操作栏、桌面区、事件管理器和主题管理器。而状态栏、开始菜单、操作栏、桌面区和事件管理器都是容器,容纳各种插件。对于可见的插件,可以有自己的表现方式,也可以采用通用的表现方式。

  公共函数库:一些抽象的类、实现插件的辅助类以及其它一些可能被公用的类。

  插件接口:对于不可见的插件要求实现事件处理功能,可见的插件还要求实现绘制功能。

  3. 模拟器设计

  一个同事负责设计另外一个平台的PC模拟环境设计。在我的建议下,他对架构作了调整。调整后的架构非常简单,也可以认为是插件式的设计,它由下面几部分组成:

  应用程序框架:负责模拟器基本功能,如模拟键盘和显示设备、换肤功能等。

  插件:就是被模拟的平台,如microwindow及相应的手机应用程序。尽管运行时通常只有一个插件运行,这样做仍然有意义,如果要换成minigui或者其它平台时,模拟器不需要作任何修改。

  公共函数库:它由应用程序框架初始化一些信息和回调函数,然后供插件(即microwindow)调用,插件利用它来实现显示和输入等驱动程序。

  插件接口:如起动和停止模拟平台等。

  4. GIMP

  GIMP是一个功能强大的图形图像编辑器,典型的基于插件式的设计,在《unix编程艺术》中,作为插件式设计示例介绍过。

  应用程序框架:GUI

  插件:完成图像的各种转换和处理功能,如模糊、去斑和色彩调整等。

  公共函数库:放在libgimp.so里。

  插件接口:对GIMP感兴趣的朋友,可以到官方网站上去阅读更多的文档。
posted @ 2007-06-08 16:01 cloud 阅读(1370) | 评论 (0)编辑 收藏

用http传输的时候要注意,在传输是最好传输字符串,而不是二进制串, 应为二进制串可能包含很多控制字符, 这样就不利于传输了.
一个可行的方案是用base64编码, 然后查找编码后的串中是否有'+', 如果有可以替换成'-'(因为如果不替换,在http的post或者get方式获得数据的时候, '+' 会变成' '), 这样就可以传输了

posted @ 2007-06-04 14:32 cloud 阅读(433) | 评论 (0)编辑 收藏

仅列出标题
共3页: 1 2 3