Статьи

JavaFX 2 GameTutorial, часть 3

Введение

Эта часть является частью серии из шести частей, относящихся к учебному пособию по JavaFX 2 . Если вы пропустили часть 1 и часть 2 , я рекомендую вам ознакомиться с ними перед началом этого урока. Напомним, что во второй части я обсудил внутреннюю работу игрового цикла, в котором мы использовали анимацию (временную шкалу JavaFX) для обновления спрайтов, проверки столкновений и очистки элементов игрового мира. Затем я почувствовал необходимость создать простой игровой движок, чтобы облегчить разработки 2D игр. В этом уроке рассказывается об использовании игрового движка и демонстрации ввода с помощью мыши и клавиатуры. В этом уроке я расскажу вам историю событий, основы обработки событий, демонстрационную игру и, наконец, реализацию. Демо-версия продемонстрирует космический корабль, способный стрелять по плавающим сферам, похожим на видеоигры Asteroids . Если вы хотите запустить демонстрацию, прокрутите вниз и нажмите кнопку WebStart ниже. Пожалуйста, ознакомьтесь с требованиями перед запуском игры.

история

Когда-то в детстве (в 80-е годы) в детстве были аркады, боулинг, пиццерии и магазины 7 Eleven, где я провел огромное количество времени, разбивая кварталы на стеклянные витрины, чтобы быть рядом с парень, который в настоящее время играет в интенсивную видеоигру. Поскольку все были окружены им, наблюдая, как он побил рекорд всех времен, мы все обрадовались, когда стали свидетелями величия. Одной из тех невероятно крутых аркадных игр были « Астероиды », созданные Atari Inc. (для игры visit play.vg )

Говоря о высоких результатах, не слишком многие знают, но Скотт Сафран (3 февраля 1967 — 27 марта 1989) имел самый высокий рекорд за все время игры в астероиды. Он добился этого в своем местном магазине 7-Eleven, играя без перерыва около двадцати часов. Позже (еще молодым) он скончался от трагического происшествия 27 марта 1989 года. В честь Скотта я создал этот урок. Я надеюсь, что люди будут помнить его как одного из величайших видеоигров всех времен (я уверен, что он также хороший брат и сын).

Что касается игры Asteroids, для визуализации фигур использовалось векторное оборудование, а не растровая графика (растровое изображение). Кроме того, Space Invaders от Midway Inc. была создана с использованием растровой графики. Интересно отметить, что есть дискуссии о том, что JavaFX 2.x имеет возможность использовать растровые изображения, называемые JavaFX Canvas Node, которые могут предоставлять растровую графику, чтобы позволить разработчикам воспользоваться преимуществами манипуляций на уровне пикселей. Я все еще поражаюсь конструкции этих стоек в аркадном стиле, в которых размещены ЭЛТ, материнская плата и контроллеры (устройства ввода), такие как кнопки, джойстик, трекболы и поворотные ручки.

Классические аркадные игры

Ниже приведены некоторые классические аркадные игры со многими типами устройств ввода :

  • Только кнопки : астероиды, космические захватчики, Rip Off, Феникс
  • Только джойстик : Q * bert, PacMan
  • Поверните только ручку : понг
  • Только для трекбола: Marble Madness
  • Рулевая колонка и кнопки : Звездные войны , Полюс, Шпион-Охотник
  • Ручки для велосипедов : Stunt Cycle, Paper Boy
  • Кнопки и дроссельная заслонка : Lunar Lander
  • Перископ и кнопки : морской волк
  • Кнопки и иго : Трон, Зона битвы
  • Кнопки, ручка поворота и иго : Звездный путь, Буря
  • Кнопки и трекбол : Ракетное командование, Сороконожка
  • Кнопки и джойстик : Защитник, Рукавица, Фроггер, Джоуст, Берзерк, Марио Брос., Донки Конг, Ксивин, Галага, Кунг-фу, Контра, Уличный боец, Двойной дракон, магия ниндзя (или дух), DigDug, Логово дракона.

Ввод / (мышь, клавиатура)

Оставив прошлое позади, мы в настоящее время сталкиваемся с новыми видами устройств ввода, такими как сенсорные экраны, акселерометры, инфракрасные приемники, камеры и т. Д. Самым распространенным входом на рабочем столе сегодня являются мышь и клавиатура. Конечно, сенсорные экраны всегда в моде на мобильных устройствах и планшетах, однако в этом уроке мы сосредоточимся только на « мыши » и « клавиатуре » в качестве входных данных для управления игрой. На основе JavaFX Roadmap вводится мультитач-ввод (к тому времени, как вы его прочитаете, он уже реализован).

При перехвате событий клавиатуры и мыши JavaFX 2.x имеет много типов событий, которые предоставляют разработчику возможность реализовывать обработчики событий, которые перехватывают инициируемые события. JavaFX 2.x API для Node или Scene содержит много методов, имеющих префикс «on», таких как метод onMousePressProperty () или onKeyPressProperty () . Всякий раз, когда вы реализуете эти методы, вы просто реализуете метод handle (), используя общий тип Java, чтобы указать объект события, который будет передан для опроса. Поэтому, когда вы создаете экземпляр класса EventHandler <MouseEvent>, вы реализуете метод handle (), который принимает MouseEvent в качестве параметра для передачи.

Фрагменты кода, показанные ниже, добавляют два обработчика событий к сцене JavaFX. Первый обработчик будет реагировать на события мыши. В нашей простой игре, когда происходит нажатие мыши, этот обработчик будет отвечать выстрелом оружия или навигацией по кораблю. Второй обработчик, показанный ниже, будет реагировать на ключевое событие. Когда клавиша нажата, этот обработчик будет обрабатывать объекты KeyEvent. В нашей игре нажатие клавиши « 2 » изменит ваше вторичное оружие на больший бластер (медленнее). Любое другое нажатие клавиши по умолчанию вернется к меньшему бластеру (быстрее).

Переместить корабль и Огонь оружие

01
02
03
04
05
06
07
08
09
10
11
EventHandler fireOrMove = new EventHandler<MouseEvent>() {
    @Override
    public void handle(MouseEvent event) {
        if (event.getButton() == MouseButton.PRIMARY) {
           // Fire weapon systems. On Windows left mouse button
        } else if (event.getButton() == MouseButton.SECONDARY) {
           // Navigate ship thrust. On Windows right mouse button
        }
    }
};
primaryStage.getScene().setOnMousePressed(fireOrMove);

Смена оружия

1
2
3
4
5
6
7
EventHandler changeWeapons = new EventHandler<KeyEvent>() {
   @Override
   public void handle(KeyEvent event) {
       myShip.changeWeapon(event.getCode());
   }
};
primaryStage.getScene().setOnKeyPressed(changeWeapons);

JavaFX 2 Input Demo — «Пространство»

Простая демо-игра представляет собой смесь StarCraft и Asteroids. При использовании мыши для навигации по кораблю он будет напоминать StarCraft’s Battle Cruiser . Если вы помните из части 2 этой серии, я создал сферы, подпрыгивающие вокруг. Я использовал код из части 2 «Atom Smasher», чтобы действовать как астероиды, как в знаменитой аркадной игре. За исключением этой игры вы не можете получить вред вообще. Цель состоит в том, чтобы стрелять своим оружием по сферам, прежде чем они поразят другие сферы, которые взорвутся при ударе. Поскольку это простое учебное пособие или даже игра на ранних стадиях разработки, игра не отслеживает счет. Я призываю вас перейти на GitHub, чтобы загрузить код и улучшить игру. Позже вы увидите диаграмму классов UML высокого уровня, описывающую классы, составляющие игру. Для краткости я не буду подробно останавливаться на каждом классе, но я надеюсь, что вы посетите GitHub здесь: https://github.com/carldea/JFXGen для всех демонстраций и исходного кода.

Требования :

  • Java 7 или более поздняя версия
  • JavaFX 2.1 или более поздняя версия
  • Windows XP или более поздняя версия (скоро будет доступна для Linux / MacOS)

Простая игра типа астероидов под названием «Пространство».

Инструкции:

  • Щелкните правой кнопкой мыши (на Windows), чтобы летать на корабле.
  • Щелчок левой кнопкой мыши (левой кнопкой мыши на Windows), чтобы стрелять оружие.
  • Нажмите клавишу «2? перейти на большие ракеты. (синий круговой снаряд)
  • Другое нажатие клавиши по умолчанию для меньших ракет. (красные круглые снаряды)
Часть 3 «Пространство»

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

InputPart3

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

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
import carlfx.gameengine.GameWorld;
package carlfx.demos.navigateship;
 
import javafx.application.Application;
import javafx.stage.Stage;
 
/**
 * The main driver of the game.
 * @author cdea
 */
public class InputPart3 extends Application {
 
    GameWorld gameWorld = new TheExpanse(59, "JavaFX 2 GameTutorial Part 3 - Input");
    /**
     * @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();
    }
 
}

TheExpanse

Класс TheExpanse наследуется от класса GameWorld . Это практически идентично части 2 «AtomSmasher», где приложение драйвера будет вызывать метод initialize () экземпляра GameWorld , чтобы настроить все игровые элементы, такие как ввод , космический корабль и эти надоедливые плавающие сферы . Задача этого класса состоит в том, чтобы убедиться, что астероиды или сферы отскакивают от стен и удаляют любые ракеты, которые достигают края экрана. Главное — ответственно управлять активами и создавать новые уровни. Когда движущихся объектов нет и игрок перемещает корабль на экране, для следующего уровня будут сгенерированы новые сферы. Ключом к этому классу является метод setupInput () . Метод setupInput (), который я создал, отвечает за установку обработчиков событий, чтобы они могли прослушивать ключевые события и события мыши.

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
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
package carlfx.demos.navigateship;
 
import carlfx.gameengine.GameWorld;
import carlfx.gameengine.Sprite;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.CacheHint;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
 
import java.util.Random;
 
/**
 * 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 will navigate his/her ship by right clicking the mouse to
 * trust forward and left click to fire weapon to atoms.
 *
 * @author cdea
 */
public class TheExpanse extends GameWorld {
 
    // mouse pt label
    Label mousePtLabel = new Label();
 
    // mouse press pt label
    Label mousePressPtLabel = new Label();
 
    TextField xCoordinate = new TextField("234");
    TextField yCoordinate = new TextField("200");
    Button moveShipButton = new Button("Rotate ship");
 
    Ship myShip = new Ship();
 
    public TheExpanse(int fps, String title) {
        super(fps, title);
    }
 
    /**
     * Initialize the game world by adding sprite objects.
     *
     * @param primaryStage The game window or primary stage.
     */
    @Override
    public void initialize(final Stage primaryStage) {
        // Sets the window title
        primaryStage.setTitle(getWindowTitle());
        //primaryStage.setFullScreen(true);
 
        // Create the scene
        setSceneNodes(new Group());
        setGameSurface(new Scene(getSceneNodes(), 800, 600));
        getGameSurface().setFill(Color.BLACK);
        primaryStage.setScene(getGameSurface());
        // Setup Game input
        setupInput(primaryStage);
 
        // Create many spheres
        generateManySpheres(2);
 
        // 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();
        getSpriteManager().addSprites(myShip);
        getSceneNodes().getChildren().add(myShip.node);
 
        // mouse point
        VBox stats = new VBox();
 
        HBox row1 = new HBox();
        mousePtLabel.setTextFill(Color.WHITE);
        row1.getChildren().add(mousePtLabel);
        HBox row2 = new HBox();
        mousePressPtLabel.setTextFill(Color.WHITE);
        row2.getChildren().add(mousePressPtLabel);
 
        stats.getChildren().add(row1);
        stats.getChildren().add(row2);
 
        // mouse point
        HBox enterCoord1 = new HBox();
        enterCoord1.getChildren().add(xCoordinate);
        enterCoord1.getChildren().add(yCoordinate);
        enterCoord1.getChildren().add(moveShipButton);
        stats.getChildren().add(enterCoord1);
        moveShipButton.setOnAction(new EventHandler() {
            @Override
            public void handle(ActionEvent actionEvent) {
                double x = Double.parseDouble(xCoordinate.getText());
                double y = Double.parseDouble(yCoordinate.getText());
                myShip.plotCourse(x, y, false);
            }
        });
 
        // ===================================================
        // Debugging purposes
        // uncomment to test mouse press and rotation angles.
        //getSceneNodes().getChildren().add(stats);
    }
 
    /**
     * Sets up the mouse input.
     *
     * @param primaryStage The primary stage (app window).
     */
    private void setupInput(Stage primaryStage) {
        System.out.println("Ship's center is (" + myShip.getCenterX() + ", " + myShip.getCenterY() + ")");
 
        EventHandler fireOrMove = new EventHandler() {
            @Override
            public void handle(MouseEvent event) {
                mousePressPtLabel.setText("Mouse Press PT = (" + event.getX() + ", " + event.getY() + ")");
                if (event.getButton() == MouseButton.PRIMARY) {
                    // Aim
                    myShip.plotCourse(event.getX(), event.getY(), false);
                    // fire
                    Missile m1 = myShip.fire();
                    getSpriteManager().addSprites(m1);
                    getSceneNodes().getChildren().add(0, m1.node);
                } else if (event.getButton() == MouseButton.SECONDARY) {
                    // determine when all atoms are not on the game surface. Ship should be one sprite left.
                    if (getSpriteManager().getAllSprites().size()                         generateManySpheres(30);
                    }
 
                    // stop ship from moving forward
                    myShip.applyTheBrakes(event.getX(), event.getY());
                    // move forward and rotate ship
                    myShip.plotCourse(event.getX(), event.getY(), true);
                }
 
            }
        };
 
        // Initialize input
        primaryStage.getScene().setOnMousePressed(fireOrMove);
        //addEventHandler(MouseEvent.MOUSE_PRESSED, me);
 
        // set up stats
        EventHandler changeWeapons = new EventHandler() {
            @Override
            public void handle(KeyEvent event) {
                myShip.changeWeapon(event.getCode());
            }
        };
        primaryStage.getScene().setOnKeyPressed(changeWeapons);
 
        // set up stats
        EventHandler showMouseMove = new EventHandler() {
            @Override
            public void handle(MouseEvent event) {
                mousePtLabel.setText("Mouse PT = (" + event.getX() + ", " + event.getY() + ")");
            }
        };
 
        primaryStage.getScene().setOnMouseMoved(showMouseMove);
    }
 
    /**
     * Make some more space spheres (Atomic particles)
     *
     * @param numSpheres The number of random sized, color, and velocity atoms to generate.
     */
    private void generateManySpheres(int numSpheres) {
        Random rnd = new Random();
        Scene gameSurface = getGameSurface();
        for (int i = 0; i < numSpheres; i++) {             Color c = Color.rgb(rnd.nextInt(255), rnd.nextInt(255), rnd.nextInt(255));             Atom b = new Atom(rnd.nextInt(15) + 5, c, true);             Circle circle = b.getAsCircle();             // random 0 to 2 + (.0 to 1) * random (1 or -1)             b.vX = (rnd.nextInt(2) + rnd.nextDouble()) * (rnd.nextBoolean() ? 1 : -1);             b.vY = (rnd.nextInt(2) + rnd.nextDouble()) * (rnd.nextBoolean() ? 1 : -1);             // random x between 0 to width of scene             double newX = rnd.nextInt((int) gameSurface.getWidth());             // check for the right of the width newX is greater than width              // minus radius times 2(width of sprite)             if (newX > (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());
            circle.setCache(true);
            circle.setCacheHint(CacheHint.SPEED);
            circle.setManaged(false);
            // 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) {
        // advance object
        sprite.update();
        if (sprite instanceof Missile) {
            removeMissiles((Missile) sprite);
        } else {
            bounceOffWalls(sprite);
        }
    }
 
    /**
     * Change the direction of the moving object when it encounters the walls.
     *
     * @param sprite The sprite to update based on the wall boundaries.
     *               TODO The ship has got issues.
     */
    private void bounceOffWalls(Sprite sprite) {
        // bounce off the walls when outside of boundaries
 
        Node displayNode;
        if (sprite instanceof Ship) {
            displayNode = sprite.node;//((Ship)sprite).getCurrentShipImage();
        } else {
            displayNode = sprite.node;
        }
        // Get the group node's X and Y but use the ImageView to obtain the width.
        if (sprite.node.getTranslateX() > (getGameSurface().getWidth() - displayNode.getBoundsInParent().getWidth()) ||
                displayNode.getTranslateX() < 0) {             // bounce the opposite direction             sprite.vX = sprite.vX * -1;         }         // Get the group node's X and Y but use the ImageView to obtain the height.         if (sprite.node.getTranslateY() > getGameSurface().getHeight() - displayNode.getBoundsInParent().getHeight() ||
                sprite.node.getTranslateY() < 0) {             sprite.vY = sprite.vY * -1;         }     }     /**      * Remove missiles when they reach the wall boundaries.      *      * @param missile The missile to remove based on the wall boundaries.      */     private void removeMissiles(Missile missile) {         // bounce off the walls when outside of boundaries         if (missile.node.getTranslateX() > (getGameSurface().getWidth() -
                missile.node.getBoundsInParent().getWidth()) ||
                missile.node.getTranslateX() < 0) {             getSpriteManager().addSpritesToBeRemoved(missile);             getSceneNodes().getChildren().remove(missile.node);         }         if (missile.node.getTranslateY() > getGameSurface().getHeight() -
                missile.node.getBoundsInParent().getHeight() ||
                missile.node.getTranslateY() < 0) {
 
            getSpriteManager().addSpritesToBeRemoved(missile);
            getSceneNodes().getChildren().remove(missile.node);
        }
    }
 
    /**
     * How to handle the collision of two sprite objects. Stops the particle
     * by zeroing out the velocity if a collision occurred.
     *
     * @param spriteA Sprite from the first list.
     * @param spriteB Sprite from the second list.
     * @return boolean returns a true if the two sprites have collided otherwise false.
     */
    @Override
    protected boolean handleCollision(Sprite spriteA, Sprite spriteB) {
        if (spriteA != spriteB) {
            if (spriteA.collide(spriteB)) {
                if (spriteA instanceof Atom && spriteB instanceof Atom) {
 
                    ((Atom) spriteA).implode(this); // will remove from the Scene onFinish()
                    ((Atom) spriteB).implode(this);
                    getSpriteManager().addSpritesToBeRemoved(spriteA, spriteB);
 
                    return true;
                }
            }
        }
 
        return false;
    }
 
}

Корабль

Класс Корабль представляет наш круто выглядящий космический корабль. Класс Ship наследуется от класса Sprite, чтобы помочь нам содержать информацию о скорости (вектор). Этот класс также будет содержать двусвязный список, содержащий 32 экземпляра ImageView ( RotatedShipImage ), которые представляют каждое направление для моделирования движения корабля вокруг его центра (центроида). В какой-то момент я хочу изменить это, сделав один объект SVGPath для поворота (я знаю, что есть компромиссы). В этом уроке я реализовал корабль, повернув объекты ImageView для поворота в 32 направлениях от 0 до 360 градусов. Ниже на рисунке 3 показаны все 32 направления с использованием 32 экземпляров ImageView и одного объекта Image изображения космического корабля для имитации вращения вокруг его центра (точки поворота).

При анимации вращения корабля я просто устанавливал все, кроме текущего изображения, видимым, используя метод setVisible (true) на узле ImageView .

Отказ от ответственности : в играх вы неизбежно столкнетесь с математикой (тригонометрия). Если вы заинтересованы и хотите копнуть глубже, посмотрите на исходный код метода инициализации () класса TheExpanse . В конце метода раскомментируйте оператор: getSceneNodes (). GetChildren (). Add (stats); , Это отобразит элементы управления, которые позволят вам использовать для отладки и проверки координат нажатия мыши. Кроме того, вы можете видеть вывод в вашей консоли (stdout), относящийся к углам, векторам и т. Д.

Переменные члена корабля :

  • turnDirection — перечисление НАПРАВЛЕНИЕ по часовой стрелке, против часовой стрелки и ни
  • u — Vec-объект, который содержит вектор относительно координат центра корабля, обозначающий начальное направление, в котором корабль начинает вращаться
  • directionalShips — список объектов RotatedShipImage, каждый из которых имеет предыдущую и следующую ссылку на другие объекты RotatedShipImage. Нулевой градус (uIndex = 0) — это когда космический корабль направлен на восток. При вращении узлов JavaFX, идущих в направлении против часовой стрелки, положительные числа в градусах
  • uIndex — индекс текущего RotatedShipImage в списке directionalShips, который должен отображаться
  • vIndex — индекс RotatedShipImage в списке directionalShips, который должен отображаться в конце анимации вращения
  • stopArea — круг JavaFX с радиусом, позволяющим судну знать, когда остановить движение корабля
  • flipBook — Группа JavaFX, содержащая все объекты RotatedShipImage (32). Группа отображается на сцене. Как и в виде анимационной книги, каждый RotatedShipImage будет определяться как отображаемый на основе uIndex и vIndex.
  • keyCode — JavaFX KeyCode поможет определить, нажимается ли клавиша, чтобы помочь изменить ваше оружие (символ ‘2?)

Функции члена корабля :

  • update () — Обновляет скорость и направление кораблей. Также определит, когда прекратить движение.
  • getCurrentShipImage () — На основании uIndex возвращает ImageView, которое является текущим изображением направления судна, которое отображается
  • getCenterX () — возвращает координату X экрана центра корабля
  • getCenterY () — возвращает координату X экрана центра корабля
  • plotCourse (double screenX, double screenY, булево направление) — после того, как пользователь щелкнет мышью по экрану, этот метод рассчитает угол поворота корабля и изменит скорость на тягу в направлении координат к точке назначения. При использовании объекта Vec экранные координаты будут преобразованы в декартовые координаты для определения угла между двумя векторами (U и V).
  • turnShip () — Метод plotCourse () вызывает метод turnShip (), чтобы выполнить фактическую анимацию вращения корабля
  • applyTheBrakes (double screenX, double screenY) — После того, как пользователь выбрал (щелчок правой кнопкой мыши), где корабль будет перемещаться, метод applyTheBrakes () просто устанавливает объект stopArea ( Circle ), чтобы судно узнало, когда остановиться.
  • fire () — возвращает объект Missile (Sprite) для игрового движка, который должен быть выведен на сцену. Каждая ракета содержит одно и то же направление корабля с увеличенной скоростью (увеличение скорости). Должно быть быстрее, чем корабль может летать.
  • changeWeapon (KeyCode keyCode) — после того, как пользователь (игрок) нажал клавишу «2»? оружие изменится, чтобы создать больший снаряд, но немного медленнее. Любое другое нажатие клавиши будет оружием по умолчанию, которое создает маленькие ракетные снаряды, которые быстрее движутся.

Ниже показан рисунок 4 диаграммы классов, отображающий членов класса Ship.

Диаграмма классов кораблей

Ниже показан исходный код класса Ship.

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
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
package carlfx.demos.navigateship;
 
import carlfx.gameengine.Sprite;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.animation.TimelineBuilder;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.CacheHint;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.image.Image;
import javafx.scene.input.KeyCode;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.util.Duration;
import java.util.ArrayList;
import java.util.List;
 
/**
 * A space ship with 32 directions
 * 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 Ship extends Sprite {
 
    /**
     * 360 degree turn
     */
    private final static int TWO_PI_DEGREES = 360;
 
    /**
     * Number of ship frames and directions the ship is pointing nose
     */
    private final static int NUM_DIRECTIONS = 32;
 
    /**
     * The angle of one direction (adjacent directions) (11.25 degrees)
     */
    private final static float UNIT_ANGLE_PER_FRAME = ((float) TWO_PI_DEGREES / NUM_DIRECTIONS);
 
    /**
     * Amount of time it takes the ship to move 180 degrees in milliseconds.
     */
    private final static int MILLIS_TURN_SHIP_180_DEGREES = 300;
 
    /**
     * When the ship turns on each direction one amount of time for one frame or turn of the ship. (18.75 milliseconds)
     */
    private final static float MILLIS_PER_FRAME = (float) MILLIS_TURN_SHIP_180_DEGREES / (NUM_DIRECTIONS / 2);
 
    /**
     * All possible turn directions Clockwise, Counter Clockwise, or Neither when the user clicks mouse around ship
     */
    private enum DIRECTION {
        CLOCKWISE, COUNTER_CLOCKWISE, NEITHER
    }
 
    /**
     * Velocity amount used vector when ship moves forward. scale vector of ship. See flipBook translateX and Y.
     */
    private final static float THRUST_AMOUNT = 3.3f;
 
    /***/
    private final static float MISSILE_THRUST_AMOUNT = 6.3F;
 
    /**
     * Angle in degrees to rotate ship.
     */
 
    /**
     * Current turning direction. default is NEITHER. Clockwise and Counter Clockwise.
     */
    private DIRECTION turnDirection = DIRECTION.NEITHER;
 
    /**
     * The current starting position of the vector or coordinate where the nose of the ship is pointing towards.
     */
    private Vec u; // current or start vector
 
    /**
     * All ImageViews of all the possible image frames for each direction the ship is pointing. ie: 32 directions.
     */
    private final List directionalShips = new ArrayList<>();
 
    /**
     * The Timeline instance to animate the ship rotating using images. This is an optical illusion similar to page
     * flipping as each frame is displayed the previous visible attribute is set to false. No rotation is happening.
     */
    private Timeline rotateShipTimeline;
 
    /**
     * The current index into the list of ImageViews representing each direction of the ship. Zero is the ship
     * pointing to the right or zero degrees.
     */
    private int uIndex = 0;
 
    /**
     * The end index into the list of ImageViews representing each direction of the ship. Zero is the ship
     * pointing to the right or zero degrees.
     */
    private int vIndex = 0;
 
    /**
     * The spot where the user has right clicked letting the engine check the ship's center is in this area.
     */
    private final Circle stopArea = new Circle();
 
    /**
     * A group contain all of the ship image view nodes.
     */
    private final Group flipBook = new Group();
 
    /**
     * A key code will be used for weapon selection.
     */
    private KeyCode keyCode;
 
    public Ship() {
 
        // Load one image.
        Image shipImage = new Image(getClass().getClassLoader().getResource("ship.png").toExternalForm(), true);
        stopArea.setRadius(40);
        RotatedShipImage prev = null;
 
        // create all the number of directions based on a unit angle. 360 divided by NUM_DIRECTIONS
        for (int i = 0; i < NUM_DIRECTIONS; i++) {
            RotatedShipImage imageView = new RotatedShipImage();
            imageView.setImage(shipImage);
            imageView.setRotate(-1 * i * UNIT_ANGLE_PER_FRAME);
            imageView.setCache(true);
            imageView.setCacheHint(CacheHint.SPEED);
            imageView.setManaged(false);
            imageView.prev = prev;
            imageView.setVisible(false);
            directionalShips.add(imageView);
            if (prev != null) {
                prev.next = imageView;
            }
            prev = imageView;
            flipBook.getChildren().add(imageView);
        }
        RotatedShipImage firstShip = directionalShips.get(0);
        firstShip.prev = prev;
        prev.next = firstShip;
        // set javafx node to an image
        firstShip.setVisible(true);
        node = flipBook;
        flipBook.setTranslateX(200);
        flipBook.setTranslateY(300);
 
    }
 
    /**
     * Change the velocity of the atom particle.
     */
    @Override
    public void update() {
        flipBook.setTranslateX(flipBook.getTranslateX() + vX);
        flipBook.setTranslateY(flipBook.getTranslateY() + vY);
 
        if (stopArea.contains(getCenterX(), getCenterY())) {
            vX = 0;
            vY = 0;
        }
 
    }
 
    private RotatedShipImage getCurrentShipImage() {
        return directionalShips.get(uIndex);
    }
 
    /**
     * The center X coordinate of the current visible image. See <code>getCurrentShipImage()</code> method.
     *
     * @return The scene or screen X coordinate.
     */
    public double getCenterX() {
        RotatedShipImage shipImage = getCurrentShipImage();
        return node.getTranslateX() + (shipImage.getBoundsInLocal().getWidth() / 2);
    }
 
    /**
     * The center Y coordinate of the current visible image. See <code>getCurrentShipImage()</code> method.
     *
     * @return The scene or screen Y coordinate.
     */
    public double getCenterY() {
        RotatedShipImage shipImage = getCurrentShipImage();
        return node.getTranslateY() + (shipImage.getBoundsInLocal().getHeight() / 2);
    }
 
    /**
     * Determines the angle between it's starting position and ending position (Similar to a clock's second hand).
     * When the user is shooting the ship nose will point in the direction of the mouse press using the primary button.
     * When the user is thrusting to a location on the screen the right click mouse will pass true to the thrust
     * parameter.
     *
     * @param screenX The mouse press' screen x coordinate.
     * @param screenY The mouse press' screen ycoordinate.
     * @param thrust  Thrust ship forward or not. True move forward otherwise false.
     */
    public void plotCourse(double screenX, double screenY, boolean thrust) {
        // get center of ship
        double sx = getCenterX();
        double sy = getCenterY();
 
        // get user's new turn position based on mouse click
        Vec v = new Vec(screenX, screenY, sx, sy);
        if (u == null) {
            u = new Vec(1, 0);
        }
 
        double atan2RadiansU = Math.atan2(u.y, u.x);
        double atan2DegreesU = Math.toDegrees(atan2RadiansU);
 
        double atan2RadiansV = Math.atan2(v.y, v.x);
        double atan2DegreesV = Math.toDegrees(atan2RadiansV);
 
        double angleBetweenUAndV = atan2DegreesV - atan2DegreesU;
 
        // if abs value is greater than 180 move counter clockwise
        //(or opposite of what is determined)
        double absAngleBetweenUAndV = Math.abs(angleBetweenUAndV);
        boolean goOtherWay = false;
        if (absAngleBetweenUAndV > 180) {
            if (angleBetweenUAndV < 0) {                 turnDirection = DIRECTION.COUNTER_CLOCKWISE;                 goOtherWay = true;             } else if (angleBetweenUAndV > 0) {
                turnDirection = DIRECTION.CLOCKWISE;
                goOtherWay = true;
            } else {
                turnDirection = Ship.DIRECTION.NEITHER;
            }
        } else {
            if (angleBetweenUAndV < 0) {                 turnDirection = Ship.DIRECTION.CLOCKWISE;             } else if (angleBetweenUAndV > 0) {
                turnDirection = Ship.DIRECTION.COUNTER_CLOCKWISE;
            } else {
                turnDirection = Ship.DIRECTION.NEITHER;
            }
        }
 
        double degreesToMove = absAngleBetweenUAndV;
        if (goOtherWay) {
            degreesToMove = TWO_PI_DEGREES - absAngleBetweenUAndV;
        }
 
        //int q = v.quadrant();
 
        uIndex = Math.round((float) (atan2DegreesU / UNIT_ANGLE_PER_FRAME));
        if (uIndex < 0) {
            uIndex = NUM_DIRECTIONS + uIndex;
        }
        vIndex = Math.round((float) (atan2DegreesV / UNIT_ANGLE_PER_FRAME));
        if (vIndex < 0) {             vIndex = NUM_DIRECTIONS + vIndex;         }         String debugMsg = turnDirection +                 " U [m(" + u.mx + ", " + u.my + ")  => c(" + u.x + ", " + u.y + ")] " +
                " V [m(" + v.mx + ", " + v.my + ")  => c(" + v.x + ", " + v.y + ")] " +
                " start angle: " + atan2DegreesU +
                " end angle:" + atan2DegreesV +
                " Angle between: " + degreesToMove +
                " Start index: " + uIndex +
                " End index: " + vIndex;
 
        System.out.println(debugMsg);
 
        if (thrust) {
            vX = Math.cos(atan2RadiansV) * THRUST_AMOUNT;
            vY = -Math.sin(atan2RadiansV) * THRUST_AMOUNT;
        }
        turnShip();
 
        u = v;
    }
 
    private void turnShip() {
 
        final Duration oneFrameAmt = Duration.millis(MILLIS_PER_FRAME);
        RotatedShipImage startImage = directionalShips.get(uIndex);
        RotatedShipImage endImage = directionalShips.get(vIndex);
        List frames = new ArrayList<>();
 
        RotatedShipImage currImage = startImage;
 
        int i = 1;
        while (true) {
 
            final Node displayNode = currImage;
 
            KeyFrame oneFrame = new KeyFrame(oneFrameAmt.multiply(i),
                    new EventHandler() {
 
                        @Override
                        public void handle(javafx.event.ActionEvent event) {
                            // make all ship images invisible
                            for (RotatedShipImage shipImg : directionalShips) {
                                shipImg.setVisible(false);
                            }
                            // make current ship image visible
                            displayNode.setVisible(true);
 
                            // update the current index
                            //uIndex = directionalShips.indexOf(displayNode);
                        }
                    }); // oneFrame
 
            frames.add(oneFrame);
 
            if (currImage == endImage) {
                break;
            }
            if (turnDirection == DIRECTION.CLOCKWISE) {
                currImage = currImage.prev;
            }
            if (turnDirection == DIRECTION.COUNTER_CLOCKWISE) {
                currImage = currImage.next;
            }
            i++;
        }
 
        if (rotateShipTimeline != null) {
            rotateShipTimeline.stop();
            rotateShipTimeline.getKeyFrames().clear();
            rotateShipTimeline.getKeyFrames().addAll(frames);
        } else {
            // sets the game world's game loop (Timeline)
            rotateShipTimeline = TimelineBuilder.create()
                    .keyFrames(frames)
                    .build();
 
        }
 
        rotateShipTimeline.playFromStart();
 
    }
 
    /**
     * Stops the ship from thrusting forward.
     *
     * @param screenX the screen's X coordinate to stop the ship.
     * @param screenY the screen's Y coordinate to stop the ship.
     */
    public void applyTheBrakes(double screenX, double screenY) {
        stopArea.setCenterX(screenX);
        stopArea.setCenterY(screenY);
    }
 
    public Missile fire() {
        Missile m1;
 
        float slowDownAmt = 0;
        if (KeyCode.DIGIT2 == keyCode) {
            m1 = new Missile(10, Color.BLUE);
            slowDownAmt = 2.3f;
        } else {
            m1 = new Missile(Color.RED);
        }
        // velocity vector of the missile
        m1.vX = Math.cos(Math.toRadians(uIndex * UNIT_ANGLE_PER_FRAME)) * (MISSILE_THRUST_AMOUNT - slowDownAmt);
        m1.vY = -Math.sin(Math.toRadians(uIndex * UNIT_ANGLE_PER_FRAME)) * (MISSILE_THRUST_AMOUNT - slowDownAmt);
 
        // make the missile launch in the direction of the current direction of the ship nose. based on the
        // current frame (uIndex) into the list of image view nodes.
        RotatedShipImage shipImage = directionalShips.get(uIndex);
 
        // start to appear in the center of the ship to come out the direction of the nose of the ship.
        double offsetX = (shipImage.getBoundsInLocal().getWidth() - m1.node.getBoundsInLocal().getWidth()) / 2;
        double offsetY = (shipImage.getBoundsInLocal().getHeight() - m1.node.getBoundsInLocal().getHeight()) / 2;
 
        // initial launch of the missile
        m1.node.setTranslateX(node.getTranslateX() + offsetX + m1.vX);
        m1.node.setTranslateY(node.getTranslateY() + offsetY + m1.vY);
        return m1;
    }
 
    public void changeWeapon(KeyCode keyCode) {
        this.keyCode = keyCode;
    }
 
}

Vec

Класс Vec — это простой вспомогательный контейнерный класс, который помогает удерживать экранные координаты щелчка мыши и преобразовывать их в декартовы координаты на основе центра спрайта, изображения или фигуры. Этот класс используется для определения угла между двумя векторами [Math.atan2 (y, x)]. Определив угол, корабль может выполнить анимацию вращения изображения спрайта.

Ниже показан исходный код класса Vec.

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package carlfx.demos.navigateship;
 
/**
 * This class represents a container class to hold a Vector in space and direction
 * the ship will move. Assuming the center of the ship is the origin the angles can
 * be determined by a unit circle via Cartesian coordinates.
 * When the user clicks on the screen the mouse coordinates or screen coordinates
 * will be stored into the mx and my instance variables.
 * The x and y data members are converted to cartesian coordinates before storing.
 *
 * I purposefully left out getters and setters. In gaming just keep things minimalistic.
 * @author cdea
 */
public class Vec {
    public double mx;
    public double my;
 
    public double x;
    public double y;
 
    /**
     * This is a default constructor which will take a Cartesian coordinate.
     * @param x  X coordinate of a point on a Cartesian system.
     * @param y  Y coordinate of a point on a Cartesian system.
     */
    public Vec(float x, float y) {
        this.x = x;
        this.y = y;
    }
 
    /**
     * Constructor will convert mouse click points into Cartesian coordinates based on the sprite's center point as
     * the origin.
     * @param mx  Mouse press' screen X coordinate.
     * @param my  Mouse press' screen Y coordinate.
     * @param centerX Screen X coordinate of the center of the ship sprite.
     * @param centerY Screen Y coordinate of the center of the ship sprite.
     */
    public Vec(double mx, double my, double centerX, double centerY) {
        this.x = convertX(mx, centerX);
        this.y = convertY(my, centerY);
        this.mx = mx;
        this.my = my;
    }
 
    /**
     * Returns a Cartesian coordinate system's quadrant from 1 to 4.
*
     *     first quadrant - 1 upper right
     *     second quadrant - 2 upper left
     *     third quadrant - 3 lower left
     *     fourth quadrant - 4 lower right
     *
     * @return int quadrant number 1 through 4
     */
    public int quadrant() {
        int q = 0;
        if (x > 0 && y > 0) {
            q =1;
        } else if (x < 0 && y > 0) {
            q = 2;
        } else if (x < 0 && y < 0) {             q = 3;         } else if (x > 0 && y < 0) {
            q = 4;
        }
        return q;
    }
    @Override
    public String toString(){
        return "(" + x + "," + y + ") quadrant=" + quadrant();
    }
    /**
     * Converts point's X screen coordinate into a Cartesian system.
     * @param mouseX Converts the mouse X coordinate into Cartesian system based on the ship center X (originX).
     * @param originX The ship center point's X coordinate.
     * @return  double value of a Cartesian system X coordinate based on the origin X.
     */
    static double convertX(double mouseX, double originX) {
        return mouseX - originX;
    }
 
    /**
     * Converts point's Y screen coordinate into a Cartesian system.
     * @param mouseY  Converts the mouse Y coordinate into Cartesian system based on the ship center Y (originY).
     * @param originY The ship center point's Y coordinate.
     * @return  double value of a Cartesian system Y coordinate based on the origin Y.
     */
    static double convertY(double mouseY, double originY) {
        return originY - mouseY;
    }
 
}

RotatedShipImage

Класс RotatedShipImage наследуется от класса ImageF JavaFX, но также содержит ссылки на предыдущий и следующий экземпляры RotatedShipImage, составляющие двусвязный список. На рисунке 3 изображены 32 изображения файла «ship.png», представленного в каждом RotatedShipImage, которые все размещены в узле JavaFX Group . Когда кажется, что корабль вращается, одновременно отображается одно изображение.

Ниже показан исходный код класса RotatedShipImage .

01
02
03
04
05
06
07
08
09
10
11
12
package carlfx.demos.navigateship;
 
import javafx.scene.image.ImageView;
 
/**
 * Represents a double link list to assist in the rotation of the ship.
 * This helps to move clockwise and counter clockwise.
 */
public class RotatedShipImage extends ImageView {
    public RotatedShipImage next;
    public RotatedShipImage prev;
}

ракета

Класс Missile наследуется от класса Atom . Ракета действует как маркерный класс, чтобы различать сферы (астероиды) и ракеты. Когда ракеты созданы, они будут содержать то же направление корабля (куда указывает нос корабля) с большей скоростью.

Ниже показан исходный код класса Missile .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
package carlfx.demos.navigateship;
 
import javafx.scene.paint.Color;
 
/**
 * A missile projectile without the radial gradient.
 */
public class Missile extends Atom {
    public Missile(Color fill) {
        super(5, fill, false);
    }
 
    public Missile(int radius, Color fill) {
        super(radius, fill, true);
    }
}

Вывод

Вклад настолько важен для любого игрового процесса, что зачастую трудно получить правильные результаты. Старые игровые движки будут опрашивать во время игрового цикла. При использовании обработки событий JavaFX 2.x вы реализуете тип события, которое будет добавлено в граф сцены или в отдельный объект Node . Надеемся, что в будущем мы увидим более изобретательные устройства ввода, используемые в играх (см . Евангелист Oracle Java Simon Ritter ). Не спускайте глаз с части 4, посвященной обнаружению столкновений. Так что следите за обновлениями и не стесняйтесь комментировать.

Полезные ссылки:

7-Eleven : http://www.7-eleven.com

Игра Астероидов : http://www.play.vg/games/4-Asteroids.html

Астероиды : http://en.wikipedia.org/wiki/Asteroids_(video_game)

Скотт Сафран : http://en.wikipedia.org/wiki/Scott_Safran

Аркада на заднем дворе : http://www.themysteryworld.com/2011/02/guy-builds-video-arcade-in-his-back.html

StarWars сокращен : http://techland.time.com/2012/04/26/man-builds-16-scale-star-wars-arcade-game/

Тригонометрия : http: //en.wikipedia.org/wiki/Trigonometry

API узла JavaFX : http://docs.oracle.com/javafx/2/api/javafx/scene/Node.html

API JavaFX Scene : http://docs.oracle.com/javafx/2/api/javafx/scene/Scene.html

JavaFX SVGPath API : http://docs.oracle.com/javafx/2/api/javafx/scene/shape/SVGPath.html

Поддержка мультитач и жестов : http://www.oracle.com/technetwork/java/javafx/overview/roadmap-1446331.html

Pro JavaFX 2 Apress публикации — стр. 62 глава 2, раздел «Обработка входных событий». http://www.apress.com/9781430268727

Java 7 Рецепты Apress публикации- стр. 602 глава 16 Рецепт 16-3 «Анимация фигур вдоль пути». http://www.apress.com/9781430240563

Кабинет видеоигр : http://en.wikipedia.org/wiki/Video_game_arcade_cabinet

Растровая графика : http://en.wikipedia.org/wiki/Raster_graphics

Исходный код части 3 на GitHub : https://github.com/carldea/JFXGen/tree/master/demos/navigateship

JavaFX Canvas Node : http://mail.openjdk.java.net/pipermail/openjfx-dev/2012-April/001210.html

JavaFX — Оптимизация производительности для приложений JavaFX : http: //www.parleys.com/#st=5&id=2738&sl=0

Oracle, евангелист Java-технологии, Саймон

Риттер : https://blogs.oracle.com/javaone/entry/interfacing_with_the_interface_javafx

Эпизод 1 средней школы по видеоиграм: http: //www.rocketjump.com/? Video = vghs-episode-1

Эпизод средней школы по видеоиграм 2 : http: //www.rocketjump.com/? Video = vghs-episode-2-5

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