游戏要求
首先,让我们为我们的简单游戏定义一些要求:
- 一个600x600的窗口。
- 屏幕上的玩家,由蓝色矩形表示。
- 可以通过按键盘上的W、S、A或D来移动玩家。
- UI由一行文本表示。
- 当玩家移动时,UI文本会更新以显示玩家在其生命周期内移动了多少像素。
在本教程的最后,你可以获得这样的一个游戏窗口 (可能略有不同):
虽然它可能看起来不像游戏,但它将帮助你了解FXGL的基本功能。完成本教程后,你可以构建各种简单的游戏。
准备工作
既然我们对游戏的期望有一个大致的概念,我们可以回到集成开发环境,为我们的游戏创建一个包。
注意: 目录结构类似于Maven目录结构,但是,如果你不知道这是什么,请不要担心。我们将在稍后阶段介绍结构。此时,将 src
作为主源目录就足够了。
我要用tutorial
作为包名称。
-
在你的IDE中创建包
tutorial
. -
在package中,使用
BasicGameApp
名称创建Java类.
通常,在你的main()所在的类上附加 "App"。这可以让其他开发者轻松识别你的游戏的主要入口在哪里。为了使你接下来的步骤更容易,我建议你打开你的BasicGameApp
类并添加这些导入。
import com.almasb.fxgl.app.GameApplication;
import com.almasb.fxgl.app.GameSettings;
import com.almasb.fxgl.dsl.FXGL;
import com.almasb.fxgl.entity.Entity;
import com.almasb.fxgl.input.Input;
import com.almasb.fxgl.input.UserAction;
import javafx.scene.input.KeyCode;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import java.util.Map;
复制代码
我们现在可以开始编写我们的代码了
编码阶段
为了使用FXGL,你的App
类需要继承GameApplication
并重写initSettings()
方法:
public class BasicGameApp extends GameApplication {
@Override
protected void initSettings(GameSettings settings) {}
}
复制代码
一旦继承GameApplication
,大多数IDEs将自动生成重写方法。现在我们希望能够开始游戏。为此,只需添加以下内容:
public static void main(String[] args) {
launch(args);
}
复制代码
如果你以前使用过JavaFX,那么你会注意到,它与我们用来启动JavaFX应用程序的方法完全相同。简而言之,FXGL是一个具有游戏开发功能的JavaFX应用程序,仅此而已。
需求1 (窗口)
在这一点上,你应该已经能够运行你的游戏,但是首先让我们调整一些设置。
@Override
protected void initSettings(GameSettings settings) {
settings.setWidth(600);
settings.setHeight(600);
settings.setTitle("Basic Game App");
settings.setVersion("0.1");
}
复制代码
正如你所看到的,所有的设置都是在initSettings()
中配置的。一旦设置好了,在运行时就不能改变设置。现在你可以在你的IDE中点击 "run",它应该以600x600的窗口和 "Basic Game App "为标题启动游戏。
我们现在达到了我们的需求1。很简单,对吧?
需求 2 (Player)
下一步是在屏幕上添加一个玩家。我们将在initGame()
中完成这一工作。简而言之,这是你设置游戏开始前需要准备的所有东西的地方。
private Entity player;
@Override
protected void initGame() {
player = FXGL.entityBuilder()
.at(300, 300)
.view(new Rectangle(25, 25, Color.BLUE))
.buildAndAttach();
}
复制代码
(注意:对于保存/加载系统来说,我们不在声明时初始化实例级字段,而是在'initGame()'中进行初始化,这一点很重要。)
如果你不熟悉函数式 API,那么上面的代码是很难一下子接受的。所以我们要慢慢开始。
接下来讲解一下上述代码:
-
有一个名为
player
的实例级字段,其类型为Entity
。 -
一个实体基本上就是一个游戏对象。这就是你现在需要知道的一切。
-
FXGL.entityBuilder()
是构建实体的首选方式。 -
通过调用
.at()
,我们将实体定位到我们想要的位置。在这个例子中,它是x = 300,y = 300。
(注意: 实体在FXGL中的位置是其左上角,就像在JavaFX中一样。)
然后我们告诉构建器,通过使用我们传入的UI节点作为参数来创建实体的视图。这里是一个标准的JavaFX Rectangle
,width=25
,height =25
,颜色为蓝色。
(注意:你可以使用任何基于JavaFX节点的对象,这非常酷。 😄)
最后,我们调用.buildAndAttach()
方法。通过调用build,我们可以获得我们正在构建的实体的引用。至于 "attach "部分,它可以方便地将构建的实体直接连接到游戏世界中。如果你运行游戏,你现在应该在屏幕中心附近看到一个蓝色的矩形。
太好了,我们刚刚完成了2号需求!
需求3 (输入)
现在,我们将继续执行与用户输入相关的要求。我们将输入处理代码放入initInput()
中。下面的代码显示了用于添加输入操作的所有API。考虑之后,我们将看到如何使用更简单的API。
We will now proceed with the requirement related to user input. We put the input handling code in initInput()
. The below code shows the "full" API for adding an input action. After considering it, we will see how to use simpler API.pixels
@Override
protected void initInput() {
Input input = FXGL.getInput();
input.addAction(new UserAction("右移") {
@Override
protected void onAction() {
player.translateX(5); // 向右移动5个像素
}
}, KeyCode.D);
}
复制代码
让我们逐行浏览这个片段。
首先得到输入对象。正如你所注意到的,要使用大部分的FXGL功能,你需要做的就是调用FXGL.***,你的IDE会显示你可以调用的所有功能。
接下来,我们添加一个动作,然后是一个按键代码。同样,如果你以前使用过JavaFX,那么你就会知道,这些键码与事件处理程序中使用的键码完全相同。我们在说:当'D'被按下时,做我们所创建的动作。现在让我们来看看动作本身。
当我们创建一个动作时,我们也给它一个名字--"右移"。这很重要,因为这个名字会直接反馈给控件和菜单系统,用户可以随时改变它们。所以这个名字必须对用户有意义,而且是唯一的。一旦我们创建了这个动作,我们就覆盖它的一个方法(这次是onAction()),并提供一些代码。该代码将在动作发生时被调用,即当 "D "被按下时。
回顾一下需求,我们想要移动玩家。所以当'D'被按下时,我们想把Player向右移动。我们调用player.translateX(5),将其X坐标平移5像素。
(注意: translate是计算机图形学中使用的术语,意思是移动。)
现在让我们缩短它:
@Override
protected void initInput() {
FXGL.onKey(KeyCode.D, () -> {
player.translateX(5); // 向右移动5个像素
});
}
复制代码
这也导致玩家实体向右移动5个像素,并且 (几乎) 等同于我们之前编写的代码。但是,我认为你会同意此API (称为DSL) 更加简洁。如果导入com.almasb.fxgl.dsl.FXGL.*,这可以进一步缩短,即:
import static com.almasb.fxgl.dsl.FXGL.*;
复制代码
然而,为了避免引入太多的新概念,我们现在还不会这么做。你可能会猜测其余的输入代码会是什么样子,但以防万一,以下是所有的代码
@Override
protected void initInput() {
FXGL.onKey(KeyCode.D, () -> {
player.translateX(5); // move right 5 pixels
});
FXGL.onKey(KeyCode.A, () -> {
player.translateX(-5); // move left 5 pixels
});
FXGL.onKey(KeyCode.W, () -> {
player.translateY(-5); // move up 5 pixels
});
FXGL.onKey(KeyCode.S, () -> {
player.translateY(5); // move down 5 pixels
});
}
复制代码
需求3--完成了,尘埃落定。我们已经完成了一半以上,做得很好!
需求 4 (UI)
我们现在进入下一个位--UI,你或许已经猜到了,initUI()
。
@Override
protected void initUI() {
Text textPixels = new Text();
textPixels.setTranslateX(50); // x = 50
textPixels.setTranslateY(100); // y = 100
FXGL.getGameScene().addUINode(textPixels); // add to the scene graph
}
复制代码
对于大多数UI对象,我们只是使用JavaFX对象,因为没有必要重新发明轮子。你应该注意到,当我们在世界中添加一个实体时,游戏场景接收到了该实体有一个与之相关的视图这一事实。因此,游戏场景神奇地将该实体添加到场景图中。对于UI对象,我们要负责将其添加到场景图中,我们可以通过调用getGameScene().addUINode()
方法来实现。
这就是需求4。继续!
需求5 (Gameplay)
为了完成最后一个要求,我们将使用游戏变量。在FXGL中,可以从游戏的任何部分访问和修改游戏变量。从某种意义上说,它是一个全局变量,其范围与FXGL游戏实例相关联。此外,这些变量可以绑定到(类似于JavaFX属性)。我们从创建这样一个变量开始:
@Override
protected void initGameVars(Map<String, Object> vars) {
vars.put("pixelsMoved", 0);
}
复制代码
然后我们需要在玩家移动时更新变量。我们可以在输入处理部分执行此操作。
FXGL.onKey(KeyCode.D, () -> {
player.translateX(5); // move right 5 pixels
FXGL.inc("pixelsMoved", +5);
});
复制代码
我会让你对剩下的动作做同样的事情 (左、上、下)。最后一步 (对于需求和教程) 是将我们的UI文本对象绑定到变量pixelsMoved
。在initUI()
一旦我们创建了textPixels
对象,我们可以执行以下操作:
textPixels.textProperty().bind(FXGL.getWorldProperties().intProperty("pixelsMoved").asString());
复制代码
之后,UI文本将显示播放器自动移动了多少像素。
你现在有了一个基本的FXGL游戏。希望你玩得开心。下面是本教程的全部源代码,所有FXGL.*调用都是静态导入的。
package tutorial;
import com.almasb.fxgl.app.GameApplication;
import com.almasb.fxgl.app.GameSettings;
import com.almasb.fxgl.dsl.FXGL;
import com.almasb.fxgl.entity.Entity;
import javafx.scene.input.KeyCode;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import java.util.Map;
import static com.almasb.fxgl.dsl.FXGL.*;
public class BasicGameApp extends GameApplication {
@Override
protected void initSettings(GameSettings settings) {
settings.setWidth(600);
settings.setHeight(600);
settings.setTitle("Basic Game App");
settings.setVersion("0.1");
}
@Override
protected void initInput() {
onKey(KeyCode.D, () -> {
player.translateX(5); // move right 5 pixels
inc("pixelsMoved", +5);
});
onKey(KeyCode.A, () -> {
player.translateX(-5); // move left 5 pixels
inc("pixelsMoved", -5);
});
onKey(KeyCode.W, () -> {
player.translateY(-5); // move up 5 pixels
inc("pixelsMoved", +5);
});
onKey(KeyCode.S, () -> {
player.translateY(5); // move down 5 pixels
inc("pixelsMoved", +5);
});
}
@Override
protected void initGameVars(Map<String, Object> vars) {
vars.put("pixelsMoved", 0);
}
private Entity player;
@Override
protected void initGame() {
player = entityBuilder()
.at(300, 300)
.view(new Rectangle(25, 25, Color.BLUE))
.buildAndAttach();
}
@Override
protected void initUI() {
Text textPixels = new Text();
textPixels.setTranslateX(50); // x = 50
textPixels.setTranslateY(100); // y = 100
textPixels.textProperty().bind(getWorldProperties().intProperty("pixelsMoved").asString());
getGameScene().addUINode(textPixels); // add to the scene graph
}
public static void main(String[] args) {
launch(args);
}