Basics

Guides

API Reference

Menu

Basics

Guides

API Reference

FXGL Getting Started Guide

FXGL is a 2D and 3D game engine built on top of JavaFX, written in Java and Kotlin by Dr. Almas Baimagambetov. This module is an Aussom wrapper around that library -- the engine itself is FXGL; Aussom exposes its public API so games can be written in Aussom without writing any Java. The wrappers cover the application lifecycle, the entity-component system, input, physics, UI, audio, animation, dialogue, mini-games, virtual controllers, the asset loader, and most of the supporting builders and services.

In Aussom, the FXGL surface lives in two places:

  • fxgl -- a static class exposing the FXGL DSL (the fxgl.gameApp entry point, plus helpers like fxgl.getInput, fxgl.set, fxgl.onKey, etc.).
  • fxgl.<Class> -- one Aussom class per wrapped FXGL Java class (fxgl.Entity, fxgl.GameSettings, fxgl.CollisionHandler, and so on).

Include only the wrapper classes you actually use. The fxgl static class is always available once fxgl itself is included.

What It Is Good For

  • 2D arcade, platformer, top-down, and side-scrolling games
  • physics-driven games (FXGL ships jbox2d behind the scenes)
  • prototypes that need an entity-component system and a render loop
  • games with menus, dialogue trees, achievements, and save / load
  • embedding a game viewport inside a larger JavaFX desktop app

Key Concepts

Area Common Classes
Lifecycle and launch fxgl.gameApp, fxglApp, GameSettings
Entities and views Entity, EntityBuilder, Component, ViewComponent
Input fxgl.onKey*, Input, UserAction
Physics PhysicsComponent, CollisionHandler, PhysicsWorld, BoundingShape
World scene GameWorld, GameScene, Viewport
World variables fxgl.set, fxgl.geti, fxgl.onIntChange, PropertyMap
UI and text fxgl.addText, fxgl.addVarText, UIFactoryService, NotificationService
Assets and audio AssetLoaderService, fxgl.loopBGM, fxgl.play
Animation AnimationBuilder, Animation, AnimationChannel

The Lifecycle

fxgl.gameApp(title, width, height) returns an fxglApp you configure by registering callbacks. Each callback is a method reference (::name). Call launch() last; it blocks until the game exits.

Hook Fires Typical work
onInitSettings(Cb) Once, before JavaFX starts Configure GameSettings
onPreInit(Cb) Once, before input init Register services
onInitInput(Cb) Once Bind keys and mouse buttons
onInitGameVars(Cb) Once Seed world properties
onInitGame(Cb) Once Spawn entities, set up world
onInitPhysics(Cb) Once Register collision handlers, gravity
onInitUI(Cb) Once Build the heads-up display
onUpdate(Cb) Every frame Per-tick logic; receives tpf (seconds)

Source: src/com/lehman/aussom/stdlib/aus/fxgl.aus:1039-1089.

Minimal Launch

A program that opens a 320 x 240 game window and exits after half a second. This is the same pattern used by the integration tests:

include fxgl;

class MyGame {
    public app = null;
    public elapsed = 0.0;

    public run() {
        this.app = fxgl.gameApp("My First Game", 320, 240);
        this.app.onUpdate(::tick);
        this.app.launch();
    }

    public tick(tpf) {
        this.elapsed = this.elapsed + tpf;
        if (this.elapsed > 0.5) { this.app.exit(); }
    }
}

new MyGame().run();

launch() blocks the calling thread. tpf is the elapsed time for the current frame in seconds. See tests/fxgl/integration/integrationTest.aus:67-71 for a working reference.

Configuring Game Settings

GameSettings is passed to your onInitSettings callback. Common setters:

include fxgl;
include fxgl.GameSettings;

public configureSettings(s) {
    s.setTitle("Coin Hunter")
     .setVersion("1.0")
     .setWidth(800)
     .setHeight(600)
     .setMainMenuEnabled(true)
     .setGameMenuEnabled(true)
     .setIntroEnabled(false);
}

All setters return this so they chain. Source: src/com/lehman/aussom/stdlib/aus/fxgl/GameSettings.aus:29-100.

Spawning Entities

An entity is anything that lives in the game world: the player, an enemy, a bullet, a wall, a coin. Each entity has a position and may carry a visual node, a hit box, a type, and any number of components.

The fluent way to build one is fxgl.entityBuilder():

include fxgl;
include fxgl.Entity;
include fxgl.FxObj;
include fx.Rectangle;
include fx.Color;

public spawnPlayer() {
    rect = new Rectangle(20.0, 20.0);
    rect.setFill(Color.BLUE());

    return fxgl.entityBuilder()
        .at(100.0, 100.0)
        .view(rect)
        .bbox("player-box")
        .collidable()
        .buildAndAttach();
}

Helpful builder methods (all chain):

  • .at(x, y) or .at(x, y, z) -- position
  • .atPoint2D(point) -- position from a JavaFX Point2D
  • .view(node) -- attach a visual JavaFX Node
  • .viewTexture(name) -- attach an image asset by file name
  • .bbox(hitBox) or .viewWithBBox(node) -- give it a hit box
  • .collidable() -- shortcut that adds a CollidableComponent
  • .with(component) -- attach any custom Component
  • .type(enumAjo) -- set the collision-handler type tag
  • .build() -- return the Entity wrapper without adding to world
  • .buildAndAttach() -- build and add to the running game world

Source: src/com/lehman/aussom/stdlib/aus/fxgl/EntityBuilder.aus.

You can also work with an Entity directly. Entity.setType("player") accepts a string, which is enough for most collision setups:

include fxgl.Entity;

e = new Entity();
e.setType("player");
e.addBoundingBox(20.0, 20.0);
e.addViewNode(rect);

Source: src/com/lehman/aussom/stdlib/aus/fxgl/Entity.aus:159-227.

Input

The simplest input API is fxgl.onKey* inside onInitInput:

include fxgl;

public bindInput() {
    fxgl.onKey("W", "Move Up", ::moveUp);
    fxgl.onKeyDown("SPACE", "Shoot", ::shoot);
    fxgl.onKeyUp("ESCAPE", "Quit", ::quitGame);
}

public moveUp() { /* runs every frame W is held */ }
public shoot() { /* runs once on the SPACE press */ }
public quitGame() { /* runs once on the ESCAPE release */ }

Key names are JavaFX KeyCode constants ("W", "SPACE", "ESCAPE", "UP", "LEFT", etc.). The three forms differ in when the callback fires:

  • onKey -- every frame the key is held
  • onKeyDown -- once on the press
  • onKeyUp -- once on the release

There is a matching trio for the mouse: onBtn, onBtnDown, onBtnUp. Source: src/com/lehman/aussom/stdlib/aus/fxgl.aus:681-696.

For more control (rebinding, modifier keys, capturing input events) adopt the running Input and create UserAction objects, the pattern used in tests/fxgl/deferred/deferredLifecycleTest.aus:

include fxgl;
include fxgl.Input;
include fxgl.UserAction;

public onInitInput() {
    input = Input.adopt(fxgl.getInput());
    action = new UserAction("Jump");
    action.onActionBegin(::onJump);
    input.addActionKey(action, "SPACE");
}

public onJump() { /* ... */ }

Authoring Custom Behavior With Components

A Component carries per-entity behavior that runs each frame. Create one, register the lifecycle hooks, and attach it:

include fxgl;
include fxgl.Entity;
include fxgl.Component;

public buildMover() {
    c = new Component();
    c.onAdded(::onAdded);
    c.onUpdate(::tick);
    c.onRemoved(::onRemoved);
    return c;
}

public onAdded(entity) { /* fires once on attach */ }
public tick(tpf) { /* fires every frame */ }
public onRemoved(entity) { /* fires once on detach */ }

Attach to an entity with entity.addComponent(c). Source: src/com/lehman/aussom/stdlib/aus/fxgl/Component.aus.

Collisions

Use CollisionHandler for pairwise collision callbacks. Set each entity's type to a matching string, register a handler, and add it to the physics world inside onInitPhysics:

include fxgl;
include fxgl.CollisionHandler;

public onInitPhysics() {
    handler = new CollisionHandler("player", "coin");
    handler.onCollisionBegin(::onPlayerCoin);
    fxgl.getPhysicsWorld().invoke("addCollisionHandler", handler.obj);
}

public onPlayerCoin(player, coin) {
    coin.invoke("removeFromWorld");
    fxgl.inc("coins", 1);
}

Callbacks receive the two Entity objects in the order of the type pair. Other hooks on CollisionHandler: onCollision (every frame of contact), onCollisionEnd (contact ends), and onHitBoxTrigger. Source: src/com/lehman/aussom/stdlib/aus/fxgl/CollisionHandler.aus.

The pattern above (from tests/fxgl/deferred/deferredLifecycleTest.aus) uses the running PhysicsWorld directly. For symmetric short-form access there is also fxgl.onCollisionBegin(typeA, typeB, cb) which takes Java enum AJOs for the types.

World Variables

FXGL holds a shared bag of typed properties for the game state. Use them so any callback can read or watch the same data without passing it around. Seed them in onInitGameVars:

include fxgl;

public onInitGameVars() {
    fxgl.set("hp", 100);
    fxgl.set("speed", 1.5);
    fxgl.set("name", "Hogan");
    fxgl.set("coins", 0);
}

public tick(tpf) {
    if (fxgl.geti("hp") <= 0) { this.app.exit(); }
}

public watchCoins() {
    fxgl.onIntChange("coins", ::onCoinsChanged);
}

public onCoinsChanged(newCount) { /* react to coin pickups */ }

Typed accessors: geti, getd, gets, getb, geto (object). Change watchers: onIntChange, onDoubleChange, onStringChange, onBooleanChange, onObjectChange. Source: tests/fxgl/integration/integrationTest.aus:88-93 and src/com/lehman/aussom/stdlib/aus/fxgl.aus:620-732.

UI and On-Screen Text

The simplest way to put text on screen during play is fxgl.addText for a static label or fxgl.addVarText for a label bound to a world property:

include fxgl;

public onInitUI() {
    fxgl.addText("Coin Hunter v1.0", 10.0, 20.0);
    fxgl.addVarText("coins", 10.0, 40.0);
}

addVarText updates automatically whenever the named property changes -- a free heads-up display for the score, the player's HP, the timer, etc. Both helpers return the underlying JavaFX Text node. Source: src/com/lehman/aussom/stdlib/fxgl/AussomFxgl.java (addText / addVarText methods).

For full control, build a JavaFX node and add it through the GameScene -- the pattern used by the integration test:

include fxgl;
include fxgl.GameScene;
include fxgl.InGamePanel;

public onInitUI() {
    gs = GameScene.adopt(fxgl.getGameScene());
    panel = new InGamePanel(120.0, 80.0, "LEFT");
    gs.addUINode(panel);
    panel.open();
}

Source: tests/fxgl/integration/integrationTest.aus:123-131.

A Tiny Complete Game

This puts the pieces together: a player rectangle, a coin entity, an input binding, a collision handler, and a HUD label showing the score.

include fxgl;
include fxgl.GameSettings;
include fxgl.Entity;
include fxgl.CollisionHandler;
include fx.Rectangle;
include fx.Color;

class CoinHunter {
    public app = null;
    public player = null;

    public run() {
        this.app = fxgl.gameApp("Coin Hunter", 400, 300);
        this.app.onInitSettings(::settings);
        this.app.onInitGameVars(::vars);
        this.app.onInitGame(::game);
        this.app.onInitPhysics(::physics);
        this.app.onInitInput(::input);
        this.app.onInitUI(::ui);
        this.app.launch();
    }

    public settings(s) { s.setMainMenuEnabled(false); }
    public vars() { fxgl.set("coins", 0); }

    public game() {
        playerRect = new Rectangle(20.0, 20.0);
        playerRect.setFill(Color.BLUE());
        this.player = fxgl.entityBuilder()
            .at(50.0, 50.0).view(playerRect).collidable()
            .buildAndAttach();
        this.player.setType("player");

        coinRect = new Rectangle(16.0, 16.0);
        coinRect.setFill(Color.GOLD());
        coin = fxgl.entityBuilder()
            .at(200.0, 200.0).view(coinRect).collidable()
            .buildAndAttach();
        coin.setType("coin");
    }

    public physics() {
        h = new CollisionHandler("player", "coin");
        h.onCollisionBegin(::onPickup);
        fxgl.getPhysicsWorld().invoke("addCollisionHandler", h.obj);
    }

    public input() {
        fxgl.onKey("W", "Up",    ::moveUp);
        fxgl.onKey("S", "Down",  ::moveDown);
        fxgl.onKey("A", "Left",  ::moveLeft);
        fxgl.onKey("D", "Right", ::moveRight);
    }

    public ui() {
        fxgl.addText("Coins:", 10.0, 20.0);
        fxgl.addVarText("coins", 60.0, 20.0);
    }

    public onPickup(player, coin) {
        coin.invoke("removeFromWorld");
        fxgl.inc("coins", 1);
    }

    public moveUp()    { this.player.translateY(-2.0); }
    public moveDown()  { this.player.translateY(2.0); }
    public moveLeft()  { this.player.translateX(-2.0); }
    public moveRight() { this.player.translateX(2.0); }
}

new CoinHunter().run();

Every method called above (setMainMenuEnabled, entityBuilder, setType, translateX/Y, removeFromWorld, onKey, addText, addVarText, inc, getPhysicsWorld) is wrapped in src/com/lehman/aussom/stdlib/aus/fxgl/ or on the fxgl DSL static class.

Going Further

When you outgrow the basics, these wrappers cover the bigger features of FXGL. Each lives at src/com/lehman/aussom/stdlib/aus/fxgl/.

Class Why look at it
AnimationBuilder Fluent translate / scale / rotate / fade animations with easing
PhysicsComponent jbox2d dynamics: setBodyType, setLinearVelocity, setAngularVelocity
ProjectileComponent Auto-velocity for bullets and arrows; pairs with ProjectileWithAccelerationComponent
ParticleEmitter and ParticleEmitters Spawn particles with built-in fire / explode / smoke recipes
Viewport Camera control, screen bounds, follow-entity helpers
GameWorld Spawn / query entities, register entity factories
GameScene Add UI nodes, switch render layers, manage the scene graph
Input, UserAction Long-form input binding, rebinding, mock-press in tests
AssetLoaderService, FXGLAssetLoaderService Load textures, sounds, music, dialogue files, text
CutsceneService, Cutscene, DialogueGraph, DialogueNode Story scenes and branching dialogue trees
NotificationService Toast notifications
SaveLoadService Save / load progress to FXGL's bundle format
AchievementService Track and unlock named achievements
MiniGameService Trigger-mash, trigger-sequence, circuit-breaker mini games
VirtualController, VirtualDpad, VirtualJoystick, VirtualMenuKey On-screen controls for touch and mobile

For the full surface, see design/fxgl-audit.md in this repo -- it lists every wrapped class, the methods on each, and the few items that are deliberately not wrapped.