- 注册时间
- 2004-8-29
- 最后登录
- 2005-4-1
⑥精研
- 积分
- 1030
|
楼主 |
发表于 2004-8-29 15:24:36
|
显示全部楼层
图1.4是一个简化的游戏循环结构,下面对图中每个部分作些说明。
图1.4 一般的游戏循环结构
第一步:初始化
在这一步中,游戏程序执行标准初始化操作,如内存分配、资源采集、从磁盘载入数据等等。
第二步:进入游戏循环
在这一步中,代码运行到游戏主循环体内部。此时各种操作开始运行,运行持续到用户退出主循环为止。
第三步:获得玩家的输入信息
在这一步里,游戏玩家的输入信息被处理和/或缓存,以备下一步人工智能和游戏逻辑使用。
第四步:执行人工智能和游戏逻辑
这一部分包括游戏代码的主体部分,诸如执行人工智能、物理系统和一般游戏逻辑,其结果用于渲染下一帧图像。
第五步:渲染下一帧图像
在这一步中,玩家的输入和第四步中游戏人工智能和游戏逻辑执行的结果,被用来产生游戏的下一帧动画。这个图像通常放在不可见的缓存区(offscreen buffer area)内,因此玩家不会看到它逐渐被渲染的过程。随后该图像被迅速拷贝到显示存储器中并显示出来。
第六步:同步显示
通常由于游戏复杂程度的不同,游戏在计算机上运行的速度会时快时慢。比如,如果屏幕上有1000 个物体在动作,CPU 的负载就比只有10 个对象时重得多。从而游戏的画面刷新率(帧速率,frame rate)也会时高时低,而这是难以接受的。因此必须把游戏按照某个最大帧速率进行同步,并使用定时功能和/或等待函数来维持同步。一般来讲能达到30 帧/秒的帧速率就非常好了。
第七步:循环
这一步非常简单,只需返回到游戏循环的入口并重新执行上述全部步骤。
第八步:关闭
这一步是游戏的结束,表示将退出主程序或游戏循环,并回到操作系统。然而,在用户进行结束之前,用户必须释放所有的资源并清理系统,这些释放操作对任何其他软件也是同样要做的。
读者可能对实际游戏循环中的众多细节还有疑问。诚然,上面进行的解释有点过于简单化,但是它突出了如何进行游戏编程的重点。在大多数情况下,游戏循环是一个含有大量状态的FSM(Finite State Machine,有限状态自动机)。清单1.1 是更详细的一个版本,基本接近游戏循环的实际C/C++代码了。
程序清单1.1 一个简单的游戏事件循环
// defines for game loop states
#define GAME_INIT // the game is initializing
#define GAME_MENU // the game is in the menu mode
#define GAME_STARTING // the game is about to run
#define GAME_RUN // the game is now running
#define GAME_RESTART // the game is going to restart
#define GAME_EXIT // the game is exiting
// game globals
int game_state = GAME_INIT; // start off in this state
int error = 0; // used to send errors back to OS
// main begins here
void main()
{
// implementation of main game loop
while (game_state!=GAME_EXIT)
{
// what state is game loop in
switch(game_state)
{
case GAME_INIT: // the game is initializing
{
// allocate all memory and resources
Init();
// move to menu state
game_state = GAME_MENU;
} break;
case GAME_MENU: // the game is in the menu mode
{
// call the main menu function and let it switch states
game_state = Menu();
// note: we could force a RUN state here
} break;
case GAME_STARTING: // the game is about to run
{
// this state is optional, but usually used to
// set things up right before the game is run
// you might do a little more housekeeping here
Setup_For_Run();
// switch to run state
game_state = GAME_RUN;
} break;
case GAME_RUN: // the game is now running
{
// this section contains the entire game logic loop
// clear the display
Clear();
// get the input
Get_Input();
// perform logic and ai
Do_Logic();
// display the next frame of animation
Render_Frame();
// synchronize the display
Wait();
// the only way that state can be changed is
// thru user interaction in the
// input section or by maybe losing the game.
} break;
case GAME_RESTART: // the game is restarting
{
// this section is a cleanup state used to
// fix up any loose ends before
// running again
Fixup();
// switch states back to the menu
game_state = GAME_MENU;
} break;
case GAME_EXIT: // the game is exiting
{
// if the game is in this state then
// it\'s time to bail, kill everything
// and cross your fingers
Release_And_Cleanup();
// set the error word to whatever
error = 0;
// note: we don\'t have to switch states
// since we are already in this state
// on the next loop iteration the code
// will fall out of the main while and
// exit back to the OS
} break;
default: break;
} // end switch
} // end while
// return error code to operating system
return(error);
} // end main
尽管清单1.1 还没有任何具体功能,但研究其游戏循环有助于理解整个游戏的结构。所有游戏循环或多或少地都是按照这个结构设计的。图1.5 表示了游戏循环逻辑的状态转换图。显然,状态转换是非常连贯的。
图1.5 一个游戏循环的状态转换图
关于游戏循环和有限状态自动机的内容将在本章最后涉及FreakOut 演示游戏的章节中再进行更详细的讨论。
常规游戏编程指导规范
下面讨论一下游戏编程常用的技术,和你应该掌握并运用的基本原理。这会让游戏编程轻松一些。
一句话,视频游戏是超高性能的计算机程序。你不应当在对运行时间或内存要求特别严格的代码段中使用高层API。特别是与游戏内循环有关的代码大都需要手工编写,否则游戏多半会碰到严重的速度和性能问题。当然,这并不意味着就不能信任DirectX 等API,因为DirectX的设计目的就是兼顾高性能和“thin”的原则。但就通常来讲,应当避免频繁调用高层的函数。
除上述情况应多加注意外,在编程时还应留意下面所列的编程技巧。
技巧
不要怕使用全局变量。许多视频游戏不让对时间要求严格的函数使用参数,而是使用一些全局变量来传递参数,例如一个函数的代码如下:
void Plot(int x, int y, int color)
{
// plots a pixel on the screen
video_buffer[x + y*MEMORY_PITCH] = color;
} // end Plot
由于参数要被压栈和出栈,执行这个函数体所需的时间小于调用函数所需的时间。在这种情况下,更好的方法可以是设立一些全局变量,然后在调用前进行赋值以传递参数,如下:
int gx,gy,gz,gcolor; // define some globals
void Plot_G(void)
{
// plot a pixel using globals
video_buffer[gx + gy*MEMORY_PITCH] = gcolor;
} // end Plot_G
技巧
使用内联函数。通过使用inline指示符来完全摆脱函数调用,你甚至能够改进上一条技巧。Inline指示符指示编译器用函数体代码去替换函数调用。这样做无疑会使编译后的程序变得更大,但却有效地提高了运行速度。下面举一个例子:
inline void Plot_I(int x, int y, int color)
{
// plots a pixel on the screen
video_buffer[x + y*MEMORY_PITCH] = color;
} // end Plot_I
注意这里并没有使用全局变量,因为编辑器有效地执行了同类型的数据别名。但是全局变量还是很有用的,尤其是如果函数调用时只有一至两个参数改变了值的情况——其余旧的值无须重新加载就可被使用。
技巧
尽量使用32 位变量而不用8 位变量或16 变量。Pentium以及更新的中央处理器都是全32 位的,这就意味着它们并不喜欢8 位或16 位的数据字。实际上,由于高速缓存和其他相关内存储器的寻址变得较不规则,较小的数据可能会使速度下降。例如,你定义了一个如下所示的结构类型:
struct CPOINT
{
short x,y;
unsigned char c;
} // end CPOINT
注意,定义这个结构看上去不错,但实际并非如此!首先,结构本身是一个5字节长的结构—— (2*sizeof(short) + sizeof(char)) = 5字节。这太糟了,由于没有注意字节对齐,内存寻址的时候会出大问题。更好的结构形式如下:
struct CPOINT
{
int x,y;
int c;
} // end CPOINT |
|