Rise的自留地

记录生活中的点滴,分享编程技术和游戏开发经验。

0%

核心子系统

核心库(Core namespace)实现了这些特性:

  • 一个实现了引用计数的RefCounted基类
  • 一个运行时类型信息系统(RTTI)
  • 一个模板智能指针, 用于处理RefCounted对象的生命周期
  • 一个由类名创建C++对象实例的工厂机制
  • 一个中央Server对象用于建立基本的Nebula3运行环境

对象模型

Nebula3在C++对象模型的基础之上实现了下面这些新特性:

  • 基于引用计数和智能指针的生命周期管理
  • 基于类名或四字符编码的对象创建
  • 一个运行时类型信息系统

实现一个新的Nebula3类

当实现一个新的类时首先要考虑它是一个传统的C++类还是要从Core::RefCounted继承. 以下几点可以帮你找到答案:

  • 如果这个类需要使用Nebula3的扩展对象特性, 如引用计数, RTTI等, 则它必须从Core::RefCounted继承.
  • 如果这个类是一个典型的小工具类, 如动态数组, 数学向量, 或其它相似的东西, 那么它从Core::RefCounted 继承也没有什么意义.

从Core::RefCounted类继承有一些限制:

  • RefCounted派生类不应该在栈上创建对象, 因为栈对象的生命周期是由C++来管理的(他们会在离开当前上下文时被销毁, 从而绕过了Nebula3的引用计数生命周期 管理)
  • RefCounted的派生类只有一个默认的构造函数.
  • RefCounted的派生类必须有一个虚析构函数.
  • RefCounted的派生类不能进行拷贝, 因为这样会造成引用计数机制混乱.

要使用Nebula3的对象模型特性, 除了需要从Core::RefCounted继承外, 还需要在头文件新类的声明中进行额外的标注:

一个标准的RefCounted派生类一般这样声明:

1: namespace MyNamespace

2: {

3: class MyClass : public Core::RefCounted

4: {

5: DeclareClass(MyClass);

6: public:

7: /// constructor

8:     MyClass();

9: /// destructor

10: virtual ~MyClass();

11: ...

12: };

13: RegisterClass(MyClass);

注意DeclareClass()宏, 构造函数, 析构函数还有类外面的RegisterClass()宏. DeclareClass()宏加入了RTTI和工厂机制所需的最小代价的信息, 它隐藏了Nebula3的对象模型, 希望可以在不影响已有类的基础进上进行内部机制的变更. RegisterClass()宏是可选的, 它把当前类在中央工厂进行注册. 如果你知道这个类永远不会由类名或四字符编码进行创建, 这个宏可以省略.

在这个类的.cpp文件里需要包含Nebula3特有的信息:

1: namespace MyNamespace

2: {

3: ImplementClass(MyNamespace::MyClass, 'MYCL', Core::RefCounted);

4:

5: }

ImplementClass()宏注册类的RTTI机制, 第一个参数描述了类的名字(注意命名空间必须包含). 第二个参数是类的四字符编码, 它必须是所有类中唯一的(如果有重复, 你会在启动程序时得到一个错误提示). 第三个参数是父类的名字, 用于RTTI系统去构造类的关系树.

引用计数和智能指针

Nebula3使用传统的引用计数来管理对象的生命周期. 一个模板智能指针类Ptr<>对程序员隐藏了引用计数的实现细节. 一般来说, 应该一直使用智能指针指向RefCounted的派生对象, 除非你能肯定在给出的代码块中这个对象的引用计数不会发生变化.

智能指针相对于一般指针有很多好处:

  • 访问一个空指针会给你一个断言警告而不是一个内存错误
  • 你不需要对引用计数的对象调用AddRef()或Release() (事实上如果你调了, 会了发生严重的错误)
  • 智能指针可以在容器类里良好地工作, 一个智能指针的数组会消除所有的一般指针需要的生命周期管理, 你永远不需要考虑去释放指针所指针的对象, 数组包含的像是真正的C++对象一样
  • 用智能指针不需要考虑指针的所属, 不需要为谁delete对象而烦恼

智能指针也有一些缺点:

  • 性能: 拷贝和赋值会引起对象的引用计数的变化, 解除引用会引起指针的断言检查. 这导致的性能消耗一般是可以忽略的, 但是你最好保证它不在内部循环中发生.
  • 应该销毁的对象还存在: 因为智能指针管理的对象只有在最后一个引用放弃时才会销毁, 这样会使对象存在超过预订的时间. 这经常会导致一个BUG的产生. 不过引用计数泄露(程序退出时还仍然存在的对象)时Nebula3会提醒你.

创建Nebula3对象

从Core::RefCounted继承的类可以通过3种不同的方式进行创建:

直接通过静态的Create方法:

1: Ptr<MyClass> myObj = MyClass::Create();

静态的Create()方法是之前提到的DeclareClass()宏加入的, 相对于new操作符来说, 它并没有多做什么. 注意正确使用智能指针来保存新建的对象.

另一种创建方式是通过类名:

1: using namespace Core;

2: Ptr<MyClass> myObj = (MyClass*)Factory::Instance()->Create("MyNamespace::MyClass");

当你在运行时通过类名来创建十分有用, 特别是对象的反序列化和脚本接口的使用. 注意类型转换是必须的, 因为工厂的Creat()方法返回的是RefCounted指针.

由类名创建的变种是根据四字符编码进行创建:

1: using namespace Core;

2: using namespace Util;

3: Ptr<MyClass> myObj = (MyClass*) Factory::Instance()->Create(FourCC('MYCL'));

这个方法看上去没有那个直观, 但是它比类名创建快得多. 并且四字符编码比类名占用的空间更少, 这更利于对象写入二进制流或从中读取.

运行时类型信息系统

Nebula3的RTTI系统可以让你在运行时访问对象的类型, 检查一个对象是不是某个类的实例, 或者某个派生类的实例. 你也可以直接获得一个对象的类名和四字符编码. 所有这些功能是由DeclareClass() 和 ImplementClass() 宏在背后实现的.

这时有示例程序:

1:     using namespace Util;

2:     using namespace Core;

3:

4: // check whether an object is instance of a specific class

5: if (myObj->IsInstanceOf(MyClass::RTTI))

6: {

7: // it's a MyClass object

8: }

9:

10: // check whether an object is instance of a derived class

11: if (myObj->IsA(RefCounted::RTTI))

12: {

13: // it's a RefCounted instance or some RefCounted-derived instance

14: }

15:

16: // get the class name of my object, this yields "MyNamespace::MyClass"

17: const String& className = myObj->GetClassName();

18:

19: // get the fourcc class identifier of my object, this yields 'MYCL'

20: const FourCC& fourcc = myObj->GetClassFourCC();

你也可以向中央工厂查询一个类是否已经注册:

1:     using namespace Core;

2:

3: // check if a class has been registered by class name

4: if (Factory::Instance()->ClassExists("MyNamespace::MyClass"))

5: {

6: // yep, the class exists

7: }

8:

9: // check if a class has been registered by class fourcc code

10: if (Factory::Instance()->ClassExists(FourCC('MYCL')))

11: {

12: // yep, the class exists

13: }

Nebula3单件

很多Nebula3的核心对象都是单件, 就是只存在一个实例, 并且所有其它对象都知道它.

你可以通过静态方法Instance()来访问单件, 它返回唯一实例的一个指针. 返回的指针保证是合法的. 如果在调用Instance()方法时对象实例不存在, 一个断点会被抛出:

1: // obtain a pointer to the Core::Server singleton

2:     Ptr<Core::Server> coreServer = Core::Server::Instance();

你也可以检查单件是否存在:

1: // does the Core::Server object exist?

2: if (Core::Server::HasInstance())

3: {

4: // yep, the core server exists

5: }

Nebula3提供了一些辅助的宏来实现单件:

1: // declare a singleton class

2: class MySingletonClass : public Core::RefCounted

3: {

4: DeclareClass(MySingletonClass);

5: DeclareSingleton(MySingletonClass);

6: public:

7: /// constructor

8:     MySingletonClass();

9: /// destructor

10: virtual ~MySingletonClass();

11: ...

12: };

13:

14: // implement the singleton class

15: ImplementClass(MyNamespace::MySingletonClass, 'MYSC', Core::RefCounted);

16: ImplementSingleton(MyNamespace::MySingletonClass);

17:

18: //------------------------------------------------------------------------------

19: /**

20:     Implements the Singleton constructor.

21: */

22: MySingletonClass::MySingletonClass()

23: {

24: ConstructSingleton;

25: }

26:

27: //------------------------------------------------------------------------------

28: /**

29:     Implements the Singleton destructor.

30: */

31: MySingletonClass:~MySingletonClass()

32: {

33: DestructSingleton;

34: }

DeclareSingleton()和ImplementSingleton()宏跟DeclareClass()和ImplementClass()宏差不多.它们在类中添加了一些静态方法(也就是Instance()和HasInstance()). 类的构造函数和析构函数必须包含ConstructSingletonDestructSingleton宏. ContructSingleton初始化了一个私有的单件指针并保证没有其它的类实例存在(如果不是, 会抛出断言). DestructSingleton让私有的单件指针无效化.

单件的访问默认是只有本地线程. 这意味着在一个线程中创建的单件无法被其他线程访问. 这使得”并行Nebula”大大简化了多线程编程. “并行Nebula”的基本思想是, 一个典型的Nebula3应用程序包含一些”Fat线程”, 每一个Fat线程都是运行在一个单独的CPU核心上. Fat线程可以用于实现异步IO, 渲染, 物理等等. 每一个Fat线程都初始化了它们自己的Nebula3运行环境, 它们执行特性任务所需的最少依赖. 这基本上消除了大部分Nebula3代码的同步问题, 并且把线程相关的代码集中到一个明确定义的代码区域中. “并行Nebula”的另一个好处就是, 程序员在多线程环境中编程时不需要关心太多. 大多数Nebula3代码看起来就像单线程代码一样, 但是它们却运行在各自的Fat线程中.

性能与内存占用的考虑

Nebula3核心层的一个设计目标就是减少底层代码的内存占用, 来更好的适应微型平台, 像手持设备. 这里有一些已经完成的目标:

  • RefCounted 类在每个实例中只增加了4byte用于引用计数.
  • RTTI机制在开头增加了30 到 60 byte, 但是这是对于每个类来说的, 而是不是每个实例.
  • 一个智能指针仅仅4 byte, 就像普通指针一样.
  • 一些监控结构只会在debug模型下创建, 特别是用来检测引擎计数泄露的RefCountedList.

这里一些用三种不种的创建方法创建一百万个RefCounted 对象所需的时间信息. 这些时间信息是在台Intel Pentium 800 MHz的笔记本上得出的.  

  • Create(): 0.29 seconds
  • FourCC: 0.65 seconds
  • 类名: 1.45 seconds

前言

目前这个引擎只是一个预览版, 很多功能仍在开发当中

硬件需求: 显卡支持ShaderModel3.0

作者Blog: http://flohofwoe.blogspot.com

用到的开源工程

为了避免版本之间的不兼容, 已经在SDK中包含

体系结构一览

  • Nebula3 分成三层, 每一层都是建立在另一层之上的:
    • 基础层: 最底层, 提供了一个图形和音频之下基本的平台抽象. 基础层可以用作任意类型的程序开发平台, 而不仅仅是实时3D程序.
    • 渲染层: 这是中间层, 它在基础层之上另加了许多特性, 像3D渲染, 音频, 物理和场景管理等.
    • 应用程序层: 这是最高的一层, 提供了一个完整的游戏框架, 这使得开发人员可以集中精力在游戏逻辑上, 而不用对关心各种细节实现.
  • Nebula3 会跟Mangalore 合为一个整体, Mangalore的各种子系统会集成到Nebula3的适合它们的层中去.
  • Nebula3 比 Nebula2更趋向于使用C++.
  • Nebula3通过引用计数和智能指针实现了对象生命周期的管理.
  • Nebula3的新对象模型使用一个4 byte的基类来代替Nebula2中70+ bytes的.
  • RTTI更高效, 更易用.
  • Nebula3仍然不使用C++异常, RTTI和STL(所有这些不是降低性能就是降低便携性).
  • 根据类名来创建对象更快更易用.
  • Nebula3 避免使用C Lib, 去除了附加的代码层.
  • Nebula3 使用LUA 代替TCL作为标准的脚本语言(当然也可以增加其它脚本语言的支持)

基础层

  • App 包含各种Application类
  • Attr 属性注册
  • Core                Nebula3的对象模型
  • Debug 程序内存转储和基于HTTP的调试信息(这个真是个好东西, 第一次听说)
  • Http 实现了HTTP服务器和客户端, 这样可以在浏览器中实时查看调试信息
  • IO 输入输出系统, 很方便
  • Math 基于C++的数学库, 没什么特别的
  • Memory 实现了定制的内存分配器
  • Messaging 消息定义
  • Net 最基本的基于TCP协议的C/S架构, 更多的功能会在高层的网络扩展中给出
  • Scripting 脚本系统
  • System 硬件平台和特定操作系统相关的一些特性
  • Threading 多线程
  • Timing 时间/定时器管理
  • Util 工具库, 包括各种容器, 数据结构, 还有一个强大的string类(抛弃STL吧!)

渲染层

  • CoreGraphics 图形库核心类
  • Frame 基于帧的处理, 如RenderTarget, PostProcess等
  • Graphics 一些实体类型定义, 如Camera之类
  • Input 输入设备, 就是鼠标键盘手柄…..
  • Lighting 光照处理, 包括阴影算法(竟然要SM3.0@_@)
  • Models 骨骼模型&粒子系统
  • RenderUtil 目前就只有一个Maya的摄像机…..
  • Resources 资源管理器

应用程序层

application_win32工程就是一个简单的游戏, 框架, 但是已经包含了许多特性, 如果物理, 数据库等. 不过好像还缺少GUI系统.

插件

目前有三个, 基于ODE的物理引擎, 基于SQLite的本地数据库, 基于nebula2模型的骨骼模型系统

编译

没什么好说的, 打开.sln直接编译就成了, 没见过这么简单的开源工程^_^

 

最近拿到SpeedTree资料,开始学习,并用到项目里去.

images/spt0.jpg

1.  该插件的特点:
api无关。它本身只是数据结构和逻辑架构,没有任何渲染语句子,因此为了把它应用到自己的引擎里,需要为之添加渲染相关的语句。而根据sdk的讲解,推荐用户为之搭建中间架构,用来联系SPEEDTREE与自己的引擎。这样做起码有两点好处,搭建的中间架构(也推荐别加任何api相关的语句),因此,即使你以后换了api(譬如从gl换成dx),中间架构还是可以继续沿用的。还有一个好处就是,当speedtree更新版本的时候,你也无须修改你的引擎,而只需要修改相对简单而且稳定的中间架构。

images/spt1.jpg

2.  该插件的具体特性:
注意,下面具体特性分析都是基于SDK里一个叫“DirectX9”的例子进行的,在这个例子里,它给出了最基本的使用方法,同时也向用户展示了它的基本特性。
A.  树的基本渲染
通过大场景的测试,DP的个数大致是树木棵数的两到三倍。详细分析下,发现
它一棵树分三部分绘制:树干和大树枝(branches),小树枝(fronds),树叶(leaves)
Branches:使用模型来绘制
Fronds:使用两个十字交叉的面模拟小树枝,为了节省三角形。
Leaves:使用billboard方式绘制,这样就能产生视觉效果比较好的叶子了。
它这样划分是出于以下三方面的考虑:这几部分的渲染状态不一样,动画的状态不一样,做LOD的时候也不一样。具体看下面的介绍。
B.  树的阴影系统
它包括两方面的阴影。首先是树干上的阴影。其次是整棵树在地面的投影。
树干的自阴影(self shadow)是预先生成的,至于生成的算法,可能是可以根据可穿透的光线跟踪,也可能是结合shadow map的逐象素地生成光照贴图(把树干的面都展开后,在对应的地方画上阴影).有了该光照贴图,那渲染树干的时候就可以跟树干本身的纹理进行混合产生比较真实的效果。
而整棵树在地面的透影子,则是使用一个矩形画出来的,阴影贴图也是预先生成好。渲染的时候浮在地面。
C.  树的动画
树的三部分的动画状态都是不一样的。这对优化有极其重要的作用。风小的时候,或是树离眼睛比较远的时候,可以不动树枝,而只是动树叶。而具体他们是怎么动的:
树叶的动画:就是一个billboard的来回平移以及他本身绕视坐标系统Z轴的转动。
树枝的动画:通过它引擎本身计算出来的矩阵进行动画。
而至于它具体怎么渲染动画的,它提供了基于CPU和GPU的方法。
基于CPU的方法是:创建顶点缓冲的时候, 使用D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY标记(这种方法能提高CPU修改和更新该缓冲的速度),渲染的时候实时更新顶点位置。
基于GPU的方法是:通过自定义的顶点shader程序进行,更新动画的时候,向shader传递常量数组。
D.  树的光照
它可以打开和关闭实时光照,对于实时光照,树干部分又分两种情况,对于没有法线贴图的树干,使用per-vertex的光照。而对于有法线贴图的,则使用per-pixcel光照。至于给不给树干渲染法线贴图,则根据具体的程序决定。
而对于树叶的渲染,因为它是一个billboard,因此也无法通过其法线来计算光照。它其实是根据这个 billboard的位置来确定其亮度的。通过把整棵树当成一个球来分析,而每个billboard的位置就相当于是球上的一点,结合光的方向,计算出该点的亮度。
E.  LOD的特点
其强大的LOD系统,为实现大规模的场景提供了有力的支持。这里的LOD分三方面:顶点的LOD,纹理的LOD,动画的LOD。
(1)  顶点的LOD:首先是针对树干,因为这里的树干是实实在在的模型。至于树干的建立,它里面是采用贝塞尔曲线来描述整个mesh的,贝塞尔曲线的描述方式无疑给即时高效率的LOD计算提供了可行性。同时这还针对树枝,远了之后,小树枝就不渲染了。到了一定的距离的时候,整棵树其实就变成一个billboard了。
(2)  纹理的LOD:树干上在最高精度的时候会有三套纹理:基本纹理,光照贴图,法线贴图。随着LOD的进行,可以依次减去法线贴图,光照贴图,最后是本身贴图,最后只为树干渲染一种颜色。
(3)  动画的LOD:现在有三种动画,大树枝(模型)的动画,小树枝(两个交叉面)的动画,以及树叶的动画。随着LOD的进行,依次去掉大树杆的动画,小树杆的动画,最后是树叶的动画。这也是符合视觉效果的。
F.  文件系统
用场景来分析的话,一个场景是.stf文件(Speed Tree Forest).该文件描述了每棵树的相关属性。而一棵树是通过一个.spt(Speed Tree)文件来描述的.用文本编辑器打开,就能看到里面记录了该树的所有信息。而该插件为此开发了配套了树木编辑器材。使用该编辑器,打开.spt文件之后,就可以对该树进行浏览以及编辑。
3.Speedtree使用实践
它提供给用户的一个最主要的类就是CSpeedTreeRT.这是一个speedtree对外界的接口,从SpeedTreeRT.h中可以看到,这个类其实是包括了该插件的核心类.因此,我们在使用该插件的时候,其实全都是通过这个接口。
譬如CSpeedTreeRT::SetCamera(eye, viewDir),通知它内部现在的摄像机的信息,然后它内部就根据这些信息计算出正确的billboard.
而如何加载一棵树呢?使用CSpeedTreeRT::LoadTree(const char *treefile);输入一个”.spt”文件,然后我们设置光照和风效果的方法如CSpeedTreeRT::SetBranchWindMethod,SetFrondWindMethod,SetBranchLightingMethod,SetLeafLightingMethod, SetLodLimits等,接着执行CSpeedTreeRT::Compute(),然后它里面就开始进行黑盒处理,最后我们就可以获取其几何数据(CspeedTreeRT::GetGeometry)进行渲染。获取之前还可以手动去设置LOD级别CSpeedTreeRT::SetLodLevel,然后你获取到的就是经过LOD处理的几何数据。
不过有一点需要要注意的是,speedtree里面用的是右手坐标系(尽管它说可以通过define Y_UP来改变坐标系统,但我没发现define改了之后有什么变化,很奇怪)。笔者开始的时候完全没注意到这点,发现搬到自己的架构后,树全都是横着的。当时死活发现不了问题,就去旋转每棵树。然后又发现那些树叶也无法正常地旋转成billboard,又查了很久。后来终于发现,是因为speedtree内部使用右手坐标系进行计算。而我的架构是使用左手,这样一来,连传给speedtree camera的数据都要修改了, CSpeedTreeRT::SetCamera(eye, viewDir),其中的eye,eyeDir,都得经过变换再传进去:
float3 viewDir=pCamera->GetViewDir();
float3 eye=pCamera->GetEye();
  float afDirection[3];
  afDirection[0] = viewDir.x;
  afDirection[2] = viewDir.y;
  afDirection[1] = -viewDir.z;
  CSpeedTreeRT::SetCamera(eye, afDirection);
4.把speedtree加到自己的引擎中去
以上所说的CSpeedTreeRT接口,笔者在使用的时候都是让一个CSpeedTreeRT对象汇聚到自己设计的一个tree类里。通过这种方式来封装speedtree,搭建中间架构。CSpeedTreeRT这接口也许多静态函数,譬如SetCamera,参照它的DEMO,
直接“CSpeedTreeRT::SetCamera(eye, eyeDir);”但要实现完美地跟自己的引擎相结合,也并不是一件容易的事情。主要是,自己的引擎本来就有一套完整的渲染系统,LOD系统,动画系统,而且跟speedtree的方式也不一样。一个极端的做法就是,对于SpeedTreeRT,屏蔽其实时计算,而是根据自己引擎的系统计算,这样的话, 其实是只利用了SpeedTree的数据结果了。而另外一个极端就是,不管 speedtree和自己引擎的关系,只保留简单的耦合,各自使用各自的系统,只是让他们的渲染行为(LOD,光照效果等)保持一致性。至于更好的办法,笔者也是在研讨中,我非常希望能跟读者进行探讨,这也是笔者写本笔记的动机之一。

http_imgload

Game组成员超过Hack组,不过Game的大部分人都不认识,有的甚至没有联系过。
我的好友中还有部分Hack没有联系上,至少应该还有20名左右吧,实际上加起来应该还比Game组的多。
我的好友中大部分应该是搞过C++或WinSDK编程的,至少是学过编程。
怀念从2000年至今的编程学习生涯,这是第三个QQ了,
最早一个QQ上的人大部分现在都应该是大牛了吧,可惜偶还是小弟,悲哀之。
这么多年过去了,发现自己依旧是那么无知无畏。
人一生所寻找的也许就是脚下的路吧,这样的人生才会意义。
这样才过的踏实一点,去年跟一朋友来到福州,来这里做游戏,
等到实际工作中才发现游戏不比传统软件行业,
这里的人比传统软件行业时的人更有想法,但大部分都不切换实际,甚至弱智。
为一点所谓的利益搞来搞去,其实真正的利益在BOSS那里,因为BOSS付出了。
不付出是得不到回报的,搞来搞去只是在牺牲自己的脑细胞。
不坚持原则,不追求完美,不追求质量,不切合实际,不明确目标。
完美的程序员至少是个政治家,虽然我不完美,但我是政治家,不要和我玩政治。
这里没有失败,因为我们只是为BOSS服务的。
损害别人并不能得到什么,因为每个人都选择的权利,因为和谁玩牌都一样。
输掉的只是大家的时间。

The Release builds include optimizaed code, but with the NiMemory system, NiMetrics, and release mode logging enabled. The Shipping builds do not have these systems enabled.
ship 和 release 工程设置基本相同,但没有NiMemory、NiMetrics、release mode logging.更像是非常稳定之后的版本.

不可否认 选择做程序员 源于真正意义上的兴趣和喜欢 在做程序员的这几年 我的生活也在一步步的提升 但同样 不可否认 在我个人眼里 就像选择其他种类的工作一样 只是一份职业 一种在社会上存身立足的手段 现在呢 又在想 自己以前想过 大家也时不时的都会想 的问题 那就是 程序员的人生 将如何规划 在校时 以及 工作后 都曾这么的想过: 做一个IT从业人员 做一个程序员 做上个一二年 然后 向上提升下 做一个项目经理 什么的 再然后 或许 开一家自己的小软件公司 或许 做一名软件顾问 再或者 开一个网站 开发一个自己的小软件 或者 真的不行的 利用做程序员这几年的积蓄 做一些小买卖 转行 另谋生计 总之 感觉做程序开发将是人生的一个过渡 可若真的按这种思路一过渡 就将是用我人生的青春年华大好时光的五六年 或者更多时间 遗憾的是 至今 对这种付出后的收获 我却没有把握 曾听有人言(这一定是国人说的): 一年管理成富翁 三年市场路路通 十年技术一场空 这话每每想来 心里都不免有些低落 低落的不是现在 而是在现在看来 自己一两年或几年后的生活 没有着落 没有依靠 而那时的自己 或许做了项目经理 薪资在才做程序员的后辈们看来 已很是不低 但却可能远远不够日常生活的进一步开支 因为我们每个人每时每刻都有让生活越来越好的念头和目标 我们不想自己上去了 又下去了 不想自己 由前几年刚毕业的 蓝领代码工人 好不容易辛苦努力才做到今天的 白领项目经理 接着却因年事问题 薪资待遇问题 而沦为 房奴 车奴 我们不回避 人生将由 1.幼年-童年-少年-青年 2.成年-壮年 3.老年 这三段的划分 也不忌讳自己真的有一点老不中用了 但我们不能容忍自己在 从三十岁到五十岁 这段时间里 碌碌无为 而现在 我们站在目前自己做程序员的角度 去看三五年后的而立之年 我们心里没底 我们站在而立之年的程序员的角度 去看自己30-50的人生成熟和收获的黄金时期 我们更多的可能看到的是灰暗和苦涩 那么 早知如此 何必当初 想问大家 也是在问自己 一句 程序员的人生 该将如何规划? ( 希望成功的前辈们 能多多赐教 也希望有同样思考的同辈们 能说说自己好的想法和规划 或许 这个问题 不单单是属于做程序员工作的同行们 也可能是属于所有现在 没有自己的事业 正在工作着的 各行各业的同志们 我思 故我在 没有对明天的思考 明天的我 也许就没有美好的未来 也许换种环境更好 比如出国 至少不会有职业歧视 至少技术与业务一视同仁 至少会认为每个人的工作同样重要 ) ­ *注:转载,部分修改.

由于项目的原由最近接触了很多优秀的项目,其中包括HTML排版引擎,以下对其做下简单的介绍和比较.

现在浏览器的内核引擎,基本上是三分天下:

  • Trident : IE 以Trident 作为内核引擎。
  • Gecko : Firefox 是基于 Gecko 开发。
  • WebKit : Safari, Google Chrome 基于 Webkit 开发。
  1. Trident

现代游戏已经不能没有声音,所以音频引擎成为游戏引擎中不可缺少的一部分.这是一篇介绍现代音频引擎的文章(http://hard.zol.com.cn/labs/2003/0520/60986.shtml).FMOD音频引擎(http://www.fmod.org)是一个非常不错的音频引擎,其使用也比较简单,下面做一些简单介绍:
一.基本准备
它是免费的,你可以从它们的主站上下载API等文件.之后,你需要添加头文件和库文件,如下(C/C++):

  • fmodvc.lib 用于 Microsoft Visual C++ 和 Codewarrior
  • fmodbc.lib 用于 Borland
  • fmodwc.lib 用于 Watcom
  • fmodcc.lib 用于 LCC-Win32
  • libfmod.a 用于 MingW and CygWin
  • fmod-3-7.lib 用于 GCC
    (参考:http://www.gamedev.net/reference/articles/article2098.asp
    之后,只要添加fmod.h头文件后就可以使用了.
    二.开始使用
    1.初始化
    开始播放声音前,需要进行初始化,很简单:
    FSOUND_Init (44100, 32, 0);
    第一个参数是输出HZ,第二是最大软件信道数可以不管也不会增加CPU负担,第三个参数可以设置一些标志可以不设置则赋值为0.
    2.基本常识
    FMOD将音频分为声音(sound)和音乐(music)两种.前者如:.MOD, .S3M, .XM, .IT, .MID, .RMI, .SGT or .FSB
    等,后者如: .WAV, .MP2, .MP3, .OGG or .RAW等.二者使用不同的函数处理.都可以通过采样后流的方式来处理.不过小文件一般通过采样方式,它可以多次播放但占用内存.大文件通过流方式,减少内存消耗.
    3.播放音乐
    首先定义一个FMUSIC_MODULE类型变量来作为文件句柄.然后就可以通过FMUSIC API来实现,如:
    装入文件:
    handle=FMUSIC_LoadSong("YourFileName");
    FMUSIC_PlaySong(handle);
    音量控制:FMUSIC_SetMasterVolume (handle, 255);后面的参数在0~255之间,值越大声音越大.
    暂停播放:FMUSIC_SetPaused (handle, true);
    重开始:FMUSIC_SetPaused (handle, false);
    循环播放:FMUSIC_SetLooping (handle, true);
    停止播放:FMUSIC_StopSong (handle);
    释放音频内存:FMUSIC_FreeSong (handle);
    下面是一个命令模式下的例子:
    #include <conio.h>
    #include "inc/fmod.h"
    FMUSIC_MODULE* handle;
    int main ()
    {
       // 初始化
       FSOUND_Init (44100, 32, 0);
       // 装如
       handle=FMUSIC_LoadSong ("canyon.mid");
       // 只播放一次
       // 播放midi文件时请关闭循环播放
        FMUSIC_SetLooping (handle, false);
       //播放
       FMUSIC_PlaySong (handle);
      // 按任一键结束
       while (!_kbhit())
       {
       }
       //释放
       FMUSIC_FreeSong (handle);
       FSOUND_Close();
    }
    4.播放声音
    4.1 采样(Sample)方式
    先定义FSOUND_SAMPLE类型变量,然后就可以使用FSOUND系列函数来实现,如:
    装如文件:
    handle=FSOUND_Sample_Load (0,"YourFileName",0,0,0);  //除文件名外的参数用于多采样或其它等
    FSOUND_PlaySound (0,handle);
    设置音量:FSOUND_SetVolume (handle, 255);
    暂听:FSOUND_SetPaused (handle, true);
    重新开始:FSOUND_SetPaused (handle, false);
    停止:FSOUND_StopSound (handle);
    释放:FSOUND_Sample_Free (handle);
    下面是一个简单的例子:
    #include <conio.h>
    #include "inc/fmod.h"
    FSOUND_SAMPLE* handle;
    int main ()
    {
       // 初始化
       FSOUND_Init (44100, 32, 0);
       // 装载和播放
       handle=FSOUND_Sample_Load (0,"sample.mp3",0, 0, 0);
       FSOUND_PlaySound (0,handle);
       // 按任一键结束
       while (!_kbhit())
       {
       }
       // 释放
       FSOUND_Sample_Free (handle);
       FSOUND_Close();
    }
    4.2 流(stream)方式
    先定义一个FSOUND_STREAM 类型变量,然后:
    装入文件:
    handle=FSOUND_Stream_Open("YourFileName",0, 0, 0);
    FSOUND_Stream_Play (0,handle);
       提示:3.7版本之前的方式是不一样的.
    停止:FSOUND_Stream_Stop (handle);
    释放:FSOUND_Stream_Close(handle);
    其它和前面是一样的.下面是一个简单的例子:
    #include <conio.h>
    #include "inc/fmod.h"
    FSOUND_STREAM* handle;
    void main ()
    {
       //init FMOD sound system
       FSOUND_Init (44100, 32, 0);
       //load and play sample
       handle=FSOUND_Stream_Open("sample.mp3",0, 0, 0);
       FSOUND_Stream_Play (0,handle);
       //wait until the users hits a key to end the app
       while (!_kbhit())
       {
       }
       //clean up
       FSOUND_Stream_Close(handle);
       FSOUND_Close();
    }
    5.关闭
    FSOUND_Close ();
    参考:
    A Quick Guide to FMOD by Joachim Rohdehttp://www.gamedev.net/reference/articles/article2098.asp
    FMOD wiki(http://www.devmaster.net/wiki/FMod