diff --git a/jme3-core/src/main/java/com/jme3/app/SimpleApplication.java b/jme3-core/src/main/java/com/jme3/app/SimpleApplication.java index f1f21b58cc..e4c6b81b50 100644 --- a/jme3-core/src/main/java/com/jme3/app/SimpleApplication.java +++ b/jme3-core/src/main/java/com/jme3/app/SimpleApplication.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2022 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -49,22 +49,42 @@ import com.jme3.system.JmeContext.Type; import com.jme3.system.JmeSystem; +import java.util.logging.Level; +import java.util.logging.Logger; + /** - * SimpleApplication is the base class for all jME3 Applications. - * SimpleApplication will display a statistics view - * using the {@link com.jme3.app.StatsAppState} AppState. It will display - * the current frames-per-second value on-screen in addition to the statistics. - * Several keys have special functionality in SimpleApplication:
+ * `SimpleApplication` is the foundational base class for all jMonkeyEngine 3 (jME3) applications. + * It provides a streamlined setup for common game development tasks, including scene management, + * camera controls, and performance monitoring. + * + *

By default, `SimpleApplication` attaches several essential {@link com.jme3.app.state.AppState} instances: + *

* - * Esc - Close the application.
- * C - Display the camera position and rotation in the console.
- * M - Display memory usage in the console.
+ *

Default Key Bindings:

+ * * - * A {@link com.jme3.app.FlyCamAppState} is by default attached as well and can - * be removed by calling stateManager.detach(stateManager.getState(FlyCamAppState.class)); + *

Applications extending `SimpleApplication` should implement the + * {@link #simpleInitApp()} method to set up their initial scene and game logic. */ public abstract class SimpleApplication extends LegacyApplication { + protected static final Logger logger = Logger.getLogger(SimpleApplication.class.getName()); + public static final String INPUT_MAPPING_EXIT = "SIMPLEAPP_Exit"; public static final String INPUT_MAPPING_CAMERA_POS = DebugKeysAppState.INPUT_MAPPING_CAMERA_POS; public static final String INPUT_MAPPING_MEMORY = DebugKeysAppState.INPUT_MAPPING_MEMORY; @@ -81,26 +101,43 @@ public abstract class SimpleApplication extends LegacyApplication { private class AppActionListener implements ActionListener { @Override - public void onAction(String name, boolean value, float tpf) { - if (!value) { + public void onAction(String name, boolean isPressed, float tpf) { + if (!isPressed) { return; } if (name.equals(INPUT_MAPPING_EXIT)) { stop(); } else if (name.equals(INPUT_MAPPING_HIDE_STATS)) { - if (stateManager.getState(StatsAppState.class) != null) { - stateManager.getState(StatsAppState.class).toggleStats(); + StatsAppState statsState = stateManager.getState(StatsAppState.class); + if (statsState != null) { + statsState.toggleStats(); } } } } + /** + * Constructs a `SimpleApplication` with a predefined set of default + * {@link com.jme3.app.state.AppState} instances. + * These states provide common functionalities like statistics display, + * fly camera control, audio listener, debug keys, and constant verification. + */ public SimpleApplication() { - this(new StatsAppState(), new FlyCamAppState(), new AudioListenerState(), new DebugKeysAppState(), + this(new StatsAppState(), + new FlyCamAppState(), + new AudioListenerState(), + new DebugKeysAppState(), new ConstantVerifierState()); } + /** + * Constructs a `SimpleApplication` with a custom array of initial + * {@link com.jme3.app.state.AppState} instances. + * + * @param initialStates An array of `AppState` instances to be attached + * to the `stateManager` upon initialization. + */ public SimpleApplication(AppState... initialStates) { super(initialStates); } @@ -111,6 +148,7 @@ public void start() { // settings dialog is not shown boolean loadSettings = false; if (settings == null) { + logger.log(Level.INFO, "AppSettings not set, creating default settings."); setSettings(new AppSettings(true)); loadSettings = true; } @@ -127,57 +165,73 @@ public void start() { } /** - * Returns the application's speed. + * Returns the current speed multiplier of the application. + * This value affects how quickly the game world updates relative to real time. + * A value of 1.0f means normal speed, 0.5f means half speed, 2.0f means double speed. * - * @return The speed of the application. + * @return The current speed of the application. */ public float getSpeed() { return speed; } /** - * Changes the application's speed. 0.0f prevents the application from updating. - * @param speed The speed to set. + * Changes the application's speed multiplier. + * A `speed` of 0.0f effectively pauses the application's update cycle. + * + * @param speed The desired speed multiplier. A value of 1.0f is normal speed. + * Must be non-negative. */ public void setSpeed(float speed) { this.speed = speed; } /** - * Retrieves flyCam - * @return flyCam Camera object + * Retrieves the `FlyByCamera` instance associated with this application. + * This camera allows free-form navigation within the 3D scene. * + * @return The `FlyByCamera` object, or `null` if `FlyCamAppState` is not attached + * or has not yet initialized the camera. */ public FlyByCamera getFlyByCamera() { return flyCam; } /** - * Retrieves guiNode - * @return guiNode Node object + * Retrieves the `Node` dedicated to 2D graphical user interface (GUI) elements. + * Objects attached to this node are rendered on top of the 3D scene, + * typically without perspective effects, suitable for HUDs and UI. * + * @return The `Node` object representing the GUI root. */ public Node getGuiNode() { return guiNode; } /** - * Retrieves rootNode - * @return rootNode Node object + * Retrieves the root `Node` of the 3D scene graph. + * All main 3D spatial objects and models should be attached to this node + * to be part of the rendered scene. * + * @return The `Node` object representing the 3D scene root. */ public Node getRootNode() { return rootNode; } + /** + * Checks whether the settings dialog is configured to be shown at application startup. + * + * @return `true` if the settings dialog will be displayed, `false` otherwise. + */ public boolean isShowSettings() { return showSettings; } /** - * Toggles settings window to display at start-up - * @param showSettings Sets true/false + * Sets whether the jME3 settings dialog should be displayed before the application starts. * + * @param showSettings `true` to show the settings dialog, `false` to suppress it. */ public void setShowSettings(boolean showSettings) { this.showSettings = showSettings; @@ -197,46 +251,53 @@ protected BitmapFont loadGuiFont() { public void initialize() { super.initialize(); - // Several things rely on having this + // Load the default GUI font. This is essential for rendering text like FPS. guiFont = loadGuiFont(); guiNode.setQueueBucket(Bucket.Gui); guiNode.setCullHint(CullHint.Never); + viewPort.attachScene(rootNode); guiViewPort.attachScene(guiNode); if (inputManager != null) { - - // We have to special-case the FlyCamAppState because too - // many SimpleApplication subclasses expect it to exist in - // simpleInit(). But at least it only gets initialized if - // the app state is added. - if (stateManager.getState(FlyCamAppState.class) != null) { + // Special handling for FlyCamAppState: + // Although FlyCamAppState manages the FlyByCamera, SimpleApplication + // historically initializes and configures a default FlyByCamera instance + // and sets its initial speed. This allows subclasses to directly access + // 'flyCam' early in simpleInitApp(). + + FlyCamAppState flyCamState = stateManager.getState(FlyCamAppState.class); + if (flyCamState != null) { flyCam = new FlyByCamera(cam); - flyCam.setMoveSpeed(1f); // odd to set this here but it did it before - stateManager.getState(FlyCamAppState.class).setCamera(flyCam); + flyCam.setMoveSpeed(1f); // Set a default movement speed for the camera + flyCamState.setCamera(flyCam); // Link the FlyCamAppState to this camera instance } + // Register the "Exit" input mapping for the Escape key, but only for Display contexts. if (context.getType() == Type.Display) { inputManager.addMapping(INPUT_MAPPING_EXIT, new KeyTrigger(KeyInput.KEY_ESCAPE)); } - if (stateManager.getState(StatsAppState.class) != null) { + // Register the "Hide Stats" input mapping for the F5 key, if StatsAppState is active. + StatsAppState statsState = stateManager.getState(StatsAppState.class); + if (statsState != null) { inputManager.addMapping(INPUT_MAPPING_HIDE_STATS, new KeyTrigger(KeyInput.KEY_F5)); inputManager.addListener(actionListener, INPUT_MAPPING_HIDE_STATS); } + // Attach the action listener to the "Exit" mapping. inputManager.addListener(actionListener, INPUT_MAPPING_EXIT); } - if (stateManager.getState(StatsAppState.class) != null) { - // Some tests rely on having access to fpsText - // for quick display. Maybe a different way would be better. - stateManager.getState(StatsAppState.class).setFont(guiFont); - fpsText = stateManager.getState(StatsAppState.class).getFpsText(); + // Configure the StatsAppState if it exists. + StatsAppState statsState = stateManager.getState(StatsAppState.class); + if (statsState != null) { + statsState.setFont(guiFont); + fpsText = statsState.getFpsText(); } - // call user code + // Call the user's application initialization code. simpleInitApp(); } @@ -246,22 +307,26 @@ public void update() { prof.appStep(AppStep.BeginFrame); } - super.update(); // makes sure to execute AppTasks + // Executes AppTasks from the main thread + super.update(); + + // Skip updates if paused or speed is zero if (speed == 0 || paused) { return; } float tpf = timer.getTimePerFrame() * speed; - // update states + // Update AppStates if (prof != null) { prof.appStep(AppStep.StateManagerUpdate); } stateManager.update(tpf); - // simple update and root node + // Call user's per-frame update method simpleUpdate(tpf); + // Update scene graph nodes (logical and geometric states) if (prof != null) { prof.appStep(AppStep.SpatialUpdate); } @@ -271,7 +336,7 @@ public void update() { rootNode.updateGeometricState(); guiNode.updateGeometricState(); - // render states + // Render AppStates and the scene if (prof != null) { prof.appStep(AppStep.StateManagerRender); } @@ -281,6 +346,7 @@ public void update() { prof.appStep(AppStep.RenderFrame); } renderManager.render(tpf, context.isRenderable()); + // Call user's custom render method simpleRender(renderManager); stateManager.postRender(); @@ -289,23 +355,53 @@ public void update() { } } + /** + * Controls the visibility of the frames-per-second (FPS) display on the screen. + * + * @param show `true` to display the FPS, `false` to hide it. + */ public void setDisplayFps(boolean show) { - if (stateManager.getState(StatsAppState.class) != null) { - stateManager.getState(StatsAppState.class).setDisplayFps(show); + StatsAppState statsState = stateManager.getState(StatsAppState.class); + if (statsState != null) { + statsState.setDisplayFps(show); } } + /** + * Controls the visibility of the comprehensive statistics view on the screen. + * This view typically includes details about memory, triangles, and other performance metrics. + * + * @param show `true` to display the statistics view, `false` to hide it. + */ public void setDisplayStatView(boolean show) { - if (stateManager.getState(StatsAppState.class) != null) { - stateManager.getState(StatsAppState.class).setDisplayStatView(show); + StatsAppState statsState = stateManager.getState(StatsAppState.class); + if (statsState != null) { + statsState.setDisplayStatView(show); } } public abstract void simpleInitApp(); + /** + * An optional method that can be overridden by subclasses for per-frame update logic. + * This method is called during the application's update loop, after AppStates are updated + * and before the scene graph's logical state is updated. + * + * @param tpf The time per frame (in seconds), adjusted by the application's speed. + */ public void simpleUpdate(float tpf) { + // Default empty implementation; subclasses can override } + /** + * An optional method that can be overridden by subclasses for custom rendering logic. + * This method is called during the application's render loop, after the main scene + * has been rendered and before post-rendering for states. + * Useful for drawing overlays or specific rendering tasks outside the main scene graph. + * + * @param rm The `RenderManager` instance, which provides access to rendering functionalities. + */ public void simpleRender(RenderManager rm) { + // Default empty implementation; subclasses can override } }