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

Orx 字体图片生成工具完成 支持unicode,当然包括中文

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

讨论新闻组及文件

工具产生的历史

    很正常又让非英语/拉丁语国家恼怒的事情就是,绝大多数的美国/欧洲人编写的软件从来没有考虑到世界上还有用其他语言的人,在以前,可以说是编码上考虑多语言难度比较大,为了简单才这样,但是在使用Unicode编码已经如此简单的时候,还是有很多软件的作者从来没有考虑过这个世界上还有用其他语言的人。作为非英语国家,甚至连拉丁语系都不是的国家,而且还拥有者世界上最复杂最多字符语言(之一)的中国受害最深,但是,情况正在改善。

    当iarwain因为Orx的中国使用者越来越多,开始为Orx加入了custom font和unicode支持的时候,他碰到了很大的问题,现在能够找到的字体图片生成工具就没有一个很好的支持Unicode的。(或者工具本身就不够好)我也尝试了几款,有很多很酷的字体工具,但是,很遗憾的,最多的支持也就是拉丁语系。于是iarwain决定自己写一个这样的工具。

    勤奋的iarwain让我印象深刻,整个周末都在忙着完成这个字体生成工具。从蒙特利尔时间晚上两点,iarwain完成第一版,因为stb_truetype的一些问题,导致一些字体的中文标点显示不正常,一直到蒙特利尔时间第二天早上7点半,iarwain将工具完整的迁移到更加稳定强大的freetype2上,我测试过新的工具,完整,可用,不知道还有谁会在工作之余如此的拼命,也许只有code for fun才能这样吧,我只能说I服了you了。

    事实上,虽然iarwain是为了Orx写了这样一个工具,但是,如我所言,现在网络上很难找到一个类似的支持unicode的字体图片生成工具,所以这样的工具其实填补了很大的空白,因为其开源,所以应该可以被其他任何游戏引擎所借鉴和使用。因为,显示一个自定义字体的原理都是一样的,事实上,为了高效的完成字体的显示,原理也只能是这样。当然,对于需要完整字库(比如需要聊天功能的网游)的游戏,这样的原理可能并不适用。假如还有什么不足的话,那就是这个工具目前仅仅是完成了文字到字体图片的转换功能,还不能添加更多很炫很酷的效果。

工具介绍

    这里,介绍一下新的工具。下载地址

    也能从Orx的SVN中获取。SVN地址:https://orx.svn.sourceforge.net/svnroot/orx。

    工具是个命令行工具。

    使用方法:

    orxFontGen.exe -s "字体大小" -f "字体文件名" -o “生成对象名" -t "输入的所有文字"

比如,我希望使用微软雅黑,来完成上面一段工具的历史的输出。我首先将上述文字保存成一个文件,这里命名为toolHistroy.txt,(需要特别注意的是,保存的文字一定要用utf-8进行编码,这点可以用notepad++或者ultraEditor之类的工具进行。并将其拷贝到工具所在的目录。然后将微软雅黑的字体文件msyh.ttf(从windows/fonts目录)拷贝到工具所在的目录。我希望将生成的对象命名为msyhFont,于是,我用下列命令执行工具:

orxFontGen.exe -s 32 -f msyh.ttf -o msyhFont -t toolHistory.txt

然后,我们得到两个东西,其一,4通道的tag文件。

 

其实这个图片可以被任何支持类似文字显示的引擎使用。

其二,一段Orx的配置。

[msyhFont]

CharacterList = " ()/27IOU_abcdefimnoprstuwxy。一丁上不且世两个中为么义之也了事二于些产人什仅从他以件任会似但体何余作使候借假入其具写决况几利到刻前功加勤半单印历原又受只可史吧周命和善因国图在填复多够大天奋好如始字完定实害家导将尔尝就工己已常应度开引强当很忙怒恼情憾戏成我或所才找拉拥拼持换擎支改效数整文新早时是显晚更最有服末本杂来果标样欧款正此比没洲测深添游源炫点然片版特现理甚生用界白的目直着知码碰示移稳空符第简类系经络绝编网美考者而能自至致英蒙虑虽补被言让许试话该语说谁象越足身转软较迁过还这连道遗那都酷鉴问间难非题高(),"

CharacterSize = (32, 32, 0)

Texture = msyhFont.tga

如此,显示这段文字,那就是很简单的事情了。(因为窗口大小问题,只显示了上面文字中的一段)

另外,很明显可以看到一点,虽然文字本身生成的时候是白色的,但是,可以由Orx的定点色进行颜色的改变。

对于还不知道怎么使用Orx显示文字的,可以参考Orx的教程
,或者我以前的文章

 

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

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

阅读全文....

在SDL中使用OpenGL

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

讨论新闻组及文件

    以前使用过SDL和GLFW,发现SDL的API设计,第3方库,以及社区支持都好过GLFW,但是SDL是对显示做过封装的,所以使用2D是方便,但是GLFW没有对显示进行封装,完全使用OpenGL,所以使用OpenGL非常方便,这里,我不禁想到,要是SDL也能很好的使用OpenGL那么就完美了,在网上也的确查到了相关的资料。在试用以后发现比我想的还要简单,因为SDL在Windows中默认使用D3D渲染,我还以为会需要加宏重新编译SDL或者啥的,但是,一切比我想象的要简单,并且,找到的教程质量非常高,该讲的都讲了,该注意的都提到了,通俗易懂,明白无误,这是一个开源产品的最大优势,SDL就是这样一个产品!本来我以为需要大书特书的东西,在这样的教程下,显得非常多余。。。。呵呵,还是贴一些自己的代码吧。

#include
#include
#include
#include "SDL.h"
#include "SDL_opengl.h"

#define WINDOW_WIDTH 640
#define WINDOW_HEIGHT 480
//OpenGL初始化开始
void SceneInit(int  w,int  h)
{
  glClearColor(0.0f , 0.0f , 0.0f , 0.0f );      // 黑色背景
  glColor3f(1.0f , 1.0f , 1.0f );

  glShadeModel(GL_FLAT);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(-50.0f , 50.0f , -50.0f , 50.0f , -1.0f , 1.0f );
}

//这里进行所有的绘图工作
void  SceneShow(GLvoid) {
  // 旋转角度

  static  float  fSpin = 0.0f ;

  fSpin += 2.0f ;

  if (fSpin > 360.0f ) {
    fSpin -= 360.0f ;
  }

  glClear(GL_COLOR_BUFFER_BIT);
  glPushMatrix();

  // 旋转矩形的主要函数
  glRotatef(fSpin, 0.0f , 0.0f , 1.0f );
  glRectf(-25.0 , -25.0 , 25.0 , 25.0 );
  glPopMatrix();
}  

int _tmain(int argc, _TCHAR* argv[])
{
  if ( SDL_Init(SDL_INIT_VIDEO) < 0 )
  {
    printf("Unable to initialize SDL: %sn", SDL_GetError());

    exit(1);
  }
  atexit(SDL_Quit);

  // 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*
  //SDL_Surface *screen = SDL_SetVideoMode(WINDOW_WIDTH, WINDOW_HEIGHT, 16, SDL_DOUBLEBUF);

  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
    // use this line instead of the commented one
    SDL_GL_SwapBuffers();
    //SDL_Flip( screen );

    // delay, 50 for simple
    SDL_Delay( 50 );

  }

    return 1;
}

以前写的使用SDL本身封装的渲染方式使用的方法比较一下,发现仅仅改变了几行代码,注释中都有说明。
一是创建窗口和使用双缓冲的代码:
  // 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*
  //SDL_Surface *screen = SDL_SetVideoMode(WINDOW_WIDTH, WINDOW_HEIGHT, 16, SDL_DOUBLEBUF);

一是flip缓冲的代码:
    SDL_GL_SwapBuffers();
    //SDL_Flip( screen );

仅此两处而已,其他地方,就可以完全使用openGL了,如上面代码所示,与GLFW的文章比较一下,发现SDL假如能够如此容易的支持OpenGL的话,即使是3D的东西,用SDL来管理一些跨平台的东西,然后使用OpenGL来渲染,其实也是非常不错的选择。
效果:

 

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

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

阅读全文....

SDL 简单入门学习

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

讨论新闻组及文件

概要

实际学习使用SDL创建窗口,并绘制图形。

前言   

    今天想要做一个简单的demo,因为一部分需要使用objective C,所以还需要跨平台,我才发现,我了解的东西还真没有一个适合做这样事情的,Cocos2D For IPhone仅仅能在IPhone下跑,HGE仅仅能在Windows下跑,Orx虽然能够跨平台,但是很显然,用于做简单的demo太麻烦了,因为我需要的仅仅是一个简单的DrawBmp函数而已,Orx那种一使用就使用一套的做法不太适合,还能想到的就是OpenGL了,但是用OpenGL做跨平台应用全靠自己那就挺麻烦了,还是找个框架吧。
    其实我有3个选择,glut/free glut, SDL, GLFW。其中glut虽然在学习OpenGL的时候用过一些,但是因为该项目已经死了,我不想再为其投入更多的学习时间,了解到可以看懂其代码的水平(其实使用也很简单),已经够了。SDL是很多人推荐的选择,我以前找工作的时候,国内的一家公司竟然直接提到过,说明其在国内也算是有人用了。我在那以后也看过SDL的API,感觉还算简单,与HGE一样,图形显示上,都是用了类似DirectDraw的抽象。(应该也是最通吃的抽象方式了)我对GLFW的感兴趣是因为最近Orx的作者iarwain提到过,并且给予了很高的评价,他说对GLFW的轻量级印象非常深刻,在最新的Orx版本中,GLFW是Orx的默认插件,并且就iarwain的测试,比SDL快5%左右(虽然不算太多),最重要的是,GLFW的封装都很简单,以直接使用OpenGL为主,借这个契机,我也顺便复习一下OpenGL,最近老是用库,我都快忘了该怎么用了。
    两相比较,我发现我不知道该用SDL还是GLFW,按我的习惯,两个一起用先,尝试一下再下结论。本文先看看SDL。

实际使用   

    其实我的需求很简单,创建窗口,在制定的地方绘图。简单的说也就是类似于CreateWindows和DrawBmp的两个函数而已。

    SDL其实真的算挺出名的了,也有人提到过,即使不真的准备使用SDL,但是想想一个库,能够被移植到这么多平台,抽象封装的方式和源代码起码都值得研究研究。因为这个,我也稍微看一下,虽然真的不打算长期SDL。SDL的协议是LGPL的(也有商业协议),还算可以接受。
    环境的搭建还算简单,Windows版本的SDL需要D3D SDK支持。
    简单的参考了一下教程,显示BMP图片的过程还算简单:
 1

 2
#include


 3
#include


 4
#include


 5
#include
"SDL.h"

 6

 7
int
 _tmain(int
 argc, _TCHAR* argv[])
 8
{
 9
  if
 ( SDL_Init(SDL_INIT_AUDIO|SDL_INIT_VIDEO) < 0
 )
10
  {
11
    printf("Unable to initialize SDL:
%s
n
"
, SDL_GetError());
12

13
    exit(1
);
14
  }
15
  atexit(SDL_Quit);
16

17
  //Load image

18
  SDL_Surface* picture = SDL_LoadBMP( "dragon.bmp"
 );
19

20
  SDL_Surface *screen = SDL_SetVideoMode(640
, 480
, 16
, SDL_DOUBLEBUF);
21
  if
 ( screen == NULL
 )
22
  {
23
    printf("Unable to set video mode:
%s
n
"
, SDL_GetError());
24

25
    exit(1
);
26
  }
27

28
  //Apply image to screen

29
  SDL_BlitSurface( picture, NULL
, screen, NULL
 );
30

31
  //Update Screen

32
  SDL_Flip( screen );
33

34
  //Pause

35
  SDL_Delay( 2000
 );
36

37
  //Free the loaded image

38
  SDL_FreeSurface( picture );
39

40
    return
 1
;
41
}

这里,可以看到,SDL没有管理主循环,同时我没有使用自己的主循环,那得牵涉到SDL的事件系统,所以,这个演示里,用了SDL_Delay,才能看到图片的显示。这里的显示没有指定大小,没有指定alpha值,所以图片原大显示。SDL_BlitSurface
函数的使用很像Windows API的对应函数。其他也没有什么好说的,看注释及函数名就知道在干什么了。

然后,通过下述方式来设置想要的透明色(在没有alpha通道的bmp中,也只能使用这样蹩脚的color key方式了)
1

2
  Uint32 colorKey = SDL_MapRGB(picture->format, 0xFF
, 0xFF
, 0xFF
);
3
  SDL_SetColorKey(picture, SDL_SRCCOLORKEY, colorKey);


其中0xFF,0xFF,0xFF分别是想要设定的颜色的R,G,B,这里都是0xFF,那就是白色了。
于是,原图:

在黑色背景下,周围的白色都透明了,显示出下列的效果:

说实话,用SDL的API还算比较简单,使用的时候有点感觉时空穿越,有点回到当年学习使用Win32 API来做类似事情的时候。接口的概念都差不多,也许SDL的优势比起当年的Win32 API仅在于速度和跨平台了。
显示两个图只需要再blit一次即可:
  //Apply image to screen

  SDL_BlitSurface( picture, NULL
, screen, NULL
 );

  SDL_Rect dest;
  dest.x = picture->w;
  dest.y = 0
;

  //Apply image to screen again and move it to right

  SDL_BlitSurface( picture, NULL
, screen, &dest );


然后,加入SDL对主循环的控制。

  // main loop

  bool
 running = true
;
  while
 (running) {
    //Update Screen

    SDL_Flip( screen );

    // delay, 50 for simple

    SDL_Delay( 50
 );

    //While there's an event to handle

    SDL_Event event;
    while
( SDL_PollEvent( &event ) ) {
      if
 (event.type == SDL_QUIT) {
        running = false
;
      }
    }
  }


SDL_PollEvent用于轮询SDL的事件。
于是,现在SDL创建的窗口可以被拖动,也可以点击关闭了。
想说的是,SDL真的很简单,我看教程的时候基本上只需要看源代码即可,就能了解大部分的意思。

    下面到关键的部分了,PNG的显示,我看到,虽然SDL本身仅仅支持BMP,但是已经有人做了一个名叫SDL_Image的库,可以支持其他格式。(考验一个开源库好不好,有没有良好的第3方支持是很重要的方面,SDL这方面明显很不错)于是,我们就不用直接使用libpng了,个人不是很喜欢libpng的接口。。。。。
    不知道是不是秉承了SDL简单的优良传统,SDL_Image的使用也非常简单,编译好后,将里面的动态库(因为需要支持PNG,所以有libpng和zlib的动态库)都拷贝到运行目录里面,然后包含"SDL_image.h"就可以了。从bmp到png的距离只有几行代码。。。。。从我发现SDL_Image到真的加载显示PNG图片,也就过了不到10分钟。。。。
全部源代码:

#include


#include


#include


#include
"SDL.h"

#include
"SDL_image.h"

int
 _tmain(int
 argc, _TCHAR* argv[])
{
  if
 ( SDL_Init(SDL_INIT_AUDIO|SDL_INIT_VIDEO) < 0
 )
  {
    printf("Unable to initialize SDL:
%s
n
"
, SDL_GetError());

    exit(1
);
  }
  if
 (IMG_Init(IMG_INIT_PNG) == 0
 ) {
    printf("Unable to initialize SDL_image"
);

    exit(1
);
  }
  atexit(SDL_Quit);

  //Load image

  //SDL_Surface* picture = SDL_LoadBMP( "dragon.bmp" );

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

  SDL_Surface *screen = SDL_SetVideoMode(640
, 480
, 16
, SDL_DOUBLEBUF);
  if
 ( screen == NULL
 )
  {
    printf("Unable to set video mode:
%s
n
"
, SDL_GetError());

    exit(1
);
  }

  // because we use png with alpha now

  //Uint32 colorKey = SDL_MapRGB(picture->format, 0xFF, 0xFF, 0xFF);

  //SDL_SetColorKey(picture, SDL_SRCCOLORKEY, colorKey);

  //Apply image to screen

  SDL_BlitSurface( picture, NULL
, screen, NULL
 );

  SDL_Rect dest;
  dest.x = picture->w;
  dest.y = 0
;

  //Apply image to screen again and move it to right

  SDL_BlitSurface( picture, NULL
, screen, &dest );

  // main loop

  bool
 running = true
;
  while
 (running) {
    //Update Screen

    SDL_Flip( screen );

    // delay, 50 for simple

    SDL_Delay( 50
 );

    //While there's an event to handle

    SDL_Event event;
    while
( SDL_PollEvent( &event ) ) {
      if
 (event.type == SDL_QUIT) {
        running = false
;
      }
    }
  }

  //Free the loaded image

  SDL_FreeSurface( picture );

    return
 1
;
}


仅仅只有几行修改,在代码中看的很清楚。

 

小结

    简单,还是简单,这是我学习SDL的最大感受,API设计的简单,相关概念也简单,SDL无愧于Simple DirectMedia Layer中的Simple一次。要想真的从OpenGL学习起,然后调用libpng来加载PNG图像并显示,你得看到红宝书的第十几章,直到纹理贴图的学习后你才能做到,但是,在SDL中做这些事情实在是太简单了。即使与当年的Win32 API相比,也少了很多Windows特定的消息循环原理等东西的学习,创建窗口的API比较起来,就会感觉MS那一帮人都是废物,设计的窗口创建API,竟然需要用几十行的代码去创建一个窗口,还需要注册窗口类。。。。。。。。。。。事实上,只需要一行代码。。。。。。

    一般来说,一个比较流行的开源库都是比较好的,因为好,才流行,因为流行而变的更好,其中,最最重要的关键在于,使用要简单,太复杂难用的库,无论设计的多么精巧,都很难吸引到使用者,也就难以进入这个良性的循环,不得不说,SDL在这方面,做的是非常好了。

 

 

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

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

阅读全文....

GLFW 简单入门学习

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

讨论新闻组及文件

概要

    实际学习使用GLFW创建窗口,并绘制图形。作为比较,可以参考一篇关于SDL的类似文章

前言

    使用SDL时,对其使用的简单印象非常深刻,假如没有效率上的原因,(SDL据说效率也不差)我想,即使是做一般的游戏引擎都可以考虑用SDL来实现。现在尝试使用一下GLFW(
http://www.glfw.org/),GLFW在国内并不是很出名,第一次听说也是从Orx的作者iarwain那里,SDL那篇文章已经说过了,因为SDL给我的感觉非常好,我很难想象GLFW会超过它,但是,平时想真的自己使用OpenGL的时候,有个框架可以使用也挺不错的,而且可以不使用glut,使得真的需要进一步做成产品时,不至于像使用glut那样受到限制。另外,GLFW使用的是zlib的协议,比SDL更加自由。

实际的使用

    现在的最新版GLFW是2.6,我下载的是源码包,下载后,support目录有VS的工程,有两个projects,一个是编译成lib,(因为GLFW使用的是zlib协议,所以这也是允许的)一个是编译成dll。我为了减少编译和链接的时间,使用dll。编译成库以后,使用时,只用包含一个头文件即可,只需要一个头文件。。。。。
    其实,假如你原因,GLFW的文件并不是很多,全部包含进自己的工程也可以。(这也不违反协议)而且,我注意到support目录下有很多有意思的东西,包括通过lua,basic,汇编语言来使用GLFW和OpenGL的例子,也算是很有意思了。因为GLFW的接口比较少,所以,事实上,做一个语言的绑定工作的工作量不是太大,而且提供的都是C接口,这让语言的绑定更加简单,只要该语言支持C语言接口,而且有了OpenGL的绑定。虽然GLFW没有提供Python绑定,不过我感觉也不难做。呵呵,这些都是题外话了。
    编译后创建工程,这些都没有什么好说的了,不过第一次编译一个使用了glfwInit函数的小工程,竟然报链接错误,我很奇怪,网上查了后,发现需要自己定义一个GLFW_DLL的宏,这点倒是比较奇怪。见此贴
。心理有点不快,这点在官方的user guide中也没有提及。

创建窗口

    一直记得在Windows下使用OpenGL有多么的麻烦,见《Win32 OpenGL 编程(1)Win32下的OpenGL编程必须步骤
》,那甚至值得用一篇论文来专门描述。使用GLFW呢?
    也还算简单,主要是调用glfwOpenWindow这个函数,参数比较多,但是还算容易理解。当我按照网上少有的教程
,(这点也体现了越流行的东西越好用的道理,SDL的教程太丰富了,其中很好的也很多,我很容易就上手了。GLFW就没有那么好了,我联想到自己用Orx的经历,更加发现如此。)开始尝试运行起一个程序时,我发现一个问题,我建立自己的主循环时,(GLFW也没有内部为你控制)尽管加入了对键盘的响应,但是还是没有用,窗口仍然死在那里。
类似这样:
//错误示范,切勿模仿
int
 _tmain(int
 argc, _TCHAR* argv[])
{
  if
(!glfwInit()) {
    printf("GLFW init error."
);
  }

  if
 (!glfwOpenWindow(800
, 600
, 6
, 6
, 6
, 0
, 32
, 0
, GLFW_WINDOW) ) {
    glfwTerminate();
    exit(1
);
  }
  glfwSetWindowTitle("The GLFW Window"
);

  // this just loops as long as the program runs

  while
(true
) {
    if
 (glfwGetKey(GLFW_KEY_ESC) == GLFW_PRESS) {
      break
;
    }

    glfwSleep(0.05
);
  }

  glfwTerminate();

    return
 0
;
}


我不得其解,后来感觉主循环可能需要加入事件的轮询吧,而且还发现了GLFW中有类似的机制,pollevent的函数也在那里,但是这个教程为什么没有用却可以呢?后来在GLFW的文档中找到这么一句:

 If it is not desirable that glfwPollEvents is called implicitly from glfwSwapBuffers, call glfwDisable
with the argument GLFW_AUTO_POLL_EVENTS.

    我就晕了,glfwSwapBuffers竟然隐含着调用了poolEvents。。。。。。。。。。我无语。因为我暂时没有绘制图形,所以从网上的教程中去掉了此句,这正是问题所在,在glfwSleep(0.05
);

前添加glfwSwapBuffers后,问题解决。再次感叹,好的接口容易让人用对,坏的接口反之。。。。。。。不要去说是用某个东西前需要将文档全看了话,那是狡辩,好的语言,API设计,应该让人仅仅看了一部分,也能正常的工作和使用,即使是想当然的撞大运编程方式,也应该让人撞对才对,这才符合最小惊讶原则。。。。。。

    用到这里,我得感叹一句,GLFW的user guide写的真的不咋地,和reference没有区别,狂列举API,却缺少实际的例子,这和reference有啥区别,user guide应该就是写来让人快速掌握的。

 

OpenGL使用

有了上述的东西,基本上OpenGL的环境已经搭好了,可以尝试用OpenGL干点什么了。

这里将原来学OpenGL中的教程的例子《Win32 OpenGL 编程(1)Win32下的OpenGL编程必须步骤
》拉过来改改,先尝试一下。


#include
"stdafx.h"

#include
"stdlib.h"

#include
"gl/glfw.h"

#define WINDOW_WIDTH (
800
)

#define WINDOW_HEIGHT (
600
)

//OpenGL初始化开始

void
 SceneInit(int
 w,int
 h)
{
  glClearColor(0.0f
, 0.0f
, 0.0f
, 0.0f
);      // 黑色背景

  glColor3f(1.0f
, 1.0f
, 1.0f
);

  glShadeModel(GL_FLAT);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(-50.0f
, 50.0f
, -50.0f
, 50.0f
, -1.0f
, 1.0f
);
}

//这里进行所有的绘图工作

void
 SceneShow(GLvoid) {
  // 旋转角度

  static
 float
 fSpin = 0.0f
;

  fSpin += 2.0f
;

  if
(fSpin > 360.0f
) {
    fSpin -= 360.0f
;
  }

  glClear(GL_COLOR_BUFFER_BIT);
  glPushMatrix();

  // 旋转矩形的主要函数

  glRotatef(fSpin, 0.0f
, 0.0f
, 1.0f
);
  glRectf(-25.0
, -25.0
, 25.0
, 25.0
);
  glPopMatrix();

  // 交换缓冲区

  glfwSwapBuffers();
}  

int
 _tmain(int
 argc, _TCHAR* argv[])
{
  if
(!glfwInit()) {
    printf("GLFW init error."
);
  }

  if
 (!glfwOpenWindow(WINDOW_WIDTH, WINDOW_HEIGHT, 6
, 6
, 6
, 0
, 32
, 0
, GLFW_WINDOW) ) {
    glfwTerminate();
    exit(1
);
  }
  glfwSetWindowTitle("The GLFW Window"
);

  SceneInit(WINDOW_WIDTH, WINDOW_HEIGHT);

  // this just loops as long as the program runs

  while
(true
) {
    if
 (glfwGetKey(GLFW_KEY_ESC) == GLFW_PRESS) {
      break
;
    }

    SceneShow();
    glfwSleep(0.05
);
  }

  glfwTerminate();

    return
 0
;
}

 

运行正常,总的来说在GLFW中搭建一个OpenGL的环境还是很简单的吧,起码比Windows下使用Win32 API来使用OpenGL要简单的多,并且,这还是跨平台的。(知道和很多人说跨平台就是废话)

真的用GLFW使用过OpenGL后,发现其实还算是比较简单,我的那些话也有些苛责了。虽然我一向很喜欢真的了解底层,比如Win32 API,比如cocoa,但是,能够有个跨平台的库,为我将这些东西都管理起来,就算我要写什么,那也会简单很多。另外,虽然SDL也能做到这些,但是相对于GLFW对于OpenGL的直接支持,(SDL中其实也可以)我感觉用起来还是更加亲切一些。

图形的显示

    这才是最终的目的。
真正的位图的显示,在OpenGL中都不是那么容易的,需要掌握一堆的东西。见《Win32 OpenGL编程(15) 位图显示
》《Win32 OpenGL编程(16) 纹理贴图
》,那是因为OpenGL中实际完全对图形的显示没有直接的支持。听起来有些奇怪。。。实际的意思就是,OpenGL的API完全不理解位图,png图的含义。(虽然在上述15中提到一些bmp的操作接口,但是很遗憾的,实际的使用中都是使用纹理贴图,即16中提到的东西) 在GLFW中呢?我看到GLFW有对图形操作的接口。可是遗憾的是仅支持TGA,连BMP都不支持,不知道这种取舍是为啥,一般而言,我感觉,支持bmp的话,是最基础的。
这里还是用《SDL 简单入门学习
》中的那个龙图。
其中,图形文件操作的接口分两种,这里只看OpenGL常用的使用纹理贴图的方式。用到的API名字叫glfwLoadTexture2D。先显示个tga试一下。
代码的主要部分还是OpenGL,所以可以参考《Win32 OpenGL编程(16) 纹理贴图
》中的代码,仅仅借用了glfw的glfwLoadTexture2D函数而已。主要代码如下:

#include
"stdafx.h"

#include
"stdlib.h"

#include
"gl/glfw.h"

#define WINDOW_WIDTH (
800
)

#define WINDOW_HEIGHT (
600
)

GLuint gTexName;
//OpenGL初始化开始

void
 SceneInit(int
 w,int
 h) {
  glClearColor(0.0f
, 0.0f
, 0.0f
, 0.0f
);      // 黑色背景

  glColor3f(1.0f
, 1.0f
, 1.0f
);

  glShadeModel(GL_FLAT);

  glGenTextures(1
 , &gTexName);
  glBindTexture(GL_TEXTURE_2D, gTexName);

  if
 ( !glfwLoadTexture2D("dragon.tga"
, GLFW_BUILD_MIPMAPS_BIT) ) {
    printf("glfw load the file failed."
);
  }

  // Use trilinear interpolation for minification

  glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
    GL_LINEAR_MIPMAP_LINEAR );
  // Use bilinear interpolation for magnification

  glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
    GL_LINEAR );

  // Enable texturing

  glEnable( GL_TEXTURE_2D );
}

//这里进行所有的绘图工作

void
 SceneShow(GLvoid) {
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glBindTexture(GL_TEXTURE_2D, gTexName);

  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();

  // 交换缓冲区

  glfwSwapBuffers();
}  

int
 _tmain(int
 argc, _TCHAR* argv[])
{
  if
(!glfwInit()) {
    printf("GLFW init error."
);
  }

  if
 (!glfwOpenWindow(WINDOW_WIDTH, WINDOW_HEIGHT, 6
, 6
, 6
, 0
, 32
, 0
, GLFW_WINDOW) ) {
    glfwTerminate();
    exit(1
);
  }
  glfwSetWindowTitle("The GLFW Window"
);

  SceneInit(WINDOW_WIDTH, WINDOW_HEIGHT);

  // this just loops as long as the program runs

  while
(true
) {
    if
 (glfwGetKey(GLFW_KEY_ESC) == GLFW_PRESS) {
      break
;
    }

    SceneShow();
    glfwSleep(0.05
);
  }

  glfwTerminate();

    return
 0
;
}


感觉相对于图像显示来说,glfw并没有如SDL那般省事,因为毕竟还是全程使用OpenGL,对比原来OpenGL中显示位图的代码来说,仅仅是没有调用Windows API了而已,仅仅是多了跨平台的特性而已,并没有简化工作,而且,glfw同样的,也没有内置png图形的支持。虽然说tga在3D中用的非常多,主要是因为无损压缩,但是2D中,我还是喜欢使用png,因为小的多。当然,一旦使用png,无可避免的会需要使用libpng/zlib,所以GLFW为了保持自身的简单,没有做这样的工作吧,相对来说tga的解压就要简单太多了。
另外,GLFW还有glfwReadImage函数可以将tga图直接读入内存,然后获取到图形的相关信息的办法(上面就没有办法获取到图形的宽高)。但是使用上都已经差不多了。

都到了这个地步了,不显示一下GLFW对OpenGL的强力支持,所以做3D比较方便都不像话了。这里套用原来的代码。见《Win32 OpenGL编程(16) 纹理贴图
》。

//OpenGL初始化开始

void
 SceneInit(int
 w,int
 h) {
  glClearColor(0.0f
, 0.0f
, 0.0f
, 0.0f
);      // 黑色背景

  glColor3f(1.0f
, 1.0f
, 1.0f
);

  glViewport(0
 ,0
 ,WINDOW_WIDTH,WINDOW_HEIGHT);                      // Reset The Current Viewport

  glMatrixMode(GL_PROJECTION);                        // Select The Projection Matrix

  glLoadIdentity();                                   // Reset The Projection Matrix

  // Calculate The Aspect Ratio Of The Window

  gluPerspective(45.0f
 ,(GLfloat)WINDOW_WIDTH/(GLfloat)WINDOW_HEIGHT,0.1f
 ,100.0f
 );

  glMatrixMode(GL_MODELVIEW);                         // Select The Modelview Matrix

  glLoadIdentity();                                   // Reset The Modelview Matrix

  glGenTextures(1
 , &gTexName);
  glBindTexture(GL_TEXTURE_2D, gTexName);

  if
 ( !glfwLoadTexture2D("dragon.tga"
, GLFW_BUILD_MIPMAPS_BIT) ) {
    printf("glfw load the file failed."
);
  }

  // Use trilinear interpolation for minification

  glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
    GL_LINEAR_MIPMAP_LINEAR );
  // Use bilinear interpolation for magnification

  glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
    GL_LINEAR );

  glEnable(GL_DEPTH_TEST);
  // Enable texturing

  glEnable( GL_TEXTURE_2D );
}

//这里进行所有的绘图工作

void
 SceneShow(GLvoid) {
  static
 float
 xrot = 0.0f
,yrot = 0.0f
,zrot = 0.0f
;
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glLoadIdentity();                                   // Reset The View

  glTranslatef(0.0f
 ,0.0f
 ,-5.0f
 );

  glRotatef(xrot,1.0f
 ,0.0f
 ,0.0f
 );
  glRotatef(yrot,0.0f
 ,1.0f
 ,0.0f
 );
  glRotatef(zrot,0.0f
 ,0.0f
 ,1.0f
 );

  glBindTexture(GL_TEXTURE_2D, gTexName);

  glBegin(GL_QUADS);
  // Front Face

  glTexCoord2f(0.0f
 , 0.0f
 ); glVertex3f(-1.0f
 , -1.0f
 ,  1.0f
 );
  glTexCoord2f(1.0f
 , 0.0f
 ); glVertex3f( 1.0f
 , -1.0f
 ,  1.0f
 );
  glTexCoord2f(1.0f
 , 1.0f
 ); glVertex3f( 1.0f
 ,  1.0f
 ,  1.0f
 );
  glTexCoord2f(0.0f
 , 1.0f
 ); glVertex3f(-1.0f
 ,  1.0f
 ,  1.0f
 );
  // Back Face

  glTexCoord2f(1.0f
 , 0.0f
 ); glVertex3f(-1.0f
 , -1.0f
 , -1.0f
 );
  glTexCoord2f(1.0f
 , 1.0f
 ); glVertex3f(-1.0f
 ,  1.0f
 , -1.0f
 );
  glTexCoord2f(0.0f
 , 1.0f
 ); glVertex3f( 1.0f
 ,  1.0f
 , -1.0f
 );
  glTexCoord2f(0.0f
 , 0.0f
 ); glVertex3f( 1.0f
 , -1.0f
 , -1.0f
 );
  // Top Face

  glTexCoord2f(0.0f
 , 1.0f
 ); glVertex3f(-1.0f
 ,  1.0f
 , -1.0f
 );
  glTexCoord2f(0.0f
 , 0.0f
 ); glVertex3f(-1.0f
 ,  1.0f
 ,  1.0f
 );
  glTexCoord2f(1.0f
 , 0.0f
 ); glVertex3f( 1.0f
 ,  1.0f
 ,  1.0f
 );
  glTexCoord2f(1.0f
 , 1.0f
 ); glVertex3f( 1.0f
 ,  1.0f
 , -1.0f
 );
  // Bottom Face

  glTexCoord2f(1.0f
 , 1.0f
 ); glVertex3f(-1.0f
 , -1.0f
 , -1.0f
 );
  glTexCoord2f(0.0f
 , 1.0f
 ); glVertex3f( 1.0f
 , -1.0f
 , -1.0f
 );
  glTexCoord2f(0.0f
 , 0.0f
 ); glVertex3f( 1.0f
 , -1.0f
 ,  1.0f
 );
  glTexCoord2f(1.0f
 , 0.0f
 ); glVertex3f(-1.0f
 , -1.0f
 ,  1.0f
 );
  // Right face

  glTexCoord2f(1.0f
 , 0.0f
 ); glVertex3f( 1.0f
 , -1.0f
 , -1.0f
 );
  glTexCoord2f(1.0f
 , 1.0f
 ); glVertex3f( 1.0f
 ,  1.0f
 , -1.0f
 );
  glTexCoord2f(0.0f
 , 1.0f
 ); glVertex3f( 1.0f
 ,  1.0f
 ,  1.0f
 );
  glTexCoord2f(0.0f
 , 0.0f
 ); glVertex3f( 1.0f
 , -1.0f
 ,  1.0f
 );
  // Left Face

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

  xrot+=0.3f
 ;
  yrot+=0.2f
 ;
  zrot+=0.4f
 ;

  // 交换缓冲区

  glfwSwapBuffers();
}  


效果:

其实最后一个例子已经与介绍GLFW没有关系了,新添加的部分纯粹属于OpenGL的内容,这里参看原来的文章,这里不多加解释了。仅仅用于演示,当使用了OpenGL后,3D图形的使用的方便。

小结

    GLFW无愧于其号称的lightweight的OpenGL框架,的确是除了跨平台必要做的事情都没有做,所以一个头文件,很少量的API,就完成了任务。GLFW的开发目的是用于替代glut的,从代码和功能上来看,我想它已经完全的完成了任务,(虽然从历史原因上考虑还没有,毕竟红宝书都还是用glut。。。)并且glfw还在持续的开发当中,虽然作者总说他很忙。
    作为与SDL(参考《SDL 简单入门学习》
)比较而写的两篇文章。这里也做个小结。相对来说,SDL真的将API做的很简单,而且因为使用的人比较多,所以第3方扩展也做的很好,并且,碰到问题,你比较容易找到答案。假如是做2D应用,SDL真的已经非常不错了。GLFW作为一个跨平台的OpenGL框架,也出色的完成了任务,不过因为定位不同,封装较少,所以在做一些基本任务的时候,因为OpenGL本身的复杂性,会复杂一些。同时,资料也少的多,所以我写本文花费的时间比SDL那篇就要长了很多,说明学习周期也会长一些。。。。(但是个人感觉类似GLFW,SDL这样的库,学习周期基本为0。。。。比起编写图形或者游戏的其他方面来说可以忽略不计)但是,同时的,GLFW封装的少,也带来更大的灵活性,我们可以还是自由的用OpenGL完成工作,也可以完成自己的进一步封装,相当于GLFW将跨平台的一些脏活累活都干了,我们就剩最核心的渲染去自由发挥了。碰到一个任务,该怎么在两者之间选择呢?出于简单考虑,2D方面的东西用SDL做实在再合适和简单不过了,但是假如想要学习OpenGL或者是做3D应用,GLFW的确是不错的选择。绝对比在glut上的投入要值,当然,其实因为glut实在太过简单,其实出于教学/学习目的去看一下也实在没有什么投入,实际用OpenGL开发东西的时候,还是选择用GLFW做吧。
    也许,下一步,我可以看看怎么在SDL中使用OpenGL。在Windows下,SDL的默认渲染API使用的是D3D,不知道是否可以更改,并且完全使用OpenGL来工作呢?

 

 

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

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

阅读全文....

站在巨人的肩膀上开发游戏(5) -- 打砖块游戏制作续

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

讨论新闻组及文件

   其实在上一节,游戏已经基本成型了,但是还不能算是完整的游戏,本篇将需要完善的部分完成吧。

游戏加个边框

    这个实在不需要我额外讲方法了,按原来的办法加上四周的边框即可。不过这里讲几个个人总结的技巧,边框需不需要显示呢?需要显示的话,那没有话说,直接创造一个边框就可以了,不需要显示呢?我想出了两个办法,其一,将边框创造在显示范围以外,那么自然是看不见了。其二,创建完全透明的图,那么你就可以将边框放置在任何你想要加碰撞阻挡,却不希望玩家看到的位置了。其三,不透明的图其实也行,只需要放置在Z轴超出视景体即可(大于远剪裁面,小于等于近剪裁面),由于box2D是2D的物理引擎,会忽略Z轴,同样的,只要X,Y轴对了,你还是能够获取到你想要的碰撞效果。

这里由于懒得做资源了,直接使用Orx作者的资源和配置,所以使用第一种方法。
首先,用作者的wall.png创造四周的墙壁,为了先看到直观的看到碰撞效果,同时也为了大概确认位置正确,我们首先将墙壁置为可见。
配置如下:

; Wall

[WallTemplate]

Graphic   =
 WallGraphic
Body      =
 WallBody
BlendMode =
 alpha

[WallGraphic]

Texture =
 data/wall.png
Pivot   =
 center

[Walls]

ChildList =
 Wall1 # Wall2 # Wall3 # Wall4

[Wall1@WallTemplate]

Position  =
 (0.0, 250, 0.0)
Rotation  =
 90.0

[Wall2@WallTemplate]

Position  =
 (-170, 0.0, 0.0)

[Wall3@WallTemplate]

Position  =
 (0.0, -250, 0.0)
Rotation  =
 90.0

[Wall4@WallTemplate]

Position  =
 (170, 0.0, 0.0)

[WallBody]

PartList  =
 WallBox
Dynamic =
 false

[WallBox]

Type        =
 box
Friction    =
 1.0
Restitution =
 0.0
SelfFlags   =
 0x0001
CheckMask   =
 0x0001
Solid       =
 true

我新建了一个名为wall.ini的配置文件,此时在原来的game.ini中添加下面这条语句表示包含。

@wall.ini@

其他的配置的含义在上一节中已经提到过,这里不重复了。

然后,在初始化的时候新添加一条代码创造墙壁。
  if (orxObject_CreateFromConfig("Walls") == NULL) {
      result = orxSTATUS_FAILURE;
  }

看到这里,提一下Orx作者iarwain推荐的方法,iarwain推荐大量的不需要直接获取指针的对象可以通过一个类似于scene的配置端来配置,然后通过ChildList字段串起来,由于一个ChildList的列表长度是255个,(已经够多了)假如还不够的话,任意一个ChildList指定的对象还可以是仅包含ChildList的字段。。。。。如此串起来,无穷尽也。。。。以此构建整个场景。。。。。好处是可以动态添加新的东西,完全不用像我一样添加代码。

然后,就可以看到正常的碰撞效果了。而且也可以看到墙壁存在。要将难看的墙壁消去,只需要修改配置将墙壁移到显示范围外即可(记得计算墙壁本身的宽度)

此例中部分配置实际的为如下内容即可:(将墙都往屏幕外移动10像素)

[Wall1@WallTemplate]

Position  =
 (0.0, 260, 0.0)
Rotation  =
 90.0

[Wall2@WallTemplate]

Position  =
 (-180, 0.0, 0.0)

[Wall3@WallTemplate]

Position  =
 (0.0, -260, 0.0)
Rotation  =
 90.0

[Wall4@WallTemplate]

Position  =
 (180, 0.0, 0.0)

响应输入

    有人将游戏称为第9艺术,并且,游戏与电影的区别就在于更多的交互,这里,我们来谈谈交互^^当然,那就是响应玩家的输入,并且做出反应罗。这里是用Win32平台来做例子的,所以,就说键盘输入吧。

首先,为了不在Run函数中去响应键盘输入,我添加一个新的自己的Update函数,需要是用一个计时器,相关的知识可以参考官方的中文WIKI

需要用下面两句注册一个Update的回调函数,这里创建的是20HZ的clock。
  orxCLOCK *pstClock = orxClock_Create(orx2F(0.05f
), orxCLOCK_TYPE_USER);
  orxClock_Register(pstClock, GameApp::Update, NULL
, orxMODULE_ID_MAIN, orxCLOCK_PRIORITY_NORMAL);


然后就可以在Update里面处理我们的的输入了。
首先,配置部分,很简单。

[Input]

SetList =
 Input
KEY_LEFT  =
 GoLeft
KEY_RIGHT =
 GoRight


主要就是配置Input配置段,这里的SetList一般情况下可以是其他配置段的名字,并且可以是一个list,这里为求简单,我指向了自身(配置简单化大法之一),这样就可以省下一个section。这里的配置表示按左方向键表示GoLeft的动作,按右方向键表示GoRight的动作。
我们在代码里面看GoLeft和GoRight怎么用的:

void
 GameApp::Update(const
 orxCLOCK_INFO *_clock_info, void
 *_context)
{
#define MOVE_SPEED
10

    if
( orxInput_IsActive("GoLeft"
) ) {
    orxVECTOR pos;
    orxObject_GetPosition(gPaddle, &pos);
    pos.fX -= MOVE_SPEED;
    orxObject_SetPosition(gPaddle, &pos);

    } if
 (orxInput_IsActive("GoRight"
)) {
    orxVECTOR pos;
    orxObject_GetPosition(gPaddle, &pos);
    pos.fX += MOVE_SPEED;
    orxObject_SetPosition(gPaddle, &pos);
    }
}

这里的gPaddle就是全局的paddle指针。然后,此时的游戏已经可以玩了。按左右方向键移动paddle即可。

这里再贴一下新的完整代码,有部分修改,还有一些改进前一节教程的内容
  1

  2
#include
"orx.h"

  3

  4
#include


  5
#include


  6
using
 namespace
 std;
  7
orxOBJECT* gPaddle;
  8
class
 GameApp
  9
{
 10
public
:
 11
  static
 orxSTATUS orxFASTCALL  EventHandler(const
 orxEVENT *_pstEvent);
 12
  static
 orxSTATUS orxFASTCALL  Init();
 13
  static
 void
 orxFASTCALL       Exit();
 14
  static
 orxSTATUS orxFASTCALL  Run();
 15
  static
 void
 orxFASTCALL   Update(const
 orxCLOCK_INFO *_clock_info, void
 *_context);
 16

 17
  GameApp() {};
 18
  ~GameApp() {};
 19

 20
  static
 GameApp* Instance() {
 21
      static
 GameApp instance;
 22
      return
 &instance;
 23
  }
 24

 25
private
:
 26
  orxSTATUS                     InitGame();
 27
};
 28

 29
#define BLOCK_TYPE
1

 30

 31
orxOBJECT *CreateText(orxSTRING _zTextSection)
 32
{
 33
    orxConfig_PushSection(_zTextSection);
 34
    orxConfig_SetString("Graphic"
, _zTextSection);
 35
    orxConfig_SetString("Text"
, _zTextSection);
 36

 37
    orxOBJECT *pstReturn = orxObject_CreateFromConfig(_zTextSection);
 38

 39
    orxConfig_PopSection();
 40

 41
    return
 pstReturn;
 42
}
 43

 44
// Init game function

 45
orxSTATUS GameApp::InitGame()
 46
{
 47
  orxSTATUS result = orxSTATUS_SUCCESS;
 48
  orxCLOCK *pstClock = orxClock_Create(orx2F(0.05f
), orxCLOCK_TYPE_USER);
 49
  orxClock_Register(pstClock, GameApp::Update, NULL
, orxMODULE_ID_MAIN, orxCLOCK_PRIORITY_NORMAL);
 50

 51

 52
  // Creates viewport

 53
  if
 ( orxViewport_CreateFromConfig("Viewport"
) == NULL
 ) {
 54
      result = orxSTATUS_FAILURE;
 55
  }
 56

 57
  if
 (orxObject_CreateFromConfig("Ball"
) == NULL
) {
 58
      result = orxSTATUS_FAILURE;
 59
  }
 60

 61

 62
  if
 ( (gPaddle = orxObject_CreateFromConfig("Paddle"
)) == NULL
) {
 63
      result = orxSTATUS_FAILURE;
 64
  }
 65

 66
  if
 (orxObject_CreateFromConfig("Blocks"
) == NULL
) {
 67
      result = orxSTATUS_FAILURE;
 68
  }
 69

 70
  if
 (orxObject_CreateFromConfig("Walls"
) == NULL
) {
 71
      result = orxSTATUS_FAILURE;
 72
  }
 73

 74
  orxEvent_AddHandler(orxEVENT_TYPE_PHYSICS, GameApp::EventHandler);
 75
  // Done!

 76
  return
 result;
 77
}
 78

 79
// Event handler

 80
orxSTATUS orxFASTCALL GameApp::EventHandler(const
 orxEVENT *_pstEvent)
 81
{
 82
    orxSTATUS eResult = orxSTATUS_SUCCESS;
 83
    if
(_pstEvent->eType == orxEVENT_TYPE_PHYSICS) {
 84
        if
( _pstEvent->eID == orxPHYSICS_EVENT_CONTACT_REMOVE ) {
 85
            /*
 Gets colliding objects
*/

 86
            orxOBJECT *object_recipient = orxOBJECT(_pstEvent->hRecipient);
 87
            orxOBJECT *object_sender = orxOBJECT(_pstEvent->hSender);
 88

 89
            string recipient_name(orxObject_GetName(object_recipient));
 90
            string sender_name(orxObject_GetName(object_sender));
 91
            if
(sender_name.substr(0
, sender_name.length()-1
) == "Block"
) {
 92
                // 这样比直接删除要安全

 93
                orxObject_SetLifeTime(object_sender, orxFLOAT_0);
 94
            }
 95
        }
 96
    }
 97
  // Done!

 98
  return
 orxSTATUS_SUCCESS;
 99
}
100

101
void
 GameApp::Update(const
 orxCLOCK_INFO *_clock_info, void
 *_context)
102
{
103
#define MOVE_SPEED
10

104
    if
( orxInput_IsActive("GoLeft"
) ) {
105
    orxVECTOR pos;
106
    orxObject_GetPosition(gPaddle, &pos);
107
    pos.fX -= MOVE_SPEED;
108
    orxObject_SetPosition(gPaddle, &pos);
109

110
    } if
 (orxInput_IsActive("GoRight"
)) {
111
    orxVECTOR pos;
112
    orxObject_GetPosition(gPaddle, &pos);
113
    pos.fX += MOVE_SPEED;
114
    orxObject_SetPosition(gPaddle, &pos);
115
    }
116
}
117
// Init function

118
orxSTATUS GameApp::Init()
119
{
120
  orxSTATUS     eResult;
121
  orxINPUT_TYPE eType;
122
  orxENUM       eID;
123

124
  /*
 Gets input binding names
*/

125
  orxInput_GetBinding("Quit"
, 0
, &eType, &eID);
126
  const
 orxSTRING zInputQuit = orxInput_GetBindingName(eType, eID);
127

128
  // Logs

129
  orxLOG("
n
- '
%s
' will exit from this tutorial"

130
         "
n
* The legend under the logo is always displayed in the current language"
, zInputQuit );
131

132
  orxLOG("Init() called!"
);
133

134
  // Inits our stand alone game

135
  eResult = GameApp::Instance()->InitGame();
136

137
  // Done!

138
  return
 eResult;
139
}
140

141
// Exit function

142
void
 GameApp::Exit()
143
{
144

145
  // Logs

146
  orxLOG("Exit() called!"
);
147
}
148

149
// Run function

150
orxSTATUS GameApp::Run()
151
{
152
  orxSTATUS eResult = orxSTATUS_SUCCESS;
153

154

155
  // Done!

156
  return
 eResult;
157
}
158

159

160
// Main program function

161
int
 main(int
 argc, char
 **argv)
162
{
163
  // Inits and runs orx using our self-defined functions

164
  orx_Execute(argc, argv, GameApp::Init, GameApp::Run, GameApp::Exit);
165

166
  // Done!

167
  return
 EXIT_SUCCESS
;
168
}
169

170

171
#ifdef __orxMSVC__

172

173
// Here's an example for a console-less program under windows with visual studio

174
int
 WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int
 nCmdShow)
175
{
176
  // Inits and executes orx

177
  orx_WinExecute(GameApp::Init, GameApp::Run, GameApp::Exit);
178

179
  // Done!

180
  return
 EXIT_SUCCESS
;
181
}
182

183
#endif
 // __orxMSVC__

一共183行。我并没有特意的精简代码,比如有两个可供切换的Main函数部分,有很多可以Init部分其实也都可以通过iarwain的方式缩减。
全部的源代码在:https://orx-sample.jtianling.googlecode.com/hg/
上,可以通过mercurial直接获取。
这里同样提供一个对比的参考。
How To Create A Breakout Game with Box2D and Cocos2D Tutorial: Part 1/2

How To Create A Breakout Game with Box2D and Cocos2D Tutorial: Part 2/2

点击链接下载源代码:Cocos2D and Box2D Breakout Game

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

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

阅读全文....

Orx官方教程: 08.物理特性(Physics)教程

本文译自 orx tutorials 的
physics tutorial
,六月的流光 译。最新版本见Orx 官方中文Wiki
。 本文转自六月的流光的博客

。原文链接在:http://yatusiter.blogbus.com/logs/68692886.html


 

Physics
tutorial

物理特性教程

 

Summary

综述

See previous
basic
tutorials


for more info about basic
object
creation


,
clock
handling


,
frames
hierarchy


,
animations

,
cameras
& viewports


,
sounds
& musics


and
FXs

.

 

查看之前关于
basic

object
creation


,
clock
handling


,
frames
hierarchy


,
animations

,
cameras
& viewports


,
sounds
& musics


and
FXs

.以获得更多的
信息。

 

This
tutorial shows how to add physical properties to objects and handle
collisions.

 


教程展示了如何为对象添加物理属性和处理碰撞。

 

As you can see, the physical properties are
completely data-driven. Thus, creating an object with physical
properties (ie. with a body) or without results in the exact same line
of code.

 

如你所见,物理
属性完全是数据驱动的。因此,可以在完全相同的一行代码创建一个拥有物理属性(即通过一个body)或没有物理属性的对象。

 

Objects
can be linked to a body which can be static or dynamic.

 

对象可以动态或
静态地链接到一个body(译注:body为Box2D中的一个概念,表示一个物理实体的概念)。

 

Each
body can be made of up to 8 parts.

 

每一个body可以由8部分组成。

 

A body
part is defined by:

 

一个body部分可以定义如下:

 


   
its shape (currently box,
sphere and mesh (ie. convex polygon) are the only available)


   
information about the shape size (corners for
the box, center and radius for the sphere, vertices for the mesh)


   
if no size data is specified, the shape will try
to fill the complete body (using the object size and scale)


   
collision “self” flags that defines this part


   
collision “check” mask that defines with which
other parts this one will collide

1)






two
parts in the same body will never collide



   
a flag (Solid) specifying if this shaped should
only give information about collisions or if it should impact on the
body physics simulation (bouncing, etc…)


   
various attributes such as
restitution, friction, density, …

 


   
它的形状(目前可用的只有箱子(
box,译注:长方体)、球体(sphere)和多边形(即 mesh,凸多边形))


   
关于形状大小的信息(箱子的拐角点,球体的球心和半径以及
凸多边形的顶点)


   
如果有没指定形状的大小数据,形状会尝试填满整个body(根据对象的大小和缩放)


   
“self”标记定义产生碰撞的部分


   
“check”掩码定义了与这部分产生碰撞的其他部分
(注释1 同一个物理的两个部分永远不会碰撞)


   
一个标记(Solid)指定了这个形状是否只给出碰撞的
信息还是应该影响body的模拟物理运动(弹跳等)


   
其他的各种属性如
弹性
(restitution)

 
、摩擦
(friction)、密度(density)……

In this tutorial we create static solid
walls around our screen. We then spawn boxes in the middle.

The
number of boxes created is tweakable through the config file and is 100
by default.

 


本教程中我们创建环绕我们屏幕的静态固体墙。然后我们在中间放置箱子。

要创建的箱子数目是可以通过配置文件调整的,默认为100。

 

The
only interaction possible is using left and right mouse buttons (or left
and right keys) to rotate the camera.

As we rotate it, we also update the
gravity vector of our simulation.

Doing so, it gives the impression that
the boxes will be always falling toward the bottom of our screen no
matter how the camera is rotated.

 

唯一可能的交互操作是使用鼠标的左右键(或者键盘左右方向键)来旋转摄像头。

在旋转时也将会
更新我们的模拟的重力矢量。

如此,会让我们有无论摄像头怎么旋转那些箱子都一直往我们屏幕底部掉落的感觉。

 

We also
register to the physics events to add a visual FXs on two colliding
objects.

By
default the FX is a fast color flash and is, as usual, tweakable in
realtime (ie. reloading the config history will apply the new settings
immediately as the FX isn't kept in cache by default).

 

我们也可以注册
物理事件来为两个碰撞的物体添加可视的特效(FXs)。

默认特效(FX)是一种快速的颜色闪烁,通常也是可以实时调整的(即,重新读取配置文
件会使新设置立即生效,因为特效默认是不保存在缓冲中的)。

 

Updating an object scale (including
changing its scale with FXs) will update its physical properties (ie.
its body).

Keep in mind that scaling an object with a
physical body is more expensive as we have to delete the current shapes
and recreate them at the correct size.

This is done this way as our current
single physics plugin is based on Box2D which doesn't allow realtime
rescaling of shapes.

 

更新一个对象的缩放比(包括通过FX改变它的缩放比)会更新它的物理属性(即 它的body)。

请注意改变一个
有物理特性的body对象代价是很高的,因为我们必须删除当前的形状并按正确的大小重建。

这么做是因为我
们目前唯一的物理引擎插件是基于Box2D的,它不支持实时重新缩放形状。

 

This tutorial does only show basic
physics and collision control, but, for example, you can also be
notified with events for object separating or keeping contact.

 

本教程只展示了
简单的物理特性和碰撞控制,但是,比如说,你也可以获取到对象碰撞和结束碰撞的事件。

 

Details

详细说明

 

As usual, we begin by loading our config
file, creating a clock and registering our Update function to it.

Please
refer to the



previous
tutorials


for more details.

 

就像往常一样,
我们会以加载配置文件,创建一个时钟并注册我们的更新函数作为开始。

请参考前面的教程(LINK)以获得更多信息。

 

We also
creates our walls. Actually we won't create them one by one, we'll
group them in a ChildList of a parent object.

 

我们也创建了我
们的墙。实时上我们并不是一个一个的创建,我们把它们在父对象中的一个ChildList组合起来。

 

orxObject_CreateFromConfig("Walls");

 

This
looks like we only create one object called Walls, but as we'll see in
the config file, it's actually a container that will spawn a couple of
walls.

 

这看起来我们只
是创建了一个叫做Walls的对象,但我们会在配置文件中看到,它实际上是一个会产生一堆墙的容器。

 

Lastly,
we create our boxes.

 

接着,我们创建我们的箱子。

 

for(i = 0; i <
orxConfig_GetU32("BoxNumber"); i++)

{

  orxObject_CreateFromConfig("Box");

}

 

As you
can see, we don't specify anything regarding the physics properties of
our walls or boxes, this is entirely done in the config file and is
fully data-driven.

 

如你所见,我们并没有指定任何关于墙或箱子的属性,这些都在配置文件中完成了并且完全是数据驱动的。

 

We then
register to physics events.

 

然后我们注册到物理事件。

 

orxEvent_AddHandler(orxEVENT_TYPE_PHYSICS,
EventHandler);

 

Nothing
really new here, so let's have a look directly to our EventHandler
callback.

 


里没有什么新东西,所以我们直接看下EventHandler回调函数。

 

if(_pstEvent->eID ==
orxPHYSICS_EVENT_CONTACT_ADD)

{

  orxOBJECT *pstObject1, *pstObject2;

 

 
pstObject1 = orxOBJECT(_pstEvent->hRecipient);

 
pstObject2 = orxOBJECT(_pstEvent->hSender);

 

 
orxObject_AddFX(pstObject1, "Bump");

  orxObject_AddFX(pstObject2, "Bump");

}

 

Basically
we only handle the new contact event and we add a FX called Bump on
both colliding objects. This FX will make them flash in a random color.

 

基本上我们只处
理了新的联系事件并且我们增加了一个叫做Bump的FX在两个碰撞的对象上。这个FX会使得它们闪烁一种随机的颜色。

 

Let's
now see our Update function.

 

现在看看我们的Update函数。

 

void
orxFASTCALL Update(const orxCLOCK_INFO *_pstClockInfo, void
*_pstContext)

{

  orxFLOAT fDeltaRotation = orxFLOAT_0;

 

 
if(orxInput_IsActive("RotateLeft"))

  {

    fDeltaRotation = orx2F(4.0f) *
_pstClockInfo->fDT;

  }   

  if(orxInput_IsActive("RotateRight"))

  {

   
fDeltaRotation = orx2F(-4.0f) * _pstClockInfo->fDT;

  }

 

 
if(fDeltaRotation != orxFLOAT_0)

  {

    orxVECTOR vGravity;

 

   
orxCamera_SetRotation(pstCamera, orxCamera_GetRotation(pstCamera) +
fDeltaRotation);

 

   
if(orxPhysics_GetGravity(&vGravity))

    {

     
orxVector_2DRotate(&vGravity, &vGravity, fDeltaRotation);

     
orxPhysics_SetGravity(&vGravity);

    }

  }

}

 

 

As you can see, we get the rotation
update from the RotateLeft and RotateRight inputs.

If a
rotation needs to be applied, we then update our camera with
orxCamera_SetRotation() and we update our physics simulation gravity
accordingly.

This way, our boxes will always look like they
fall toward the bottom of our screen, whichever the camera rotation is.

Note
the use of orxVector_2DRotate() so as to rotate the gravity vector.

 

如你所见,我们
从RotateLeft和RotateRight输入得到了旋转的更新。

如果需要旋转,那么我们就用orxCamera_SetRotation()
来更新摄像头并且我们更新相应的物理特性模拟的重力(矢量)。

这么做,无论摄像头怎么旋转,我们的箱子总会来看起来是向我们屏幕的底部运动。

注意使用
orxVector_2DRotate()以便旋转重力矢量。

 

NB: All rotations in orx's code are
always expressed in radians!

 

注意:orx代码中的所有的旋转总是用弧度表示!

 

Let's
now have a look at our config data. You can find more info on the config
parameters in the

body
section of config settings


.

First, we created implicitely many walls
using the ChildList property. See below how it is done.

 

现在我们看一下
我们的配置数据。你可以在

body
section of config settings


(LINK 配置设置中的body 配置段)找到更多关于配置参数的信息。

首先,我们通过
ChildList属性隐式地((译者注:原文中的implicitely应为implicitly,应该是作者笔误))创建了许多墙。实现如下:

 

[Walls]

ChildList
= Wall1 # Wall2 # Wall3 # Wall4; # Wall5 # Wall6

 

As we
can see, our Walls object is empty, it will just create Wall1, Wall2,
Wall3 and Wall4 (note the ';' ending the list there).You can remove this
';' to create 2 additional walls.

 

 

正如我们看到的,我们的墙对象为空,只是创建了Wall1、Wall2、Wall3
和Wall4 (注意 ‘;’ 在列表的结尾)。你可以移除这个 ‘;’ 新增两堵墙。

 

Let's now see how we define our walls
and their physical properties.

Let's begin with the shape we'll use for
both WallBody and BoxBody.

 

现在让我们看看怎么定义我们的墙和它们的物理属性。

让我们从我们将
要在WallBody和BoxBody中都用到的形状开始。

 

[FullBoxPart]

Type       
= box

Restitution
= 0.0

Friction   
= 1.0

SelfFlags  
= 0x0001

CheckMask   = 0xFFFF

Solid      
= true

Density    
= 1.0

 

Here we
request a part that will use a box shape with no Restitution (ie. no
bounciness) and some Friction.

We also define the SelfFlags and
CheckMask for this wall.

The first ones defines the identity
flags for this part, and the second ones define to which

idendity(应
该是 identity)

flags it'll be sensitive (ie. with who it'll
collide).

Basically, if we have two objects: Object1 and
Object2. They'll collide if the below expression is TRUE.

 

这里我们创造了
一个没有Restitution(弹性)和一点Friction(摩擦)的box (长方体)形状。

同样我们也为这
堵墙定义了 SelfFlags 和 CheckMask。

第一个(译注:SelfFlags)定义了标识标记,第二个定义了那些它会碰撞到的标
识标记


本上,如果我们有两个对象:Object1和Object2.如果以下条件为TRUE,它们就会发生碰撞。

(Object1.SeflFlags
& Object2.CheckMask) && (Object1.CheckMask &
Object2.SelfFlags)

 

NB: As we don't specify the TopLeft and
BottomRight attributes for this FullBoxPart part, it will use the full
size of the body/object that will reference it.

 

注意:我们没有
为这个FullBoxPart指定TopLeft 和 BottomRight 属性,所以它会使用所引用的body/object的完整尺寸。

 

Now we
need to define our bodies for the boxes and the walls.

 

现在我们需要为
这些箱子和墙定义我们的body。

 

[WallBody]

PartList
= FullBoxPart

 

[BoxBody]

PartList 
= FullBoxPart

Dynamic   = true

 

We can see they both use the same part
2)

. (they
can have up to 8 parts, but only 1 is used here)

As
Dynamic is set to true for the BoxBody, this object will move according
to the physics simulation.

For the WallBody, nothing is specified
for the Dynamic attribute, it'll then default to false, and walls won't
move no matter what happen to them.

NB: As there can't be any collision
between two non-dynamic (ie. static) objects, walls won't collide even
if they touch or overlap.

 

我们可以看到他们都使用相同的部分(注释2
它们最多可以使用8个部分,但这里只使用了一个)。

由于BoxBody(箱子的body)的Dynamic属性被设置为TRUE,这个对
象会根据物理特性的模拟移动。

对WallBody(墙的body),没有特别指定Dynamic属性(的值),则会默认设置为
FALSE,并且无论发生什么这些墙都不会移动。

注意:由于两个non-dynamic(即
static,译注设置Dynamic属性为FALSE或不设置)的对象之间不能发生碰撞,即便两堵墙接触或重叠它们也不会碰撞。

 

Now
that we have our bodies, let's see how we apply them to our objects.

First,
our boxes.

 

现在我们有了我们的body,让我们看看怎么把它们应用到我们的对象。

首先,我们的箱
子。

 

[Box]

Graphic  
= BoxGraphic


Position  = (50.0, 50.0, 0.0) ~ (750.0, 550.0,
0.0)


Body      = BoxBody

Scale    
= 2.0

 

As you
can see, our Box has a Body attribute set to BoxBody.

We can
also notice it's random position, which means everytime we create a new
box, it'll have a new random position in this range.

 

如你所见,我们
的箱子有一个被设置为BoxBody的Body属性。

我们也注意到它的位置随机,这意味着每一次我们创建一个新的箱子,它会有一个在这个范
围内的随机位置。

 

Let's
now see our walls.

 

现在看看我们的墙。

 

[WallTemplate]

Body =
WallBody

 

[VerticalWall@WallTemplate]

Graphic
= VerticalWallGraphic;

Scale   = @VerticalWallGraphic.Repeat;

 

[HorizontalWall@WallTemplate]

Graphic
= HorizontalWallGraphic;

Scale   = @HorizontalWallGraphic.Repeat;

 

[Wall1@VerticalWall]

Position
= (0, 24, 0)

 

[Wall2@VerticalWall]

Position
= (768, 24, 0)

 

[Wall3@HorizontalWall]

Position
= (0, -8, 0)

 

[Wall4@HorizontalWall]

Position
= (0, 568, 0)

 

[Wall5@VerticalWall]

Position
= (384, 24, 0)

 

[Wall6@HorizontalWall]

Position
= (0, 284, 0)

 

As
we can see we use inheritance once again.

First
we define a WallTemplate that contains our WallBody as a Body attribute.

We then
inherits from this section with HorizontalWall and VerticalWall. They
basically have the same physical property but a different Graphic
attribute.

Now that we have our wall templates for both
vertical and horizontal wall, we only need to specify them a bit more by
adding a position.

That's what we do with Wall1, Wall2, etc…

 

正如我们看到的
我们再一次使用到了继承。

首先我们定义一个包含设置为WallBody的Body属性的WallTemplate。

然后
HorizontalWall和VerticalWall从这个配置段继承。基本上它们除了一个不同的Graphic属性外拥有相同的物理属性。

现在我们有水平
和垂直的墙模板了,我们只需要再给它们添加一个位置。

这就是我们对Wall1、Wall2,等……所做的

 

Resources

资源

 

源代码:
08_Physics.c

配置文件


08_Physics.ini

 

 

术语:

restitution:
弹性


gravity vector :重力矢量

 

阅读全文....

Orx1.2新添功能 自定义字体及Unicode 以中文显示为例

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

讨论新闻组及文件

    在Orx1.2版本中新增了对Unicode和自定义字体的支持,至此,Orx可以支持中文的显示了。在"Uni-code to rule them all?
"一文中作者有所提及。这可是主要就是为了中国用户才添加的功能,我自然需要大力支持罗。

    首先,由于1.2版本还未发布,(本来代码已经完成了,但是据作者描述,其显卡正好坏了,买新的显卡还没有到,需要新显卡做Linux和Windows版本)所以我使用的SVN上
的版本。

    另外,对于此功能,作者已经添加了新的教程内容
,并且此教程已经有中文版本
了,欢迎大家查看学习。同时也在此感谢参与Orx WIKI翻译工作的全体兄弟。

    因为此教程,还是讲一些拉丁字符的显示,这里我依据教程内容,真正的完成中文的显示教程。不过最最郁闷的是,目前没有找到很好的支持中文的字体生成工具。也就是将汉字从TTF等格式转成点阵图的工具,这样的英文字体工具很多,但是没有找到合适的支持中文的工具。

    另外,处于效率考虑,一般的游戏引擎都是按照图片方式显示文字,这样可以与普通的游戏图片内容一起刷新,速度最快,Orx也一样。所以其实不直接支持TTF的文件的字体,而是支持图片格式的文字。

首先,我们看作者的教程10,用的图片:(因为作者用的是白色图片,为了能够显示出来,这里我进行了反色)

 

使用方式在原来文字显示的基础上学习其实很简单:(原教程内容)

在 text字段添加Font,表示需要使用自定义的字体,Font的内容为自定义字体的配置段,并且与locale相关,最后是选择的语言配置段中的字体。

[Legend1Text]

String = $Content

Font   = $LocalizedFont

自定义的配置段内容如下:

[CustomFont]

Texture = ../../data/object/penguinattack.png

CharacterList = " !""#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[/]^_`abcdefghijklmnopqrstuvwxyz{|}~�€�‚ƒ„…†‡ˆ‰Š‹Œ�Ž��‘’“”•–—˜™š›œ�žŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ"

CharacterSize = (19, 24, 0)

在Texture添加字体的图片,CharacterList添加字体图片对应的字符,CharacterSize是个vector,表示图片中每个字符的大小。就是这些新内容。

原来iarwain完成的显示效果如下:


现在来看看汉字的使用方式,如法炮制,这里其实还有个问题,因为汉字比较特殊,没有办法像作者添加的ISO字符一样,都放在一个字体文件中,但是现在的Font可以根据locale来修改。

   因为没有找到合适的工具,(这是个问题,有人找到好工具了记得告诉我),所以我自己用photoshop拼出了一个汉字的图片(借助一个在线的字体生成网 站)。。。。痛苦啊。

如下:

然后就是改配置罗,

修改教程10的部分配置如下:


E:/MyProgram/ClipboardHighlighterVersion0.2/Untitled.html


 1

 2
[Locale]

 3
LanguageList =
 English # French # Spanish # German # Finnish # Swedish # Norwegian # Chinese
 4

 5
[Chinese]

 6
Content =
  这是囧ㄏㄨ的标志
 7
Lang    =
  (Chinese)
 8
LocalizedFont =
 ChineseCustomFont
 9

10
[ChineseCustomFont]

11
Texture =
  ../../data/object/CustomChineseFont.png
12
CharacterList =
  "这是囧ㄏㄨ的标志"
13
CharacterSize =
  (72, 72, 0)

一如既往的,我也帮iarwain强调一句,不用改一句代码,直接运行原来的教程时的程序即可看到显示效果:(注意按空格切换,切换过N多语言以后,就会看到中文了)


对于此例子来讲,几乎看不到使用字体的任何好处,因为每个汉字只显示了一次,但是换换字符内容就能知道好处了。

比如:

[Chinese]

Content =
 囧ㄏㄨ的标志这是
Lang    =
 (Chinese)

LocalizedFont =
 ChineseCustomFont

 

而且,因为没有合适的工具,我在Photoshop中做出来的图其实还是有问题的。。。。。。。后面的空太长,字切的不准。。。。同时,这也反映了教程中作者提出来的问题,所有的自定义字体必须是等宽字体(不然怎么切啊?)

 

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

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

阅读全文....

【转】提高工作效率的方法

本文来自于 killman.liu 的博客 坪溪笔谈
,原文链接在:http://blog.feihoo.com/2010/04/work-effcient-thoughts.html

 

因为个人看了后很有感触,在这里向大家推荐。

 

提高工作效率的方法

最近一段时间以来,效率相当地高。 纵观前些年的工作效率中,少有这么利落快速而且不加班的。 回顾这几年,我工作效率的提高主要有下面的几个方面。

 

一、集中目标

专注目标。
凡是工作效率高的时间段里,工作目标都很明确。每天一上班,心里就想明白了今天要干什么,每天下班时,想想今天都实现了什么,有什么目标还没有实现。尽管每天早上来了先看看股票新闻,中午饭后看看股票新闻,偶尔下午还打个盹什么的,但工作的时候确实格外专注,丝毫也不会走神。

分离与当前无关的任务/问题。

专注目标不是那么容易做到的。印象中以前工作中经常会碰到的问题是工作中遇到的问题。一方面可能得益于基本功的增强,最近虽然也遇到了一些问题,但是都能够通过简单阅读或查找文档,或浏览问题相关的库的源码解决;
另一方面,遇到的问题我总是避繁就简,首先倾向于寻找简单可用可靠的方案,并将心中的疑虑记录下来,集中成一个列表,工作之外翻翻书,系统思考和学习,而不会因为这个问题而叉开思路对相关的内容研究一番。总之,专注当前的任务,把新问题记录下来,回头再专心攻克。

例如我第一次用ibatis,遇到了一些表可以用集合和映射来加强Pojo的OO功能,但是我并不熟悉ibatis的这些特性,并且使用简单的方案也是可行的,于是就直接使用简便方案(在我看来,ibatis主要是将SQL集中起来管理、简化SQL操纵,对OO不感冒)。

工作列表。
不论是开发还是设计,一个文本格式的位于源代码存储系统下的待办工作列表与IDE中的TODO项一起构成了专注目标的重要工具。工作目标分解是基本工作步骤,记录工作中产生的新的问题(任务),这样,子问题越来越多,项目中要干的事情越来越丰富。经常性地调整当前工作任务列表,根据重要性对这些任务进行划分。每天都干掉一些问题,经常想着那些最重要的问题。

简单但有迹可寻的设计材料。
在思考和工作的过程中,一方面,项目的过程通常比较长,另一方面经验相对丰富的人手头总是有多个事情在并行进行。当时间太长记不清出了或者是切换任务时,经常要查看此前的代码和设计,易于修改以及有历史记录的设计材料对于保持设计思路的完整十分有意义。(设计材料还应该包括重要的图)

正式准确可以依靠的需求文档。
这一条无论怎么说都不过分,遇到需求不确定的地方,就去查需求文档。如果没有,就请能够回答的人来回答,委托给他,先去做别的。细想在上一家公司,经常要承担多种角色,甚至连需求也经常没人给一个最终决策,设计开发的时候在模棱两可的需求之间做决定,很烦心。对于一个设计/开发人员而言,需求人员在需求问题方面就是绝对的权威。如果有些建议,可以建议,但一切均以需求/产品人员为准。

单元测试。
另一有助于集中精力编码的就是单元测试。单元测试让我集中精力实现当前的功能,需要依赖的其他功能,我总是先生成一个接口,让当前的功能通过测试后再专心去实现该接口。一天下来,一个一个的测试点亮了绿色,一个一个的接口被实现,整天都是十分惬意的。单元测试的另外一个收益就是放心地去重构吧,放心地去做新的特性吧。

二、简化问题的能力

无论是在广义的工作方法/工作态度上,还是在针对具体问题的设计/实现上,我认为最重要的个人能力就是化繁为简了。化繁为简是所有工作方法/软件设计的核心。将那些可以砍掉的工作砍掉(最多做个记录),做到尽可能地经济,尽可能地简单。

从工作方法和态度上来讲,真正需要去做的工作才值得去做,大力砍掉那些不应该在当前工作中处理的事情。例如不必要的优化,不必要的扩展性,不必要的性能,不必要的功能,可以不要的技术,不必要的流程,不必要的文档,统统砍掉,一切可以没有的全都不能有

工作中也可能遇到非关键的难题,通常绕过它们,使用更简单的方案就是了。纠缠于这些不重要的难题,最容易浪费时间。例如,eclipse忽然坏掉了,最好的方法是重装一个;
又如以前我经常自己建一个持续集成服务器,现在我直接养成每次写完代码手工跑一遍的习惯(前提是够用)。简化你的工作!简化!

从设计/实现来讲,最好的方案就是最简单直接、一眼就能看懂的方案。记得刚到一个新的环境,有一个统计任务,既要用到数据库也要用缓存,我做了精细的设计来保证最终一致性,状态流程都很完整,并且使用线程池来并发运行分批处理,最后再合并。虽然控制得很完美很精细,但是流程和结构都很复杂。上头根据此前的项目经验给出了一个更简单的例子,直接将各个任务划分为多个线程,分开存储,到了该统计的时候,冻结数据在所有的分区上做统计。状态砍掉一半,流程缩减一半。

事实上,作为简单直接的一个附带效应,最简单直接的方式,通常性能也最好。

简化问题的能力,是一个人的核心能力。

三、基本功

基本功的内容十分复杂。首先,对整个计算机体系的理解,对操作系统/虚拟机/数据库本质的理解,对语言基础类和库的理解,我觉得是核心基本功

第二项基本功,就是学习能力

通过快速阅读核心文档理解核心思想,然后其他的东西总是能从文档中查到就行。细枝末节的东西,即学即用,学过就忘可也。

第三项基本功,就是文档、资料的搜索和收集

要想在工作中如行云流水,另外一个方面就是避开暂时还不熟悉的技术和工具,不熟悉的东西很难用好,更难用顺畅。尤其是那些纠结复杂、华而不实的技术,不要去碰。这属于简化能力的范畴。

四、工具

选择工具的核心标准,就是简单朴素可信赖

文本格式的设计,加上易于修改的图。
我喜欢用一个简单的文本格式来记录设计,随时修改,随时查阅。而附上几幅简单直接的图,经常能够更简单直接地表达更多的内容。

简单的可信赖的工具。我曾经将很长的时间用来构建Maven的环境上,用Maven管理依赖,尤其是跟Eclipse协作时,经常出现诡异。现在我用ant,或者只用Maven,maven不与eclipse纠缠在一起。如果一个工具出几次诡异现象,那就干脆丢掉它。

版本管理工具。 不仅仅源码要由版本管理,整个项目过程的所有知识,全部用版本管理系统管理起来,集中存放。现在我用subversion和git。

 

阅读全文....

【转】【翻译】Orx官方教程:6.声音和音乐(sound&music)

本文译自 orx tutorials 的 声音和音乐(sound&music)
,~麽黛誌~ 译。最新版本见Orx 官方中文Wiki
。 (因为这里格式不好看,推荐去官方WIKI查看)本文转自~麽黛誌~的博客

。原文链接在:http://blog.csdn.net/v_023/archive/2010/07/06/5717261.aspx

 

声音和音乐
(sound&music) 教程

综述

参看前面的教程基础
, 对象创建
, 时钟
,
框架层次结构

动画
视口与摄像机

本教程则演示如何播放声音(样本)和音乐(流)。和先前其他功能一样,在大部分时候,只需要一行代码,一切都是数据驱动。

本教程还演示了如何通过士兵的图像作为视觉反馈,展现实时改变的声音设置。

当你按上/下箭头,声音将做出相应的改变。士兵也会因此发生变化。

通过点击左右键,音乐的音调(音频)会相应地改变。士兵将会向收音机的旋钮一样旋转。

左控制键将会在音乐停顿的时候播放音乐(同时激活士兵),并会在音乐播放的情况下暂停音乐(并停止士兵的活动)最后,回车和空格会在士兵上播放出声音效
果。

用空格触发声音效果跟用回车是一样的,唯一的区别在于空格键控制的音量和音调是随机定义在默认配置文件中的。

这种配置控制的频率随机性允许简单步骤或没有多余的代码稍有不同击中的声音。我们随意改变士兵的颜色来说明这一点。声音效果,只会增加和表现在有效士兵角
色。

如果你想表现一个声音效果不用对象来支持,你可以像本教程中创建音乐方法一样。但是,在对象上表现一个声音将需要空间声音定位(本教程不做介绍)。

许多声音效果可以同时表现在一个单一的对象上。

声音的配置属性KeepDataInCache允许保留在内存中,而不是每次都从文件中读取声音样本。这只针对非流数据(即不是音乐类型)。

如果它被设置为false,样本将从文件中重新加载,除非有另一个相同类型的声音效果正在播放。

我们也注册声音事件去得到什么时候开始或停止播放声音。这些事件仅仅在实际播放时才触发。

 

详细说明

通常,我们先载入config
file(配置文件),创建一个viewport,创建一个clock(时钟)并且注册Update(更新)函数,最后创建一个主对象。请从之前的教程中
获得更多的信息。

接下来我们来创建一个音乐对象并且播放它。

orxSOUND *

pstMusic;

pstMusic =

orxSound_CreateFromConfig(

"Music"

)
;


orxSound_Play(

pstMusic)
;

正如我们看到的,音乐和声音都属于orxSOUND类型。主要区别在于音乐是流,而声音是完全加载在内存中。

接下来,让我来看它们在设置配置文件上的差异。

初始化函数最后一步:我们添加音频事件响应。

 

orxEvent_AddHandler(orxEVENT_TYPE_SOUND, EventHandler);

 

我们只在音频开始/停止记录日志,相应代码如下:

E:/MyProgram/ClipboardHighlighterVersion0.2/Untitled.html



E:/MyProgram/ClipboardHighlighterVersion0.2/Untitled.html



orxSOUND_EVENT_PAYLOAD *pstPayload;

pstPayload = (orxSOUND_EVENT_PAYLOAD *)_pstEvent->pstPayload;

switch
(_pstEvent->eID)
{
  case
 orxSOUND_EVENT_START:
    orxLOG("Sound <
%s
>@<
%s
> has started!"
, pstPayload->zSoundName, orxObject_GetName(orxOBJECT(_pstEvent->hRecipient)));
    break
;

  case
 orxSOUND_EVENT_STOP:
    orxLOG("Sound <
%s
>@<
%s
> has stoped!"
, pstPayload->zSoundName, orxObject_GetName(orxOBJECT(_pstEvent->hRecipient)));
    break
;
}

return
 orxSTATUS_SUCCESS;


 

 

正如你所看见的,没有什么是新东西的。

现在我们来看怎样去添加一个音频到士兵角色上。

E:/MyProgram/ClipboardHighlighterVersion0.2/Untitled.html



if(orxInput_IsActive("RandomSFX") && orxInput_HasNewStatus("RandomSFX"))
{
  orxObject_AddSound(pstSoldier, "RandomBip");
  orxObject_SetColor(pstSoldier, orxColor_Set(&stColor, orxConfig_GetVector("RandomColor", &v), orxFLOAT_1));
}

if(orxInput_IsActive("DefaultSFX") && orxInput_HasNewStatus("DefaultSFX"))
{
  orxObject_AddSound(pstSoldier, "DefaultBip");
  orxObject_SetColor(pstSoldier, orxColor_Set(&stColor, &orxVECTOR_WHITE, orxFLOAT_1));
}

 

 

我们看到的是,添加一个音频到一个士兵角色上只需要一行代码,并且更为重要是随机和固定音频也是这样做的。后面我们会介绍它们在配置文件上的不同。

当我们添加一个RandomBip音频,通过配置文件中定义的key-RandomColor随机改变士兵颜色.当播放DefaultBip时,我们可以
简单地将颜色改回白色。

注意:一个声音将会在每次有对应输入的时候被播放。

到目前为止,我们只关心一个输入是否处于激活状态,现在,我们需要在输入被激活的一瞬间做一些操作。

为此,我们使用orxInput_HasNewStatus()
函数,它将在输入状态变化的时候返回orxTRUE
。(比
如从未激活到激活状态,从激活到未激活状态)

再结合 orxInput_IsActive()可以确保当我们只播放声音时,获取的输入是从非激活到激活的。

现在,让我们一起演示一下。

E:/MyProgram/ClipboardHighlighterVersion0.2/Untitled.html
 

E:/MyProgram/ClipboardHighlighterVersion0.2/Untitled.html

if
(orxInput_IsActive("ToggleMusic"
) && orxInput_HasNewStatus("ToggleMusic"
))
{
  if
(orxSound_GetStatus(pstMusic) != orxSOUND_STATUS_PLAY)
  {
    orxSound_Play(pstMusic);
    orxObject_Enable(pstSoldier, orxTRUE);
  }
  else

  {
    orxSound_Pause(pstMusic);
    orxObject_Enable(pstSoldier, orxFALSE);
  }
}


 

通过这个简单的代码可以看到,当我们ToggleMusic的输入激活时,在非播放状态下将开始音乐播放且激活士兵。播放状态下则停止音乐播放且不激活士

兵。

现在,让我们来改变音高。

E:/MyProgram/ClipboardHighlighterVersion0.2/Untitled.html



if(orxInput_IsActive("PitchUp"))
{
  orxSound_SetPitch(pstMusic, orxSound_GetPitch(pstMusic) + orx2F(0.01f));
  orxObject_SetRotation(pstSoldier, orxObject_GetRotation(pstSoldier) + orx2F(4.0f) * _pstClockInfo->fDT);
}

 

没有特别的,降低音高也是如此就是参数换成了PitchDown.

最后,我们来改变音量。

E:/MyProgram/ClipboardHighlighterVersion0.2/Untitled.html



if(orxInput_IsActive("VolumeDown"))
{
  orxSound_SetVolume(pstMusic, orxSound_GetVolume(pstMusic) - orx2F(0.05f));
  orxObject_SetScale(pstSoldier, orxVector_Mulf(&v, orxObject_GetScale(pstSoldier, &v), orx2F(0.98f)));
}

 

具体做法和改变Pitch一样,没有什么特别的。

注意:我们可以看到,只有将我们的对象的旋转时间一致(参见时钟教程
(clock tutorial)

)。

音乐的音高和声量,包括对象的缩放都将是帧相关的(framerate-dependent),这是一个不好的事情。

为了解决这个问题,我们只需要使用the clock's DT 1)

去确定参数即可。2)

我们已经了解代码部分,现在来看下数据部分。

首先,定义下音乐。

E:/MyProgram/ClipboardHighlighterVersion0.2/Untitled.html



[Music]

Music =
 ../../data/sound/gbloop.ogg
Loop  =
 true

 

很容易!如果我们没有明确地定义Loop=true,音乐就不会循环播放。

现在让我们来看看DefaultBip。

E:/MyProgram/ClipboardHighlighterVersion0.2/Untitled.html



[DefaultBip]

Sound       =
 ../../data/sound/bip.wav
KeepInCache =
 true;
Pitch       =
 1.0
Volume      =
 1.0

 

和以前一样,KeepInCache属性将确保这音频将永远不会被自动从内存中卸载。

音高和音量明确地定义为不是实际需要的默认值。

最后,让我们来看看我们的RandomBip。

E:/MyProgram/ClipboardHighlighterVersion0.2/Untitled.html


[RandomBip@DefaultBip]

Pitch   =
 0.1 ~ 3.0
Volume  =
 0.5 ~ 3.0

 

我们可以看到,RandomBip从DefaultBip继承。这意味着,如果我们改变了DefaultBip样本,它也可能改变RandomBip。

我们只需改变节距(即频率)和音量随机值。这意味着,每次播放
RandomBip,它就会有不同的频率和数量,而且,所有的这些都不需要改变代码,只需要改变配置即可!

源代码: 06_Sound.c

配置文件: 06_Sound.ini

1)

译者注:作者应该是表示clock结构的DT字段,在Orx中此结构的此字段表示时间值

2)

译者注:即将其改为时间相关

 

阅读全文....

【翻译】Orx官方教程:09.卷轴效果(scrolling)教程

本文译自 orx tutorials 的
 
卷轴效果(Scrolling)教程

,tarzan_sp译。最新版本见Orx
官方中文Wiki

,因为格式的原因,推荐所有文章都去官方的WIKI

查看,那里的格式最好。 
望有新人能够加入这个翻译者的队伍,早日将
Orx的WIKI页中文化。有兴趣的请加入qq群73063577,并与我取得联系,防止重复翻译。

 

卷轴效
果(

scrolling
)教程

 

综述


习前面的基础教程来了解更多关于对象创建,时钟控制,框架层次结构,动画,视口和摄像机,音频和音乐,特效和物理特性的基础信息。


教程说明了如何去展示一个视差卷轴。

 

正如你可以看到的,视角切换本身没有用到特别的代码。


实上,orx的默认2D 渲染插件将根据你在配置文件中怎么设置对象的属性来帮你设置好视差卷轴。

 

默认情况下,在
这个教程里,配置''AutoScroll''将被设置为'both',表示两个坐标轴方向上都有滚动。

这意味着当摄像
机移动的时候视差卷轴将在X,Y两个坐标轴上同时发生。

你可以尝试用这个值去设置x,y,甚至移除它。

 

除了
''AutoScroll''的属性外,你可以找到''DepthScale''属性(深度缩放比)。

这个属性将用来
依据对象离摄像机的距离自动的调整对象的缩放。//

摄像机视锥体越小,自动缩放将会应用的越快。

你可以尝试将对
象定位到摄像机的远&近剪裁平面来获取你感觉合适的视差卷轴和深度缩放比。

你可以通过配置文件来改变视差卷轴的速度(注释:摄像机移动的速度)。就像平时一样,
你可以实时修改它的值并且重新读取配置。

 

正如你所能看到的,我们的update代码需要简单地移动3D空间里的摄像机就行了。

 

按下方向键能把摄像机视角按照X轴和Y轴移动,按下control和alt键会让视角按照Z轴移动。

正如前面说的,
所有的视差卷轴效果将会正常发生,因为所有的对象都已近被合适地处理了。((译者注:包括缩放和位置))

在场景里移动摄
像机只需要很少的代码,而且不用去关心任何的滚动效果。((译者注:即这些效果已经自动的实现了))

你对你想要有多
少个滚动平面,以及哪些对象应该被滚动所影响有完全的控制。

 

最后一点与天空的显示有关。

正如[[frame|框架教程(frame
tutorial)]]里我们所看到的,我们把天空对象的frame设置为摄像机视角的一个子frame。

这意味着在配置
文件里对天空对象位置的设置将总是相对与摄像机位置的相对值。

换句话说,天空将总是跟随着摄像机视角。

当我们把它的深
度值设置为默认值1000时,(注释:与视锥矩形的远剪裁平面的值相同)它将会停留在背景中。

 

 

详细说明

就像平时一样,
我们通过加载配置文件,创建一个时钟,注册我们的Update函数。


后,我们创建我们的天空背景和我们所有的云对象。


回头看前面的教程来了解更多的细节。


在,让我们来看Update函数。首先,我们从配置文件得到摄像机速度,然后通过依赖
DT((译者注:指clock结构的DT字段,见下面的例子))来更新它,从而摆脱帧率依赖。

 

orxVECTOR vScrollSpeed;

 

orxConfig_SelectSection("Tutorial");

 

orxConfig_GetVector("ScrollSpeed",
&vScrollSpeed);

orxVector_Mulf(&vScrollSpeed,
&vScrollSpeed, _pstClockInfo->fDT);

 

到目前为止没有什么是真正新鲜的。//

现在我们需要根据输入改变摄像头移动的vector(向量)。//

 

if(orxInput_IsActive("CameraRight"))

{


vMove.fX += vScrollSpeed.fX;

}

if(orxInput_IsActive("CameraLeft"))

{


vMove.fX -= vScrollSpeed.fX;

}

if(orxInput_IsActive("CameraDown"))

{


vMove.fY += vScrollSpeed.fY;

}

if(orxInput_IsActive("CameraUp"))

{


vMove.fY -= vScrollSpeed.fY;

}

if(orxInput_IsActive("CameraZoomIn"))

{


vMove.fZ += vScrollSpeed.fZ;

}

if(orxInput_IsActive("CameraZoomOut"))

{


vMove.fZ -= vScrollSpeed.fZ;

 

 


后我们将这个移动(向量)应 用到摄像头上

orxCamera_SetPosition(pstCamera,
orxVector_Add(&vPosition, orxCamera_GetPosition(pstCamera,
&vPosition), &vMove));

 


如前面陈述的,我们将不需要 写一行代码来控制视差卷轴。

 


有的事将会在配置端被完成。 我们简单地在我们的3D空间里移动我们的摄像头。

 


我们看看配置数据。这是第一 块有我们自己数据的教程部分。

[Tutorial]

CloudNumber = 1000

ScrollSpeed = (300.0, 300.0, 400.0)

 

 


如你所看见的,我们用ScrollSpeed和CloudNumber两个字段来控制这个程 序。

ScrollSpeed可以在运行时修改然后重载读取配置文件(通过按
Backspace键)进行更新。

 

现在让我们看看云朵对象

[CloudGraphic]

Texture = ../../data/scenery/cloud.png

Pivot   = center

 

[Cloud]

Graphic    
= CloudGraphic

Position    = (0.0, 0.0,
100.0) ~ (3000.0, 2000.0, 500.0)

AutoScroll 
= both

DepthScale  = true

Color       = (180, 180, 180) ~ (220, 220, 220)

Alpha       = 0.0

Scale       = 1.0 ~ 1.5

FXList      = FadeIn

 

 


活视差卷轴的AutoScroll字段和DpthScale地段是非常重要的两个字段。

 


先AutoScroll字段可以使用‘x’的值,‘x’的值或者‘both’的值。


将决定针对这个对象的视差卷轴将在哪个坐标轴上发生。

 


差卷轴将根据对象的Z轴坐标(注释:它在摄像机的

视锥矩形里面的深度

被渲染。

对象在Z坐标轴上距离摄像机越近,视差卷轴进行得越快。

AutoScrol 的缺省值是none。

 

DepthScale(深度缩放比)属性将决定渲染插件是根据它的Z轴坐标来调整对象
的比例。

对象在Z轴上距离摄像机越近,它将被显示得越大。

DepathScale(深度缩放比)的缺省值是false

 

现在让我们看看我们的天空对象

[SkyGraphic]

Texture = ../../data/scenery/sky.png

Pivot   = center

 

[Sky]

Graphic      
= SkyGraphic

Scale         = (0.5,
0.004167, 1.0)

Position      = (0.0, 0.0,
1.0)

ParentCamera  = Camera

 


如你所看到的,我们为''Sky''对象设置 了一个ParentCamera(父摄像机),意味着我们的''Sky''将在摄像机的local
space中(本地空间)(注释:它将跟随着摄像机一起移动)。

 


们把天空的位置设为(0.0 , 0.0 , 1.0 ),这意味着它被置于摄像机的视角的中央,同时它也成为摄像机所能看到画面的背景。

 

当有一个ParentCamera存在的时候,Scale(缩放比)和
Position(坐标) 属性默认将在父对象的空间中表示,除非把 UseParentSpace(使用父级空间)设置为false。

 

因此把scale(缩放比)的值设置为’weird’(古怪的)。如果我们有一个对象
是由单像素 构成的,一个缩放比设置为( 1.0 , 1.0 , 1.0 )的对象将覆盖整个父级摄像机所能看到的画面。

 

因为我们的sky.png位图在X轴上的宽度是2像素,我们在X轴方向需要一个大小为
0.5的 scale(缩放比)

同样的方式,因
为它在Y坐标轴上的长度是240 像素,我们在Y轴上需要一个大小为1/240的scale(缩放比)

 

资源

 

源代码:


09_Scrolling.c

配置文件:


09_Scrolling.ini

阅读全文....