Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
211 changes: 207 additions & 4 deletions COMPILING.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ FlixelGDX is a framework, not a standalone game, so it cannot be run by itself.
4. [Compiling FlixelGDX](#compiling-flixelgdx)
5. [Testing with a test project](#testing-with-a-test-project)
6. [How to test the framework properly](#how-to-test-the-framework-properly)
7. [Setting up the Android SDK (for contributing to the Android platform)](#setting-up-the-android-sdk-for-contributing-to-the-android-platform)
8. [Setting up an iOS development environment (for contributing to the iOS platform)](#setting-up-an-ios-development-environment-for-contributing-to-the-ios-platform)
9. [Troubleshooting](#troubleshooting)
7. [Web (TeaVM) setup and configuration](#web-teavm-setup-and-configuration)
8. [Setting up the Android SDK (for contributing to the Android platform)](#setting-up-the-android-sdk-for-contributing-to-the-android-platform)
9. [Setting up an iOS development environment (for contributing to the iOS platform)](#setting-up-an-ios-development-environment-for-contributing-to-the-ios-platform)
10. [Troubleshooting](#troubleshooting)

---

Expand Down Expand Up @@ -234,7 +235,7 @@ Work through the gdx-liftoff screens in order. For a **minimal** test project us
3. **Languages**
- Leave **Java** selected (FlixelGDX uses Java).
4. **Extensions**
- For a minimal project, leave all extensions **unchecked** (no Box2D, Ashley, FreeType, etc.).
- For a minimal project, leave all extensions **unchecked** (no Ashley, FreeType, etc.).
5. **Template**
- For quick testing, choose **Classic** or **ApplicationAdapter** — this generates a single main class that implements `ApplicationListener`. You will replace its logic with `FlixelGame` and states.
- Alternatively, **Game** gives you a `Game` plus `Screen` structure; you can still make the main class extend `FlixelGame` and use `FlixelState` instead of `Screen`.
Expand Down Expand Up @@ -435,6 +436,208 @@ Composite build lets the test project use your local FlixelGDX source so changes

---

## Web (TeaVM) setup and configuration

FlixelGDX supports web builds through the **TeaVM** backend (`flixelgdx-teavm`). TeaVM transpiles
Java bytecode into JavaScript so your game can run in a browser without plugins.

> [!IMPORTANT]
> **Do not use the TeaVM module generated by gdx-liftoff.** Liftoff generates a module using the
> old `backend-teavm` + `TeaVMBuilder.java` approach, which is incompatible with FlixelGDX's
> `backend-web` + `org.teavm` Gradle plugin approach. Delete `TeaVMBuilder.java` and replace the
> entire `build.gradle` with the template below.

### Recommended: use the FlixelGDX TeaVM Gradle plugin

`flixelgdx-teavm-plugin` is a companion Gradle plugin that automates the three steps that
otherwise require manual setup:

| Without plugin | With plugin |
|---|---|
| Write `src/main/webapp/index.html` by hand | Auto-generated with the correct canvas ID |
| Add a `copyAssets` Gradle task | Handled by `flixelCopyAssets` |
| Wire copy tasks to `generateJavaScript` | Handled automatically |

#### 1. Add plugin resolution to `settings.gradle`

The plugin is published to `mavenLocal` (dev) and JitPack (distribution), so add those
repositories to the `pluginManagement` block at the top of your root `settings.gradle`:

```gradle
pluginManagement {
repositories {
mavenLocal()
maven { url 'https://jitpack.io' }
gradlePluginPortal()
}
}
```

#### 2. Create the web module

Add a `teavm/` directory (or any name you prefer) to your project. A minimal
`teavm/build.gradle`:

```gradle
plugins {
id 'org.teavm' version '0.13.0'
id 'me.stringdotjar.flixelgdx.teavm' version '0.1.0-beta'
}

teavm {
all { mainClass = 'com.mygame.teavm.MyTeaVMLauncher' }
js {
addedToWebApp = true
targetFileName = 'teavm.js'
outputDir = file("$buildDir/dist/webapp")
}
}

dependencies {
implementation 'me.stringdotjar.flixelgdx:flixelgdx-teavm:0.1.0-beta'
implementation project(':core')
}
```

Include the module in your root `settings.gradle`:

```gradle
include 'core', 'lwjgl3', 'teavm'
```

#### 3. Create the launcher class

```java
public class MyTeaVMLauncher {
public static void main(String[] args) {
FlixelTeaVMLauncher.launch(new MyGame("My Game", 800, 600, new PlayState()));
}
}
```

That is the entire setup. The plugin automatically:

- Copies `<rootProject>/assets/` to `<outputDir>/assets/` before each build.
- Generates a default `index.html` (with the correct canvas ID and script path) if you do not
provide one in `src/main/webapp/`.
- Wires everything to `generateJavaScript` and `javaScriptDevServer`.

#### 4. Run the game in the browser

```bash
./gradlew :teavm:javaScriptDevServer
```

Open `http://localhost:8080` in a browser. Stop the server with
`./gradlew :teavm:stopJavaScriptDevServer`.

For a static build (e.g. to deploy):

```bash
./gradlew :teavm:generateJavaScript
```

The output in `teavm/build/dist/webapp/` is a self-contained folder you can serve with any
HTTP server.

### Optional plugin customization

Use the `flixelgdx {}` block to override defaults:

```gradle
flixelgdx {
// Canvas element ID (default: "flixelgdx-canvas").
// Must match WebApplicationConfiguration.canvasID in your launcher.
canvasId = 'my-canvas'

// Where the assembled web app goes (default: "$buildDir/dist/webapp").
// Must match teavm.js.outputDir.
outputDir = file("$buildDir/dist/webapp")

// Game assets to copy (default: rootProject/assets/).
assetsDir = file('../assets')

// User-provided web resources directory (default: src/main/webapp/).
// If this directory contains an index.html the auto-generation is skipped.
webappDir = file('src/main/webapp')

// Set to false to disable index.html auto-generation entirely (default: true).
generateDefaultIndexHtml = true
}
```

To provide a completely custom `index.html`, place it in `src/main/webapp/index.html`. The
plugin detects it and skips generation automatically. The canvas ID in your custom HTML must
match `FlixelTeaVMLauncher`'s default (`flixelgdx-canvas`) or the value you pass to
`WebApplicationConfiguration.canvasID`.

### Web configuration customization

The launcher accepts an optional `Consumer<WebApplicationConfiguration>` to override canvas ID,
dimensions, or other web-specific settings:

```java
FlixelTeaVMLauncher.launch(
new MyGame("My Game", 800, 600, new InitialState()),
FlixelRuntimeMode.RELEASE,
config -> {
config.canvasID = "my-canvas";
config.antialiasing = true;
}
);
```

### Reflection metadata

FlixelGDX's TeaVM build auto-generates a `teavm.json` reflection metadata file during the
`processResources` phase. This file preserves class/field/method information that TeaVM's
ahead-of-time compiler would otherwise strip.

The reflection profile is controlled by `flixelReflectionProfile` in `gradle.properties`:

| Profile | What is preserved |
|------------|-------------------|
| `SIMPLE` | FlixelGDX classes only. |
| `STANDARD` | FlixelGDX + libGDX classes (recommended). |
| `ALL` | FlixelGDX + libGDX + visible dependencies (anim8, miniaudio). |

To include your own game packages in the metadata, set `flixelReflectionExtraPackages` in
`gradle.properties`:

```properties
flixelReflectionExtraPackages=com.mygame,org.example.tools
```

### Platform limitations on web

The web backend intentionally omits several features that are unavailable in a browser
environment:

- **File logging** is disabled. There is no host filesystem, so `Flixel.startFileLogging()` is
a safe no-op. Console output (`System.out.println`) maps to `console.log` in the browser.
- **Jansi / ANSI colors** are not installed. Terminal color codes are irrelevant in a browser
console.
- **`FlixelVarTween`** relies on runtime reflection and may exhibit slower performance on
TeaVM. Prefer `FlixelPropertyTween` (getter/setter lambdas) for web-targeted games.
- **`FlixelGitUtil`** and other `ProcessBuilder`-based utilities are unavailable (no subprocess
support in browsers).
- **`FlixelDefaultAssetManager.extractAssetPath()`** uses `java.io.File` for temp file
extraction. On web, audio assets load through the browser's network stack and do not need
filesystem materialization.

### Validation checklist for web readiness

Before shipping a web build, verify the following:

1. The game boots and the initial state renders in the browser.
2. No `ClassNotFoundException` or `NoSuchMethodException` in the browser console (indicates
missing reflection metadata; widen the profile or add extra packages).
3. Tweens animate correctly (prefer `FlixelPropertyTween` over `FlixelVarTween`).
4. Audio plays in the browser (uses the Web Audio API via libGDX's backend).
5. Input (keyboard, mouse/touch) responds as expected.

---

## Setting up the Android SDK (for contributing to the Android platform)

If you want to contribute to the **flixelgdx-android** module or run and test FlixelGDX on Android (in this repo or in a test project), you need the Android SDK and a way to run an Android app (emulator or physical device). This section covers installation, configuration, and the limitations and workarounds you may hit depending on your OS.
Expand Down
22 changes: 18 additions & 4 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ configure(subprojects.findAll { p -> p.name == 'flixelgdx-core' || p.name == 'fl
// These modules are currently NOT configured in the root build.gradle for java-library
// but they apply it themselves.

configure(subprojects.findAll { p -> p.name != 'flixelgdx-core' && p.name != 'flixelgdx-test' }) {
configure(subprojects.findAll { p -> p.name != 'flixelgdx-core' && p.name != 'flixelgdx-test' && p.name != 'flixelgdx-teavm-plugin' }) {
afterEvaluate {
if (it.plugins.hasPlugin('java-library') || it.plugins.hasPlugin('com.android.library')) {
dependencies {
Expand All @@ -63,7 +63,7 @@ configure(subprojects.findAll { p -> p.name != 'flixelgdx-core' && p.name != 'fl
}
}

configure(subprojects.findAll { !it.path.contains('flixelgdx-android') && it.name != 'flixelgdx-test' }) {
configure(subprojects.findAll { !it.path.contains('flixelgdx-android') && it.name != 'flixelgdx-test' && it.name != 'flixelgdx-teavm-plugin' }) {
// From https://lyze.dev/2021/04/29/libGDX-Internal-Assets-List/
// The article can be helpful when using assets.txt in your project.
tasks.register('generateAssetList') {
Expand Down Expand Up @@ -93,7 +93,7 @@ configure(subprojects.findAll { !it.path.contains('flixelgdx-android') && it.nam
}
}

configure(subprojects.findAll { p -> p.name != 'flixelgdx-core' && p.name != 'flixelgdx-jvm' && p.name != 'flixelgdx-teavm' && p.name != 'flixelgdx-test' }) {
configure(subprojects.findAll { p -> p.name != 'flixelgdx-core' && p.name != 'flixelgdx-jvm' && p.name != 'flixelgdx-teavm' && p.name != 'flixelgdx-test' && p.name != 'flixelgdx-teavm-plugin' }) {
afterEvaluate {
if (it.plugins.hasPlugin('java-library') || it.plugins.hasPlugin('com.android.library')) {
dependencies {
Expand Down Expand Up @@ -170,7 +170,7 @@ subprojects { Project p ->
}
}

configure(subprojects.findAll { it.name != 'flixelgdx-test' }) {
configure(subprojects.findAll { it.name != 'flixelgdx-test' && it.name != 'flixelgdx-teavm-plugin' }) {
apply plugin: 'maven-publish'
afterEvaluate {
publishing {
Expand All @@ -189,6 +189,20 @@ configure(subprojects.findAll { it.name != 'flixelgdx-test' }) {
}
}

// The plugin module uses java-gradle-plugin which auto-creates the pluginMaven publication
// (including the plugin marker artifact). We apply maven-publish and configure the POM there
// separately to avoid a duplicate 'maven' publication.
configure(subprojects.findAll { it.name == 'flixelgdx-teavm-plugin' }) {
apply plugin: 'maven-publish'
afterEvaluate {
publishing {
publications.withType(MavenPublication).configureEach {
rootProject.configureFlixelMavenPom(delegate)
}
}
}
}

subprojects {
afterEvaluate { Project p ->
if (!p.plugins.hasPlugin('maven-publish')) {
Expand Down
4 changes: 0 additions & 4 deletions flixelgdx-android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,6 @@ dependencies {
api "games.rednblack.miniaudio:gdx-miniaudio-platform:$miniaudioVersion:natives-arm64-v8a"
api "games.rednblack.miniaudio:gdx-miniaudio-platform:$miniaudioVersion:natives-x86"
api "games.rednblack.miniaudio:gdx-miniaudio-platform:$miniaudioVersion:natives-x86_64"
api "com.badlogicgames.gdx:gdx-box2d-platform:$gdxVersion:natives-arm64-v8a"
api "com.badlogicgames.gdx:gdx-box2d-platform:$gdxVersion:natives-armeabi-v7a"
api "com.badlogicgames.gdx:gdx-box2d-platform:$gdxVersion:natives-x86"
api "com.badlogicgames.gdx:gdx-box2d-platform:$gdxVersion:natives-x86_64"
api "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-arm64-v8a"
api "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-armeabi-v7a"
api "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-x86"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
import me.stringdotjar.flixelgdx.Flixel;
import me.stringdotjar.flixelgdx.FlixelGame;
import me.stringdotjar.flixelgdx.backend.android.alert.FlixelAndroidAlerter;
import me.stringdotjar.flixelgdx.backend.jvm.audio.FlixelMiniAudioSoundHandler;
import me.stringdotjar.flixelgdx.backend.jvm.logging.FlixelDefaultStackTraceProvider;
import me.stringdotjar.flixelgdx.backend.jvm.logging.FlixelJvmLogFileHandler;
import me.stringdotjar.flixelgdx.backend.reflect.FlixelDefaultReflectionHandler;
import me.stringdotjar.flixelgdx.backend.runtime.FlixelRuntimeMode;

Expand Down Expand Up @@ -50,6 +52,8 @@ public static void launch(FlixelGame game, AndroidApplication activity, FlixelRu
Flixel.setAlerter(new FlixelAndroidAlerter(activity));
Flixel.setStackTraceProvider(new FlixelDefaultStackTraceProvider());
Flixel.setReflection(new FlixelDefaultReflectionHandler());
Flixel.setLogFileHandler(new FlixelJvmLogFileHandler());
Flixel.setSoundBackendFactory(new FlixelMiniAudioSoundHandler());
Flixel.setRuntimeMode(runtimeMode);
Flixel.setDebugMode(runtimeMode == FlixelRuntimeMode.DEBUG);
Flixel.initialize(game);
Expand Down
3 changes: 0 additions & 3 deletions flixelgdx-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,10 @@ configurations.configureEach {

dependencies {
api "com.badlogicgames.gdx:gdx:$gdxVersion"
api "com.badlogicgames.gdx:gdx-box2d:$gdxVersion"
api "com.badlogicgames.gdx:gdx-freetype:$gdxVersion"
api "com.github.tommyettinger:anim8-gdx:$anim8Version"
api "com.github.tommyettinger:libgdx-utils:$utilsVersion"
api "games.rednblack.miniaudio:miniaudio:$miniaudioVersion"

implementation "org.fusesource.jansi:jansi:$jansiVersion"
implementation "org.jetbrains:annotations:26.1.0"

if (enableGraalNative == 'true') {
Expand Down
Loading
Loading