| name | fxgl-game-lifecycle |
| description | Bootstrap and structure an FXGL game application — set up GameApplication subclass, configure initSettings, initGameVars, initInput, initGame, initPhysics, initUI, and onUpdate. Use this skill when creating a new FXGL game, adding engine services, configuring window size/title, setting application mode, or wiring the game loop.
|
| triggers | ["create FXGL game","GameApplication","initSettings","initGame","game loop","FXGL project setup","engine service","game bootstrap"] |
| compatibility | Java 17+, FXGL 21.x, Maven or Gradle. JavaFX runtime must be on the module path.
|
| category | fxgl/fundamentals |
| tags | ["fxgl","java","javafx","fundamentals","game","lifecycle"] |
| metadata | {"author":"fxgl-skills","version":"1.0","fxgl-version":"21.1"} |
| allowed-tools | ["Read","Write","Edit","Bash"] |
FXGL Game Lifecycle
Overview
Every FXGL game extends GameApplication and launches via launch(args).
The engine calls lifecycle methods in strict order — honour that order or face NPEs.
Lifecycle Order (guaranteed by engine)
launch(args)
└─ initSettings(GameSettings) ← configure window, services, achievements
└─ initGameVars(Map) ← declare all typed world variables
└─ initInput() ← register keyboard/mouse actions
└─ initGame() ← spawn entities, load level, register factories
└─ initPhysics() ← set gravity, collision handlers
└─ initUI() ← build HUD nodes
└─ [game loop starts]
└─ onUpdate(double tpf) ← runs every frame
Rule: Never call getGameWorld(), getPhysicsWorld(), or getInput() inside
initSettings() — those services are not yet initialised.
Minimal Working Game
import com.almasb.fxgl.app.GameApplication;
import com.almasb.fxgl.app.GameSettings;
import static com.almasb.fxgl.dsl.FXGL.*;
public class MyGame extends GameApplication {
@Override
protected void initSettings(GameSettings settings) {
settings.setWidth(1280);
settings.setHeight(720);
settings.setTitle("My Game");
settings.setVersion("0.1");
}
@Override
protected void initGame() {
entityBuilder()
.at(400, 300)
.view(new javafx.scene.shape.Rectangle(40, 40, javafx.scene.paint.Color.BLUE))
.buildAndAttach();
}
public static void main(String[] args) {
launch(args);
}
}
See assets/templates/GameAppTemplate.java for a full template with all hooks.
initSettings — Key Options
@Override
protected void initSettings(GameSettings settings) {
settings.setWidth(1280);
settings.setHeight(720);
settings.setTitle("My Game");
settings.setVersion("1.0");
settings.setMainMenuEnabled(true);
settings.setGameMenuEnabled(true);
settings.setEnabledMenuItems(EnumSet.of(MenuItem.NEW_GAME, MenuItem.SAVE, MenuItem.LOAD, MenuItem.EXIT));
settings.setApplicationMode(ApplicationMode.DEVELOPER);
settings.setDeveloperMenuEnabled(true);
settings.setProfilingEnabled(true);
settings.setFullScreenAllowed(true);
settings.setFullScreenFromStart(false);
settings.addEngineService(QuestService.class);
settings.addEngineService(SaveLoadService.class);
settings.getAchievements().add(new Achievement("First Kill", "Kill an enemy", "kills", 1));
}
initGameVars — World Variable Declaration
All variables used elsewhere must be declared here. Missing a variable declaration
causes IllegalArgumentException on first access.
@Override
protected void initGameVars(Map<String, Object> vars) {
vars.put("score", 0);
vars.put("lives", 3);
vars.put("speed", 200.0);
vars.put("paused", false);
vars.put("playerName", "");
}
initInput — Binding Actions
@Override
protected void initInput() {
onKey(KeyCode.A, () -> getPlayer().translateX(-5));
onKey(KeyCode.D, () -> getPlayer().translateX(5));
onKeyDown(KeyCode.SPACE, () -> shoot());
onKeyUp(KeyCode.SHIFT, () -> stopSprint());
onBtnDownPrimary(() -> spawnProjectile());
}
initGame — Entity & World Setup
@Override
protected void initGame() {
getGameWorld().addEntityFactory(new MyEntityFactory());
setLevelFromMap("level1.tmx");
spawn("player", 100, 100);
spawn("enemy", 500, 300);
entityBuilder().buildScreenBoundsAndAttach(40);
Entity player = getGameWorld().getSingleton(e -> e.isType(EntityType.PLAYER));
getGameScene().getViewport().bindToEntity(player, getAppWidth() / 2.0, getAppHeight() / 2.0);
}
initPhysics — Gravity & Collision Handlers
@Override
protected void initPhysics() {
getPhysicsWorld().setGravity(0, 1200);
onCollisionBegin(EntityType.PLAYER, EntityType.COIN, (player, coin) -> {
coin.removeFromWorld();
inc("score", +10);
});
onCollisionBegin(EntityType.PLAYER, EntityType.ENEMY, (player, enemy) -> {
inc("lives", -1);
if (geti("lives") <= 0) showGameOver();
});
}
initUI — HUD
@Override
protected void initUI() {
Text scoreText = getUIFactoryService().newText("", Color.WHITE, 24);
scoreText.textProperty().bind(getip("score").asString("Score: %d"));
addUINode(scoreText, 20, 40);
Text livesText = getUIFactoryService().newText("", Color.RED, 24);
livesText.textProperty().bind(getip("lives").asString("Lives: %d"));
addUINode(livesText, 20, 70);
}
onUpdate — Game Loop
@Override
protected void onUpdate(double tpf) {
spawnTimer += tpf;
if (spawnTimer > 3.0) {
spawn("enemy", random(0, getAppWidth()), -50);
spawnTimer = 0;
}
}
private double spawnTimer = 0;
Custom Engine Services
public class ScoreService extends EngineService {
private int highScore = 0;
@Override
public void onGameReady() { }
public int getHighScore() { return highScore; }
public void updateHighScore(int score) { highScore = Math.max(highScore, score); }
}
settings.addEngineService(ScoreService.class);
FXGL.getService(ScoreService.class).updateHighScore(geti("score"));
Gotchas
- Asset directory: all assets must be under
src/main/resources/assets/. FXGL
auto-prefixes this path. Writing getAssetLoader().loadTexture("textures/player.png")
resolves to assets/textures/player.png on the classpath.
launch(args) is final — do not override it. Override lifecycle hooks instead.
initSettings runs before JavaFX Application.start() — avoid any JavaFX node
creation here.
- Kotlin DSL: use
import com.almasb.fxgl.dsl.* and GameApplication() — same hooks,
same order. The FXGLForKt file provides Kotlin-specific extension functions.
- Application mode: always ship with
ApplicationMode.RELEASE. The developer menu
and profiler are disabled automatically.
- Module path for JavaFX: add
--module-path and --add-modules javafx.controls,javafx.fxml
to JVM args in your build file if running outside a module-aware launcher.
- initGameVars is mandatory for reactive properties:
getip("score") throws
IllegalArgumentException if "score" was not declared in initGameVars.
- World reset: calling
getGameController().startNewGame() re-runs all init* hooks.
Avoid storing state in instance fields unless you reset it in initGameVars.
Quick Reference
| DSL method | Description |
|---|
getApp() | Returns the GameApplication instance |
getAppWidth() / getAppHeight() | Window dimensions in pixels |
getAppCenter() | Point2D of the window centre |
getSettings() | Read-only settings snapshot |
getGameController() | Control game state (startNewGame, exit, pause) |
tpf() | Time per frame of the last frame (seconds) |
See references/settings-reference.md for the full GameSettings API.