0.前言
我想通过编写一个完整的游戏程序方式引导读者体验程序设计的全过程。我将采用多种方式编写具有相同效果的应用程序,并通过不同方式形成的代码和实现方法的对比来理解程序开发更深层的知识。
了解我编写教程的思路,请参阅体现我最初想法的那篇文章中的“1.编程计划”和“2.已经编写完成的文章(目录)”:
学习编程从游戏开始——编程计划(目录) - lexyao - 博客园
我已经在下面这篇文章中介绍了使用LCL和FCL组件构建一个项目(pTetris)的过程,后续的使用Lazarus的文章中使用的例子都是以向这个项目添加新功能的方式表述的:
在Lazarus下的Free Pascal编程教程——用向导创建一个使用LCL和FCL组件的项目(pTetris) - lexyao - 博客园
在前面写的文章中我们已经构建了pTetris项目的框架,并逐步添加了一些功能,作为示例的应用程序俄罗斯方块游戏已经达到了可玩的程度,并提供了丰富的操作方法。在这篇文章中我们将通过示例讲述更多组件的使用方法。
俄罗斯方块游戏中操作方块是核心。在前面的示例中我们已经让作为示例的应用程序俄罗斯方块游戏已经达到了可玩的程度,并提供了丰富的操作方法,在这篇文章中我们将增加定制游戏计分方法、加速算法和难度设置的功能,而在设置的操作界面中加入了能够识别当前情景的智能感知能力。
在这篇文章里,我主要讲述以下几个方面的内容:
- 应用程序定制选项设计的一般方法
- 场景智能感知及其在应用程序界面设计中的应用
- 今天的布局中使用的主要组件介绍
- 给pTetris项目添加定制游戏计分的方法
- 给pTetris项目添加定制加速规则的方法
- 给pTetris项目添加定制起始难度的方法
- 结束语
1.应用程序定制选项设计的一般方法
应用程序具有可定制性已经成为一种潮流。之所以有可定制性,是为了让应用程序具有更强的适应性。一方面是为了功能性的需要,比如软件开发环境中的编译选项的定制;另一方面是适应不同人的需要,比如软件开发环境中的文本格式、快捷键定制等;当然还有更多的需要定制的理由。
作为一种网络竞技游戏,可能由设计者定制规则,使用者只能能者规则玩游戏,这样可以保证竞技的公平公正性。
作为一种单机版的游戏,游戏的过程是人机对战,可变的规则让用户有更多的选择,从而增加游戏的趣味性。比如,一般的动作游戏中,用户都可以选择不同的角色,设置不同的难度。
我们作为示例的pTetris项目策划了丰富的可定制选项,设置这些选项的目的一方面是为了增加游戏的趣味性,另一方面是在设置这些选项的时候需要用到更多的组件。我们通过编写设置这些选项的界面可以演示更多组件的使用方法。
在早期的应用程序中,通常是每设置一个选项,都会从菜单打开一格独特的界面,用户在这个界面中输入或者选择选项的值。
而现在的应用程序中,这种独立的操作界面已经很少见了,基本上都是综合性的设置界面:
- 在一个综合设置窗口中包含几乎所有的可定制选项
- 选项按类别分成若干页面
- 每个页面有尽可能少的选项,让界面更加清晰
- 从菜单或弹出菜单中可能有多个进入选项的入口,每个入口打开所需要的综合选项窗口中的特定页面而不是一个特定的窗口
以下插图是Lazarus的IDE选项窗口,充分体现了设置窗口的这个几个特点。
我们的pTetris项目的配置表没有使用独立的窗口只是为了演示布局的需要,在一个主界面中使用尽可能多的组件,即使如此,我们也把配置表分成了若干页面,在每个页面中尽可能按不同的类型分组,让用户比较容易看懂。
2.场景智能感知及其在应用程序界面设计中的应用
所谓场景智能感知是指应用程序界面中的依赖于相关操作或者选项的操作项目仅在需要的时候显示或者可用,而不需要的时候则隐藏或者变成不可用。场景智能感知是应用程序界面设计中常用的一种手法,它可以让操作界面变得简洁易懂,避免用户面对众多的选项不知所措或者误操作。在前面的文章中我们曾经讲到和使用过Lazarus菜单设计器中的操作按钮就是这样的例子。
选项分页设置和场景智能感知相结合,可在应用程序界面变得更加简洁、易懂,基本可以排除用户不知所措和误操作的情况发生。
3.今天的布局中使用的主要组件介绍
在开始布局之前,先了解今天布局中要用到的组件。
3.1 TTrackBar 组件:
在今天的界面布局中我们将使用TTrackBar 作为在一个范围内做数值选择的组件。
TTrackBar 帮助文档的网址是TTrackBar
在以前的布局中我们曾经用到过这个组件,只是没有仔细讲解。
以下是摘录的TTrackBar - Free Pascal wiki中的介绍:
TTrackBar 是 Component Palette 的 Common Controls 选项卡上的一个组件,它显示跟踪栏控件。 TTrackBar 包含一个滑块,有时还包含刻度线。TTrackBar 是类似于 TScrollBar 的可滚动控件。
TTrackBar 的position属性
表示 TrackBar 控件的当前值。 min和
max
属性用于设置 TrackBar 的最小和最大限制。
orientation属性确定 TrackBar 的方向是水平轴还是垂直轴。 相应的值为 trHorizontal或trVertical 。
frequency属性定义沿滑块的刻度线之间的距离。 默认情况下,frequency设置为 1。 例如,如果 TrackBar 的min和
max
值为 0 和 20。 设置frequency为 2 表示从 0 到 20 需要 10 个步骤。例如,当按下 键Pgup
当 TrackBar 控件值发生更改时,将触发onChanged事件。 可以处理此事件以更新链接到 TrackBar 的所有功能。
3.2 TCheckBox 组件:
在今天的界面布局中我们将使用TCheckBox作为开关选项,用户可以选择用或者不用。
TCheckBox的帮助文档网址是TCheckBox
以下是摘录的TCheckBox - Free Pascal wiki中的介绍:
TCheckBox 是一个组件,它为标签提供一个框,该框可以包含一个复选标记。TCheckbox 控件是标准复选框。它位于 Component Palette 的 Standard 选项卡中。当 TCheckBox 选中( ☑) 时,checked 属性为 True,否则为 False。 如果属性 AllowGrayed 设置为 False(默认值),则复选框只有两种可能的状态(属性 State):cbChecked 和 cbUnchecked。如果 AllowGrayed 设置为 True,则复选框有三种可能的状态:cbChecked、cbUnchecked 和 cbGrayed。
3.3 TRadioGroup 组件:
在今天的界面布局中我们将使用TRadioGroup 作为从多个可选项中选择其一的组件。
TRadioGroup 帮助文档的网址是TRadioGroup
在以前的布局中我们曾经用到过这个组件,只是没有仔细讲解。
以下是摘录的TRadioGroup - Lazarus wiki中的介绍:
TRadioGroup 是一组相关但互斥的 TRadioButton,要求用户从一组备选项中选择一个。它就像一个集成了 TRadioButton 的 TGroupBox。 要在表单上使用 TRadioGroup,您只需在 Component Palette 的 Standard 选项卡上选择它,然后单击表单来放置它。
TRadioGroup 在 Items 属性中将其项作为 TStrings 列表进行管理。Object Inspector 提供了一个小的文本输入,其中每行代表单选按钮组框中的一个条目。
使用 Columns 属性,可以很容易地将布局调整为列布局。属性 ColumnLayout 允许选择“先水平后垂直”或“先垂直后水平”的排列方向。
当前选定的项目不是通过TRadioButton 提供的checked 属性确定的,而是使用 ItemIndex。-1 等于 “没有选中”,0 选择第一个选项,1 选择第二个选项,依此类推。ItemIndex 既可读又可写。
可以使用 Caption 属性更改组的标题。对 Font 属性的更改会影响标题以及框内项目的文本。
TRadioGroup 相当于在一个TGroupBox组件中包含几个TRadioButton组件,但TRadioGroup 使用更方便。下图是几个 TRadioButton 和一个 TRadioGroup 与信息框中给出的特征属性的比较。
TGroupBox组件在以前的文章中曾经讲述,在此不再重复讲解。
4.给pTetris项目添加定制游戏计分的方法
在俄罗斯方块游戏中,计分是一切趣味性和挑战性的基础。我们将通过设置不同的计分方法来增加趣味性,让用户思考在特定的计分规则下怎么玩能够获得更高的积分。
而作为程序员,我们更关心的是怎么给用户提供设置这些规则的界面。
4.1 计分规则设计
我们已经在《学习编程从游戏开始——多彩俄罗斯方块的设计构想 - lexyao - 博客园》中提出了积分规则的初步设想,提出了计分定制的基本要求:
- 计分方法可以定制,包括用户可以在基础计分的基础上是否:
- 按一次消减的数量加分,加分的倍数可设置
- 消减多行是否按行数加分
- 消减行所在的高度是否影响加分
现在我们将根据以上设想细化计分规则:
- 每移动一个方块到达堆积区得基础分1分,按方块个数计算每组的总基础得分。比如,每组4个方块,可得总基础得分为4分
- 以下可选的加分规则若干选中,则按设定的加分比例乘以基础分。基础分乘以所有加分倍数得到一组方块的最终得分
- 放置高度加分:按放置方块的基点所在的行号(即5x5方格的中心点到堆积区底部的距离)计算加分倍数
- 从最低部的行开始依次为1倍、2倍……
- 消除行加分:放下方块后形成满行(满行的方块将被消除)会得到加分奖励
- 消除一行得分增加的基础倍数,可选1-10
- 消除行的高度加分:消除行的基础倍数乘以消除行所在高度
- 同时消除多行加分:消除行的基础倍数乘以同时消除的行数
- 消除一行得分增加的基础倍数,可选1-10
- 放置高度加分:按放置方块的基点所在的行号(即5x5方格的中心点到堆积区底部的距离)计算加分倍数
4.2 计分规则选项界面设计
在以前写的文章中有许多关于界面布局的内容,今天也将涉及到有关布局的内容,而今天重点使用的是组件的对齐属性。
下面开始配置表中“计分规则”页面的布局:
- 在对象查看器或者窗体设计器中选中PageControl1的tbsScore(计分规则)页面,添加一个TPanel组件,Name属性改为pnScoreBase(此处是为了叙述方便而设置Name属性,对于代码中用不到的组件可以保持自动生成的属性值),
- 设置pnScoreBase的Align属性为alTop向上边停靠
设置pnScoreBase的BevelOuter属性为bvNone,隐藏边缘的3D立体效果
调整pnScoreBase的高度足够大,为下一步添加组件留出空间- 向pnScoreBase中添加一个TDividerBevel组件,Name属性自动为DividerBevel5,修改Caption属性为“基础分数”,Align属性为alTop向上边停靠
- 向pnScoreBase中添加一个TLabel组件,Name属性改为lblScoreBase,Caption属性改为“每移动一个方块到达堆积区得分以基础分为计算基数,拖动滑块修改取值(当前取值1分)”,Align属性为alTop向上边停靠
- 向pnScoreBase中添加一个TTrackBar组件,Name属性改为trcScoreBase,Align属性为alTop向上边停靠,Min属性改为1,Max属性改为9,Position属性改为1
- 设置pnScoreBase的AutoSize属性为true,释放多余的空间
- 设置pnScoreBase的Align属性为alTop向上边停靠
- 在对象查看器或者窗体设计器中选中PageControl1的tbsScore(计分规则)页面,添加一个TPanel组件,Name属性自动为Panel4
- 设置Panel4的Align属性为alTop向上边停靠
设置Panel4的BevelOuter属性为bvNone,隐藏边缘的3D立体效果
调整Panel4的高度足够大,为下一步添加组件留出空间- 向Panel4中添加一个TDividerBevel组件,Name属性自动为DividerBevel6,修改Caption属性为“加分规则”,Align属性为alTop向上边停靠
- 向Panel4中添加一个TLabel组件,Name属性自动为Label3,Caption属性改为“每移动一个方块到达堆积区最终得分为基础分乘以加分倍数”,Align属性为alTop向上边停靠
- 向Panel4中添加一个TGroupBox组件,Name属性自动为GroupBox1,Caption属性改为“高度加分”,Align属性为alTop向上边停靠
调整GroupBox1的高度足够大,为下一步添加组件留出空间- 向GroupBox1中添加一个TCheckBox组件,Name属性改为ckScoreHeight,Align属性为alTop向上边停靠,Caption属性改为“启用行高加分(行高×1倍)”,Checked属性改为true
- 向GroupBox1中添加一个TTrackBar组件,Name属性改为trcScoreHeight,Align属性为alTop向上边停靠,Min属性改为1,Max属性改为9,Position属性改为1
- 设置GroupBox1的AutoSize属性为true,释放多余的空间
- 在窗体设计器或对象查看器中选中GroupBox1组件,从右键菜单中选择“复制”,然后选中Panel4,从右键菜单中选择“粘贴”,一个与GroupBox1及其包含的组件同样布局的组件组合被添加在GroupBox1之下,其中的TGroupBox组件Name属性自动命名为GroupBox2
- 修改GroupBox2及其包含组件的属性(未提及的属性保持与GroupBox1中组件一致):
GroupBox2的Caption属性改为“消除行加分”
TCheckBox组件,Name属性改为ckScoreDesBase,Caption属性改为“启用消行加分(每行基础加分1倍)”
TTrackBar组件,Name属性改为trcScoreDesBase - 设置GroupBox2的AutoSize属性为false,调整GroupBox2的高度足够大,为下一步添加组件留出空间
- 向GroupBox2中添加一个TPanel组件,Name属性自动为Panel5
设置Panel5的Align属性为alTop向上边停靠
设置Panel5的BevelOuter属性为bvNone,隐藏边缘的3D立体效果
设置Panel5的AutoSize属性为true
- 在对象查看器中将ckScoreDesBase、trcScoreDesBase依次拖入Panel5中
在窗体设置器中看上去ckScoreDesBase、trcScoreDesBase在GroupBox2的视觉效果跟添加Panel5之前应该是一致的,现在的差别只是嵌套了一层容器Panel5
- 在对象查看器中将ckScoreDesBase、trcScoreDesBase依次拖入Panel5中
- 用添加GroupBox2及其包含组件的方法复制Panel5后在GroupBox2中粘贴添加Panel5及其包含组件相同的一组组件,修改新添加组件的属性如下:
TPanel组件,Name属性改为pnScoreDesHeight
TCheckBox组件,Name属性改为ckScoreDesHeight,Caption属性改为“消行高度加分(行高×1倍)”
TTrackBar组件,Name属性改为trcScoreDesHeight - 用添加pnScoreDesHeight及其包含组件的方法再复制Panel5后在GroupBox2中粘贴添加Panel5及其包含组件相同的一组组件,修改新添加组件的属性如下:
TPanel组件,Name属性改为pnScoreDesRows
TCheckBox组件,Name属性改为ckScoreDesRows,Caption属性改为“消除多行加分(行数×1倍)”
TTrackBar组件,Name属性改为trcScoreDesRows - 【说明】经过以上复制->粘贴->修改后,Panel5、pnScoreDesHeight、trcScoreDesRows在GroupBox2中从上到下依次排列,除了修改的属性外,其他的外观效果及属性值应该是一致的
- 向GroupBox2中添加一个TPanel组件,Name属性自动为Panel5
- 设置GroupBox2的AutoSize属性为true,释放多余的空间
- 修改GroupBox2及其包含组件的属性(未提及的属性保持与GroupBox1中组件一致):
- 设置Panel4的AutoSize属性为true,释放多余的空间
- 设置Panel4的Align属性为alTop向上边停靠
这是布局完成后的样子:
4.3 计分界面的代码设计
【说明】可以在添加代码之前完成“加速规则”和“起始难度”两个页面的添加组件布局,这样可以避免在从“计分规则”页面复制组件时会包含事件处理函数,减少修改的工作量。
作为设置操作界面,选项的值修改后应该保存到配置文件中,保存的时机有两种选择:
- 在设置界面中修改选项,点击[确认]或[应用]按钮将选项修改后的值保存到配置文件中,而点击[取消]按钮则舍弃修改的值,配置文件保持修改前的数值。Lazarus的“选项”窗口以及以前的大多数应用软件、操作系统等都是这样做的。
- 在设置界面中修改选项,通过组件的修改事件操作函数直接将修改的数值保存到配置文件中,不再需要[确认]或[取消]。Windows11的设置、智能手机的Android系统都是采用的这种方式。
在这里我们计划采用后一种方式,不过由于用于保存设置数据的类还没有建立起来,所以现在还不能添加保存设置数据的代码。现在我们要添加的代码是体现界面中智能感知情景模式的代码。
4.3.1 基础分数
由于我们设置数字使用的是TTrackBar,它只显示刻画线却没有刻度数字,我们需要把数字直观地显示出来,只能是显示在位于其上的lblScoreBase组件的Caption中。拖动TTrackBar组件的滑块会引发OnChange事件,我们就为它的这个事件添加事件处理函数。双击trcScoreBase组件添加事件处理函数trcScoreBaseChange,添加代码如下:
procedure TfrmMain.trcScoreBaseChange(Sender: TObject); beginlblScoreBase.Caption:=Format('每移动一个方块到达堆积区得分以基础分为计算基数,拖动滑块修改取值(当前取值%d分)',[trcScoreBase.Position]); end;
有了以上代码,运行时拖动trcScoreBase的滑块,它的数值就会即时显示在lblScoreBase的标题上。
4.3.2 高度加分
高度加分栏目中有以下两项感知变化:
- trcScoreHeight的滑块拖动时数值在ckScoreHeight的标题上显示
- ckScoreHeight不被选中(false)时trcScoreHeight不可用
基于以上两点,添加代码如下:
procedure TfrmMain.ckScoreHeightChange(Sender: TObject); begintrcScoreHeight.Enabled := ckScoreHeight.Checked; end; procedure TfrmMain.trcScoreHeightChange(Sender: TObject); beginckScoreHeight.Caption:=Format('启用行高加分(行高×%d倍)',[trcScoreHeight.Position]); end;
4.3.3 消除行加分
高度加分栏目中有以下两项感知变化:
- 三个TTrackBar组件的滑块拖动时数值在它上面的TCheckBox组件的标题上显示
- TCheckBox组件不被选中(false)时它对应的TTrackBar不可用
- ckScoreDesBase(启用消行加分)不被选中(false)时,“消行高度加分”和“消除多行加分”两组组件都不可用
基于以上三点,添加代码如下:
procedure TfrmMain.ckScoreDesBaseChange(Sender: TObject); begintrcScoreDesBase.Enabled := ckScoreDesBase.Checked;pnScoreDesHeight.Enabled := ckScoreDesBase.Checked;pnScoreDesRows.Enabled := ckScoreDesBase.Checked; end;procedure TfrmMain.ckScoreDesHeightChange(Sender: TObject); begintrcScoreDesHeight.Enabled := ckScoreDesHeight.Checked; end;procedure TfrmMain.ckScoreDesRowsChange(Sender: TObject); begintrcScoreDesRows.Enabled := ckScoreDesRows.Checked; end; procedure TfrmMain.trcScoreDesBaseChange(Sender: TObject); beginckScoreDesBase.Caption:=Format('启用消行加分(每行基础加分%d倍)',[trcScoreDesBase.Position]); end;procedure TfrmMain.trcScoreDesHeightChange(Sender: TObject); beginckScoreDesHeight.Caption:=Format('消行高度加分(行高×%d倍)',[trcScoreDesHeight.Position]); end;procedure TfrmMain.trcScoreDesRowsChange(Sender: TObject); beginckScoreDesRows.Caption:=Format('消除多行加分(行数×)%d倍)',[trcScoreDesRows.Position]); end;
5.给pTetris项目添加定制加速规则的方法
5.1加速规则设计
我们已经在《学习编程从游戏开始——多彩俄罗斯方块的设计构想 - lexyao - 博客园》中提出了加速规则的初步设想,提出了加速定制的基本要求:
- 速度可定制,速度是指两次动作之间的间隔时间,最大间隔为1秒(1000毫秒)包括
- 是否按消减数量的增加而加快速度
- 按消减方块数量加分的算法
- 一次消减多行是否有速度奖励,奖励算法
- 是否按游戏时间的延长而提高速度
- 消耗时间的加速算法
- 暂停是否计入游戏时间
- 是否按消减数量的增加而加快速度
现在我们将根据以上设想细化加速规则:
- 方块从移动区顶部自动跳动下落,每次下落一行,两次跳动动作的时间间隔
- 基础时间间隔在1-1000毫秒之间,默认初始值为1000,可在难度中设置初始值
- 每放下一组方块跳动动作的时间间隔缩短,缩短的时间按基准时间计算所得
- 计算方法为:缩短时间=基准时间×加速倍数/得分总数
- 基准时间在1-10毫秒之间,默认为1毫秒
- 加速倍数为以下可选项的倍数总和
- 按下落跳动次数加倍
- 每按键一次增加1-10倍,默认为1倍
5.2 加速规则选项界面设计
“加速规则”选项界面采用与“计分规则”页面同样的方法布局,有些组件是可以直接复制的。
下面开始配置表中“加速规则”页面的布局:
- 从PageControl1的tbsScore(计分规则)页面复制pnScoreBase粘贴到tbsScore(加速规则),修改粘贴后在tbsScore中新添加的组件属性如下:
TPanel组件,Name属性为Panel6
TDividerBevel组件,Name属性为DividerBevel7,Caption属性为“基础时差”
TLabel组件,Name属性为lblSpeedBase,Caption属性为“每移动一组方块到达堆积区跳动间隔缩短时间以基础时差为计算基数(当前取值1毫秒)”
TTrackBar组件,Name属性为trcSpeedBase - 从PageControl1的tbsScore(计分规则)页面复制Panel4粘贴到tbsScore(加速规则),修改粘贴后在tbsScore中新添加的组件属性如下:
TPanel组件,Name属性为Panel7
TDividerBevel组件,Name属性为DividerBevel8,Caption属性为“增速规则”
TLabel组件,Name属性为Label4,Caption属性为“每移动一个方块到达堆积区缩短时间增加的倍数”
TGroupBox组件,Name属性为GroupBox3,Caption属性为“手动操作增速”
TGroupBox组件,Name属性为GroupBox4,Caption属性为“自动下落增速”
- GroupBox3中组件的属性:
TCkeckBox组件,Name属性为ckSpeedKey,Caption属性为“启用手动操作增速(操作次数×1倍)”
TTrackBar组件,Name属性为trcSpeedKey - GroupBox4中组件的属性:
保留一个TCkeckBox组件和一个TTrackBar组件,删除其他组件
TCkeckBox组件,Name属性为ckSpeedTimer,Caption属性为“启用自动下落增速(跳动次数×1倍)”
TTrackBar组件,Name属性为trcSpeedTimer
- GroupBox3中组件的属性:
- 复制Panel6粘贴在tbsScore下部空白处,新添加的TPanel组件,Name属性改为pnTimeCalc,自动向上对齐排在Panel7之下
删除pnTimeCalc中的组件,仅保留其中的TDividerBevel组件- TDividerBevel组件,Name属性为DividerBevel9,Caption属性为“增速算法”
- 在pnTimeCalc中添加一个TRadioGroup组件,Name属性改为grpTimeCalc,Align属性为alTop,
Caption属性为“每完成一组方块移动间隔时间缩短取值”,
Items属性为“基础时差×增速倍数,基础时差×增速倍数/移动方块得分”(分成两行),
调整grpTimeCalc为适宜高度
这是布局完成后的样子:
5.3 加速规则界面的代码设计
加速规则界面添加代码的情形与计分规则的情况是一样的。以下是加速规则界面实现体现界面中智能感知情景模式的代码。
5.3.1 基础时差
基础时差与基础分数的情形是一样的,代码如下:
procedure TfrmMain.trcSpeedBaseChange(Sender: TObject); beginlblSpeedBase.Caption:=Format('每移动一组方块到达堆积区跳动间隔缩短时间以基础时差为计算基数(当前取值%d毫秒)',[trcSpeedBase.Position]); end;
5.3.2 增速规则
增速规则的两个选项跟高度加分的情况是一样的。不同之处在于增速规则的两种增速都部选中时,下面的“增速算法”不可用。
手动操作增速的代码:
procedure TfrmMain.ckSpeedKeyChange(Sender: TObject); begintrcSpeedKey.Enabled := ckSpeedKey.Checked;grpTimeCalc.Enabled := ckSpeedKey.Checked or ckSpeedTimer.Checked; end; procedure TfrmMain.trcSpeedKeyChange(Sender: TObject); beginckSpeedKey.Caption:=Format('启用手动操作增速(操作次数×%d倍)',[trcSpeedKey.Position]); end;
自动下落增速的代码:
procedure TfrmMain.ckSpeedTimerChange(Sender: TObject); begintrcSpeedTimer.Enabled := ckSpeedTimer.Checked; grpTimeCalc.Enabled := ckSpeedKey.Checked or ckSpeedTimer.Checked; end; procedure TfrmMain.trcSpeedTimerChange(Sender: TObject); beginckSpeedTimer.Caption:=Format('启用自动下落增速(跳动次数×%d倍)',[trcSpeedTimer.Position]); end;
5.3.3 增速算法
增速算法暂时没有代码。
6.给pTetris项目添加定制起始难度的方法
6.1 起始难度设计
我们已经在《学习编程从游戏开始——多彩俄罗斯方块的设计构想 - lexyao - 博客园》中提出了起始难度的初步设想,提出了起始难度的基本要求:
- 影响游戏难度的因素分析
- 方块组合样式:传统的俄罗斯方块游戏的方块都是由4个方块6中排列方式组成的,也有加入异形排列的,不同的排列方式形成空洞的能力不同,也就有了不同的难度
- 跳动时间间隔:方块跳动下落的的速度越快,给用户留出的思考时间越短,所以跳动时间间隔对难度的影响是显而易见的。跳动时间间隔的设置方式有不同的选择:
- 固定的时间间隔:游戏速度自始至终没有变化,缺少挑战性,有可能出现高手永不停止的情况
- 时间间隔逐渐缩短:随着游戏的进展,跳动时间间隔缩短,下落速度加快,从而使得难度越来越大。加速算法也有不同的方式:
- 每次缩短的时间相同:游戏难度随着涌动方块组数的增加或者时间的流逝逐渐增加,难度增加的速度在配置中设定,与用户游戏操作无关
- 按用户操作计算时间:采用一种复杂的计算方式计算出每次缩短的时间间隔,用户可以通过自己的在游戏中的操作来影响难度变化的速度,从而增加了挑战性和趣味性
- 可击穿:正常情况下方块盒子下落遇到方块要停止移动,可击穿是指能够穿透方块到达下面的空穴,从而破坏用户在高位排列方块的意图
- 基于以上分析,难度可定制包括以下几个方面
- 游戏中可变因素的起始值设置,包括
- 起始行数:指定游戏开始时已经存在方块的行数。这是一格双刃剑,一方面增加了难度,另一方面提供了游戏开始在高位获得高分的机会
- 起始速度:指定游戏开始时两次动作之间的间隔毫秒数,取值0-1000毫秒。取值越小则移动速度越快,游戏难度也就越大
- 游戏中不变的因素,包括:
- 方块组合样式
- 可击穿
- 游戏中可变因素的起始值设置,包括
6.2 起始难度选项界面设计
“起始难度”选项界面采用与“计分规则”、“加速规则”页面同样的方法布局,有些组件也是可以直接复制的。
下面开始配置表中“起始难度”页面的布局:
- 从PageControl1的tbsScore(加速规则)页面复制Panel6粘贴到tbsStart(起始难度),修改粘贴后在tbsStart中新添加的组件属性如下:
TPanel组件,Name属性为Panel8
TDividerBevel组件,删除
TLabel组件,Name属性为lblStartTime,Caption属性为“方块跳动起始时间间隔(1000毫秒)”
TTrackBar组件,Name属性为trcStartTime,Min属性为0,Max属性为9,Position属性为9 - 复制Panel8粘贴到tbsStart下部空白处,新添加的TPanel组件Name属性为Panel9,自动向上对齐排列在Panel8之下,修改Panel9中组件属性如下:
TLabel组件,Name属性为lblStartHeight,Caption属性为“方块堆积起始高度(0行)”
TTrackBar组件,Name属性为trcStartHeight,Min属性为0,Max属性为19,Position属性为0 - 复制Panel8粘贴到tbsStart下部空白处,新添加的TPanel组件Name属性为Panel10,自动向上对齐排列在Panel8之下,修改Panel10中组件属性如下:
TLabel组件,Name属性为lblNextNumber,Caption属性为“待移动方块提示组数(4组)”
TTrackBar组件,Name属性为trcNextNumber,Min属性为0,Max属性为4,Position属性为4 - 在tbsStart下部空白处添加一个TRadioGroup组件,Name属性改为grpBoxStyle,Align属性为alTop,
Caption属性为“方块组合样式”,
Items属性为“常规4块,异形4块,常规5块,1至5块”(分成四行)
Columns属性为2
ColumnsLayout属性为clVerticalThenHorizontal
调整高度为适宜 - 在tbsStart下部空白处添加一个TCheckBox组件,Name属性改为ckPenetrate,Align属性为alTop,
Caption属性为“盒子跌落可击穿堆积的方块到达下部空穴”
这是布局完成后的样子:
6.3 起始难度的代码设计
起始难度界面添加代码的情形与计分规则、加速规则的情况是一样的。以下是加速规则界面实现体现界面中智能感知情景模式的代码。
6.3.1 方块跳动起始时间间隔
方块跳动起始时间间隔拖动滑块时有以下两点:
- trcStartTime的取值范围为0-9,而对应的时间值是100-1000毫秒,刻度值换算为时间值的算法是sTime := (trcStartTime.Position + 1) * 100
- trcStartTime的滑块多动会同步体现在方块移动区左边的trcStart组件的SelEnd属性上。trcStart组件只是为了一种形象的显示,而不是为了设置数值。
- trcStart.SelEnd的数值对应方块跳动的间隔时间值,显示为从上部开始的一段蓝色柱状线。游戏中随着间隔时间的缩短,蓝色柱状线向下延伸
- trcStart.SelEnd=0对应时间间隔1000毫秒,trcStart.SelEnd=1000对应时间间隔0毫秒,所以换算关系为trcStart.SelEnd := 1000 - sTime
基于以上情形,添加代码如下:
procedure TfrmMain.trcStartTimeChange(Sender: TObject); varsTime: integer; beginsTime := (trcStartTime.Position + 1) * 100;trcStart.SelEnd := 1000 - sTime;lblStartTime.Caption := Format('方块跳动起始时间间隔(%d毫秒)', [sTime]); end;
6.3.2 方块堆积起始高度
方块堆积起始高度拖动滑块时有以下两点:
- trcStartHeight.Position的取值范围为0-19
- trcStartHeight的滑块多动会同步体现在方块移动区左边的trcStart组件的SelEnd属性上。trcStart组件只是为了一种形象的显示,而不是为了设置数值。
- trcStart.Position的取值范围是0-1000,对应方块移动区的20行,每行对应的刻划值为50
- trcStart.Position=0在上部,trcStart.Position=1000在下部
- 当trcStartHeight.Position=0时,trcStart.Position要显示在下部(滑块对应移动最下部的一行),所以换算关系为trcStart.Position := 1000 - trcStartHeight.Position * 50;
基于以上情形,添加代码如下:
procedure TfrmMain.trcStartHeightChange(Sender: TObject); begin trcStart.Position := 1000 - trcStartHeight.Position * 50;lblStartHeight.Caption := Format('方块堆积起始高度(%d行)', [trcStartHeight.Position]); end;
6.3.3 方块组合样式、盒子跌落可击穿堆积的方块到达下部空穴
方块组合样式、盒子跌落可击穿堆积的方块到达下部空穴这两个项目暂时没有代码
7.结束语
在这篇文章中我们重点讲到了应用程序定制选项设计的一般方法、场景智能感知及其在应用程序界面设计中的应用,并通过示例程序演示了这种设计思想的实现过程。
我们在选项设置中使用TTrackBar组件设置指定范围的数字只是为了形象地表达,但并不是说这是唯一的或者说最好的方法。同样能达到这种效果的还有多种方式,每种方式的表现形式不同,优缺点也不同。不管选用哪一种方式,前提只有一个 :要让用户能够理解和方便使用。