0.前言
我想通过编写一个完整的游戏程序方式引导读者体验程序设计的全过程。我将采用多种方式编写具有相同效果的应用程序,并通过不同方式形成的代码和实现方法的对比来理解程序开发更深层的知识。
了解我编写教程的思路,请参阅体现我最初想法的那篇文章中的“1.编程计划”和“2.已经编写完成的文章(目录)”:
学习编程从游戏开始——编程计划(目录) - lexyao - 博客园
我已经在下面这篇文章中介绍了使用LCL和FCL组件构建一个项目(pTetris)的过程,后续的使用Lazarus的文章中使用的例子都是以向这个项目添加新功能的方式表述的:
在Lazarus下的Free Pascal编程教程——用向导创建一个使用使用LCL和FCL组件的项目(pTetris) - lexyao - 博客园
在上一篇文章中我们介绍了使用布局组件构建应用程序界面。在界面中用到的组件都是可以看得见的,所以叫做可是组件。在这一篇文章中,我们将会用到另一类组件,一种看不到却在背后默默工作的组件,也叫做非可视组件。
俄罗斯方块游戏中的方块除了游戏玩家有鼠标或者键盘操作的移动、旋转之外,还有有一个不断下落的动作。这个下落的动作是用计时器操作的。在pTetris项目中,有一个叫做TTimer的非可视组件,这就是计时器组件。
在这篇文章里,我主要讲述以下几个方面的内容:
- 计时器组件TTimer简介
- 向pTetris项目添加一个计时器组件
- 计时器组件使用示例
- 结束语
了解不同方式实现同样效果的差异,请阅读以下文章:
1.计时器组件TTimer简介
每个操作系统都有计时器组件。在Lazarus中计时器组件是TTimer。
TTimer 是 Component Palette 的 System 选项卡上的组件,它提供通常具有毫秒分辨率的计时器。它从 TCustomTimer 继承其大部分属性。它在 ExtCtrls 单元中定义。
以下是Lazarus Wiki和帮助文件中介绍TTimer的网址链接。在看本文下面的内容之前,希望能先看一看Lazarus的介绍。
- TTimer:帮助文件中的TTimer组件
- TCustomTimer:TTimer组件的父类
- TTimer - Free Pascal wiki:wiki中TTimer的示例
从TTimer的帮助文件中,我们看到它定义了以下成员:
属性 | Enabled: Boolean; | 指示计时器是否已准备好启动。 |
属性 | Interval: Cardinal; | 计时器通知的时间间隔 (以毫秒为单位)。 |
事件 | OnTimer: TNotifyEvent; |
事件处理程序在计时器的 Interval 已过时发出信号。 |
事件 | OnStartTimer: TNotifyEvent; | 事件处理程序在计时器启动时发出信号。 |
事件 | OnStopTimer: TNotifyEvent; | 用于停止计时器的事件处理程序。 |
2.向pTetris项目添加一个计时器组件
在Lazaus中向应用程序添加计时器,跟添加其他的可视组件一样,只要在组件面板中双击TTimer图标,一个计时器组件就添加添加到窗体设计器的应用程序的界面中了。
TTimer虽然在运行时看不到,但设计时在窗体设计器中还是能看到它的图标的。这个图标只是表示计时器的存在,它的位置不影响它的使用。通常我们只是为了使用方便把它放到一个自己认为合适的地方。比如:在pTetris项目中我们是用它来控制pnBox中移动的方块的,我们就把它拖放到pnBox上。
在pTetris项目中,我们添加一个TTimer组件,Name属性自动命名为Timer1。记住这个名字,在以后的文章中还会用到。
3.计时器组件使用示例
为了演示计时器的使用,我们添加一个使用计时器事件移动方块的代码。
首先,打开pTetris项目,向主窗口的pnBox中添加一个TPanel组件,Name属性自动设置为Panel1。在属性列表中设置Panel1的属性:
- 设置Color为clRed,面板表面变为红色
- 设置Height、Width为17,变成一个17x17的小方块
然后,为计时器组件Timer1添加事件处理代码。在组件列表或窗体设计器中双击Timer1,会看到代码编辑器中添加给TfrmMain添加了一个成员函数。在定义中添加了以下代码:
procedure Timer1Timer(Sender: TObject);
在实现区域添加了以下代码:
procedure TfrmMain.Timer1Timer(Sender: TObject); beginend;
我只需要在实现区域添加定时器动作需要的代码就行了。作为一个演示,在这里我们添加的代码是让小方块Panel1在pnBox中移动,与宝玉道边框后反弹。代码如下:
procedure TfrmMain.Timer1Timer(Sender: TObject); varxx,yy:integer; beginif dx=0 then dx:=1;if dy=0 then dy:=1;xx:=Panel1.Left+Panel1.Width*dx;yy:=Panel1.Top+Panel1.Height*dy;if xx<0 thenbegindx:=-dx;xx:=0;endelse if xx>=pnBox.Width-Panel1.Width thenbegindx:=-dx;xx:=pnBox.Width-Panel1.Width;end;if yy<0 thenbegindy:=-dy;yy:=0;endelse if yy>=pnBox.Height-Panel1.Height thenbegindy:=-dy;yy:=pnBox.Height-Panel1.Height;end;Panel1.Left:=xx;Panel1.Top:=yy; end;
编译运行pTetris项目,查看运行的效果。这时我们看到pnBox中有一个红色的小方块一直不停地跳动,每隔1秒跳动一次,沿着斜线前进,遇到边框就按45°夹角反弹后继续移动。只要程序不关闭窗口,它会一直跳动。
关闭pTetris项目的运行窗口,在设计器中选中Timer1,在属性列表中修改属性Interval的值,然后重新编译运行,查看有什么变化。
Interval是计时器通知的时间间隔,也就是方块跳动的时间间隔。它是以毫秒计算的。把这个值设置的越小,方块跳动得就越快。
现在看到了方块跳动,也有了改变方块跳动速度的方法,美中不足的是方块一直跳动,就是不停下来。
下面我添加一个新的功能:通过点击pnBox下面的“暂停”按钮,让方块按着我们的意愿停止跳动或者继续跳动。为此,我需要添加一个事件处理程序。
在窗体设计器中双击“暂停”按钮,代码窗口中添加了点击在暂停按钮的事件处理程序,我们添加如下代码:
procedure TfrmMain.btnPauseClick(Sender: TObject); beginTimer1.Enabled:=not Timer1.Enabled; end;
在代码中我们设置Timer1的Enabled属性,让程序运行时,如果方块移动时点击暂停按钮,方块就停止跳动,方块静止时点击暂停按钮,方块又开始跳动。
虽然编译运行后看到了预期的效果,但总是感觉有些别扭:点击“暂停”让方块动起来。如果在方块静止的时候暂停按钮的文字变成“恢复”,我们点击“恢复”让方块恢复跳动,这样是不是更好呢?
为了达到这样的效果,我们再给Timer1添加两个事件处理程序。
在组件列表或窗体设计器中选中Timer1,再点击属性列表中的“事件”,显示出Timer1可用的事件列表。双击前两个事件,在代码中添加两个事件处理函数,然后再添加如下代码:
procedure TfrmMain.Timer1StartTimer(Sender: TObject); beginbtnPause.Caption:='暂停' ; end;procedure TfrmMain.Timer1StopTimer(Sender: TObject); beginbtnPause.Caption:='恢复'; end;
编译运行,这时我们看到:方块开始跳动时btnPause按钮的标题是“暂停”,方块静止时btnPause按钮的标题是“恢复”。这样用起来就没有困惑了。
在这里也看到了添加事件处理程序的操作方法的不同:
添加Timer1Timer、btnPauseClick时我们时通过双击组件添加的,这是因为他们是组件的默认事件。
添加Timer1StartTimer、Timer1StopTimer是我们是在事件列表中添加的,添加非默认事件都是要这样操作。
4.结束语
在这一篇文章里,我们了解了在Lazarus中使用计时器组件的方法,也了解了添加默认事件和非默认事件处理程序的差别。
编写事件处理程序是图形界面操作系统编程的一个重要的内容。在编写事件处理程序的时候要记住一点:需要长时间操作的事件处理会导致程序停止动作,等待事件处理程序执行完成。在此期间程序就像卡死了一样。为了避免这种假死现象,让操作着感到困惑,通常有两种方案:
- 显示一个能显示动作进度的界面提示,让操作者指导事情还没有做完,请耐心等待
- 把耗时很长的操作放入另一个线程中去完成,这样界面就不会卡死了