Super Brick Breaker - 一款简单的 DirectDraw 游戏






4.79/5 (14投票s)
2004年1月12日
14分钟阅读

331651

12167
一篇关于使用 DirectX(DirectDraw)创建简单游戏的文章。
引言
小时候,我的父母给我和我的兄弟买了一台 Atari 5200,这是我们的第一台游戏机。随附的游戏是《Super Break Out》。我记得我非常喜欢玩这款游戏。《Super Brick Breaker》是《Super Break Out》的一个克隆版本。《Super Brick Breaker》也是我第一次尝试使用 DirectX 和 Visual C++ 6.0 编写游戏(我主要是一名 Visual Basic、Delphi、4GL 程序员和数据库管理员)。我将游戏设计得相当简单。没有屏幕外的表面或位图。所有的绘图都是通过彩色填充矩形“blit”完成的。
我选择制作一个 Break Out 克隆作为我的第一个游戏的主要原因是我认为这类游戏会简单且编码速度快。不幸的是,事实并非如此。游戏中花费精力(和时间)最多的部分是球与砖块、球与挡板以及球与游戏区域边界之间的碰撞检测和碰撞解决(即如何反射球)。我对当前的碰撞检测和碰撞解决代码相当满意,但我会继续改进。 (任何建议/批评都欢迎)。碰撞解决的主要问题是当球对称地与砖块或挡板的单个角落相交时。
背景
在编译项目之前,您的机器上必须安装 DirectX SDK(8.0 或更高版本),并且 DirectX 头文件和库文件必须位于您的 Visual C++ 搜索路径的顶部。
Super Brick Breaker 游戏导览
概述
如前所述,该游戏基本上是 Break Out 的克隆。游戏的目标是使用屏幕底部的挡板让球保持在游戏中,并尽可能多地打破(即击中)砖块。我设计了三种不同的游戏选项:
- 标准 - 这是基本游戏。一次只有一个挡板和一个球在游戏中。玩家在游戏中总共有五次机会。
- 双重 - 这是标准游戏的一个变体。在此游戏中,您有两个挡板(一个“漂浮”在标准挡板上方),并且有两个球在游戏中。游戏开始时会释放第一个球。一旦球击中挡板,第二个球就会被释放。由于有两个球在游戏中,玩家只有三次机会。
- 逃脱 - 此游戏类似于基本游戏,只有一个挡板,玩家有五次机会。但是,在游戏砖块中,有两个球“等待逃脱”。球被困在砖块中,直到足够多的砖块被移除,球才能逃脱。一旦它们“逃脱”,球就会进入游戏。
基本游戏玩法
每次击中游戏中的砖块都会获得积分。积分值由砖块的颜色决定:
- 蓝色 - 2 分
- 绿色 - 4 分
- 黄色 - 6 分
- 红色 - 8 分
如果游戏中的球撞到游戏区域的顶部边界,挡板的宽度会缩减到标准宽度的大约 1/3。球仍然会以正常速度从砖块上反射。
从所有游戏(游戏)的第 3 关开始,红色砖块的行数将增加(覆盖其他颜色)。因此,在第 3 关将有三行红色砖块(只有 1 行黄色),在第 4 关将有四行红色砖块(没有黄色),依此类推,直到所有行都变成红色。
游戏在游戏中的最后一个球因为低于挡板而离开游戏区域并且没有剩余回合时结束。回合在游戏中的最后一个球因为低于挡板而离开游戏区域后结束(注意:对于双重和逃脱游戏,可能不止一个球在游戏中)。
Super Brick Breaker 代码导览
概述
游戏由一系列类构成。大多数功能都包含在一个或多个类中。主要类是CDirectDrawGame
和 CSuperBrickBreaker
(它继承自 CDirectDrawGame
)。这两个类包含了大部分“游戏引擎”功能。其余的类(CBall
、CBricks
和 CPaddle
)用于描述游戏执行期间处理的主要游戏对象。每个类将在下文进行更详细的介绍。CBall 类
CBall
是一个非常简单的类。它的唯一目的是保存有关球的当前位置、速度和状态的信息。大多数方法都是简单的“Get/Set”类型方法,用于检索和设置球的各种属性。球的状态决定了球在游戏执行期间如何被处理。在主菜单和回合开始期间,球的状态是 IDLE。在此状态下,球在帧处理期间不会移动或绘制。在玩 Escape 游戏时,被捕获的球的状态是 CAPTIVE。在此状态下,球被限制在成员变量
m_captive_rect
定义的矩形内。状态为 CAPTIVE 的球在每一帧都会被绘制和处理,包括显示主菜单时。在一般游戏过程中,任何“非捕获”和“非丢失”的球都处于 FREE 状态。这是主要状态,处于此状态的球将在每一帧被处理和绘制。CPaddle 类
CPaddle
也是一个相当简单的类。该类负责存储有关挡板在游戏区域中的当前位置、当前速度、当前长度和边界的信息。该类中的大多数方法都是简单的“Get/Set”类型方法。方法详解
方法 | 描述/目的/注释 |
---|---|
IsPaddleAtPosition | 这是游戏用于测试球与挡板之间碰撞的主要方法之一。它确定一个挡板矩形(当玩双人游戏时可能不止一个)是否位于传入的 X、Y 坐标处。此方法由 CSuperBrickBreaker 类的 CheckHitPaddle 方法调用。如果发生相交,则将相交的挡板矩形的 RECT 作为指针参数返回。 |
CBricks 类
CBricks
是一个更有趣的类。CBricks
类的主要成员变量是 m_bricks
,一个 TBrick
结构体的动态数组。砖块数组在 InitializeBricks
方法执行期间分配,基于 SetRowColProperties
方法中设置的行数和列数。TBrick
s 结构体包含用于存储每个砖块的当前位置、状态、积分值和反射速度的所有属性。
其他主要相关成员变量是 m_brick_width
、m_brick_height
、m_brick_hor_space
和 m_brick_ver_space
。这些变量定义了在 InitializeBricks
方法执行期间,砖块数组将如何被初始化、定位和在游戏区域中进行间隔。
行颜色数组(成员变量 m_row_colors
)、积分值(成员变量 m_row_points
)和反射速度(成员变量 m_row_vel
)是根据 SetRowColProperties
方法中传递的行数和值动态分配的。假定传递给过程的数组长度与要创建的砖块行数相匹配。
方法详解
方法 | 描述/目的/注释 |
---|---|
IsBrickAtPosition | 这是游戏用于测试球与砖块之间碰撞的主要方法之一。它确定一个砖块是否位于传入的 X、Y 坐标处。此方法由 CSuperBrickBreaker 类的 IsBrickAtBallLocation 方法调用。如果发生相交,则将相交的砖块的 TBrick 结构体作为指针参数返回。 |
CDirectDrawGame 类
此类提供了使用 DirectX 进行游戏的主要功能。它提供了初始化和关闭 DirectDraw 和 DirectInput DirectX 组件的变量和函数。这里的 DirectX 组件的初始化相当静态。DirectDraw 曲面的颜色深度必须是 16 位,始终只有一个后备缓冲区,并且键盘是使用 DirectInput 初始化到的唯一输入设备。CDirectDrawGame
提供了所有用于在屏幕上绘图(DirectDraw 用于矩形,GDI 用于文本)、从 DirectInput 检索键盘数据以及通过 DirectSound 播放声音的方法。它还提供了一些“实用”函数来管理帧率、16 位颜色位掩码/移位以及设置类成员变量。
相关方法/变量
方法 | 描述/目的/注释 |
---|---|
InitializeDirectDraw | 此方法初始化并创建所有 DirectDraw 成员变量的实例供应用程序/游戏使用。假定所有相关的成员变量(窗口句柄、应用程序实例等)已通过相应的类方法设置。 |
InitializeDirectInput | 此方法初始化并创建所有 DirectInput 成员变量的实例供应用程序/游戏使用。注册的唯一输入设备是键盘。 |
StartScene | 此方法设计为在开始绘制新帧/场景时调用的第一个方法。此方法通过使用 DirectDraw 函数 Blit 进行黑色颜色填充来简单地擦除后备缓冲区的显示。 |
DrawRectangle | 此方法是游戏的主力。如前所述,没有位图,因此屏幕上的所有内容(文本除外)都通过此方法绘制。它使用 DirectDraw 函数 Blit 在指定位置以指定颜色在后备缓冲区上绘制矩形。 |
EndScene | 此方法设计为在完成帧/场景所需的所有绘制后调用的最后一个方法。此方法使用 DirectDraw 函数 Flip 将后备缓冲区的显示传输到主曲面。 |
StartGDIDrawing | 此方法用于捕获后备缓冲区的所需变量(即设备上下文),以便在 DirectDraw 曲面上使用 GDI 绘图函数。调用 DrawGDIText 之前必须先调用此方法。 |
DrawGDIText | 此方法使用 GDI 函数 DrawText 在后备缓冲区上以指定的 RGB 颜色绘制指定的文本。 |
EndGDIDrawing | 此方法用于释放方法 StartGDIDrawing 获取的所有 GDI 变量。 |
CalculateRGBBitShift | 此方法由 InitializeDirectDraw 方法使用,以确定如何通过位掩码和位移将 16 位颜色从 RGB 值转换为单个 DWORD 颜色。这些值存储在成员变量中,并由 RGBTo16BitColor 方法使用。 |
RGBto16BitColor | 此方法将 RGB 颜色转换为单个 DWORD 颜色,供 DirectDraw 绘图函数使用。此方法通过使用方法 CalculateRGBBitShift 确定的位移变量进行转换。 |
CSuperBrickBreaker 类
此类是游戏的主要代码。它包含所有游戏处理代码,包括所有游戏对象的绘图和处理。作为主类,它也是游戏中最大、最复杂的类。此类包含许多成员变量,用于管理游戏的当前整体状态、游戏区域边界、用于绘制游戏对象的颜色以及游戏对象本身。
方法详解
方法 | 描述/目的/注释 |
---|---|
InitializeBalls | 此方法用于根据当前游戏类型和当前状态,使用球的起始位置和状态初始化球数组(成员变量 m_ball)。 |
InitializeBricks | 此方法设置砖块的行和列属性(即,行数、列数以及每行砖块的颜色、积分值和反射速度)。此方法还会移除“Escape”游戏中捕获球所需的砖块。 |
InitializeColors | 此方法将 RGB 颜色值转换为 DWORD 颜色,然后可供 DirectDraw 绘图函数使用。它使用方法 RGBto16BitColor (来自父类 CDirectDrawGame )进行转换。 |
InitializePaddle | 此方法在每个回合开始前初始化挡板的位置和大小。(在回合开始时,挡板始终恢复到其完整宽度并移动到游戏区域的中央)。 |
InitializePlayingArea | 此方法初始化定义游戏边界和边界包围区域的矩形。这些矩形由类中的其他方法使用,以控制球和挡板在游戏区域中的移动。 |
ProcessBalls | 此方法可能是整个游戏中唯一最复杂的方法。ProcessBalls 负责控制球在游戏区域中的行为。它根据球的当前速度移动球,并且是解决球与游戏区域边界、砖块或挡板之间发生的任何碰撞的起点。 |
ProcessPaddle | 此方法根据挡板的当前速度(按下右箭头键时为 10 像素,按下左箭头键时为 -10 像素)在游戏区域底部移动挡板。 |
ProcessStateChange | 当游戏需要转换游戏状态时,此方法会处理所有必要的处理。有效游戏状态包括 GS_INITIALIZE 、GS_MAINMENU 、GS_STARTTURN 、GS_GAMEPLAY 、GS_GAMEPAUSED 和 GS_GAMEOVER 。 |
CheckHitPaddle | 这是碰撞检测例程之一。它检查球的任何角是否与任何挡板矩形相交。如果发生相交,则调用 BallRectIntersect 方法来确定球如何与挡板相交,并最终如何从挡板反射。 |
CheckHitBrick | 这是碰撞检测例程之一。它通过调用 IsBrickAtBallLocation 方法来检查球的任何角是否与任何可见砖块相交。如果发生相交,则调用 BallRectIntersect 方法来确定球如何与砖块相交,并最终如何从砖块反射。 |
BallRectIntersect | 此方法也是碰撞检测例程之一。它确定传入的球(以 RECT 形式传入)如何与传入的游戏对象(也以 RECT 形式传入的砖块或挡板)相交。利用球的角,它确定球是与游戏对象的顶部、右侧、底部还是左侧相交,然后以适当的方向反射球。 |
IsBrickAtBallLocation | 此方法是最终的碰撞检测例程。它检查球的任何角是否与任何可见砖块相交。 |
GameInitialize | 此方法负责初始化适用于所有游戏类型的游戏变量。它初始化颜色、用于绘制菜单和标题行的字体、游戏区域以及砖块的默认属性。 |
GameMain | 此方法是主游戏处理函数。此方法在每一帧都被调用,并驱动每个场景的绘图和处理。 |
WinMain
WinMain 不包含类,但仍然值得快速提及。WinMain 包含应用程序入口点并声明游戏的主窗口。它还包含 Windows 消息循环和消息处理函数。WinMain 还声明、初始化并使用 CSuperBrickBreaker 类来驱动游戏。结论
希望这能让您对游戏的内容和构建方式有一个很好的了解。代码注释相当详细,因此我在这里没有提到的任何内容应该在源代码中有详细描述。如果您有任何问题/意见,请随时发帖或给我发电子邮件。历史
2004 年 1 月 13 日 - 文章首次发布