Hardware FixRecommendedDevice not working? Your driver may be the problemCheck updates for common hardware issues.Fix DriversOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan Now×
Skip to content
VGSources
Blog

Creating a Simple 3D Platformer Game Using JavaFX

Build a playable desktop 3D platformer in JavaFX with a controllable player, platforms, gravity, jumping, collision detection, camera control, and respawning.
Length9 min Posted Quest giverVGSources Team
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Yes—you can build a small 3D platformer in Java using JavaFX. For a beginner-friendly desktop prototype, JavaFX is the most practical Java-native choice: it provides a 3D scene graph, cameras, lights, primitive shapes, materials, keyboard events, and an animation timer without requiring you to write an OpenGL renderer first.

This project uses colored boxes instead of imported models. By the end, you will have a controllable player, platforms, gravity, jumping, axis-aligned collision detection, a fixed-angle camera, respawning, and a simple goal.

What you will build

The finished prototype has one rectangular level containing three or more platforms. The player moves horizontally, falls under gravity, jumps only while grounded, and respawns when falling below the level. A goal platform can complete the level.

This is a learning project, not a replacement for a commercial game engine. It deliberately excludes imported assets, skeletal animation, networking, enemy AI, procedural generation, and complex physics.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Why use JavaFX?

JavaFX is a desktop application framework with 3D graphics features. It includes Scene, SubScene, PerspectiveCamera, Box, PhongMaterial, lights, keyboard events, and AnimationTimer. Its official documentation is available at Oracle’s JavaFX documentation.

JavaFX is separate from the JDK beginning with Java 11, so a current project must declare JavaFX dependencies. A practical baseline is Java 21 or Java 25 with the matching JavaFX 21 or JavaFX 25 release. Oracle’s JavaFX downloads page lists available releases and platform artifacts; verify the exact version before starting.

Option Best for Trade-off
JavaFX Small desktop prototypes and learning scene graphs Limited game-specific tooling and no complete physics system
LWJGL Direct OpenGL, GLFW, OpenAL, and custom engine work Much more setup and rendering code
libGDX Game-oriented Java development Requires a separately verified project setup and different APIs

LWJGL is a low-level library collection, not a full engine. Its official getting-started guide covers native-library configuration and notes the -XstartOnFirstThread option required by macOS applications.

Set up the Maven project

Install a JDK, not only a JRE. Use an IDE such as IntelliJ IDEA, Eclipse, or another Java editor, then create a Maven project. JavaFX dependencies must match both the JavaFX release and operating system.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The following is a non-modular Maven pattern. Replace 21.0.12 with the JavaFX version you have selected, and choose the classifier for your operating system: win, mac, or linux.

<properties>
    <maven.compiler.release>21</maven.compiler.release>
    <javafx.version>21.0.12</javafx.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.openjfx</groupId>
        <artifactId>javafx-controls</artifactId>
        <version>${javafx.version}</version>
        <classifier>win</classifier>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-maven-plugin</artifactId>
            <version>0.0.8</version>
            <configuration>
                <mainClass>com.example.platformer.GameApp</mainClass>
            </configuration>
        </plugin>
    </plugins>
</build>

For macOS or Linux, change the classifier. If your build reports missing JavaFX classes, reimport Maven, confirm the JDK and JavaFX versions, and check that you have not mixed different JavaFX major versions. IntelliJ’s JavaFX project guide also covers project creation and packaging.

Establish the 3D coordinate system

Use a consistent convention before writing movement code:

  • X: left and right.
  • Y: vertical movement; gravity makes Y velocity negative.
  • Z: depth. In this example, forward movement uses negative Z.

JavaFX boxes are positioned by their centers. A platform at y = 0 with height 1 therefore has a top surface at y = 0.5. The player’s feet are calculated from its center position and half its height.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Create the scene, camera, lights, and shapes

A typical JavaFX application uses a normal Scene containing a SubScene for the 3D world. Enable depth buffering so nearer objects correctly obscure farther ones.

public final class GameApp extends Application {
    @Override
    public void start(Stage stage) {
        Group world = new Group();

        PerspectiveCamera camera = new PerspectiveCamera(true);
        camera.setNearClip(0.1);
        camera.setFarClip(1000);
        camera.setTranslateX(12);
        camera.setTranslateY(-10);
        camera.setTranslateZ(-18);
        camera.setRotationAxis(Rotate.X_AXIS);
        camera.setRotate(-20);

        AmbientLight ambient = new AmbientLight(Color.color(0.7, 0.7, 0.7));
        PointLight light = new PointLight(Color.WHITE);
        light.setTranslateY(-10);
        light.setTranslateZ(-10);
        world.getChildren().addAll(ambient, light);

        Box platform = new Box(10, 1, 6);
        platform.setMaterial(new PhongMaterial(Color.DARKGREEN));
        platform.setTranslateY(1);
        world.getChildren().add(platform);

        SubScene subScene = new SubScene(world, 960, 540, true,
                SceneAntialiasing.BALANCED);
        subScene.setCamera(camera);

        StackPane root = new StackPane(subScene);
        Scene scene = new Scene(root, 960, 540, true);
        stage.setScene(scene);
        stage.setTitle("Java 3D Platformer");
        stage.show();
        root.requestFocus();
    }
}

If the window is blank, verify that the subscene is attached to the visible root, the camera is assigned, the objects are in front of it, the camera is not inside geometry, and at least one light illuminates the material.

Represent platforms and the player

Keep the first design small. Separate Player and Platform classes, but avoid creating an elaborate engine architecture.

public final class Platform {
    private final Box node;

    public Platform(double width, double height, double depth,
                    double x, double y, double z, Material material) {
        node = new Box(width, height, depth);
        node.setTranslateX(x);
        node.setTranslateY(y);
        node.setTranslateZ(z);
        node.setMaterial(material);
    }

    public Box node() {
        return node;
    }
}

public final class Player {
    private final Box node = new Box(1.0, 1.5, 1.0);
    private double velocityX;
    private double velocityY;
    private double velocityZ;
    private boolean grounded;

    public Player() {
        node.setMaterial(new PhongMaterial(Color.CORNFLOWERBLUE));
    }

    public Box node() { return node; }
    public double velocityX() { return velocityX; }
    public double velocityY() { return velocityY; }
    public double velocityZ() { return velocityZ; }
}

Store platforms in a List<Platform> and add each platform’s node to the world group. A helper such as createPlatform(width, height, depth, x, y, z) makes level construction readable.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Track keyboard input

Track keys as a set rather than checking only one key per frame. This permits diagonal movement and prevents a held key from being missed.

private final Set<KeyCode> keys =
        EnumSet.noneOf(KeyCode.class);

scene.setOnKeyPressed(event -> keys.add(event.getCode()));
scene.setOnKeyReleased(event -> keys.remove(event.getCode()));

boolean left = keys.contains(KeyCode.A) || keys.contains(KeyCode.LEFT);
boolean right = keys.contains(KeyCode.D) || keys.contains(KeyCode.RIGHT);
boolean forward = keys.contains(KeyCode.W) || keys.contains(KeyCode.UP);
boolean back = keys.contains(KeyCode.S) || keys.contains(KeyCode.DOWN);
Action Keys
Move left/right A/D or Left/Right
Move forward/back W/S or Up/Down
Jump Space
Reset R

If controls do nothing, click the game window, request focus on the root or scene host, check that another control is not consuming the event, and temporarily log key events.

Build a frame-rate-independent game loop

Use AnimationTimer and elapsed time. Movement measured in pixels per frame changes speed when the frame rate changes.

AnimationTimer timer = new AnimationTimer() {
    private long previousTime;

    @Override
    public void handle(long now) {
        if (previousTime == 0) {
            previousTime = now;
            return;
        }

        double deltaSeconds = (now - previousTime) / 1_000_000_000.0;
        deltaSeconds = Math.min(deltaSeconds, 0.05);
        previousTime = now;

        game.update(deltaSeconds);
    }
};
timer.start();

The clamp prevents a minimized or stalled window from producing one enormous physics step. A variable timestep is adequate for this small tutorial. More demanding games usually use a fixed physics timestep.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Run updates in this order:

  1. Read input.
  2. Set horizontal velocity.
  3. Apply gravity.
  4. Move and resolve collisions.
  5. Update the camera.
  6. Check respawn and goal conditions.

Add movement, gravity, and jumping

Use velocity rather than directly teleporting the player. These starter values are intentionally simple:

private static final double MOVE_SPEED = 5.0;
private static final double GRAVITY = -18.0;
private static final double JUMP_SPEED = 8.0;

velocityX = 0;
velocityZ = 0;
if (left)  velocityX -= MOVE_SPEED;
if (right) velocityX += MOVE_SPEED;
if (forward) velocityZ -= MOVE_SPEED;
if (back) velocityZ += MOVE_SPEED;

velocityY += GRAVITY * deltaSeconds;

Integrate the position with the timestep:

player.setTranslateX(player.getTranslateX()
        + velocityX * deltaSeconds);
player.setTranslateY(player.getTranslateY()
        + velocityY * deltaSeconds);
player.setTranslateZ(player.getTranslateZ()
        + velocityZ * deltaSeconds);

For jumping, maintain a grounded flag. Set it to false before vertical collision checks, then set it to true only after a downward landing:

if (jumpPressed && grounded) {
    velocityY = JUMP_SPEED;
    grounded = false;
}

Do not infer grounded status from any intersection. A player touching the side of a platform is not standing on it. Coyote time—allowing a jump for roughly 0.1 seconds after leaving a platform—is a useful later improvement, not a requirement for the first version.

Implement AABB collision detection

Because the player and platforms are unrotated boxes, axis-aligned bounding boxes are sufficient. Two boxes overlap when all three axes overlap:

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
boolean overlaps(Bounds a, Bounds b) {
    return a.getMinX() < b.getMaxX()
        && a.getMaxX() > b.getMinX()
        && a.getMinY() < b.getMaxY()
        && a.getMaxY() > b.getMinY()
        && a.getMinZ() < b.getMaxZ()
        && a.getMaxZ() > b.getMinZ();
}

Use world coordinates. JavaFX’s node.getBoundsInParent() is convenient when nodes are positioned directly under the world group.

Resolve vertical landings directionally

Save the player’s previous position before movement. A landing is valid only when the player was above a platform, is now crossing its top surface, and is moving downward.

double previousBottom = previousY - playerHeight / 2.0;
double currentBottom = player.getTranslateY() - playerHeight / 2.0;
double platformTop = platformY + platformHeight / 2.0;

boolean horizontalOverlap =
        playerMinX < platformMaxX && playerMaxX > platformMinX
        && playerMinZ < platformMaxZ && playerMaxZ > platformMinZ;

if (velocityY <= 0
        && previousBottom >= platformTop
        && currentBottom <= platformTop
        && horizontalOverlap) {
    player.setTranslateY(platformTop + playerHeight / 2.0);
    velocityY = 0;
    grounded = true;
}

This is more reliable than testing overlap alone, which can incorrectly treat side contact as a landing.

Resolve axes separately

For a sturdier prototype, do not move and resolve all three axes at once:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  1. Move on X and resolve X collisions.
  2. Move on Z and resolve Z collisions.
  3. Move on Y and resolve landings or underside hits.

Axis separation reduces wall sticking and makes it easier to determine whether a collision stopped horizontal movement or vertical movement. If the player hits a platform from below, place it just below the platform and set upward velocity to zero.

Thin platforms, high speeds, frame-time spikes, and corner contacts can still cause tunneling. Use reasonable platform thickness, modest speeds, the timestep clamp, and—if necessary—a maximum movement distance per frame or a fixed-step physics loop. This custom AABB system is a simple physics approximation, not a general-purpose physics engine.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Add a camera

Start with a fixed-angle camera. It requires little mathematics and keeps movement and collision debugging straightforward. Position it above and behind the level, aim it toward the level using a rotation or parent pivot, and keep the near and far clipping planes sensible.

A follow camera can later use an offset:

camera.setTranslateX(player.getTranslateX() + offsetX);
camera.setTranslateY(player.getTranslateY() + offsetY);
camera.setTranslateZ(player.getTranslateZ() + offsetZ);

Copying position alone does not make the camera look at the player. A proper follow camera also needs orientation, commonly implemented with a pivot node or a look-at rotation. Watch for cameras inside geometry, objects behind the camera, clipping through platforms, and players leaving the visible area.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Respawn and finish the level

Keep a spawn position and reset the player if it falls below the level:

private static final double FALL_LIMIT = 20.0;
private final Point3D spawn = new Point3D(0, -1, 0);

if (player.getTranslateY() < -FALL_LIMIT) {
    player.setTranslateX(spawn.getX());
    player.setTranslateY(spawn.getY());
    player.setTranslateZ(spawn.getZ());
    velocityX = velocityY = velocityZ = 0;
    grounded = false;
}

A goal can be another platform or an invisible trigger volume. When the player’s bounds overlap that goal, display a “Level complete” label, stop movement, or offer a reset with R. Add UI only after the movement loop works; a small text overlay is enough for a prototype.

A useful project structure

com.example.platformer
├── GameApp.java
├── GameWorld.java
├── Player.java
├── Platform.java
├── CollisionSystem.java
├── InputState.java
└── CameraController.java

For a very short prototype, GameWorld and CollisionSystem can be combined. Keep mutable physics state private or update it through methods rather than exposing every field publicly.

Improve the prototype in the right order

  1. Add a visible control overlay and a win message.
  2. Add more platforms and test different heights.
  3. Add textures or imported models only after primitive collisions work.
  4. Add moving platforms with carefully defined collision behavior.
  5. Add coyote time and jump buffering to improve controls.
  6. Add sound, particles, collectibles, and multiple levels.

Do not begin with assets, networking, procedural generation, or enemy AI. Primitive boxes make dimensions, transforms, bounds, and collision errors easy to see.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Run and distribute the application

A normal JAR does not automatically contain a complete JavaFX runtime. For local development, run through Maven or your IDE with the JavaFX dependencies configured. For distribution, create a platform-appropriate runtime image with tools such as jlink, or use a packaging workflow supported by your build system and IDE. Test Windows, macOS, and Linux separately because JavaFX native artifacts and packaging are platform-specific.

When JavaFX is no longer the right choice

Stay with JavaFX when the goal is a small desktop learning project, a scene-graph demonstration, or a primitive 3D prototype. Investigate LWJGL when you need low-level rendering and are prepared to build more engine infrastructure. Consider libGDX or a dedicated game engine when you need a mature asset pipeline, physics integration, audio systems, multiple targets, or production-scale tooling.

The important concepts transfer between all of these choices: define coordinates, update from elapsed time, model velocity, separate input from simulation, resolve collisions directionally, and keep rendering separate from game state.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More quests from Patch Notes

  1. How to Host a Mindustry Server in 2026: server-release.jar, Port 6567 and the Commands That MatterBlog7min
  2. Left 4 Dead 2 Server Hosting (2026): 5 Best Hosts for Co-op and VersusBlog8min
  3. How to Set Up a Subnautica Nitrox Server (2026): Launcher, Port 11000 and server.cfgBlog8min
Recommended PC Tool
Recommended PC Tool
Crashes, No Sound, or Screen Glitches?Free driver scan
Windows Errors? Fix Them Before They SpreadFree repair scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.