JavaFX 2 GameTutorial第2部分
Ť他的是一系列与一个JavaFX 2游戏教程博客条目的第二批。 如果您尚未阅读第1部分,请参阅JavaFX 2游戏教程的简介部分。 在第1部分中,我提到了游戏的某些方面以及原型飞船的简单演示(原型由简单的形状组成),该飞船能够通过鼠标进行导航。 免责声明 :这是一个很长的教程,因此如果您只想运行演示,请单击“ 此处” 。 该演示称为Atom Smasher,您可以在其中生成碰撞的原子(球体)。 您可以冻结游戏以添加更多原子。 目的是使一个以上的原子活着并反弹。 文本显示当前漂浮的原子数。 在开始讨论游戏循环之前,我想向您介绍一些有关游戏和动画的背景历史。
历史
早在20世纪80年代至90年代期间,许多尝试制作图像动画的游戏程序员都曾遇到过臭名昭著的屏幕闪烁问题。 这是你的精灵 (图形图像)往往会闪烁,使游戏看起来相当可怕的。 所有监视器的刷新率均以一定间隔重绘像素(称为垂直回描CRT )。 例如,如果刷新率是80 Hz,则刷新率大约是每秒80次,屏幕将重新绘制。 如果要在屏幕上进行修改,则由于处于刷新间隔的中间,通常可能会不同步。 你应该怎么做? 好吧,实际上有两件事可以帮助解决这个问题(双缓冲和知道周期何时发生)。 一些聪明的开发人员创建了一种称为双缓冲的技术。 双重缓冲是一种由两个表面组成的技术,其中两个表面依次轮流变为可显示的表面,另一个表面外的区域(缓冲表面)。 这种技术实际上是一种数字技巧,开发人员可以在其中预先计算要在屏幕外表面绘制的精灵及其位置。 一旦在屏幕外缓冲区上完成绘制,代码便会将其切换为可显示的表面。 需要指出的重要一点是,由于在刷新间隔即将开始重绘过程时需要通知我们,因此仍然存在问题。 在Java中,此功能是通过BufferStrategy API内置的。 那么,我要去哪里呢? 有时解释过去的策略将有助于我们欣赏今天的状况。 我们需要在JavaFX中执行此操作吗? 不。 不用担心JavaFX在这里! 我已经提到的所有问题都通过使用JavaFX的场景图API来解决。 但是,大多数游戏仍将使用旧的方式来制作图形动画和更新游戏世界,称为“ 游戏循环” 。
游戏循环
简单地说,游戏循环负责更新子画面(图形),检查碰撞和清理。 较早的游戏循环将在循环中检查按键和鼠标事件。 由于JavaFX对事件进行抽象以允许Scene或单个节点处理事件,因此在我们的游戏循环中无需侦听低级事件。 下面显示的是典型游戏循环的源代码片段,该循环将在每个周期更新精灵,检查碰撞和清理精灵。 您会注意到JavaFX 2.x中的Duration对象,该对象表示60除以1000毫秒或每秒60帧(FPS)。 每个帧都会调用JavaFX的EventHandler接口的handle()方法,以更新游戏世界。 假设地,我创建了三个方法updateSprites() , checkCollisions()和cleanupSprites() ,它们将被调用以处理游戏中的精灵。
final Duration oneFrameAmt = Duration.millis(1000/60); final KeyFrame oneFrame = new KeyFrame(oneFrameAmt, new EventHandler() { @Override public void handle(javafx.event.ActionEvent event) { // update actors updateSprites(); // check for collision checkCollisions(); // removed dead things cleanupSprites(); } }); // oneFrame // sets the game world's game loop (Timeline) TimelineBuilder.create() .cycleCount(Animation.INDEFINITE) .keyFrames(oneFrame) .build() .play();
上面的代码片段实际上是创建简单游戏或动画所需的全部。 但是,您可能希望将事情带到一个新的高度。 您可能想要创建一个可以管理精灵和游戏世界状态的游戏引擎。
游戏引擎
游戏引擎是负责封装游戏世界,运行游戏循环,管理子画面,物理等的实用程序或库的奇特名称。这实际上是一个小型游戏框架,允许您扩展或重用,因此您无需从头开始创建2D游戏时必须重新发明轮子。 为了快速前进,我创建了游戏引擎设计的UML类图。
下面显示的是图1 JavaFX Game Engine类图。
|
图1. JavaFX 2游戏引擎设计 |
在图1 JavaFX 2游戏引擎设计中,您会注意到GameWorld , SpriteManager和Sprite这三个类。 GameWorld类负责初始化游戏状态,执行游戏循环,更新精灵,处理精灵冲突以及清理。 接下来是SpriteManager类,该类负责通过添加,删除以及其他内部冲突管理来管理Sprite。 最后是Sprite类,它负责维护图像(演员)的状态。 在2D世界中,子画面可以包含对象的速度,旋转,场景节点或最终在每个周期(关键帧/秒)渲染的图像。
请快速提醒一下UML表示法:
- 加号“ + ”表示班级成员是公开的。
- 减号“ – ”表示班级成员是私人的
- 哈希符号“ # ”表示类成员受到保护。
游戏世界
下面是GameWorld类的源代码实现。 单击以展开。 稍后,您将看到一个类图,该类图描述了将扩展GameWorld类的简单演示游戏(请参阅AtomSmasher)。
package carlfx.gameengine; import javafx.animation.Animation; import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.animation.TimelineBuilder; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.scene.Group; import javafx.scene.Scene; import javafx.stage.Stage; import javafx.util.Duration; /** * This application demonstrates a JavaFX 2.x Game Loop. * Shown below are the methods which comprise of the fundamentals to a * simple game loop in JavaFX: * * <strong>initialize()</strong> - Initialize the game world. * <strong>beginGameLoop()</strong> - Creates a JavaFX Timeline object containing the game life cycle. * <strong>updateSprites()</strong> - Updates the sprite objects each period (per frame) * <strong>checkCollisions()</strong> - Method will determine objects that collide with each other. * <strong>cleanupSprites()</strong> - Any sprite objects needing to be removed from play. * * @author cdea */ public abstract class GameWorld { /** The JavaFX Scene as the game surface */ private Scene gameSurface; /** All nodes to be displayed in the game window. */ private Group sceneNodes; /** The game loop using JavaFX's <code>Timeline</code> API.*/ private static Timeline gameLoop; /** Number of frames per second. */ private final int framesPerSecond; /** Title in the application window.*/ private final String windowTitle; /** * The sprite manager. */ private final SpriteManager spriteManager = new SpriteManager(); /** * Constructor that is called by the derived class. This will * set the frames per second, title, and setup the game loop. * @param fps - Frames per second. * @param title - Title of the application window. */ public GameWorld(final int fps, final String title) { framesPerSecond = fps; windowTitle = title; // create and set timeline for the game loop buildAndSetGameLoop(); } /** * Builds and sets the game loop ready to be started. */ protected final void buildAndSetGameLoop() { final Duration oneFrameAmt = Duration.millis(1000/getFramesPerSecond()); final KeyFrame oneFrame = new KeyFrame(oneFrameAmt, new EventHandler() { @Override public void handle(javafx.event.ActionEvent event) { // update actors updateSprites(); // check for collision checkCollisions(); // removed dead things cleanupSprites(); } }); // oneFrame // sets the game world's game loop (Timeline) setGameLoop(TimelineBuilder.create() .cycleCount(Animation.INDEFINITE) .keyFrames(oneFrame) .build()); } /** * Initialize the game world by update the JavaFX Stage. * @param primaryStage */ public abstract void initialize(final Stage primaryStage); /**Kicks off (plays) the Timeline objects containing one key frame * that simply runs indefinitely with each frame invoking a method * to update sprite objects, check for collisions, and cleanup sprite * objects. * */ public void beginGameLoop() { getGameLoop().play(); } /** * Updates each game sprite in the game world. This method will * loop through each sprite and passing it to the handleUpdate() * method. The derived class should override handleUpdate() method. * */ protected void updateSprites() { for (Sprite sprite:spriteManager.getAllSprites()){ handleUpdate(sprite); } } /** Updates the sprite object's information to position on the game surface. * @param sprite - The sprite to update. */ protected void handleUpdate(Sprite sprite) { } /** * Checks each game sprite in the game world to determine a collision * occurred. The method will loop through each sprite and * passing it to the handleCollision() * method. The derived class should override handleCollision() method. * */ protected void checkCollisions() { // check other sprite's collisions spriteManager.resetCollisionsToCheck(); // check each sprite against other sprite objects. for (Sprite spriteA:spriteManager.getCollisionsToCheck()){ for (Sprite spriteB:spriteManager.getAllSprites()){ if (handleCollision(spriteA, spriteB)) { // The break helps optimize the collisions // The break statement means one object only hits another // object as opposed to one hitting many objects. // To be more accurate comment out the break statement. break; } } } } /** * When two objects collide this method can handle the passed in sprite * objects. By default it returns false, meaning the objects do not * collide. * @param spriteA - called from checkCollision() method to be compared. * @param spriteB - called from checkCollision() method to be compared. * @return boolean True if the objects collided, otherwise false. */ protected boolean handleCollision(Sprite spriteA, Sprite spriteB) { return false; } /** * Sprites to be cleaned up. */ protected void cleanupSprites() { spriteManager.cleanupSprites(); } /** * Returns the frames per second. * @return int The frames per second. */ protected int getFramesPerSecond() { return framesPerSecond; } /** * Returns the game's window title. * @return String The game's window title. */ public String getWindowTitle() { return windowTitle; } /** * The game loop (Timeline) which is used to update, check collisions, and * cleanup sprite objects at every interval (fps). * @return Timeline An animation running indefinitely representing the game * loop. */ protected static Timeline getGameLoop() { return gameLoop; } /** * The sets the current game loop for this game world. * @param gameLoop Timeline object of an animation running indefinitely * representing the game loop. */ protected static void setGameLoop(Timeline gameLoop) { GameWorld.gameLoop = gameLoop; } /** * Returns the sprite manager containing the sprite objects to * manipulate in the game. * @return SpriteManager The sprite manager. */ protected SpriteManager getSpriteManager() { return spriteManager; } /** * Returns the JavaFX Scene. This is called the game surface to * allow the developer to add JavaFX Node objects onto the Scene. * @return */ public Scene getGameSurface() { return gameSurface; } /** * Sets the JavaFX Scene. This is called the game surface to * allow the developer to add JavaFX Node objects onto the Scene. * @param gameSurface The main game surface (JavaFX Scene). */ protected void setGameSurface(Scene gameSurface) { this.gameSurface = gameSurface; } /** * All JavaFX nodes which are rendered onto the game surface(Scene) is * a JavaFX Group object. * @return Group The root containing many child nodes to be displayed into * the Scene area. */ public Group getSceneNodes() { return sceneNodes; } /** * Sets the JavaFX Group that will hold all JavaFX nodes which are rendered * onto the game surface(Scene) is a JavaFX Group object. * @param sceneNodes The root container having many children nodes * to be displayed into the Scene area. */ protected void setSceneNodes(Group sceneNodes) { this.sceneNodes = sceneNodes; } }
精灵管理器
Sprite Manager类是帮助程序类,可帮助游戏循环跟踪Sprite。 通常,一个精灵管理器将包含所有精灵,每个精灵都包含一个JavaFX节点,该节点显示在“场景”图上。
下面显示的是源代码。 单击以展开。
package carlfx.gameengine; import java.util.*; /** * Sprite manager is responsible for holding all sprite objects, and cleaning up * sprite objects to be removed. All collections are used by the JavaFX * application thread. During each cycle (animation frame) sprite management * occurs. This assists the user of the API to not have to create lists to * later be garbage collected. Should provide some performance gain. * @author cdea */ public class SpriteManager { /** All the sprite objects currently in play */ private final static List GAME_ACTORS = new ArrayList<>(); /** A global single threaded list used to check collision against other * sprite objects. */ private final static List CHECK_COLLISION_LIST = new ArrayList<>(); /** A global single threaded set used to cleanup or remove sprite objects * in play. */ private final static Set CLEAN_UP_SPRITES = new HashSet<>(); /** */ public List getAllSprites() { return GAME_ACTORS; } /** * VarArgs of sprite objects to be added to the game. * @param sprites */ public void addSprites(Sprite... sprites) { GAME_ACTORS.addAll(Arrays.asList(sprites)); } /** * VarArgs of sprite objects to be removed from the game. * @param sprites */ public void removeSprites(Sprite... sprites) { GAME_ACTORS.removeAll(Arrays.asList(sprites)); } /** Returns a set of sprite objects to be removed from the GAME_ACTORS. * @return CLEAN_UP_SPRITES */ public Set getSpritesToBeRemoved() { return CLEAN_UP_SPRITES; } /** * Adds sprite objects to be removed * @param sprites varargs of sprite objects. */ public void addSpritesToBeRemoved(Sprite... sprites) { if (sprites.length > 1) { CLEAN_UP_SPRITES.addAll(Arrays.asList((Sprite[]) sprites)); } else { CLEAN_UP_SPRITES.add(sprites[0]); } } /** * Returns a list of sprite objects to assist in collision checks. * This is a temporary and is a copy of all current sprite objects * (copy of GAME_ACTORS). * @return CHECK_COLLISION_LIST */ public List getCollisionsToCheck() { return CHECK_COLLISION_LIST; } /** * Clears the list of sprite objects in the collision check collection * (CHECK_COLLISION_LIST). */ public void resetCollisionsToCheck() { CHECK_COLLISION_LIST.clear(); CHECK_COLLISION_LIST.addAll(GAME_ACTORS); } /** * Removes sprite objects and nodes from all * temporary collections such as: * CLEAN_UP_SPRITES. * The sprite to be removed will also be removed from the * list of all sprite objects called (GAME_ACTORS). */ public void cleanupSprites() { // remove from actors list GAME_ACTORS.removeAll(CLEAN_UP_SPRITES); // reset the clean up sprites CLEAN_UP_SPRITES.clear(); } }
雪碧
Sprite类表示要显示在JavaFX Scene图形上的图像或节点。 在2D游戏中,子画面将包含其他信息,例如,对象在场景区域中移动时对象的速度。 游戏循环将在关键帧的每个时间间隔调用update()和collide()方法。
下面显示的是源代码。 单击以展开。
package carlfx.gameengine; import java.util.ArrayList; import java.util.List; import javafx.animation.Animation; import javafx.scene.Node; /** * The Sprite class represents a image or node to be displayed. * In a 2D game a sprite will contain a velocity for the image to * move across the scene area. The game loop will call the update() * and collide() method at every interval of a key frame. A list of * animations can be used during different situations in the game * such as rocket thrusters, walking, jumping, etc. * @author cdea */ public abstract class Sprite { /** Animation for the node */ public List animations = new ArrayList<>(); /** Current display node */ public Node node; /** velocity vector x direction */ public double vX = 0; /** velocity vector y direction */ public double vY = 0; /** dead? */ public boolean isDead = false; /** * Updates this sprite object's velocity, or animations. */ public abstract void update(); /** * Did this sprite collide into the other sprite? * * @param other - The other sprite. * @return */ public boolean collide(Sprite other) { return false; } }
JavaFX 2游戏循环演示– Atom Smasher
ew! 如果您已经走了这么远,那么您就是一个勇敢的灵魂。 让我们休息一下,尝试使用上面的游戏引擎创建的演示。
下面显示的是Java Webstart按钮,用于启动游戏演示。 稍后,您将看到设计和源代码,详细说明了它是如何创建的。
要求:
- Java 7或更高版本
- JavaFX
2.0.22.1或更高版本 - Windows XP或更高版本(应该很快可用于Linux / MacOS)
|
演示版 |
GameLoopPart2设计
下面是一个名为Atom Smasher的游戏演示的类图,它使用前面提到的游戏引擎框架。
下面显示的是图2 Atom Smasher类图。
|
图2. Atom Smasher类图 |
GameLoopPart2
GameLoopPart2是运行游戏的驱动程序或主要JavaFX应用程序。 这将创建一个要初始化的GameWorld对象,并开始游戏循环。
下面显示的是源代码。 单击以展开。
package carlfx; import carlfx.gameengine.GameWorld; import javafx.application.Application; import javafx.stage.Stage; /** * The main driver of the game. * @author cdea */ public class GameLoopPart2 extends Application { GameWorld gameWorld = new AtomSmasher(60, "JavaFX 2 GameTutorial Part 2 - Game Loop"); /** * @param args the command line arguments */ public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { // setup title, scene, stats, controls, and actors. gameWorld.initialize(primaryStage); // kick off the game loop gameWorld.beginGameLoop(); // display window primaryStage.show(); } }
原子粉碎机
AtomSmasher是GameWorld类的派生类。 它创建了许多以随机速度,颜色和位置进行动画处理的球。 按钮控件使用户可以生成更多的“原子”(JavaFX Circle节点)。 当每个原子相互碰撞时,它们将调用implode()方法,该方法生成淡入淡出过渡动画。 您将注意到,仅通过实现initialize(),handleUpdate(),handleCollision()和cleanupSprites()方法即可轻松实现此游戏。 一旦实现,游戏引擎就会完成其余的工作。 initialize()方法为用户创建按钮控件。 要更新精灵的位置或更改游戏状态,您将实现handleUpdate()方法。 要比较所有精灵相互碰撞的情况,您将实现handleCollision() 。 游戏循环生命周期的最后一部分是清理精灵。 清理意味着更新精灵管理器并更新JavaFX Scene(删除节点)。
下面显示的是源代码。 单击以展开。
package carlfx; import carlfx.gameengine.GameWorld; import carlfx.gameengine.Sprite; import java.util.Random; import javafx.animation.Timeline; import javafx.event.EventHandler; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.control.ButtonBuilder; import javafx.scene.control.Label; import javafx.scene.input.MouseEvent; import javafx.scene.layout.HBoxBuilder; import javafx.scene.layout.VBox; import javafx.scene.layout.VBoxBuilder; import javafx.scene.paint.Color; import javafx.scene.shape.Circle; import javafx.stage.Stage; import static javafx.animation.Animation.Status.RUNNING; import static javafx.animation.Animation.Status.STOPPED; /** * This is a simple game world simulating a bunch of spheres looking * like atomic particles colliding with each other. When the game loop begins * the user will notice random spheres (atomic particles) floating and * colliding. The user is able to press a button to generate more * atomic particles. Also, the user can freeze the game. * * @author cdea */ public class AtomSmasher extends GameWorld { /** Read only field to show the number of sprite objects are on the field*/ private final static Label NUM_SPRITES_FIELD = new Label(); public AtomSmasher(int fps, String title){ super(fps, title); } /** * Initialize the game world by adding sprite objects. * @param primaryStage */ @Override public void initialize(final Stage primaryStage) { // Sets the window title primaryStage.setTitle(getWindowTitle()); // Create the scene setSceneNodes(new Group()); setGameSurface(new Scene(getSceneNodes(), 640, 580)); primaryStage.setScene(getGameSurface()); // Create many spheres generateManySpheres(150); // Display the number of spheres visible. // Create a button to add more spheres. // Create a button to freeze the game loop. final Timeline gameLoop = getGameLoop(); VBox stats = VBoxBuilder.create() .spacing(5) .translateX(10) .translateY(10) .children(HBoxBuilder.create() .spacing(5) .children(new Label("Number of Particles: "), // show no. particles NUM_SPRITES_FIELD).build(), // button to build more spheres ButtonBuilder.create() .text("Regenerate") .onMousePressed(new EventHandler() { @Override public void handle(MouseEvent arg0) { generateManySpheres(150); }}).build(), // button to freeze game loop ButtonBuilder.create() .text("Freeze/Resume") .onMousePressed(new EventHandler() { @Override public void handle(MouseEvent arg0) { switch (gameLoop.getStatus()) { case RUNNING: gameLoop.stop(); break; case STOPPED: gameLoop.play(); break; } }}).build() ).build(); // (VBox) stats on children // lay down the controls getSceneNodes().getChildren().add(stats); } /** * Make some more space spheres (Atomic particles) */ private void generateManySpheres(int numSpheres) { Random rnd = new Random(); Scene gameSurface = getGameSurface(); for (int i=0; i (gameSurface.getWidth() - (circle.getRadius() * 2))) { newX = gameSurface.getWidth() - (circle.getRadius() * 2); } // check for the bottom of screen the height newY is greater than height // minus radius times 2(height of sprite) double newY = rnd.nextInt((int) gameSurface.getHeight()); if (newY > (gameSurface.getHeight() - (circle.getRadius() * 2))) { newY = gameSurface.getHeight() - (circle.getRadius() * 2); } circle.setTranslateX(newX); circle.setTranslateY(newY); circle.setVisible(true); circle.setId(b.toString()); // add to actors in play (sprite objects) getSpriteManager().addSprites(b); // add sprite's getSceneNodes().getChildren().add(0, b.node); } } /** * Each sprite will update it's velocity and bounce off wall borders. * @param sprite - An atomic particle (a sphere). */ @Override protected void handleUpdate(Sprite sprite) { if (sprite instanceof Atom) { Atom sphere = (Atom) sprite; // advance the spheres velocity sphere.update(); // bounce off the walls when outside of boundaries if (sphere.node.getTranslateX() > (getGameSurface().getWidth() - sphere.node.getBoundsInParent().getWidth()) || sphere.node.getTranslateX() < 0 ) { sphere.vX = sphere.vX * -1; } if (sphere.node.getTranslateY() > getGameSurface().getHeight()- sphere.node.getBoundsInParent().getHeight() || sphere.node.getTranslateY() < 0) { sphere.vY = sphere.vY * -1; } } } /** * How to handle the collision of two sprite objects. Stops the particle * by zeroing out the velocity if a collision occurred. * @param spriteA * @param spriteB * @return */ @Override protected boolean handleCollision(Sprite spriteA, Sprite spriteB) { if (spriteA.collide(spriteB)) { ((Atom)spriteA).implode(this); ((Atom)spriteB).implode(this); getSpriteManager().addSpritesToBeRemoved(spriteA, spriteB); return true; } return false; } /** * Remove dead things. */ @Override protected void cleanupSprites() { // removes from the scene and backend store super.cleanupSprites(); // let user know how many sprites are showing. NUM_SPRITES_FIELD.setText(String.valueOf(getSpriteManager().getAllSprites().size())); } }
原子
Atom类是Sprite类的扩展。 原子是一个精灵,看起来像是在整个场景中移动的球形物体。 原子将具有随机的半径,颜色和速度。 当每个原子精灵与另一个原子碰撞时,它们将为淡入淡出过渡设置动画(implode()方法)。
下面显示的是源代码。 单击以展开。
package carlfx; import carlfx.gameengine.GameWorld; import carlfx.gameengine.Sprite; import javafx.animation.FadeTransitionBuilder; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.scene.paint.Color; import javafx.scene.paint.RadialGradient; import javafx.scene.paint.RadialGradientBuilder; import javafx.scene.paint.Stop; import javafx.scene.shape.Circle; import javafx.scene.shape.CircleBuilder; import javafx.util.Duration; /** * A spherical looking object (Atom) with a random radius, color, and velocity. * When two atoms collide each will fade and become removed from the scene. The * method called implode() implements a fade transition effect. * * @author cdea */ public class Atom extends Sprite { public Atom(double radius, Color fill) { Circle sphere = CircleBuilder.create() .centerX(radius) .centerY(radius) .radius(radius) .cache(true) .build(); RadialGradient rgrad = RadialGradientBuilder.create() .centerX(sphere.getCenterX() - sphere.getRadius() / 3) .centerY(sphere.getCenterY() - sphere.getRadius() / 3) .radius(sphere.getRadius()) .proportional(false) .stops(new Stop(0.0, fill), new Stop(1.0, Color.BLACK)) .build(); sphere.setFill(rgrad); // set javafx node to a circle node = sphere; } /** * Change the velocity of the atom particle. */ @Override public void update() { node.setTranslateX(node.getTranslateX() + vX); node.setTranslateY(node.getTranslateY() + vY); } @Override public boolean collide(Sprite other) { if (other instanceof Atom) { return collide((Atom)other); } return false; } /** * When encountering another Atom to determine if they collided. * @param other Another atom * @return boolean true if this atom and other atom has collided, * otherwise false. */ private boolean collide(Atom other) { // if an object is hidden they didn't collide. if (!node.isVisible() || !other.node.isVisible() || this == other) { return false; } // determine it's size Circle otherSphere = other.getAsCircle(); Circle thisSphere = getAsCircle(); double dx = otherSphere.getTranslateX() - thisSphere.getTranslateX(); double dy = otherSphere.getTranslateY() - thisSphere.getTranslateY(); double distance = Math.sqrt( dx * dx + dy * dy ); double minDist = otherSphere.getRadius() + thisSphere.getRadius() + 3; return (distance < minDist); } /** * Returns a node casted as a JavaFX Circle shape. * @return Circle shape representing JavaFX node for convenience. */ public Circle getAsCircle() { return (Circle) node; } /** * Animate an implosion. Once done remove from the game world * @param gameWorld - game world */ public void implode(final GameWorld gameWorld) { vX = vY = 0; FadeTransitionBuilder.create() .node(node) .duration(Duration.millis(300)) .fromValue(node.getOpacity()) .toValue(0) .onFinished(new EventHandler() { @Override public void handle(ActionEvent arg0) { isDead = true; gameWorld.getSceneNodes().getChildren().remove(node); } }) .build() .play(); } }
结论
希望您有机会了解游戏循环的基础,并在以后通过实施强大的游戏引擎来应用这些知识。 虽然,我简要提到了碰撞,但我将其保存在这些教程的第4部分中。 请继续关注第3部分,我们将在此使用键盘或鼠标进行输入。 随时尝试。 让我知道你的想法。
要获取源代码,请使用浏览器中的“ 将链接另存为 ”选项, 将链接下载到以下jar文件中。 如果您使用的是Windows系统,则可以将扩展名从jar更改为zip以便轻松扩展。 它将包含带有源代码的目录“ src ”。
源代码位置 :
http://www.jroller.com/carldea/resource/javafx2.0_games/part2source_code.jar
源代码的发布版本位于GitHub上,名为( JFXGen ),供您克隆并复制到您的心中内容(可以在其中用于自己的项目)。 请享用。
https://github.com/carldea/JFXGen
git [email protected]:carldea / JFXGen.git
参考:来自我们的JCG合作伙伴 Carl Dea的JavaFX 2 GameTutorial第2部分 ,位于Carl's FX Blog博客上。
翻译自: https://www.javacodegeeks.com/2012/05/javafx-2-gametutorial-part-2.html