Автор оригинала: Pankaj Kumar.
В этом простом уроке по FXGL мы разработаем игру под названием “Космический рейнджер”.
Что такое FX GL?
FXGL-это игровой движок, построенный на базе JavaFX, который представляет собой набор инструментов с графическим интерфейсом для настольных, мобильных и встроенных систем.
Зачем использовать JavaFX FX GLover?
Сам по себе JavaFX предоставляет возможности рендеринга общего назначения и пользовательского интерфейса. Кроме того, FXGL предлагает методы и инструменты разработки игр в реальном мире, что облегчает разработку кроссплатформенных игр.
Как скачать банки FX GL?
FXGL можно загрузить как зависимость Maven или Gradle. Например, координаты Maven следующие, которые можно использовать с Java 11+.
com.github.almasb fxgl 11.8
Если вы застряли в какой-либо момент, вы можете найти ссылку с полным исходным кодом в конце этого руководства.
Игра Космические рейнджеры
Идея нашей игры относительно проста. У нас есть куча врагов, которые пытаются добраться до нашей базы, и у нас есть единственный защитник базы — игрок. Учитывая, что это вводный урок, мы не будем использовать какие-либо ресурсы, такие как изображения, звуки и другие внешние ресурсы. Когда игра будет завершена, она будет выглядеть так:
Давайте начнем!
Необходимый Импорт
Во-первых, давайте позаботимся обо всем импорте, чтобы мы могли сосредоточиться на аспекте кода в учебнике. Для простоты весь код будет находиться в одном файле, однако вы можете разместить каждый класс в отдельном файле.
Создайте файл SpaceRangerApp.java
и разместите следующие импортные:
import com.almasb.fxgl.animation.Interpolators; import com.almasb.fxgl.app.GameApplication; import com.almasb.fxgl.app.GameSettings; import com.almasb.fxgl.core.math.FXGLMath; import com.almasb.fxgl.dsl.components.ProjectileComponent; import com.almasb.fxgl.entity.Entity; import com.almasb.fxgl.entity.EntityFactory; import com.almasb.fxgl.entity.SpawnData; import com.almasb.fxgl.entity.Spawns; import javafx.geometry.Point2D; import javafx.scene.input.KeyCode; import javafx.scene.input.MouseButton; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.util.Duration; import static com.almasb.fxgl.dsl.FXGL.*;
Вот и все, наш первый шаг сделан. Обратите внимание, что последний статический импорт упрощает множество вызовов, выполняемых в FXGL, и является рекомендуемым подходом.
Код игры
Теперь мы начнем писать некоторый код. Как и в любом другом приложении, мы начинаем с указания точки входа в программу. Мы также предоставим некоторые базовые настройки для FXGL, такие как ширина, высота и название нашей игры.
public class SpaceRangerApp extends GameApplication { @Override protected void initSettings(GameSettings settings) { settings.setTitle("Space Ranger"); settings.setWidth(800); settings.setHeight(600); } public static void main(String[] args) { launch(args); } }
Игровые Объекты/Сущности
Каждый игровой движок имеет свою собственную терминологию для внутриигровых объектов. В FXGL, использующей модель “Сущность-компонент”, игровые объекты называются сущностями. Каждая сущность обычно имеет свой тип, например, игрок, враг, снаряд и так далее. В нашей игре у нас будут именно такие типы, которые только что были описаны:
public enum EntityType { PLAYER, ENEMY, PROJECTILE }
FXGL должен знать, как создавать эти типы. Для этого мы создаем фабрику. Как упоминалось ранее, мы сохраняем все классы в одном файле. На этом этапе, если вы предпочитаете, вы можете переместить класс “Фабрика космических рейнджеров” в его собственный файл (если вы это сделаете, просто удалите ключевое слово “статический”).
public class SpaceRangerApp extends GameApplication { public static class SpaceRangerFactory implements EntityFactory { } }
Существует упрощенный процесс определения сущностей. Внутри “Фабрики космических рейнджеров” мы определяем новый метод, который принимает объект данных Порождения, возвращает объект сущности и содержит аннотацию порождения:
@Spawns("player") public Entity newPlayer(SpawnData data) { var top = new Rectangle(60, 20, Color.BLUE); top.setStroke(Color.GRAY); var body = new Rectangle(25, 60, Color.BLUE); body.setStroke(Color.GRAY); var bot = new Rectangle(60, 20, Color.BLUE); bot.setStroke(Color.GRAY); bot.setTranslateY(40); return entityBuilder() .type(EntityType.PLAYER) .from(data) .view(body) .view(top) .view(bot) .build(); }
Как легко видно из аннотации, этот метод порождает объект игрока (сущность). Теперь мы подробно рассмотрим этот метод.
Сначала мы построим три части представления игрока, которые являются стандартными прямоугольниками JavaFX. Обратите внимание, что мы используем синтаксис “var”. Используя “EntityBuilder()”, мы указываем тип, данные, на основе которых можно задать положение и представления сущности игрока. Этот свободный API позволяет нам создавать объекты в сжатой форме.
Наш следующий шаг-определить снаряды в нашей игре:
@Spawns("projectile") public Entity newProjectile(SpawnData data) { var view = new Rectangle(30, 3, Color.LIGHTBLUE); view.setStroke(Color.WHITE); view.setArcWidth(15); view.setArcHeight(10); return entityBuilder() .type(EntityType.PROJECTILE) .from(data) .viewWithBBox(view) .collidable() .zIndex(-5) .with(new ProjectileComponent(new Point2D(1, 0), 760)) .build(); }
Вид снова представляет собой прямоугольник JavaFX со скругленными углами. На этот раз мы вызываем “просмотр с помощью функции Box()”, а не просто “просмотр ()”. Первый метод автоматически создает ограничивающую рамку на основе представления. Аккуратно!
Затем, используя “collidable()”, мы помечаем наш снаряд как объект, который может столкнуться с другими объектами. Мы вернемся к столкновениям позже. Мы устанавливаем z-индекс на отрицательное значение, чтобы он рисовался перед игроком (по умолчанию каждый объект имеет z-индекс 0).
Наконец, мы добавляем компонент снаряда со скоростью, равной 760, и вектор направления (1, 0), что означает 1 по оси X и 0 по оси Y, что, в свою очередь, означает движение вправо.
Наша последняя сущность, которую нужно определить, относится к типу враг:
@Spawns("enemy") public Entity newEnemy(SpawnData data) { var view = new Rectangle(80, 20, Color.RED); view.setStroke(Color.GRAY); view.setStrokeWidth(0.5); animationBuilder() .interpolator(Interpolators.SMOOTH.EASE_OUT()) .duration(Duration.seconds(0.5)) .repeatInfinitely() .animate(view.fillProperty()) .from(Color.RED) .to(Color.DARKRED) .buildAndPlay(); return entityBuilder() .type(EntityType.ENEMY) .from(data) .viewWithBBox(view) .collidable() .with(new ProjectileComponent(new Point2D(-1, 0), FXGLMath.random(50, 150))) .build(); }
Мы уже рассмотрели методы fluent API, такие как “type()” и “collidable()”, поэтому вместо этого мы сосредоточимся на анимации.
Как вы можете видеть, конструктор анимации также следует аналогичному соглашению fluent API. Это позволяет нам устанавливать различные настройки анимации, такие как продолжительность и количество повторений.
Мы можем наблюдать, что анимация работает с “свойством заполнения()” на прямоугольном представлении, которое мы используем для представления врага. В частности, заливка анимируется от КРАСНОГО до ТЕМНО-КРАСНОГО каждые 0,5 секунды. Не стесняйтесь настраивать параметры анимации, чтобы увидеть, что лучше всего подходит для вашей игры.
Наш заводской класс теперь завершен, и мы начнем объединять наш код.
Ввод
Все входные данные FXGL обычно обрабатываются внутри метода “initInput” следующим образом:
@Override protected void initInput() { onKey(KeyCode.W, () -> getGameWorld().getSingleton(EntityType.PLAYER).translateY(-5)); onKey(KeyCode.S, () -> getGameWorld().getSingleton(EntityType.PLAYER).translateY(5)); onBtnDown(MouseButton.PRIMARY, () -> { double y = getGameWorld().getSingleton(EntityType.PLAYER).getY(); spawn("projectile", 0, y + 10); spawn("projectile", 0, y + 50); }); }
Первые два звонка определили наше движение игрока. Более конкретно, клавиши W и S будут перемещать плеер вверх и вниз соответственно. Наш последний звонок настраивает действие игрока, которое заключается в стрельбе. Когда нажата основная кнопка мыши, мы запускаем наши снаряды, которые мы определили ранее. Два последних аргумента функции “порождение” – это значения x и y, указывающие, где должен появиться снаряд.
Игровая Логика
Прежде чем мы сможем начать игру, нам нужно инициализировать некоторую игровую логику, которую мы можем сделать следующим образом:
@Override protected void initGame() { getGameScene().setBackgroundColor(Color.BLACK); getGameWorld().addEntityFactory(new SpaceRangerFactory()); spawn("player", 0, getAppHeight() / 2 - 30); run(() -> { double x = getAppWidth(); double y = FXGLMath.random(0, getAppHeight() - 20); spawn("enemy", x, y); }, Duration.seconds(0.25)); }
Мы установили фон игровой сцены в черный цвет (вы можете выбрать другой цвет в соответствии с вашими потребностями).
Затем мы добавляем нашу фабрику сущностей — FXGL должен знать, как создавать наши сущности.
После этого мы создаем нашего игрока в левой части экрана, так как значение x равно 0.
Наконец, мы настроили действие таймера, которое выполняется каждые 0,25 секунды. Действие состоит в том, чтобы породить врага в случайном месте Y.
Физика
Наш физический код тривиален, так как в нашей игре не так много вещей, которые будут сталкиваться.
@Override protected void initPhysics() { onCollisionBegin(EntityType.PROJECTILE, EntityType.ENEMY, (proj, enemy) -> { proj.removeFromWorld(); enemy.removeFromWorld(); }); }
Как видно выше, единственными двумя типами сущностей, которые нас интересуют в отношении столкновений, являются снаряд и враг. Мы настраиваем обработчик, который вызывается при столкновении этих двух типов, предоставляя нам ссылки на конкретные объекты, которые столкнулись. Обратите внимание на порядок, в котором определены типы. Это порядок, в котором передаются ссылки на сущности. Мы хотим удалить их обоих из мира, когда они столкнутся.
Обновление
В нашем игровом цикле не так много вещей. Мы хотим знать только, когда враг достиг нашей базы, т. Е. значение X врага меньше 0.
@Override protected void onUpdate(double tpf) { var enemiesThatReachedBase = getGameWorld().getEntitiesFiltered(e -> e.isType(EntityType.ENEMY) && e.getX() < 0); if (!enemiesThatReachedBase.isEmpty()) { showMessage("Game Over!", () -> getGameController().startNewGame()); } }
Чтобы достичь этого, мы запрашиваем игровой мир, чтобы он выдал нам все сущности, тип которых-враг, а значение X меньше 0. Если список не пуст, значит, мы проиграли игру, поэтому показываем соответствующее сообщение и перезапускаемся.
Вывод
На этом этот урок заканчивается. Теперь вы должны иметь возможность запускать игру в своей среде разработки. Игровой движок FXFL полностью с открытым исходным кодом,и исходный код доступен по адресу https://github.com/AlmasB/FXGL .
Полный исходный код этого руководства доступен по адресу https://github.com/AlmasB/FXGLGames/tree/master/SpaceRanger .