FlixelGDX is a high-level 2D game framework built on top of libGDX. It aims to replicate the powerful and developer-friendly API of HaxeFlixel (based on the original ActionScript Flixel) while leveraging the robust cross-platform capabilities and performance of the libGDX ecosystem.
The goal of FlixelGDX is to provide the familiar Flixel-like structure that developers love, while remaining open for experienced libGDX developers to integrate their own lower-level functionality.
Note
FlixelGDX is an independent project and is not officially affiliated with HaxeFlixel or libGDX.
- Contributing Guide: Learn how to contribute to the project, coding standards, and PR requirements.
- Project Structure: Understand the multi-module layout and how Gradle is used.
- Compiling & Testing: How to build the framework and test it as a dependency in your own projects.
FlixelGDX supports the following platforms through its modular backend system:
- Desktop: Windows, macOS, and Linux via LWJGL3.
- Android: Full support for Android mobile devices.
- iOS: Support via RoboVM.
- Web: Support via TeaVM.
Warning
FlixelGDX is a modern framework, which means it requires modern Java. You must use Java 17 or higher to be able to use FlixelGDX. Because of this, web games don't (and can't) support GWT; it is done through a modern library called TeaVM, which transpiles Java code to JavaScript to be run in a browser.
FlixelGDX attempts to bring the ease of use and rapid prototyping capabilities of HaxeFlixel to Java, while also using the tools of libGDX to allow it to be seamlessly integrated into new and existing projects alike.
We use libGDX's standard lifecycle and incorporate Flixel's methods (update(), draw(), destroy()) and performance-oriented tools (like Poolable interfaces)
to ensure that lifecycle and performance are handled correctly.
Because everything is built on top of libGDX, adding FlixelGDX to a new or existing project will be a breeze.
We also provide dedicated launcher classes for every platform, so you can run your game without any additional setup.
When you use gdx-liftoff, you can quickly generate a new libGDX project, then you can add FlixelGDX as a dependency and start coding your game right away.
In your core code, you can simply extend FlixelGame and start coding your game:
public class MyGame extends FlixelGame {
// Make sure to create a constructor so you can use it in a launcher!
public MyGame(String title, int width, int height, FlixelState initialState) {
super(title, width, height, initialState);
}
@Override
public void create() {
super.create();
// Your game initialization code here!
}
}Then, in a platform-specific launcher class, you can create an instance of your game and start it:
// Example of how to create a new game instance and run it using the LWJGL3 launcher.
public class Lwjgl3Launcher {
public static void main(String[] args) {
if (StartupHelper.startNewJvmIfRequired()) { // This handles macOS support and helps on Windows.
return;
}
MyGame game = new MyGame(
"My Game",
800,
600,
new InitialState() // The initial state the game enters when it starts!
);
// Start the game using the LWJGL3 launcher.
FlixelLwjgl3Launcher.launch(game);
}
}We keep the "hood open" for developers who need to drop down into standard libGDX code when necessary. This allows you to use the full power of libGDX without having to reinvent the wheel or use hacky workarounds, while also keeping things simple and easy to understand by default for newbies.
One example is in the FlixelCamera class. While by default it works just like an FlxCamera from the original Flixel library,
it secretly uses a libGDX Camera and Viewport under the hood, and we allow you to access and modify these directly.
public class Custom3DCamera extends FlixelCamera {
public Custom3DCamera(float x, float y, int width, int height, float zoom) {
// You can add your own custom camera and viewport directly!
super(x, y, width, height, zoom, new PerspectiveCamera(), new ExtendViewport(640, 360));
}
}- Entity Component-ish System: Clean separation of logic and rendering via
FlixelBasic,FlixelObject, andFlixelSprite. - State Management: Simple state switching and substates for menus, pauses, and transitions.
- Group System: Powerful grouping for batch updates, collisions, and nested transformations.
- Tweening: Built-in tweening system for smooth animations and transitions, with property-based tweens that work naturally with getters/setters.
- Input Handling: Simplified keyboard and touch/mouse input.
- Logging & Debugging: Integrated logging system with stack trace support.
FlixelGDX keeps the familiar FlxSprite-style workflow that HaxeFlixel developers expect, but in plain Java and on top of libGDX.
public class Player extends FlixelSprite {
public Player() {
// Create a 16×16 white box at (0, 0).
makeGraphic(16, 16, Color.WHITE);
// Basic positioning and motion come from FlixelObject.
setX(100);
setY(80);
velocityX = 60; // move right (velocityY, acceleration, drag, etc. also available)
}
}How it Works
The entity hierarchy mirrors HaxeFlixel:
FlixelBasic- The base for everything. It has no position or graphics, only lifecycle flags:active(whetherupdate()runs),exists(whether it is updated and drawn),alive(for game logic), andvisible(whether it is drawn). Overrideupdate(float)anddraw(Batch)for custom behavior; usekill()/revive()to temporarily disable or bring back objects.FlixelObject- Adds position (x,y), size (width,height), rotation (angle), and physics-style motion: velocity, acceleration, drag, and max velocity (X/Y and angular). Setmoves = true(default) soupdate()callsupdateMotion(), which applies velocity and acceleration each frame.FlixelSprite- Adds rendering: textures, spritesheets, animations, scale, origin, offset, color tint, and flip. UseloadGraphic()for images,makeGraphic()for solid rectangles, orloadSparrowFrames()for XML-based atlases; thenaddAnimation()/addAnimationByPrefix()andplayAnimation()for frame-based animation.
So you get a clear separation: logic and “does it exist?” in FlixelBasic, position and movement in FlixelObject, and drawing in FlixelSprite. That keeps code organized whether you’re prototyping or building a larger game.
In a FlixelGDX project you add sprites (and other FlixelBasic objects) to a state with add(). The state’s update/draw loop will then update and draw them automatically, and you can use groups to organize them (see Group System).
public class PlayState extends FlixelState {
private Player player;
@Override
public void create() {
player = new Player();
add(player); // managed and updated by the state
}
}Because FlixelState implements libGDX's Screen API, this integrates cleanly with a normal libGDX launcher.
You can use FlixelSprite and FlixelObject even if you don’t use FlixelGame or FlixelState. Construct them, call loadGraphic() or makeGraphic() as needed, and then in your own Screen or ApplicationListener call sprite.update(delta) and sprite.draw(batch) each frame. You’re responsible for the update/draw order and lifecycle; FlixelGDX doesn’t require a specific game loop.
States give you a high-level way to structure screens (menus, loading, gameplay, pause overlays, etc.), similar to HaxeFlixel's FlxState and FlxSubState. Each state is a Screen and a group: you override create(), update(float), and draw(Batch) and add sprites/groups with add().
How it works.
FlixelStateextendsFlixelGroup<FlixelBasic>and implements libGDX’sScreen. When the game switches to a state,create()is called once; then each frame the state’supdate(delta)anddraw(batch)run, which in turn update and draw all members you added. UseFlixel.switchState(newState)to transition; the old state is disposed automatically.FlixelSubStateis a state that is opened on top of another state. By default the parent state stops updating (persistentUpdate = false) but keeps drawing (persistentDraw = true), so you get a classic “pause overlay” or modal dialog. The substate hasopenCallbackandcloseCallback; callclose()from inside the substate to remove it and resume the parent.- Outros. Override
startOutro(onOutroComplete)on a state to run an exit animation or transition before the switch; when done, call the callback so the framework proceeds with the state change.
public class MenuState extends FlixelState {
@Override
public void create() {
// Set your background color and add sprites/buttons here.
}
@Override
public void update(float delta) {
super.update(delta);
if (Flixel.keyJustPressed(FlixelKey.ENTER)) {
Flixel.switchState(new PlayState());
}
}
}Substates are ideal for pause menus, dialogs, or any overlay that should block input to the underlying state:
public class PauseSubState extends FlixelSubState {
public PauseSubState() {
super(new Color(0, 0, 0, 0.5f)); // semi-transparent overlay
}
@Override
public void create() {
// Add resume button, etc.
}
@Override
public void update(float delta) {
super.update(delta);
if (Flixel.keyJustPressed(FlixelKey.ESCAPE)) {
close(); // returns to parent state
}
}
}From the play state you open it with:
openSubState(new PauseSubState());You can nest substates (a substate can open another substate). Control whether the parent keeps updating or drawing while a substate is open via persistentUpdate and persistentDraw on the parent state.
In a standard FlixelGDX setup you pass the initial FlixelState to FlixelGame and use Flixel.switchState(...) for transitions; the game handles Screen switching and disposal. If you embed FlixelGDX in an existing libGDX game, you can still instantiate states and call their create(), update(), and draw() yourself, then use Flixel.switchState() only if you’ve also initialized Flixel with FlixelGame; otherwise manage which state is “current” and call its methods from your own render loop.
Groups batch-update and draw many objects so you don’t hand-write loops everywhere. FlixelState is a FlixelGroup<FlixelBasic>, so when you add() sprites to a state you’re already using the group system.
How it Works
FlixelGroup<T>holds a list ofFlixelBasicmembers. Each frame it iterates over them and callsupdate(elapsed)thendraw(batch). Youadd()andremove()members;clear()removes all without destroying them;destroy()destroys every member and clears the list.- Capacity. The constructor takes an optional
maxSize. IfmaxSize > 0,add()won’t add when the group is full, which helps for fixed-size object pools (e.g. bullets). - Iteration. Use
forEachMember(callback)to run logic on every member, orforEachMemberType(Class<C>, callback)to iterate only over members of a given type (e.g. allFlixelSpriteor your ownEnemyclass).
public class EnemyGroup extends FlixelGroup<FlixelBasic> {
public EnemyGroup() {
super(0); // 0 = unlimited size
}
public void spawnEnemy(float x, float y) {
Enemy enemy = new Enemy(x, y);
add(enemy);
}
}Inside any FlixelState you can mix groups and single objects:
EnemyGroup enemies = new EnemyGroup();
add(enemies);
enemies.spawnEnemy(200, 120);
// Or iterate over a specific type
enemies.forEachMemberType(Enemy.class, enemy -> enemy.setTarget(player));Groups can contain other groups (nested structure). Because everything uses libGDX’s Batch and the same update/draw lifecycle, this fits into an existing libGDX game while giving you a Flixel-style hierarchy.
Use FlixelGroup the same way: create a group, add() your FlixelBasic (or FlixelSprite) instances, and each frame call group.update(delta) and group.draw(batch) from your own game loop. You don’t need FlixelGame or FlixelState for the group logic to work.
FlixelGDX includes a built-in tweening system inspired by Flixel/HaxeFlixel that aims to solve two common problems:
- Reducing boilerplate compared to Universal Tween Engine (UTE), which typically requires writing a
TweenAccessorfor every type you want to animate. - Providing both beginner-friendly defaults and power features that experienced users expect (easing, delays, callbacks, looping), without forcing you into reflection-heavy or brittle APIs.
At the core is FlixelTween plus a settings object FlixelTweenSettings. For property-based tweens, FlixelPropertyTween lets you supply getters and setters directly, so you can tween real Java properties with side effects instead of raw fields.
FlixelSprite player = new FlixelSprite()
.makeGraphic(16, 16, Color.WHITE);
// Tween the player's X and Y using getters/setters.
FlixelTween.tween(
new FlixelTweenSettings()
.setDuration(0.5f)
.setEase(FlixelEase::quadOut)
.addGoal(player::getX, 400f, player::setX)
.addGoal(player::getY, 200f, player::setY)
);This pattern is familiar to HaxeFlixel users (property-style tweens) while being very approachable for beginners:
- Beginners just pass method references (
player::getX,player::setX) and a target value. - Experienced users can configure easing, durations, delays, callbacks, and loop behavior through
FlixelTweenSettings, or drop down to lower-level types likeFlixelVarTweenif they want reflection-based tweens.
Compared to Universal Tween Engine's TweenAccessor pattern, you do not need to:
- Implement a custom accessor interface per type.
- Register accessors in a global registry.
- Manually map indices to fields.
Instead, you declare exactly what you want to tween with strongly typed getter/setter pairs. This keeps the API compact while still preserving all the niceties of having tweens as first-class objects.
In a typical FlixelGDX project, the global tween manager is automatically updated for you by the main FlixelGame loop. You can create tweens anywhere (usually in your FlixelState#create() or as a response to an input event):
public class PlayState extends FlixelState {
private FlixelSprite player;
@Override
public void create() {
player = new FlixelSprite()
.makeGraphic(16, 16, Color.WHITE);
add(player);
// Fade the player in over 1 second (alpha: 0 -> 1).
FlixelTween.tween(
new FlixelTweenSettings()
.setDuration(1.0f)
.addGoal(() -> player.getColor().a, 1.0f, player::setAlpha)
);
}
}Here addGoal uses the FlixelPropertyTween path under the hood, so your sprite's setters run on every step, which keeps collision bounds, listeners, or other side effects in sync with the animation.
If you already have an existing libGDX game that does not use FlixelGame or FlixelState, you can still use the tweening system by manually updating the global tween manager:
public class MyLibGdxScreen implements Screen {
private final FlixelSprite player = new FlixelSprite();
@Override
public void show() {
player.makeGraphic(16, 16, Color.WHITE);
FlixelTween.tween(
new FlixelTweenSettings()
.setDuration(0.75f)
.setEase(FlixelEase::sineInOut)
.addGoal(player::getScaleX, 2.0f, value -> player.setScale(value, value))
);
}
@Override
public void render(float delta) {
FlixelTween.getGlobalManager().update(delta); // advance all Flixel tweens
// ...your normal libGDX update & render code...
}
}This lets you adopt FlixelGDX's tweening in small pieces inside an existing libGDX codebase, while still giving HaxeFlixel-style ergonomics (tweens that understand your properties) and powerful configuration for experienced developers.
FlixelGDX wraps libGDX input behind a small set of helpers so you can check “was this key just pressed?” or “is it held?” without touching Gdx.input directly. That keeps gameplay code focused on intent (e.g. “jump when space is pressed”) and works the same on desktop, Android, and web.
How it Works
Flixel.keyPressed(int key)- Returns whether the key is currently held (same asGdx.input.isKeyPressed(key)). Use for movement or continuous actions.Flixel.keyJustPressed(int key)- Returns whether the key was pressed this frame (one-shot). Use for jump, shoot, menu confirm, etc., so the action doesn’t repeat every frame.FlixelKey- ExtendsInput.Keysand provides the same constants (e.g.FlixelKey.SPACE,FlixelKey.LEFT,FlixelKey.A). Pass these as thekeyargument so your code stays readable.
@Override
public void update(float delta) {
super.update(delta);
// One-shot: jump only when space is first pressed
if (Flixel.keyJustPressed(FlixelKey.SPACE)) {
player.jump();
}
// Held: move while arrow keys are down
if (Flixel.keyPressed(FlixelKey.LEFT)) player.velocityX = -100;
if (Flixel.keyPressed(FlixelKey.RIGHT)) player.velocityX = 100;
}Touch and mouse input still go through libGDX (Gdx.input); FlixelGDX’s helpers are for keyboard so you don’t have to remember the exact Gdx.input calls.
Flixel.keyPressed() and Flixel.keyJustPressed() delegate to Gdx.input, so they work in any libGDX app as long as Gdx.input is available (which it is once the application is running). You don’t need FlixelGame for these; just call them from your update loop. If you haven’t initialized Flixel at all, the static methods still work because they only use Gdx.input.
FlixelGDX provides a small logging API so you can tag messages, control how much detail is shown, and optionally attach stack traces - without scattering System.out.println or wiring a full logging framework up front.
How it Works
Flixel.info(message)/Flixel.warn(message)/Flixel.error(message)- Log with the default tag. Use for general state, warnings, and errors.Flixel.info(tag, message)/Flixel.warn(tag, message)/Flixel.error(tag, message)- Log with a custom tag (e.g."Player","Save") so you can filter or grep logs more easily.Flixel.error(tag, message, throwable)- Log an error with an exception; the logger can include the stack trace depending on mode.- Log mode. The default logger uses
FlixelLogMode.SIMPLE(compact, HaxeFlixel-style) orFlixelLogMode.DETAILED(timestamp, class, line, method). You can replace the global logger withFlixel.setLogger(yourLogger)or set a default tag withFlixel.setDefaultLogTag("MyGame").
Flixel.info("Player entered level 2");
Flixel.warn("Save", "Quota almost full");
Flixel.error("Network", "Connection failed", exception);This keeps logging consistent and readable. You can plug in your own FlixelLogger implementation (e.g. to your existing logging library) via Flixel.setLogger().
The static methods Flixel.info, Flixel.warn, and Flixel.error use a default logger that is created when Flixel is initialized (e.g. by FlixelGame). If you use FlixelGDX only as a library and never call Flixel.initialize(), you can still call these methods after setting your own logger with Flixel.setLogger(logger) so that all Flixel log calls go through your pipeline. Alternatively, use your existing logging in parallel; Flixel’s API is just a convenience and doesn’t replace libGDX or other logging.