
首先,让我们为我们的简单游戏定义一些要求:
在本教程的最后,你可以获得这样的一个游戏窗口 (可能略有不同):

虽然它可能看起来不像游戏,但它将帮助你了解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应用程序,仅此而已。
在这一点上,你应该已经能够运行你的游戏,但是首先让我们调整一些设置。
@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。很简单,对吧?
下一步是在屏幕上添加一个玩家。我们将在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号需求!
现在,我们将继续执行与用户输入相关的要求。我们将输入处理代码放入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--完成了,尘埃落定。我们已经完成了一半以上,做得很好!
我们现在进入下一个位--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。继续!
为了完成最后一个要求,我们将使用游戏变量。在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);
}
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于
出于纯粹的兴趣,我很好奇如何按顺序创建PI,而不是在过程结果之后生成数字,而是让数字在过程本身生成时显示。如果是这种情况,那么数字可以自行产生,我可以对以前看到的数字实现垃圾收集,从而创建一个无限系列。结果只是在Pi系列之后每秒生成一个数字。这是我通过互联网筛选的结果:这是流行的计算机友好算法,类机器算法:defarccot(x,unity)xpow=unity/xn=1sign=1sum=0loopdoterm=xpow/nbreakifterm==0sum+=sign*(xpow/n)xpow/=x*xn+=2sign=-signendsumenddefcalc_pi(digits
我正在尝试使用ruby和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我
我正在使用i18n从头开始构建一个多语言网络应用程序,虽然我自己可以处理一大堆yml文件,但我说的语言(非常)有限,最终我想寻求外部帮助帮助。我想知道这里是否有人在使用UI插件/gem(与django上的django-rosetta不同)来处理多个翻译器,其中一些翻译器不愿意或无法处理存储库中的100多个文件,处理语言数据。谢谢&问候,安德拉斯(如果您已经在rubyonrails-talk上遇到了这个问题,我们深表歉意) 最佳答案 有一个rails3branchofthetolkgem在github上。您可以通过在Gemfi
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t