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

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

阅读全文....

【转】【翻译】Orx官方教程:10.独立程序和本地化 stand alone & localization


本文译自 orx tutorials 的
独立程序与本地化教程

(stand alone &
localization),六月流光 译。最新版本见Orx

官方中文Wiki

。 本文转自六月流光的
博客

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

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

StandAlone tutorial

独立程序与本地化教程(stand alone
& localization)

Summary

综述

This is our first basic C++ tutorial. It also
shows how to write a stand alone executable using orx and how to use
the localization module (orxLOCALE).

 

这是我们的第一个C++基础教程。它也展示了如何使用orx编写独立的可执行文件和使用本地化模
块(orxLOCALE)。

 

As
we are NOT using the default executable anymore for this tutorial, its
code will be directly compiled into the executable and not into an
external library.

 


于我们在此教程中不再使用默认的可执行文件,它的代码会被直接编译成可执行文件而不是外部库。

 

This implies that we
will NOT have the default hardcoded behavior we had in the previous
tutorials:

 

这暗示了我们不再有前几个教程中的如下默认行
为:(译注:内置于默认可执行文件,同时下面的几点原本也都是否定句式,但与这里的否定矛盾,原意应该是下面这些特性会在StandAlone版本中失
效)

  • F11 will not affect vertical sync toggler
  • Escape
    won't automatically exit
  • F12 won't capture a
    screenshot
  • Backspace won't reload configuration files
  • the
    [Main] section in the config file won't be used to load a plugin
    (“GameFile” key)

 

  • F11 切换垂直同步
  • Escape
    退出
  • F12 截屏
  • 退格键(Backspace)
    重新载入全部配置文件
  • 配置文件中的[Main] 配置段被用于加载一个插件(“GameFile” 键)

A program based
directly on orx

1)

, by default, will
also NOT exit     if  it receives the orxSYSTEM_EVENT_CLOSE event.To do
so, we will either have to use the helper orx_Execute() function (

see below

) or handle it
ourselves.

 

一个直接基于orx的程序(注释1,即不再依靠
ORX 启动器),默认收到orxSYSTEM_EVENT_CLOSE事件后不退出。为了改变这种情况,我们必须使用orx_Execute()
函数(如下link)或者自己处理。

 

See
previous

basic tutorials

for more info about
basic

object creation

,
clock handling

,
frames hierarchy

,
animations

,
cameras &
viewports


,

sounds &
musics


,

FXs

,
physics

and
scrolling

.

 

查看之前关于basic
object creation

,
clock handling

,
frames hierarchy

,
animations

,
cameras &
viewports


,

sounds &
musics


,

FXs

,
physics

and
scrolling

的基础教程以获得更多的信息。

 

As we're on our own
here, we need to write the main function and initialize orx manually.The
good thing is that we can then specify which modules we want to use,
and deactivates display or any other module at will, if needed.

 

我们现在只能靠自己了,所以必须自己写main
函数和手动初始化orx。


处是如果需要,我们可以任意指定要使用哪些模块,停用显示模块或其他模块。

 

If we still want a semi-automated
initialization of orx, we can use the orx_Execute() function.This
tutorial will cover the use of orx with this helper function, but you
can decide not to use it if its behavior doesn't suit your needs.

 

如果我们还是想要半自动初始化orx,我们可以
使用orx_Execute()函数。

本教程将通过这个辅助函数使用orx,但是如果它的行为不符合你的需求也可以不使用。

 

This helper function
will take care of initializing everything correctly and exiting
properly.

It
will also make sure the clock module is constantly ticked (as it's part
of orx's core) and that we exit if the orxSYSTEM_EVENT_CLOSE event is
sent.

This
event is sent when closing the windows, for example, but it can also be
sent under your own criteria (escape key pressed, for example).

 

这个辅助函数将会确保所有部分正确初始化和妥善
退出。


也将确保clock模块正常运作(作为 orx的核心部分)并且当(备注,一个模块不断被触发,不太好理解,事实上是表示clock一直在正常计时)

orxSYSTEM_EVENT_CLOSE事
件发送时退出。


事件在关闭窗口时发送,但是也可以按你的标准发送(例如按下Esc键)。

 

This code is also a basic C++ example to show
how to use orx without having to write C code.

This tutorial could
have been architectured in a better way (cutting it into pieces with
headers files, for example) but we wanted to keep a single file per
*basic* tutorial.

 


些代码同样也是一个基础的C++示例,用来演示如何通过C语言以外的语言来使用orx。

本教程本来应该用另外一种更好的方式组织起来
(例如分割成多个头文件)但是我们希望每个基础教程都只有一个文件。

 

This stand alone executable also creates a
console (as does the default orx executable), but you can have you own
console-less program if you wish.

In order to achieve that, you only need to
provide an argc/argv style parameter list that contains the executable
name.

If
you don't, the default loaded config file will be orx.ini instead of
being based on our executable name (ie. 10_StandAlone.ini).

这个独立可执行文件会创建一个终端(和默认的
orx可执行文件一样),但是如果你喜欢也可以创建一个没有终端的可执行文件。

为了实现上述目标,你只需要提供一个包含该可执行文件名的argc/argv风格的
参数表即可。


果不这样的话,默认加载的配置文件是orx.ini,取代基于可执行文件名的配置文件(例如  10_StandAlone.ini)。

 

For
visual studio

users (windows), it
can easily be achieved by writing a WinMain() function instead of
main(), and by getting the executable name (or hardcoding it, as it's
shamelessly done in this tutorial

 

对visual studio
用户(windows)而言,可以轻松通过撰写WinMain() 替代main()
函数,并且获取可执行文件的名称(或者硬编码实现,正如本教程中可耻地这么做鸟^.^) 。

 

This tutorial simply
display orx's logo and a localized legend. Press space or click left
mouse button to cycle through all the availables languages for the
legend's text.

 


教程简单的现实了orx的logo和一个本地化的说明。按空格键或者点击鼠标左键以循环显示所有可用语言的说明文本。

 

Some explanations
about core elements that you can find in this tutorial:

 

下面是一些你可以在本教程中找到的核心元素的解
释:

 

  • Run
    function: Don't put *ANY* logic code here, it's only a backbone where
    you can handle default core behaviors (tracking exit or changing locale,
    for example) or profile some stuff. As it's directly called from the
    main loop and not part of the clock system, time consistency can't be
    enforced. For all your main game execution, please create (or use an
    existing) clock and register your callback to it.

 

  • 运行
    函数(Run
    function):不要在这里放置任何逻辑代码,它只是一个处理默认核心行为(例如跟踪退出或者更改locale)或简要描述一些东西的主干。由于它直
    接从main循环中调用并且不是clock系统的的一部分,时间一致性无法被强制执行。对你所有的主游戏可执行文件,请在此创建(或者使用已有
    的)clock并且注册你的回调函数。

 

  • Event handlers: When
    an event handler returns orxSTATUS_SUCCESS, no other handler will be
    called after it for the same event. On the other hand, if
    orxSTATUS_FAILURE is returned, event processing will continue for this
    event if other handlers are listening this event type. We'll monitor
    locale events to update our legend's text when the selected language is
    changed.

 

  • 事件处理器(Event
    handlers):当一个事件响应函数返回orxSTATUS_SUCCESS后,对此事件就不会再调用其他事件响应函数。另一方面,如果返回的是
    orxSTATUS_FAILURE,事件会传递给其他正在监听这个事件的响应函数。我们将监视locale事情以便当选择的语言切换时更新我们的说明文
    本。(备注:这个翻译有点乱,主要要了解Orx的事件处理方式)

 

  • orx_Execute():
    Inits and executes orx using our self-defined functions (Init, Run and
    Exit). We can of course not use this helper and handles everything
    manually if its behavior doesn't suit our needs. You can have a look at
    the content of orx_Execute()

    2)

    to have a better idea
    on how to do this.

 

  • orx_Execute():通过我们自定义
    的函数(Init,Run和Exit)初始化和执行orx。当然,如果它不符合我们的需求,我们可以不使用这个辅助函数并且手动处理所有事情。你可以通过
    参考orx_Execute()的内容(注2,在orx.h中实现)了解怎么这样做。(备注,作者的better
    idea不是说更好的注意,而是对于how to do this的修饰,就是我们不知道时候说,I have no idea一样)

Details

详细说明

Let's start with the
includes.


我们从包含的文件开始。

 

#include "orx.h"

That's all you need to
include so as to use orx. This include works equally with a C or a C++
compiler

3)

.


    这就是你要使用orx所需要包含的唯一文件。它在C和C++(注释3,在这种情 况下预编译宏 __orxCPP__
会被自动定义)编译器下都工作良好。

Let's
now have a look at our StandAlone class that contains orx's Init(),
Run() and Exit() callbacks.

现在看下我们的StandAlone类,包含orx
Init()、Run()和Exit()回调函数。

class StandAlone

{

public:

    static orxSTATUS
orxFASTCALL EventHandler(const orxEVENT *_pstEvent);


      static
orxSTATUS orxFASTCALL Init();


      static void orxFASTCALL Exit();

      static
orxSTATUS orxFASTCALL Run();


    void SelectNextLanguage();

    StandAlone() :
m_poLogo(NULL), s32LanguageIndex(0) {};


      ~StandAlone()
{};   


private:


   
orxSTATUS InitGame();   


    Logo *m_poLogo;

    orxS32
s32LanguageIndex;


};

All
the callbacks could actually have been defined out of any class. This
is done here just to show how to do it if you need it.


We see that our
StandAlone class also contains our Logo object and an index to the
current selected language.

所有的回调函数实际上都可以定义在任何类之外。这里这么做只是演示当你需要的时候你可以这么做。
我们看到StandAlone类也包含了我们的logo对象和一个当前选中的语言的索引。

Let's now have a look
to our Logo class definition.


现在看一下Logo类的定义:

class Logo

{

   private:

    orxOBJECT
*m_pstObject;


    orxOBJECT *m_pstLegend;

    public:

    Logo();

    ~Logo();

};

Nothing fancy here, we
have a reference to an orxOBJECT that will be our logo and another one
that will be the displayed localized legend.


As you'll see we won't
use the reference at all in this executable, we just keep them so as to
show a proper cleaning when our Logo object is destroyed. If we don't
do it manually, orx will take care of it when quitting anyway.

这里没有什么特别的,我们用指针指向一个
orxOBJECT作为我们的logo,另一个用来指向显示的本地化说明。如你所见,我们在这个可执行文件里将不会使用这个可执行文件的所有引用,我们只
是保持它们以便显示在销毁Logo对象时被正确地清理。如果我们不手动做,在退出的时候orx会替我们搞定。

Let's now see its
constructor.


现在让我们看一下它的构造函数:

Logo::Logo()

{

    m_pstObject =
orxObject_CreateFromConfig("Logo");


     
orxObject_SetUserData(m_pstObject, this);


    m_pstLegend =
orxObject_CreateFromConfig("Legend");


}

As seen in the previous tutorials we
create our two objects (Logo and Legend) and we link our Logo C++ object
to its orx equivalent using orxObject_SetUserData().

用前面的教程中讲的方法,我们创建了两个对象
(Logo和Legend)并且我们通过


orxObject_SetUserData()把Logo C++对象链接到它对应的orx对象上。

Logo::~Logo()

{

     
orxObject_Delete(m_pstObject);


      orxObject_Delete(m_pstLegend);

}

Simple cleaning here
as we only delete our both objects.

这里只是简单的删除了我们的两个对象。

Let's now see our main
function.



在看看我们的main函数:

int
main(int argc, char **argv)


{

      orx_Execute(argc, argv,
StandAlone::Init, StandAlone::Run, StandAlone::Exit);


    return
EXIT_SUCCESS;


}

As
we can see, we're using the orx_Execute() helper that will initialize
and execute orx for us.


In order to do so, we need to provide it our
executable name and the command line parameters along with three
callbacks: Init(), Run() and Exit().

正如我们看见的,我们使用 orx_Execute()
辅助函数来初始化和执行orx。


这样我们需要提供我们的可执行文件名称和命令行参数和三个回调函数:Init()、Run() 和Exit()。

We will only exit from
this helper function when orx quits.

只有当orx退出时我们才从这个辅助函数退出。

Let's have a quick
glance at the console-less version for windows.


我们快速浏览一下windows的无终端版本。

#ifdef __orxMSVC__  

int WINAPI
WinMain(HINSTANCE hInstance, HINSTANCE PrevInstance,


        LPSTR
lpCmdLine, int nCmdShow) {


    // Inits and executes orx

   
orx_WinExecute(StandAlone::Init, StandAlone::Run, StandAlone::Exit);


    // Done!

    return
EXIT_SUCCESS;


}   #endif

Same as for the traditional main() version
except that we use the orx_WinExecute() helper that will compute the
correct command line parameters and use it.

4)(the ones given
as parameter don't contain the executable name which is needed to
determine the main config file name






This only works for a
console-less windows game

5)

.(which uses WinMain()
instead of main())


旧的main()
版本一样除了我们使用orx_WinExecute()辅助函数来计算正确的命令行参数并使用它(注释4,这些给出的参数并没有包含我们需要的用来决定主
配置文件名的可执行文件的名字)。


这只能在一个无命令行(终端)的windows游戏下工作。(注释5,使用WinMain()替代main())

Let's now see how our
Init() code looks like.


现在看看我们的Init() 代码是怎么样的:

orxSTATUS
StandAlone::Init()


{

      orxLOG("10_StandAlone Init() called!");

    return
soMyStandAloneGame.InitGame();


}

We simply initialize our StandAlone instance
by calling its InitGame() method.

我们简单地用StandAlone实例中的InitGame()方法来初始化。

Let's see its content.

让我们看看它的内容:

orxEvent_AddHandler(orxEVENT_TYPE_LOCALE,
EventHandler);


m_poLogo = new Logo();

std::cout << "The available
languages are:" << std::endl;


for(orxS32 i = 0; i <
orxLocale_GetLanguageCounter(); i++)


{

      std::cout << " - "
<< orxLocale_GetLanguage(i) << std::endl;


}

orxViewport_CreateFromConfig("Viewport");

We simply register a
callback to catch all the orxEVENT_TYPE_LOCALE events.


We then instanciate
our Logo object that contains both logo and legend.


We also outputs all
the available languages that have been defined in config files. We could
have used the orxLOG() macro to log as usual (on screen and in file),
but we did it the C++ way here to show some diversity.


We finish by creating
our viewport, as seen in all the previous tutorials.

我们简单地注册了一个捕捉所有
orxEVENT_TYPE_LOCALE事件的回调函数。


然后实例化所有在配置文件中定义的可用语言。我们通常可以用orxLOG()
宏来记录(在屏幕上和文件里),但是我们这里用C++的方式来实现以表示多样性。


我们通过创建一个视口来结束,就像我们在之前所有教程中做的那样。

Let's now see our
Exit() callback.


在看下我们的Exit()回调函数:

void
StandAlone::Exit()


{

    delete soMyStandAloneGame.m_poLogo;

   
soMyStandAloneGame.m_poLogo = NULL;   


    orxLOG("10_StandAlone Exit()
called!");


}

Simple Logo object
deletion here, nothing surprising.

没有什么特别的,简单地在这里销毁了Logo对象。

Now let's have a look
to our Run() callback.


让我们看下Run()回调函数:

orxSTATUS
StandAlone::Run()


{

    orxSTATUS eResult = orxSTATUS_SUCCESS;

   
if(orxInput_IsActive("CycleLanguage") &&
orxInput_HasNewStatus("CycleLanguage"))


          {

              
soMyStandAloneGame.SelectNextLanguage();


    }

   
if(orxInput_IsActive("Quit"))


          {

              
orxLOG("Quit action triggered, exiting!");


        eResult =
orxSTATUS_FAILURE;


    }

    return eResult;

}

Two things are done here.

First when the input
CycleLanguage is activated we switch to the next available language,
then when the Quit one is activated, we simply return orxSTATUS_FAILURE.


When the Run()
callback returns orxSTATUS_FAILURE orx (when used with the helper
orx_Execute()) will quit.

这里完成了两件事情。

首先我们当CycleLanguage被激活时,我们切换到下一个可用的语言,其次
如果Quit被激活,我们简单地返回orxSTATUS_FAILURE。


当Run()回调函数返回orxSTATUS_FAILURE时orx(使用
orx_Execute()辅助函数)将会退出。

Let's have a quick look to the
SelectNextLanguage() method.

让我们快速的浏览一下 SelectNextLanguage() 方法。

void
StandAlone::SelectNextLanguage()


{

s32LanguageIndex = (s32LanguageIndex ==
orxLocale_GetLanguageCounter() - 1) ? 0 : s32LanguageIndex + 1;


orxLocale_SelectLanguage(orxLocale_GetLanguage(s32LanguageIndex));

}

We basically go to the
next available language (cycling back to the beginning of the list when
we reached the last one) and selects it with the
orxLocale_SelectLanguage() function.


When doing so, all created orxTEXT
objects will be automatically updated if they use a localized string.
We'll see how to do that below in the config description.


We can also catch any
language selection as done in our EventHandler callback.

基本上我们只是移动到下一个可用的语言(如果到
最后一个则循环到列表的开头)并通过orxLocale_SelectLanguage()函数选择它。


当我们这么做时,如果使用一个本地化字符串的已
创建orxTEXT对象将会被自动更新。我们会在下面的配置描述中看到如何实现。


我们可以在EventHandler回调函数中做捕捉任意语言的选择事件。

orxSTATUS orxFASTCALL
StandAlone::EventHandler(const orxEVENT *_pstEvent)


{


 switch(_pstEvent->eID)


 {

   case orxLOCALE_EVENT_SELECT_LANGUAGE:




     orxLOCALE_EVENT_PAYLOAD *pstPayload;


     pstPayload =
(orxLOCALE_EVENT_PAYLOAD *)_pstEvent->pstPayload;



     orxLOG("Switching to '%s'.", pstPayload->zLanguage);


     break;



   default:



     break;

 }



 return
orxSTATUS_FAILURE;


}

As
you can see, we only track the orxLOCALE_EVENT_SELECT_LANGUAGE event
here so as to display which is the new selected language.

如你所见,我们只跟踪
orxLOCALE_EVENT_SELECT_LANGUAGE事件来显示新选择的语言。

We're now done with
the code part of this tutorial. Let's now have a look at the config.

现在我们完成了本教程代码的部分。让我们看看配
置吧。

First of all, as you
might have seen, we use different folder for different architectures.


In other words, the
tutorials for Mac OS X are in the /mac folder, the ones for Linux in the
/linux and so on. By default orx will look in the current folder to
find the main config file.


As we don't want to duplicate the config file
in all the architecture folders, we create a very simple one which
purpose is only to include the one that contains all the info and which
is in the parent folder.

首选,正如你已经见到的,我们使用对不同的平台使用不同的文件夹。

换而言之,Mac OS X的教程放在
/mac文件夹,Linux的教程放在/linux,以此类推。默认的情况下,orx会查找当前目录下的主配置文件。


为了避免在不同的平台下都有重复的配置文件,我
们创建了一个简单地配置文件包含了父文件夹中配置文件的全部信息。

Let's see how we do this by looking at the
content of 10_StandAlone.ini from one of the sub-folder (ie. one that is
stored in the same folder than the tutorial executable).

让我们通过其中一个子目录(即和教程可执行文件
存储位置相同的文件夹)下的10_StandAlone.ini 的内容看看怎么实现的。

@../10_StandAlone.ini@

That's all we can find
in it. As you can see in the

template files

, we can include other
config files by writing @path/to/FileToInclude@.

所有的内容如上。就像你在
配置语法说明

中看到的,我们可以通过@path/to
/FileToInclude@ 在一个配置文件中包含其他的配置文件。(译注:这里怀疑是作者的问题,template
files实际是想链接到WIKI的配置的说明上去)

Let's now have a look at the config file
which is stored in the parent folder (ie.

../10_StandAlone.ini

).

现在我们看看父文件夹中存储的配置文件(即
../10_StandAlone.ini

link):

First let's define our
display.

首先定义我们的display配置段:

[Display]

ScreenWidth   = 800

ScreenHeight  = 600

Title         = Stand
Alone/Locale Tutorial

As you can see, we're creating a window of
resolution 800×600 and define its title.

如你所见,我们将要创建一个分辨率为
800×600的窗口,并且定义了他的标题。

We now need to provide info for our viewport
and camera.

现在我们要提供视口(viewport)和摄像
头(camera)配置段的信息:

[Viewport]

Camera          =
Camera


BackgroundColor
= (20, 10, 10)




[Camera]

FrustumWidth  = @Display.ScreenWidth

FrustumHeight =
@Display.ScreenHeight


FrustumFar    = 2.0

Position      = (0.0,
0.0, -1.0)

Nothing new here as
everything was already covered in the

viewport tutorial

.

与我们在
视口教程

(viewport
tutorial)中提到的没有任何区别。

Let's now see which inputs are defined.

现在看看输入(inputs)是怎么定义的:

[Input]

SetList = MainInput



[MainInput]

KEY_ESCAPE  = Quit

KEY_SPACE   =
CycleLanguage


MOUSE_LEFT  = CycleLanguage

In the Input section, we define all our
input sets. In this tutorial we'll only use one called MainInput but we
can define as many sets as we want (for example, one for the main menu,
one for in-game, etc…).


The MainInput sets contain 3 mapping:

  • KEY_ESCAPE
    will trigger the input named Quit
  • KEY_SPACE
    and MOUSE_LEFT will both trigger the input named CycleLanguage

在Input配置段,我们定义我们所有的输入集
合。本教程中我们只会用到一个叫做MainInput的集合,但我们也定义其他任意想要使用的集合(例如,主菜单一个,游戏中一个,等等)。


MainInput集合包括3个映射:

  • KEY_ESCAPE
    会触发名为Quit的输入
  • KEY_SPACE 和 MOUSE_LEFT 都会触发名为CycleLanguage的输入

We can add as many
inputs we want in this section and bind them to keys, mouse buttons
(including wheel up/down), joystick buttons or even joystick axes.

我们可以在这个配置段加入任意多的输入集合并且
将它们绑定到按键、鼠标按钮(包括滚轮上/下)、游戏摇杆按钮甚至游戏摇杆的方向轴。

Let's now see how we
define languages that will be used by the orxLOCALE module.

现在让我们看看怎么定义将要被
orxLOCALE模块使用的语言。

[Locale]

LanguageList =
English#French#Spanish#German#Finnish#Swedish#Norwegian




[English]

Content = This is
orx's logo.


Lang
   = (English)




[French]

Content = Ceci est le logo d'orx.

Lang    = (Fran?ais)



[Spanish]

Content = Este es el
logotipo de orx.


Lang    = (Espa?ol)



[German]

Content = Das ist orx Logo.

Lang    = (Deutsch)



[Finnish]

Content = T?m? on orx
logo.


Lang
   = (Suomi)




[Swedish]

Content = Detta ?r orx logotyp.

Lang    = (Svenska)



[Norwegian]

Content = Dette er orx
logo.


Lang
   = (Norsk)

To
define languages for localization we only need to define a Locale
section and define a Language List that will contain all the languages
we need.


After
that we need to define one section per language and for every needed
keys (here Content and Lang) we set their localized text.

为了定义本地化需要的语言我们要定义一个
Locale配置段和一个包含我们所需要全部的语言的列表。

As the localization system in based on orx's
config one, we can use its inheritance capacity for easily adding new
languages to the list (in another extern file, for example), or even for
completing languages that have been partially defined.

由于本地化系统是基于orx配置部分,我们可以
使用它的继承能力来简化把语言加入列表的过程(例如在另一个外部文件中),甚至也可以完善曾经只被部分定义的语言。

Let's now see how we
defined our Logo object.

现在看看我们是怎么定义Logo对象:

[LogoGraphic]

Texture =
../../data/object/orx.png


Pivot   = center



[Logo]

Graphic   =
LogoGraphic


FXList
   = FadeIn # LoopFX # ColorCycle1


Smoothing = true

Again, everything we
can see here is already covered in the

object tutorial

.

又一次,所有的内容我们都已经在
对象教程

(object tutorial)中涵盖了。

If you're curious you
can look directly at

10_StandAlone.ini

to see which kind of
FXs we defined, but we won't cover them in detail here.

如果你对我们定义了哪些FX感到好奇,你可以直
接查看

10_StandAlone.ini

,但是我们不会在这里讨论它们的细节。

 

Next thing to check:
our Legend object.


一个要查看的是:我们的Legend对象:

[Legend]

ChildList = Legend1 # Legend2

Surprise! Actually
it's an empty object that will spawn two child objects: Legend1 and
Legend2.

奇怪吧!其实它只是一个我们由两子对象繁衍出来的空对象。

Code-wise we were
creating a single object called Legend but apparently we'll end up with
more than one object.


The same kind of technique can be used to
generated a whole group of objects, or a complete scenery for example,
without having to create them one by one code-wise.


It's even possible to
chain objects with ChildList and only create a single object in our code
and having hundreds of actual objects created.


However, we won't have
direct pointers on them, which means we won't be able to manipulate
them directly.


That being said, for all non-interactive/background object
it's usually not a problem.


Be also aware that their frames (cf.
frame tutorial

) will reflect the
hierarchy of the ChildList 'chaining'.

通过这样的编码方式,我们创建了一个叫做Legend的单独对象但显然最终将超过一
个对象。同样的技术也可以用来创建一组的对象,或者是完成一个场景,而不是一个一个创建。同样可以将对象通过ChildList串联起来并只在我们的代码
中创建一个单独的对象而同时有数百个对象被创建。


然而我们对它们不会有直接的指针,这意味着我们将不可能直接操作它们。

话虽这么说,对所有非交互/后台对象这却不是一
个问题。


同时
请注意他们的帧(参考:frame tutorial LINK)会影响ChildList ‘串联’的继承。

Ok, now let's get back
to our two object, Legend1 and Legend2.

好了,现在让我们回到Legend1
和Legend2两个对象。

[Legend1]

Graphic       =
Legend1Graphic


Position      = (0, 0.25, 0.0)

FXList        =
ColorCycle2


ParentCamera
 = Camera




[Legend2]

Graphic       =
Legend2Graphic


Position      = (0, 0.3, 0.0)

FXList        =
@Legend1


ParentCamera
 = @Legend1

They
look very basic, they're both using the same FX (ColorCyle2), they both
have a Position and each of them has its own Graphic.

它们看起来很基本,都是用了相同的
FX(ColorCyle2),它们也都有一个位置并且各自有它们的Graphic。

NB: We can also see
that we defined the ParentCamera attribute for both of them. This means
that their actual parent will become the camera and not the Legend
object in the end.


意:我们也可以看到我们为它们定义了ParentCamera属性。这意味着最终它们实际的父对象为Camera而不是Legend对象。

However Legend will
still remain their owner, which means that they'll automatically be
erased when Legend will be deleted.

然而Legend还将是它们的拥有者,这说明它们将会在Legend被销毁时自动被
销毁。

Let's now finish by
having a look at their Graphic objects.

现在让我们看一下它们的Graphic对象作为
结束。

[Legend1Text]

String = $Content



[Legend2Text]

String = $Lang



[Legend1Graphic]

Pivot = center

Text  = Legend1Text



[Legend2Graphic]

Pivot = center

Text  = Legend2Text

We can see that each
Graphic has its own Text attribute: Legend1Text and Legend2Text.


They both have a
different String.


The leading $ character indicates that we won't display a raw
text but that we'll use the content as a key for the localization
system.


So
in the end, the Legend1 object will display the localized string with
the key Content, and Legend2 the one which has the key Lang.

我们可以看到每一个Graphic都有自己的
Text属性:Legend1Text和Legend2Text。


它们都有不同的String字段。(译注:这里的String是指配置)

开头的$字符说明我们不会显示一个原始的文本但
内容作为我们将会把内容作为本地化系统的关键字。


所以在最后,Legend1对象会显示该关键字Content的本地化字符串,Legend2会
显示关键字Lang的本地化字符串。

Everytime
we will switch to another language, both orxTEXT objects (ie.
Legend1Text and Legend2Text) will have their content updated
automagically in the new selected language.



As we saw earlier, we can catch the
orxLOCALE_EVENT_SELECT_LANGUAGE event to do our own specific processing
in addition, if needed.

每一次我们会切换到另一个语言,所有的orxTEXT对象(例如Legend1Text和
Legend2Text)会根据新选择的语言自动更新它们的内容。:)


正如我们早前看到的,如果需要,我们还可以捕获
orxLOCALE_EVENT_SELECT_LANGUAGE事件来进行特定的处理。

Resources

资源

源代码:
10_StandAlone.cpp

配置文件:
10_StandAlone.ini

1)

即不再依靠ORX 启动器

2)

在orx.h中实现

3)

在这种情 况下预编译宏
__orxCPP__ 会被自动定义

4)


这些给出的参数并没有包含我们需要的用来决定主配置文件名的可执行文件的名字

5)


使用WinMain()替代main()

 

阅读全文....

【转】【翻译】Orx官方教程:07.特效(FX)

本文译自
orx tutorials

FX
胡四娃
译最新版本见Orx
官方Wiki中文教程

。转载自:胡四娃的博客
。原文链接在http://www.cppblog.com/Husiwa/archive/2010/07/07/119534.aspx

 

 

综述

这篇教程介绍了什么是特效以及
如何创建它们

特效是将曲线及其组合而成的一组数据(正弦线、三角型边、矩形或者线性),应用在不同类型的参数
中。如:

缩放
、旋转、位置、速度、颜色等。

 

特效在配置文件中设置,仅仅只
需要一行代码就可以在对象上使用这些特效。

可以有最多8条任意类型的曲线组合在一起形成一个特效。

在同一时间,可以有最多4个特
效应用于同一个对象上面。

 

特效可以使用绝对值或者相对值,这取决于配置文件中Absolute标签。

控制曲线的周期、相位、和振幅
都是允许的。

对于位置和速度特效来说,输出值可以使用对象的方向 和/或
缩放值,以相对方式应用于对象目前的状态。

这也就允许我们创造极其拉风的视觉特效。

 

除非特效已经缓存在内存中,否
则特效参数全部在配置文件中进行调整,并且使用退格键来即时重载。 (cf.通过 ''KeepInCache'' 属性来实现内存的缓存).

比如说:你不能调整正在运行的
循环特效,因为他已经在默认的配置文件中定义好了。在这个测试程序运行的时候,所有其它的特效能够被更新。

 

通常说来,随机值的使用可以给
特效带来更多的变化。

比如, 晃动方式的缩放(the wobble scale), 带颜色的闪光(the
flash color) 和 攻击式的移动(the "attack" move) 等特效就使用了少量的随机值。

 

就像显示事件一样,我们也可以
注册特效的开始播放和停止的事件。

因为循环时间是永远不会停下来的,所以对应的停止事件
(''orxFX_EVENT_STOP'')永远不会发生.

 

们也会简单的介绍一下如何一些个性数据(仅仅包
含一个布尔值的结构)添加到orxOBJECT中。((九天注:这里作者有点穿越了,需要看下面的例子才能懂,作者定义了一个仅包含一个Bool值的结构
MyObject))

在事件的回调函数中,我们通过它,在特效开始的时候为对象加锁,在结束的时候解锁。

我们使用锁是为了让
soldier(士兵)在同一时刻只有一个特效在发挥作用。

把这些东西写在这里,仅仅具有教育意义。
((九天注:本来一个对象可以同时有4个特效发
生,这里作者仅仅是告诉你怎么使用“个性数据”才这样做的,所以说仅仅具有教育意义。))


 

 

详细内容

通常,我们先载入配置文件,创
建一个时钟,然后注册更新函数,最后,创建我们的soldier和盒对象。请在之前的教程中获取更多信息。

 

注册 输入和特效事件

 

orxEvent_AddHandler(orxEVENT_TYPE_FX, EventHandler);

orxEvent_AddHandler(orxEVENT_TYPE_INPUT, EventHandler);

 

大家可以看到,在这两个事件中,我们使用了同一个回调函数
(EventHandler).

现在我们迅速的扫一眼自己的“对象”数据结构。

 

typedef
 struct
 MyObject
{
 orxBOOL bLock;

} MyObject;

接下来,看看如何用
orxObject_SetUserData()将它绑定到soldier上

 

MyObject *pstMyObject;
 
pstMyObject = orxMemory_Allocate(sizeof
(MyObject), orxMEMORY_TYPE_MAIN);
pstMyObject->bLock = orxFALSE;
 

orxObject_SetUserData(pstSoldier, pstMyObject);

 

现在看看如何在Update函数中使用特效

 

orxSTRING zSelectedFX;
 
if
(orxInput_IsActive("SelectWobble"
))
{
 zSelectedFX = "WobbleFX"
;
}
else
 if
(orxInput_IsActive("SelectCircle"
))
{
 zSelectedFX = "CircleFX"
;
}
 
[...]
 
// Soldier not locked?

if
(!((MyObject *)orxObject_GetUserData(pstSoldier))->bLock)
{
 if
(orxInput_IsActive("ApplyFX"
) && orxInput_HasNewStatus("ApplyFX"
))
 {
   orxObject_AddFX(pstSoldier, zSelectedFX);
 }

}

 

可以看到,我们通过orxObject_GetUserData()这个函数得到了
我们想要的数据,向solder里添加特效的方法跟添加声音的方法如出一辙,用的都是这个函数orxObject_AddFX()。

 

接下来,看看EventHandler这个函数

首先是输入方面,这里只展示了每次输入时哪个按
键被使用了。

 

if
(_pstEvent->eType == orxEVENT_TYPE_INPUT)
{
 if
(_pstEvent->eID == orxINPUT_EVENT_ON)
 {
   orxINPUT_EVENT_PAYLOAD *pstPayload;
 
   pstPayload = (orxINPUT_EVENT_PAYLOAD *)_pstEvent->pstPayload;
 
   if
(pstPayload->aeType[1
] != orxINPUT_TYPE_NONE)
   {
     orxLOG("[
%s
] triggered by '
%s
' + '
%s
'."
, pstPayload->zInputName, orxInput_GetBindingName(pstPayload->aeType[0
], pstPayload->aeID[0
]), orxInput_GetBindingName(pstPayload->aeType[1
], pstPayload->aeID[1
]));
   }
   else

   {
     orxLOG("[
%s
] triggered by '
%s
'."
, pstPayload->zInputName, orxInput_GetBindingName(pstPayload->aeType[0
], pstPayload->aeID[0
]));
   }
 }

}

 

正如你所见,我们通过按下的是一个单键还是一个组合键来判断展示不同的信息。

我们仅使用了两个首次输入点,因为我们知道,我
们的配置文件中没有超过两个的组合键。尽管orx支持最多四个组合键来做为一个单键。

 

orxInput_GetBindingName()
函数给了我们一个输入的文字显示。

注意:这些名称在配置文件中也绑定到了对应的按键上面。

现在来看下如何处理这个事件

 

if
(_pstEvent->eType == orxEVENT_TYPE_FX)
{
 orxFX_EVENT_PAYLOAD *pstPayload;
 orxOBJECT           *pstObject;
 
 pstPayload = _pstEvent->pstPayload;
 pstObject  = orxOBJECT(_pstEvent->hRecipient);
 
 switch
(_pstEvent->eID)
 {
   case
 orxFX_EVENT_START:
     orxLOG("FX <
%s
>@<
%s
> has started!"
, pstPayload->zFXName, orxObject_GetName(pstObject));
 
     if
(pstObject == pstSoldier)
     {
       // Locks it

       ((MyObject *)orxObject_GetUserData(pstObject))->bLock = orxTRUE;
     }
     break
;
 
   case
 orxSOUND_EVENT_STOP:
     orxLOG("FX <
%s
>@<
%s
> has stoped!"
, pstPayload->zFXName, orxObject_GetName(pstObject));
 
     if
(pstObject == pstSoldier)
     {
       // Unlocks it

       ((MyObject *)orxObject_GetUserData(pstObject))->bLock = orxFALSE;
     }
     break
;
 }
}

在soldier上的动画开始
的时候,我们用自己的数据结构来锁定它,相应的,停止的时候解锁。

 

看完了代码部分,我们再去看看配置文件。

首先看个简单的特效
:盒子上旋转的特效。

 

[RotateLoopFX]

SlotList  =
 Rotate
Loop      =
 true
 
[Rotate]

Type        =
 rotation
StartTime   =
 0.0
EndTime     =
 2.0
Curve       =
 sine
Pow         =
 2.0
StartValue  =
 0
EndValue    =
 360
 
[Box]

FXList =
 RotateLoopFX

 

看到了吧,特效是在它创建之初
直接应用在盒对象上面的,而不是在代码中。

RotateLoopFX包含仅包含一个时间段(Rotate)并且一直循环
(attribute Loop)

然后定义Rotates时间段。时间的单位都是秒,角度的单位都是度。

定义这个旋转动画的时候,我们使用了一个正弦曲
线,让他每两秒旋转360度。

 

下面看下我们的摇摆特效。

 

[WobbleFX]

SlotList =
 Wobble
 
[Wobble]

Type          =
 scale
StartTime     =
 0.0
EndTime       =
 1.0
Period        =
 0.2
Curve         =
 sine
Amplification =
 0.0
StartValue    =
 (1.0, 1.0, 1.0)

EndValue      =
 (2.0, 2.0, 1.0) ~ (6.0, 6.0, 1.0)

 

我们修改了scale属性,并赋予它一个
''StartValue''(开始值)和''EndValue''(结束值)。

他们都是用向量来表示的,如果不想使用任何
各向异性

的值(译者注:专业名
词anisotropic

(各向异性)去知道确切意思)的话,也可是使用
float类型来表示。

虽然看起来我们正在使用一个
isotropic(各向同性

)
((Z值不影响2D元素))的值,这个EndValue也 不过是一个随机值。

也就是说,它的X和Y部分可能是完全统统的随机值!

除此之外,我们使用了一个简单的周期为0.2秒
的正弦曲线,它将会播放1秒钟。

 

看到了吧,我们将Amplification(增幅)
的值设为0,这就是说,随着时间的推进,曲线的振幅会逐渐变低。

注意:默认的Amplification是1,表示不随时间变化,保持稳定,当值大于1时,振幅
就会加大;当值小于1时,振幅就会减少。

 

看看圆是如何运动的。

 

[CircleFX]

SlotList        =
 CircleX#CircleY
KeepInCache     =
 true
 
[CircleX]

Type            =
 position
StartTime       =
 0.0
EndTime         =
 1.0
Curve           =
 sine
StartValue      =
 (0.0, 0.0, 0.0)
EndValue        =
 (-50.0, 0.0, 0.0)
UseOrientation  =
 true
UseScale        =
 true
 
[CircleY@CircleX]

Phase       =
 0.25
StartValue  =
 (0.0, -25.0, 0.0)
EndValue    =
 (0.0, 25.0, 0.0)

我们使用两个时间段来控制它的位置,这样才能做
出一个圆形的运动。第一个时间段是CircleX,他将会应用在对象的X轴向的振幅。第二个时间段CircleY,会产生一个同样幅度的作用效果在Y轴
上。

如果我们不更改CircleY的相位,是不会发
生圆形的运动。

 

现在假设一个正弦曲线,在初始值
(''StartValue'')是相位0,准备增加

在相位0。25的时候,到达中间点,将会继续增加

在相位0.5的时候,到达最高值
(''EndValue''),准备下降


相位0.75的时候,回到中间点,继续下降

在相位1.0的时候,就跟相位0(''StartValue'')是一样的了

注意:这段描述正弦曲线的工作过程也同样适用于
三角形,但是却不适用于线形。


们将略过大多数其他的特效,因为那里没有什么我们不知道的新知识了。

 

但是我们还是要迅速的看一眼翻转的特效,他将会向我们展示如何翻转一个对象。就像Paper
Mario Wii((九天注:Wii上的[[wp>Paper
Mario|纸片马里奥]]是个很出名的游戏,作者的意思就是这里的flip描述的就是那个游戏里面的风格和效果))的风格.

 

[FlipFX]

SlotList =
 Flip
 
[Flip@Wobble]

EndTime       =
 0.5
Period        =
 1.0
Amplification =
 1.0

EndValue      =
 (-1.0, 1.0, 1.0)

 

看到了吧,我们很简单的使用负值完成了这个效果。

同时也注意到,我们给Period(周期)设了一个明确的值。

我们选了一个两倍于定义的正弦曲线的
Period,这样我们就只使用了正弦曲线的上升的那一半。同时,我们也将Amplification改 回了1。(在”“Wobble”“中被设为0)

 

资源

源代码:
07_FX.c

配置文件:
07_FX.ini

阅读全文....

【转】【翻译】Orx官方教程:05.视口与摄像机 (viewport & camera)

本文译自 orx tutorials 视口与摄像机
(viewport & camera)


落后的簔羽鹤译。最新版本见Orx 官方中文Wiki

本文转自落后的簔羽鹤的博客
。原文链接在:http://blog.csdn.net/wind2006/archive/2010/07/07/5717831.aspx
。望有新人能够加入这个翻译者的队伍,早日将Orx的WIKI页中文化。有兴趣的请加入qq群73063577,并与我取得联系,防止重复翻译。

 

综述

请阅读前面的基本教程
basic object creation(基本类创建),clock handling(时钟) ,fremes hierarchy(层次结构) 和
animations(动画)。

此教程显示了如何使用有多个摄像机的多视口技术。教程中将同时创建4个视口。

分别为左上角的(Viewport1),右下角的(Viewport4),它们共用一个摄像机
(Camera1),实现此功能,只需要在配置文件中配置2个视口的Camera属性,为同一个(也就是Camera1)。当我们使用鼠标的左右键旋转摄
像机(Camera1),left Control或left
Shift键+方向键进行摄像机的缩放操作,关联的两个Viewport1和Viewport4将相应的发生变化。

右上角视口(Viewport2)是基于另一个摄像机(Camrea2),此摄像机的视锥较第一
个窄,所以显示时比例是其的两倍大。在教程的程序中,我们不能通过任何操作设置此视口。

最后一个视口(Viewport3)是基于Camera3的,Camera3的配置与
Camera1完全一样。

NB:当两个视口重叠,较先创建的将显示在顶层。

最后,有一个固定不动的箱子和一个世界坐标随着鼠标实时移动的小兵,也就是说无论如何设置视口的
摄像机,无论鼠标在那个视口上移动,小兵在它所属的视口中,相对于鼠标在在屏幕中的位置移动。

在配置文件中使用随机关键字符‘~’,使的视口和基本对象的颜色和大小可以随机创建。

NB:摄像机将它的坐标/缩放尺度/旋转存放在
orxFRAME
结构中,在[[frame|frame]]教程中我们看到他们是orxFrame继承体系的一部分。另一方面Object应该置于其Camera所关联的
Viewport中。

详细说明


  通常我们需要首先载入配置文件,创建时钟和注册回调的Update函数,最后创建主要的Object信息。关于实现的详情,请联系前面的教程。


  虽然这次我们创建了4个视口,却没有什么新东西,仅仅是以下4行代码。

 

pstViewport =
orxViewport_CreateFromConfig("Viewport1");

orxViewport_CreateFromConfig("Viewport2");

orxViewport_CreateFromConfig("Viewport3");

orxViewport_CreateFromConfig("Viewport4");

 

正如你所看到的,我们只使用了
Viewport1的引用,以便后面进行操作。

让我们直接跳到Update函数的代码。

首先我们通过捕捉鼠标的坐标,
设置士兵的位置。我们已经在frame
tutorial里实现过了。这里我们做了一样的事情,但在4个视口中工作的都很完美。当鼠标离开视口时,世界坐标的指针,将被orxNull值所代替,
也就不会触发士兵的移动了。

orxVECTOR vPos;

 

if(orxRender_GetWorldPosition(orxMouse_GetPosition(&vPos),
&vPos) != orxNULL)

{

 orxVECTOR
vSoldierPos;

 


 orxObject_GetWorldPosition(pstSoldier, &vSoldierPos);

 vPos.fZ =
vSoldierPos.fZ;

 

 orxObject_SetPosition(pstSoldier,
&vPos);

}

在操作视口之前,我们先关注下视口所关联的摄像机,我们可以移动,旋转和缩放它。获
取摄像机的代码如下所示:

pstCamera =
orxViewport_GetCamera(pstViewport);

非常简单。让我们实现旋转。

((其他方向仅仅只有部分代码,但是逻辑是一样的)).

if(orxInput_IsActive("CameraRotateLeft"))

{


 orxCamera_SetRotation(pstCamera, orxCamera_GetRotation(pstCamera) +
orx2F(-4.0f) * _pstClockInfo->fDT);

}

我们再次看到旋转的角度时间并
不依赖于FPS而且可以做时间的伸缩控制(译者注:比代码依然很简单。

现在让们来看看视口的数据配置情况。如慢动作)
 因为用的是时钟的DT。


们再次看到旋转的角度时间并不依赖于FPS而是时钟的DT。我们也可以通过设置System这个配置选项来设置旋转速度,而不是使用硬编码。

实现缩放如下:

if(orxInput_IsActive("CameraZoomIn"))

{


 orxCamera_SetZoom(pstCamera, orxCamera_GetZoom(pstCamera) *
orx2F(1.02f));

}

 

因为这个代码没有使用时钟信息,所以他将会被时钟频率和帧率所影响。

最后让我们移动摄像机。

orxCamera_GetPosition(pstCamera,
&vPos);

 

if(orxInput_IsActive("CameraRight"))

{

 vPos.fX +=
orx2F(500) * _pstClockInfo->fDT;

}

 

orxCamera_SetPosition(pstCamera,
&vPos);


了,与摄像机有关的先到这里吧。

在下面的配置中我们将看到,同一个摄像机被连接到两个不同的视口。操作摄像机将同时
影响两个视口。

我们可以直接修改视口的位置和尺寸,如下所示:

orxFLOAT
fWidth, fHeight, fX, fY;

 

orxViewport_GetRelativeSize(pstViewport,
&fWidth, &fHeight);

 

if(orxInput_IsActive("ViewportScaleUp"))

{

 fWidth *=
orx2F(1.02f);

 fHeight*= orx2F(1.02f);

}

 

orxViewport_SetRelativeSize(pstViewport,
fWidth, fHeight);

 

orxViewport_GetPosition(pstViewport,
&fX, &fY);

 

if(orxInput_IsActive("ViewportRight"))

{

 fX +=
orx2F(500) * _pstClockInfo->fDT;

}

 

orxViewport_SetPosition(pstViewport,
fX, fY);

 

如上 所示,没有什么惊奇的,非常简单。

让我们来接着看看
viewport的配置方面的东西。

 

[Viewport1]

Camera
           = Camera1

RelativeSize      = (0.5, 0.5, 0.0)

RelativePosition
 = top left

BackgroundColor   = (0, 100, 0) ~ (0, 255, 0)

 

[Viewport2]

Camera
           = Camera2

RelativeSize      = @Viewport1

RelativePosition
 = top right

BackgroundColor   = (100, 0, 0) ~ (255, 0, 0)

 

[Viewport3]

Camera
           = Camera3

RelativeSize      = @Viewport1

RelativePosition
 = bottom left

BackgroundColor   = (0, 0, 100) ~ (0, 0, 255)

 

[Viewport4]

Camera
           = @Viewport1

RelativeSize      = @Viewport1

RelativePosition
 = bottom right

BackgroundColor   = (255, 255, 0)#(0, 255,
255)#(255, 0, 255)

 

一共有3个摄像机,它们关联了4个视口,其中Camera1关联了
Viewport1和Viewport4。

我们注意到Viewport1的配置文件中relativeSize设置为
(0.5,0.5,0).它代表的意思在x轴和y轴方向上分别使用一半的显示尺寸(z轴被忽略)。也就是说,任何一个视口实际上显示部分的内容是可调的,
可以是全屏或者非全屏。

接下来我们注意到其他视口的RelativeSize属性被设置成@Viewport1。它的意
思是RelativeSize属性继承Viewport1的RelativeSize属性,也就是说它们的RelativeSize属性和
Viewport1的RelativeSize属性一样。我们也可以看到Viewport4的Camera属性被设置成@Viewport1,表明它继承
自Viewport1的摄像机。

为了避免视口在屏幕中互相重叠遮盖,我们可以设置RelativePosition属性为常量字
符或者使用vector(向量)设置它们的合理位置。

最后前三个视口使用随机的红色作为背景颜色,设置如下:

BackgroundColor
= (200, 0, 0) ~ (255, 0, 0)

如果我们希望通过准确的随机颜色进行设置,可以使用一下列表的形式设置,随机的颜色
分别为黄、青和品红,设置如下:

BackgroundColor = (255, 255, 0)#(0, 255,
255)#(255, 0, 255)

这种使用方式是相当于在三个颜色(黄色,蓝绿色,品红)中进行随机。

最后让我们关注摄像机的设置。

[Camera1]

FrustumWidth
 = @Display.ScreenWidth

FrustumHeight = @Display.ScreenHeight

FrustumFar
   = 1.0

FrustumNear   = 0.0

Position
     = (0.0, 0.0, -1.0)

 

[Camera2]

FrustumWidth
 = 400.0

FrustumHeight = 300.0

FrustumFar
   = 1.0

FrustumNear   = 0.0

Position
     = (0.0, 0.0, -1.0)

 

[Camera3@Camera1]


基本属性frustum(视
锥),被摄像机所拍摄的世界空间的一部分,将被映射到视口显示。

NB:使用2D摄像机视锥的形状是
长方体

我们可以发现Camera3完
全继承自Camera1,它没有覆盖Camera1的任何属性。

NB:使用完全继承所有属性可以写成:
[MySection@ParentSection]。

为什么实用两个不同的摄像头呢?仅仅因为可以有
两个不同的物理实体(physical entities):我们在代码中修改了Camera1的属性,而 Camara3将保持不变。

我们注意到Camera1的
FrustumWidth和FrustumHeight属性继承自Display的屏幕设置。

NB: 当继承某个属性,可以写成MyKey =
@ParentSection.ParentKey.当两个key一样时,其中父选关键字可以省略如:SameKey =
@ParentSection.

最后我们注意到Camera2具有较小的视锥。

也就是说Camera2只能看
到世界空间的较小部分。所以视口看起来具有了放大的效果。

 

资源

源代码: 
05_Viewport.c

配置文件

: 
05_Viewport.ini

1) very useful
for making HUD & UI, for example

在HUD和UI中很有用

2) 
其他方向仅仅只有部分代码,但是逻辑是一样的

3) composed of
keywords top, bottom, center, right and left

由关键字
top,bottom,center,right和left组成

4) the '~' character is used as a
random operator between two numeric values

'~'
字符被用在两个数字之间,作为随机操作符

阅读全文....