作为巴西塞阿拉联邦大学系统与数字媒体专业的二年级学生,我接到的任务是重制 1978 年的经典 Atari 2600 打砖块游戏。我仍然处于学习软件开发的初期阶段,这是一次具有挑战性的经历。但这也是一次有益的经历,因为我学到了很多东西,特别是关于应用面向对象概念的知识。

(Vaneska Karen, CC BY-SA 4.0)
我将解释我是如何完成这项挑战的,如果您按照逐步说明进行操作,在本篇文章结束时,您将拥有自己经典打砖块游戏的最初部分。
选择 Java 和 TotalCross
我的几门课程都使用 Processing,这是一个使用 Java 的软件引擎。Java 是一种学习编程概念的优秀语言,部分原因在于它是一种强类型语言。
尽管我可以自由选择任何语言或框架来完成我的打砖块项目,但我还是选择继续使用 Java,以便应用我在课程中学到的知识。我也想使用一个框架,这样我就不必从头开始做所有事情。我考虑过使用 Godot,但这意味着我几乎不需要进行任何编程。
相反,我选择了 TotalCross。它是一个开源软件开发工具包 (SDK) 和框架,带有一个简单的游戏引擎,可以为 Linux Arm 设备(如 Raspberry Pi)和智能手机生成代码。此外,由于我在 TotalCross 工作,我可以接触到比我更有经验的开发人员,并且非常了解该平台。这似乎是最安全的方式,尽管遇到了一些麻烦,但我一点也不后悔。开发整个项目并看到它在手机和 Raspberry Pi 上运行,感觉非常酷。

使用 Java 和 TotalCross 构建的打砖块重制版在 Raspberry Pi 3 Model B 上运行。(Vaneska Karen, CC BY-SA 4.0)
定义项目机制和结构
在开始开发任何应用程序,尤其是游戏时,您需要考虑将要实现的主要功能或机制。我观看了几次原始打砖块游戏的玩法,并在互联网上玩了一些版本。然后,我根据所学的知识定义了游戏机制和项目结构。
游戏机制
- 平台根据用户的指令向左或向右移动。当它到达末端时,它会撞到“墙壁”(边缘)。
- 当球击中平台时,它会沿来时的相反方向返回。
- 每次球击中“砖块”(蓝色、绿色、黄色、橙色或红色)时,砖块就会消失。
- 当 01 级中的所有砖块都被摧毁后,新的砖块会出现(与之前的砖块位置相同),并且球的速度会增加。
- 当 02 级中的所有砖块都被摧毁后,游戏将继续进行,屏幕上没有任何障碍物。
- 当球掉落时,游戏结束。
项目结构
RunBreakoutApplication.java
类负责调用继承GameEngine
并运行模拟器的类。Breakout.java
是主类,它继承自GameEngine
类并“组装”游戏,在其中它将调用对象、定义位置等。sprites
包是所有负责精灵的类(例如,方块、平台和球的图像和行为)所在的位置。util
包包含用于简化项目维护的类,例如常量、图像初始化和颜色。
动手编写代码
首先,从 VSCode 安装 TotalCross 插件。如果您正在使用另一个集成开发环境 (IDE),请查看 TotalCross 的文档以获取安装说明。
如果您正在使用该插件,只需按 Ctrl
+P
,输入 totalcross
,然后单击 Create new project
。填写请求的信息
文件夹名称:
gameTCArtifactId:
com.totalcross项目名称:
BreakoutTotalCross 版本:
6.1.1(或最新版本)构建平台:
-Android 和 -Linux_arm(选择您想要的平台)
在填写上述字段并生成项目后,如果您位于 RunBreakoutApplication.java
类中,右键单击它并单击“运行”将打开模拟器,如果您已正确使用 TotalCross 创建了 Java 项目,则“Hello World!”将出现在您的屏幕上。

(Vaneska Karen, CC BY-SA 4.0)
如果您遇到问题,请查看 文档 或在 Telegram 上向 TotalCross 社区 寻求帮助。
项目配置完成后,下一步是在 Resources
> Sprites
中添加项目图像。创建两个名为 util
和 sprites
的包,以便稍后使用。
您的项目结构将是

(Vaneska Karen, CC BY-SA 4.0)
了解幕后
为了更轻松地维护代码并将图像更改为您想要使用的颜色,最佳实践是通过创建类来集中管理所有内容。将此功能的所有类都放在 util
包中。
Constants.java
首先,创建 constants.java
类,该类是放置模式(例如屏幕边缘和平台开始位置之间的距离)、速度、块数等所在的位置。这对于玩耍、更改数字以及理解事物发生变化的位置和原因很有用。对于那些刚开始学习 Java 的人来说,这是一个很好的练习。
package com.totacross.util;
import totalcross.sys.Settings;
import totalcross.ui.Control;
import totalcross.util.UnitsConverter;
public class Constants {
//Position
public static final int BOTTOM_EDGE = UnitsConverter.toPixels(430 + Control.DP);
public static final int DP_23 = UnitsConverter.toPixels(23 + Control.DP);
public static final int DP_50 = UnitsConverter.toPixels(50 + Control.DP);
public static final int DP_100 = UnitsConverter.toPixels(100 + Control.DP);
//Sprites
public static final int EDGE_RACKET = UnitsConverter.toPixels(20 + Control.DP);
public static final int WIDTH_BALL = UnitsConverter.toPixels(15 + Control.DP);
public static final int HEIGHT_BALL = UnitsConverter.toPixels(15 + Control.DP);
//Bricks
public static final int NUM_BRICKS = 10;
public static final int WIDTH_BRICKS = Settings.screenWidth / NUM_BRICKS;
public static final int HEIGHT_BRICKS = Settings.screenHeight / 32;
//Brick Points
public static final int BLUE_POINT = 1;
public static final int GREEN_POINT = 2;
public static final int YELLOW_POINT = 3;
public static final int DARK_ORANGE_POINT = 4;
public static final int ORANGE_POINT = 5;
public static final int RED_POINT = 6;
}
如果您想了解有关像素密度 (DP) 单位的更多信息,我建议阅读 Material Design 描述。
Colors.java
顾名思义,此类是您在其中定义游戏中使用的颜色的位置。我建议根据颜色的用途来命名事物,例如背景、字体颜色等。这将使您更轻松地在单个类中更新项目的调色板。
package com.totacross.util;
public class Colors {
public static int PRIMARY = 0x161616;
public static int P_FONT = 0xFFFFFF;
public static int SECONDARY = 0xE63936;
public static int SECONDARY_DARK = 0xCE3737;
}
Images.java
images.java
类无疑是最常用的。
package com.totacross.util;
import static com.totacross.util.Constants.*;
import totalcross.ui.dialog.MessageBox;
import totalcross.ui.image.Image;
public class Images {
public static Image paddle, ball;
public static Image red, orange, dark_orange, yellow, green, blue;
public static void loadImages() {
try {
// general
paddle = new Image("sprites/paddle.png");
ball = new Image("sprites/ball.png").getScaledInstance(WIDTH_BALL, HEIGHT_BALL);
// Bricks
red = new Image("sprites/red_brick.png").getScaledInstance(WIDTH_BRICKS, HEIGHT_BRICKS);
orange = new Image("sprites/orange_brick.png").getScaledInstance(WIDTH_BRICKS, HEIGHT_BRICKS);
dark_orange = new Image("sprites/orange2_brick.png").getScaledInstance(WIDTH_BRICKS, HEIGHT_BRICKS);
yellow = new Image("sprites/yellow_brick.png").getScaledInstance(WIDTH_BRICKS, HEIGHT_BRICKS);
green = new Image("sprites/green_brick.png").getScaledInstance(WIDTH_BRICKS, HEIGHT_BRICKS);
blue = new Image("sprites/blue_brick.png").getScaledInstance(WIDTH_BRICKS, HEIGHT_BRICKS);
} catch (Exception e) {
MessageBox.showException(e, true);
}
}
}
getScaledInstance()
方法将操作图像以匹配通过常量传递的值。尝试更改这些值并观察对游戏的影响。
回顾
此时,您的项目应该看起来像这样

(Vaneska Karen, CC BY-SA 4.0)
创建您的第一个精灵
现在项目结构正确,您已准备好在 sprite 包中创建您的第一个类:paddle.java
,它是平台——用户的交互对象。
Paddle.java
paddle.java
类必须继承自 sprite
,它是负责游戏中对象的类。这是游戏引擎开发中的一个基本概念,因此当从精灵继承时,TotalCross 框架将已经关注屏幕内的运动定界、精灵之间的碰撞检测以及其他重要功能。您可以在 Javadoc 中查看所有详细信息。
在打砖块游戏中,挡板在 X 轴上以用户命令(通过触摸屏或鼠标移动)确定的速度移动。paddle.java
类负责定义此运动和精灵的图像(“面孔”)。
package com.totacross.sprites;
import com.totacross.util.Images;
import totalcross.game.Sprite;
import totalcross.ui.image.ImageException;
public class Paddle extends Sprite {
private static final int SPEED = 4;
public Paddle() throws IllegalArgumentException, IllegalStateException, ImageException {
super(Images.paddle, -1, true, null);
}
//Move the platform according the speed and the direction
public final void move(boolean left, int speed) {
if (left) {
centerX -= SPEED;
} else {
centerX += SPEED;
}
setPos(centerX, centerY, true);
}
}
您在构造函数中指示图像 (Images.paddle
),而 move
方法(TotalCross 功能)接收在类开头定义的速度。尝试使用其他值并观察运动会发生什么。
当挡板向左移动时,挡板在任何时刻的中心都定义为其自身减去速度,而当它向右移动时,则定义为其自身加上速度。最终,您定义了精灵在屏幕上的位置。
现在您的精灵已准备就绪,因此您需要在屏幕上添加它,并包含用户的移动以调用 move
方法并创建移动。在您的主类 Breakout.java
中执行此操作。
添加屏幕显示和用户交互
在构建游戏引擎时,您需要关注一些标准点。为了简洁起见,我将在代码中添加注释。
基本上,您将删除自动生成的 initUI()
方法,并且您将从 GameEngine
而不是 MainWindow
继承它。您的类名中将出现一个“红色”,因此只需单击 IDE 的灯泡或建议符号,然后单击 Add unimplemented methods
。这将自动生成 onGameInit()
方法,该方法负责游戏开始的时刻,即调用 breakout
类的时刻。
在构造函数内部,您必须添加样式类型 (MaterialUI
) 和屏幕上的刷新时间 (70
),并指示游戏具有界面 (gameHasUI = true;
)。
最后但并非最不重要的一点是,您必须通过 onGameInit()
上的 this.start()
启动游戏,并专注于其他一些方法
onGameInit()
是第一个被调用的方法。在其中,您必须初始化精灵和图像 (Images.loadImages
),并告诉游戏它可以开始了。onGameStart()
在游戏开始时被调用。它设置平台的初始位置(在屏幕中心的 X 轴上,并在 Y 轴上低于中心并带有边框)。onPaint()
是您说出每个帧将绘制的内容的位置。首先,它将背景绘制为黑色(以免留下精灵的痕迹),然后使用.show()
显示精灵。onPenDrag
和onPenDown
方法识别用户何时移动挡板(通过在触摸屏上拖动手指或在按住鼠标左键的同时移动鼠标)。这些方法通过setPos()
方法更改挡板运动,该方法触发Paddle.java
类中的move
方法。请注意,racket.setPos
方法的最后一个参数是true
,以便精确限制挡板在屏幕内的运动,使其永远不会从用户的视野中消失。
package com.totacross;
import com.totacross.sprites.Paddle;
import com.totacross.util.Colors;
import com.totacross.util.Constants;
import com.totacross.util.Images;
import totalcross.game.GameEngine;
import totalcross.sys.Settings;
import totalcross.ui.MainWindow;
import totalcross.ui.dialog.MessageBox;
import totalcross.ui.event.PenEvent;
import totalcross.ui.gfx.Graphics;
public class Breakout extends GameEngine {
private Paddle racket;
public Breakout() {
setUIStyle(Settings.MATERIAL_UI);
gameName = "Breakout";
gameVersion = 100;
gameHasUI = true;
gameRefreshPeriod = 70;
}
@Override
public void onGameInit() {
setBackColor(Colors.PRIMARY);
Images.loadImages();
try {
racket = new Paddle();
} catch (Exception e) {
MessageBox.showException(e, true);
MainWindow.exit(0);
}
this.start();
}
public void onGameStart() {
racket.setPos(Settings.screenWidth / 2, (Settings.screenHeight - racket.height) - Constants.EDGE_RACKET, true);
}
//to draw the interface
@Override
public void onPaint(Graphics g) {
super.onPaint(g);
if (gameIsRunning) {
g.backColor = Colors.PRIMARY;
g.fillRect(0, 0, this.width, this.height);
if (racket != null) {
racket.show();
}
}
}
//To make the paddle moving with the mouse/press moviment
@Override
public final void onPenDown(PenEvent evt) {
if (gameIsRunning) {
racket.setPos(evt.x, racket.centerY, true);
}
}
@Override
public final void onPenDrag(PenEvent evt) {
if (gameIsRunning) {
racket.setPos(evt.x, racket.centerY, true);
}
}
}
运行游戏
要运行游戏,只需用鼠标右键单击 RunBreakoutApplication.java
,然后单击“运行”即可查看其外观。

(Vaneska Karen, CC BY-SA 4.0)
如果您想在 Raspberry Pi 上运行它,请将 RunBreakoutApplication.java
类中的参数更改为
TotalCrossApplication.run(Breakout.class, "/scr", "848x480");
这会将屏幕尺寸设置为与 Raspberry Pi 匹配。

(Vaneska Karen, CC BY-SA 4.0)
第一个精灵和游戏机制已准备就绪!
下一步
在下一篇文章中,我将展示如何添加球精灵并制作碰撞效果。如果您需要帮助,请在 Telegram 上的 社区群组 中呼叫我,或在 TotalCross 论坛 中发帖,我会在那里提供帮助。
如果您将本文付诸实践,请在评论中分享您的经验。所有反馈都很重要!如果您愿意,请收藏 GitHub 上的 TotalCross,因为这会提高项目在该平台上的相关性。
评论已关闭。