通过构建经典街机游戏学习 Java

在构建有趣的游戏的过程中,练习如何构建项目和编写 Java 代码。
116 位读者喜欢这篇文章。

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

我将解释我是如何完成这项挑战的,如果您按照逐步说明进行操作,在本篇文章结束时,您将拥有自己经典打砖块游戏的最初部分。

选择 Java 和 TotalCross

我的几门课程都使用 Processing,这是一个使用 Java 的软件引擎。Java 是一种学习编程概念的优秀语言,部分原因在于它是一种强类型语言。

尽管我可以自由选择任何语言或框架来完成我的打砖块项目,但我还是选择继续使用 Java,以便应用我在课程中学到的知识。我也想使用一个框架,这样我就不必从头开始做所有事情。我考虑过使用 Godot,但这意味着我几乎不需要进行任何编程。

相反,我选择了 TotalCross。它是一个开源软件开发工具包 (SDK) 和框架,带有一个简单的游戏引擎,可以为 Linux Arm 设备(如 Raspberry Pi)和智能手机生成代码。此外,由于我在 TotalCross 工作,我可以接触到比我更有经验的开发人员,并且非常了解该平台。这似乎是最安全的方式,尽管遇到了一些麻烦,但我一点也不后悔。开发整个项目并看到它在手机和 Raspberry Pi 上运行,感觉非常酷。

Breakout remake

使用 Java 和 TotalCross 构建的打砖块重制版在 Raspberry Pi 3 Model B 上运行。(Vaneska Karen, CC BY-SA 4.0)

定义项目机制和结构

在开始开发任何应用程序,尤其是游戏时,您需要考虑将要实现的主要功能或机制。我观看了几次原始打砖块游戏的玩法,并在互联网上玩了一些版本。然后,我根据所学的知识定义了游戏机制和项目结构。

游戏机制

  1. 平台根据用户的指令向左或向右移动。当它到达末端时,它会撞到“墙壁”(边缘)。
  2. 当球击中平台时,它会沿来时的相反方向返回。
  3. 每次球击中“砖块”(蓝色、绿色、黄色、橙色或红色)时,砖块就会消失。
  4. 当 01 级中的所有砖块都被摧毁后,新的砖块会出现(与之前的砖块位置相同),并且球的速度会增加。
  5. 当 02 级中的所有砖块都被摧毁后,游戏将继续进行,屏幕上没有任何障碍物。
  6. 当球掉落时,游戏结束。

项目结构

  • RunBreakoutApplication.java 类负责调用继承 GameEngine 并运行模拟器的类。
  • Breakout.java 是主类,它继承自 GameEngine 类并“组装”游戏,在其中它将调用对象、定义位置等。
  • sprites 包是所有负责精灵的类(例如,方块、平台和球的图像和行为)所在的位置。
  • util 包包含用于简化项目维护的类,例如常量、图像初始化和颜色。

动手编写代码

首先,从 VSCode 安装 TotalCross 插件。如果您正在使用另一个集成开发环境 (IDE),请查看 TotalCross 的文档以获取安装说明。 

如果您正在使用该插件,只需按 Ctrl+P,输入 totalcross,然后单击 Create new project。填写请求的信息

  • 文件夹名称: gameTC
  • ArtifactId: com.totalcross
  • 项目名称: Breakout
  • TotalCross 版本: 6.1.1(或最新版本)
  • 构建平台: -Android 和 -Linux_arm(选择您想要的平台)

在填写上述字段并生成项目后,如果您位于 RunBreakoutApplication.java 类中,右键单击它并单击“运行”将打开模拟器,如果您已正确使用 TotalCross 创建了 Java 项目,则“Hello World!”将出现在您的屏幕上。

如果您遇到问题,请查看 文档 或在 Telegram 上向 TotalCross 社区 寻求帮助。

项目配置完成后,下一步是在 Resources > Sprites 中添加项目图像。创建两个名为 utilsprites 的包,以便稍后使用。

您的项目结构将是

了解幕后

为了更轻松地维护代码并将图像更改为您想要使用的颜色,最佳实践是通过创建类来集中管理所有内容。将此功能的所有类都放在 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() 方法将操作图像以匹配通过常量传递的值。尝试更改这些值并观察对游戏的影响。

回顾

此时,您的项目应该看起来像这样

创建您的第一个精灵

现在项目结构正确,您已准备好在 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() 显示精灵。
  • onPenDragonPenDown 方法识别用户何时移动挡板(通过在触摸屏上拖动手指或在按住鼠标左键的同时移动鼠标)。这些方法通过 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,然后单击“运行”即可查看其外观。

如果您想在 Raspberry Pi 上运行它,请将 RunBreakoutApplication.java 类中的参数更改为

        TotalCrossApplication.run(Breakout.class, "/scr", "848x480");

这会将屏幕尺寸设置为与 Raspberry Pi 匹配。

第一个精灵和游戏机制已准备就绪!

下一步

在下一篇文章中,我将展示如何添加球精灵并制作碰撞效果。如果您需要帮助,请在 Telegram 上的 社区群组 中呼叫我,或在 TotalCross 论坛 中发帖,我会在那里提供帮助。

如果您将本文付诸实践,请在评论中分享您的经验。所有反馈都很重要!如果您愿意,请收藏 GitHub 上的 TotalCross,因为这会提高项目在该平台上的相关性。

接下来阅读
标签
User profile image.
TotalCross 的数字媒体和系统学生以及社区领导者。

评论已关闭。

Creative Commons 许可协议本作品根据知识共享署名-相同方式共享 4.0 国际许可协议获得许可。
© . All rights reserved.