开发游戏引擎(12)

 

3. 运行游戏

以上就是游戏进行演示所需要的所有代码。唯一没有介绍的就是游戏窗体了。这可以通过该类的构造函数、OnPaintBackground方法、Paint事件(在本节的开始介绍过)来实现。此外,还要在窗体上添加一个标签,用来显示当前游戏的帧率。这需要使用一个Timer控件,并将其设置为每隔1秒更新一次。

接下来,我们要创建一个循环来驱动游戏实际运行。一个简单的方法就是在窗体上再放一个Timer控件,为了使时间间隔尽可能地小,先将其设置为1。这样可以运行,但可能不是最佳的效果,因为在前一个间隔结束和下一个间隔开始之间总会有个小的延时。

不过我们不采用这种方法,而是创建一个永久的循环,利用每个循环周期来推进游戏的运行,直到游戏窗体关闭该循环才停止,这才能使应用程序关闭。该循环在RenderLoop函数中创建,如程序清单4-14所示。

程序清单4-14  通过RenderLoop函数驱动游戏运行

/// <summary>

/// Drive the game

/// </summary>

private void RenderLoop()

{

do

{

// If we lose focus, stop rendering

if (!this.Focused)

{

System.Threading.Thread.Sleep(100);

}

else

{

// Advance the game

_game.Advance();

}

// Process pending events

Application.DoEvents();

// If our window has been closed then return without doing anything 

// more

if (_formClosed) return;

// Loop forever (or at least, until our game form is closed).

} while (true);

}

函数RenderLoop首先检查窗体是否实际获得了焦点。如果窗体被最小化了,就说明用户不在游戏中,因此不用将很多CPU时间花费到更新游戏上。如果我们探测到游戏失去了焦点,就将线程挂起1/10秒,不做其他处理。在此期间因为没有调用游戏的Advance函数,所以当游戏失去焦点时可以有效地将它暂停。

假设游戏拥有焦点,那么会调用游戏引擎的Advance函数,游戏就会得到更新。更新时会触发窗体的Paint事件,因为要调用游戏引擎中所包含的窗体的Invalidate方法。

接下来调用Application.DoEvents函数,这个调用很重要,如果少了它,游戏窗体中所有的事件都将排队等待RenderLoop函数结束后才开始执行。这也就意味着我们将无法处理任何输入事件或者其他可能发生的窗体事件,例如窗口大小调整、最小化、或者关闭等。

最后检查窗体是否已经关闭了。在.NET CF 3.5中,可以通过查看窗体的IsDisposed属性很容易地得到结果,但是,在.NET CF 2.0中没有提供该属性。为了使代码能够兼容两个版本的框架,我们采用了一种稍微不同的方式,_formClosed变量是一个类级别的变量,在窗体的Closed事件中将它设置为true。这可以触发线程退出循环。

为了启动循环绘制,我们从窗体的Load事件中调用它。在调用之前要确保窗体是可见的,并且要调用窗体的Show方法及Invalidate方法将它完全重绘。这部分代码以及对帧率计时器进行初始化所用的代码,如程序清单4-15所示。

读书导航