九天雁翎的博客
如果你想在软件业获得成功,就使用你知道的最强大的语言,用它解决你知道的最难的问题,并且等待竞争对手的经理做出自甘平庸的选择。 -- Paul Graham

非典型2D游戏引擎 Orx 源码阅读笔记(1) 总体结构

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

前言

    完全不了解Orx的可以参考我写的《orx 库简单介绍
》以及官方主页

    开始学习Orx其实很久了,但是一直仅仅是学习了一些基础的用法,没有深入研究源代码,在用Orx写游戏的时候常常会因为某个配置出现问题而完全束手无策,求助于iarwain,最近在移植一个Win32编好的游戏到IPhone上时,又碰到了问题,还是束手无策,因为我决定还是看看Orx的源代码,不然,命运总是不掌握在自己的手里,就像撞大运编程一样。这也是我当时选择引擎最大的要求之一,开源,所能带来的好处之一。而Orx这种"数据驱动"式的引擎,更加是使得了解源代码如此重要,因为我感觉对于我来说,我更加能够发现调用一个API的错误,而非常难去发现一个配置上的错误,我一直也认为这是Orx"数据驱动"的弊端之一,这也是我认为Orx非典型的原因。

 

源码阅读思路及目标

    对于源码阅读,我还是用个人习惯的的先从整体上了解各个模块,然后单独了解各模块源码,最后整个的走一下主要的执行流程,了解各个模块是怎么结合在一起的。

    对于Orx的源码阅读,我还是不准备达到到句句理解的地步,基本上还是以整体了解为主,顺便关心一下个人比较感兴趣的渲染部分,对源码的理解程度以最后能够完全脱离Orx的配置模块,靠自己的代码将各个模块组合起来,并实现基本的Orx功能为止。Orx作为跨平台引擎,我不能去分析所有其支持的平台,主要关心的版本是Win32版本及IPhone版本,当然,事实上因为Orx的跨平台主要是依赖于将SDL,GLFW等库作为插件来完成的,所以,其实在Orx这一层的代码其实都一样。(IPhone版本比较特殊)

    为了无论在何时都能找到本文对应的源码,这里使用最新的Orx1.2版本的源码,并未使用svn上的版本。

 

Orx总体结构

作为一个iarwain目标为完整game engine的Orx,模块的构成还是比较复杂的。

物理结构:

从Orx源代码的目录上可以基本区分出来:

Animation:动画部分

base:基础部分,包含一些为了跨平台而定义的宏,Orx本身需要的宏和常用常量,函数,还有Orx的模块处理

core:内核部分,包括时钟,配置,事件部分,本地化部分

debug:日志部分及FPS显示

display:显示部分

io:IO部分,包括文件IO,及摇杆,鼠标,键盘的输入。

main:main函数参数处理

math:数学相关部分,包括一个vector的实现

memory:内存处理部分,

object:object部分

physics:物理部分

plugin:插件部分

render:渲染部分,包括摄像机,特效,渲染器,shader,视口等部分

sound:声音部分

utils:工具类部分,包括用C语言实现的HashTable,List,Tree3大容器,一些有用的String函数,以及一个screenshot实现。(感觉这个放在display更加合适)

另外,Orx比较特殊的是包含一套插件:

通过插件来实现的部分有:Display,Joystick,Keyboard,Mouse,Physics,Render,Sound。

通用插件(实现除物理,声音以外的功能)本身现在有4套:GLFW
,IPhone,SDL
,SFML

其中SFML是1.1版本前使用的插件。GLFW是现在(1.2版本)默认使用的插件。

其中SFML,GLFW插件支持3大主流平台(Win32,Linux,MacOS),SDL插件仅支持Win32,Linux。

IPhone的插件是因为IPhone版本比较特殊而特别加入的,对应支持IPhone/IPad平台。

声音插件现在有SFML的插件和OpenAL的插件。SFML为1.1以前版本默认声音插件,OpenAL的实现为1.2版本默认声音插件。可能是因为OpenAL插件太好,所以作者虽然使用了SDL,但是并没有使用SDL的声音模块做SDL的声音插件。

物理插件目前仅有Box2D实现的插件。

逻辑结构:

从"orxModule.h"的一个枚举定义中,可以看到Orx作者为Orx整体的逻辑模块的划分。

/*
* Module enum
*/

typedef
 enum
 __orxMODULE_ID_t

{

  orxMODULE_ID_ANIM = 0
,

  orxMODULE_ID_ANIMPOINTER,

  orxMODULE_ID_ANIMSET,

  orxMODULE_ID_BANK,

  orxMODULE_ID_BODY,

  orxMODULE_ID_CAMERA,

  orxMODULE_ID_CLOCK,

  orxMODULE_ID_CONFIG,

  orxMODULE_ID_DISPLAY,

  orxMODULE_ID_EVENT,

  orxMODULE_ID_FILE,

  orxMODULE_ID_FILESYSTEM,

  orxMODULE_ID_FONT,

  orxMODULE_ID_FPS,

  orxMODULE_ID_FRAME,

  orxMODULE_ID_FX,

  orxMODULE_ID_FXPOINTER,

  orxMODULE_ID_GRAPHIC,

  orxMODULE_ID_INPUT,

  orxMODULE_ID_JOYSTICK,

  orxMODULE_ID_KEYBOARD,

  orxMODULE_ID_LOCALE,

  orxMODULE_ID_MAIN,

  orxMODULE_ID_MEMORY,

  orxMODULE_ID_MOUSE,

  orxMODULE_ID_OBJECT,

  orxMODULE_ID_PARAM,

  orxMODULE_ID_PHYSICS,

  orxMODULE_ID_PLUGIN,

  orxMODULE_ID_RENDER,

  orxMODULE_ID_SCREENSHOT,

  orxMODULE_ID_SHADER,

  orxMODULE_ID_SHADERPOINTER,

  orxMODULE_ID_SOUND,

  orxMODULE_ID_SOUNDPOINTER,

  orxMODULE_ID_SOUNDSYSTEM,

  orxMODULE_ID_SPAWNER,

  orxMODULE_ID_STRUCTURE,

  orxMODULE_ID_SYSTEM,

  orxMODULE_ID_TEXT,

  orxMODULE_ID_TEXTURE,

  orxMODULE_ID_VIEWPORT,

  orxMODULE_ID_NUMBER,

  orxMODULE_ID_MAX_NUMBER = 64
,

  orxMODULE_ID_NONE = orxENUM_NONE

} orxMODULE_ID;

因为英文命名的枚举已经很能说明各逻辑模块包含的内容了,这里就不一一介绍了。

引用的外部工程

现在已经不是需要所有事情都从头做起的年代了,作为游戏引擎,需要处理的各个方面内容实在太多,很难都完全自己处理,Orx使用了一些外部库来完成一些相应功能。

深蓝在Orx的官方中文论坛中进行了一些总结
,较为详细,可供参考。

我这里仅仅简单的说明一下:

Box2D:物理部分

freetype:制作字体工具时使用,因为Orx引擎本身并不支持freetype即时生成的材质实现字体显示,所以事实上Orx引擎本身并不需要freetype。

glfw,SDL,SFML:作为主要模块的插件实现,主要用于解决跨平台相关的问题。

SOIL:用于支持常用的图片格式。

OpenAL:声音处理。

stb_vorbis:ogg声音格式支持。

libsndfile:wav,aiff声音格式支持。

我描述的仅仅是大概的情况,关于引用外部工程以及插件的详细使用情况可以参考Orx的changelog

关于SFML

在最后提供一些额外的关于SFML信息,虽然与Orx的总体结构无关,但是也顺便在此记录。

很多外部工程的引用都是从新的1.2开始的,而且都是为了替代SFML的相关部分而引入的,也就是说,最后Orx用glfw/SDL + SOIL + OpenAL + stb_vorbis + libsndfile才实现了SFML的部分功能。这还真的仅仅是SFML的部分功能,仅仅是Orx需要的那一部分功能。这里可以看出SFML有多么强大,其作为simple and fast Multimedia library的库,与SDL(Simple DirectMedia Layer)相比真的是太不simple了。事实上,从SFML的License页
可以看到,SFML本身就使用了

这么多的外部库。而其中Orx最后使用用于替代SFML的相应部分的库,可以看到,也基本来自于SFML本身使用的这些库。。。。。。。。#@¥#@%……¥#@……¥6

iarwain对SFML的评价是强大,简单,但是buggy。

并且,从效率上来看,iarwain也是使用了这些外部库,但是在changelog中,有这么一句:

* IMPORTANT: Added new plugins for embedded versions: SDL plugins for win/linux (35-40% than the SFML ones) and GLFW plugins for win/linux/osx (~2% faster than SDL ones).

也就是说,在1.2版本比1.1版本要快35%一样,仅仅因为使用了新的插件替代了SFML。不知道SFML在simple与fast的平衡中,是否因为太过强大,功能太过丰富,而导致实现simple的代价过大。

features页面
上,可以看到,SFML甚至还包含一个网络模块。。。。。

不过,SFML用C++结合了上面这么多有用的库,实现那么多功能,还有一堆的其他语言(Python,Ruby,D,C#等)绑定,并且也使用很自由的zlib/png协议,感觉还是值得一试的。特别是当对效率没有那么高的要求的时候。。。。。。

 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

 

阅读全文....

SDL源码阅读笔记(3)渲染模块

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

    这是SDL中我最关心的模块,即SDL是怎么抽象渲染模块的接口然后实现跨平台的,已经在DirectX/OpenGL中绘制图形的。
    本文与前面两篇使用不同的描述方式,前面两篇文章以结构性的铺开分析为主,本文决定使用流程分析。

SDL+OpenGL

    对于OpenGL版本,由于主要的绘制都是由OpenGL的API来完成,与SDL关系并不大,所以只看SDL_SetVideoMode
部分。
    在写上一篇文章
的时候,我很惊讶的发现,即使是在SDL使用了DirectX版本的video driver后,仍然可以使用OpenGL来完成渲染。关键点在SDL_SetVideoMode
函数上,先看此函数。
使用OpenGL时,用以下方式调用此函数:
SDL_Surface* screen = SDL_SetVideoMode( WINDOW_WIDTH, WINDOW_HEIGHT, 16, SDL_OPENGL);
即最后一个参数为SDL_OPENGL。
此时,最后使用DirectX来渲染还是使用OpenGL来渲染,最大的不同自然在于surface的创建,
在SDL_SetVideoMode中会有这么一句:
mode = video->SetVideoMode(this, prev_mode,video_w,video_h,video_bpp,flags);
即使用当前video driver来设置video mode,由于我已经选中了directX的video driver了,所以实际调用的是:
SDL_Surface *DX5_SetVideoMode(_THIS, SDL_Surface *current,
                int width, int height, int bpp, Uint32 flags)
此函数中,有这么一段代码:

    /*
 If we are setting a GL mode, use GDI, not DirectX (yuck)
*/

    if
 ( flags & SDL_OPENGL ) {
        Uint32 Rmask, Gmask, Bmask;

        /*
 Recalculate the bitmasks if necessary
*/

        if
 ( bpp == current->format->BitsPerPixel ) {
            video = current;
        } else
 {
            switch
 (bpp) {
                case
 15
:
                case
 16
:
                if
 ( 0
 /*
DIB_SussScreenDepth() == 15
*/
 ) {
                    /*
 5-5-5
*/

                    Rmask = 0x00007c00
;
                    Gmask = 0x000003e0
;
                    Bmask = 0x0000001f
;
                } else
 {
                    /*
 5-6-5
*/

                    Rmask = 0x0000f800
;
                    Gmask = 0x000007e0
;
                    Bmask = 0x0000001f
;
                }
                break
;
                case
 24
:
                case
 32
:
                /*
 GDI defined as 8-8-8
*/

                Rmask = 0x00ff0000
;
                Gmask = 0x0000ff00
;
                Bmask = 0x000000ff
;
                break
;
                default
:
                Rmask = 0x00000000
;
                Gmask = 0x00000000
;
                Bmask = 0x00000000
;
                break
;
            }
            video = SDL_CreateRGBSurface(SDL_SWSURFACE, 0
, 0
, bpp,
                                         Rmask, Gmask, Bmask, 0
);
            if
 ( video == NULL
 ) {
                SDL_OutOfMemory();
                return
(NULL
);
            }
        }


看作者的注释:
/*
 If we are setting a GL mode, use GDI, not DirectX (yuck)
*/


啥都明白了,原来即使是使用DirectX的video driver,SDL会在SetVideoMode的时候判断是否是使用OpenGL,是的话还是改用了GDI。
然后,WIN_GL_SetupWindow函数中,完成了在Windows下使用OpenGL需要设定的内容。具体的内容可以参考《Win32下的OpenGL编程必须步骤
》。

既然使用DirectX版本video driver的SDL最后其实也是使用GDI版本的surface,这里回头来看看GDI版本创建surface的步骤。
在GDI版本的video driver实际调用的是DIB_SetVideoMode函数来设定VideMode。

    /*
 Recalculate the bitmasks if necessary
*/

    if
 ( bpp == current->format->BitsPerPixel ) {
        video = current;
    } else
 {
        switch
 (bpp) {
            case
 15
:
            case
 16
:
                if
 ( DIB_SussScreenDepth() == 15
 ) {
                    /*
 5-5-5
*/

                    Rmask = 0x00007c00
;
                    Gmask = 0x000003e0
;
                    Bmask = 0x0000001f
;
                } else
 {
                    /*
 5-6-5
*/

                    Rmask = 0x0000f800
;
                    Gmask = 0x000007e0
;
                    Bmask = 0x0000001f
;
                }
                break
;
            case
 24
:
            case
 32
:
                /*
 GDI defined as 8-8-8
*/

                Rmask = 0x00ff0000
;
                Gmask = 0x0000ff00
;
                Bmask = 0x000000ff
;
                break
;
            default
:
                Rmask = 0x00000000
;
                Gmask = 0x00000000
;
                Bmask = 0x00000000
;
                break
;
        }
        video = SDL_CreateRGBSurface(SDL_SWSURFACE,
                    0
, 0
, bpp, Rmask, Gmask, Bmask, 0
);
        if
 ( video == NULL
 ) {
            SDL_OutOfMemory();
            return
(NULL
);
        }
    }


其实与DX5_SetVideoMode中创建Surface的代码一模一样,DX5_SetVideoMode函数中所谓的使用GDI的surface其实指的就是拷贝一份GDI创建surface的代码过去吧。。。。。接下来,同样的调用WIN_GL_SetupWindow完成Windows下设置OpenGL使用的必要操作。
看完相关源代码后,发现一切还是比较简单。

SDL GDI video driver

    看完了SDL+OpenGL,这里了解一下SDL在使用GDI video driver时,自己附带的渲染功能。至于DirectX部分我就不看了,毕竟我对DirectX了解的不算太多,而且SDL用的DirectX版本实在太老了,但是,其实应该也不难懂,使用DDraw的方法其实和SDL的surface差不多。
SDL_LoadBMP其实是这样定义的一个宏:
#define SDL_LoadBMP(file)    SDL_LoadBMP_RW(SDL_RWFromFile(file, "rb"), 1)
SDL用SDL_Surface * SDL_LoadBMP_RW (SDL_RWops *src, int freesrc)这个函数来完成自己的从二进制数据中对BMP的解析,没有使用Windows提供的API(为了不同平台使用一套代码),加上BMP图形的数据解析起来还是比较简单的。

用类似
 SDL_Surface *screen = SDL_SetVideoMode(640 , 480 , 16 , SDL_DOUBLEBUF);
的代码创建surface的时候,(即不是使用OpenGL的时候)
在SetVideoMode的时候,SDL用以下代码来完成offscreen buffer:(这也是我第一次了解怎么使用Windows API来创建offscreen buffer)

        /*
 Create the offscreen bitmap buffer
*/

        hdc = GetDC(SDL_Window);
        screen_bmp = CreateDIBSection(hdc, binfo, DIB_RGB_COLORS,
                    (void
 **)(&video->pixels), NULL
, 0
);
        ReleaseDC(SDL_Window, hdc);
        SDL_free(binfo);
        if
 ( screen_bmp == NULL
 ) {
            if
 ( video != current ) {
                SDL_FreeSurface(video);
            }
            SDL_SetError("Couldn't create DIB section"
);
            return
(NULL
);
        }


最主要的API自然是CreateDIBSection,我查了下MSDN:

CreateDIBSection Function

The CreateDIBSection
function creates a DIB that applications can write to directly. The function gives you a pointer to the location of the bitmap bit values. You can supply a handle to a file-mapping object that the function will use to create the bitmap, or you can let the system allocate the memory for the bitmap.

按API的意思,其实这也不是原生用于offscreen buffer的,仅仅是一个可以直接写bitmap的内存。
在最后flip的时候,下面这段函数很关键:

static
 void
 DIB_NormalUpdate(_THIS, int
 numrects, SDL_Rect *rects)
{
    HDC hdc, mdc;
    int
 i;

    hdc = GetDC(SDL_Window);
    if
 ( screen_pal ) {
        SelectPalette(hdc, screen_pal, FALSE);
    }
    mdc = CreateCompatibleDC(hdc);
    SelectObject(mdc, screen_bmp);
    for
 ( i=0
; i
        BitBlt(hdc, rects[i].x, rects[i].y, rects[i].w, rects[i].h,
                    mdc, rects[i].x, rects[i].y, SRCCOPY);
    }
    DeleteDC(mdc);
    ReleaseDC(SDL_Window, hdc);
}


在这里,使用了

    mdc = CreateCompatibleDC(hdc);
    SelectObject(mdc, screen_bmp);

选择offscreen buffer到新创建的mdc中,然后从新创建的mdc向当前的hdc进行BitBlt,完成flip操作。当然,个人感觉,这种GDI模拟offscreen buffer的操作,在flip的时候还需要进行BitBlt,即使在两个dc间进行BitBlt比从内存到dc的快,但是效率上还是与OpenGL/D3D那种直接支持offscrren buffer的不在一个数量级,那可是直接切换显示不同区域的显存数据而已。

 

SDL源码大概浏览了一遍,没有细看,总的来说,看源码还是挺有帮助的,对于开源软件来说,文档也不能完全的详细说明每一个API的使用,但是对于开源软件,源码就是最好的文档。就此结束SDL的源码阅读过程,下一步,也许是略看Orx源码的时候了。。。。。。。。。。。。。。。。。。。。。。

 

总结

基本上,对于游戏来讲,OpenGL的渲染部分其实并没有太大价值,虽然其接口的确非常简单,(也许不是游戏可以考虑使用一下)但是,作为一个跨平台的库,SDL还是有其独特的价值,SDL提供了一个跨平台的窗口管理模块,事件系统,多线程模块,声音模块,并且能够让你很方便的使用OpenGL渲染与其配置,基本上可以看做是一个非常强大并且完整的OpenGL使用环境。就像glut/free glut曾经想要做到的那样,只不过SDL比其走的更远而已。类似的库还有GLFW
,完成的其实也不错。对于只想好好的关心渲染部分,对其他跨平台的脏活累活不感兴趣的,SDL算是不错的选择。

 

 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....

SDL源码阅读笔记(2) video dirver的初始化及选择

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

前一篇文章
讲了SDL的除video以外的大部分模块。本文主要关注SDL的video模块部分。

SDL中的video模块包含了大部分与平台相关的代码,并且SDL处理的很有技巧性,这里利用C语言的函数指针来模拟了一种类似于面向对象的效果。

主要的关键点在

SDL_VideoDevice *current_video = NULL;

这一句定义的全局变量current_video上,我们先来看看这个变量类型SDL_VideoDevice。

主要在SDL_sysvideo.h这个文件中

struct
 SDL_VideoDevice {

    /*
 * *
*/

    /*
 The name of this video driver
*/

    const
 char
 *name;

    /*
 * *
*/

    /*
 Initialization/Query functions
*/

    /*
 Initialize the native video subsystem, filling 'vformat' with the

       "best" display pixel format, returning 0 or -1 if there's an error.

     */

    int
 (*VideoInit)(_THIS, SDL_PixelFormat *vformat);

    /*
 List the available video modes for the given pixel format, sorted

       from largest to smallest.

     */

    SDL_Rect **(*ListModes)(_THIS, SDL_PixelFormat *format, Uint32 flags);

    /*
 Set the requested video mode, returning a surface which will be

       set to the SDL_VideoSurface.  The width and height will already

       be verified by ListModes(), and the video subsystem is free to

       set the mode to a supported bit depth different from the one

       specified -- the desired bpp will be emulated with a shadow

       surface if necessary.  If a new mode is returned, this function

       should take care of cleaning up the current mode.

     */

    SDL_Surface *(*SetVideoMode)(_THIS, SDL_Surface *current,

                int
 width, int
 height, int
 bpp, Uint32 flags);

    /*
 Toggle the fullscreen mode
*/

    int
 (*ToggleFullScreen)(_THIS, int
 on);

    /*
 This is called after the video mode has been set, to get the

       initial mouse state.  It should queue events as necessary to

       properly represent the current mouse focus and position.

     */

    void
 (*UpdateMouse)(_THIS);

    /*
 Create a YUV video surface (possibly overlay) of the given

       format.  The hardware should be able to perform at least 2x

       scaling on display.

     */

    SDL_Overlay *(*CreateYUVOverlay)(_THIS, int
 width, int
 height,

                                     Uint32 format, SDL_Surface *display);

        /*
 Sets the color entries { firstcolor .. (firstcolor+ncolors-1) }

       of the physical palette to those in 'colors'. If the device is

       using a software palette (SDL_HWPALETTE not set), then the

       changes are reflected in the logical palette of the screen

       as well.

       The return value is 1 if all entries could be set properly

       or 0 otherwise.

    */

    int
 (*SetColors)(_THIS, int
 firstcolor, int
 ncolors,

             SDL_Color *colors);

    /*
 This pointer should exist in the native video subsystem and should

       point to an appropriate update function for the current video mode

     */

    void
 (*UpdateRects)(_THIS, int
 numrects, SDL_Rect *rects);

    /*
 Reverse the effects VideoInit() -- called if VideoInit() fails

       or if the application is shutting down the video subsystem.

    */

    void
 (*VideoQuit)(_THIS);

    /*
 * *
*/

    /*
 Hardware acceleration functions
*/

    /*
 Information about the video hardware
*/

    SDL_VideoInfo info;

    /*
 The pixel format used when SDL_CreateRGBSurface creates SDL_HWSURFACEs with alpha
*/

    SDL_PixelFormat* displayformatalphapixel;

    

    /*
 Allocates a surface in video memory
*/

    int
 (*AllocHWSurface)(_THIS, SDL_Surface *surface);

    /*
 Sets the hardware accelerated blit function, if any, based

       on the current flags of the surface (colorkey, alpha, etc.)

     */

    int
 (*CheckHWBlit)(_THIS, SDL_Surface *src, SDL_Surface *dst);

    /*
 Fills a surface rectangle with the given color
*/

    int
 (*FillHWRect)(_THIS, SDL_Surface *dst, SDL_Rect *rect, Uint32 color);

    /*
 Sets video mem colorkey and accelerated blit function
*/

    int
 (*SetHWColorKey)(_THIS, SDL_Surface *surface, Uint32 key);

    /*
 Sets per surface hardware alpha value
*/

    int
 (*SetHWAlpha)(_THIS, SDL_Surface *surface, Uint8 value);

    /*
 Returns a readable/writable surface
*/

    int
 (*LockHWSurface)(_THIS, SDL_Surface *surface);

    void
 (*UnlockHWSurface)(_THIS, SDL_Surface *surface);

    /*
 Performs hardware flipping
*/

    int
 (*FlipHWSurface)(_THIS, SDL_Surface *surface);

    /*
 Frees a previously allocated video surface
*/

    void
 (*FreeHWSurface)(_THIS, SDL_Surface *surface);

    /*
 * *
*/

    /*
 Gamma support
*/

    Uint16 *gamma;

    /*
 Set the gamma correction directly (emulated with gamma ramps)
*/

    int
 (*SetGamma)(_THIS, float
 red, float
 green, float
 blue);

    /*
 Get the gamma correction directly (emulated with gamma ramps)
*/

    int
 (*GetGamma)(_THIS, float
 *red, float
 *green, float
 *blue);

    /*
 Set the gamma ramp
*/

    int
 (*SetGammaRamp)(_THIS, Uint16 *ramp);

    /*
 Get the gamma ramp
*/

    int
 (*GetGammaRamp)(_THIS, Uint16 *ramp);

    /*
 * *
*/

    /*
 OpenGL support
*/

    /*
 Sets the dll to use for OpenGL and loads it
*/

    int
 (*GL_LoadLibrary)(_THIS, const
 char
 *path);

    /*
 Retrieves the address of a function in the gl library
*/

    void
* (*GL_GetProcAddress)(_THIS, const
 char
 *proc);

    /*
 Get attribute information from the windowing system.
*/

    int
 (*GL_GetAttribute)(_THIS, SDL_GLattr attrib, int
* value);

    /*
 Make the context associated with this driver current
*/

    int
 (*GL_MakeCurrent)(_THIS);

    /*
 Swap the current buffers in double buffer mode.
*/

    void
 (*GL_SwapBuffers)(_THIS);

    /*
 OpenGL functions for SDL_OPENGLBLIT
*/

#if SDL_VIDEO_OPENGL

#if !defined(__WIN32__)

#define WINAPI

#endif

#define SDL_PROC(ret,func,params) ret (WINAPI *func) params;

#include
"SDL_glfuncs.h"

#undef SDL_PROC

    /*
 Texture id
*/

    GLuint texture;
#endif

    int
 is_32bit;

 

    /*
 * *
*/

    /*
 Window manager functions
*/

    /*
 Set the title and icon text
*/

    void
 (*SetCaption)(_THIS, const
 char
 *title, const
 char
 *icon);

    /*
 Set the window icon image
*/

    void
 (*SetIcon)(_THIS, SDL_Surface *icon, Uint8 *mask);

    /*
 Iconify the window.

       This function returns 1 if there is a window manager and the

       window was actually iconified, it returns 0 otherwise.

    */

    int
 (*IconifyWindow)(_THIS);

    /*
 Grab or ungrab keyboard and mouse input
*/

    SDL_GrabMode (*GrabInput)(_THIS, SDL_GrabMode mode);

    /*
 Get some platform dependent window information
*/

    int
 (*GetWMInfo)(_THIS, SDL_SysWMinfo *info);

    /*
 * *
*/

    /*
 Cursor manager functions
*/

    /*
 Free a window manager cursor

       This function can be NULL if CreateWMCursor is also NULL.

     */

    void
 (*FreeWMCursor)(_THIS, WMcursor *cursor);

    /*
 If not NULL, create a black/white window manager cursor
*/

    WMcursor *(*CreateWMCursor)(_THIS,

        Uint8 *data, Uint8 *mask, int
 w, int
 h, int
 hot_x, int
 hot_y);

    /*
 Show the specified cursor, or hide if cursor is NULL
*/

    int
 (*ShowWMCursor)(_THIS, WMcursor *cursor);

    /*
 Warp the window manager cursor to (x,y)

       If NULL, a mouse motion event is posted internally.

     */

    void
 (*WarpWMCursor)(_THIS, Uint16 x, Uint16 y);

    /*
 If not NULL, this is called when a mouse motion event occurs
*/

    void
 (*MoveWMCursor)(_THIS, int
 x, int
 y);

    /*
 Determine whether the mouse should be in relative mode or not.

       This function is called when the input grab state or cursor

       visibility state changes.

       If the cursor is not visible, and the input is grabbed, the

       driver can place the mouse in relative mode, which may result

       in higher accuracy sampling of the pointer motion.

    */

    void
 (*CheckMouseMode)(_THIS);

    /*
 * *
*/

    /*
 Event manager functions
*/

    /*
 Initialize keyboard mapping for this driver
*/

    void
 (*InitOSKeymap)(_THIS);

    /*
 Handle any queued OS events
*/

    void
 (*PumpEvents)(_THIS);

    /*
 * *
*/

    /*
 Data common to all drivers
*/

    SDL_Surface *screen;

    SDL_Surface *shadow;

    SDL_Surface *visible;

        SDL_Palette *physpal;   /*
 physical palette, if != logical palette
*/

        SDL_Color *gammacols;   /*
 gamma-corrected colours, or NULL
*/

    char
 *wm_title;

    char
 *wm_icon;

    int
 offset_x;

    int
 offset_y;

    SDL_GrabMode input_grab;

    /*
 Driver information flags
*/

    int
 handles_any_size;  /*
 Driver handles any size video mode
*/

    /*
 * *
*/

    /*
 Data used by the GL drivers
*/

    struct
 {

        int
 red_size;

        int
 green_size;

        int
 blue_size;

        int
 alpha_size;

        int
 depth_size;

        int
 buffer_size;

        int
 stencil_size;

        int
 double_buffer;

        int
 accum_red_size;

        int
 accum_green_size;

        int
 accum_blue_size;

        int
 accum_alpha_size;

        int
 stereo;

        int
 multisamplebuffers;

        int
 multisamplesamples;

        int
 accelerated;

        int
 swap_control;

        int
 driver_loaded;

        char
 driver_path[256
];

        void
* dll_handle;

    } gl_config;

    /*
 * *
*/

    /*
 Data private to this driver
*/

    struct
 SDL_PrivateVideoData *hidden;

    struct
 SDL_PrivateGLData *gl_data;

    /*
 * *
*/

    /*
 The function used to dispose of this structure
*/

    void
 (*free)(_THIS);

};

这个结构主要包含的是函数指针,每个函数指针表示了一个与平台相关的函数。在运行时,给这些函数指针赋值,指定成对应平台的函数实现,以此实现了使用此结构指针current_video的上层的代码的稳定与一致。

这里首先关心两个流程,其一,初始化给这些函数指针赋值的过程。

这个过程在SDL_VideoInit函数中实现:

SDL初始化基本流程:

SDL_Init(SDL_INIT_VIDEO)->SDL_InitSubSystem(SDL_INIT_VIDEO)->SDL_VideoInit()

中途会调用以"SDL_VIDEODRIVER"为参数调用SDL_getenv函数来获取全局配置中指定的对应的video driver。

代码如下:

        if
 ( SDL_VideoInit(SDL_getenv("SDL_VIDEODRIVER"
),

                           (flags&SDL_INIT_EVENTTHREAD)) < 0
 ) {

            return
(-1
);

        }

我没有设定,所以会以name = NULL为第一参数来调用SDL_VideoInit,也就是让SDL自己选择一个video driver。

SDL会从一个

typedef
 struct
 VideoBootStrap {

    const
 char
 *name;

    const
 char
 *desc;

    int
 (*available)(void
);

    SDL_VideoDevice *(*create)(int
 devindex);

} VideoBootStrap;

结构的全局变量bootstrap数组中选取一个可以使用的video driver。

一共有这么多可能的video driver:

/*
 Available video drivers
*/

static
 VideoBootStrap *bootstrap[] = {
#if SDL_VIDEO_DRIVER_QUARTZ

    &QZ_bootstrap,
#endif

#if SDL_VIDEO_DRIVER_X11

    &X11_bootstrap,
#endif

#if SDL_VIDEO_DRIVER_DGA

    &DGA_bootstrap,
#endif

#if SDL_VIDEO_DRIVER_NANOX

    &NX_bootstrap,
#endif

#if SDL_VIDEO_DRIVER_IPOD

    &iPod_bootstrap,
#endif

#if SDL_VIDEO_DRIVER_QTOPIA

    &Qtopia_bootstrap,
#endif

#if SDL_VIDEO_DRIVER_WSCONS

    &WSCONS_bootstrap,
#endif

#if SDL_VIDEO_DRIVER_FBCON

    &FBCON_bootstrap,
#endif

#if SDL_VIDEO_DRIVER_DIRECTFB

    &DirectFB_bootstrap,
#endif

#if SDL_VIDEO_DRIVER_PS2GS

    &PS2GS_bootstrap,
#endif

#if SDL_VIDEO_DRIVER_PS3

    &PS3_bootstrap,
#endif

#if SDL_VIDEO_DRIVER_GGI

    &GGI_bootstrap,
#endif

#if SDL_VIDEO_DRIVER_VGL

    &VGL_bootstrap,
#endif

#if SDL_VIDEO_DRIVER_SVGALIB

    &SVGALIB_bootstrap,
#endif

#if SDL_VIDEO_DRIVER_GAPI

    &GAPI_bootstrap,
#endif

#if SDL_VIDEO_DRIVER_WINDIB

    &WINDIB_bootstrap,
#endif

#if SDL_VIDEO_DRIVER_DDRAW

    &DIRECTX_bootstrap,
#endif

#if SDL_VIDEO_DRIVER_BWINDOW

    &BWINDOW_bootstrap,
#endif

#if SDL_VIDEO_DRIVER_TOOLBOX

    &TOOLBOX_bootstrap,
#endif

#if SDL_VIDEO_DRIVER_DRAWSPROCKET

    &DSp_bootstrap,
#endif

#if SDL_VIDEO_DRIVER_PHOTON

    &ph_bootstrap,
#endif

#if SDL_VIDEO_DRIVER_EPOC

    &EPOC_bootstrap,
#endif

#if SDL_VIDEO_DRIVER_XBIOS

    &XBIOS_bootstrap,
#endif

#if SDL_VIDEO_DRIVER_GEM

    &GEM_bootstrap,
#endif

#if SDL_VIDEO_DRIVER_PICOGUI

    &PG_bootstrap,
#endif

#if SDL_VIDEO_DRIVER_DC

    &DC_bootstrap,
#endif

#if SDL_VIDEO_DRIVER_NDS

    &NDS_bootstrap,
#endif

#if SDL_VIDEO_DRIVER_RISCOS

    &RISCOS_bootstrap,
#endif

#if SDL_VIDEO_DRIVER_OS2FS

    &OS2FSLib_bootstrap,
#endif

#if SDL_VIDEO_DRIVER_AALIB

    &AALIB_bootstrap,
#endif

#if SDL_VIDEO_DRIVER_CACA

    &CACA_bootstrap,
#endif

#if SDL_VIDEO_DRIVER_DUMMY

    &DUMMY_bootstrap,
#endif

    NULL

};

都是通过宏来筛选的。

-        bootstrap    0x100a2b1c bootstrap    VideoBootStrap * [4]

+        [0x0]    0x100a1160 _WINDIB_bootstrap {name=0x10099428 "windib" desc=0x1009940c "Win95/98/NT/2000/CE GDI" available=0x1005eaf0 ...}    VideoBootStrap *

+        [0x1]    0x100a25e8 _DIRECTX_bootstrap {name=0x10099ab4 "directx" desc=0x10099a98 "Win95/98/2000 DirectX" available=0x100651d0 ...}    VideoBootStrap *

+        [0x2]    0x100a2aec _DUMMY_bootstrap {name=0x100995a4 "dummy" desc=0x1009b3e4 "SDL dummy video driver" available=0x10072090 ...}    VideoBootStrap *

+        [0x3]    0x00000000 {name=??? desc=??? available=??? ...}    VideoBootStrap *

在我的电脑上(Win32),一共有上面3种可能,windib(即GDI),directX,dummy。事实上OpenGL在Win32的环境中都没有列出来,当然,但是在以前的例子中,我还是使用了SDL+OpenGL来渲染,原因在于video毕竟不是如同其名字所示,仅仅包含渲染或者视频方面的东西,它其实代表了大部分SDL与平台相关的东西,渲染在SDL中也仅仅是占比较小的一部分。

选取video driver的时候,先调用VideoBootStrap
结构中的available
函数来判断此video driver有效则通过此结构的create函数来创建一个video driver.

如下:

        for
 ( i=0
; bootstrap[i]; ++i ) {

            if
 ( bootstrap[i]->available() ) {

                video = bootstrap[i]->create(index);

                if
 ( video != NULL
 ) {

                    break
;

                }

            }

        }

创建的时候附带index,并且一旦创建成功,就停止了创建过程,在我的例子中,由于GDI方式排在第一,所以事实上,SDL在Win32环境下默认是选择了使用GDI的video driver。

此GDI video driver的create函数如下:

static
 SDL_VideoDevice *DIB_CreateDevice(int
 devindex)

{

    SDL_VideoDevice *device;

    /*
 Initialize all variables that we clean on shutdown
*/

    device = (SDL_VideoDevice *)SDL_malloc(sizeof
(SDL_VideoDevice));

    if
 ( device ) {

        SDL_memset(device, 0
, (sizeof
 *device));

        device->hidden = (struct
 SDL_PrivateVideoData *)

                SDL_malloc((sizeof
 *device->hidden));

        if
(device->hidden){

            SDL_memset(device->hidden, 0
, (sizeof
 *device->hidden));

            device->hidden->dibInfo = (DibInfo *)SDL_malloc((sizeof
(DibInfo)));

            if
(device->hidden->dibInfo == NULL
)

            {

                SDL_free(device->hidden);

                device->hidden = NULL
;

            }

        }

        

        device->gl_data = (struct
 SDL_PrivateGLData *)

                SDL_malloc((sizeof
 *device->gl_data));

    }

    if
 ( (device == NULL
) || (device->hidden == NULL
) ||

                         (device->gl_data == NULL
) ) {

        SDL_OutOfMemory();

        DIB_DeleteDevice(device);

        return
(NULL
);

    }

    SDL_memset(device->hidden->dibInfo, 0
, (sizeof
 *device->hidden->dibInfo));

    SDL_memset(device->gl_data, 0
, (sizeof
 *device->gl_data));

    /*
 Set the function pointers
*/

    device->VideoInit = DIB_VideoInit;

    device->ListModes = DIB_ListModes;

    device->SetVideoMode = DIB_SetVideoMode;

    device->UpdateMouse = WIN_UpdateMouse;

    device->SetColors = DIB_SetColors;

    device->UpdateRects = NULL
;

    device->VideoQuit = DIB_VideoQuit;

    device->AllocHWSurface = DIB_AllocHWSurface;

    device->CheckHWBlit = NULL
;

    device->FillHWRect = NULL
;

    device->SetHWColorKey = NULL
;

    device->SetHWAlpha = NULL
;

    device->LockHWSurface = DIB_LockHWSurface;

    device->UnlockHWSurface = DIB_UnlockHWSurface;

    device->FlipHWSurface = NULL
;

    device->FreeHWSurface = DIB_FreeHWSurface;

    device->SetGammaRamp = DIB_SetGammaRamp;

    device->GetGammaRamp = DIB_GetGammaRamp;
#if SDL_VIDEO_OPENGL

    device->GL_LoadLibrary = WIN_GL_LoadLibrary;

    device->GL_GetProcAddress = WIN_GL_GetProcAddress;

    device->GL_GetAttribute = WIN_GL_GetAttribute;

    device->GL_MakeCurrent = WIN_GL_MakeCurrent;

    device->GL_SwapBuffers = WIN_GL_SwapBuffers;
#endif

    device->SetCaption = WIN_SetWMCaption;

    device->SetIcon = WIN_SetWMIcon;

    device->IconifyWindow = WIN_IconifyWindow;

    device->GrabInput = WIN_GrabInput;

    device->GetWMInfo = WIN_GetWMInfo;

    device->FreeWMCursor = WIN_FreeWMCursor;

    device->CreateWMCursor = WIN_CreateWMCursor;

    device->ShowWMCursor = WIN_ShowWMCursor;

    device->WarpWMCursor = WIN_WarpWMCursor;

    device->CheckMouseMode = WIN_CheckMouseMode;

    device->InitOSKeymap = DIB_InitOSKeymap;

    device->PumpEvents = DIB_PumpEvents;

    /*
 Set up the windows message handling functions
*/

    WIN_Activate = DIB_Activate;

    WIN_RealizePalette = DIB_RealizePalette;

    WIN_PaletteChanged = DIB_PaletteChanged;

    WIN_WinPAINT = DIB_WinPAINT;

    HandleMessage = DIB_HandleMessage;

    device->free = DIB_DeleteDevice;

    /*
 We're finally ready
*/

    return
 device;

}

开始时为video driver分配内存,然后为相应的参数赋值,最最重要的就是为video driver的函数指针赋值,赋值成当前video driver的函数,以此实现我开始说的,以C语言实现类似面向对象的效果。

video driver的那一堆函数指针就像是抽象的接口,这里的函数就像是子类的实现。上层逻辑只需要使用video driver的指针并调用其中的函数即可,完全统一,并且不用关心指针具体是调用了哪个"子类“的函数。因为习惯了C++,我很少使用C语言来编写大规模的代码,所以对这些特性并不是非常熟悉,但是在C语言实现面向对象特性方面,我见过几派,我感觉这种方式算是比较好的,比完全使用宏来模拟C++的效果看上去要更加容易理解和自然。当然,因为C语言的确没有"标准"的面向对象实现方式,所以到底那个更好,也只能是见仁见智的问题了,估计也会像"大括号战争”一样没有休止。

此处还值得一提的是,SDL_VIDEO_OPENGL宏是默认开启的,也就是说,在Win32下使用GDI这个默认的video driver时,

SDL进行了
    device->GL_LoadLibrary = WIN_GL_LoadLibrary;

    device->GL_GetProcAddress = WIN_GL_GetProcAddress;

    device->GL_GetAttribute = WIN_GL_GetAttribute;

    device->GL_MakeCurrent = WIN_GL_MakeCurrent;

    device->GL_SwapBuffers = WIN_GL_SwapBuffers;

这些函数的赋值。形成了对OpenGL的支持。这里与我以前了解的有些差异,因为我以前以为SDL在Win32下是默认使用DirectX加速的,现在看来并不是,就如侯捷所言,"源码面前了无秘密",这些点点滴滴的东西,也算是看源码的一种收获。

然后,我查看了文档:
SDL支持哪些系统平台?

有如下描述:

 

  • 有两个版本,一个是适合所有基于Win32的系统的安全版本,另一个是基于DirectX的高性能版本。
  • 安全版本的视频显示采用GDI。高性能版本采用DirectDraw,并支持硬件加速。
  • 安全版本的音频回放采用waveOut API。高性能版本采用DirectSound。

这里可以看出,默认的时候SDL使用了Win32系统的安全版本。

 

此步初始化后,再利用video driver自己的VideoInit函数再次进行针对特殊的video driver的初始化。然后再开始SDL的事件线程(详细内容见上节)

然后,此时已经回到SDL_InitSubSystem函数了,此函数还需要进行一些其他模块的初始化,比如时间模块,摇杆等,这里就不多讲了。

看到这里,对于初始化部分的流程应该就比较清楚了,现在有个问题:

怎么改变SDL使用的video driver的默认值?比如,改成DirectX版本的。

从上面来看,有两种办法,

其一,改变bootstrap
数组中各driver的顺序,将directX的driver提到第一,那样默认就初始化directX版本了。

其二,让SDL_getenv("SDL_VIDEODRIVER"
)

返回directX版本driver的名字。

个人感觉方法二明显更加自然一些,感觉也是SDL作者提供的选择方案。那么,尝试一下,这里我通过SDL提供的接口SDL_putenv来设置环境变量,看看效果。

根据SDL_getenv的调用及前面查看的bootstrap的值,知道SDL查看的环境变量名为SDL_VIDEODRIVER,directX表示的driver名字为directx,所以在初始化前添加如下代码:

SDL_putenv("SDL_VIDEODRIVER=directx");

可以看到效果是以"directx"为第一参数调用SDL_VideoInit,并且进入了一下代码:

        for
 ( i=0
; bootstrap[i]; ++i ) {

            if
 ( SDL_strcasecmp(bootstrap[i]->name, driver_name) == 0
) {

                if
 ( bootstrap[i]->available() ) {

                    video = bootstrap[i]->create(index);

                    break
;

                }

            }

        }

此时,通过SDL_strcasecmp的调用,略过了GDI的video driver,然后使用了directX的driver。首先进入的available函数是:DX5_Available,晕了,DX5。。。。。什么年代的东西啊。。。。。。无语中。

在DX5_Available的调用中,仅仅通过以下语句判断了DINPUT.DLL和DDRAW.DLL动态库及DirectDrawCreate函数的存在,来判断DX5 driver是否可用。

DInputDLL = LoadLibrary(TEXT("DINPUT.DLL"));

DDrawDLL = LoadLibrary(TEXT("DDRAW.DLL"));

DDrawCreate = (void *)GetProcAddress(DDrawDLL, TEXT("DirectDrawCreate"));

同时,上述语句也说明,SDL使用的DirectX加速使用的是DX5.....并且使用的是DirectDraw,在那古老的年代,我不知道有没有D3D,不过对DDraw的使用倒是印证了我初看SDL渲染接口时的印象,抽象的接口与DDraw太像了。。。。。。。下面函数赋值的时候应该还能看到。

DX5_CreateDevice为SDL DirectX driver的创建函数,如同GDI版本一样对函数进行赋值。

    /*
 Set the function pointers
*/

    device->VideoInit = DX5_VideoInit;

    device->ListModes = DX5_ListModes;

    device->SetVideoMode = DX5_SetVideoMode;

    device->UpdateMouse = WIN_UpdateMouse;

    device->CreateYUVOverlay = DX5_CreateYUVOverlay;

    device->SetColors = DX5_SetColors;

    device->UpdateRects = NULL
;

    device->VideoQuit = DX5_VideoQuit;

    device->AllocHWSurface = DX5_AllocHWSurface;

    device->CheckHWBlit = DX5_CheckHWBlit;

    device->FillHWRect = DX5_FillHWRect;

    device->SetHWColorKey = DX5_SetHWColorKey;

    device->SetHWAlpha = DX5_SetHWAlpha;

    device->LockHWSurface = DX5_LockHWSurface;

    device->UnlockHWSurface = DX5_UnlockHWSurface;

    device->FlipHWSurface = DX5_FlipHWSurface;

    device->FreeHWSurface = DX5_FreeHWSurface;

    device->SetGammaRamp = DX5_SetGammaRamp;

    device->GetGammaRamp = DX5_GetGammaRamp;
#if SDL_VIDEO_OPENGL

    device->GL_LoadLibrary = WIN_GL_LoadLibrary;

    device->GL_GetProcAddress = WIN_GL_GetProcAddress;

    device->GL_GetAttribute = WIN_GL_GetAttribute;

    device->GL_MakeCurrent = WIN_GL_MakeCurrent;

    device->GL_SwapBuffers = WIN_GL_SwapBuffers;
#endif

    device->SetCaption = WIN_SetWMCaption;

    device->SetIcon = WIN_SetWMIcon;

    device->IconifyWindow = WIN_IconifyWindow;

    device->GrabInput = WIN_GrabInput;

    device->GetWMInfo = WIN_GetWMInfo;

    device->FreeWMCursor = WIN_FreeWMCursor;

    device->CreateWMCursor = WIN_CreateWMCursor;

    device->ShowWMCursor = WIN_ShowWMCursor;

    device->WarpWMCursor = WIN_WarpWMCursor;

    device->CheckMouseMode = WIN_CheckMouseMode;

    device->InitOSKeymap = DX5_InitOSKeymap;

    device->PumpEvents = DX5_PumpEvents;

    /*
 Set up the windows message handling functions
*/

    WIN_Activate = DX5_Activate;

    WIN_RealizePalette = DX5_RealizePalette;

    WIN_PaletteChanged = DX5_PaletteChanged;

    WIN_WinPAINT = DX5_WinPAINT;

    HandleMessage = DX5_HandleMessage;

这里我们可以看到,相对GDI版本而言,除了速度之外,DirectX版本明显支持的特性更多,上面的函数指针赋值就没有GDI版本那么多NULL了,全部都有对应的函数。

最让人惊讶的时候,还是有OpenGL的函数。最后经过我测试,的确也是能够使用OpenGL。。。。。。

很显然,关键在于

SDL_Surface* screen = SDL_SetVideoMode( WINDOW_WIDTH, WINDOW_HEIGHT, 16, SDL_OPENGL);

一句的调用。

因为本文太长,在Google Document上输入都已经很卡了,所以留待下篇文章再看。


 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

 

阅读全文....

SDL源码阅读笔记(1) 基本模块

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

前言

    对于大牛来说,写关于阅读源码的文章都会叫源码剖析或者深入浅出啥的,对于我,自己阅读阅读源码,写一些自己的阅读笔记吧。

    SDL我就不多介绍了,很多使用过的人都说很好,我自己实际使用的感觉
SDL也是非常成熟易用,绝对对得起其
simple两字。

 

基本模块

通过SDL.h中看到SDL作者对SDL进行的划分,可以看出SDL大概包含的内容:

#include


"SDL_main.h"

#include


"SDL_stdinc.h"

#include


"SDL_audio.h"   //


声音

#include


"SDL_cdrom.h"

#include


"SDL_cpuinfo.h"

#include


"SDL_endian.h"

#include


"SDL_error.h"

#include


"SDL_events.h"

#include


"SDL_loadso.h"  // Unix/Linux


动态库

#include


"SDL_mutex.h"   //


多线程互斥量

#include


"SDL_rwops.h"

#include


"SDL_thread.h"  //


多线程

#include


"SDL_timer.h"   //


计时器

#include


"SDL_video.h"   // video


模块

#include


"SDL_version.h"

 

有声音模块,cdrom模块,有事件模块,多线程模块,计时器模块,video模块等6大模块。

 

SDL_InitSubSystem的参数,可以看出SDL的作者将SDL划分为下列可选子系统:

#define


SDL_INIT_TIMER
       0x00000001

#define


SDL_INIT_AUDIO
      0x00000010

#define


SDL_INIT_VIDEO
      0x00000020

#define


SDL_INIT_CDROM
      0x00000100

#define


SDL_INIT_JOYSTICK
   0x00000200

#define


SDL_INIT_NOPARACHUTE
    0x00100000 /**< Don't catch fatal signals */

#define


SDL_INIT_EVENTTHREAD
    0x01000000 /**< Not supported on all OS's */

#define


SDL_INIT_EVERYTHING
0x0000FFFF

 

这里的可选模块与包含的头文件并不是太一致。

 

除了声音我的确不是很了解以外,其他模块会在这里逐一来看看,因为个人爱好,将以video模块为主,当然,事实上这也应该是SDL中最重要的模块,虽然video模块的字面意义上是显示模块,但是实际上SDL主要的平台相关函数几乎都集中在了video模块中了。其中,对源码的最主要分析在于SDL是怎么使用C语言对各类接口进行抽象并实现跨平台的,并不会逐句逐句的理解,那样非常没有必要。当然,我不能理解所有SDL支持的平台,这里仅以我熟悉的3大平台为例,Win32,Unix/Linux,Macos,事实上这也是目前最为流行的平台,应该也算是最具代表性了。

 

 

计时器
(timer)

计时器和时间相关的东西是几乎每个有意义的游戏都会用到的,将其接口抽象出来并实现跨平台几乎是每个跨平台库的必要工作,相对来说还比较简单,这里以此作为第一篇,也算是热热身。

以下是SDL的timer.h头文件中有的计时器API:

extern


DECLSPEC
Uint32
SDLCALL
SDL_GetTicks
(void
);

extern


DECLSPEC
void
SDLCALL
SDL_Delay
(Uint32
ms
);

typedef


Uint32
(SDLCALL
*SDL_TimerCallback
)(Uint32
interval
);

 

extern


DECLSPEC
int
SDLCALL
SDL_SetTimer
(Uint32
interval
, SDL_TimerCallback
callback
);

typedef


Uint32
(SDLCALL
*SDL_NewTimerCallback
)(Uint32
interval
, void
*param
);

typedef


struct
_SDL_TimerID
*SDL_TimerID
;

extern


DECLSPEC
SDL_TimerID
SDLCALL
SDL_AddTimer
(Uint32
interval
, SDL_NewTimerCallback
callback
, void
*param
);

extern


DECLSPEC
SDL_bool
SDLCALL
SDL_RemoveTimer
(SDL_TimerID
t
);

 

个人感觉是没有什么值得学习的,叫100个人来设计计时器接口,基本99个与这个类似。无论是用过Unix/Linux还是用过Windows 计时器的人都可以对SDL的计时器API与相应平台的计时器接口对号入座。接口是简单,但是实现并不算简单,各个平台之间的计时器和时间函数还是有些差异。当然,作为C语言的跨平台库,首先要习惯的就是满篇满篇的宏了,这是C语言库跨平台的必要装备。

 

 

SDL_GetTicks




Win32主要由QueryPerformanceCounter或者timeGetTime实现。

Unix主要由clock_gettime或者更常见的gettimeofday实现。

MacOS下首先实现了一套FastTimer的函数,然后再调用这些函数实现,虽然我没有完全明白作者为什么不直接使用MacOS freeBSD相关的系统调用来实现,但是看了源代码感觉主要的问题在于现在MacOS都是64位的,所以作者需要进行一些处理。另外,还有PowerPC的MacOS部分,作者甚至使用了汇编来完成。

总的来说,很明显支持MacOS是最不容易的。。。。。假如作者原意在MacOS部分使用Objective C的代码的话,可能会好很多,但是估计作者是想尽量保持SDL简单的C库特征。

另外,此函数返回的并不是一般的从开机开始的计时,而是SDL自己计算的从SDL_StartTicks开始的计时。此函数在SDL的计时模块初始化的时候调用。

 

SDL_Delay

Win32下是最清晰简单的函数,sleep而已。

Unix下有nano sleep的话调用nanosleep自然是最简单的,没有的话调用select来进行等待。当年看《Unix网络编程》的时候,作者用select实现了一个定时器,想不到还真有这样用的。

Macos下完全自己计时进行循环。。。。。。。这个应该是效率最低的了,不明白为啥这样做。

 

 

SDL_SetTimer
SDL_AddTimer
SDL_RemoveTimer

我原以为每个平台都会有的函数

Win32完全靠计时线程加一个SDL自己的timerID列表来完成,不明白为什么不用Win32自己的Timer来完成。

Unix下用setitimer开始,也用setitimer结束。

MacOS下用InsXTime,PrimeTime开始,然后用RmvTime停止。

 

 

多线程模块

extern


DECLSPEC
SDL_Thread
* SDLCALL
SDL_CreateThread
(int
(SDLCALL
*fn
)(void
*), void
*data
);

#endif

extern


DECLSPEC
Uint32
SDLCALL
SDL_ThreadID
(void
);

extern


DECLSPEC
Uint32
SDLCALL
SDL_GetThreadID
(SDL_Thread
*thread
);

 

extern


DECLSPEC
void
SDLCALL
SDL_WaitThread
(SDL_Thread
*thread
, int
*status
);

extern


DECLSPEC
void
SDLCALL
SDL_KillThread
(SDL_Thread
*thread
);

 

 

多线程模块应该是最一致的模块了,Win32下分别上述接口在的一一对应的接口。

Unix,MacOS下使用pthread,接口也几乎一一对应,只是命名上有些差异,没有什么好说的。

这应该是最最简单的模块了。

 

事件模块

在事件模块中,SDL统一了所有的输入,将所有的输入最大的与上层逻辑解耦。

/** Event enumerations */

typedef


enum
{

       SDL_NOEVENT
= 0,         /**< Unused (do not remove) */

       SDL_ACTIVEEVENT
,         /**< Application loses/gains visibility */

       SDL_KEYDOWN
,         /**< Keys pressed */

       SDL_KEYUP
,        /**< Keys released */

       SDL_MOUSEMOTION
,         /**< Mouse moved */

       SDL_MOUSEBUTTONDOWN
,     /**< Mouse button pressed */

       SDL_MOUSEBUTTONUP
,       /**< Mouse button released */

       SDL_JOYAXISMOTION
,       /**< Joystick axis motion */

       SDL_JOYBALLMOTION
,       /**< Joystick trackball motion */

       SDL_JOYHATMOTION
,    /**< Joystick hat position change */

       SDL_JOYBUTTONDOWN
,       /**< Joystick button pressed */

       SDL_JOYBUTTONUP
,         /**< Joystick button released */

       SDL_QUIT
,         /**< User-requested quit */

       SDL_SYSWMEVENT
,          /**< System specific event */

       SDL_EVENT_RESERVEDA
,     /**< Reserved for future use.. */

       SDL_EVENT_RESERVEDB
,     /**< Reserved for future use.. */

       SDL_VIDEORESIZE
,         /**< User resized video mode */

       SDL_VIDEOEXPOSE
,         /**< Screen needs to be redrawn */

       SDL_EVENT_RESERVED2
,     /**< Reserved for future use.. */

       SDL_EVENT_RESERVED3
,     /**< Reserved for future use.. */

       SDL_EVENT_RESERVED4
,     /**< Reserved for future use.. */

       SDL_EVENT_RESERVED5
,     /**< Reserved for future use.. */

       SDL_EVENT_RESERVED6
,     /**< Reserved for future use.. */

       SDL_EVENT_RESERVED7
,     /**< Reserved for future use.. */

       /** Events SDL_USEREVENT through SDL_MAXEVENTS-1 are for your use */

       SDL_USEREVENT
= 24,

       /** This last event is only for bounding internal arrays

    *  It is the number of bits in the event mask datatype -- Uint32

        */

       SDL_NUMEVENTS
= 32

} SDL_EventType
;

 

 

通过这个枚举,可以看到,除了输入,还包括一些系统事件,比如SDL_VIDEORESIZE和SDL_QUIT。

虽然说大部分以C语言实现的事件系统都实现不了类型安全以及订阅-发布的模式,所以都避免不了需要通过事件类型来决定事件的输入,并且进行强转的操作,然后形成一个很长的switch case,

但是SDL中的事件系统还算实现的不错了。

事件的参数是一个这样的联合结构:

/** General event structure */

typedef


union
SDL_Event
{

    Uint8
type
;

    SDL_ActiveEvent
active
;

    SDL_KeyboardEvent
key
;

    SDL_MouseMotionEvent
motion
;

    SDL_MouseButtonEvent
button
;

    SDL_JoyAxisEvent
jaxis
;

    SDL_JoyBallEvent
jball
;

    SDL_JoyHatEvent
jhat
;

    SDL_JoyButtonEvent
jbutton
;

    SDL_ResizeEvent
resize
;

    SDL_ExposeEvent
expose
;

    SDL_QuitEvent
quit
;

    SDL_UserEvent
user
;

    SDL_SysWMEvent
syswm
;

} SDL_Event
;

 

所以可以不进行强转,在相应的事件中,直接通过指定对应的成员变量来获取信息。虽然并不是完全类型安全,但是起码减少了强转的步骤。

事件模块有的API如下:

 

extern


DECLSPEC
void
SDLCALL
SDL_PumpEvents
(void
);

typedef


enum
{

    SDL_ADDEVENT
,

    SDL_PEEKEVENT
,

    SDL_GETEVENT

} SDL_eventaction
;

extern


DECLSPEC
int
SDLCALL
SDL_PeepEvents
(SDL_Event
*events
, int
numevents
,

              SDL_eventaction
action
, Uint32
mask
);

extern


DECLSPEC
int
SDLCALL
SDL_PollEvent
(SDL_Event
*event
);

extern


DECLSPEC
int
SDLCALL
SDL_WaitEvent
(SDL_Event
*event
);

extern


DECLSPEC
int
SDLCALL
SDL_PushEvent
(SDL_Event
*event
);

typedef


int
(SDLCALL
*SDL_EventFilter
)(const
SDL_Event
*event
);

extern


DECLSPEC
void
SDLCALL
SDL_SetEventFilter
(SDL_EventFilter
filter
);

extern


DECLSPEC
SDL_EventFilter
SDLCALL
SDL_GetEventFilter
(void
);

#define


SDL_QUERY
-1

#define


SDL_IGNORE
   0

#define


SDL_DISABLE
  0

#define


SDL_ENABLE
   1

extern


DECLSPEC
Uint8
SDLCALL
SDL_EventState
(Uint8
type
, int
state
);

 

其中各个接口的含义还算比较好理解,与Win32的API中消息相关的接口类似,只是命名有差异而已。另外,事件模块本身的实现是与各个操作系统并不太相关的,属于依靠ANSI C跨平台的代码。

我用的最多的是SDL_PollEvent接口,比如在主循环中:

 

  while
(running
) {

    //While there's an event to handle

    SDL_Event
event
;

    while
( SDL_PollEvent
( &event
) ) {

      if
(event
.type
== SDL_QUIT
) {

        running
= false
;

      }

}

}


这样来轮询事件。

 

这个最常用的API是由其他API实现的:

 

int


SDL_PollEvent
(SDL_Event
*event
)

{

    SDL_PumpEvents
();

 

    /* We can't return -1, just return 0 (no event) on error */

    if
( SDL_PeepEvents
(event
, 1, SDL_GETEVENT
, SDL_ALLEVENTS
) <= 0 )

       return
0;

    return
1;

}

所以这里再看看SDL_PumpEvents()和SDL_PeepEvents:

SDL_PumpEvents主要就是调用显示模块的PumpEvent函数以获取上面那些与显示相关的事件,然后就是检测按键和摇杆了,其实很简单。

SDL_PeepEvents是事件模块中较为关键的函数,通过SDL_eventaction参数类型区别并完成

 

typedef


enum
{

    SDL_ADDEVENT
,

    SDL_PEEKEVENT
,

    SDL_GETEVENT

} SDL_eventaction
;


这三类操作。添加事件,查看事件和获取事件。

看完API,来看看事件模块的内部:在内部,通过上述函数,其实都是操作同一个全局的事件列表:

 

static


struct
{

    SDL_mutex
*lock
;

    int
active
;

    int
head
;

    int
tail
;

    SDL_Event
event
[MAXEVENTS
];

    int
wmmsg_next
;

    struct
SDL_SysWMmsg
wmmsg
[MAXEVENTS
];

} SDL_EventQ
;

并且,SDL通过自己的事件线程来维护事件系统的运作,在SDL_StartEventThread函数中启动了一个函数为SDL_GobbleEvents的事件线程。

此函数中是一个死循环(只要事件模块还是激活状态)以下是有删节的主要内容:


    while
( SDL_EventQ
.active
) {

       SDL_VideoDevice
*video
= current_video
;

       SDL_VideoDevice
*this
  = current_video
;

 

       /* Get events from the video subsystem */

       if
( video
) {

           video
->PumpEvents
(this
);

       }

 

       /* Queue pending key-repeat events */

       SDL_CheckKeyRepeat
();

 

#if


!SDL_JOYSTICK_DISABLED

       /* Check for joystick state change */

       if
( SDL_numjoysticks
&& (SDL_eventstate
& SDL_JOYEVENTMASK
) ) {

           SDL_JoystickUpdate
();

       }

#endif

       if
( SDL_timer_running
) {

           SDL_ThreadedTimerCheck
();

       }

       SDL_Delay
(1);

    }


 

可以看到主要是调用显示模块的PumpEvents来获取显示系统相关的事件,其次就是重复按键的事件检测和摇杆了。另外,可以看到前面那个全局事件列表是带互斥量的,事实上,整个SDL的事件系统的实现都是线程安全的。
SDL_SetEventFilter等事件过滤的函数,实现非常简单,就是根据设置的回调函数的结果来决定事件是否添加到事件列表,就不多说了。

事实上,虽然各类输入最后都统一归结到SDL的事件模块中,然后提供给用户或者SDL本身进行处理,但是在SDL中将这些输入检测本身都放在了video模块中。所以这里暂时不讲它们的实现了。

 

小结:

     以一般看源码一边记录下来的方式完成本文,基本上将SDL除Video以外的一些周边模块都看了一遍,除了事件模块,这些模块的编写都没有太多值得深入了解的东西,无非就是抽象出一个大概的API,然后在不同的平台上实现,相当于SDL将很多平台相关的dirty的工作都做了,剩下clean的跨平台接口供用户使用。事件模块本身就不是平台相关的,SDL的实现相对来说使用还是比较方便的。利用联合来减少强转虽然不是第一次见,但是还是比更常见的void*表示event data+强转更加方便。看得出SDL的用心设计。

    因为这些周边模块主要抽象一些接口,然后实现,没有太多亮点,所以看的时候有的时候都不知道该写什么,所以按照自己看源码的流程记录了一些琐碎的东西,全文比较混乱。

    准备下篇文章中讲述SDL最关键的video模块,相对来说,就比这些周边模块有意思的多。

 

 

 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....

编程世界中惯性的力量

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

    今天下载lua的Windows二进制发布luabinaries
的时候,发现luabinaries的发布包含两个dll,lua5.1.dll,lua51.dll,其中lua5.1.dll大小合适,lua51.dll只有11K,感觉不像是个有用的lua dll,对此比较不解,查看了luabinaries的文档,

文档中如此解释:

The LuaBinaries DLL packages have a dll proxy called "lua51.dll". It can be used to replace other "lua51.dll" released by other distributions. It will simply forward calls to the "lua5.1.dll". There is no compiled source code involved in the forwarding.

也就是说,lua51.dll完全是一个lua5.1.dll的代理类,没有任何实际的代码,仅仅是做一个到lua5.1.dll的forwarding。

    不过文档解释了lua51.dll是什么,却没有解释为啥会需要一个这样的东西呢?我还从来没有见过类似的情况。要说有一点点类似的情况的话,也是以前做反外挂的时候,知道可以通过替换现有的dll,并且完全模拟原来dll的接口,并将不需要hack的函数全部forwarding到原来的dll中。对于lua怎么会需要这样的功能呢?

于是我google了一下,发现了原因:
The standard DLL name "lua51.dll" has been selected more than

three years ago. Around twice every year someone comes along and

thinks "Oh, we absolutely need a dot in the DLL version number".

Alas, Windows does not like an extra dot in there. Many things

break when you have an extra dot in DLL names. Depends on the

version of the OS, on the specific system call, on the library or

tool used ... it's hopeless. So please let's forget about it.

--Mike

来自lua-users.org的一个帖子

原来是3年前有人确定了一个lua51.dll名字的动态库,并且,有人觉得我们非常需要在5和1之间加一个点,不然lua的5.1版岂不是看成lua的51版了?

对此,有人进一步提出了疑问
,认为这个问题怎么这么久了竟然没有人修复?

接着有人回答了:
But who's duty is to resolve the issue?


It's clearly not an issue of Lua as a language. It's just a consequence of (a very popular) LuaBinaries once releasing lua5.1.dll that became a de facto binary standard, then authors of many third-party Lua libraries were releasing binary packages compatible with LuaBinaries.

Note: I'm not blaming LuaBinaries; that was just one unfortunate decision that is difficult to be undone. --

Shmuel


我们知道了:

LuaBinaries做出了一个错误的决定,但是已经发布了,很多第3方的库也发布了,并且依赖于LuaBinaries的这个lua51.dll,于是:
that was just one unfortunate decision that is difficult to be undone.

那仅仅是一个过去做下,现在难以撤销的不幸决定。。。。。。。。。。。。


    有的东西存在了,即使是不合理的存在,因为它存在了一段时间了,因为惯性,它还会存在在那里。编程中,这种情况经常出现。突然让我想起上个项目中,大家经常对项目中蹩脚代码存在原因的解释:“历史原因”。

    对于代码来说,即使大家都知道可以重构,但是重构是有代价的,很多时候大家就妥协在历史原因当中。

    对于语言来说,C++就是对历史进行最大妥协而产生的语言,大家都承认,假如当年C++不兼容C的话,C++根本就得不到现在这样的流行程度,也都承认,因为C++兼容C,(常常被称为历史的包袱)C++在语言的优美程度上损失了太多。

    想起国内某个大牛有过类似的感慨,“现在每做一个设计决定的时候都非常小心,因为那可能会被使用非常非常长的时间,当它还能正常工作的时候,甚至不会有人想要去重写它“

    呵呵,仅仅将这个有趣的事件作为编程中的一个轶事来看吧。

 

 

 

 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....

数据/配置 的存储方式 Lua篇

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

前言

    在《数据/配置 的存储方式 Json篇 以JsonCpp库使用为例
》《数据/配置 的存储方式 Json篇 以Cocos2D For Iphone+TouchJson为例
》中我总结了一下怎么使用Json作为配置,但是,其实Json虽然语法格式简单,同时解析库的使用也非常简单,但是,因为这种简单性,缺少有的时候的确非常需要的功能。比如配置文件中对另一行配置的引用,比如配置段的继承等,这些可以使用XML来解决,另外,有的时候还会需要在配置文件中进行简单的运算,比如我就常常会有想将某个图片缩放到目前分辨率的几分之几这样的需求,此时,用一个完整的可以进行运算的语言来做配置的诱惑实在太大了。。。。。。
    Lua就是为此而生的。。。。。。虽然现在Lua的功能已经远远不止这样了,但是最初设计Lua的目的就是一个简单的配置语言。见Lua的历史
。虽然其实用Python作为配置也完全可行,但是Lua有着速度快的优点,而且,Lua实在比Python小太多了,很适合嵌入到程序中去。

对于使用Lua作为配置,事实上就相当于在C++中嵌入Lua,只不过不用其脚本语言的一些复杂特性,仅仅将其作为一个配置文件来看。

这里还是以原来《数据/配置 的存储方式 Json篇 以JsonCpp库使用为例
》文中开始的SDL工程作为例子。

首先搭建可以内嵌使用Lua的C++编译环境,下载一个luabinaries
是简单的办法,现在我使用的是lua5_1_4_Win32_dll8_lib.zip
。这里我甚至不需要一个可以运行的Lua交互环境。。。。。

简单示例

首先建立一个简单的Lua文件,命名为picture.lua
name="dragon.png"

rotation=180



虽然只有简单的两行,也是个合法的Lua程序。

然后在C++程序中读取出来,这个过程实际上是使用了Lua的C API,具体的Lua的C API这里就不详细讲解了,那比较复杂,可以参考《Programming in Lua》,中文版也已经有了。这里仅仅作为一个例子提供参考吧。
现在可以开始完成C++的程序了:
首先,包含必要的头文件:
extern "C" {
#include "lua/lua.h"
#include "lua/lauxlib.h"
#include "lua/lualib.h"
}
因为lua的头文件完全是只考虑C语言的情况,所以在C++中使用的时候需要自己添加extern "C"。这是典型的对C++不屑的态度,因为现在大部分的纯C语言写的库,都会很自然的通过宏判断来自动使用extern "C"语句。

然后:
using
 namespace
 std;
struct
  PictureInfo {
  string name;
  float
 rotation;
}gPictureInfo;

void
  PictureInit() {
  lua_State *L = luaL_newstate();
  if
( luaL_dofile(L, "picture.lua"
) != 0
) {
    printf("Error happen."
);
    // handle the error.

    exit(1
);
  }

  lua_getglobal(L, "name"
);
  assert(lua_isstring(L, -1
) == 1
);

  gPictureInfo.name = lua_tostring(L, -1
);

  lua_getglobal(L, "rotation"
);
  gPictureInfo.rotation = (float
)lua_tonumber(L, -1
);

  lua_close(L);
}

这里我们使用的所有变量全部都是global的变量,所以调用起来非常方便。上面的主要内容就是对Lua C API的使用,由于其本身较为复杂,这里一下也讲不清楚。
基本流程是创建新的Lua state,然后用luaL_dofile
执行我们需要的配置文件,然后调用lua_getglobal
加适当的转换来获取我们需要的配置值。这里的例子是一个字符串和一个浮点数。
具体显示的效果和其他代码参考《数据/配置 的存储方式 Json篇 以JsonCpp库使用为例
》就好了,这里不重复描述了。

数组

    在Lua中数组也通过table的形式来表示,这里我仅仅介绍怎么从lua文件中获取配置,具体的显示等东西也不再累述了。
    这里,我们建立一个包含数组的lua文件作为配置:

data = {

    {
 name="dragon.png"
, rotation=180
 }
,
    {
 name="dragon.png"
, rotation=0
 }

}

下面看怎么将配置读取出来:

using
 namespace
 std;
struct
  PictureInfo {
  string name;
  float
 rotation;
};

vector gPictureInfoVec;

void
  PictureInit() {
  lua_State *L = luaL_newstate();
  if
( luaL_dofile(L, "picture.lua"
) != 0
) {
    printf("Error happen."
);
    // handle the error.

    exit(1
);
  }

  // get the table

  lua_getglobal(L, "data"
);
  assert(lua_istable(L, -1
) == 1
);

  /*
 table is in the stack at index 't'
*/

  lua_pushnil(L);  /*
 first key
*/

  while
 (lua_next(L, -2
) != 0
) {
    PictureInfo info;
    /*
'key' (at index -2) and 'value' (at index -1)
*/

    // push the key to stack for getting the value

    lua_pushstring(L, "name"
);

    // now the table is in the -2 and key in the top(-1)

    lua_gettable(L, -2
);
    assert(lua_isstring(L, -1
));

    info.name = lua_tostring(L, -1
);

    lua_pop(L, 1
);

    // push the key to stack for getting the value

    lua_pushstring(L, "rotation"
);

    // now the table is in the -2 and key in the top(-1)

    lua_gettable(L, -2
);
    assert(lua_isnumber(L, -1
));

    info.rotation = lua_tonumber(L, -1
);

    gPictureInfoVec.push_back(info);
    /*
 removes the key we pushed and the 'value' of the global table ;   keeps 'key' for next iteration
*/

    lua_pop(L, 2
);
  }

  lua_close(L);
}


代码中的注释解释的已经够详细了,但是因为Lua 的API的确不是太容易理解,这里也无法一下讲清楚,所以还是先了解Lua 的 API为好,具体的API的意义可以参考参考手册


上面仅仅使用了Lua API遍历数组以及从table中获取元素的方法。
假如仅仅只有上面这些,是看不出用lua作为配置的好处的,用lua做配置的好处在于可以利用lua的特性实现配置段之间的继承以及完整的运算功能。
比如说,完全不用修改上面的读取配置的代码,我们仅仅修改配置,看看在Lua中使用上述功能:

data1 = {
 name="dragon.png"
, rotation=180
 }

data2 = {
 name = data1.name, rotation = data1.rotation / 2
}

data = {

    data1, data2
}

此时,data2的数据完全依赖于data1的数据,当有任何东西需要修改的时候,你只需要修改一个地方,对于配置来说,don't repeat yourself也是很有意义的。最最重要的的是,在配置中能够进行计算那是非常强大,即使完全不用lua的函数功能与代码的交互,仅仅通过lua的计算,也可以完成游戏中所有sprite的布局。。。。。。

小结

    相对于使用XML,Json,使用Lua作为配置绝对是最最强大的。。。。。。作为一个完整的语言,它拥有你想在配置中实现的一切功能。但是缺点也是很明显的:
    首先,速度上,Lua需要解释运行,可能明显跟不上XML或者Json的解析速度。但是,配置的解析读取可以都放在初始化阶段,所以配置不是太多的时候,也不算太过难以接受,另外,还可以将lua的配置完全作为开发期的一种机制使用,在发布后完全转化为2进制数据。
    其次,Lua的配置读取需要手动调用Lua的API来完成,相对于XML,Json那种有很方便的库的情况,使用上还是麻烦一些,特别是Lua的API的使用并不是那么简单易懂,不算太直观。但是,这也不是不可以克服的,完全可以自己写个小型的库将Lua的API封装起来,形成一个类似JsonCpp那样的库,用Map来表示一切。这个可能需要限制一些Lua语法的时候,或者在解析的时候进行取舍,比如函数啥的可能需要过滤掉,不然那就不太像将Lua作为配置使用了。
    还有,Lua的配置的生成更加是没有XML,Json那样有完善库支持来的方便,这点暂时没有办法克服,写一个自动生成Lua配置文件的库感觉并不是太过容易的事情。而且,自动生成时,lua的优势其实并没有余地发挥出来,那样的话,还不如使用Json。
    总的来说,假如是没有工具,需要大量手写配置的话,对Lua的API进行一层简单的封装,然后使用Lua来做配置,那还是件很爽的事情,需要手写的时候,你才会感觉到一个完整语言作为配置给你带来的好处。

 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....

数据/配置 的存储方式 Json篇 以JsonCpp库使用为例

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

配置的好处

    我不知道我需不需要用一节内容来向大家讲解这个公认的事实。现在公认的事实实际上比单纯所谓的配置还要走的远,一般的看法,游戏中最好的用法是数据驱动而不是代码驱动。那样才能满足游戏需要灵活改变的特点,并且易于编辑。在开发期可以使用文本配置,以易于调试,XML是大多时候的选择。发布时通过资源处理,将文本配置转换为二进制配置以加快读取速度,减少解析时间。(假如效率可以接受的话,这一步甚至可以省略)配置对于C++这样编译时间超长的语言更加是非常必要(偏偏游戏编程大部分是用C++),当某个属性需要修改的时候只需要改动配置而不是文件,可以极大的减少开发期在编译中消耗的时间,特别是原来的宏定义的方式,假如很多地方需要而被放在了头文件中,每次的更改对于大型工程来说简直就是程序员休息上网的好时间。。。。。。。

JSon介绍

JSON
(JavaScript Object Notation) 是一种轻量级的数据交换格式。 易于人阅读和编写。同时也易于机器解析和生成。 它基于JavaScript Programming Language
, Standard ECMA-262 3rd Edition - December 1999
的一个子集。 JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C, C++, C#, Java, JavaScript, Perl, Python等)。 这些特性使JSON成为理想的数据交换语言。

JSON建构于两种结构:

  • “名称/值”对的集合(A collection of name/value pairs)。不同的语言中,它被理解为对象 (object)
    ,纪录(record),结构(struct),字典(dictionary),哈希表(hash table),有键列表(keyed list),或者关联数组 (associative array)。
  • 值的有序列表(An ordered list of values)。在大部分语言中,它被理解为数组(array)。

这些都是常见的数据结构。事实上大部分现代计算机语言都以某种形式支持它们。这使得一种数据格式在同样基于这些结构的编程语言之间交换成为可能。

JSON具有以下这些形式:

对象是一个无序的“‘名称/值’对”集合。一个对象以“{”(左括号)开始,“}”(右括号)结束。每个“名称”后跟一个“:”(冒号);“‘名称 /值’ 对”之间使用“,”(逗号)分隔。

数组是值(value)的有序集合。一个数组以“[”(左中括号)开始,“]”(右中括号)结束。值之间使用“,”(逗号)分隔。

值(value
)可以是双引号括起来的字符串(string
)、数值(number)、true
false
null
、对象(object)或者数组(array)。这些结构可以嵌套。

字符串(string
)是由双引号包围的任意数量Unicode字符的集合,使用反斜线转义。一个字符(character)即 一个单独的字符串(character string)。

字符串(string
)与C或者Java的字符串非常相似。

数值(number
)也与C或者Java的数值非常相似。除去未曾使用的八进制与十六进制格式。除去一些编码细节。

空白可以加入到任何符号之间。 以下描述了完整的语言。

介绍部分完全完全copy自Json的中文网页(http://www.json.org/json-zh.html)。

下面才是我自己的内容:

为什么要使用Json

    Json的格式更加简单,同样的内容,用Json来表示比用XML表示时字符少的多,没有一大堆的标记及尖括号,决定了Json更加容易手写。同样的,字符少,信息量大,所以我认为也更加易读。而且,不常进行网页开发的我,非常讨厌尖括号!我不想去写那些重复的尖括号标记。。。。。。。。。。让人手写太多重复的东西简直就是犯罪。

    在使用Json以前其实我都不是太喜欢配置,因为使用太麻烦,写起来浪费时间,虽然说配置可以节省编译时间,但是有那功夫写读写配置的代码,都编译N次了。特别是以前用Excel或者SQL的时候,读写数据/配置,都是非常非常麻烦的事情。此时我想起一句LUA之父说的话,只有当配置的使用足够简单灵活,人们才会使用它。

Json的格式到底有多简单,其实上面的介绍很多其实归根于两句话:

  • “名称/值”对的集合(A collection of name/value pairs)。
  • 值的有序列表(An ordered list of values)。

仅此而已。

一种就是

{x : y}

形式的名称/值对,说成key-value对可能更多人更好理解。

一种就是

{x : [1,2,3,4] }

或者 [1,2,3,4]

形式的有序列表。也就是数组。第二个例子说明数组也可以作为根对象。

另加上明白一个{}构成一个json对象,然后,因为上面的3种形式,可以任意嵌套,也就可以满足任意复杂的需求了。

缺点:

    简单有简单的优势,但是也有简单带来的缺点,那就是缺乏高级特性,比如继承,引用这些很好用的特性在Json中就都没有,相对应的,XML中有类似的概念。另外,作为手工编写的配置,常常会需要用到运算,特别是引用+运算,当然,这些XML都很难做到,那得指望Lua了。事实上,我的确准备在以后尝试使用Lua做配置,因为以一个完整的语言来做配置的吸引力实在是太大了。。。。。。。。。。。另外,Json最后不支持可有可无的逗号(如C++ enum的语法格式那样),有的就必须有,没有的地方坚决不能没有,这个导致很容易出现问题,(主要是手写Json的时候),需要特别注意,一般可以通过工具验证一下自己的Json文件。比如Jslint
,jsonlint
.我个人常用jslint,报错非常详细,甚至有对不良语法的警告

Json有多流行?

看看有多少语言的解析库
,有多少语言有多个解析库就能一窥一二。对于JS来说,使用多方便就不用说了,像php,python事实上都已经原生支持Json的解析了。(通过函数或者标准库)

实例说明

    得益于简单的格式,所以解析及创建都可以做的很简单的,特别是JSONCPP这个库,在使用的时候简直就像获取到JS中的Object一样,直接通过 [] 操作完成索引及建立操作,使用起来非常简单。这里以JsonCPP + SDL + OpenGL 为例,来记录一下Json的使用。之所以选用SDL ,仅仅是因为我了解并知道怎么它们,作为了解Json来说,下面的例子中,假如对SDL不熟悉的话,知道那些大量冗余的部分都是用来实际绘图和控制程序流程的,只关心与Json相关的部分即可。本例实现的功能与图像有关,需要用图形想关的东西属于迫不得已。至于为啥举与图像有关的例子,仅仅因为那样更酷^^难道我以读取配置然后正常printf读取的配置为例子吗?

JsonCPP + SDL + OpenGL

JsonCPP
介绍

JsonCpp是我本人非常喜欢的一个Json解析库,有读写模块,实现具有很强的移植性,当时在公司项目中嵌入JsonCpp时,仅仅通过修改了一个读文件的接口就完美的集成了进去,无论是在Windows平台还是在IPhone平台上都运行良好,当时我还当心在IPhone上会碰到什么问题,但是结果是没有碰到任何问题。。。。。。。。。对此,我印象非常深刻。JsonCpp中带有完整的测试套件,对于这样一个底层库,有测试套件存在,让人使用的时候心里放心很多。另外,JsonCpp使用C++的Map特性,(得益于Json语法的简单)可以非常方便的查找需要的任何Json数据,并且效率不低,当然,因为JsonCpp使用了一种DOM方式解析Json文档(参考XML的描述),一次将全部文档都解析了以后,然后再查询使用,使用虽然方便,但是效率上还是逊于SAX方式,但是,对于小规模应用,这应该不是问题。jsoncpp的文件比较少,其实全部拷贝进自己的工程都完全没有问题。jsoncpp的协议是公有领域,也就是说作者完全放弃了版权,你可以随便使用,连版权协议都不用带。需要include的头文件就是json.h,但是需要将include/json下的头文件都拷贝到需要的地方。例子中我为了方便还是用了静态库,将json附带的VS工程的Runtime library改成Multi-threaded Debug DLL (/MDd),(个人习惯使用dll方式进行开发,这个需要和正在进行的工程一致)编译后,生成静态库。

这里我以某些参数显示某个图片为例,介绍json/jsoncpp的用法。

首先,原来的例子:

#include
<stdlib.h>

#include
<stdio.h>

#include
<tchar.h>

#include
"SDL.h"

#include
"SDL_opengl.h"

#include
"SDL_image.h"

#define WINDOW_WIDTH
300

#define WINDOW_HEIGHT
300

GLuint gTexName;
//OpenGL初始化开始

void
 SceneInit(int
  w,int
  h)

{

  gluOrtho2D(-1.0
, 1.0
, -1.0
, 1.0
);

  glShadeModel(GL_FLAT);

  SDL_Surface *surface = IMG_Load("dragon.png"
);

  if
 (!surface)

  {

    printf("Load the picture failed."
);

    exit(1
);

  }

  GLenum texture_format;

  // get the number of channels in the SDL surface

  GLint nOfColors = surface->format->BytesPerPixel;

  if
 (nOfColors == 4
)     // contains an alpha channel

  {

    if
 (surface->format->Rmask == 0x000000ff
)

      texture_format = GL_RGBA;

    else

      texture_format = GL_BGRA;

  } else
 if
 (nOfColors == 3
)     // no alpha channel

  {

    if
 (surface->format->Rmask == 0x000000ff
)

      texture_format = GL_RGB;

    else

      texture_format = GL_BGR;

  } else
 {

    printf("warning: the image is not truecolor..  this will probably break
/n
"
);

    exit(1
);

  }

  glGenTextures(1
  , &gTexName);

  glBindTexture(GL_TEXTURE_2D, gTexName);

  // Specify filtering and edge actions

  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);

  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

  glTexImage2D( GL_TEXTURE_2D, 0
, nOfColors, surface->w, surface->h, 0
,

    texture_format, GL_UNSIGNED_BYTE, surface->pixels );

  glEnable(GL_DEPTH_TEST);

  glEnable( GL_TEXTURE_2D );

  //Free the loaded image

  SDL_FreeSurface( surface );

}

// display

void
  SceneShow(GLvoid) {

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  glBindTexture(GL_TEXTURE_2D, gTexName);

  glBegin(GL_QUADS);

  glTexCoord2f(0.0
 , 1.0
 ); glVertex3f(-1.0
 , -1.0
 , 0.0
 );

  glTexCoord2f(1.0
 , 1.0
 ); glVertex3f(1.0
 , -1.0
 , 0.0
 );

  glTexCoord2f(1.0
 , 0.0
 ); glVertex3f(1.0
 , 1.0
 , 0.0
 );

  glTexCoord2f(0.0
 , 0.0
 ); glVertex3f(-1.0
 , 1.0
 , 0.0
 );

  glEnd();

}  

int
 _tmain(int
 argc, _TCHAR* argv[])

{

  if
 ( SDL_Init(SDL_INIT_VIDEO) < 0
 )

  {

    printf("Unable to initialize SDL:
%s
/n
"
, SDL_GetError());

    exit(1
);

  }

  atexit(SDL_Quit);

  if
  (IMG_Init(IMG_INIT_PNG) == 0
  ) {

    printf("Unable to initialize SDL_image"
 );

    exit(1
);

  }

  // use these two lines instead of the commented one

  SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1
 ); // *new*

  SDL_Surface* screen = SDL_SetVideoMode( WINDOW_WIDTH, WINDOW_HEIGHT, 16
, SDL_OPENGL); // *changed*

  SceneInit(WINDOW_WIDTH, WINDOW_HEIGHT);

  // main loop

  bool
 running = true
;

  while
 (running) {

    //While there's an event to handle

    SDL_Event event;

    while
( SDL_PollEvent( &event ) ) {

      if
 (event.type == SDL_QUIT) {

        running = false
;

      }

    }

    SceneShow();

    //Update Screen

    SDL_GL_SwapBuffers();

    // delay, 50 for simple

    SDL_Delay( 50
 );

  }

    return
 1
;

}

代码主要就是SDL+OpenGL,很浅显易懂,可以参考原来的文章。《GLFW 简单入门学习
》,《SDL 简单入门学习
》,《在SDL 中使用OpenGL
》。

显示如下的图片:(为了减少截图提及,上面我特意将窗口创建成300*300这样比较小的体积了)


 

这是原图,现在我希望将其显示加入一定的属性配置,以达到我的显示要求, 这里将配置放到Json中完成。

构建一段最简单的Json文件:

{

    "name"
 : "dragon.png"
,

    "rotation"
 : 180
}



里面只有图片名字和旋转度数。顺便以此例解释下Json的语法,前面提到过Json的格式总的来说就是Key : value.如上所示,所有的key都是字符串,value可以是各种值,包括整数,boolean,字符串,数组,甚至是一个{ }表示的object。上例中,name表示图片的名字,rotation表示图片旋转的度数,这里的度数按照OpenGL的规范,以逆时针为正。

JsonCpp的使用就非常简单了。比如我用下列代码来解析上述Json文件:

struct
 PictureInfo {

  string name;

  float
rotation;

}gPictureInfo;

void
 PictureInit()

{

  Json::Reader reader;

  ifstream file("picture.json"
);

  assert(file.is_open());

  Json::Value root;

  if
 (!reader.parse(file, root, false
)) {

    printf("Parse error"
);

    exit(1
);

  }

  gPictureInfo.name = root["name"
].asString();

  gPictureInfo.rotation = root["rotation"
].asDouble();

}

虽然已经如此简单,但还是解释一下:

reader是用于parse Json文件的jsoncpp类,传入打开的ifstream文件对象即可完成parse。Json::Value是一个包罗万象的类,可以存储一个Json 对象,如上所示,整个Json文件就是一个Json对象,(以{}表示)在JsonCpp中表示为root,在parse函数中传入,打开的文件,根Value,parse后,root就包含了解析后的Json文件信息。对value的检索都是使用类似于C++中map的方式,直接以字符串为key索引,索引返回的还是一个Json::Value的对象,同样也可以有包罗万象的内容。(与Json文件格式本身对应)从Json::Value到C++的静态类型的转换在JsonCpp中通过Json::Value对象的asXXX函数来完成。上例中,asStrint表示返回一个C++的String对象,asDouble表示返回一个浮点数,以此类推。

通过上述方式,获得了图片名字,图片的rotation。以此来完成新的图片的显示。

将SceneShow部分改成如下代码:

// display

void
  SceneShow(GLvoid) {

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  glBindTexture(GL_TEXTURE_2D, gTexName);

  glMatrixMode(GL_MODELVIEW);

  glPushMatrix();

  glRotatef(gPictureInfo.rotation, 0.0
, 0.0
, 1.0
);

  glBegin(GL_QUADS);

  glTexCoord2f(0.0
 , 1.0
 ); glVertex3f(-1.0
 , -1.0
 , 0.0
 );

  glTexCoord2f(1.0
 , 1.0
 ); glVertex3f(1.0
 , -1.0
 , 0.0
 );

  glTexCoord2f(1.0
 , 0.0
 ); glVertex3f(1.0
 , 1.0
 , 0.0
 );

  glTexCoord2f(0.0
 , 0.0
 ); glVertex3f(-1.0
 , 1.0
 , 0.0
 );

  glEnd();

  glPopMatrix();

}  

添加了 

glPushMatrix();

glRotatef(gPictureInfo.rotation, 0.0
, 0.0
, 1.0
);

glPopMatrix();

3句来进行模型矩阵变换,来完成旋转。

因为上面是180度,所以显示效果如下:


在游戏编程古老的岁月中,大家都全靠图片来堆积游戏,仅仅是一个这样的旋转,都不会使用。下图是某游戏同一个武器不同方向的图片。事实上,完全没有必要。

即使在今天,人物的4向行走用4组图片也不是什么稀罕事。。。。虽然其实是那么的没有必要。

这里再添加一些其他的要素,比如scale,position。(这里没有使用一般2D引擎中用的屏幕坐标,还是用OpenGL坐标)

Json配置:

{

    "name"
 : "dragon.png"
,

    "rotation"
 : 0,

    "positionX"
 : -0.5,

    "positionY"
 : 0.0,

    "scaleX"
 : 0.3,

    "scaleY"
 : 1.0
}



代码改动部分:

struct
 PictureInfo {

  string name;

  float
 rotation;

  float
 positionX;

  float
 positionY;

  float
 scaleX;

  float
 scaleY;

  // can't read from config,read from surface

  int
 width;

  int
 height;

}gPictureInfo;

void
 ReadPictureInfo() {

  Json::Reader reader;

  ifstream file("picture.json"
);

  assert(file.is_open());

  Json::Value root;

  if
 (!reader.parse(file, root, false
)) {

    printf("Parse error"
);

    exit(1
);

  }

  gPictureInfo.name = root["name"
].asString();

  gPictureInfo.rotation = (float
)root["rotation"
].asDouble();

  gPictureInfo.positionX = (float
)root["positionX"
].asDouble();

  gPictureInfo.positionY = (float
)root["positionY"
].asDouble();

  gPictureInfo.scaleX = (float
)root["scaleX"
].asDouble();

  gPictureInfo.scaleY = (float
)root["scaleY"
].asDouble();

}

void
 PictureInit() {

  ReadPictureInfo();

}



// display

void
  SceneShow(GLvoid) {

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  glBindTexture(GL_TEXTURE_2D, gTexName);

  glPushMatrix();

  glMatrixMode(GL_MODELVIEW);

  glTranslatef(gPictureInfo.positionX, gPictureInfo.positionY, 0
);

  glScalef(gPictureInfo.scaleX, gPictureInfo.scaleY, 0.0
);

  glRotatef(gPictureInfo.rotation, 0
, 0
, 1.0
);

  glBegin(GL_QUADS);

  glTexCoord2f(0.0
 , 1.0
 ); glVertex3f(-1.0
 , -1.0
 , 0.0
 );

  glTexCoord2f(1.0
 , 1.0
 ); glVertex3f(1.0
 , -1.0
 , 0.0
 );

  glTexCoord2f(1.0
 , 0.0
 ); glVertex3f(1.0
 , 1.0
 , 0.0
 );

  glTexCoord2f(0.0
 , 0.0
 ); glVertex3f(-1.0
 , 1.0
 , 0.0
 );

  glEnd();

  glPopMatrix();

}  

添加了相应的读取代码而已,并在模型矩阵变换的时候添加了scale和rotate相关的代码,其实都没有什么新鲜的。


这里的乐趣在于,你可以不改变任何代码,通过配置实现很多改变了,虽然仅仅是这么一幅图。比如:

{

    "name"
 : "dragon.png"
,

    "rotation"
 : 0,

    "positionX"
 : 0.5,

    "positionY"
 : 0.0,

    "scaleX"
 : -0.3,

    "scaleY"
 : 1.0
}



会得到如下的图:


Json数组

    其实讲了上面的两个例子,我是想说明怎么样同时显示上述两幅图,都通过配置。这需要用到Json中的数组的概念,当然,懂一点编程的人对此都不会太陌生。配置如下:

[


{

    "name"
 : "dragon.png"
,

        "rotation"
 : 0,

        "positionX"
 : -0.5,

        "positionY"
 : 0.0,

        "scaleX"
 : 0.3,

        "scaleY"
 : 1.0
}

,
{

    "name"
 : "dragon.png"
,

    "rotation"
 : 0,

    "positionX"
 : 0.5,

    "positionY"
 : 0.0,

    "scaleX"
 : -0.3,

    "scaleY"
 : 1.0
}


]

同时,代码的改动可能有些大,因为需要在任意地方都将原来的全局对象改为全局vector.实际的新内容除了解析Json那一部分,倒是几乎完全没有。解析Json数组时,与用普通的字符串key来索引value的差别仅仅在于此时只需要用整数来索引即可。

如下:



using
 namespace
 std;
struct
 PictureInfo {

  string name;

  float
 rotation;

  float
 positionX;

  float
 positionY;

  float
 scaleX;

  float
 scaleY;

  // can't read from config,read from surface

  int
 width;

  int
 height;

  GLuint texName;

};

// didn't care about efficiency too much as a demo

vector<PictureInfo> gPictureInfoVec;

void
 ReadPictureInfo() {

  Json::Reader reader;

  ifstream file("picture.json"
);

  assert(file.is_open());

  Json::Value root;

  if
 (!reader.parse(file, root, false
)) {

    printf("Parse error"
);

    exit(1
);

  }

  assert(root.isArray());

  PictureInfo info;

  int
 size = root.size();

  for
 (int
 i = 0
; i < size; ++i) {

    Json::Value &current = root[i];

    info.name = current["name"
].asString();

    info.rotation = (float
)current["rotation"
].asDouble();

    info.positionX = (float
)current["positionX"
].asDouble();

    info.positionY = (float
)current["positionY"
].asDouble();

    info.scaleX = (float
)current["scaleX"
].asDouble();

    info.scaleY = (float
)current["scaleY"
].asDouble();

    gPictureInfoVec.push_back(info);

  }

}

上面这段for循环中的内容,root[i]的使用方式就是JsonCpp中索引Json数组的方式,很简单是吧?

于是,添加相应的显示代码后(其实也没有新内容,就是原来的东西变成数组)

//OpenGL初始化开始

void
 SceneInit(int
  w,int
  h)

{

  glClearColor (1.0f
 , 1.0f
 , 1.0f
 , 0.0
 );

  glViewport(0
, 0
, w, h);

  glShadeModel(GL_FLAT);

  for
 (vector<PictureInfo>::iterator it = gPictureInfoVec.begin();

    it != gPictureInfoVec.end();

    ++it) {

      PictureInfo& info = *it;

      SDL_Surface *surface = IMG_Load(info.name.c_str());

      if
 (!surface)

      {

        printf("Load the picture failed:
%s
"
,info.name.c_str());

        exit(1
);

      }

      info.width = surface->w;

      info.height = surface->h;

      GLenum texture_format;

      // get the number of channels in the SDL surface

      GLint nOfColors = surface->format->BytesPerPixel;

      if
 (nOfColors == 4
)     // contains an alpha channel

      {

        if
 (surface->format->Rmask == 0x000000ff
)

          texture_format = GL_RGBA;

        else

          texture_format = GL_BGRA;

      } else
 if
 (nOfColors == 3
)     // no alpha channel

      {

        if
 (surface->format->Rmask == 0x000000ff
)

          texture_format = GL_RGB;

        else

          texture_format = GL_BGR;

      } else
 {

        printf("warning: the image is not truecolor..  this will probably break
/n
"
);

        exit(1
);

      }

      glGenTextures(1
  , &info.texName);

      glBindTexture(GL_TEXTURE_2D, info.texName);

      glTexImage2D( GL_TEXTURE_2D, 0
, nOfColors, surface->w, surface->h, 0
,

        texture_format, GL_UNSIGNED_BYTE, surface->pixels );

      // Specify filtering and edge actions

      glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);

      glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

      //Free the loaded image

      SDL_FreeSurface( surface );

  }

  glEnable(GL_DEPTH_TEST);

  glEnable( GL_TEXTURE_2D );

}

// display

void
  SceneShow(GLvoid) {

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  for
 (vector<PictureInfo>::const_iterator it = gPictureInfoVec.begin();

    it != gPictureInfoVec.end();

    ++it) {

      const
 PictureInfo& info = *it;

      glBindTexture(GL_TEXTURE_2D, info.texName);

      glPushMatrix();

      glMatrixMode(GL_MODELVIEW);

      glTranslatef(info.positionX, info.positionY, 0
);

      glScalef(info.scaleX, info.scaleY, 0.0
);

      glRotatef(info.rotation, 0
, 0
, 1.0
);

      glBegin(GL_QUADS);

      glTexCoord2f(0.0
 , 1.0
 ); glVertex3f(-1.0
 , -1.0
 , 0.0
 );

      glTexCoord2f(1.0
 , 1.0
 ); glVertex3f(1.0
 , -1.0
 , 0.0
 );

      glTexCoord2f(1.0
 , 0.0
 ); glVertex3f(1.0
 , 1.0
 , 0.0
 );

      glTexCoord2f(0.0
 , 0.0
 ); glVertex3f(-1.0
 , 1.0
 , 0.0
 );

      glEnd();

      glPopMatrix();

  }

}  

可以看到一个很有意思的图^^当然,中间要是再加个球那就更好了。




因为此时已经有点数据驱动的意思了,需要添加新龙的时候改改配置就好了。

其实这个例子还可以进一步发挥,在每个数组值之间进行插值,实现类似flash的关键帧动画^^让上图中左边的龙渐变成右边的龙,其实本例已经与此相差不远了。。。。。。限于精力有限,暂时就不进一步发挥了。

 

 

 

 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....

数据/配置 的存储方式 Json篇 以Cocos2D For Iphone+TouchJson为例

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

前言

配置的好处,JSon介绍,及为什么要使用Json都参看原来JsonCpp篇
的内容。本文仅针对TouchJson稍微进行一些讲解。

 

Cocos2D For IPhone
+ TouchJson

    Cocos2D For IPhone是我见过的开源2D引擎中特性最完备的一个,即使速度上不算最快的。因为其只支持IPhone平台,所以能够在这个平台上做的很出众,现在新版的Cocos2D For IPhone已经支持iOS4和IPhone4,其工程模板的安装使用也是非常方便。新版甚至将原来的LGPL协议改为现在的MIT协议了,使用更加灵活自由。。。。
    TouchJson的使用属于不想在一个比较完全的Objective C环境中添加C++代码,(事实上使用Box2D的话还是避免不了)所以不使用JsonCpp来配合Cocos2D,何况Cocos2D的模板工程中本来就带有TouchJson了,将其删掉再插进JsonCpp也太不人道了。。。。呵呵。另外,因为TouchJson用Objective C完成,解析后也是个NSDictionary的对象所以与Objecitve C的对象组合使用会更加自然一些,使用苹果并为苹果开发,我是几乎已经习惯一整套都是用apple平台专有的东西了,唉。。。。对比当年简单的因为C#完全掌握在MS手中而不想学习,这也算是一种悲哀,因为Objective C比C#更加封闭,而且,起码C#还是这个世界上语法最漂亮,最先进语言的代表。

首先,利用Cocos2D的模板,创建一个新的工程,此时默认的效果是显示一个Hello World。如下:

这里,我们就不用其他图了,看看怎么配置这个Hello World。

最最基础的流程:
建立一个Json文件,仅仅有两行配置,一行表示显示的文字,一行表示文字的旋转
{


  "text"
  : "Don't Hello World"
,
   "rotation"
 : 20
}



然后将此Json文件放入工程的Resources目录,我这里命名为picture.json。

然后可以开始着手解析这个Json文件了。
整个解析过程又分几步,首先,#import "CJSONDeserializer.h"
然后,获取到编译打包后在Resources目录文件的位置:
NSString *path  = [[NSBundle mainBundle]pathForResource:@"picture" ofType:@"json"];

获取文件路径后,从文件中读取数据:
NSData *jsonData = [[NSFileManager defaultManager] contentsAtPath:path];

获取文件数据后,解析Json文件:
// Parse JSON results with TouchJSON.  It converts it into a dictionary.
CJSONDeserializer *jsonDeserializer = [CJSONDeserializer deserializer];
NSError *error = nil;
NSDictionary *jsonDict = [jsonDeserializer deserializeAsDictionary:jsonData error:&error];
if (error) {
//handle Error, didn't have here.
}


此时jsonDict保存的就是解析后的Json数据了。
(以上代码都添加在HelloWorldScene的init中)

下面来看使用:
首先,text改变HelloWorld显示的文字:
    NSString *text = [jsonDict valueForKey:@"text"];
        // create and initialize a Label
        CCLabel* label = [CCLabel labelWithString:text fontName:@"Marker Felt" fontSize:64];
这里就已经是cocoa中NSDictionary
怎么使用的问题了。

然后,rotation改变旋转:
    NSNumber *rotation = [jsonDict valueForKey:@"rotation"];
    NSAssert(rotation, @"Didn't have a key named rotation");
    label.rotation = [rotation floatValue];

一切就绪,看效果:

上面的流程已经基本完整了,作为补充,还是添加一个Json数组使用的例子。
在TouchJson中,作者不推荐将根对象设定为数组(参见这里
,TouchJson的作者自己说的),事实上也就不那么做就好了。我们随便用一个key来指定这个数组即可。
所以,定义Json文件如下:

{


  "result"
 :
  [


    {


      "text"
  : "Don't Hello World"
,

      "rotation"
 : 20

    }

,
    {


      "text"
  : "Just Hello World"
,
      "rotation"
 : -20
    }


  ]


}

然后,读取的时候还是先读取出一个NSDictionary对象,但是我们随后从中取出数组:
NSArray *dictArray = [jsonDict valueForKey:@"result"];

然后再遍历数组,此时数组中的每个对象又是NSDictionary对象

for (NSDictionary *dict in dictArray) {}

此时获取到NSDictionary的对象就与原来的字典对象很像了,直接通过valueForKey取对应的配置使用即可。较完整的循环代码如下:

    for (NSDictionary *dict in dictArray) {
      NSString *text = [dict valueForKey:@"text"];
      // create and initialize a Label
      CCLabel* label = [CCLabel labelWithString:text fontName:@"Marker Felt" fontSize:64];
      
      NSNumber *rotation = [dict valueForKey:@"rotation"];
      NSAssert(rotation, @"Didn't have a key named rotation");
      label.rotation = [rotation floatValue];
      
      // ask director the the window size
      CGSize size = [[CCDirector sharedDirector] winSize];
      
      // position the label on the center of the screen
      label.position =  ccp( size.width /2 , size.height/2 );
      
      // add the label as a child to this Layer
      [self addChild: label];
    }

此时可以看到同时显示多个文字的效果:

小结:
在使用了JsonCpp和TouchJson后,可以发现由于Json的数据结构主要就是一个Key:Value的映射加数组,所以无论在C++中还是在Objective C中,总是能用语言的原生结构很好的表示,(在C++中是map,在Objective C中是NSDictionary和NSArray)所以使用会非常方便,对比XML的强大并且复杂,简单的Json在保持概念非常简单的情况下完成了配置任务。

 

 

 

 

 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....

SDL与OpenGL配合使用时,OpenGL的纹理的UV坐标是上下颠倒的

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

同样的一段程序,在SDL with OpenGL时是颠倒的,而在GLFW和完全使用Windows API加OpenGL时又是正确的。
如下:
  glBegin(GL_QUADS);
  glTexCoord2f(0.0 , 0.0 ); glVertex3f(-1.0 , -1.0 , 0.0 );
  glTexCoord2f(1.0 , 0.0 ); glVertex3f(1.0 , -1.0 , 0.0 );
  glTexCoord2f(1.0 , 1.0 ); glVertex3f(1.0 , 1.0 , 0.0 );
  glTexCoord2f(0.0 , 1.0 ); glVertex3f(-1.0 , 1.0 , 0.0 );
  glEnd();

在默认情况下, 纹理的UV坐标的原点是是左下角,在SDL中默认定在了左上角,所以需要将上面的纹理的坐标Y轴倒过来,这样显示就没有问题了。就像下面这样:
  glBegin(GL_QUADS);
  glTexCoord2f(0.0 , 1.0 ); glVertex3f(-1.0 , -1.0 , 0.0 );
  glTexCoord2f(1.0 , 1.0 ); glVertex3f(1.0 , -1.0 , 0.0 );
  glTexCoord2f(1.0 , 0.0 ); glVertex3f(1.0 , 1.0 , 0.0 );
  glTexCoord2f(0.0 , 0.0 ); glVertex3f(-1.0 , 1.0 , 0.0 );
  glEnd();

 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....

Orx 1.2正式发布

经历了长达两个月的多次延期,历经iarwain的1984次更新,发布前的多次通宵奋斗,Orx 1.2终于正式发布了。。。。。。。。。。

 

作者的发布宣言在这里:http://orx-project.org/component/content/article/1-orx/65-orx-v12-has-been-released

 

新版本的下载地址在这里:http://sourceforge.net/projects/orx/files/orx/orx%20-%201.2%20%281984%29/

 

说实话,因为Orx目前没有好的工程文件管理系统,而又需要同时支持如此多的平台,iarwain光是为各个平台打包都花费了较长的时间。真希望有精通CMake的人能够帮他一把。将整个过程实现自动化。

 

新版本的Orx已经支持了目前所有的流行平台,添加了custom font和unicode支持,已经可以很好的显示中文及各类工具生成美化过的英文,iarwain为unicode的显示还自己写了一个字体工具,并且以zlib这样一个非常自由的协议发布。Have Fun!

 

贴一段Orx的新的Notes:

Orx - Portable Game Engine  (Version 1.2)

=============================================================================

Email iarwain [at] orx-project.org to contact the author; or better, check
orx's homepage at http://orx-project.org for the most up-to-date contact information.

This engine is licensed under the zlib license, see the LICENSE file for details.

Intro
-----

Orx is a 2D-oriented, data-driven, portable game engine focused primarily on extreme ease of use.

This is the final release for orx v1.2. If you find any bugs,
please report them at http://forum.orx-project.org, on the "Bug report - Feature request" board,
or via orx's SourceForge page/tracker.

The current features of orx engine are:
- hardware-accelerated rendering that provides: translation, anisotropic scale, rotation,
  transparency (alpha blending), different blending modes, coloring, tiling and mirroring
- automatic differential scrolling and depth scaling upon request
- animation engine (including a chaining graph & custom animation events for synchronization)
- fragment (pixel) shader support
- collision handling and rigid body physics
- generic input system that abstracts keyboard, mouse, joystick, touch and accelerometer inputs
- powerful config system that makes orx data-driven and provides an easy to use load/save system
- powerful localization module
- camera/viewport scheme allowing multiple views displayed with camera translation, zoom and rotation
- visual FXs based on curve combinations
- spawners (provides an easy way to create particles or projectiles)
- 3D positioning using "scene nodes"
- custom bitmap font support
- music and spatialized sound support
- clock system that provides time consistency and allows time stretching + high precision timers
- event manager
- unicode support with UTF-8 encoding
- plugin system
- screenshot capture tool (supports bmp, png, jpg, tga and dds)

See ./doc/html directory for the doxygen documentation of orx's API.

You can find details about the tutorials (as well as community-made ones) and the data-driven side of orx
(ie. how to use the config system) on orx's wiki at: http://wiki.orx-project.org

The English version is the most complete one, Chinese and Spanish translations are being written by community members.

Supported Platforms
-------------------

The engine compiles and is tested for:
- Linux (x86/x86-64 with codelite)
- Win32 (msvs2005, msvs2008, msvs2010 and mingw32 with code::blocks or codelite)
- MacOS X (ppc/x86 with xcode), version 10.5+ for GLFW plugins (default version) and 10.4+ for SFML ones (no joystick support).
- iPhone/iPod Touch/iPad (simulator & device with xcode)

The GP2X target is in debug stage and is not currently actively developed/maintained.

Versions
--------

Those are not revision versions but link/run versions.
Orx library can be compiled as a static or a dynamic library.
In addition to these modes, orx can be compiled as an embedded version or not.

Orx's core is basically platform-independent. All the platform/OS-dependent features
are implemented via plugins. These plugins can be loaded at runtime (hotplug) or
they can be embedded at linktime.

If you use the non-embedded versions, you'll have to specify which plugins to use.
This is more flexible but also requires additional files (the plugins themselves).
The embedded version will store everything in orx's library, meaning you won't be able
to choose which plugin to use at runtime, but will also be more compact. This will
also make orx run considerably faster.

From the download page you'll find precompiled binaries for Win32, Linux (x86) and MacOS X (ppc/x86)
using the dynamic embedded versions only.
If you want to use the non-embedded versions (to use with your own plugins)
or the static ones, you'll need to compile orx yourself from the source.
Everything compiles out-of-the-box for the hardware platforms cited above.

The embedded versions currently use:
- GLFW-based (+SOIL) plugins for display, joystick, keyboard and mouse for all non-iPhone platforms
- OpenAL-based (+libsndfile/stb_vorbis) plugins for sound
- Box2D-based plugin for physics
- homemade plugin for 2D rendering

- OpenGL/OpenAL-based plugins for display and sound on iPhone
- Touch/MultiTouch-based plugin for mouse on iPhone
- Accelerometer-based plugin for joystick on iPhone

Packages
--------

You can download all the packages from sourceforge (http://orx.sf.net).
Here is a list with a small description for each package.

- orx-doc-1.2.zip: orx's API doxygen documentation + PDF version of the English wiki (detailed tutorials and config system)

- orx-src-1.2.zip: orx's source code including build projects for
  . code::blocks (Win32 only, non-embedded versions)
  . codelite (Win32 and Linux, all versions)
  . msvs2005, msvs2008 & msvs2010 (Win32 Visual Studio, all versions)
  . xcode (2.4+, for MacOS X, non-static versions, for iPhone/iPod Touch, static embedded versions)
NB: You'll need orx-extern-1.2.zip (orx plugins' external dependencies) if you intend to compile orx yourself.

- orx-extern-1.2.zip: orx's external dependencies. You will only *NEED* these if you
use orx-src-1.2.zip and you plan on compiling orx yourself. They are not neeeded otherwise.
The external libraries are usually modified versions of the original ones.
VERY IMPORTANT: If you want to compile orx yourself, you'll need these versions and not the official ones.
Warning for slow connections: this package is ~67MB.

- orx-tools-1.2.zip: orx's additional set of tools (Win32, Linux and MacOS X) containing:
  . orxCrypt  : command line tool to encrypt/decrypt/merge multiple config files
  . orxFontGen: command line tool based on FreeType2 for generating custom bitmap fonts (.tga texture & .ini config file) from TrueType fonts

- orx-dev-linux-1.2.tar.bz2: orx's dynamic embedded precompiled binaries for Linux (x86), release and debug.
- orx-dev-mac-1.2.zip      : orx's dynamic embedded precompiled binaries for MacOS X (ppc/x86), release and debug.
- orx-dev-mingw-1.2.zip    : orx's dynamic embedded precompiled binaries for Win32 (mingw), release and debug.
- orx-dev-msvs2005-1.2.zip : orx's dynamic embedded precompiled binaries for Win32 (Visual Studio 2005), release and debug.
- orx-dev-msvs2008-1.2.zip : orx's dynamic embedded precompiled binaries for Win32 (Visual Studio 2008), release and debug.
- orx-dev-msvs2010-1.2.zip : orx's dynamic embedded precompiled binaries for Win32 (Visual Studio 2010), release and debug.

- orx-full-iphone-1.2.zip  : orx's static embedded precompiled binaries for iPhone/iPod Touch/iPad, release/debug, simulator/device + doc + source + XCode project file.

- orx-tutorial-linux-1.2.tar.bz2: orx's precompiled tutorial for Linux (x86), release only.
- orx-tutorial-mac-1.2.zip      : orx's precompiled tutorial for MacOS X (ppc/x86), release only.
- orx-tutorial-mingw-1.2.zip    : orx's precompiled tutorial for Win32 (mingw), release only.
- orx-tutorial-msvs2005-1.2.zip : orx's precompiled tutorial for Win32 (Visual Studio 2005), release only.
- orx-tutorial-msvs2008-1.2.zip : orx's precompiled tutorial for Win32 (Visual Studio 2008), release only.
- orx-tutorial-msvs2010-1.2.zip : orx's precompiled tutorial for Win32 (Visual Studio 2010), release only.

All the *-dev-* packages above include:
 . orx release/debug libraries used for linking
 . runtime release/debug orx libraries, launchers and config en/de-crypting tool
 . external runtime librairies needed on some platforms
 . headers to include at compile time
 . Template files exposing wich properties can be accessed using the config system (for user reference only, not needed by orx)

All the *-tutorial-* packages above include:
 . heavily commented source code for 11 basic and advanced tutorials
 . precompiled binaries (orx link library, orx runtime library and launcher, external runtime library)
 . headers to include at compile time
 . Template files exposing wich properties can be accessed using the config system (for user reference only, not needed by orx)
 . project files (xcode (MacOS X), codelite (Win32 mingw & Linux x86), msvs2005 & msvs2008)

NB: Most of the binaries have been packed using UPX (http://upx.sf.net) for the sake of bandwidth.
You can unpack them (upx -d) if you feel like it, but it shouldn't affect their execution anyway.

Compiling
---------
 
The easiest way to learn how to compile your project using orx for a given platform
is to look at the tutorial build project files. If you want to use another version
(tutorials use the dynamic embedded versions of orx), please look at
orx's launcher (main executable) build project file.

The debug version is far slower than the release one, but will output all the warning
and error messages useful for debugging.

Here's a quick list of the available compile preprocessor flags:
- __orxDEBUG__    : used to compile and link against the debug versions of orx library
                    (liborxd.a / orxd.lib / liborxd.dylib), if not specified it refers to
                    the release versions (liborx.a / orx.lib / liborx.dylib).
                    NB: If you want to link against the debug versions of orx library, you need
                    to specify it to your compiler!

- __orxSTATIC__   : used to compile and link against the static versions of orx library.
                    NB: If you want to link against the static versions of orx library, you need
                    to specify it to your compiler!

- __orxEMBEDDED__ : used to compile the embedded versions of orx library.
                    NB: this flag is *ONLY* needed when compiling orx library, not when linking
                    against it.

- __orxFREEBASIC__: used to compile and link the freebasic version of orx, still WIP.

There are other preprocessor flags used when compiling the orx library,
but those should be easy enough to decipher.
However, you might want to specify them manually for cross-compiling or
use them so that your code will behave differently depending on the architecture
for which you're compiling. Here's a quick list of these flags:
- __orxPPC__          : orx is being compiled for a PowerPC architecture
- __orxX86_64__       : orx is being compiled for a x86-64 architecture
- __orxLITTLE_ENDIAN  : orx is being compiled for a little endian architecture
- __orxBIG_ENDIAN     : orx is being compiled for a big endian architecture
- __orxGCC__          : orx is being compiled with gcc
- __orxMSVC__         : orx is being compiled with visual studio C/C++
- __orxWINDOWS__      : orx is being compiled for Win32
- __orxLINUX__        : orx is being compiled for Linux (x86)
- __orxMAC__          : orx is being compiled for MacOS X (ppc/x86)
- __orxIPHONE__       : orx is being compiled for iPhone/iPod Touch
- __orxWII__          : orx is being compiled for Wii
- __orxGP2X__         : orx is being compiled for GP2X (gcc-arm)
- __orxCPP__          : orx is being compiled with a C++ compiler
- __orxOBJC__         : orx is being compiled with an Objective-C compiler
- __orxFREEBASIC__    : orx is being compiled for FreeBasic
- __orxPLUGIN__       : a plugin for orx is being compiled
- __orxEXTERN__       : code using orx's library is being compiled

Comments
--------

If you have any questions, comments, ideas or reviews, feel free to post them
on orx's forum (http://forum.orx-project.org) or send them directly by mail to iarwain [at] orx-project.org

Enjoy!

阅读全文....