If you’ve previously built an interactive game with p5.js and now want to port it to a Java-based desktop application, you might be wondering where to start. This article walks through the process of converting a complex p5.js planetary fleet battle simulation into a JavaFX version. We’ll discuss key differences between p5.js and JavaFX, show how to replace p5.js global functions with JavaFX application logic, and highlight various code translation techniques.

Prerequisites

Before we begin, ensure the following setup:

  • JDK Installed: Make sure you have a JDK installed and properly configured.
  • JavaFX SDK Installed: Download and integrate the JavaFX SDK. You’ll need to add JavaFX libraries (like javafx.controls, javafx.graphics) to your project’s classpath.
  • IntelliJ IDEA Community or other IDE: We’ll use IntelliJ IDEA Community as our reference environment. Configure your project to include the JavaFX SDK and set VM arguments such as:
    --module-path "path/to/javafx-sdk/lib" --add-modules=javafx.controls,javafx.fxml

Framework Conversion: p5.js vs. JavaFX

p5.js Approach:
In p5.js, you rely on global functions like setup(), draw(), mousePressed(), keyPressed(), etc. The environment calls these automatically. You also get a global createCanvas(...) function that sets up the drawing surface, and draw() is called repeatedly at a given frame rate.

JavaFX Approach:
In JavaFX, we don’t have a global drawing loop by default. Instead:

  • We start from the Application class’s start() method to create a Canvas and GraphicsContext.
  • We use an AnimationTimer to implement the equivalent of draw(). The timer’s handle(long now) method is called once per frame, allowing you to update and redraw your scene.
  • Event handlers on the Scene manage keyboard, mouse, and scroll events—replacing mousePressed(), keyPressed(), and mouseWheel() from p5.js.

For example, where p5.js uses createCanvas(1200, 900), JavaFX code might look like:

Canvas canvas = new Canvas(1200, 900);
GraphicsContext gc = canvas.getGraphicsContext2D();

Then, to simulate draw(), we set up:

new AnimationTimer() {
@Override
public void handle(long now) {
// The equivalent of draw()
// Update game state and call drawing methods here
}
}.start();

Mouse and keyboard events are added to the Scene:

scene.setOnMousePressed(e -> {
// Handle mouse click, equivalent to mousePressed()
});
scene.setOnKeyPressed(e -> {
// Handle keyboard input, equivalent to keyPressed()
});
scene.setOnScroll(e -> {
// Handle mouse wheel, equivalent to mouseWheel()
});

Translating Drawing Functions

p5.js provides drawing commands like ellipse(x, y, w, h) or text(txt, x, y) which are drawn with the center-based coordinate system. JavaFX, however, often uses top-left coordinates. Also, p5.js fill() and stroke() carry over drawing styles, while JavaFX GraphicsContext methods explicitly set fill and stroke before drawing.

  • ellipse(x, y, w, h) in p5.js is centered at (x, y). In JavaFX:

    gc.fillOval(x - w/2, y - h/2, w, h);
  • rect(x, y, w, h) in p5.js is also often center-based if you’ve set rectMode(CENTER), but by default it’s top-left. JavaFX similarly draws from top-left:

    gc.fillRect(x, y, w, h);
  • line(x1, y1, x2, y2) in JavaFX:

    gc.strokeLine(x1, y1, x2, y2);
  • text(txt, x, y): In p5.js, text() is centered or aligned based on textAlign(). In JavaFX, fillText() defaults to top-left. Adjust your coordinates or use gc.setTextAlign(TextAlignment.CENTER):

    gc.fillText(txt, x, y);
  • push()/pop() in p5.js helps save and restore drawing states (like transformations). JavaFX doesn’t have a direct equivalent. For complex transformations, you might manage your own transformations or simply recalculate coordinates without relying on stack-based transformations.

For complex shapes where p5.js uses beginShape(), vertex(), endShape(), you can store your points in an array and use gc.strokePolyline(...) or gc.fillPolygon(...) in JavaFX.

Data Structures and Architecture

The original p5.js code may have relied heavily on global variables and arrays. In Java, it’s cleaner and more maintainable to encapsulate data into classes and use ArrayList instead of raw arrays. This also helps when converting modal dialogs, buttons, and other components into nested classes.

For instance, where you previously wrote:

let ships = [];

you now have:

ArrayList<Ship> ships = new ArrayList<>();

and Ship becomes a dedicated class with attributes and methods.

We also might encapsulate GUI elements like Button or Modal into inner classes for cleanliness and organization.

Handling Events

p5.js global event functions like mousePressed() or keyPressed() are straightforward. In JavaFX, you set event listeners on the Scene or Canvas:

  • mousePressed() equivalent:

    scene.setOnMousePressed(e -> {
    double mx = e.getX();
    double my = e.getY();
    // handle mouse pressed logic
    });
  • keyPressed() equivalent:

    scene.setOnKeyPressed(e -> {
    KeyCode code = e.getCode();
    // handle key pressed logic
    });
  • mouseWheel() equivalent:

    scene.setOnScroll(e -> {
    double deltaY = e.getDeltaY();
    // handle mouse wheel logic
    });

For continuous key states (similar to keyIsDown() in p5.js), maintain a boolean array or a Set<KeyCode> in keyPressed and keyReleased events.

Mathematical Differences

  • random(1) in p5.js can be replaced by Math.random() in Java, which returns a double between 0 and 1.
  • radians() can be replaced by Math.toRadians().
  • p5.js constants like TWO_PI can be defined as double TWO_PI = 2 * Math.PI;.

For map() function, which p5.js uses to re-map a value from one range to another, implement a custom method:

double mapValue(double value, double start1, double stop1, double start2, double stop2) {
return start2 + (stop2 - start2) * ((value - start1) / (stop1 - start1));
}

Alerts and Cursor Changes

  • alert() in p5.js might just show a browser popup. In JavaFX, you can either use System.out.println() or create a simple Alert dialog.
  • p5.js cursor(HAND) can be translated to scene.setCursor(Cursor.HAND) in JavaFX.

If you previously used window.location.reload() in p5.js, you don’t have such a function in JavaFX. A suitable replacement might be System.exit(0) to terminate the application, or reinitialize your states to simulate a “restart.”

Final Thoughts

The key idea is to replace each p5.js global function and variable with a structured JavaFX approach. The draw() loop maps naturally to an AnimationTimer, setup() logic goes into start() or an initialization method, and event handling transitions from global functions to scene event listeners.

Where p5.js draws instantly and automatically, JavaFX requires you to call drawing methods on a GraphicsContext at each frame. State is maintained as class fields rather than global variables. Complex shapes and transitions require more manual math and array handling, but the result is a robust, object-oriented Java application.

You can check the final Java code provided above as an example. It takes the original p5.js game (which included planet rotation, fleet formation, equipment selection, and red point enemy attacks) and ports it into a JavaFX environment. Every function and global variable from p5.js now lives in a structured Java class, event handlers replicate the behavior of mousePressed() and keyPressed(), and drawing routines carefully translate ellipses, rectangles, lines, and text.

This approach gives you a powerful and flexible desktop version of your original web-based p5.js game, leveraging the strengths of Java’s strong typing, richer libraries, and performance.