Статьи

JavaFX 2 GameTutorial, часть 2

Введение

Это вторая часть серии записей блога, относящихся к учебному пособию по JavaFX 2. Если вы не читали Часть 1, ознакомьтесь с разделом введения в Учебное пособие по JavaFX 2. Напомним, что в первой части я упомяну некоторые аспекты игрового процесса и простую демонстрацию опытного космического корабля (состоящего из простых фигур), способного перемещаться с помощью мыши. Отказ от ответственности : это длинный урок, поэтому, если вы просто хотите запустить демонстрацию, просто нажмите здесь . Демо называется Atom Smasher, где вы генерируете атомы (сферы), которые сталкиваются. Вы можете заморозить игру, чтобы добавить больше атомов. Цель состоит в том, чтобы иметь более одного атома живым и подпрыгивающим. Текст отображает текущее количество атомов, плавающих вокруг. Прежде чем начать обсуждение игрового цикла, я хотел бы рассказать вам немного об играх и анимации.

история

В свое время (в течение 80-х-90-х годов) многие программисты игр, пытавшиеся анимировать изображения, столкнулись с печально известной проблемой мерцания экрана. Здесь ваши спрайты (графические изображения) часто вспыхивают и делают игру довольно ужасной. Все мониторы имеют частоту обновления, при которой с определенными интервалами пиксели будут перерисовываться (так называемые ЭЛТ с вертикальным возвратом ). Например, если частота обновления составляет 80 Гц, то примерно 80 раз в секунду экран перерисовывается. Если вы изменяете что-то на экране, вы часто можете потерять равновесие, потому что находитесь в середине интервала обновления. Что вы должны сделать по этому поводу? Ну, на самом деле есть две вещи, которые помогут решить эту проблему (двойная буферизация и знание, когда происходит цикл). Некоторые умные разработчики создали технику, называемую двойной буферизацией. Двойная буферизация — это метод, который состоит из двух поверхностей, каждая из которых по очереди превращается в отображаемую поверхность, а другая — внеэкранную область (поверхность буфера). Этот метод действительно является цифровым трюком, где разработчик может предварительно рассчитать спрайты и их положения, которые должны быть нарисованы на неэкранной поверхности. Как только вы закончите рисовать за пределами экрана буфера, код переключит его в качестве отображаемой поверхности. Важно отметить, что у нас все еще есть проблема из-за того, что мы должны быть уведомлены, когда интервал обновления собирается начать процесс перерисовки. В Java эта возможность встроена через API BufferStrategy . Итак, куда я иду с этим? Иногда объяснение прошлых стратегий поможет нам оценить то, что мы имеем сегодня. Нужно ли делать это в JavaFX? Нет. Не бойтесь JavaFX здесь! Все проблемы, о которых я упоминал, решаются для нас с помощью JavaFX Scene graph API. Тем не менее, в большинстве игр по-прежнему используется старомодный способ анимации графики и обновления игрового мира, называемого Game Loop .

Игровой цикл

Проще говоря, игровой цикл отвечает за обновление спрайтов (графики), проверку коллизий и очистку. Старые игровые циклы будут проверять наличие клавиш и событий мыши как часть цикла. Поскольку JavaFX абстрагирует события, чтобы позволить сцене или отдельным узлам обрабатывать события, возможность прослушивания событий низкого уровня в нашем игровом цикле не требуется. Ниже показан фрагмент исходного кода типичного игрового цикла, который будет обновлять спрайты, проверять столкновения и очищать спрайты в каждом цикле. Вы заметите объект Duration из JavaFX 2.x, который представляет 60, деленное на 1000 миллисекунд или 60 кадров в секунду (FPS). Каждый кадр будет вызывать метод handle () интерфейса JavaFX EventHandler для обновления игрового мира. Гипотетически я создал три метода updateSprites () , checkCollisions () и cleanupSprites (), которые будут вызываться для обработки спрайтов в игре.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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 для дизайна игрового движка.

Ниже показана диаграмма класса игрового движка JavaFX.

Рисунок 1. Дизайн игрового движка JavaFX 2

На рисунке 1 в игровом движке JavaFX 2 вы увидите три класса — GameWorld , SpriteManager и Sprite . Класс GameWorld отвечает за инициализацию игрового состояния, выполнение игрового цикла, обновление спрайтов, обработку коллизий спрайтов и очистку. Следующим является класс SpriteManager, который отвечает за управление спрайтами путем добавления, удаления и других служебных операций при столкновениях. Наконец, это класс Sprite, который отвечает за поддержание состояния изображения (Actor). В двумерном мире спрайт может содержать скорость объекта, вращение, узел сцены или изображение, которое в конечном итоге визуализируется в каждом цикле (ключевой кадр / кадры в секунду).

Просто быстрое напоминание о нотации UML:

  • Знак « + » означает, что член класса является открытым.
  • Символ минус « » обозначает, что член класса является частным
  • Хэш-символ ‘ # ‘ обозначает, что член класса защищен.

Мир игры

Ниже приведена реализация исходного кода класса GameWorld. Нажмите, чтобы развернуть. Позже вы увидите диаграмму классов, изображающую простую демонстрационную игру, которая расширит класс GameWorld (см. AtomSmasher).

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
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;
    }
 
}

SpriteManager

Класс диспетчера спрайтов — это вспомогательный класс, помогающий игровому циклу отслеживать спрайты. Обычно диспетчер спрайтов будет содержать все спрайты, и каждый спрайт содержит узел JavaFX, который отображается на графе сцены.
Ниже показан исходный код. Нажмите, чтобы развернуть.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
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. В 2D-игре спрайт будет содержать дополнительную информацию, такую ​​как его скорость для объекта при его перемещении по области сцены. Игровой цикл будет вызывать методы update () и collide () на каждом интервале ключевого кадра.
Ниже показан исходный код. Нажмите, чтобы развернуть.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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 Game Loop Demo — Atom Smasher

Уф! Если у вас так далеко, вы одна храбрая душа. Давайте сделаем небольшой перерыв и попробуем демонстрацию, которую я создал, используя игровой движок выше.
Ниже показана кнопка Java Webstart для запуска демонстрации игры. Позже вы увидите дизайн и исходный код, подробно описывающий, как он был создан.

Требования:

  • Java 7 или более поздняя версия
  • JavaFX 2.0.2 2.1 или позже
  • Windows XP или более поздняя версия (скоро будет доступна для Linux / MacOS)
AtomSmasher Демо-версия игрового цикла
демонстрация

GameLoopPart2 Design

Ниже приведена диаграмма классов демонстрационной версии игры под названием Atom Smasher, в которой используется упомянутый ранее каркас игрового движка.
Ниже показана диаграмма класса Atom Smasher.

Рисунок 2. Диаграмма классов Atom Smasher

GameLoopPart2

GameLoopPart2 — это драйвер или основное приложение JavaFX, которое запускает игру. Это создает объект GameWorld для инициализации и запускает игровой цикл.
Ниже показан исходный код. Нажмите, чтобы развернуть.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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

AtomSmasher является производным классом класса GameWorld. Он создает множество сфер, которые анимируются со случайными скоростями, цветами и позициями. Кнопочные элементы управления позволяют пользователю генерировать больше «атомов» (узлов JavaFX Circle). Когда каждый атом сталкивается друг с другом, он вызывает метод implode (), который создает анимацию перехода с постепенным исчезновением. Вы заметите, как легко реализовать эту игру, просто реализуя методы initialize (), handleUpdate (), handleCollision () и cleanupSprites () . После того, как реализованный игровой движок сделает все остальное. Метод initialize () создает кнопки управления для пользователя. Чтобы обновить позиции спрайтов или изменить состояние игры, вы реализуете метод handleUpdate () . Чтобы сравнить все спрайты, если они столкнулись друг с другом, вы будете реализовывать handleCollision () . Последняя часть жизненного цикла игрового цикла — очистка спрайтов. Очистка означает обновление диспетчера спрайтов и обновление сцены JavaFX (удаление узлов).
Ниже показан исходный код. Нажмите, чтобы развернуть.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
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 ()).
Ниже показан исходный код. Нажмите, чтобы развернуть.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
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();
    }
}

Вывод

Надеюсь, у вас есть шанс понять основы игрового цикла, а затем применить полученные знания, внедрив мощный игровой движок. Хотя я кратко упомяну о столкновении, я сохраню это для четвертой части этих руководств. Пожалуйста, следите за обновлениями части 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

Ссылка: JavaFX 2 GameTutorial, часть 2 от нашего партнера по JCG Карла Деа в блоге Carl’s FX Blog .