使用Java FXGL构建太空游侠游戏
在这个简单的FXGL教程中,我们将开发一个名为" Space Ranger"的游戏。
什么是FXGL?
FXGL是基于顶级JavaFX构建的游戏引擎,JavaFX是用于台式机,移动和嵌入式系统的GUI工具包。
为什么在JavaFX上使用FXGL?
JavaFX本身提供了通用的呈现和UI功能。
最重要的是,FXGL带来了现实世界的游戏开发技术和工具,使开发跨平台游戏变得容易。
如何下载FXGL JAR?
FXGL可以作为Maven或者Gradle依赖项下载。
例如,Maven坐标如下,可与Java 11+一起使用。
<dependency> <groupId>com.github.almasb</groupId> <artifactId>fxgl</artifactId> <version>11.8</version> </dependency>
如果您有任何困难,可以在本教程的结尾找到完整的源代码链接。
太空游侠游戏
我们的游戏思路比较简单。
我们有一堆试图进入我们基地的敌人,而我们只有一个基地保护者-玩家。
鉴于这是一个入门教程,因此我们将不使用任何资产,例如图像,声音和其他外部资源。
完成后,游戏将如下所示:
让我们开始吧!
所需进口
首先,让我们处理所有的导入操作,以便我们专注于本教程的代码方面。
为了简单起见,所有代码都在一个文件中,但是您可能希望将每个类放在自己的文件中。
创建一个文件SpaceRangerApp.java并放置以下导入:
import com.almasb.fxgl.animation.Interpolators; import com.almasb.fxgl.app.GameApplication; import com.almasb.fxgl.app.GameSettings; import com.almasb.fxgl.core.math.FXGLMath; import com.almasb.fxgl.dsl.components.ProjectileComponent; import com.almasb.fxgl.entity.Entity; import com.almasb.fxgl.entity.EntityFactory; import com.almasb.fxgl.entity.SpawnData; import com.almasb.fxgl.entity.Spawns; import javafx.geometry.Point2D; import javafx.scene.input.KeyCode; import javafx.scene.input.MouseButton; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.util.Duration; import static com.almasb.fxgl.dsl.FXGL.*;
就是这样,我们的第一步已经完成。
请注意,最后一次静态导入简化了对FXGL的许多调用,这是推荐的方法。
游戏代码
现在,我们将开始编写一些代码。
与任何其他应用程序一样,我们从指定程序的入口点开始。
我们还将为FXGL提供一些基本设置,例如宽度,高度和游戏标题。
public class SpaceRangerApp extends GameApplication { @Override protected void initSettings(GameSettings settings) { settings.setTitle("Space Ranger"); settings.setWidth(800); settings.setHeight(600); } public static void main(String[] args) { launch(args); } }
游戏对象/实体
每个游戏引擎都有自己的用于游戏中对象的术语。
在使用实体组件模型的FXGL中,游戏对象称为实体。
每个实体通常具有一种类型,例如,玩家,敌人,弹丸等。
在我们的游戏中,我们将恰好描述了以下类型:
public enum EntityType { PLAYER, ENEMY, PROJECTILE }
FXGL需要知道如何构造这些类型。
为此,我们创建了一家工厂。
如前所述,我们将所有类保留在同一文件中。
此时,如果您愿意,可以将" SpaceRangerFactory"类移动到其自己的文件中(如果这样做,只需删除" static"关键字)。
public class SpaceRangerApp extends GameApplication { public static class SpaceRangerFactory implements EntityFactory { } }
有一个简化的流程来定义实体。
在" SpaceRangerFactory"内部,我们定义了一个新方法,该方法接受一个SpawnData对象,返回一个Entity对象,并具有一个Spawns注释:
@Spawns("player") public Entity newPlayer(SpawnData data) { var top = new Rectangle(60, 20, Color.BLUE); top.setStroke(Color.GRAY); var body = new Rectangle(25, 60, Color.BLUE); body.setStroke(Color.GRAY); var bot = new Rectangle(60, 20, Color.BLUE); bot.setStroke(Color.GRAY); bot.setTranslateY(40); return entityBuilder() .type(EntityType.PLAYER) .from(data) .view(body) .view(top) .view(bot) .build(); }
从注释中可以很容易地看出,该方法产生了一个播放器对象(实体)。
现在,我们将详细考虑该方法。
首先,我们构造播放器视图的三个部分,它们是标准JavaFX矩形。
请注意,我们使用的是" var"语法。
使用" entityBuilder()",我们指定类型,用于设置玩家实体位置和视图的数据。
这种流畅的API使我们能够以简洁的方式构建实体。
我们的下一步是在游戏中定义弹丸:
@Spawns("projectile") public Entity newProjectile(SpawnData data) { var view = new Rectangle(30, 3, Color.LIGHTBLUE); view.setStroke(Color.WHITE); view.setArcWidth(15); view.setArcHeight(10); return entityBuilder() .type(EntityType.PROJECTILE) .from(data) .viewWithBBox(view) .collidable() .zIndex(-5) .with(new ProjectileComponent(new Point2D(1, 0), 760)) .build(); }
该视图再次是带有圆角的JavaFX矩形。
这次我们称" viewWithBBox()",而不仅仅是" view()"。
前一种方法会根据视图自动生成边界框。
整齐!
接下来,使用" collidable()",将射弹标记为可以与其他实体碰撞的实体。
稍后我们将回到冲突中。
我们将z-index设置为负值,以便在玩家之前绘制它(默认情况下,每个实体的z-index为0)。
最后,我们添加一个ProjectileComponent,其速度等于760,方向矢量为(1,0),这意味着X轴为1,Y轴为0,这又意味着向右移动。
我们最后定义的实体是敌人类型:
@Spawns("enemy") public Entity newEnemy(SpawnData data) { var view = new Rectangle(80, 20, Color.RED); view.setStroke(Color.GRAY); view.setStrokeWidth(0.5); animationBuilder() .interpolator(Interpolators.SMOOTH.EASE_OUT()) .duration(Duration.seconds(0.5)) .repeatInfinitely() .animate(view.fillProperty()) .from(Color.RED) .to(Color.DARKRED) .buildAndPlay(); return entityBuilder() .type(EntityType.ENEMY) .from(data) .viewWithBBox(view) .collidable() .with(new ProjectileComponent(new Point2D(-1, 0), FXGLMath.random(50, 150))) .build(); }
我们已经介绍了流畅的API方法,例如" type()"和" collidable()",因此我们将重点放在动画上。
如您所见,动画构建器还遵循类似的流畅API约定。
它允许我们设置各种动画设置,例如持续时间和重复次数。
我们可以观察到动画在矩形视图的" fillProperty()"上运行,我们用它来表示敌人。
特别是,填充会每0.5秒从红色变为暗红色。
随意调整动画设置,以查看最适合您的游戏的动画。
我们的工厂程序现已完成,我们将开始将我们的代码整合在一起。
输入项
通常,所有FXGL输入都在" initInput"方法内部进行处理,如下所示:
@Override protected void initInput() { onKey(KeyCode.W, () -> getGameWorld().getSingleton(EntityType.PLAYER).translateY(-5)); onKey(KeyCode.S, () -> getGameWorld().getSingleton(EntityType.PLAYER).translateY(5)); onBtnDown(MouseButton.PRIMARY, () -> { double y = getGameWorld().getSingleton(EntityType.PLAYER).getY(); spawn("projectile", 0, y + 10); spawn("projectile", 0, y + 50); }); }
前两个呼叫设置了我们的玩家移动。
更具体地说,W和S键将分别向上和向下移动播放器。
我们的最后一个通话设置了玩家的动作,即射击。
当按下主鼠标按钮时,我们会生成我们先前定义的弹丸。
" spawn"函数的最后两个参数是生成弹丸的位置的x和y值。
游戏逻辑
在开始游戏之前,我们需要初始化一些游戏逻辑,我们可以执行以下操作:
@Override protected void initGame() { getGameScene().setBackgroundColor(Color.BLACK); getGameWorld().addEntityFactory(new SpaceRangerFactory()); spawn("player", 0, getAppHeight()/2 - 30); run(() -> { double x = getAppWidth(); double y = FXGLMath.random(0, getAppHeight() - 20); spawn("enemy", x, y); }, Duration.seconds(0.25)); }
我们将游戏场景背景设置为黑色(您可以根据需要选择其他颜色)。
接下来,我们添加实体工厂-FXGL需要知道如何生成我们的实体。
此后,由于x值为0,我们在屏幕左侧生成了播放器。
最后,我们设置了一个计时器动作,该动作每0.25秒运行一次。
动作是在一个随机的Y位置生成一个敌人。
物理
我们的物理代码是微不足道的,因为在我们的游戏中不会发生很多冲突。
@Override protected void initPhysics() { onCollisionBegin(EntityType.PROJECTILE, EntityType.ENEMY, (proj, enemy) -> { proj.removeFromWorld(); enemy.removeFromWorld(); }); }
从上面可以看出,我们关心的关于碰撞的仅有的两种实体类型是射弹和敌人。
我们设置了当这两种类型发生冲突时会调用的处理程序,为我们提供了对发生冲突的特定实体的引用。
请注意定义类型的顺序。
这是实体引用传递的顺序。
当它们相互碰撞时,我们希望将两者从世界中删除。
更新资料
我们的游戏循环中没有很多东西。
我们只想知道敌人何时到达我们的基地,即敌人的X值小于0。
@Override protected void onUpdate(double tpf) { var enemiesThatReachedBase = getGameWorld().getEntitiesFiltered(e -> e.isType(EntityType.ENEMY) && e.getX() < 0); if (!enemiesThatReachedBase.isEmpty()) { showMessage("Game Over!", () -> getGameController().startNewGame()); } }
为此,我们查询游戏世界以提供所有类型为敌人且X值小于0的实体。
如果列表不为空,则我们输了游戏,因此我们显示适当的消息并重新启动。