From f1b47b2bfe9d61a9cfbc624a83df61879b796f67 Mon Sep 17 00:00:00 2001 From: String Date: Mon, 12 Jan 2026 02:45:40 -0600 Subject: [PATCH 01/17] Completely rework screens and fix a bug with tweens --- assets/another_test.groovy | 5 +- assets/test.groovy | 10 +- .../java/me/stringfromjava/funkin/Funkin.java | 129 ++++++++++-------- .../me/stringfromjava/funkin/FunkinGame.java | 110 +++++++++++++-- .../funkin/audio/FunkinSound.java | 11 +- .../funkin/backend/FunkinReflect.java | 53 +++++++ .../funkin/backend/{system => }/Paths.java | 2 +- .../funkin/backend/Reflect.java | 18 --- .../funkin/backend/display/FunkinScreen.java | 100 -------------- .../funkin/game/InitScreen.java | 2 +- .../funkin/game/menus/TitleScreen.java | 18 +-- .../funkin/graphics/screen/FunkinScreen.java | 58 ++++++++ .../funkin/graphics/sprite/FunkinObject.java | 7 + .../funkin/graphics/sprite/FunkinSprite.java | 14 ++ .../text/FunkinText.java} | 6 +- .../funkin/polyverse/Polyverse.java | 6 +- .../funkin/tween/FunkinTween.java | 7 +- .../stringfromjava/funkin/util/Constants.java | 24 +++- .../system => util/signal}/FunkinSignal.java | 12 +- .../funkin/util/signal/FunkinSignalData.java | 21 +++ gradle.properties | 1 - 21 files changed, 395 insertions(+), 219 deletions(-) create mode 100644 core/src/main/java/me/stringfromjava/funkin/backend/FunkinReflect.java rename core/src/main/java/me/stringfromjava/funkin/backend/{system => }/Paths.java (90%) delete mode 100644 core/src/main/java/me/stringfromjava/funkin/backend/Reflect.java delete mode 100644 core/src/main/java/me/stringfromjava/funkin/backend/display/FunkinScreen.java create mode 100644 core/src/main/java/me/stringfromjava/funkin/graphics/screen/FunkinScreen.java create mode 100644 core/src/main/java/me/stringfromjava/funkin/graphics/sprite/FunkinObject.java create mode 100644 core/src/main/java/me/stringfromjava/funkin/graphics/sprite/FunkinSprite.java rename core/src/main/java/me/stringfromjava/funkin/{backend/display/text/DisplayText.java => graphics/text/FunkinText.java} (77%) rename core/src/main/java/me/stringfromjava/funkin/{backend/system => util/signal}/FunkinSignal.java (87%) create mode 100644 core/src/main/java/me/stringfromjava/funkin/util/signal/FunkinSignalData.java diff --git a/assets/another_test.groovy b/assets/another_test.groovy index b160cbc..0051f55 100644 --- a/assets/another_test.groovy +++ b/assets/another_test.groovy @@ -3,7 +3,8 @@ import com.badlogic.gdx.Input import com.badlogic.gdx.graphics.Texture import com.badlogic.gdx.graphics.g2d.Sprite import me.stringfromjava.funkin.Funkin -import me.stringfromjava.funkin.backend.system.Paths +import me.stringfromjava.funkin.backend.Paths +import me.stringfromjava.funkin.graphics.sprite.FunkinSprite import me.stringfromjava.funkin.polyverse.script.type.Script import me.stringfromjava.funkin.util.Constants @@ -27,7 +28,7 @@ class AnotherTestClass extends Script { super.onRender(delta) if (Gdx.input.isKeyJustPressed(Input.Keys.SPACE)) { - var sprite = new Sprite(new Texture(Paths.image("pauseAlt/bfLol"))) + var sprite = new FunkinSprite(new Texture(Paths.image("pauseAlt/bfLol"))) var randomPosX = new Random().nextInt(Constants.Display.WINDOW_WIDTH) var randomPosY = new Random().nextInt(Constants.Display.WINDOW_HEIGHT) diff --git a/assets/test.groovy b/assets/test.groovy index a3adf17..35af7c6 100644 --- a/assets/test.groovy +++ b/assets/test.groovy @@ -2,10 +2,10 @@ import com.badlogic.gdx.Gdx import com.badlogic.gdx.Input import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.Texture -import com.badlogic.gdx.graphics.g2d.Sprite import me.stringfromjava.funkin.Funkin -import me.stringfromjava.funkin.backend.display.FunkinScreen -import me.stringfromjava.funkin.backend.system.Paths +import me.stringfromjava.funkin.graphics.screen.FunkinScreen +import me.stringfromjava.funkin.backend.Paths +import me.stringfromjava.funkin.graphics.sprite.FunkinSprite import me.stringfromjava.funkin.polyverse.script.type.SystemScript class TestScript extends SystemScript { @@ -38,13 +38,13 @@ class TestScript extends SystemScript { class TestScreen extends FunkinScreen { - private Sprite test + private FunkinSprite test @Override void show() { super.show() - test = new Sprite(new Texture(Paths.image('NOTE_hold_assets'))) + test = new FunkinSprite(new Texture(Paths.image('NOTE_hold_assets'))) add(test) bgColor = new Color(0, 1, 0, 1) diff --git a/core/src/main/java/me/stringfromjava/funkin/Funkin.java b/core/src/main/java/me/stringfromjava/funkin/Funkin.java index 8290389..7213040 100644 --- a/core/src/main/java/me/stringfromjava/funkin/Funkin.java +++ b/core/src/main/java/me/stringfromjava/funkin/Funkin.java @@ -3,11 +3,13 @@ import com.badlogic.gdx.Gdx; import com.badlogic.gdx.audio.Music; import com.badlogic.gdx.audio.Sound; +import com.badlogic.gdx.scenes.scene2d.Stage; import me.stringfromjava.funkin.audio.FunkinSound; -import me.stringfromjava.funkin.backend.display.FunkinScreen; -import me.stringfromjava.funkin.backend.system.FunkinSignal; -import me.stringfromjava.funkin.backend.system.Paths; +import me.stringfromjava.funkin.graphics.screen.FunkinScreen; +import me.stringfromjava.funkin.util.signal.FunkinSignal; +import me.stringfromjava.funkin.backend.Paths; import me.stringfromjava.funkin.util.Constants; +import me.stringfromjava.funkin.util.signal.FunkinSignalData.*; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @@ -74,23 +76,23 @@ public static void initialize(FunkinGame gameInstance) { * Sets the current screen to the provided screen. This is just a more direct version of {@code * Funkin.game.setScreen(screen)} with some extra functionality put into it. * - * @param screen The new {@code FunkinScreen} to set as the current screen. + * @param newScreen The new {@code FunkinScreen} to set as the current screen. */ - public static void setScreen(FunkinScreen screen) { - Signals.preScreenSwitch.dispatch(new Signals.ScreenSwitchSignalData(screen)); + public static void setScreen(FunkinScreen newScreen) { + Signals.preScreenSwitch.dispatch(new ScreenSwitchSignalData(newScreen)); if (!initialized) { throw new IllegalStateException("FNF:JE has not been initialized yet!"); } - if (screen == null) { + if (newScreen == null) { throw new IllegalArgumentException("Screen cannot be null!"); } if (Funkin.screen != null) { Funkin.screen.hide(); Funkin.screen.dispose(); } - Funkin.screen = screen; - game.setScreen(screen); - Signals.postScreenSwitch.dispatch(new Signals.ScreenSwitchSignalData(screen)); + Funkin.screen = newScreen; + Funkin.screen.show(); + Signals.postScreenSwitch.dispatch(new ScreenSwitchSignalData(newScreen)); } /** @@ -150,7 +152,7 @@ public static Music playMusic(String path, float volume, boolean looped) { } public static void info(Object message) { - info("Funkin", message); + info(Constants.System.LOG_TAG, message); } public static void info(String tag, Object message) { @@ -158,7 +160,7 @@ public static void info(String tag, Object message) { } public static void warn(Object message) { - warn("Funkin", message); + warn(Constants.System.LOG_TAG, message); } public static void warn(String tag, Object message) { @@ -166,55 +168,27 @@ public static void warn(String tag, Object message) { } public static void error(String message) { - error("Funkin", message); + error(Constants.System.LOG_TAG, message, null); } public static void error(String tag, Object message) { - outputLog(tag, message, FunkinLogLevel.ERROR); + error(tag, message, null); } - public static FunkinGame getGame() { - return game; + public static void error(String tag, Object message, Throwable throwable) { + String msg = + (throwable != null) + ? (message + " | Exception: " + throwable.toString()) + : message.toString(); + outputLog(tag, msg, FunkinLogLevel.ERROR); } - // ====================================== - // UTILITY FUNCTIONS, IGNORE BELOW - // ====================================== - - private static void outputLog(String tag, Object message, FunkinLogLevel level) { - String color = switch (level) { - case INFO -> Constants.Colors.WHITE; - case WARN -> Constants.Colors.YELLOW; - case ERROR -> Constants.Colors.RED; - }; - - boolean underline = (level == FunkinLogLevel.ERROR); - String timeAndDate = colorText( - LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + " ", - color, true, false, underline); - String formattedTag = colorText("[" + tag + "] [" + level + "] ", color, true, false, underline); - String formattedMessage = colorText(message.toString(), color, false, true, underline); - - System.out.println(timeAndDate + formattedTag + formattedMessage); + public static FunkinGame getGame() { + return game; } - - private static String colorText( - String text, String color, boolean bold, boolean italic, boolean underline) { - StringBuilder sb = new StringBuilder(); - if (bold) { - sb.append(Constants.Colors.BOLD); - } - if (italic) { - sb.append(Constants.Colors.ITALIC); - } - if (underline) { - sb.append(Constants.Colors.UNDERLINE); - } - sb.append(color); - sb.append(text); - sb.append(Constants.Colors.RESET); - return sb.toString(); + public static Stage getStage() { + return game.stage; } /** @@ -236,16 +210,59 @@ public static class Signals { new FunkinSignal<>(); public static final FunkinSignal preGameClose = new FunkinSignal<>(); public static final FunkinSignal postGameClose = new FunkinSignal<>(); + public static final FunkinSignal windowFocused = new FunkinSignal<>(); + public static final FunkinSignal windowUnfocused = new FunkinSignal<>(); + public static final FunkinSignal windowMinimized = + new FunkinSignal<>(); public static final FunkinSignal preSoundPlayed = new FunkinSignal<>(); public static final FunkinSignal postSoundPlayed = new FunkinSignal<>(); - public record RenderSignalData(float delta) {} + private Signals() {} + } - public record ScreenSwitchSignalData(FunkinScreen screen) {} + // ====================================== + // UTILITY FUNCTIONS, IGNORE BELOW + // ====================================== - public record SoundPlayedSignalData(FunkinSound sound) {} + private static void outputLog(String tag, Object message, FunkinLogLevel level) { + String color = + switch (level) { + case INFO -> Constants.AsciiCodes.WHITE; + case WARN -> Constants.AsciiCodes.YELLOW; + case ERROR -> Constants.AsciiCodes.RED; + }; - private Signals() {} + boolean underline = (level == FunkinLogLevel.ERROR); + String timeAndDate = + colorText( + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + " ", + color, + true, + false, + underline); + String formattedTag = + colorText("[" + tag + "] [" + level + "] ", color, true, false, underline); + String formattedMessage = colorText(message.toString(), color, false, true, underline); + + System.out.println(timeAndDate + formattedTag + formattedMessage); + } + + private static String colorText( + String text, String color, boolean bold, boolean italic, boolean underline) { + StringBuilder sb = new StringBuilder(); + if (bold) { + sb.append(Constants.AsciiCodes.BOLD); + } + if (italic) { + sb.append(Constants.AsciiCodes.ITALIC); + } + if (underline) { + sb.append(Constants.AsciiCodes.UNDERLINE); + } + sb.append(color); + sb.append(text); + sb.append(Constants.AsciiCodes.RESET); + return sb.toString(); } private Funkin() {} diff --git a/core/src/main/java/me/stringfromjava/funkin/FunkinGame.java b/core/src/main/java/me/stringfromjava/funkin/FunkinGame.java index 0cac92f..481fd26 100644 --- a/core/src/main/java/me/stringfromjava/funkin/FunkinGame.java +++ b/core/src/main/java/me/stringfromjava/funkin/FunkinGame.java @@ -1,54 +1,135 @@ package me.stringfromjava.funkin; -import com.badlogic.gdx.Game; +import com.badlogic.gdx.ApplicationListener; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.audio.Sound; -import me.stringfromjava.funkin.backend.system.Paths; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.OrthographicCamera; +import com.badlogic.gdx.graphics.Pixmap; +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.scenes.scene2d.Stage; +import com.badlogic.gdx.utils.ScreenUtils; +import com.badlogic.gdx.utils.viewport.FitViewport; +import me.stringfromjava.funkin.graphics.screen.FunkinScreen; +import me.stringfromjava.funkin.backend.Paths; import me.stringfromjava.funkin.game.InitScreen; +import me.stringfromjava.funkin.graphics.sprite.FunkinObject; +import me.stringfromjava.funkin.graphics.sprite.FunkinSprite; import me.stringfromjava.funkin.polyverse.Polyverse; import me.stringfromjava.funkin.polyverse.script.type.Script; import me.stringfromjava.funkin.polyverse.script.type.SystemScript; import me.stringfromjava.funkin.tween.FunkinTween; +import me.stringfromjava.funkin.util.Constants; import java.util.Set; -import static me.stringfromjava.funkin.Funkin.Signals.RenderSignalData; +import me.stringfromjava.funkin.util.signal.FunkinSignalData; /** - * An enhanced version of libGDX's {@link Game} object. + * The game object used for containing the main loop and core elements of Funkin'. * *

If you want to change what happens to the pre and window configurations, you might want to see * {@code Lwjgl3Launcher} in the {@code lwjgl3} folder. */ -public class FunkinGame extends Game { +public class FunkinGame implements ApplicationListener { /** Is the game's window currently minimized? */ protected boolean isMinimized = false; + /** The main stage used for rendering all screens and sprites on screen. */ + protected Stage stage; + + /** The main viewport used to fit the world no matter the screen size. */ + protected FitViewport viewport; + + /** The main camera used to see the world. */ + protected OrthographicCamera camera; + + /** The main sprite batch used for rendering all sprites on screen. */ + protected SpriteBatch batch; + + /** The 1x1 texture used to draw the background color of the current screen. */ + protected Texture bgTexture; + @Override public void create() { + // Configure the main view of the game's world and view. + var wWidth = Constants.Display.WINDOW_WIDTH; + var wHeight = Constants.Display.WINDOW_HEIGHT; + + batch = new SpriteBatch(); + viewport = new FitViewport(wWidth, wHeight); + viewport.apply(); + + camera = new OrthographicCamera(); + camera.setToOrtho(false, wWidth, wHeight); + + stage = new Stage(viewport, batch); + + Pixmap pixmap = new Pixmap(1, 1, Pixmap.Format.RGBA8888); + pixmap.setColor(Color.WHITE); + pixmap.fill(); + bgTexture = new Texture(pixmap); + pixmap.dispose(); + + // Configure the Polyverse scripting and modding engine for the game. configurePolyverse(); - setScreen(new InitScreen()); + Funkin.setScreen(new InitScreen()); + } + + @Override + public void resize(int width, int height) { + viewport.update(width, height, true); } @Override public void render() { - super.render(); float delta = Gdx.graphics.getDeltaTime(); + FunkinScreen screen = Funkin.screen; + + Funkin.Signals.preRender.dispatch(new FunkinSignalData.RenderSignalData(delta)); + + // Update and render the current screen that's active. + if (screen != null) { + ScreenUtils.clear(Color.BLACK); + viewport.apply(); + batch.setProjectionMatrix(camera.combined); + batch.begin(); + batch.setColor(screen.getBgColor()); + batch.draw(bgTexture, 0, 0, viewport.getWorldWidth(), viewport.getWorldHeight()); + batch.setColor(Color.WHITE); // Set color back to white so display objects aren't affected. + screen.render(delta); + for (FunkinObject object : screen.members) { + if (object instanceof FunkinSprite) { + ((FunkinSprite) object).draw(batch); + } + } + } + batch.end(); - Funkin.Signals.preRender.dispatch(new RenderSignalData(delta)); + // Update the current screen and stage. + stage.act(delta); + stage.draw(); FunkinTween.globalManager.update(delta); Polyverse.forAllScripts(script -> script.onRender(delta)); - Funkin.Signals.postRender.dispatch(new RenderSignalData(delta)); + Funkin.Signals.postRender.dispatch(new FunkinSignalData.RenderSignalData(delta)); } + @Override + public void pause() {} + + @Override + public void resume() {} + /** Called when the user regains focus on the game's window. */ public void onWindowFocused() { Funkin.masterVolume = 1.0f; Funkin.music.setVolume(1); + Funkin.Signals.windowFocused.dispatch(); Funkin.info("Game window has regained focus."); } @@ -59,6 +140,8 @@ public void onWindowUnfocused() { } Funkin.masterVolume = 0.008f; Funkin.music.setVolume(0.008f); + Funkin.Signals.windowMinimized.dispatch( + new FunkinSignalData.WindowMinimizedSignalData(isMinimized)); Funkin.info("Game window has lost focus."); } @@ -75,6 +158,7 @@ public void onWindowMinimized(boolean iconified) { } Funkin.masterVolume = 0.0f; Funkin.music.setVolume(0); + Funkin.Signals.windowMinimized.dispatch(); Funkin.info("Game window has been minimized."); } @@ -84,6 +168,14 @@ public void dispose() { Funkin.Signals.preGameClose.dispatch(); + // Dispose of the main stage, current screen and batch. + Funkin.info("Disposing the screen display..."); + Funkin.screen.hide(); + Funkin.screen.dispose(); + stage.dispose(); + batch.dispose(); + bgTexture.dispose(); + // Dispose of all sounds and the music (if there is any playing). Funkin.info("Disposing music..."); if (Funkin.music != null) { diff --git a/core/src/main/java/me/stringfromjava/funkin/audio/FunkinSound.java b/core/src/main/java/me/stringfromjava/funkin/audio/FunkinSound.java index 77c199e..ee9c9c4 100644 --- a/core/src/main/java/me/stringfromjava/funkin/audio/FunkinSound.java +++ b/core/src/main/java/me/stringfromjava/funkin/audio/FunkinSound.java @@ -4,7 +4,8 @@ import com.badlogic.gdx.audio.Sound; import com.badlogic.gdx.files.FileHandle; import me.stringfromjava.funkin.Funkin; -import me.stringfromjava.funkin.backend.system.Paths; +import me.stringfromjava.funkin.backend.Paths; +import me.stringfromjava.funkin.util.signal.FunkinSignalData; /** * An enhanced version of libGDX's {@link Sound}. @@ -56,13 +57,13 @@ public long play(float volume, float pitch) { @Override public long play(float volume, float pitch, float pan) { - Funkin.Signals.preSoundPlayed.dispatch(new Funkin.Signals.SoundPlayedSignalData(this)); + Funkin.Signals.preSoundPlayed.dispatch(new FunkinSignalData.SoundPlayedSignalData(this)); this.volume = volume * Funkin.masterVolume; this.pitch = pitch; this.pan = pan; this.looping = false; this.isPaused = false; - Funkin.Signals.postSoundPlayed.dispatch(new Funkin.Signals.SoundPlayedSignalData(this)); + Funkin.Signals.postSoundPlayed.dispatch(new FunkinSignalData.SoundPlayedSignalData(this)); return thisSound.play(volume, pitch, pan); } @@ -82,13 +83,13 @@ public long loop(float volume, float pitch) { @Override public long loop(float volume, float pitch, float pan) { - Funkin.Signals.preSoundPlayed.dispatch(new Funkin.Signals.SoundPlayedSignalData(this)); + Funkin.Signals.preSoundPlayed.dispatch(new FunkinSignalData.SoundPlayedSignalData(this)); this.volume = volume * Funkin.masterVolume; this.pitch = pitch; this.pan = pan; this.looping = true; this.isPaused = false; - Funkin.Signals.postSoundPlayed.dispatch(new Funkin.Signals.SoundPlayedSignalData(this)); + Funkin.Signals.postSoundPlayed.dispatch(new FunkinSignalData.SoundPlayedSignalData(this)); return thisSound.loop(volume, pitch, pan); } diff --git a/core/src/main/java/me/stringfromjava/funkin/backend/FunkinReflect.java b/core/src/main/java/me/stringfromjava/funkin/backend/FunkinReflect.java new file mode 100644 index 0000000..da13cfe --- /dev/null +++ b/core/src/main/java/me/stringfromjava/funkin/backend/FunkinReflect.java @@ -0,0 +1,53 @@ +package me.stringfromjava.funkin.backend; + +import me.stringfromjava.funkin.Funkin; +import me.stringfromjava.funkin.util.Constants; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Backend utility class for obtaining and manipulating fields on objects through the usage of Java + * reflection. + */ +public class FunkinReflect { + + /** + * Obtains all fields of a class, including the master types above it all the way to {@link + * Object}. + * + * @param type A class literal to obtain the fields from. + * @return All fields from itself and its master classes above it. + */ + public static List getAllFields(Class type) { + List fields = new ArrayList<>(); + for (Class c = type; c != null; c = c.getSuperclass()) { + fields.addAll(Arrays.asList(c.getDeclaredFields())); + } + return fields; + } + + /** + * Checks if a class of a certain package is final. + * + * @param classPath The package definition of the class to check if final. An example could be + * {@code "me.stringfromjava.funkin.Funkin"}. + * @return If the class provided is final. If there was an exception caught, then {@code false} is + * automatically returned. + */ + public static boolean isClassFinal(String classPath) { + try { + Class clazz = Class.forName(classPath); + // Uses the java.lang.reflect.Modifier utility. + return Modifier.isFinal(clazz.getModifiers()); + } catch (ClassNotFoundException e) { + // Treat non-existent class as non-final for safe binding, + // though the user will hit an error later. + Funkin.error(Constants.System.LOG_TAG, "Failed to check if a class was final.", e); + return false; + } + } +} diff --git a/core/src/main/java/me/stringfromjava/funkin/backend/system/Paths.java b/core/src/main/java/me/stringfromjava/funkin/backend/Paths.java similarity index 90% rename from core/src/main/java/me/stringfromjava/funkin/backend/system/Paths.java rename to core/src/main/java/me/stringfromjava/funkin/backend/Paths.java index 6a1f874..9e72089 100644 --- a/core/src/main/java/me/stringfromjava/funkin/backend/system/Paths.java +++ b/core/src/main/java/me/stringfromjava/funkin/backend/Paths.java @@ -1,4 +1,4 @@ -package me.stringfromjava.funkin.backend.system; +package me.stringfromjava.funkin.backend; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.files.FileHandle; diff --git a/core/src/main/java/me/stringfromjava/funkin/backend/Reflect.java b/core/src/main/java/me/stringfromjava/funkin/backend/Reflect.java deleted file mode 100644 index cd4ff4f..0000000 --- a/core/src/main/java/me/stringfromjava/funkin/backend/Reflect.java +++ /dev/null @@ -1,18 +0,0 @@ -package me.stringfromjava.funkin.backend; - -import java.lang.reflect.Modifier; - -public class Reflect { - - public static boolean isClassFinal(String classPath) { - try { - Class clazz = Class.forName(classPath); - // Uses the java.lang.reflect.Modifier utility - return Modifier.isFinal(clazz.getModifiers()); - } catch (ClassNotFoundException e) { - // Treat non-existent class as non-final for safe binding, - // though the user will hit an error later. - return false; - } - } -} diff --git a/core/src/main/java/me/stringfromjava/funkin/backend/display/FunkinScreen.java b/core/src/main/java/me/stringfromjava/funkin/backend/display/FunkinScreen.java deleted file mode 100644 index 3204807..0000000 --- a/core/src/main/java/me/stringfromjava/funkin/backend/display/FunkinScreen.java +++ /dev/null @@ -1,100 +0,0 @@ -package me.stringfromjava.funkin.backend.display; - -import com.badlogic.gdx.Screen; -import com.badlogic.gdx.graphics.Color; -import com.badlogic.gdx.graphics.OrthographicCamera; -import com.badlogic.gdx.graphics.Texture; -import com.badlogic.gdx.graphics.g2d.Sprite; -import com.badlogic.gdx.graphics.g2d.SpriteBatch; -import com.badlogic.gdx.utils.ScreenUtils; -import com.badlogic.gdx.utils.viewport.FitViewport; -import com.badlogic.gdx.utils.viewport.Viewport; -import me.stringfromjava.funkin.util.Constants; - -import java.util.ArrayList; - -/** - * Base class for creating a better screen display with more functionality than the default {@link - * com.badlogic.gdx.Screen} interface. - */ -public abstract class FunkinScreen implements Screen { - - // TODO: Create a way to add more than one camera! - /** The {@link OrthographicCamera} used to see the world. */ - public OrthographicCamera camera; - - /** The current {@link Viewport} of {@code this} current screen. */ - public Viewport viewport; - - /** The background color of {@code this} current screen. */ - public Color bgColor; - - /** All display objects that are shown in {@code this} screen. */ - public final ArrayList members = new ArrayList<>(); - - /** The {@link SpriteBatch} used to render sprites in the current screen. */ - protected SpriteBatch spriteBatch; - - @Override - public void show() { - spriteBatch = new SpriteBatch(); - camera = new OrthographicCamera(); - camera.setToOrtho(false, Constants.Display.WINDOW_WIDTH, Constants.Display.WINDOW_HEIGHT); - viewport = new FitViewport(Constants.Display.WINDOW_WIDTH, Constants.Display.WINDOW_HEIGHT, camera); - viewport.apply(); - bgColor = new Color(0, 0, 0, 1); - } - - @Override - public void render(float delta) { - // Refresh the screen display. - ScreenUtils.clear(bgColor); - - camera.update(); - spriteBatch.setProjectionMatrix(camera.combined); - - spriteBatch.begin(); - for (Sprite s : members) { - s.draw(spriteBatch); - } - - spriteBatch.end(); - } - - @Override - public void resize(int width, int height) { - viewport.update(width, height, true); - } - - @Override - public void pause() {} - - @Override - public void resume() {} - - @Override - public void hide() {} - - @Override - public void dispose() { - spriteBatch.dispose(); - for (Sprite s : members) { - Texture texture = s.getTexture(); - if (texture != null) { - texture.dispose(); - } - } - } - - /** - * Adds a new sprite to {@code this} screen. If it is {@code null}, it will not be added and - * simply ignored. - * - * @param s The sprite to add to the screen. - */ - public void add(Sprite s) { - if (s != null) { - members.add(s); - } - } -} diff --git a/core/src/main/java/me/stringfromjava/funkin/game/InitScreen.java b/core/src/main/java/me/stringfromjava/funkin/game/InitScreen.java index 27e5748..6f67f69 100644 --- a/core/src/main/java/me/stringfromjava/funkin/game/InitScreen.java +++ b/core/src/main/java/me/stringfromjava/funkin/game/InitScreen.java @@ -1,8 +1,8 @@ package me.stringfromjava.funkin.game; import me.stringfromjava.funkin.Funkin; -import me.stringfromjava.funkin.backend.display.FunkinScreen; import me.stringfromjava.funkin.game.menus.TitleScreen; +import me.stringfromjava.funkin.graphics.screen.FunkinScreen; public class InitScreen extends FunkinScreen { diff --git a/core/src/main/java/me/stringfromjava/funkin/game/menus/TitleScreen.java b/core/src/main/java/me/stringfromjava/funkin/game/menus/TitleScreen.java index 7e2e032..2130173 100644 --- a/core/src/main/java/me/stringfromjava/funkin/game/menus/TitleScreen.java +++ b/core/src/main/java/me/stringfromjava/funkin/game/menus/TitleScreen.java @@ -3,18 +3,18 @@ import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input; import com.badlogic.gdx.graphics.Texture; -import com.badlogic.gdx.graphics.g2d.Sprite; import me.stringfromjava.funkin.Funkin; import me.stringfromjava.funkin.audio.FunkinSound; -import me.stringfromjava.funkin.backend.display.FunkinScreen; -import me.stringfromjava.funkin.backend.system.Paths; +import me.stringfromjava.funkin.graphics.screen.FunkinScreen; +import me.stringfromjava.funkin.backend.Paths; +import me.stringfromjava.funkin.graphics.sprite.FunkinSprite; import me.stringfromjava.funkin.tween.FunkinTween; import me.stringfromjava.funkin.tween.FunkinEase; import me.stringfromjava.funkin.tween.settings.FunkinTweenSettings; public class TitleScreen extends FunkinScreen { - private Sprite logo; + private FunkinSprite logo; private FunkinSound tickleFight; private FunkinTween tween; @@ -23,7 +23,7 @@ public class TitleScreen extends FunkinScreen { public void show() { super.show(); - logo = new Sprite(new Texture(Paths.image("stage_light"))); + logo = new FunkinSprite(new Texture(Paths.image("stage_light"))); add(logo); tickleFight = new FunkinSound("shared/sounds/tickleFight.ogg"); @@ -35,7 +35,7 @@ public void show() { .addGoal("rotation", 180) .setDuration(0.7f) .setEase(FunkinEase::circInOut); - tween = FunkinTween.tween(logo, settings, (values) -> { + tween = FunkinTween.tween(logo, settings, values -> { logo.setX(values.get("x")); logo.setY(values.get("y")); logo.setRotation(values.get("rotation")); @@ -43,10 +43,10 @@ public void show() { } @Override - public void render(float delta) { - super.render(delta); + public void render(float elapsed) { + super.render(elapsed); - float speed = 200 * delta; + float speed = 500 * elapsed; if (Gdx.input.isKeyPressed(Input.Keys.W)) { logo.setY(logo.getY() + speed); } diff --git a/core/src/main/java/me/stringfromjava/funkin/graphics/screen/FunkinScreen.java b/core/src/main/java/me/stringfromjava/funkin/graphics/screen/FunkinScreen.java new file mode 100644 index 0000000..64f82f9 --- /dev/null +++ b/core/src/main/java/me/stringfromjava/funkin/graphics/screen/FunkinScreen.java @@ -0,0 +1,58 @@ +package me.stringfromjava.funkin.graphics.screen; + +import com.badlogic.gdx.Screen; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.g2d.Sprite; +import me.stringfromjava.funkin.graphics.sprite.FunkinObject; + +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Base class for creating a better screen display with more functionality than the default {@link + * com.badlogic.gdx.Screen} interface. + */ +public abstract class FunkinScreen implements Screen { + + /** The background color of {@code this} current screen. */ + protected Color bgColor; + + /** All display objects that are shown in {@code this} screen. */ + public final CopyOnWriteArrayList members = new CopyOnWriteArrayList<>(); + + @Override + public void show() {} + + @Override + public void render(float delta) {} + + @Override + public void resize(int width, int height) {} + + @Override + public void pause() {} + + @Override + public void resume() {} + + @Override + public void hide() {} + + @Override + public void dispose() {} + + /** + * Adds a new sprite to {@code this} screen. If it is {@code null}, it will not be added and + * simply ignored. + * + * @param object The sprite to add to the screen. + */ + public void add(FunkinObject object) { + if (object != null) { + members.add(object); + } + } + + public Color getBgColor() { + return (bgColor != null) ? bgColor : Color.BLACK; + } +} diff --git a/core/src/main/java/me/stringfromjava/funkin/graphics/sprite/FunkinObject.java b/core/src/main/java/me/stringfromjava/funkin/graphics/sprite/FunkinObject.java new file mode 100644 index 0000000..f7ee545 --- /dev/null +++ b/core/src/main/java/me/stringfromjava/funkin/graphics/sprite/FunkinObject.java @@ -0,0 +1,7 @@ +package me.stringfromjava.funkin.graphics.sprite; + +/** + * An interface which allows any class that implements it to be added to a {@link + * me.stringfromjava.funkin.graphics.screen.FunkinScreen}. + */ +public interface FunkinObject {} diff --git a/core/src/main/java/me/stringfromjava/funkin/graphics/sprite/FunkinSprite.java b/core/src/main/java/me/stringfromjava/funkin/graphics/sprite/FunkinSprite.java new file mode 100644 index 0000000..8935ad9 --- /dev/null +++ b/core/src/main/java/me/stringfromjava/funkin/graphics/sprite/FunkinSprite.java @@ -0,0 +1,14 @@ +package me.stringfromjava.funkin.graphics.sprite; + +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.Sprite; + +/** + * An enhanced version of libGDX's {@link Sprite} class. + */ +public class FunkinSprite extends Sprite implements FunkinObject { + + public FunkinSprite(Texture texture) { + super(texture); + } +} diff --git a/core/src/main/java/me/stringfromjava/funkin/backend/display/text/DisplayText.java b/core/src/main/java/me/stringfromjava/funkin/graphics/text/FunkinText.java similarity index 77% rename from core/src/main/java/me/stringfromjava/funkin/backend/display/text/DisplayText.java rename to core/src/main/java/me/stringfromjava/funkin/graphics/text/FunkinText.java index f1b628c..da42da5 100644 --- a/core/src/main/java/me/stringfromjava/funkin/backend/display/text/DisplayText.java +++ b/core/src/main/java/me/stringfromjava/funkin/graphics/text/FunkinText.java @@ -1,7 +1,7 @@ -package me.stringfromjava.funkin.backend.display.text; +package me.stringfromjava.funkin.graphics.text; /** A display object for creating a piece of text to show on the screen. */ -public class DisplayText { +public class FunkinText { /** The text to be written onto the screen. */ public String text; @@ -15,7 +15,7 @@ public class DisplayText { /** * @param text The string to be displayed. */ - public DisplayText(String text) { + public FunkinText(String text) { this.text = text; x = 0; y = 0; diff --git a/core/src/main/java/me/stringfromjava/funkin/polyverse/Polyverse.java b/core/src/main/java/me/stringfromjava/funkin/polyverse/Polyverse.java index e9759fe..b1f7007 100644 --- a/core/src/main/java/me/stringfromjava/funkin/polyverse/Polyverse.java +++ b/core/src/main/java/me/stringfromjava/funkin/polyverse/Polyverse.java @@ -92,7 +92,7 @@ public static void registerScript(FileHandle handle) { script.onCreate(); } } catch (Exception e) { - Funkin.error("Polyverse", "Failed to load script: " + handle.path()); + Funkin.error("Polyverse", "Failed to load script: " + handle.path(), e); } } @@ -113,7 +113,7 @@ public static void forEachScript(Class type, Consumer a try { action.accept(script); } catch (Exception e) { - Funkin.error("Polyverse", "Error in " + script.getClass().getSimpleName()); + Funkin.error("Polyverse", "Error in " + script.getClass().getSimpleName(), e); } } } @@ -138,7 +138,7 @@ public static void forAllScripts(Consumer action) { try { action.accept(script); } catch (Exception e) { - Funkin.error("Polyverse", "Error in " + script.getClass().getSimpleName()); + Funkin.error("Polyverse", "Error in " + script.getClass().getSimpleName(), e); } } } diff --git a/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTween.java b/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTween.java index 6928584..eb592e6 100644 --- a/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTween.java +++ b/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTween.java @@ -1,6 +1,9 @@ package me.stringfromjava.funkin.tween; +import me.stringfromjava.funkin.Funkin; +import me.stringfromjava.funkin.backend.FunkinReflect; import me.stringfromjava.funkin.tween.settings.FunkinTweenSettings; +import me.stringfromjava.funkin.util.Constants; import org.jetbrains.annotations.NotNull; import java.lang.reflect.Field; @@ -167,7 +170,7 @@ public FunkinTween start() { finished = false; // Ensure that the fields provided actually exist on the object and are floating point values. - var allFields = object.getClass().getDeclaredFields(); + var allFields = FunkinReflect.getAllFields(object.getClass()); var neededFields = tweenSettings.getGoalFields(); for (Field field : allFields) { try { @@ -183,7 +186,7 @@ public FunkinTween start() { } initialValues.put(fName, field.getFloat(object)); } catch (IllegalAccessException e) { - e.printStackTrace(); + Funkin.error(Constants.System.LOG_TAG, "Could not access field \"" + field.getName() + "\".", e); } } return this; diff --git a/core/src/main/java/me/stringfromjava/funkin/util/Constants.java b/core/src/main/java/me/stringfromjava/funkin/util/Constants.java index 7eade3f..24b8002 100644 --- a/core/src/main/java/me/stringfromjava/funkin/util/Constants.java +++ b/core/src/main/java/me/stringfromjava/funkin/util/Constants.java @@ -7,7 +7,7 @@ */ public final class Constants { - /** Static subclass for holding values for components such as the window's width and height. */ + /** Holds values for aspects of the game's display, such as the window's width and height. */ public static final class Display { /** @@ -25,7 +25,25 @@ public static final class Display { private Display() {} } - public static final class Colors { + /** + * Stores constants for things related to the backend of Funkin'. This includes components like + * logging, folder paths, etc. + */ + public static final class System { + + /** + * The default and globally recognized default tag for logs that are outputted inside the + * console. + */ + public static final String LOG_TAG = "Funkin"; + + private System() {} + } + + /** + * Holds ASCII color code constants for text in the console. + */ + public static final class AsciiCodes { public static final String RESET = "\u001B[0m"; public static final String BOLD = "\033[0;1m"; @@ -40,7 +58,7 @@ public static final class Colors { public static final String CYAN = "\u001B[36m"; public static final String WHITE = "\u001B[37m"; - private Colors() {} + private AsciiCodes() {} } private Constants() {} diff --git a/core/src/main/java/me/stringfromjava/funkin/backend/system/FunkinSignal.java b/core/src/main/java/me/stringfromjava/funkin/util/signal/FunkinSignal.java similarity index 87% rename from core/src/main/java/me/stringfromjava/funkin/backend/system/FunkinSignal.java rename to core/src/main/java/me/stringfromjava/funkin/util/signal/FunkinSignal.java index 59efa42..89d3af0 100644 --- a/core/src/main/java/me/stringfromjava/funkin/backend/system/FunkinSignal.java +++ b/core/src/main/java/me/stringfromjava/funkin/util/signal/FunkinSignal.java @@ -1,4 +1,4 @@ -package me.stringfromjava.funkin.backend.system; +package me.stringfromjava.funkin.util.signal; import java.util.concurrent.CopyOnWriteArrayList; @@ -40,6 +40,16 @@ public void addOnce(SignalHandler callback) { } } + /** + * Removes a specific callback from {@code this} signal. + * + * @param callback The callback to remove. + */ + public void remove(SignalHandler callback) { + callbacks.remove(callback); + tempCallbacks.remove(callback); + } + /** Removes all callbacks from {@code this} signal. */ public void clear() { callbacks.clear(); diff --git a/core/src/main/java/me/stringfromjava/funkin/util/signal/FunkinSignalData.java b/core/src/main/java/me/stringfromjava/funkin/util/signal/FunkinSignalData.java new file mode 100644 index 0000000..a172e20 --- /dev/null +++ b/core/src/main/java/me/stringfromjava/funkin/util/signal/FunkinSignalData.java @@ -0,0 +1,21 @@ +package me.stringfromjava.funkin.util.signal; + +import me.stringfromjava.funkin.audio.FunkinSound; +import me.stringfromjava.funkin.graphics.screen.FunkinScreen; + +/** + * Convenience class for holding all signal data records used in the default Funkin' signals. + */ +public final class FunkinSignalData { + + public record RenderSignalData(float delta) {} + + public record ScreenSwitchSignalData(FunkinScreen screen) {} + + public record SoundPlayedSignalData(FunkinSound sound) {} + + public record WindowMinimizedSignalData(boolean iconified) {} + + private FunkinSignalData() {} +} + diff --git a/gradle.properties b/gradle.properties index 5f61b4a..d779a5e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,7 +14,6 @@ org.gradle.logging.level=quiet anim8Version=0.5.3 ktxVersion=1.13.1-rc1 utilsVersion=0.13.7 -universalTweenVersion=6.3.3 graalHelperVersion=2.0.1 android.useAndroidX=true android.enableR8.fullMode=false From 84a4d33b7be1d2e2ec2cb2ae16133e162f929edf Mon Sep 17 00:00:00 2001 From: String Date: Wed, 14 Jan 2026 00:23:29 -0600 Subject: [PATCH 02/17] Add animation functionality to FunkinSprite class --- android/build.gradle | 5 +- assets/another_test.groovy | 3 +- assets/test.groovy | 2 +- core/build.gradle | 1 + .../java/me/stringfromjava/funkin/Funkin.java | 41 ++- .../me/stringfromjava/funkin/FunkinGame.java | 35 +- .../funkin/audio/FunkinSound.java | 67 ++-- .../stringfromjava/funkin/backend/Paths.java | 6 +- .../funkin/game/menus/TitleScreen.java | 9 +- .../funkin/graphics/sprite/FunkinSprite.java | 301 +++++++++++++++++- .../funkin/util/signal/FunkinSignalData.java | 3 +- gradle.properties | 3 +- lwjgl3/build.gradle | 4 +- 13 files changed, 415 insertions(+), 65 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index b8ef611..9e17554 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -65,6 +65,10 @@ dependencies { implementation "com.badlogicgames.gdx:gdx-backend-android:$gdxVersion" implementation project(':core') + api "games.rednblack.miniaudio:gdx-miniaudio-platform:$miniaudioVersion:natives-armeabi-v7a" + 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" natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-arm64-v8a" natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-armeabi-v7a" natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-x86" @@ -73,7 +77,6 @@ dependencies { natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-armeabi-v7a" natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-x86" natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-x86_64" - } // Called every time gradle gets executed, takes the native dependencies of diff --git a/assets/another_test.groovy b/assets/another_test.groovy index 0051f55..32f0bd8 100644 --- a/assets/another_test.groovy +++ b/assets/another_test.groovy @@ -1,7 +1,6 @@ import com.badlogic.gdx.Gdx import com.badlogic.gdx.Input import com.badlogic.gdx.graphics.Texture -import com.badlogic.gdx.graphics.g2d.Sprite import me.stringfromjava.funkin.Funkin import me.stringfromjava.funkin.backend.Paths import me.stringfromjava.funkin.graphics.sprite.FunkinSprite @@ -28,7 +27,7 @@ class AnotherTestClass extends Script { super.onRender(delta) if (Gdx.input.isKeyJustPressed(Input.Keys.SPACE)) { - var sprite = new FunkinSprite(new Texture(Paths.image("pauseAlt/bfLol"))) + var sprite = new FunkinSprite().loadGraphic(new Texture(Paths.sharedImage("pauseAlt/bfLol"))) var randomPosX = new Random().nextInt(Constants.Display.WINDOW_WIDTH) var randomPosY = new Random().nextInt(Constants.Display.WINDOW_HEIGHT) diff --git a/assets/test.groovy b/assets/test.groovy index 35af7c6..7a74da9 100644 --- a/assets/test.groovy +++ b/assets/test.groovy @@ -44,7 +44,7 @@ class TestScreen extends FunkinScreen { void show() { super.show() - test = new FunkinSprite(new Texture(Paths.image('NOTE_hold_assets'))) + test = new FunkinSprite().loadGraphic(new Texture(Paths.sharedImage('NOTE_hold_assets'))) add(test) bgColor = new Color(0, 1, 0, 1) diff --git a/core/build.gradle b/core/build.gradle index 9b3110a..c0c016b 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -8,6 +8,7 @@ dependencies { api "com.github.tommyettinger:anim8-gdx:$anim8Version" api "com.github.tommyettinger:libgdx-utils:$utilsVersion" api "io.github.libktx:ktx-freetype:$ktxVersion" + api "games.rednblack.miniaudio:miniaudio:$miniaudioVersion" // FNF:JE. implementation "org.apache.groovy:groovy:4.0.12" diff --git a/core/src/main/java/me/stringfromjava/funkin/Funkin.java b/core/src/main/java/me/stringfromjava/funkin/Funkin.java index 7213040..ab318e7 100644 --- a/core/src/main/java/me/stringfromjava/funkin/Funkin.java +++ b/core/src/main/java/me/stringfromjava/funkin/Funkin.java @@ -2,7 +2,6 @@ import com.badlogic.gdx.Gdx; import com.badlogic.gdx.audio.Music; -import com.badlogic.gdx.audio.Sound; import com.badlogic.gdx.scenes.scene2d.Stage; import me.stringfromjava.funkin.audio.FunkinSound; import me.stringfromjava.funkin.graphics.screen.FunkinScreen; @@ -39,20 +38,20 @@ public final class Funkin { *

The key is the sound's ID (created by libGDX), and the value is the sound itself. Note that * it's not recommended to access this unless you know what you're doing! */ - public static Map soundPool = new HashMap<>(); + public static Map soundPool = new HashMap<>(); /** The object where the current music being played is stored. */ public static Music music = null; - /** The global volume multiplier for all sounds and music. */ - public static float masterVolume = 1.0f; - /** * The static instance used to access the core elements of the game. This includes the loop, * setting the current screen, and more. */ private static FunkinGame game; + /** The global volume multiplier for all sounds and music. */ + private static float globalVolume = 1.0f; + /** Has the global manager been initialized yet? */ private static boolean initialized = false; @@ -151,6 +150,32 @@ public static Music playMusic(String path, float volume, boolean looped) { return music; } + /** + * Sets Funkin's global volume and applies it automatically to all sounds in the sound pool and + * the current music playing. + * + * @param v The new volume to apply globally. + * @return The new global volume. + */ + public static float setGlobalVolume(float v) { + globalVolume = v; + if (music != null) { + music.setVolume(music.getVolume() * v); + } + for (FunkinSound sound : soundPool.values()) { + sound.setVolume(sound.ID, sound.getVolume(), true); + } + return globalVolume; + } + + public static boolean keyPressed(int key) { + return Gdx.input.isKeyPressed(key); + } + + public static boolean keyJustPressed(int key) { + return Gdx.input.isKeyJustPressed(key); + } + public static void info(Object message) { info(Constants.System.LOG_TAG, message); } @@ -178,7 +203,7 @@ public static void error(String tag, Object message) { public static void error(String tag, Object message, Throwable throwable) { String msg = (throwable != null) - ? (message + " | Exception: " + throwable.toString()) + ? (message + " | Exception: " + throwable) : message.toString(); outputLog(tag, msg, FunkinLogLevel.ERROR); } @@ -187,6 +212,10 @@ public static FunkinGame getGame() { return game; } + public static float getGlobalVolume() { + return globalVolume; + } + public static Stage getStage() { return game.stage; } diff --git a/core/src/main/java/me/stringfromjava/funkin/FunkinGame.java b/core/src/main/java/me/stringfromjava/funkin/FunkinGame.java index 481fd26..c1df87d 100644 --- a/core/src/main/java/me/stringfromjava/funkin/FunkinGame.java +++ b/core/src/main/java/me/stringfromjava/funkin/FunkinGame.java @@ -24,7 +24,8 @@ import java.util.Set; -import me.stringfromjava.funkin.util.signal.FunkinSignalData; +import static me.stringfromjava.funkin.util.signal.FunkinSignalData.RenderSignalData; +import static me.stringfromjava.funkin.util.signal.FunkinSignalData.WindowMinimizedSignalData; /** * The game object used for containing the main loop and core elements of Funkin'. @@ -89,34 +90,34 @@ public void render() { float delta = Gdx.graphics.getDeltaTime(); FunkinScreen screen = Funkin.screen; - Funkin.Signals.preRender.dispatch(new FunkinSignalData.RenderSignalData(delta)); + Funkin.Signals.preRender.dispatch(new RenderSignalData(delta)); // Update and render the current screen that's active. + ScreenUtils.clear(Color.BLACK); + viewport.apply(); + batch.setProjectionMatrix(camera.combined); + batch.begin(); if (screen != null) { - ScreenUtils.clear(Color.BLACK); - viewport.apply(); - batch.setProjectionMatrix(camera.combined); - batch.begin(); batch.setColor(screen.getBgColor()); batch.draw(bgTexture, 0, 0, viewport.getWorldWidth(), viewport.getWorldHeight()); batch.setColor(Color.WHITE); // Set color back to white so display objects aren't affected. screen.render(delta); for (FunkinObject object : screen.members) { - if (object instanceof FunkinSprite) { - ((FunkinSprite) object).draw(batch); + if (object instanceof FunkinSprite sprite) { + sprite.update(delta); + sprite.draw(batch); } } } batch.end(); - // Update the current screen and stage. stage.act(delta); stage.draw(); FunkinTween.globalManager.update(delta); Polyverse.forAllScripts(script -> script.onRender(delta)); - Funkin.Signals.postRender.dispatch(new FunkinSignalData.RenderSignalData(delta)); + Funkin.Signals.postRender.dispatch(new RenderSignalData(delta)); } @Override @@ -127,8 +128,7 @@ public void resume() {} /** Called when the user regains focus on the game's window. */ public void onWindowFocused() { - Funkin.masterVolume = 1.0f; - Funkin.music.setVolume(1); + Funkin.setGlobalVolume(1); Funkin.Signals.windowFocused.dispatch(); Funkin.info("Game window has regained focus."); } @@ -138,10 +138,8 @@ public void onWindowUnfocused() { if (isMinimized) { return; } - Funkin.masterVolume = 0.008f; - Funkin.music.setVolume(0.008f); - Funkin.Signals.windowMinimized.dispatch( - new FunkinSignalData.WindowMinimizedSignalData(isMinimized)); + Funkin.setGlobalVolume(0.008f); + Funkin.Signals.windowUnfocused.dispatch(); Funkin.info("Game window has lost focus."); } @@ -156,9 +154,8 @@ public void onWindowMinimized(boolean iconified) { if (!isMinimized) { return; } - Funkin.masterVolume = 0.0f; - Funkin.music.setVolume(0); - Funkin.Signals.windowMinimized.dispatch(); + Funkin.setGlobalVolume(0); + Funkin.Signals.windowMinimized.dispatch(new WindowMinimizedSignalData(isMinimized)); Funkin.info("Game window has been minimized."); } diff --git a/core/src/main/java/me/stringfromjava/funkin/audio/FunkinSound.java b/core/src/main/java/me/stringfromjava/funkin/audio/FunkinSound.java index ee9c9c4..542d839 100644 --- a/core/src/main/java/me/stringfromjava/funkin/audio/FunkinSound.java +++ b/core/src/main/java/me/stringfromjava/funkin/audio/FunkinSound.java @@ -5,7 +5,8 @@ import com.badlogic.gdx.files.FileHandle; import me.stringfromjava.funkin.Funkin; import me.stringfromjava.funkin.backend.Paths; -import me.stringfromjava.funkin.util.signal.FunkinSignalData; + +import static me.stringfromjava.funkin.util.signal.FunkinSignalData.SoundPlayedSignalData; /** * An enhanced version of libGDX's {@link Sound}. @@ -18,7 +19,7 @@ public class FunkinSound implements Sound { /** The ID of {@code this} specific sound. */ public final long ID; - private Sound thisSound; + private final Sound libGdxSound; private float volume; private float pitch; private float pan; @@ -30,20 +31,20 @@ public FunkinSound(String path) { } public FunkinSound(FileHandle path) { - thisSound = Gdx.audio.newSound(path); - ID = thisSound.play(); + libGdxSound = Gdx.audio.newSound(path); + ID = libGdxSound.play(); volume = 1.0f; pitch = 1.0f; pan = 0.0f; looping = false; isPaused = false; - thisSound.stop(); - Funkin.soundPool.put(ID, thisSound); + libGdxSound.stop(); + Funkin.soundPool.put(ID, this); } @Override public long play() { - return thisSound.play(volume, pitch, pan); + return libGdxSound.play(volume, pitch, pan); } @Override @@ -57,14 +58,14 @@ public long play(float volume, float pitch) { @Override public long play(float volume, float pitch, float pan) { - Funkin.Signals.preSoundPlayed.dispatch(new FunkinSignalData.SoundPlayedSignalData(this)); - this.volume = volume * Funkin.masterVolume; + Funkin.Signals.preSoundPlayed.dispatch(new SoundPlayedSignalData(this)); + this.volume = volume; this.pitch = pitch; this.pan = pan; this.looping = false; this.isPaused = false; - Funkin.Signals.postSoundPlayed.dispatch(new FunkinSignalData.SoundPlayedSignalData(this)); - return thisSound.play(volume, pitch, pan); + Funkin.Signals.postSoundPlayed.dispatch(new SoundPlayedSignalData(this)); + return libGdxSound.play(volume, pitch, pan); } @Override @@ -83,32 +84,32 @@ public long loop(float volume, float pitch) { @Override public long loop(float volume, float pitch, float pan) { - Funkin.Signals.preSoundPlayed.dispatch(new FunkinSignalData.SoundPlayedSignalData(this)); - this.volume = volume * Funkin.masterVolume; + Funkin.Signals.preSoundPlayed.dispatch(new SoundPlayedSignalData(this)); + this.volume = volume * Funkin.getGlobalVolume(); this.pitch = pitch; this.pan = pan; this.looping = true; this.isPaused = false; - Funkin.Signals.postSoundPlayed.dispatch(new FunkinSignalData.SoundPlayedSignalData(this)); - return thisSound.loop(volume, pitch, pan); + Funkin.Signals.postSoundPlayed.dispatch(new SoundPlayedSignalData(this)); + return libGdxSound.loop(volume, pitch, pan); } @Override public void stop() { isPaused = false; - thisSound.stop(); + libGdxSound.stop(); } @Override public void pause() { isPaused = true; - thisSound.pause(); + libGdxSound.pause(); } @Override public void resume() { isPaused = false; - thisSound.resume(); + libGdxSound.resume(); } @Override @@ -118,45 +119,55 @@ public void dispose() { pan = 0.0f; looping = false; isPaused = false; - thisSound.dispose(); + libGdxSound.dispose(); Funkin.soundPool.remove(ID); } @Override public void stop(long soundId) { isPaused = false; - thisSound.stop(); + libGdxSound.stop(); } @Override public void pause(long soundId) { isPaused = true; - thisSound.pause(soundId); + libGdxSound.pause(soundId); } @Override public void resume(long soundId) { isPaused = false; - thisSound.resume(); + libGdxSound.resume(); } @Override public void setLooping(long soundId, boolean looping) { this.looping = looping; - thisSound.setLooping(soundId, looping); + libGdxSound.setLooping(soundId, looping); } @Override public void setPitch(long soundId, float pitch) { this.pitch = pitch; - thisSound.setPitch(soundId, pitch); + libGdxSound.setPitch(soundId, pitch); } @Override public void setVolume(long soundId, float volume) { - float v = volume * Funkin.masterVolume; - this.volume = v; - thisSound.setVolume(soundId, v); + setVolume(soundId, volume, true); + } + + /** + * Sets the new volume for {@code this} sound. + * + * @param soundId The sound's unique ID, generated by libGDX. + * @param volume The new volume to use. + * @param useGlobalVolume Should the new volume automatically adapt to Funkin's global volume? + */ + public void setVolume(long soundId, float volume, boolean useGlobalVolume) { + this.volume = useGlobalVolume ? volume * Funkin.getGlobalVolume() : volume; + libGdxSound.setVolume(soundId, this.volume); } public void setPan(long soundId, float pan) { @@ -165,7 +176,7 @@ public void setPan(long soundId, float pan) { @Override public void setPan(long soundId, float pan, float volume) { - thisSound.setPan(soundId, pan, volume); + libGdxSound.setPan(soundId, pan, volume); } public float getVolume() { diff --git a/core/src/main/java/me/stringfromjava/funkin/backend/Paths.java b/core/src/main/java/me/stringfromjava/funkin/backend/Paths.java index 9e72089..f47e1d3 100644 --- a/core/src/main/java/me/stringfromjava/funkin/backend/Paths.java +++ b/core/src/main/java/me/stringfromjava/funkin/backend/Paths.java @@ -14,7 +14,11 @@ public static FileHandle shared(String path) { return asset(String.format("shared/%s", path)); } - public static FileHandle image(String path) { + public static FileHandle xml(String path) { + return asset(path + ".xml"); + } + + public static FileHandle sharedImage(String path) { return shared(String.format("images/%s.png", path)); } diff --git a/core/src/main/java/me/stringfromjava/funkin/game/menus/TitleScreen.java b/core/src/main/java/me/stringfromjava/funkin/game/menus/TitleScreen.java index 2130173..a9e9351 100644 --- a/core/src/main/java/me/stringfromjava/funkin/game/menus/TitleScreen.java +++ b/core/src/main/java/me/stringfromjava/funkin/game/menus/TitleScreen.java @@ -23,7 +23,10 @@ public class TitleScreen extends FunkinScreen { public void show() { super.show(); - logo = new FunkinSprite(new Texture(Paths.image("stage_light"))); + var t = Paths.sharedImage("noteStrumline"); + var xml = Paths.shared("images/noteStrumline.xml"); + logo = new FunkinSprite().loadSparrowFrames(t, xml); + logo.addAnimationByPrefix("test", "confirmDown", 24, false); add(logo); tickleFight = new FunkinSound("shared/sounds/tickleFight.ogg"); @@ -60,6 +63,10 @@ public void render(float elapsed) { logo.setX(logo.getX() + speed); } + if (Gdx.input.isKeyJustPressed(Input.Keys.SPACE)) { + logo.playAnimation("test", false); + } + if (Gdx.input.isKeyJustPressed(Input.Keys.T)) { tween.start(); } diff --git a/core/src/main/java/me/stringfromjava/funkin/graphics/sprite/FunkinSprite.java b/core/src/main/java/me/stringfromjava/funkin/graphics/sprite/FunkinSprite.java index 8935ad9..6562aef 100644 --- a/core/src/main/java/me/stringfromjava/funkin/graphics/sprite/FunkinSprite.java +++ b/core/src/main/java/me/stringfromjava/funkin/graphics/sprite/FunkinSprite.java @@ -1,14 +1,311 @@ package me.stringfromjava.funkin.graphics.sprite; +import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.Animation; +import com.badlogic.gdx.graphics.g2d.Batch; import com.badlogic.gdx.graphics.g2d.Sprite; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.XmlReader; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; /** * An enhanced version of libGDX's {@link Sprite} class. + * + *

It allows you to load animations, textures, and do much more with simplicity and ease. */ public class FunkinSprite extends Sprite implements FunkinObject { - public FunkinSprite(Texture texture) { - super(texture); + /** The texture image that {@code this} sprite uses. */ + protected Texture texture; + + /** The atlas regions used in this sprite (used for animations). */ + protected Array atlasRegions; + + /** A map that animations that stored and registered in. */ + protected final Map> animations; + + /** The current frame that {@code this} sprite is on in its animation (if one is playing). */ + protected TextureAtlas.AtlasRegion currentFrame; + + /** Used for updating {@code this} sprite's current animation. */ + protected float stateTime = 0; + + /** The name of the current animation playing. */ + protected String currentAnim = ""; + + /** Is {@code this} sprites current animation looping indefinitely? */ + protected boolean looping = true; + + /** + * Where all the image frames are stored. This is also where the main image is stored when using + * {@link #loadGraphic(FileHandle)}. + */ + protected TextureRegion[][] frames; + + public FunkinSprite() { + animations = new HashMap<>(); + } + + /** + * Updates {@code this} sprite. + * + * @param delta The amount of time that has passed since the last frame update. + */ + public void update(float delta) { + if (animations.containsKey(currentAnim)) { + stateTime += delta; + currentFrame = + (TextureAtlas.AtlasRegion) animations.get(currentAnim).getKeyFrame(stateTime, looping); + setRegion(currentFrame); + } + } + + /** + * Load's a texture and automatically resizes the size of {@code this} sprite. + * + * @param path The directory of the {@code .png} to load onto {@code this} sprite. + * @return {@code this} sprite for chaining. + */ + public FunkinSprite loadGraphic(FileHandle path) { + Texture texture = new Texture(path); + return loadGraphic(texture, texture.getWidth(), texture.getHeight()); + } + + /** + * Load's a texture and automatically resizes the size of {@code this} sprite. + * + * @param path The directory of the {@code .png} to load onto {@code this} sprite. + * @param frameWidth How wide the sprite should be. + * @return {@code this} sprite for chaining. + */ + public FunkinSprite loadGraphic(FileHandle path, int frameWidth) { + Texture texture = new Texture(path); + return loadGraphic(texture, frameWidth, texture.getHeight()); + } + + /** + * Load's a texture and automatically resizes the size of {@code this} sprite. + * + * @param path The directory of the {@code .png} to load onto {@code this} sprite. + * @param frameWidth How wide the sprite should be. + * @param frameHeight How tall the sprite should be. + * @return {@code this} sprite for chaining. + */ + public FunkinSprite loadGraphic(FileHandle path, int frameWidth, int frameHeight) { + return loadGraphic(new Texture(path), frameWidth, frameHeight); + } + + public FunkinSprite loadGraphic(Texture texture, int frameWidth, int frameHeight) { + frames = TextureRegion.split(texture, frameWidth, frameHeight); + // Set default visual to the first frame. + setRegion(frames[0][0]); + setSize(frameWidth, frameHeight); + setOriginCenter(); + return this; + } + + /** + * Loads an {@code .xml} spritesheet with {@code SubTexture} data inside of it. + * + * @param texture The path to the {@code .png} texture file for slicing and extracting the + * different frames from. + * @param xmlFile The path to the {@code .xml} file which contains the data for each subtexture of + * the sparrow atlas. + * @return {@code this} sprite for chaining. + */ + public FunkinSprite loadSparrowFrames(FileHandle texture, FileHandle xmlFile) { + return loadSparrowFrames(new Texture(texture), new XmlReader().parse(xmlFile)); + } + + /** + * Loads an {@code .xml} spritesheet with {@code SubTexture} data inside of it. + * + * @param texture The {@code .png} texture file for slicing and extracting the different frames + * from. + * @param xmlFile The {@link XmlReader.Element} data which contains the data for each subtexture + * of the sparrow atlas. + * @return {@code this} sprite for chaining. + */ + public FunkinSprite loadSparrowFrames(Texture texture, XmlReader.Element xmlFile) { + // We store regions in a list so we can filter them by prefix later. + // TextureAtlas.AtlasRegion is used because it supports offsets. + atlasRegions = new Array<>(); + + for (XmlReader.Element subTexture : xmlFile.getChildrenByName("SubTexture")) { + String name = subTexture.getAttribute("name"); + int x = subTexture.getInt("x"); + int y = subTexture.getInt("y"); + int width = subTexture.getInt("width"); + int height = subTexture.getInt("height"); + + this.texture = texture; + TextureAtlas.AtlasRegion region = new TextureAtlas.AtlasRegion(texture, x, y, width, height); + region.name = name; + + // Handle Trimming/Offsets (frameX, frameY, frameWidth, frameHeight). + // These are crucial for centering notes/characters correctly. + if (subTexture.hasAttribute("frameX")) { + region.offsetX = Math.abs(subTexture.getInt("frameX")); + region.offsetY = Math.abs(subTexture.getInt("frameY")); + region.originalWidth = subTexture.getInt("frameWidth"); + region.originalHeight = subTexture.getInt("frameHeight"); + } else { + region.offsetX = 0; + region.offsetY = 0; + region.originalWidth = width; + region.originalHeight = height; + } + + atlasRegions.add(region); + } + + // Set the default visual. + setRegion(atlasRegions.first()); + setSize(getRegionWidth(), getRegionHeight()); + return this; + } + + /** + * Adds an animation by looking for sub textures that start with the prefix passed down. + * + * @param name The name of the animation (e.g., "confirm"). + * @param prefix The prefix in the {@code .xml} file (e.g., "left confirm"). + * @param frameRate How fast the animation should play in frames-per-second. Standard is 24. + * @param loop Should the new animation loop indefinitely? + */ + public void addAnimationByPrefix(String name, String prefix, int frameRate, boolean loop) { + Array frames = new Array<>(); + + for (TextureAtlas.AtlasRegion region : atlasRegions) { + if (region.name.startsWith(prefix)) { + frames.add(region); + } + } + + if (frames.size > 0) { + // Ensure frames are sorted alphabetically (e.g., confirm0000, confirm0001). + frames.sort(Comparator.comparing(o -> o.name)); + + animations.put( + name, + new Animation<>( + 1f / frameRate, frames, loop ? Animation.PlayMode.LOOP : Animation.PlayMode.NORMAL)); + } + } + + /** + * Adds a new animation to the animations list, if it doesn't exist already. + * + * @param name The name of the animation. This is what you'll use every time you use {@code + * playAnimation()}. + * @param frameIndices An array of integers used for animation frame indices. + * @param frameDuration How long each frame lasts for in seconds. + */ + public void addAnimation(String name, int[] frameIndices, float frameDuration) { + Array animFrames = new Array<>(); + + // Convert 1D indices (0, 1, 2...) to 2D grid coordinates. + int cols = frames[0].length; + for (int index : frameIndices) { + int row = index / cols; + int col = index % cols; + animFrames.add(frames[row][col]); + } + + animations.put(name, new Animation<>(frameDuration, animFrames)); + } + + /** + * Plays an animation {@code this} sprite has, with looping enabled by default. + * + * @param name The name of the animation to play. + */ + public void playAnimation(String name) { + playAnimation(name, true); + } + + /** + * Plays an animation {@code this} sprite has, if it exists. + * + * @param name The name of the animation to play. + * @param loop Should this animation loop indefinitely? + */ + public void playAnimation(String name, boolean loop) { + if (!currentAnim.equals(name) || isAnimationFinished()) { + this.currentAnim = name; + this.looping = loop; + this.stateTime = 0; + } + } + + @Override + public void draw(Batch batch) { + if (currentFrame != null) { + // We use the currentFrame's offsets to adjust the drawing position. + // This prevents the "shaking" or "jittering" in sparrow animations. + float drawX = getX() + currentFrame.offsetX; + + // libGDX coordinate system is bottom-left, but Sparrow is top-down. + // This math aligns the frame within its "original" box. + float drawY = + getY() + + (currentFrame.originalHeight + - currentFrame.getRegionHeight() + - currentFrame.offsetY); + + batch.draw( + currentFrame, + drawX, + drawY, + getOriginX(), + getOriginY(), + currentFrame.getRegionWidth(), + currentFrame.getRegionHeight(), + getScaleX(), + getScaleY(), + getRotation()); + } else { + super.draw(batch); // Fallback to standard sprite drawing. + } + } + + public boolean isAnimationFinished() { + Animation anim = animations.get(currentAnim); + if (anim == null) return true; + return anim.isAnimationFinished(stateTime); + } + + public Map> getAnimations() { + return animations; + } + + public Array getAtlasRegions() { + return atlasRegions; + } + + public TextureAtlas.AtlasRegion getCurrentFrame() { + return currentFrame; + } + + public float getStateTime() { + return stateTime; + } + + public String getCurrentAnim() { + return currentAnim; + } + + public boolean isLooping() { + return looping; + } + + public TextureRegion[][] getFrames() { + return frames; } } diff --git a/core/src/main/java/me/stringfromjava/funkin/util/signal/FunkinSignalData.java b/core/src/main/java/me/stringfromjava/funkin/util/signal/FunkinSignalData.java index a172e20..d27c22f 100644 --- a/core/src/main/java/me/stringfromjava/funkin/util/signal/FunkinSignalData.java +++ b/core/src/main/java/me/stringfromjava/funkin/util/signal/FunkinSignalData.java @@ -4,7 +4,8 @@ import me.stringfromjava.funkin.graphics.screen.FunkinScreen; /** - * Convenience class for holding all signal data records used in the default Funkin' signals. + * Convenience class for holding all signal data records used in the default signals stored in + * the global {@link me.stringfromjava.funkin.Funkin} manager class. */ public final class FunkinSignalData { diff --git a/gradle.properties b/gradle.properties index d779a5e..d3f3f53 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,4 +19,5 @@ android.useAndroidX=true android.enableR8.fullMode=false enableGraalNative=false gdxVersion=1.13.1 -projectVersion=1.0.0-ALPHA +miniaudioVersion=0.7 +projectVersion=0.1.0-ALPHA diff --git a/lwjgl3/build.gradle b/lwjgl3/build.gradle index 37d3d2e..9e4435f 100644 --- a/lwjgl3/build.gradle +++ b/lwjgl3/build.gradle @@ -28,6 +28,7 @@ if (JavaVersion.current().isJava9Compatible()) { } dependencies { + api "games.rednblack.miniaudio:gdx-miniaudio-platform:$miniaudioVersion:natives-desktop" implementation "com.badlogicgames.gdx:gdx-backend-lwjgl3:$gdxVersion" implementation "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-desktop" implementation "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop" @@ -35,8 +36,7 @@ dependencies { if(enableGraalNative == 'true') { implementation "io.github.berstanio:gdx-svmhelper-backend-lwjgl3:$graalHelperVersion" - - } + } } def os = System.properties['os.name'].toLowerCase() From e280a4c0beea48294926176e2db1b9be200d79bb Mon Sep 17 00:00:00 2001 From: String Date: Wed, 14 Jan 2026 21:30:48 -0600 Subject: [PATCH 03/17] Rework the audio system to use something better --- assets/another_test.groovy | 2 +- assets/test.groovy | 2 +- .../java/me/stringfromjava/funkin/Funkin.java | 230 ++++++++++++------ .../me/stringfromjava/funkin/FunkinGame.java | 30 +-- .../funkin/audio/FunkinSound.java | 201 --------------- .../stringfromjava/funkin/backend/Paths.java | 12 +- .../funkin/game/menus/TitleScreen.java | 34 ++- .../funkin/graphics/sprite/FunkinSprite.java | 16 +- .../funkin/tween/FunkinTween.java | 4 +- .../FunkinReflectUtil.java} | 5 +- .../funkin/util/signal/FunkinSignalData.java | 6 +- 11 files changed, 209 insertions(+), 333 deletions(-) delete mode 100644 core/src/main/java/me/stringfromjava/funkin/audio/FunkinSound.java rename core/src/main/java/me/stringfromjava/funkin/{backend/FunkinReflect.java => util/FunkinReflectUtil.java} (93%) diff --git a/assets/another_test.groovy b/assets/another_test.groovy index 32f0bd8..60ccb28 100644 --- a/assets/another_test.groovy +++ b/assets/another_test.groovy @@ -27,7 +27,7 @@ class AnotherTestClass extends Script { super.onRender(delta) if (Gdx.input.isKeyJustPressed(Input.Keys.SPACE)) { - var sprite = new FunkinSprite().loadGraphic(new Texture(Paths.sharedImage("pauseAlt/bfLol"))) + var sprite = new FunkinSprite().loadGraphic(Paths.sharedImageAsset("pauseAlt/bfLol")) var randomPosX = new Random().nextInt(Constants.Display.WINDOW_WIDTH) var randomPosY = new Random().nextInt(Constants.Display.WINDOW_HEIGHT) diff --git a/assets/test.groovy b/assets/test.groovy index 7a74da9..a046598 100644 --- a/assets/test.groovy +++ b/assets/test.groovy @@ -44,7 +44,7 @@ class TestScreen extends FunkinScreen { void show() { super.show() - test = new FunkinSprite().loadGraphic(new Texture(Paths.sharedImage('NOTE_hold_assets'))) + test = new FunkinSprite().loadGraphic(Paths.sharedImageAsset('NOTE_hold_assets')) add(test) bgColor = new Color(0, 1, 0, 1) diff --git a/core/src/main/java/me/stringfromjava/funkin/Funkin.java b/core/src/main/java/me/stringfromjava/funkin/Funkin.java index ab318e7..bf220e7 100644 --- a/core/src/main/java/me/stringfromjava/funkin/Funkin.java +++ b/core/src/main/java/me/stringfromjava/funkin/Funkin.java @@ -1,19 +1,20 @@ package me.stringfromjava.funkin; import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.audio.Music; +import com.badlogic.gdx.assets.AssetManager; +import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.scenes.scene2d.Stage; -import me.stringfromjava.funkin.audio.FunkinSound; +import games.rednblack.miniaudio.MAGroup; +import games.rednblack.miniaudio.MASound; +import games.rednblack.miniaudio.MiniAudio; +import games.rednblack.miniaudio.loader.MASoundLoader; import me.stringfromjava.funkin.graphics.screen.FunkinScreen; import me.stringfromjava.funkin.util.signal.FunkinSignal; -import me.stringfromjava.funkin.backend.Paths; import me.stringfromjava.funkin.util.Constants; import me.stringfromjava.funkin.util.signal.FunkinSignalData.*; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -import java.util.HashMap; -import java.util.Map; /** * Global manager and utility class for the game. @@ -32,16 +33,19 @@ public final class Funkin { */ public static FunkinScreen screen = null; - /** - * A map containing all sounds that are currently playing. - * - *

The key is the sound's ID (created by libGDX), and the value is the sound itself. Note that - * it's not recommended to access this unless you know what you're doing! - */ - public static Map soundPool = new HashMap<>(); + /** The main audio object used to create, */ + private static MiniAudio engine; + + private static AssetManager assetManager; + + /** The audio group for regular small, sound effects. */ + private static MAGroup soundsGroup; + + /** The sound for playing music throughout the game. */ + public static MASound music; - /** The object where the current music being played is stored. */ - public static Music music = null; + /** The current master volume that is set. */ + private static float masterVolume = 1; /** * The static instance used to access the core elements of the game. This includes the loop, @@ -49,9 +53,6 @@ public final class Funkin { */ private static FunkinGame game; - /** The global volume multiplier for all sounds and music. */ - private static float globalVolume = 1.0f; - /** Has the global manager been initialized yet? */ private static boolean initialized = false; @@ -68,6 +69,15 @@ public static void initialize(FunkinGame gameInstance) { throw new IllegalStateException("FNF:JE has already been initialized!"); } game = gameInstance; + + assetManager = new AssetManager(); + + // Set up the game's global audio system. + engine = new MiniAudio(); + soundsGroup = engine.createGroup(); + assetManager.setLoader( + MASound.class, new MASoundLoader(engine, assetManager.getFileHandleResolver())); + initialized = true; } @@ -95,77 +105,138 @@ public static void setScreen(FunkinScreen newScreen) { } /** - * Plays a sound. (Duh.) + * Plays a new sound effect. + * + *

When you want to play a sound externally, outside the assets folder, you can use a {@link + * FileHandle} like so: + * + *

{@code
+   * // Notice how it uses the Paths class provided by Funkin'.
+   * Funkin.playSound(Paths.external("your/path/here").path());
+   * }
* - * @param path The path to play the sound from. - * @return The sound instance itself, as a {@link FunkinSound}. + * @param path The path to load the sound from. Note that if you're loading an external sound + * outside the game's assets, you should use {@link FileHandle}; otherwise, just pass down a + * regular string (without {@code assets/} at the beginning). + * @return The new sound instance. */ - public static FunkinSound playSound(String path) { - FunkinSound sound = new FunkinSound(path); - if (sound.ID != -1) { // libGDX will return -1 if the sound fails to play. - soundPool.put(sound.ID, sound); - } - sound.play(); - return sound; + public static MASound playSound(String path) { + return playSound(path, 1, false, null, false); } /** - * Plays new music. (Duh.) + * Plays a new sound effect. * - * @param path The path to play the music from. - * @return The music instance itself. + *

When you want to play a sound externally, outside the assets folder, you can use a {@link + * FileHandle} like so: + * + *

{@code
+   * // Notice how it uses the Paths class provided by Funkin'.
+   * Funkin.playSound(Paths.external("your/path/here").path(), 1);
+   * }
+ * + * @param path The path to load the sound from. Note that if you're loading an external sound + * outside the game's assets, you should use {@link FileHandle}; otherwise, just pass down a + * regular string (without {@code assets/} at the beginning). + * @param volume The volume to play the new sound with. + * @return The new sound instance. */ - public static Music playMusic(String path) { - return playMusic(path, 1.0f, true); + public static MASound playSound(String path, float volume) { + return playSound(path, volume, false, null, false); } /** - * Plays new music. (Duh.) + * Plays a new sound effect. + * + *

When you want to play a sound externally, outside the assets folder, you can use a {@link + * FileHandle} like so: * - * @param path The path to play the music from. - * @param volume The volume to play the music at. - * @return The music instance itself. + *

{@code
+   * // Notice how it uses the Paths class provided by Funkin'.
+   * Funkin.playSound(Paths.external("your/path/here").path(), 1, false);
+   * }
+ * + * @param path The path to load the sound from. Note that if you're loading an external sound + * outside the game's assets, you should use {@link FileHandle}; otherwise, just pass down a + * regular string (without {@code assets/} at the beginning). + * @param volume The volume to play the new sound with. + * @param looping Should the new sound loop indefinitely? + * @return The new sound instance. */ - public static Music playMusic(String path, float volume) { - return playMusic(path, volume, true); + public static MASound playSound(String path, float volume, boolean looping) { + return playSound(path, volume, looping, null, false); } /** - * Plays new music. (Duh.) + * Plays a new sound effect. + * + *

When you want to play a sound externally, outside the assets folder, you can use a {@link + * FileHandle} like so: + * + *

{@code
+   * // Notice how it uses the Paths class provided by Funkin'.
+   * // If null is passed down for the group, then the default sound group will be used.
+   * Funkin.playSound(Paths.external("your/path/here").path(), 1, false, null);
+   * }
* - * @param path The path to play the music from. - * @param volume The volume to play the music at. - * @param looped Should the music loop when it is finished playing? - * @return The music instance itself. + * @param path The path to load the sound from. Note that if you're loading an external sound + * outside the game's assets, you should use {@link FileHandle}; otherwise, just pass down a + * regular string (without {@code assets/} at the beginning). + * @param volume The volume to play the new sound with. + * @param looping Should the new sound loop indefinitely? + * @param group The sound group to add the new sound to. If {@code null} is passed down, then the + * default sound group will be used. + * @return The new sound instance. */ - public static Music playMusic(String path, float volume, boolean looped) { - Music music = Gdx.audio.newMusic(Paths.asset(path)); - if (Funkin.music != null && Funkin.music.isPlaying()) { - Funkin.music.stop(); - } - Funkin.music = music; - music.setVolume(volume); - music.setLooping(looped); - music.play(); - return music; + public static MASound playSound(String path, float volume, boolean looping, MAGroup group) { + return playSound(path, volume, looping, group, false); } /** - * Sets Funkin's global volume and applies it automatically to all sounds in the sound pool and - * the current music playing. + * Plays a new sound effect. + * + *

When you want to play a sound externally, outside the assets folder, you can use a {@link + * FileHandle} like so: * - * @param v The new volume to apply globally. - * @return The new global volume. + *

{@code
+   * // Notice how it uses the Paths class provided by Funkin'.
+   * // If null is passed down for the group, then the default sound group will be used.
+   * // For the boolean attribuite "external", you only should make it true for mobile builds,
+   * // otherwise just simply leave it be or make it "false" for other platforms like desktop.
+   * Funkin.playSound(Paths.external("your/path/here").path(), 1, false, null, true);
+   * }
+ * + * @param path The path to load the sound from. Note that if you're loading an external sound + * outside the game's assets, you should use {@link FileHandle}; otherwise, just pass down a + * regular string (without {@code assets/} at the beginning). + * @param volume The volume to play the new sound with. + * @param looping Should the new sound loop indefinitely? + * @param group The sound group to add the new sound to. If {@code null} is passed down, then the + * default sound group will be used. + * @param external Should this sound be loaded externally? (This is only for mobile platforms!) + * @return The new sound instance. */ - public static float setGlobalVolume(float v) { - globalVolume = v; - if (music != null) { - music.setVolume(music.getVolume() * v); - } - for (FunkinSound sound : soundPool.values()) { - sound.setVolume(sound.ID, sound.getVolume(), true); - } - return globalVolume; + public static MASound playSound( + String path, float volume, boolean looping, MAGroup group, boolean external) { + MASound sound = + engine.createSound(path, (short) 0, (group != null) ? group : soundsGroup, external); + sound.setVolume(volume); + sound.setLooping(looping); + sound.play(); + return sound; + } + + /** + * Sets the game master/global volume, which is automatically applied to all current sounds. + * + *

(This is just a helper method for creating a faster version of {@code + * Funkin.getAudioEngine().setMasterVolume(float)}). + * + * @param volume The new master volume to set. + */ + public static void setMasterVolume(float volume) { + engine.setMasterVolume(volume); + masterVolume = volume; } public static boolean keyPressed(int key) { @@ -202,9 +273,7 @@ public static void error(String tag, Object message) { public static void error(String tag, Object message, Throwable throwable) { String msg = - (throwable != null) - ? (message + " | Exception: " + throwable) - : message.toString(); + (throwable != null) ? (message + " | Exception: " + throwable) : message.toString(); outputLog(tag, msg, FunkinLogLevel.ERROR); } @@ -212,14 +281,26 @@ public static FunkinGame getGame() { return game; } - public static float getGlobalVolume() { - return globalVolume; - } - public static Stage getStage() { return game.stage; } + public static MiniAudio getAudioEngine() { + return engine; + } + + public static float getMasterVolume() { + return masterVolume; + } + + public static AssetManager getAssetManager() { + return assetManager; + } + + public static MAGroup getSoundsGroup() { + return soundsGroup; + } + /** * Contains all the global events that get dispatched when something happens in the game. * @@ -241,8 +322,7 @@ public static class Signals { public static final FunkinSignal postGameClose = new FunkinSignal<>(); public static final FunkinSignal windowFocused = new FunkinSignal<>(); public static final FunkinSignal windowUnfocused = new FunkinSignal<>(); - public static final FunkinSignal windowMinimized = - new FunkinSignal<>(); + public static final FunkinSignal windowMinimized = new FunkinSignal<>(); public static final FunkinSignal preSoundPlayed = new FunkinSignal<>(); public static final FunkinSignal postSoundPlayed = new FunkinSignal<>(); diff --git a/core/src/main/java/me/stringfromjava/funkin/FunkinGame.java b/core/src/main/java/me/stringfromjava/funkin/FunkinGame.java index c1df87d..f47890e 100644 --- a/core/src/main/java/me/stringfromjava/funkin/FunkinGame.java +++ b/core/src/main/java/me/stringfromjava/funkin/FunkinGame.java @@ -2,7 +2,6 @@ import com.badlogic.gdx.ApplicationListener; import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.audio.Sound; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.graphics.Pixmap; @@ -22,10 +21,7 @@ import me.stringfromjava.funkin.tween.FunkinTween; import me.stringfromjava.funkin.util.Constants; -import java.util.Set; - import static me.stringfromjava.funkin.util.signal.FunkinSignalData.RenderSignalData; -import static me.stringfromjava.funkin.util.signal.FunkinSignalData.WindowMinimizedSignalData; /** * The game object used for containing the main loop and core elements of Funkin'. @@ -128,7 +124,7 @@ public void resume() {} /** Called when the user regains focus on the game's window. */ public void onWindowFocused() { - Funkin.setGlobalVolume(1); + Funkin.setMasterVolume(1); Funkin.Signals.windowFocused.dispatch(); Funkin.info("Game window has regained focus."); } @@ -138,7 +134,7 @@ public void onWindowUnfocused() { if (isMinimized) { return; } - Funkin.setGlobalVolume(0.008f); + Funkin.setMasterVolume(0.008f); Funkin.Signals.windowUnfocused.dispatch(); Funkin.info("Game window has lost focus."); } @@ -154,8 +150,8 @@ public void onWindowMinimized(boolean iconified) { if (!isMinimized) { return; } - Funkin.setGlobalVolume(0); - Funkin.Signals.windowMinimized.dispatch(new WindowMinimizedSignalData(isMinimized)); + Funkin.setMasterVolume(0); + Funkin.Signals.windowMinimized.dispatch(); Funkin.info("Game window has been minimized."); } @@ -165,7 +161,6 @@ public void dispose() { Funkin.Signals.preGameClose.dispatch(); - // Dispose of the main stage, current screen and batch. Funkin.info("Disposing the screen display..."); Funkin.screen.hide(); Funkin.screen.dispose(); @@ -173,24 +168,13 @@ public void dispose() { batch.dispose(); bgTexture.dispose(); - // Dispose of all sounds and the music (if there is any playing). - Funkin.info("Disposing music..."); + Funkin.info("Disposing all sounds from sound group and music..."); + Funkin.getAudioEngine().dispose(); + Funkin.getSoundsGroup().dispose(); if (Funkin.music != null) { - Funkin.music.stop(); Funkin.music.dispose(); } - Funkin.info("Disposing sound pool..."); - Set soundPoolKeys = Funkin.soundPool.keySet(); - for (long key : soundPoolKeys) { - Sound sound = Funkin.soundPool.get(key); - if (sound == null) { - continue; - } - sound.stop(); - sound.dispose(); - } - Funkin.info("Disposing and shutting down scripts..."); Polyverse.forAllScripts(Script::onDispose); diff --git a/core/src/main/java/me/stringfromjava/funkin/audio/FunkinSound.java b/core/src/main/java/me/stringfromjava/funkin/audio/FunkinSound.java deleted file mode 100644 index 542d839..0000000 --- a/core/src/main/java/me/stringfromjava/funkin/audio/FunkinSound.java +++ /dev/null @@ -1,201 +0,0 @@ -package me.stringfromjava.funkin.audio; - -import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.audio.Sound; -import com.badlogic.gdx.files.FileHandle; -import me.stringfromjava.funkin.Funkin; -import me.stringfromjava.funkin.backend.Paths; - -import static me.stringfromjava.funkin.util.signal.FunkinSignalData.SoundPlayedSignalData; - -/** - * An enhanced version of libGDX's {@link Sound}. - * - *

This is mostly for ensuring that a sound's volume changes when the users' global volume - * changes. - */ -public class FunkinSound implements Sound { - - /** The ID of {@code this} specific sound. */ - public final long ID; - - private final Sound libGdxSound; - private float volume; - private float pitch; - private float pan; - private boolean looping; - private boolean isPaused; - - public FunkinSound(String path) { - this(Paths.asset(path)); - } - - public FunkinSound(FileHandle path) { - libGdxSound = Gdx.audio.newSound(path); - ID = libGdxSound.play(); - volume = 1.0f; - pitch = 1.0f; - pan = 0.0f; - looping = false; - isPaused = false; - libGdxSound.stop(); - Funkin.soundPool.put(ID, this); - } - - @Override - public long play() { - return libGdxSound.play(volume, pitch, pan); - } - - @Override - public long play(float volume) { - return play(volume, 1.0f, 0.0f); - } - - public long play(float volume, float pitch) { - return play(volume, pitch, 0.0f); - } - - @Override - public long play(float volume, float pitch, float pan) { - Funkin.Signals.preSoundPlayed.dispatch(new SoundPlayedSignalData(this)); - this.volume = volume; - this.pitch = pitch; - this.pan = pan; - this.looping = false; - this.isPaused = false; - Funkin.Signals.postSoundPlayed.dispatch(new SoundPlayedSignalData(this)); - return libGdxSound.play(volume, pitch, pan); - } - - @Override - public long loop() { - return loop(1.0f, 1.0f, 0.0f); - } - - @Override - public long loop(float volume) { - return loop(volume, 1.0f, 0.0f); - } - - public long loop(float volume, float pitch) { - return loop(volume, pitch, 0.0f); - } - - @Override - public long loop(float volume, float pitch, float pan) { - Funkin.Signals.preSoundPlayed.dispatch(new SoundPlayedSignalData(this)); - this.volume = volume * Funkin.getGlobalVolume(); - this.pitch = pitch; - this.pan = pan; - this.looping = true; - this.isPaused = false; - Funkin.Signals.postSoundPlayed.dispatch(new SoundPlayedSignalData(this)); - return libGdxSound.loop(volume, pitch, pan); - } - - @Override - public void stop() { - isPaused = false; - libGdxSound.stop(); - } - - @Override - public void pause() { - isPaused = true; - libGdxSound.pause(); - } - - @Override - public void resume() { - isPaused = false; - libGdxSound.resume(); - } - - @Override - public void dispose() { - volume = 1.0f; - pitch = 1.0f; - pan = 0.0f; - looping = false; - isPaused = false; - libGdxSound.dispose(); - Funkin.soundPool.remove(ID); - } - - @Override - public void stop(long soundId) { - isPaused = false; - libGdxSound.stop(); - } - - @Override - public void pause(long soundId) { - isPaused = true; - libGdxSound.pause(soundId); - } - - @Override - public void resume(long soundId) { - isPaused = false; - libGdxSound.resume(); - } - - @Override - public void setLooping(long soundId, boolean looping) { - this.looping = looping; - libGdxSound.setLooping(soundId, looping); - } - - @Override - public void setPitch(long soundId, float pitch) { - this.pitch = pitch; - libGdxSound.setPitch(soundId, pitch); - } - - @Override - public void setVolume(long soundId, float volume) { - setVolume(soundId, volume, true); - } - - /** - * Sets the new volume for {@code this} sound. - * - * @param soundId The sound's unique ID, generated by libGDX. - * @param volume The new volume to use. - * @param useGlobalVolume Should the new volume automatically adapt to Funkin's global volume? - */ - public void setVolume(long soundId, float volume, boolean useGlobalVolume) { - this.volume = useGlobalVolume ? volume * Funkin.getGlobalVolume() : volume; - libGdxSound.setVolume(soundId, this.volume); - } - - public void setPan(long soundId, float pan) { - setPan(soundId, pan, 1.0f); - } - - @Override - public void setPan(long soundId, float pan, float volume) { - libGdxSound.setPan(soundId, pan, volume); - } - - public float getVolume() { - return volume; - } - - public float getPitch() { - return pitch; - } - - public float getPan() { - return pan; - } - - public boolean isLooping() { - return looping; - } - - public boolean isPaused() { - return isPaused; - } -} diff --git a/core/src/main/java/me/stringfromjava/funkin/backend/Paths.java b/core/src/main/java/me/stringfromjava/funkin/backend/Paths.java index f47e1d3..d406bab 100644 --- a/core/src/main/java/me/stringfromjava/funkin/backend/Paths.java +++ b/core/src/main/java/me/stringfromjava/funkin/backend/Paths.java @@ -3,7 +3,7 @@ import com.badlogic.gdx.Gdx; import com.badlogic.gdx.files.FileHandle; -/** Utility class for simplifying asset paths. */ +/** Utility class for simplifying asset paths and libGDX {@link FileHandle}s. */ public final class Paths { public static FileHandle asset(String path) { @@ -14,13 +14,17 @@ public static FileHandle shared(String path) { return asset(String.format("shared/%s", path)); } - public static FileHandle xml(String path) { - return asset(path + ".xml"); + public static FileHandle xmlAsset(String path) { + return asset(String.format("%s.xml", path)); } - public static FileHandle sharedImage(String path) { + public static FileHandle sharedImageAsset(String path) { return shared(String.format("images/%s.png", path)); } + public static FileHandle external(String path) { + return Gdx.files.external(path); + } + private Paths() {} } diff --git a/core/src/main/java/me/stringfromjava/funkin/game/menus/TitleScreen.java b/core/src/main/java/me/stringfromjava/funkin/game/menus/TitleScreen.java index a9e9351..a6b5e86 100644 --- a/core/src/main/java/me/stringfromjava/funkin/game/menus/TitleScreen.java +++ b/core/src/main/java/me/stringfromjava/funkin/game/menus/TitleScreen.java @@ -1,10 +1,8 @@ package me.stringfromjava.funkin.game.menus; -import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input; -import com.badlogic.gdx.graphics.Texture; +import games.rednblack.miniaudio.MASound; import me.stringfromjava.funkin.Funkin; -import me.stringfromjava.funkin.audio.FunkinSound; import me.stringfromjava.funkin.graphics.screen.FunkinScreen; import me.stringfromjava.funkin.backend.Paths; import me.stringfromjava.funkin.graphics.sprite.FunkinSprite; @@ -15,22 +13,22 @@ public class TitleScreen extends FunkinScreen { private FunkinSprite logo; - private FunkinSound tickleFight; private FunkinTween tween; + private MASound tickleFight; @Override public void show() { super.show(); - var t = Paths.sharedImage("noteStrumline"); + var t = Paths.sharedImageAsset("noteStrumline"); var xml = Paths.shared("images/noteStrumline.xml"); logo = new FunkinSprite().loadSparrowFrames(t, xml); logo.addAnimationByPrefix("test", "confirmDown", 24, false); add(logo); - tickleFight = new FunkinSound("shared/sounds/tickleFight.ogg"); - Funkin.playMusic("preload/music/freakyMenu/freakyMenu.ogg", 0.5f); + tickleFight = Funkin.playSound("shared/sounds/tickleFight.ogg"); +// Funkin.playMusic("preload/music/freakyMenu/freakyMenu.ogg", 0.5f); FunkinTweenSettings settings = new FunkinTweenSettings() .addGoal("x", 600) @@ -50,32 +48,32 @@ public void render(float elapsed) { super.render(elapsed); float speed = 500 * elapsed; - if (Gdx.input.isKeyPressed(Input.Keys.W)) { + if (Funkin.keyPressed(Input.Keys.W)) { logo.setY(logo.getY() + speed); } - if (Gdx.input.isKeyPressed(Input.Keys.S)) { + if (Funkin.keyPressed(Input.Keys.S)) { logo.setY(logo.getY() - speed); } - if (Gdx.input.isKeyPressed(Input.Keys.A)) { + if (Funkin.keyPressed(Input.Keys.A)) { logo.setX(logo.getX() - speed); } - if (Gdx.input.isKeyPressed(Input.Keys.D)) { + if (Funkin.keyPressed(Input.Keys.D)) { logo.setX(logo.getX() + speed); } - if (Gdx.input.isKeyJustPressed(Input.Keys.SPACE)) { - logo.playAnimation("test", false); + if (Funkin.keyJustPressed(Input.Keys.SPACE)) { + logo.playAnimation("test", true); } - if (Gdx.input.isKeyJustPressed(Input.Keys.T)) { + if (Funkin.keyJustPressed(Input.Keys.T)) { tween.start(); } - if (Gdx.input.isKeyJustPressed(Input.Keys.R)) { + if (Funkin.keyJustPressed(Input.Keys.R)) { tween.reset(); } - if (Gdx.input.isKeyJustPressed(Input.Keys.Y)) { + if (Funkin.keyJustPressed(Input.Keys.Y)) { if (tween.paused) { tween.resume(); } else { @@ -83,8 +81,8 @@ public void render(float elapsed) { } } - if (Gdx.input.isKeyJustPressed(Input.Keys.Z)) { - tickleFight.play(1.0f); + if (Funkin.keyJustPressed(Input.Keys.Z)) { + tickleFight.play(); } } } diff --git a/core/src/main/java/me/stringfromjava/funkin/graphics/sprite/FunkinSprite.java b/core/src/main/java/me/stringfromjava/funkin/graphics/sprite/FunkinSprite.java index 6562aef..9df4ed0 100644 --- a/core/src/main/java/me/stringfromjava/funkin/graphics/sprite/FunkinSprite.java +++ b/core/src/main/java/me/stringfromjava/funkin/graphics/sprite/FunkinSprite.java @@ -237,7 +237,21 @@ public void playAnimation(String name) { * @param loop Should this animation loop indefinitely? */ public void playAnimation(String name, boolean loop) { - if (!currentAnim.equals(name) || isAnimationFinished()) { + playAnimation(name, loop, true); + } + + /** + * Plays an animation {@code this} sprite has, if it exists. + * + * @param name The name of the animation to play. + * @param loop Should this animation loop indefinitely? + * @param forceRestart Should the animation automatically restart regardless if its playing? + */ + public void playAnimation(String name, boolean loop, boolean forceRestart) { + if (currentAnim.equals(name) && !forceRestart) { + return; + } + if (isAnimationFinished() || forceRestart) { this.currentAnim = name; this.looping = loop; this.stateTime = 0; diff --git a/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTween.java b/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTween.java index eb592e6..c04a63d 100644 --- a/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTween.java +++ b/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTween.java @@ -1,7 +1,7 @@ package me.stringfromjava.funkin.tween; import me.stringfromjava.funkin.Funkin; -import me.stringfromjava.funkin.backend.FunkinReflect; +import me.stringfromjava.funkin.util.FunkinReflectUtil; import me.stringfromjava.funkin.tween.settings.FunkinTweenSettings; import me.stringfromjava.funkin.util.Constants; import org.jetbrains.annotations.NotNull; @@ -170,7 +170,7 @@ public FunkinTween start() { finished = false; // Ensure that the fields provided actually exist on the object and are floating point values. - var allFields = FunkinReflect.getAllFields(object.getClass()); + var allFields = FunkinReflectUtil.getAllFields(object.getClass()); var neededFields = tweenSettings.getGoalFields(); for (Field field : allFields) { try { diff --git a/core/src/main/java/me/stringfromjava/funkin/backend/FunkinReflect.java b/core/src/main/java/me/stringfromjava/funkin/util/FunkinReflectUtil.java similarity index 93% rename from core/src/main/java/me/stringfromjava/funkin/backend/FunkinReflect.java rename to core/src/main/java/me/stringfromjava/funkin/util/FunkinReflectUtil.java index da13cfe..41a64c7 100644 --- a/core/src/main/java/me/stringfromjava/funkin/backend/FunkinReflect.java +++ b/core/src/main/java/me/stringfromjava/funkin/util/FunkinReflectUtil.java @@ -1,7 +1,6 @@ -package me.stringfromjava.funkin.backend; +package me.stringfromjava.funkin.util; import me.stringfromjava.funkin.Funkin; -import me.stringfromjava.funkin.util.Constants; import java.lang.reflect.Field; import java.lang.reflect.Modifier; @@ -13,7 +12,7 @@ * Backend utility class for obtaining and manipulating fields on objects through the usage of Java * reflection. */ -public class FunkinReflect { +public class FunkinReflectUtil { /** * Obtains all fields of a class, including the master types above it all the way to {@link diff --git a/core/src/main/java/me/stringfromjava/funkin/util/signal/FunkinSignalData.java b/core/src/main/java/me/stringfromjava/funkin/util/signal/FunkinSignalData.java index d27c22f..d48cb2c 100644 --- a/core/src/main/java/me/stringfromjava/funkin/util/signal/FunkinSignalData.java +++ b/core/src/main/java/me/stringfromjava/funkin/util/signal/FunkinSignalData.java @@ -1,6 +1,6 @@ package me.stringfromjava.funkin.util.signal; -import me.stringfromjava.funkin.audio.FunkinSound; +import games.rednblack.miniaudio.MASound; import me.stringfromjava.funkin.graphics.screen.FunkinScreen; /** @@ -13,9 +13,7 @@ public record RenderSignalData(float delta) {} public record ScreenSwitchSignalData(FunkinScreen screen) {} - public record SoundPlayedSignalData(FunkinSound sound) {} - - public record WindowMinimizedSignalData(boolean iconified) {} + public record SoundPlayedSignalData(MASound sound) {} private FunkinSignalData() {} } From 25a6012182ced59a12b434cb2f62f0ed4787fb05 Mon Sep 17 00:00:00 2001 From: String Date: Fri, 16 Jan 2026 01:15:07 -0600 Subject: [PATCH 04/17] Too tired to make a real title sob --- android/build.gradle | 4 +- assets/another_test.groovy | 1 - assets/test.groovy | 2 +- .../java/me/stringfromjava/funkin/Funkin.java | 142 ++++++++++++++++-- .../me/stringfromjava/funkin/FunkinGame.java | 79 ++++++---- .../funkin/graphics/screen/FunkinScreen.java | 3 +- .../funkin/graphics/sprite/FunkinSprite.java | 28 +++- .../funkin/tween/FunkinTween.java | 141 +++++++++++------ .../funkin/tween/FunkinTweenManager.java | 50 +++++- .../funkin/util/FunkinReflectUtil.java | 33 ++++ .../funkin/util/FunkinRuntimeUtil.java | 57 +++++++ .../funkin/util/signal/FunkinSignalData.java | 2 + 12 files changed, 442 insertions(+), 100 deletions(-) create mode 100644 core/src/main/java/me/stringfromjava/funkin/util/FunkinRuntimeUtil.java diff --git a/android/build.gradle b/android/build.gradle index 9e17554..5218c0e 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -9,7 +9,7 @@ apply plugin: 'com.android.application' android { namespace "me.stringfromjava.funkin" - compileSdk 35 + compileSdk 36 sourceSets { main { manifest.srcFile 'AndroidManifest.xml' @@ -31,7 +31,7 @@ android { } defaultConfig { applicationId 'me.stringfromjava.funkin' - minSdkVersion 26 + minSdkVersion 34 targetSdkVersion 35 versionCode 1 versionName "1.0" diff --git a/assets/another_test.groovy b/assets/another_test.groovy index 60ccb28..b162bc9 100644 --- a/assets/another_test.groovy +++ b/assets/another_test.groovy @@ -1,6 +1,5 @@ import com.badlogic.gdx.Gdx import com.badlogic.gdx.Input -import com.badlogic.gdx.graphics.Texture import me.stringfromjava.funkin.Funkin import me.stringfromjava.funkin.backend.Paths import me.stringfromjava.funkin.graphics.sprite.FunkinSprite diff --git a/assets/test.groovy b/assets/test.groovy index a046598..452a41d 100644 --- a/assets/test.groovy +++ b/assets/test.groovy @@ -49,7 +49,7 @@ class TestScreen extends FunkinScreen { bgColor = new Color(0, 1, 0, 1) - Funkin.playMusic('songs/guns/Inst.ogg') + Funkin.playMusic('songs/darnell/Inst.ogg') } @Override diff --git a/core/src/main/java/me/stringfromjava/funkin/Funkin.java b/core/src/main/java/me/stringfromjava/funkin/Funkin.java index bf220e7..c91f5e5 100644 --- a/core/src/main/java/me/stringfromjava/funkin/Funkin.java +++ b/core/src/main/java/me/stringfromjava/funkin/Funkin.java @@ -12,6 +12,7 @@ import me.stringfromjava.funkin.util.signal.FunkinSignal; import me.stringfromjava.funkin.util.Constants; import me.stringfromjava.funkin.util.signal.FunkinSignalData.*; +import org.jetbrains.annotations.NotNull; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @@ -24,21 +25,16 @@ */ public final class Funkin { - /** - * The current {@code FunkinScreen} being displayed, stored in a static instance for global - * access. - * - *

Use this instead of {@code Funkin.game.getScreen()} for actually accessing all the custom - * functions and attributes that a regular {@code Screen} doesn't have! - */ - public static FunkinScreen screen = null; + /** The current {@code FunkinScreen} being displayed. */ + private static FunkinScreen screen; /** The main audio object used to create, */ private static MiniAudio engine; + /** The global asset manager used to obtain preloaded assets. */ private static AssetManager assetManager; - /** The audio group for regular small, sound effects. */ + /** The audio group for all sound effects, including the current music. */ private static MAGroup soundsGroup; /** The sound for playing music throughout the game. */ @@ -48,7 +44,7 @@ public final class Funkin { private static float masterVolume = 1; /** - * The static instance used to access the core elements of the game. This includes the loop, + * The static instance used to access the core elements of the game. This includes the main loop, * setting the current screen, and more. */ private static FunkinGame game; @@ -217,15 +213,109 @@ public static MASound playSound(String path, float volume, boolean looping, MAGr * @return The new sound instance. */ public static MASound playSound( - String path, float volume, boolean looping, MAGroup group, boolean external) { + @NotNull String path, float volume, boolean looping, MAGroup group, boolean external) { MASound sound = engine.createSound(path, (short) 0, (group != null) ? group : soundsGroup, external); + Signals.preSoundPlayed.dispatch(new SoundPlayedSignalData(sound)); sound.setVolume(volume); sound.setLooping(looping); sound.play(); + Signals.postSoundPlayed.dispatch(new SoundPlayedSignalData(sound)); return sound; } + /** + * Sets the current music playing for the entire game. + * + *

When you want to play music located externally, outside the assets folder, you can use a + * {@link FileHandle} like so: + * + *

{@code
+   * // Notice how it uses the Paths class provided by Funkin'.
+   * Funkin.playMusic(Paths.external("your/path/here").path());
+   * }
+ * + * @param path The path to load the music from. Note that if you're loading an external sound file + * outside the game's assets, you should use {@link FileHandle}; otherwise, just pass down a + * regular string (without {@code assets/} at the beginning). + */ + public static void playMusic(String path) { + playMusic(path, 1, true, false); + } + + /** + * Sets the current music playing for the entire game. + * + *

When you want to play music located externally, outside the assets folder, you can use a + * {@link FileHandle} like so: + * + *

{@code
+   * // Notice how it uses the Paths class provided by Funkin'.
+   * Funkin.playMusic(Paths.external("your/path/here").path(), 1);
+   * }
+ * + * @param path The path to load the music from. Note that if you're loading an external sound file + * outside the game's assets, you should use {@link FileHandle}; otherwise, just pass down a + * regular string (without {@code assets/} at the beginning). + * @param volume The volume to play the new music with. + */ + public static void playMusic(String path, float volume) { + playMusic(path, volume, true, false); + } + + /** + * Sets the current music playing for the entire game. + * + *

When you want to play music located externally, outside the assets folder, you can use a + * {@link FileHandle} like so: + * + *

{@code
+   * // Notice how it uses the Paths class provided by Funkin'.
+   * Funkin.playMusic(Paths.external("your/path/here").path(), 1, false);
+   * }
+ * + * @param path The path to load the music from. Note that if you're loading an external sound file + * outside the game's assets, you should use {@link FileHandle}; otherwise, just pass down a + * regular string (without {@code assets/} at the beginning). + * @param volume The volume to play the new music with. + * @param looping Should the new music loop indefinitely? + */ + public static void playMusic(String path, float volume, boolean looping) { + playMusic(path, volume, looping, false); + } + + /** + * Sets the current music playing for the entire game. + * + *

When you want to play music located externally, outside the assets folder, you can use a + * {@link FileHandle} like so: + * + *

{@code
+   * // Notice how it uses the Paths class provided by Funkin'.
+   * // For the boolean attribuite "external", you only should make it true for mobile builds,
+   * // otherwise just simply leave it be or make it "false" for other platforms like desktop.
+   * Funkin.playMusic(Paths.external("your/path/here").path(), 1, false, true);
+   * }
+ * + * @param path The path to load the music from. Note that if you're loading an external sound file + * outside the game's assets, you should use {@link FileHandle}; otherwise, just pass down a + * regular string (without {@code assets/} at the beginning). + * @param volume The volume to play the new music with. + * @param looping Should the new music loop indefinitely? + * @param external Should this music be loaded externally? (This is only for mobile platforms!) + */ + public static void playMusic(String path, float volume, boolean looping, boolean external) { + Signals.preMusicPlayed.dispatch(new MusicPlayedSignalData(music)); + if (music != null) { + music.stop(); + } + music = engine.createSound(path, (short) 0, soundsGroup, external); + music.setVolume(volume); + music.setLooping(looping); + music.play(); + Signals.postMusicPlayed.dispatch(new MusicPlayedSignalData(music)); + } + /** * Sets the game master/global volume, which is automatically applied to all current sounds. * @@ -285,6 +375,14 @@ public static Stage getStage() { return game.stage; } + public static FunkinScreen getScreen() { + return screen; + } + + public static MASound getMusic() { + return music; + } + public static MiniAudio getAudioEngine() { return engine; } @@ -311,7 +409,7 @@ public static MAGroup getSoundsGroup() { * same thing. If a signal has {@code pre}, then the signal gets ran BEFORE any functionality is * executed, and {@code post} means AFTER all functionality was executed. */ - public static class Signals { + public static final class Signals { public static final FunkinSignal preRender = new FunkinSignal<>(); public static final FunkinSignal postRender = new FunkinSignal<>(); @@ -325,6 +423,8 @@ public static class Signals { public static final FunkinSignal windowMinimized = new FunkinSignal<>(); public static final FunkinSignal preSoundPlayed = new FunkinSignal<>(); public static final FunkinSignal postSoundPlayed = new FunkinSignal<>(); + public static final FunkinSignal preMusicPlayed = new FunkinSignal<>(); + public static final FunkinSignal postMusicPlayed = new FunkinSignal<>(); private Signals() {} } @@ -334,6 +434,17 @@ private Signals() {} // ====================================== private static void outputLog(String tag, Object message, FunkinLogLevel level) { + StackWalker.StackFrame caller = StackWalker.getInstance() + .walk(s -> s.skip(3).findFirst()) + .orElse(null); + + String file = "UnknownFile.java:0"; + String method = "unknownMethod()"; + if (caller != null) { + file = caller.getFileName() + ":" + caller.getLineNumber(); + method = caller.getMethodName() + "()"; + } + String color = switch (level) { case INFO -> Constants.AsciiCodes.WHITE; @@ -350,7 +461,12 @@ private static void outputLog(String tag, Object message, FunkinLogLevel level) false, underline); String formattedTag = - colorText("[" + tag + "] [" + level + "] ", color, true, false, underline); + colorText( + "[" + level + "] [" + tag + "] [" + file + "] [" + method + "] ", + color, + true, + false, + underline); String formattedMessage = colorText(message.toString(), color, false, true, underline); System.out.println(timeAndDate + formattedTag + formattedMessage); diff --git a/core/src/main/java/me/stringfromjava/funkin/FunkinGame.java b/core/src/main/java/me/stringfromjava/funkin/FunkinGame.java index f47890e..ab8d10d 100644 --- a/core/src/main/java/me/stringfromjava/funkin/FunkinGame.java +++ b/core/src/main/java/me/stringfromjava/funkin/FunkinGame.java @@ -1,5 +1,6 @@ package me.stringfromjava.funkin; +import com.badlogic.gdx.Application; import com.badlogic.gdx.ApplicationListener; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.Color; @@ -20,6 +21,7 @@ import me.stringfromjava.funkin.polyverse.script.type.SystemScript; import me.stringfromjava.funkin.tween.FunkinTween; import me.stringfromjava.funkin.util.Constants; +import me.stringfromjava.funkin.util.FunkinRuntimeUtil; import static me.stringfromjava.funkin.util.signal.FunkinSignalData.RenderSignalData; @@ -51,28 +53,9 @@ public class FunkinGame implements ApplicationListener { @Override public void create() { - // Configure the main view of the game's world and view. - var wWidth = Constants.Display.WINDOW_WIDTH; - var wHeight = Constants.Display.WINDOW_HEIGHT; - - batch = new SpriteBatch(); - viewport = new FitViewport(wWidth, wHeight); - viewport.apply(); - - camera = new OrthographicCamera(); - camera.setToOrtho(false, wWidth, wHeight); - - stage = new Stage(viewport, batch); - - Pixmap pixmap = new Pixmap(1, 1, Pixmap.Format.RGBA8888); - pixmap.setColor(Color.WHITE); - pixmap.fill(); - bgTexture = new Texture(pixmap); - pixmap.dispose(); - - // Configure the Polyverse scripting and modding engine for the game. - configurePolyverse(); - + configureCrashHandler(); // Crash handler for uncaught exceptions. + configureWindow(); // Window and viewport. + configurePolyverse(); // Polyverse scripting and modding system. Funkin.setScreen(new InitScreen()); } @@ -84,7 +67,7 @@ public void resize(int width, int height) { @Override public void render() { float delta = Gdx.graphics.getDeltaTime(); - FunkinScreen screen = Funkin.screen; + FunkinScreen screen = Funkin.getScreen(); Funkin.Signals.preRender.dispatch(new RenderSignalData(delta)); @@ -98,7 +81,8 @@ public void render() { batch.draw(bgTexture, 0, 0, viewport.getWorldWidth(), viewport.getWorldHeight()); batch.setColor(Color.WHITE); // Set color back to white so display objects aren't affected. screen.render(delta); - for (FunkinObject object : screen.members) { + for (int i = screen.members.size - 1; i >= 0; i--) { + FunkinObject object = screen.members.get(i); if (object instanceof FunkinSprite sprite) { sprite.update(delta); sprite.draw(batch); @@ -110,7 +94,7 @@ public void render() { stage.act(delta); stage.draw(); - FunkinTween.globalManager.update(delta); + FunkinTween.getGlobalManager().update(delta); Polyverse.forAllScripts(script -> script.onRender(delta)); Funkin.Signals.postRender.dispatch(new RenderSignalData(delta)); @@ -162,18 +146,18 @@ public void dispose() { Funkin.Signals.preGameClose.dispatch(); Funkin.info("Disposing the screen display..."); - Funkin.screen.hide(); - Funkin.screen.dispose(); + Funkin.getScreen().hide(); + Funkin.getScreen().dispose(); stage.dispose(); batch.dispose(); bgTexture.dispose(); Funkin.info("Disposing all sounds from sound group and music..."); - Funkin.getAudioEngine().dispose(); - Funkin.getSoundsGroup().dispose(); - if (Funkin.music != null) { - Funkin.music.dispose(); + if (Funkin.getMusic() != null) { + Funkin.getMusic().dispose(); } + Funkin.getSoundsGroup().dispose(); + Funkin.getAudioEngine().dispose(); Funkin.info("Disposing and shutting down scripts..."); Polyverse.forAllScripts(Script::onDispose); @@ -185,6 +169,39 @@ public boolean isMinimized() { return isMinimized; } + private void configureCrashHandler() { + Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> { + String logs = FunkinRuntimeUtil.getFullExceptionMessage(throwable); + String msg = "There was an uncaught exception on thread \"" + thread.getName() + "\"!\n\t" + logs; + Funkin.error(Constants.System.LOG_TAG, msg); + dispose(); + // Only use Gdx.app.exit() on non-iOS platforms to avoid App Store guideline violations! + if (Gdx.app.getType() != Application.ApplicationType.iOS) { + Gdx.app.exit(); + } + }); + } + + private void configureWindow() { + var wWidth = Constants.Display.WINDOW_WIDTH; + var wHeight = Constants.Display.WINDOW_HEIGHT; + + batch = new SpriteBatch(); + viewport = new FitViewport(wWidth, wHeight); + viewport.apply(); + + camera = new OrthographicCamera(); + camera.setToOrtho(false, wWidth, wHeight); + + stage = new Stage(viewport, batch); + + Pixmap pixmap = new Pixmap(1, 1, Pixmap.Format.RGBA8888); + pixmap.setColor(Color.WHITE); + pixmap.fill(); + bgTexture = new Texture(pixmap); + pixmap.dispose(); + } + private void configurePolyverse() { // Register Polyverse script types. Polyverse.registerScriptType(Script.class); // Master type, DO NOT REMOVE THIS! diff --git a/core/src/main/java/me/stringfromjava/funkin/graphics/screen/FunkinScreen.java b/core/src/main/java/me/stringfromjava/funkin/graphics/screen/FunkinScreen.java index 64f82f9..39a2aa8 100644 --- a/core/src/main/java/me/stringfromjava/funkin/graphics/screen/FunkinScreen.java +++ b/core/src/main/java/me/stringfromjava/funkin/graphics/screen/FunkinScreen.java @@ -3,6 +3,7 @@ import com.badlogic.gdx.Screen; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.g2d.Sprite; +import com.badlogic.gdx.utils.Array; import me.stringfromjava.funkin.graphics.sprite.FunkinObject; import java.util.concurrent.CopyOnWriteArrayList; @@ -17,7 +18,7 @@ public abstract class FunkinScreen implements Screen { protected Color bgColor; /** All display objects that are shown in {@code this} screen. */ - public final CopyOnWriteArrayList members = new CopyOnWriteArrayList<>(); + public final Array members = new Array<>(); @Override public void show() {} diff --git a/core/src/main/java/me/stringfromjava/funkin/graphics/sprite/FunkinSprite.java b/core/src/main/java/me/stringfromjava/funkin/graphics/sprite/FunkinSprite.java index 9df4ed0..8fd3f70 100644 --- a/core/src/main/java/me/stringfromjava/funkin/graphics/sprite/FunkinSprite.java +++ b/core/src/main/java/me/stringfromjava/funkin/graphics/sprite/FunkinSprite.java @@ -8,6 +8,7 @@ import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.Pool; import com.badlogic.gdx.utils.XmlReader; import java.util.Comparator; @@ -19,7 +20,7 @@ * *

It allows you to load animations, textures, and do much more with simplicity and ease. */ -public class FunkinSprite extends Sprite implements FunkinObject { +public class FunkinSprite extends Sprite implements FunkinObject, Pool.Poolable { /** The texture image that {@code this} sprite uses. */ protected Texture texture; @@ -295,6 +296,31 @@ public boolean isAnimationFinished() { return anim.isAnimationFinished(stateTime); } + @Override + public void reset() { + setPosition(0f, 0f); + stateTime = 0; + currentAnim = null; + looping = true; + texture.dispose(); + texture = null; + currentFrame.getTexture().dispose(); + currentFrame = null; + for (int i = atlasRegions.size; i >= 0; i--) { + var region = atlasRegions.items[i]; + region.getTexture().dispose(); + } + atlasRegions.setSize(0); + atlasRegions = null; + for (int i = frames.length - 1; i >= 0; i--) { + var frame = frames[i]; + for (TextureRegion region : frame) { + region.getTexture().dispose(); + } + } + frames = null; + } + public Map> getAnimations() { return animations; } diff --git a/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTween.java b/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTween.java index c04a63d..65d71a0 100644 --- a/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTween.java +++ b/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTween.java @@ -1,5 +1,6 @@ package me.stringfromjava.funkin.tween; +import com.badlogic.gdx.utils.Pool; import me.stringfromjava.funkin.Funkin; import me.stringfromjava.funkin.util.FunkinReflectUtil; import me.stringfromjava.funkin.tween.settings.FunkinTweenSettings; @@ -7,18 +8,21 @@ import org.jetbrains.annotations.NotNull; import java.lang.reflect.Field; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; /** * Core class for creating new tweens to add nice and smooth animations to visual objects. * *

Note that this doesn't have to be used on sprites, it can be used on just about anything! */ -public class FunkinTween { +public class FunkinTween implements Pool.Poolable { /** The global tween manager for the entire game. */ - public static FunkinTweenManager globalManager = new FunkinTweenManager(); + protected static FunkinTweenManager globalManager = new FunkinTweenManager(); /** The object to tween. */ protected Object object; @@ -59,15 +63,25 @@ public class FunkinTween { protected final Map initialValues = new HashMap<>(); /** + * Cache of the fields being tweened for faster access so they aren't accessed every time the + * {@code start()} method is called. + */ + protected Field[] fieldsCache = null; + + /** + * Constructs a new tween. + * + *

Note that this does NOT add the tween to the global manager, it just assigns its main + * values. That's it. If you wish to create a tween to automatically start working, you might want + * to see {@code tween()} + * * @param object The object to tween values. * @param settings The settings that configure and determine how the tween should animate and last * for. * @param updateCallback Callback function for updating the objects values when the tween updates. */ - public FunkinTween( - Object object, - @NotNull FunkinTweenSettings settings, - FunkinTweenUpdateCallback updateCallback) { + protected FunkinTween( + Object object, FunkinTweenSettings settings, FunkinTweenUpdateCallback updateCallback) { this.object = object; this.tweenSettings = settings; this.updateCallback = updateCallback; @@ -90,6 +104,15 @@ public void update(float delta) { if (paused || finished) { return; } + if (object == null) { + return; + } + if (tweenSettings == null) { + return; + } + if (updateCallback == null) { + return; + } var ease = tweenSettings.getEase(); var duration = tweenSettings.getDuration(); @@ -125,13 +148,6 @@ public void update(float delta) { } } - if (secondsSinceStart > delay && !running) { - running = true; - if (onStart != null) { - onStart.run(this); - } - } - // Update the object's fields based on the tween progress. var newValues = new HashMap(); for (String field : tweenSettings.getGoalFields()) { @@ -147,11 +163,6 @@ public void update(float delta) { if (secondsSinceStart >= duration + delay) { scale = (backward) ? 0 : 1; finished = true; - switch (tweenSettings.getType()) { - case ONESHOT -> { - manager.activeTweens.remove(this); - } - } } else { if (postTick > preTick && onUpdate != null) { onUpdate.run(this); @@ -169,24 +180,43 @@ public FunkinTween start() { running = true; finished = false; - // Ensure that the fields provided actually exist on the object and are floating point values. - var allFields = FunkinReflectUtil.getAllFields(object.getClass()); + if (tweenSettings == null) { + Funkin.warn("FunkinTween", "No tween settings were provided for the tween."); + return this; + } + var neededFields = tweenSettings.getGoalFields(); - for (Field field : allFields) { + if (neededFields == null || neededFields.isEmpty()) { + Funkin.warn("FunkinTween", "No fields were provided to tween on the object."); + return this; + } + if (fieldsCache == null) { + fieldsCache = FunkinReflectUtil.getAllFieldsAsArray(object.getClass()); + } + + // Ensure that the fields provided actually exist on the object and are floating point values. + // If there are fields that the tween is trying to tween that don't exist, then throw an error. + Set fieldIds = Arrays.stream(fieldsCache) + .map(Field::getName) + .collect(Collectors.toSet()); + for (String neededField : neededFields) { + if (!fieldIds.contains(neededField)) { + String message = "Field \"" + neededField + "\" does not exist on the given object."; + Funkin.error("FunkinTween", message); + throw new RuntimeException(message); + } + } + + for (Field field : fieldsCache) { try { String fName = field.getName(); - if (!field.trySetAccessible()) { - continue; - } - if (!neededFields.contains(fName)) { - continue; - } if (field.getType() != float.class) { continue; } initialValues.put(fName, field.getFloat(object)); } catch (IllegalAccessException e) { - Funkin.error(Constants.System.LOG_TAG, "Could not access field \"" + field.getName() + "\".", e); + Funkin.error( + Constants.System.LOG_TAG, "Could not access field \"" + field.getName() + "\".", e); } } return this; @@ -226,31 +256,43 @@ public FunkinTween stop() { } /** - * Cancels {@code this} tween and immediately removes it from the active tweens in its manager. + * Cancels {@code this} tween and automatically defaults its values, removing it from the manager + * by default. * * @return {@code this} tween. */ public FunkinTween cancel() { - reset(); - manager.activeTweens.remove(this); - return this; + return cancel(true); } /** - * Resets {@code this} tween back to its initial state without removing it from its manager. + * Cancels {@code this} tween and automatically defaults its values. * + * @param remove Should {@code this} tween be removed from its manager? * @return {@code this} tween. */ - public FunkinTween reset() { - paused = false; - backward = false; - running = false; - finished = true; + public FunkinTween cancel(boolean remove) { + reset(); + if (remove) { + manager.getTweenPool().free(this); + } + return this; + } + + @Override + public void reset() { + object = null; + tweenSettings = null; + updateCallback = null; + manager = null; scale = 0.0f; secondsSinceStart = 0.0f; executions = 0; + paused = false; + running = false; + finished = false; + backward = false; initialValues.clear(); - return this; } public FunkinTweenSettings getTweenSettings() { @@ -262,10 +304,19 @@ public FunkinTween setTweenSettings(@NotNull FunkinTweenSettings tweenSettings) return this; } - public FunkinTween setManager(FunkinTweenManager manager) { - if (manager != null) { - this.manager = manager; - this.manager.activeTweens.add(this); + public static FunkinTweenManager getGlobalManager() { + return globalManager; + } + + public FunkinTween setManager(FunkinTweenManager newManager) { + if (newManager != null) { + if (manager != null) { + int index = manager.getActiveTweens().indexOf(this, true); + manager.getActiveTweens().removeIndex(index); + manager.getTweenPool().free(this); + } + manager = newManager; + manager.getTweenPool().obtain(); } return this; } @@ -275,8 +326,8 @@ public FunkinTween setManager(FunkinTweenManager manager) { public interface FunkinTweenUpdateCallback { /** - * A callback method that is called when the tween updates its values during its tweening - * (or animating) process. + * A callback method that is called when the tween updates its values during its tweening (or + * animating) process. * * @param values The new current values of the fields being tweened during the animation. */ diff --git a/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTweenManager.java b/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTweenManager.java index 709be1d..feff3fe 100644 --- a/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTweenManager.java +++ b/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTweenManager.java @@ -1,19 +1,59 @@ package me.stringfromjava.funkin.tween; -import java.util.concurrent.CopyOnWriteArrayList; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.Pool; /** Core manager class for handling all {@link FunkinTween}s that are currently active. */ -public final class FunkinTweenManager { +public class FunkinTweenManager { - /** A list where all current active tweens are stored. */ - public final CopyOnWriteArrayList activeTweens = new CopyOnWriteArrayList<>(); + /** Array where all current active tweens are stored. */ + protected final Array activeTweens = new Array<>(); + /** A pool where all tweens are stored to preserve memory. */ + protected final Pool tweenPool = new Pool<>() { + @Override + protected FunkinTween newObject() { + return new FunkinTween(null, null, null); + } + }; + + /** + * Updates all active tweens that are stored and updated in {@code this} manager. + * + * @param delta The amount of time that has passed since the last frame. + */ public void update(float delta) { - for (FunkinTween tween : activeTweens) { + for (int i = activeTweens.size - 1; i >= 0; i--) { + FunkinTween tween = activeTweens.items[i]; if (tween == null) { continue; } tween.update(delta); + + if (tween.finished) { + if (tween.manager != this) { + continue; + } + var settings = tween.getTweenSettings(); + if (settings == null) { + continue; + } + + switch (settings.getType()) { + case ONESHOT -> { + activeTweens.removeIndex(i); + tweenPool.free(tween); + } + } + } } } + + public Array getActiveTweens() { + return activeTweens; + } + + public Pool getTweenPool() { + return tweenPool; + } } diff --git a/core/src/main/java/me/stringfromjava/funkin/util/FunkinReflectUtil.java b/core/src/main/java/me/stringfromjava/funkin/util/FunkinReflectUtil.java index 41a64c7..2fae80d 100644 --- a/core/src/main/java/me/stringfromjava/funkin/util/FunkinReflectUtil.java +++ b/core/src/main/java/me/stringfromjava/funkin/util/FunkinReflectUtil.java @@ -7,6 +7,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; /** * Backend utility class for obtaining and manipulating fields on objects through the usage of Java @@ -14,6 +15,26 @@ */ public class FunkinReflectUtil { + /** + * Checks if a field exists on a given object. + * + * @param target The object to verify the existence of the given field to check. + * @param fieldName The name of the + * @return If the field exists on the given object. + */ + public static boolean hasField(Object target, String fieldName) { + AtomicBoolean exists = new AtomicBoolean(); + getAllFields(target.getClass()) + .forEach( + field -> { + if (field.getName().equals(fieldName)) { + exists.set(true); + } + exists.set(false); + }); + return exists.get(); + } + /** * Obtains all fields of a class, including the master types above it all the way to {@link * Object}. @@ -29,6 +50,18 @@ public static List getAllFields(Class type) { return fields; } + /** + * Obtains all fields of a class, including the master types above it all the way to {@link + * Object}. Obvious to the name, this version returns an array instead of a list unlike its + * similar function . + * + * @param type A class literal to obtain the fields from. + * @return All fields from itself and its master classes above it. + */ + public static Field[] getAllFieldsAsArray(Class type) { + return getAllFields(type).toArray(new Field[0]); + } + /** * Checks if a class of a certain package is final. * diff --git a/core/src/main/java/me/stringfromjava/funkin/util/FunkinRuntimeUtil.java b/core/src/main/java/me/stringfromjava/funkin/util/FunkinRuntimeUtil.java new file mode 100644 index 0000000..84b8b04 --- /dev/null +++ b/core/src/main/java/me/stringfromjava/funkin/util/FunkinRuntimeUtil.java @@ -0,0 +1,57 @@ +package me.stringfromjava.funkin.util; + +/** + * Utility class for handling operation related to the runtime environment, including OS detection, + * extracting runtime information, obtaining information from exceptions, and other related tasks. + */ +public final class FunkinRuntimeUtil { + + /** + * Obtains a string representation of where an exception was thrown from, including the class, + * method, file, and line number. + * + * @param exception The exception to obtain the location from. + * @return A string representation of where the exception was thrown from. + */ + public static String getExceptionLocation(Throwable exception) { + if (exception == null) { + return "Unknown Location"; + } + StackTraceElement[] stackTrace = exception.getStackTrace(); + if (stackTrace.length == 0) { + return "Unknown Location"; + } + StackTraceElement element = stackTrace[0]; + return "FILE=" + + element.getFileName() + + ", CLASS=" + + element.getClassName() + + ", METHOD=" + + element.getMethodName() + + "(), LINE=" + + element.getLineNumber(); + } + + /** + * Obtains a full detailed message from an exception, including its type, location, and stack + * trace. + * + * @param exception The exception to obtain the message from. + * @return A full detailed message from the exception. + */ + public static String getFullExceptionMessage(Throwable exception) { + if (exception == null) { + return "No Exception Provided"; + } + StringBuilder messageBuilder = new StringBuilder(); + messageBuilder.append("Exception: ").append(exception.toString()).append("\n"); + messageBuilder.append("Location: ").append(getExceptionLocation(exception)).append("\n"); + messageBuilder.append("Stack Trace:\n"); + for (StackTraceElement element : exception.getStackTrace()) { + messageBuilder.append("\tat ").append(element.toString()).append("\n"); + } + return messageBuilder.toString(); + } + + private FunkinRuntimeUtil() {} +} diff --git a/core/src/main/java/me/stringfromjava/funkin/util/signal/FunkinSignalData.java b/core/src/main/java/me/stringfromjava/funkin/util/signal/FunkinSignalData.java index d48cb2c..8bb6fef 100644 --- a/core/src/main/java/me/stringfromjava/funkin/util/signal/FunkinSignalData.java +++ b/core/src/main/java/me/stringfromjava/funkin/util/signal/FunkinSignalData.java @@ -15,6 +15,8 @@ public record ScreenSwitchSignalData(FunkinScreen screen) {} public record SoundPlayedSignalData(MASound sound) {} + public record MusicPlayedSignalData(MASound music) {} + private FunkinSignalData() {} } From 5d257b978818a73600529e567b0e36324d121a8f Mon Sep 17 00:00:00 2001 From: String Date: Sun, 18 Jan 2026 01:42:10 -0600 Subject: [PATCH 05/17] Fix offset bugs, add multiple optimizations and add fullscreen --- .../me/stringfromjava/funkin/FunkinGame.java | 47 ++++++++++++----- .../funkin/game/menus/TitleScreen.java | 4 +- .../funkin/graphics/screen/FunkinScreen.java | 7 +-- .../funkin/graphics/sprite/FunkinSprite.java | 50 +++++++++++------- .../funkin/polyverse/Polyverse.java | 51 ++++++++----------- .../funkin/tween/FunkinTween.java | 43 ++++++++++++---- .../funkin/tween/FunkinTweenManager.java | 9 ++-- .../tween/settings/FunkinTweenSettings.java | 5 ++ 8 files changed, 138 insertions(+), 78 deletions(-) diff --git a/core/src/main/java/me/stringfromjava/funkin/FunkinGame.java b/core/src/main/java/me/stringfromjava/funkin/FunkinGame.java index ab8d10d..fa198c4 100644 --- a/core/src/main/java/me/stringfromjava/funkin/FunkinGame.java +++ b/core/src/main/java/me/stringfromjava/funkin/FunkinGame.java @@ -3,6 +3,7 @@ import com.badlogic.gdx.Application; import com.badlogic.gdx.ApplicationListener; import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Input; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.graphics.Pixmap; @@ -71,26 +72,32 @@ public void render() { Funkin.Signals.preRender.dispatch(new RenderSignalData(delta)); + if (Funkin.keyJustPressed(Input.Keys.F11)) { + toggleFullscreen(); + } + // Update and render the current screen that's active. ScreenUtils.clear(Color.BLACK); viewport.apply(); batch.setProjectionMatrix(camera.combined); batch.begin(); + if (screen != null) { batch.setColor(screen.getBgColor()); batch.draw(bgTexture, 0, 0, viewport.getWorldWidth(), viewport.getWorldHeight()); batch.setColor(Color.WHITE); // Set color back to white so display objects aren't affected. screen.render(delta); - for (int i = screen.members.size - 1; i >= 0; i--) { - FunkinObject object = screen.members.get(i); + var members = screen.members.begin(); + for (FunkinObject object : members) { if (object instanceof FunkinSprite sprite) { sprite.update(delta); sprite.draw(batch); } } + screen.members.end(); } - batch.end(); + batch.end(); stage.act(delta); stage.draw(); @@ -139,6 +146,18 @@ public void onWindowMinimized(boolean iconified) { Funkin.info("Game window has been minimized."); } + /** Toggles fullscreen mode on or off, depending on the current state. */ + public void toggleFullscreen() { + boolean isFullscreen = Gdx.graphics.isFullscreen(); + if (isFullscreen) { + Gdx.graphics.setWindowedMode(Constants.Display.WINDOW_WIDTH, Constants.Display.WINDOW_HEIGHT); + Funkin.info("Exiting fullscreen mode."); + } else { + Gdx.graphics.setFullscreenMode(Gdx.graphics.getDisplayMode()); + Funkin.info("Entering fullscreen mode."); + } + } + @Override public void dispose() { Funkin.warn("SHUTTING DOWN GAME AND DISPOSING ALL RESOURCES."); @@ -170,16 +189,18 @@ public boolean isMinimized() { } private void configureCrashHandler() { - Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> { - String logs = FunkinRuntimeUtil.getFullExceptionMessage(throwable); - String msg = "There was an uncaught exception on thread \"" + thread.getName() + "\"!\n\t" + logs; - Funkin.error(Constants.System.LOG_TAG, msg); - dispose(); - // Only use Gdx.app.exit() on non-iOS platforms to avoid App Store guideline violations! - if (Gdx.app.getType() != Application.ApplicationType.iOS) { - Gdx.app.exit(); - } - }); + Thread.setDefaultUncaughtExceptionHandler( + (thread, throwable) -> { + String logs = FunkinRuntimeUtil.getFullExceptionMessage(throwable); + String msg = + "There was an uncaught exception on thread \"" + thread.getName() + "\"!\n" + logs; + Funkin.error(Constants.System.LOG_TAG, msg); + dispose(); + // Only use Gdx.app.exit() on non-iOS platforms to avoid App Store guideline violations! + if (Gdx.app.getType() != Application.ApplicationType.iOS) { + Gdx.app.exit(); + } + }); } private void configureWindow() { diff --git a/core/src/main/java/me/stringfromjava/funkin/game/menus/TitleScreen.java b/core/src/main/java/me/stringfromjava/funkin/game/menus/TitleScreen.java index a6b5e86..b2cb975 100644 --- a/core/src/main/java/me/stringfromjava/funkin/game/menus/TitleScreen.java +++ b/core/src/main/java/me/stringfromjava/funkin/game/menus/TitleScreen.java @@ -9,6 +9,7 @@ import me.stringfromjava.funkin.tween.FunkinTween; import me.stringfromjava.funkin.tween.FunkinEase; import me.stringfromjava.funkin.tween.settings.FunkinTweenSettings; +import me.stringfromjava.funkin.tween.settings.FunkinTweenType; public class TitleScreen extends FunkinScreen { @@ -35,7 +36,8 @@ public void show() { .addGoal("y", 40) .addGoal("rotation", 180) .setDuration(0.7f) - .setEase(FunkinEase::circInOut); + .setEase(FunkinEase::circInOut) + .setType(FunkinTweenType.PERSIST); tween = FunkinTween.tween(logo, settings, values -> { logo.setX(values.get("x")); logo.setY(values.get("y")); diff --git a/core/src/main/java/me/stringfromjava/funkin/graphics/screen/FunkinScreen.java b/core/src/main/java/me/stringfromjava/funkin/graphics/screen/FunkinScreen.java index 39a2aa8..69d390f 100644 --- a/core/src/main/java/me/stringfromjava/funkin/graphics/screen/FunkinScreen.java +++ b/core/src/main/java/me/stringfromjava/funkin/graphics/screen/FunkinScreen.java @@ -2,12 +2,9 @@ import com.badlogic.gdx.Screen; import com.badlogic.gdx.graphics.Color; -import com.badlogic.gdx.graphics.g2d.Sprite; -import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.SnapshotArray; import me.stringfromjava.funkin.graphics.sprite.FunkinObject; -import java.util.concurrent.CopyOnWriteArrayList; - /** * Base class for creating a better screen display with more functionality than the default {@link * com.badlogic.gdx.Screen} interface. @@ -18,7 +15,7 @@ public abstract class FunkinScreen implements Screen { protected Color bgColor; /** All display objects that are shown in {@code this} screen. */ - public final Array members = new Array<>(); + public final SnapshotArray members = new SnapshotArray<>(FunkinObject.class); @Override public void show() {} diff --git a/core/src/main/java/me/stringfromjava/funkin/graphics/sprite/FunkinSprite.java b/core/src/main/java/me/stringfromjava/funkin/graphics/sprite/FunkinSprite.java index 8fd3f70..0811048 100644 --- a/core/src/main/java/me/stringfromjava/funkin/graphics/sprite/FunkinSprite.java +++ b/core/src/main/java/me/stringfromjava/funkin/graphics/sprite/FunkinSprite.java @@ -7,6 +7,7 @@ import com.badlogic.gdx.graphics.g2d.Sprite; import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Pool; import com.badlogic.gdx.utils.XmlReader; @@ -25,6 +26,11 @@ public class FunkinSprite extends Sprite implements FunkinObject, Pool.Poolable /** The texture image that {@code this} sprite uses. */ protected Texture texture; + /** + * The hitbox used for collision detection and angling. + */ + protected Rectangle hitbox; + /** The atlas regions used in this sprite (used for animations). */ protected Array atlasRegions; @@ -166,6 +172,15 @@ public FunkinSprite loadSparrowFrames(Texture texture, XmlReader.Element xmlFile atlasRegions.add(region); } + if (atlasRegions.size > 0) { + currentFrame = atlasRegions.first(); + setRegion(currentFrame); + + // This ensures the Sprite's internal width/height matches + // the frame before the first update(). + setSize(currentFrame.getRegionWidth(), currentFrame.getRegionHeight()); + } + // Set the default visual. setRegion(atlasRegions.first()); setSize(getRegionWidth(), getRegionHeight()); @@ -261,33 +276,34 @@ public void playAnimation(String name, boolean loop, boolean forceRestart) { @Override public void draw(Batch batch) { - if (currentFrame != null) { - // We use the currentFrame's offsets to adjust the drawing position. - // This prevents the "shaking" or "jittering" in sparrow animations. - float drawX = getX() + currentFrame.offsetX; - - // libGDX coordinate system is bottom-left, but Sparrow is top-down. - // This math aligns the frame within its "original" box. - float drawY = + if (currentFrame == null) { + super.draw(batch); + return; + } + + // Calculate the center based on the ORIGINAL frame size. + // This ensures the rotation point stays consistent across frames. + float originX = currentFrame.originalWidth / 2f; + float originY = currentFrame.originalHeight / 2f; + + // Adjust drawing position by the Sparrow offsets + float drawX = getX() + currentFrame.offsetX; + float drawY = getY() - + (currentFrame.originalHeight - - currentFrame.getRegionHeight() - - currentFrame.offsetY); + + (currentFrame.originalHeight - currentFrame.getRegionHeight() - currentFrame.offsetY); - batch.draw( + batch.draw( currentFrame, drawX, drawY, - getOriginX(), - getOriginY(), + originX - currentFrame.offsetX, + originY + - (currentFrame.originalHeight - currentFrame.getRegionHeight() - currentFrame.offsetY), currentFrame.getRegionWidth(), currentFrame.getRegionHeight(), getScaleX(), getScaleY(), getRotation()); - } else { - super.draw(batch); // Fallback to standard sprite drawing. - } } public boolean isAnimationFinished() { diff --git a/core/src/main/java/me/stringfromjava/funkin/polyverse/Polyverse.java b/core/src/main/java/me/stringfromjava/funkin/polyverse/Polyverse.java index b1f7007..50a98e6 100644 --- a/core/src/main/java/me/stringfromjava/funkin/polyverse/Polyverse.java +++ b/core/src/main/java/me/stringfromjava/funkin/polyverse/Polyverse.java @@ -46,7 +46,8 @@ public static List getScripts(Class type) { /** * Registers a new script instance into the Polyverse system, auto-detecting its type accordingly. - * Note that this automatically calls the script's {@link Script#onCreate()} method after registration. + * Note that this automatically calls the script's {@link Script#onCreate()} method after + * registration. * * @param handle The {@link FileHandle} that holds where the script is located. */ @@ -103,43 +104,35 @@ public static void registerScript(FileHandle handle) { * @param action The action to perform on each script. */ public static void forEachScript(Class type, Consumer action) { - List scriptList = getScripts(type); - - // Use a standard for-loop to prevent ConcurrentModificationException - // and ensure we are iterating over the current snapshot of scripts. - for (int i = 0; i < scriptList.size(); i++) { - T script = scriptList.get(i); - if (script != null) { - try { - action.accept(script); - } catch (Exception e) { - Funkin.error("Polyverse", "Error in " + script.getClass().getSimpleName(), e); - } - } - } + executeScriptList(getScripts(type), action); } /** - * Executes an action for all scripts of all types, with error handling. - * Note that this function is NOT recommended to be used frequently due to how generic it is. It's - * mainly intended for cleanup and disposal tasks. If you know the specific type of scripts you - * want to operate on, use {@link #forEachScript(Class, Consumer)} instead. + * Executes an action for all scripts of all types, with error handling. Note that this function + * is NOT recommended to be used frequently due to how generic it is. It's mainly intended for + * cleanup and disposal tasks. If you know the specific type of scripts you want to operate on, + * use {@link #forEachScript(Class, Consumer)} instead. * * @param action The action to perform on each script. */ public static void forAllScripts(Consumer action) { for (Class type : scripts.keySet()) { + executeScriptList(scripts.get(type), action); + } + } + + private static void executeScriptList( + List scriptList, Consumer action) { + // Use a standard for-loop to prevent ConcurrentModificationException + // and ensure we are iterating over the current snapshot of scripts. + for (int i = 0; i < scriptList.size(); i++) { @SuppressWarnings("unchecked") - List scriptList = (List) scripts.get(type); - - for (int i = 0; i < scriptList.size(); i++) { - T script = scriptList.get(i); - if (script != null) { - try { - action.accept(script); - } catch (Exception e) { - Funkin.error("Polyverse", "Error in " + script.getClass().getSimpleName(), e); - } + T script = (T) scriptList.get(i); + if (script != null) { + try { + action.accept(script); + } catch (Exception e) { + Funkin.error("Polyverse", "Error in " + script.getClass().getSimpleName(), e); } } } diff --git a/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTween.java b/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTween.java index 65d71a0..ca533d4 100644 --- a/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTween.java +++ b/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTween.java @@ -68,6 +68,9 @@ public class FunkinTween implements Pool.Poolable { */ protected Field[] fieldsCache = null; + /** Default constructor for pooling purposes. */ + protected FunkinTween() {} + /** * Constructs a new tween. * @@ -176,7 +179,7 @@ public void update(float delta) { * @return {@code this} tween. */ public FunkinTween start() { - reset(); + resetBasic(); running = true; finished = false; @@ -184,21 +187,20 @@ public FunkinTween start() { Funkin.warn("FunkinTween", "No tween settings were provided for the tween."); return this; } - var neededFields = tweenSettings.getGoalFields(); if (neededFields == null || neededFields.isEmpty()) { Funkin.warn("FunkinTween", "No fields were provided to tween on the object."); return this; } + if (fieldsCache == null) { fieldsCache = FunkinReflectUtil.getAllFieldsAsArray(object.getClass()); } // Ensure that the fields provided actually exist on the object and are floating point values. // If there are fields that the tween is trying to tween that don't exist, then throw an error. - Set fieldIds = Arrays.stream(fieldsCache) - .map(Field::getName) - .collect(Collectors.toSet()); + Set fieldIds = + Arrays.stream(fieldsCache).map(Field::getName).collect(Collectors.toSet()); for (String neededField : neededFields) { if (!fieldIds.contains(neededField)) { String message = "Field \"" + neededField + "\" does not exist on the given object."; @@ -210,6 +212,12 @@ public FunkinTween start() { for (Field field : fieldsCache) { try { String fName = field.getName(); + if (!field.trySetAccessible()) { + continue; + } + if (!neededFields.contains(fName)) { + continue; + } if (field.getType() != float.class) { continue; } @@ -281,10 +289,26 @@ public FunkinTween cancel(boolean remove) { @Override public void reset() { - object = null; - tweenSettings = null; - updateCallback = null; +// object = null; +// tweenSettings = null; +// updateCallback = null; manager = null; + scale = 0.0f; + secondsSinceStart = 0.0f; + executions = 0; + paused = false; + running = false; + finished = false; + backward = false; + fieldsCache = null; + initialValues.clear(); + } + + /** + * Resets only the basic values of {@code this} tween without removing any references to the + * object, its settings or its callback function. + */ + public void resetBasic() { scale = 0.0f; secondsSinceStart = 0.0f; executions = 0; @@ -316,7 +340,8 @@ public FunkinTween setManager(FunkinTweenManager newManager) { manager.getTweenPool().free(this); } manager = newManager; - manager.getTweenPool().obtain(); +// manager.getTweenPool().obtain(); + manager.getActiveTweens().add(this); } return this; } diff --git a/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTweenManager.java b/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTweenManager.java index feff3fe..a6c20e5 100644 --- a/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTweenManager.java +++ b/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTweenManager.java @@ -7,13 +7,13 @@ public class FunkinTweenManager { /** Array where all current active tweens are stored. */ - protected final Array activeTweens = new Array<>(); + protected final Array activeTweens = new Array<>(FunkinTween.class); - /** A pool where all tweens are stored to preserve memory. */ + /** A pool where all unused tweens are stored to preserve memory. */ protected final Pool tweenPool = new Pool<>() { @Override protected FunkinTween newObject() { - return new FunkinTween(null, null, null); + return new FunkinTween(); } }; @@ -24,7 +24,7 @@ protected FunkinTween newObject() { */ public void update(float delta) { for (int i = activeTweens.size - 1; i >= 0; i--) { - FunkinTween tween = activeTweens.items[i]; + var tween = activeTweens.items[i]; if (tween == null) { continue; } @@ -44,6 +44,7 @@ public void update(float delta) { activeTweens.removeIndex(i); tweenPool.free(tween); } + case PERSIST -> {} // Do nothing, let it be. } } } diff --git a/core/src/main/java/me/stringfromjava/funkin/tween/settings/FunkinTweenSettings.java b/core/src/main/java/me/stringfromjava/funkin/tween/settings/FunkinTweenSettings.java index 2f2ccaf..554cd2a 100644 --- a/core/src/main/java/me/stringfromjava/funkin/tween/settings/FunkinTweenSettings.java +++ b/core/src/main/java/me/stringfromjava/funkin/tween/settings/FunkinTweenSettings.java @@ -159,6 +159,11 @@ public FunkinTweenSettings setFramerate(float framerate) { return this; } + public FunkinTweenSettings setType(@NotNull FunkinTweenType type) { + this.type = type; + return this; + } + /** * A record containing basic info for a tween goal (aka a field to tween a numeric value to). * From b9c49679cb76dca44f233b6f2d7523bb691f7095 Mon Sep 17 00:00:00 2001 From: String Date: Sun, 18 Jan 2026 19:28:26 -0600 Subject: [PATCH 06/17] Fix many null errors with tweening --- .../funkin/tween/FunkinTween.java | 34 ++++++++++--------- .../funkin/util/FunkinRuntimeUtil.java | 4 +-- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTween.java b/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTween.java index ca533d4..01db10b 100644 --- a/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTween.java +++ b/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTween.java @@ -64,7 +64,7 @@ public class FunkinTween implements Pool.Poolable { /** * Cache of the fields being tweened for faster access so they aren't accessed every time the - * {@code start()} method is called. + * {@link #start()} method is called. */ protected Field[] fieldsCache = null; @@ -76,7 +76,8 @@ protected FunkinTween() {} * *

Note that this does NOT add the tween to the global manager, it just assigns its main * values. That's it. If you wish to create a tween to automatically start working, you might want - * to see {@code tween()} + * to see {@link FunkinTween#tween(Object object, FunkinTweenSettings tweenSettings, + * FunkinTweenUpdateCallback updateCallback)}. * * @param object The object to tween values. * @param settings The settings that configure and determine how the tween should animate and last @@ -153,14 +154,26 @@ public void update(float delta) { // Update the object's fields based on the tween progress. var newValues = new HashMap(); - for (String field : tweenSettings.getGoalFields()) { + var goals = tweenSettings.getGoalFields(); + for (String field : goals) { FunkinTweenSettings.FunkinTweenGoal goal = tweenSettings.getGoal(field); + if (goal == null) { + continue; + } + if (initialValues.isEmpty()) { + continue; + } + if (!initialValues.containsKey(field)) { + continue; + } float startValue = initialValues.get(field); float goalValue = goal.value(); float newValue = startValue + (goalValue - startValue) * scale; newValues.put(field, newValue); } - updateCallback.update(newValues); + if (!newValues.isEmpty() && initialValues.keySet().containsAll(newValues.keySet())) { + updateCallback.update(newValues); + } // Check if the tween has finished. if (secondsSinceStart >= duration + delay) { @@ -289,19 +302,9 @@ public FunkinTween cancel(boolean remove) { @Override public void reset() { -// object = null; -// tweenSettings = null; -// updateCallback = null; + resetBasic(); manager = null; - scale = 0.0f; - secondsSinceStart = 0.0f; - executions = 0; - paused = false; - running = false; - finished = false; - backward = false; fieldsCache = null; - initialValues.clear(); } /** @@ -340,7 +343,6 @@ public FunkinTween setManager(FunkinTweenManager newManager) { manager.getTweenPool().free(this); } manager = newManager; -// manager.getTweenPool().obtain(); manager.getActiveTweens().add(this); } return this; diff --git a/core/src/main/java/me/stringfromjava/funkin/util/FunkinRuntimeUtil.java b/core/src/main/java/me/stringfromjava/funkin/util/FunkinRuntimeUtil.java index 84b8b04..f5b4572 100644 --- a/core/src/main/java/me/stringfromjava/funkin/util/FunkinRuntimeUtil.java +++ b/core/src/main/java/me/stringfromjava/funkin/util/FunkinRuntimeUtil.java @@ -41,10 +41,10 @@ public static String getExceptionLocation(Throwable exception) { */ public static String getFullExceptionMessage(Throwable exception) { if (exception == null) { - return "No Exception Provided"; + return "No exception provided."; } StringBuilder messageBuilder = new StringBuilder(); - messageBuilder.append("Exception: ").append(exception.toString()).append("\n"); + messageBuilder.append("Exception: ").append(exception).append("\n"); messageBuilder.append("Location: ").append(getExceptionLocation(exception)).append("\n"); messageBuilder.append("Stack Trace:\n"); for (StackTraceElement element : exception.getStackTrace()) { From 9b76019e0f53a466bad19705b13ad0160161327d Mon Sep 17 00:00:00 2001 From: String Date: Tue, 20 Jan 2026 05:59:29 -0600 Subject: [PATCH 07/17] Reformat files to match new rules added --- .editorconfig | 12 +- .../java/me/stringfromjava/funkin/Funkin.java | 128 ++++++++---------- .../me/stringfromjava/funkin/FunkinGame.java | 26 ++-- .../funkin/game/menus/TitleScreen.java | 4 +- .../funkin/graphics/sprite/FunkinSprite.java | 68 +++++----- .../funkin/polyverse/Polyverse.java | 16 +-- .../funkin/tween/FunkinEase.java | 49 +++---- .../funkin/tween/FunkinTween.java | 24 ++-- .../stringfromjava/funkin/util/Constants.java | 2 - .../funkin/util/FunkinReflectUtil.java | 19 ++- .../funkin/util/FunkinRuntimeUtil.java | 14 +- .../funkin/util/signal/FunkinSignalData.java | 20 ++- 12 files changed, 188 insertions(+), 194 deletions(-) diff --git a/.editorconfig b/.editorconfig index c13f780..def4063 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,17 +12,21 @@ trim_trailing_whitespace = true # 2-space indentation (Google Style Guide Section 4.2) indent_style = space -# Limit line length to 100 characters (Section 4.4) -max_line_length = 100 +# Even though Google Style Guide Section 2.3 recommends 100 characters, +# many modern IDEs default to 120 characters, so we follow that convention. +max_line_length = 120 -# Standard Java formatting rules +# Standard Java formatting rules. ij_java_use_single_class_imports = true ij_java_insert_inner_class_imports = false ij_java_class_count_to_use_import_on_demand = 999 ij_java_names_count_to_use_import_on_demand = 999 ij_java_packages_to_use_import_on_demand = unset -ij_java_blank_lines_before_class_end = 1 +ij_java_blank_lines_before_class_end = 0 ij_java_blank_lines_after_class_header = 1 +ij_java_doc_align_param_comments = true +ij_java_doc_do_not_wrap_if_one_line = true +ij_any_keep_simple_blocks_in_one_line = true # Block formatting (Section 4.1.2) ij_java_class_brace_style = end_of_line diff --git a/core/src/main/java/me/stringfromjava/funkin/Funkin.java b/core/src/main/java/me/stringfromjava/funkin/Funkin.java index c91f5e5..f722901 100644 --- a/core/src/main/java/me/stringfromjava/funkin/Funkin.java +++ b/core/src/main/java/me/stringfromjava/funkin/Funkin.java @@ -9,9 +9,12 @@ import games.rednblack.miniaudio.MiniAudio; import games.rednblack.miniaudio.loader.MASoundLoader; import me.stringfromjava.funkin.graphics.screen.FunkinScreen; -import me.stringfromjava.funkin.util.signal.FunkinSignal; import me.stringfromjava.funkin.util.Constants; -import me.stringfromjava.funkin.util.signal.FunkinSignalData.*; +import me.stringfromjava.funkin.util.signal.FunkinSignal; +import me.stringfromjava.funkin.util.signal.FunkinSignalData.MusicPlayedSignalData; +import me.stringfromjava.funkin.util.signal.FunkinSignalData.RenderSignalData; +import me.stringfromjava.funkin.util.signal.FunkinSignalData.ScreenSwitchSignalData; +import me.stringfromjava.funkin.util.signal.FunkinSignalData.SoundPlayedSignalData; import org.jetbrains.annotations.NotNull; import java.time.LocalDateTime; @@ -71,8 +74,7 @@ public static void initialize(FunkinGame gameInstance) { // Set up the game's global audio system. engine = new MiniAudio(); soundsGroup = engine.createGroup(); - assetManager.setLoader( - MASound.class, new MASoundLoader(engine, assetManager.getFileHandleResolver())); + assetManager.setLoader(MASound.class, new MASoundLoader(engine, assetManager.getFileHandleResolver())); initialized = true; } @@ -112,8 +114,8 @@ public static void setScreen(FunkinScreen newScreen) { * } * * @param path The path to load the sound from. Note that if you're loading an external sound - * outside the game's assets, you should use {@link FileHandle}; otherwise, just pass down a - * regular string (without {@code assets/} at the beginning). + * outside the game's assets, you should use {@link FileHandle}; otherwise, just pass down a + * regular string (without {@code assets/} at the beginning). * @return The new sound instance. */ public static MASound playSound(String path) { @@ -131,9 +133,9 @@ public static MASound playSound(String path) { * Funkin.playSound(Paths.external("your/path/here").path(), 1); * } * - * @param path The path to load the sound from. Note that if you're loading an external sound - * outside the game's assets, you should use {@link FileHandle}; otherwise, just pass down a - * regular string (without {@code assets/} at the beginning). + * @param path The path to load the sound from. Note that if you're loading an external sound + * outside the game's assets, you should use {@link FileHandle}; otherwise, just pass down a + * regular string (without {@code assets/} at the beginning). * @param volume The volume to play the new sound with. * @return The new sound instance. */ @@ -152,10 +154,10 @@ public static MASound playSound(String path, float volume) { * Funkin.playSound(Paths.external("your/path/here").path(), 1, false); * } * - * @param path The path to load the sound from. Note that if you're loading an external sound - * outside the game's assets, you should use {@link FileHandle}; otherwise, just pass down a - * regular string (without {@code assets/} at the beginning). - * @param volume The volume to play the new sound with. + * @param path The path to load the sound from. Note that if you're loading an external sound + * outside the game's assets, you should use {@link FileHandle}; otherwise, just pass down a + * regular string (without {@code assets/} at the beginning). + * @param volume The volume to play the new sound with. * @param looping Should the new sound loop indefinitely? * @return The new sound instance. */ @@ -175,13 +177,13 @@ public static MASound playSound(String path, float volume, boolean looping) { * Funkin.playSound(Paths.external("your/path/here").path(), 1, false, null); * } * - * @param path The path to load the sound from. Note that if you're loading an external sound - * outside the game's assets, you should use {@link FileHandle}; otherwise, just pass down a - * regular string (without {@code assets/} at the beginning). - * @param volume The volume to play the new sound with. + * @param path The path to load the sound from. Note that if you're loading an external sound + * outside the game's assets, you should use {@link FileHandle}; otherwise, just pass down a + * regular string (without {@code assets/} at the beginning). + * @param volume The volume to play the new sound with. * @param looping Should the new sound loop indefinitely? - * @param group The sound group to add the new sound to. If {@code null} is passed down, then the - * default sound group will be used. + * @param group The sound group to add the new sound to. If {@code null} is passed down, then the + * default sound group will be used. * @return The new sound instance. */ public static MASound playSound(String path, float volume, boolean looping, MAGroup group) { @@ -202,20 +204,18 @@ public static MASound playSound(String path, float volume, boolean looping, MAGr * Funkin.playSound(Paths.external("your/path/here").path(), 1, false, null, true); * } * - * @param path The path to load the sound from. Note that if you're loading an external sound - * outside the game's assets, you should use {@link FileHandle}; otherwise, just pass down a - * regular string (without {@code assets/} at the beginning). - * @param volume The volume to play the new sound with. - * @param looping Should the new sound loop indefinitely? - * @param group The sound group to add the new sound to. If {@code null} is passed down, then the - * default sound group will be used. + * @param path The path to load the sound from. Note that if you're loading an external sound + * outside the game's assets, you should use {@link FileHandle}; otherwise, just pass down a + * regular string (without {@code assets/} at the beginning). + * @param volume The volume to play the new sound with. + * @param looping Should the new sound loop indefinitely? + * @param group The sound group to add the new sound to. If {@code null} is passed down, then the + * default sound group will be used. * @param external Should this sound be loaded externally? (This is only for mobile platforms!) * @return The new sound instance. */ - public static MASound playSound( - @NotNull String path, float volume, boolean looping, MAGroup group, boolean external) { - MASound sound = - engine.createSound(path, (short) 0, (group != null) ? group : soundsGroup, external); + public static MASound playSound(@NotNull String path, float volume, boolean looping, MAGroup group, boolean external) { + MASound sound = engine.createSound(path, (short) 0, (group != null) ? group : soundsGroup, external); Signals.preSoundPlayed.dispatch(new SoundPlayedSignalData(sound)); sound.setVolume(volume); sound.setLooping(looping); @@ -236,8 +236,8 @@ public static MASound playSound( * } * * @param path The path to load the music from. Note that if you're loading an external sound file - * outside the game's assets, you should use {@link FileHandle}; otherwise, just pass down a - * regular string (without {@code assets/} at the beginning). + * outside the game's assets, you should use {@link FileHandle}; otherwise, just pass down a + * regular string (without {@code assets/} at the beginning). */ public static void playMusic(String path) { playMusic(path, 1, true, false); @@ -254,9 +254,9 @@ public static void playMusic(String path) { * Funkin.playMusic(Paths.external("your/path/here").path(), 1); * } * - * @param path The path to load the music from. Note that if you're loading an external sound file - * outside the game's assets, you should use {@link FileHandle}; otherwise, just pass down a - * regular string (without {@code assets/} at the beginning). + * @param path The path to load the music from. Note that if you're loading an external sound file + * outside the game's assets, you should use {@link FileHandle}; otherwise, just pass down a + * regular string (without {@code assets/} at the beginning). * @param volume The volume to play the new music with. */ public static void playMusic(String path, float volume) { @@ -274,10 +274,10 @@ public static void playMusic(String path, float volume) { * Funkin.playMusic(Paths.external("your/path/here").path(), 1, false); * } * - * @param path The path to load the music from. Note that if you're loading an external sound file - * outside the game's assets, you should use {@link FileHandle}; otherwise, just pass down a - * regular string (without {@code assets/} at the beginning). - * @param volume The volume to play the new music with. + * @param path The path to load the music from. Note that if you're loading an external sound file + * outside the game's assets, you should use {@link FileHandle}; otherwise, just pass down a + * regular string (without {@code assets/} at the beginning). + * @param volume The volume to play the new music with. * @param looping Should the new music loop indefinitely? */ public static void playMusic(String path, float volume, boolean looping) { @@ -297,11 +297,11 @@ public static void playMusic(String path, float volume, boolean looping) { * Funkin.playMusic(Paths.external("your/path/here").path(), 1, false, true); * } * - * @param path The path to load the music from. Note that if you're loading an external sound file - * outside the game's assets, you should use {@link FileHandle}; otherwise, just pass down a - * regular string (without {@code assets/} at the beginning). - * @param volume The volume to play the new music with. - * @param looping Should the new music loop indefinitely? + * @param path The path to load the music from. Note that if you're loading an external sound file + * outside the game's assets, you should use {@link FileHandle}; otherwise, just pass down a + * regular string (without {@code assets/} at the beginning). + * @param volume The volume to play the new music with. + * @param looping Should the new music loop indefinitely? * @param external Should this music be loaded externally? (This is only for mobile platforms!) */ public static void playMusic(String path, float volume, boolean looping, boolean external) { @@ -362,8 +362,7 @@ public static void error(String tag, Object message) { } public static void error(String tag, Object message, Throwable throwable) { - String msg = - (throwable != null) ? (message + " | Exception: " + throwable) : message.toString(); + String msg = (throwable != null) ? (message + " | Exception: " + throwable) : message.toString(); outputLog(tag, msg, FunkinLogLevel.ERROR); } @@ -414,8 +413,7 @@ public static final class Signals { public static final FunkinSignal preRender = new FunkinSignal<>(); public static final FunkinSignal postRender = new FunkinSignal<>(); public static final FunkinSignal preScreenSwitch = new FunkinSignal<>(); - public static final FunkinSignal postScreenSwitch = - new FunkinSignal<>(); + public static final FunkinSignal postScreenSwitch = new FunkinSignal<>(); public static final FunkinSignal preGameClose = new FunkinSignal<>(); public static final FunkinSignal postGameClose = new FunkinSignal<>(); public static final FunkinSignal windowFocused = new FunkinSignal<>(); @@ -445,35 +443,25 @@ private static void outputLog(String tag, Object message, FunkinLogLevel level) method = caller.getMethodName() + "()"; } - String color = - switch (level) { - case INFO -> Constants.AsciiCodes.WHITE; - case WARN -> Constants.AsciiCodes.YELLOW; - case ERROR -> Constants.AsciiCodes.RED; - }; + String color = switch (level) { + case INFO -> Constants.AsciiCodes.WHITE; + case WARN -> Constants.AsciiCodes.YELLOW; + case ERROR -> Constants.AsciiCodes.RED; + }; boolean underline = (level == FunkinLogLevel.ERROR); - String timeAndDate = - colorText( - LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + " ", - color, - true, - false, - underline); - String formattedTag = - colorText( - "[" + level + "] [" + tag + "] [" + file + "] [" + method + "] ", - color, - true, - false, - underline); + String timeAndDate = colorText(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + " ", + color, + true, + false, + underline); + String formattedTag = colorText("[" + level + "] [" + tag + "] [" + file + "] [" + method + "] ", color, true, false, underline); String formattedMessage = colorText(message.toString(), color, false, true, underline); System.out.println(timeAndDate + formattedTag + formattedMessage); } - private static String colorText( - String text, String color, boolean bold, boolean italic, boolean underline) { + private static String colorText(String text, String color, boolean bold, boolean italic, boolean underline) { StringBuilder sb = new StringBuilder(); if (bold) { sb.append(Constants.AsciiCodes.BOLD); diff --git a/core/src/main/java/me/stringfromjava/funkin/FunkinGame.java b/core/src/main/java/me/stringfromjava/funkin/FunkinGame.java index fa198c4..f892d80 100644 --- a/core/src/main/java/me/stringfromjava/funkin/FunkinGame.java +++ b/core/src/main/java/me/stringfromjava/funkin/FunkinGame.java @@ -12,9 +12,9 @@ import com.badlogic.gdx.scenes.scene2d.Stage; import com.badlogic.gdx.utils.ScreenUtils; import com.badlogic.gdx.utils.viewport.FitViewport; -import me.stringfromjava.funkin.graphics.screen.FunkinScreen; import me.stringfromjava.funkin.backend.Paths; import me.stringfromjava.funkin.game.InitScreen; +import me.stringfromjava.funkin.graphics.screen.FunkinScreen; import me.stringfromjava.funkin.graphics.sprite.FunkinObject; import me.stringfromjava.funkin.graphics.sprite.FunkinSprite; import me.stringfromjava.funkin.polyverse.Polyverse; @@ -134,7 +134,7 @@ public void onWindowUnfocused() { * Called when the user minimizes the game's window. * * @param iconified Whether the window is iconified (minimized) or not. This parameter is provided - * for compatibility with the window listener in the LWJGL3 (desktop) launcher. + * for compatibility with the window listener in the LWJGL3 (desktop) launcher. */ public void onWindowMinimized(boolean iconified) { isMinimized = iconified; @@ -189,18 +189,16 @@ public boolean isMinimized() { } private void configureCrashHandler() { - Thread.setDefaultUncaughtExceptionHandler( - (thread, throwable) -> { - String logs = FunkinRuntimeUtil.getFullExceptionMessage(throwable); - String msg = - "There was an uncaught exception on thread \"" + thread.getName() + "\"!\n" + logs; - Funkin.error(Constants.System.LOG_TAG, msg); - dispose(); - // Only use Gdx.app.exit() on non-iOS platforms to avoid App Store guideline violations! - if (Gdx.app.getType() != Application.ApplicationType.iOS) { - Gdx.app.exit(); - } - }); + Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> { + String logs = FunkinRuntimeUtil.getFullExceptionMessage(throwable); + String msg = "There was an uncaught exception on thread \"" + thread.getName() + "\"!\n" + logs; + Funkin.error(Constants.System.LOG_TAG, msg); + dispose(); + // Only use Gdx.app.exit() on non-iOS platforms to avoid App Store guideline violations! + if (Gdx.app.getType() != Application.ApplicationType.iOS) { + Gdx.app.exit(); + } + }); } private void configureWindow() { diff --git a/core/src/main/java/me/stringfromjava/funkin/game/menus/TitleScreen.java b/core/src/main/java/me/stringfromjava/funkin/game/menus/TitleScreen.java index b2cb975..ab95517 100644 --- a/core/src/main/java/me/stringfromjava/funkin/game/menus/TitleScreen.java +++ b/core/src/main/java/me/stringfromjava/funkin/game/menus/TitleScreen.java @@ -3,11 +3,11 @@ import com.badlogic.gdx.Input; import games.rednblack.miniaudio.MASound; import me.stringfromjava.funkin.Funkin; -import me.stringfromjava.funkin.graphics.screen.FunkinScreen; import me.stringfromjava.funkin.backend.Paths; +import me.stringfromjava.funkin.graphics.screen.FunkinScreen; import me.stringfromjava.funkin.graphics.sprite.FunkinSprite; -import me.stringfromjava.funkin.tween.FunkinTween; import me.stringfromjava.funkin.tween.FunkinEase; +import me.stringfromjava.funkin.tween.FunkinTween; import me.stringfromjava.funkin.tween.settings.FunkinTweenSettings; import me.stringfromjava.funkin.tween.settings.FunkinTweenType; diff --git a/core/src/main/java/me/stringfromjava/funkin/graphics/sprite/FunkinSprite.java b/core/src/main/java/me/stringfromjava/funkin/graphics/sprite/FunkinSprite.java index 0811048..deed5d3 100644 --- a/core/src/main/java/me/stringfromjava/funkin/graphics/sprite/FunkinSprite.java +++ b/core/src/main/java/me/stringfromjava/funkin/graphics/sprite/FunkinSprite.java @@ -26,9 +26,7 @@ public class FunkinSprite extends Sprite implements FunkinObject, Pool.Poolable /** The texture image that {@code this} sprite uses. */ protected Texture texture; - /** - * The hitbox used for collision detection and angling. - */ + /** The hitbox used for collision detection and angling. */ protected Rectangle hitbox; /** The atlas regions used in this sprite (used for animations). */ @@ -68,7 +66,7 @@ public void update(float delta) { if (animations.containsKey(currentAnim)) { stateTime += delta; currentFrame = - (TextureAtlas.AtlasRegion) animations.get(currentAnim).getKeyFrame(stateTime, looping); + (TextureAtlas.AtlasRegion) animations.get(currentAnim).getKeyFrame(stateTime, looping); setRegion(currentFrame); } } @@ -87,7 +85,7 @@ public FunkinSprite loadGraphic(FileHandle path) { /** * Load's a texture and automatically resizes the size of {@code this} sprite. * - * @param path The directory of the {@code .png} to load onto {@code this} sprite. + * @param path The directory of the {@code .png} to load onto {@code this} sprite. * @param frameWidth How wide the sprite should be. * @return {@code this} sprite for chaining. */ @@ -99,8 +97,8 @@ public FunkinSprite loadGraphic(FileHandle path, int frameWidth) { /** * Load's a texture and automatically resizes the size of {@code this} sprite. * - * @param path The directory of the {@code .png} to load onto {@code this} sprite. - * @param frameWidth How wide the sprite should be. + * @param path The directory of the {@code .png} to load onto {@code this} sprite. + * @param frameWidth How wide the sprite should be. * @param frameHeight How tall the sprite should be. * @return {@code this} sprite for chaining. */ @@ -121,9 +119,9 @@ public FunkinSprite loadGraphic(Texture texture, int frameWidth, int frameHeight * Loads an {@code .xml} spritesheet with {@code SubTexture} data inside of it. * * @param texture The path to the {@code .png} texture file for slicing and extracting the - * different frames from. + * different frames from. * @param xmlFile The path to the {@code .xml} file which contains the data for each subtexture of - * the sparrow atlas. + * the sparrow atlas. * @return {@code this} sprite for chaining. */ public FunkinSprite loadSparrowFrames(FileHandle texture, FileHandle xmlFile) { @@ -134,9 +132,9 @@ public FunkinSprite loadSparrowFrames(FileHandle texture, FileHandle xmlFile) { * Loads an {@code .xml} spritesheet with {@code SubTexture} data inside of it. * * @param texture The {@code .png} texture file for slicing and extracting the different frames - * from. + * from. * @param xmlFile The {@link XmlReader.Element} data which contains the data for each subtexture - * of the sparrow atlas. + * of the sparrow atlas. * @return {@code this} sprite for chaining. */ public FunkinSprite loadSparrowFrames(Texture texture, XmlReader.Element xmlFile) { @@ -190,10 +188,10 @@ public FunkinSprite loadSparrowFrames(Texture texture, XmlReader.Element xmlFile /** * Adds an animation by looking for sub textures that start with the prefix passed down. * - * @param name The name of the animation (e.g., "confirm"). - * @param prefix The prefix in the {@code .xml} file (e.g., "left confirm"). + * @param name The name of the animation (e.g., "confirm"). + * @param prefix The prefix in the {@code .xml} file (e.g., "left confirm"). * @param frameRate How fast the animation should play in frames-per-second. Standard is 24. - * @param loop Should the new animation loop indefinitely? + * @param loop Should the new animation loop indefinitely? */ public void addAnimationByPrefix(String name, String prefix, int frameRate, boolean loop) { Array frames = new Array<>(); @@ -209,18 +207,18 @@ public void addAnimationByPrefix(String name, String prefix, int frameRate, bool frames.sort(Comparator.comparing(o -> o.name)); animations.put( - name, - new Animation<>( - 1f / frameRate, frames, loop ? Animation.PlayMode.LOOP : Animation.PlayMode.NORMAL)); + name, + new Animation<>( + 1f / frameRate, frames, loop ? Animation.PlayMode.LOOP : Animation.PlayMode.NORMAL)); } } /** * Adds a new animation to the animations list, if it doesn't exist already. * - * @param name The name of the animation. This is what you'll use every time you use {@code - * playAnimation()}. - * @param frameIndices An array of integers used for animation frame indices. + * @param name The name of the animation. This is what you'll use every time you use {@code + * playAnimation()}. + * @param frameIndices An array of integers used for animation frame indices. * @param frameDuration How long each frame lasts for in seconds. */ public void addAnimation(String name, int[] frameIndices, float frameDuration) { @@ -259,8 +257,8 @@ public void playAnimation(String name, boolean loop) { /** * Plays an animation {@code this} sprite has, if it exists. * - * @param name The name of the animation to play. - * @param loop Should this animation loop indefinitely? + * @param name The name of the animation to play. + * @param loop Should this animation loop indefinitely? * @param forceRestart Should the animation automatically restart regardless if its playing? */ public void playAnimation(String name, boolean loop, boolean forceRestart) { @@ -289,21 +287,21 @@ public void draw(Batch batch) { // Adjust drawing position by the Sparrow offsets float drawX = getX() + currentFrame.offsetX; float drawY = - getY() - + (currentFrame.originalHeight - currentFrame.getRegionHeight() - currentFrame.offsetY); + getY() + + (currentFrame.originalHeight - currentFrame.getRegionHeight() - currentFrame.offsetY); batch.draw( - currentFrame, - drawX, - drawY, - originX - currentFrame.offsetX, - originY - - (currentFrame.originalHeight - currentFrame.getRegionHeight() - currentFrame.offsetY), - currentFrame.getRegionWidth(), - currentFrame.getRegionHeight(), - getScaleX(), - getScaleY(), - getRotation()); + currentFrame, + drawX, + drawY, + originX - currentFrame.offsetX, + originY + - (currentFrame.originalHeight - currentFrame.getRegionHeight() - currentFrame.offsetY), + currentFrame.getRegionWidth(), + currentFrame.getRegionHeight(), + getScaleX(), + getScaleY(), + getRotation()); } public boolean isAnimationFinished() { diff --git a/core/src/main/java/me/stringfromjava/funkin/polyverse/Polyverse.java b/core/src/main/java/me/stringfromjava/funkin/polyverse/Polyverse.java index 50a98e6..81c746c 100644 --- a/core/src/main/java/me/stringfromjava/funkin/polyverse/Polyverse.java +++ b/core/src/main/java/me/stringfromjava/funkin/polyverse/Polyverse.java @@ -82,12 +82,12 @@ public static void registerScript(FileHandle handle) { if (!typeScripts.contains(script)) { typeScripts.add(script); Funkin.info( - "Polyverse", - "Registered Polyverse script \"" - + script.getClass().getSimpleName() - + "\" of script type \"" - + mostSpecificType.getSimpleName() - + "\"."); + "Polyverse", + "Registered Polyverse script \"" + + script.getClass().getSimpleName() + + "\" of script type \"" + + mostSpecificType.getSimpleName() + + "\"."); } } script.onCreate(); @@ -100,7 +100,7 @@ public static void registerScript(FileHandle handle) { /** * Executes an action for each script of a certain type, with error handling. * - * @param type The class type of scripts to iterate over. + * @param type The class type of scripts to iterate over. * @param action The action to perform on each script. */ public static void forEachScript(Class type, Consumer action) { @@ -122,7 +122,7 @@ public static void forAllScripts(Consumer action) { } private static void executeScriptList( - List scriptList, Consumer action) { + List scriptList, Consumer action) { // Use a standard for-loop to prevent ConcurrentModificationException // and ensure we are iterating over the current snapshot of scripts. for (int i = 0; i < scriptList.size(); i++) { diff --git a/core/src/main/java/me/stringfromjava/funkin/tween/FunkinEase.java b/core/src/main/java/me/stringfromjava/funkin/tween/FunkinEase.java index f6cfc5c..5d89e43 100644 --- a/core/src/main/java/me/stringfromjava/funkin/tween/FunkinEase.java +++ b/core/src/main/java/me/stringfromjava/funkin/tween/FunkinEase.java @@ -130,9 +130,9 @@ public static float circOut(float t) { public static float circInOut(float t) { return (float) - (t <= .5 - ? (Math.sqrt(1 - t * t * 4) - 1) / -2 - : (Math.sqrt(1 - (t * 2 - 2) * (t * 2 - 2)) + 1) / 2); + (t <= .5 + ? (Math.sqrt(1 - t * t * 4) - 1) / -2 + : (Math.sqrt(1 - (t * 2 - 2) * (t * 2 - 2)) + 1) / 2); } public static float expoIn(float t) { @@ -145,7 +145,7 @@ public static float expoOut(float t) { public static float expoInOut(float t) { return (float) - (t < .5 ? Math.pow(2, 10 * (t * 2 - 1)) / 2 : (-Math.pow(2, -10 * (t * 2 - 1)) + 2) / 2); + (t < .5 ? Math.pow(2, 10 * (t * 2 - 1)) / 2 : (-Math.pow(2, -10 * (t * 2 - 1)) + 2) / 2); } public static float backIn(float t) { @@ -165,37 +165,37 @@ public static float backInOut(float t) { public static float elasticIn(float t) { return (float) - -(ELASTIC_AMPLITUDE - * Math.pow(2, 10 * (t -= 1)) - * Math.sin( - (t - (ELASTIC_PERIOD / (2 * Math.PI) * Math.asin(1 / ELASTIC_AMPLITUDE))) - * (2 * Math.PI) - / ELASTIC_PERIOD)); + -(ELASTIC_AMPLITUDE + * Math.pow(2, 10 * (t -= 1)) + * Math.sin( + (t - (ELASTIC_PERIOD / (2 * Math.PI) * Math.asin(1 / ELASTIC_AMPLITUDE))) + * (2 * Math.PI) + / ELASTIC_PERIOD)); } public static float elasticOut(float t) { return (float) - (ELASTIC_AMPLITUDE - * Math.pow(2, -10 * t) - * Math.sin( - (t - (ELASTIC_PERIOD / (2 * Math.PI) * Math.asin(1 / ELASTIC_AMPLITUDE))) - * (2 * Math.PI) - / ELASTIC_PERIOD) - + 1); + (ELASTIC_AMPLITUDE + * Math.pow(2, -10 * t) + * Math.sin( + (t - (ELASTIC_PERIOD / (2 * Math.PI) * Math.asin(1 / ELASTIC_AMPLITUDE))) + * (2 * Math.PI) + / ELASTIC_PERIOD) + + 1); } public static float elasticInOut(float t) { if (t < 0.5) { return (float) - (-0.5 - * (Math.pow(2, 10 * (t -= 0.5f)) - * Math.sin((t - (ELASTIC_PERIOD / 4)) * (2 * Math.PI) / ELASTIC_PERIOD))); + (-0.5 + * (Math.pow(2, 10 * (t -= 0.5f)) + * Math.sin((t - (ELASTIC_PERIOD / 4)) * (2 * Math.PI) / ELASTIC_PERIOD))); } return (float) - (Math.pow(2, -10 * (t -= 0.5f)) - * Math.sin((t - (ELASTIC_PERIOD / 4)) * (2 * Math.PI) / ELASTIC_PERIOD) - * 0.5 - + 1); + (Math.pow(2, -10 * (t -= 0.5f)) + * Math.sin((t - (ELASTIC_PERIOD / 4)) * (2 * Math.PI) / ELASTIC_PERIOD) + * 0.5 + + 1); } @FunctionalInterface @@ -217,4 +217,5 @@ public interface FunkinEaseUpdateCallback { public interface FunkinEaseCompleteCallback { void run(FunkinTween tween); } + } diff --git a/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTween.java b/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTween.java index 01db10b..2f7b21c 100644 --- a/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTween.java +++ b/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTween.java @@ -2,9 +2,9 @@ import com.badlogic.gdx.utils.Pool; import me.stringfromjava.funkin.Funkin; -import me.stringfromjava.funkin.util.FunkinReflectUtil; import me.stringfromjava.funkin.tween.settings.FunkinTweenSettings; import me.stringfromjava.funkin.util.Constants; +import me.stringfromjava.funkin.util.FunkinReflectUtil; import org.jetbrains.annotations.NotNull; import java.lang.reflect.Field; @@ -27,9 +27,7 @@ public class FunkinTween implements Pool.Poolable { /** The object to tween. */ protected Object object; - /** - * The settings used for how the tween is handled and calculated (aka how it looks and animates). - */ + /** The settings used for how the tween is handled and calculated (aka how it looks and animates). */ protected FunkinTweenSettings tweenSettings; /** The parent manager that {@code this} tween gets updated in. */ @@ -79,13 +77,13 @@ protected FunkinTween() {} * to see {@link FunkinTween#tween(Object object, FunkinTweenSettings tweenSettings, * FunkinTweenUpdateCallback updateCallback)}. * - * @param object The object to tween values. - * @param settings The settings that configure and determine how the tween should animate and last - * for. + * @param object The object to tween values. + * @param settings The settings that configure and determine how the tween should animate and last + * for. * @param updateCallback Callback function for updating the objects values when the tween updates. */ protected FunkinTween( - Object object, FunkinTweenSettings settings, FunkinTweenUpdateCallback updateCallback) { + Object object, FunkinTweenSettings settings, FunkinTweenUpdateCallback updateCallback) { this.object = object; this.tweenSettings = settings; this.updateCallback = updateCallback; @@ -94,13 +92,13 @@ protected FunkinTween( /** * Creates a new tween with the provided settings and starts it in the global tween manager. * - * @param object The object to tween its values. - * @param tweenSettings The settings that configure and determine how the tween should animate. + * @param object The object to tween its values. + * @param tweenSettings The settings that configure and determine how the tween should animate. * @param updateCallback Callback function for updating the objects values when the tween updates. * @return The newly created and started tween. */ public static FunkinTween tween( - Object object, FunkinTweenSettings tweenSettings, FunkinTweenUpdateCallback updateCallback) { + Object object, FunkinTweenSettings tweenSettings, FunkinTweenUpdateCallback updateCallback) { return new FunkinTween(object, tweenSettings, updateCallback).setManager(globalManager).start(); } @@ -213,7 +211,7 @@ public FunkinTween start() { // Ensure that the fields provided actually exist on the object and are floating point values. // If there are fields that the tween is trying to tween that don't exist, then throw an error. Set fieldIds = - Arrays.stream(fieldsCache).map(Field::getName).collect(Collectors.toSet()); + Arrays.stream(fieldsCache).map(Field::getName).collect(Collectors.toSet()); for (String neededField : neededFields) { if (!fieldIds.contains(neededField)) { String message = "Field \"" + neededField + "\" does not exist on the given object."; @@ -237,7 +235,7 @@ public FunkinTween start() { initialValues.put(fName, field.getFloat(object)); } catch (IllegalAccessException e) { Funkin.error( - Constants.System.LOG_TAG, "Could not access field \"" + field.getName() + "\".", e); + Constants.System.LOG_TAG, "Could not access field \"" + field.getName() + "\".", e); } } return this; diff --git a/core/src/main/java/me/stringfromjava/funkin/util/Constants.java b/core/src/main/java/me/stringfromjava/funkin/util/Constants.java index 24b8002..d5d04db 100644 --- a/core/src/main/java/me/stringfromjava/funkin/util/Constants.java +++ b/core/src/main/java/me/stringfromjava/funkin/util/Constants.java @@ -60,6 +60,4 @@ public static final class AsciiCodes { private AsciiCodes() {} } - - private Constants() {} } diff --git a/core/src/main/java/me/stringfromjava/funkin/util/FunkinReflectUtil.java b/core/src/main/java/me/stringfromjava/funkin/util/FunkinReflectUtil.java index 2fae80d..ec53a0d 100644 --- a/core/src/main/java/me/stringfromjava/funkin/util/FunkinReflectUtil.java +++ b/core/src/main/java/me/stringfromjava/funkin/util/FunkinReflectUtil.java @@ -18,20 +18,19 @@ public class FunkinReflectUtil { /** * Checks if a field exists on a given object. * - * @param target The object to verify the existence of the given field to check. + * @param target The object to verify the existence of the given field to check. * @param fieldName The name of the * @return If the field exists on the given object. */ public static boolean hasField(Object target, String fieldName) { AtomicBoolean exists = new AtomicBoolean(); getAllFields(target.getClass()) - .forEach( - field -> { - if (field.getName().equals(fieldName)) { - exists.set(true); - } - exists.set(false); - }); + .forEach(field -> { + if (field.getName().equals(fieldName)) { + exists.set(true); + } + exists.set(false); + }); return exists.get(); } @@ -66,9 +65,9 @@ public static Field[] getAllFieldsAsArray(Class type) { * Checks if a class of a certain package is final. * * @param classPath The package definition of the class to check if final. An example could be - * {@code "me.stringfromjava.funkin.Funkin"}. + * {@code "me.stringfromjava.funkin.Funkin"}. * @return If the class provided is final. If there was an exception caught, then {@code false} is - * automatically returned. + * automatically returned. */ public static boolean isClassFinal(String classPath) { try { diff --git a/core/src/main/java/me/stringfromjava/funkin/util/FunkinRuntimeUtil.java b/core/src/main/java/me/stringfromjava/funkin/util/FunkinRuntimeUtil.java index f5b4572..6ab4507 100644 --- a/core/src/main/java/me/stringfromjava/funkin/util/FunkinRuntimeUtil.java +++ b/core/src/main/java/me/stringfromjava/funkin/util/FunkinRuntimeUtil.java @@ -23,13 +23,13 @@ public static String getExceptionLocation(Throwable exception) { } StackTraceElement element = stackTrace[0]; return "FILE=" - + element.getFileName() - + ", CLASS=" - + element.getClassName() - + ", METHOD=" - + element.getMethodName() - + "(), LINE=" - + element.getLineNumber(); + + element.getFileName() + + ", CLASS=" + + element.getClassName() + + ", METHOD=" + + element.getMethodName() + + "(), LINE=" + + element.getLineNumber(); } /** diff --git a/core/src/main/java/me/stringfromjava/funkin/util/signal/FunkinSignalData.java b/core/src/main/java/me/stringfromjava/funkin/util/signal/FunkinSignalData.java index 8bb6fef..93f71ad 100644 --- a/core/src/main/java/me/stringfromjava/funkin/util/signal/FunkinSignalData.java +++ b/core/src/main/java/me/stringfromjava/funkin/util/signal/FunkinSignalData.java @@ -9,14 +9,24 @@ */ public final class FunkinSignalData { - public record RenderSignalData(float delta) {} + public record RenderSignalData(float delta) { - public record ScreenSwitchSignalData(FunkinScreen screen) {} + } - public record SoundPlayedSignalData(MASound sound) {} + public record ScreenSwitchSignalData(FunkinScreen screen) { - public record MusicPlayedSignalData(MASound music) {} + } + + public record SoundPlayedSignalData(MASound sound) { + + } + + public record MusicPlayedSignalData(MASound music) { + + } + + private FunkinSignalData() { + } - private FunkinSignalData() {} } From 574a51154dce7abc98e31dcaba6fa0a70fd788f3 Mon Sep 17 00:00:00 2001 From: String Date: Thu, 22 Jan 2026 04:01:42 -0600 Subject: [PATCH 08/17] Moved everything into its own folder (especially FlixelGDX) --- .editorconfig | 2 +- .../funkin/android/AndroidLauncher.java | 11 +- assets/another_test.groovy | 24 +- assets/shared/images/characters/Nene.xml | 4 +- .../shared/images/characters/Pico_Death.xml | 4 +- assets/test.groovy | 25 +- .../Funkin.java => flixelgdx/Flixel.java} | 154 +++++----- .../stringfromjava/flixelgdx/FlixelGame.java | 271 ++++++++++++++++++ .../backend/FlixelPaths.java} | 6 +- .../graphics/screen/FlixelScreen.java} | 10 +- .../graphics/sprite/FlixelObject.java | 9 + .../graphics/sprite/FlixelSprite.java} | 18 +- .../graphics/text/FlixelText.java} | 6 +- .../signal/FlixelSignal.java} | 6 +- .../flixelgdx/signal/FlixelSignalData.java | 22 ++ .../tween/FlixelEase.java} | 12 +- .../tween/FlixelTween.java} | 68 ++--- .../tween/FlixelTweenManager.java} | 18 +- .../tween/settings/FlixelTweenSettings.java} | 57 ++-- .../tween/settings/FlixelTweenType.java} | 4 +- .../util/FlixelConstants.java} | 26 +- .../util/FlixelReflectUtil.java} | 10 +- .../util/FlixelRuntimeUtil.java} | 9 +- .../me/stringfromjava/funkin/FunkinGame.java | 227 ++------------- .../funkin/game/InitScreen.java | 14 - .../funkin/game/menus/TitleScreen.java | 90 ------ .../funkin/graphics/sprite/FunkinObject.java | 7 - .../funkin/init/InitScreen.java | 14 + .../funkin/menus/TitleScreen.java | 90 ++++++ .../funkin/util/FunkinConstants.java | 26 ++ .../funkin/util/signal/FunkinSignalData.java | 32 --- .../{funkin => }/polyverse/Polyverse.java | 16 +- .../polyverse/script/type/Script.java | 2 +- .../polyverse/script/type/SystemScript.java | 2 +- .../funkin/lwjgl3/Lwjgl3Launcher.java | 30 +- 35 files changed, 710 insertions(+), 616 deletions(-) rename core/src/main/java/me/stringfromjava/{funkin/Funkin.java => flixelgdx/Flixel.java} (75%) create mode 100644 core/src/main/java/me/stringfromjava/flixelgdx/FlixelGame.java rename core/src/main/java/me/stringfromjava/{funkin/backend/Paths.java => flixelgdx/backend/FlixelPaths.java} (86%) rename core/src/main/java/me/stringfromjava/{funkin/graphics/screen/FunkinScreen.java => flixelgdx/graphics/screen/FlixelScreen.java} (77%) create mode 100644 core/src/main/java/me/stringfromjava/flixelgdx/graphics/sprite/FlixelObject.java rename core/src/main/java/me/stringfromjava/{funkin/graphics/sprite/FunkinSprite.java => flixelgdx/graphics/sprite/FlixelSprite.java} (95%) rename core/src/main/java/me/stringfromjava/{funkin/graphics/text/FunkinText.java => flixelgdx/graphics/text/FlixelText.java} (78%) rename core/src/main/java/me/stringfromjava/{funkin/util/signal/FunkinSignal.java => flixelgdx/signal/FlixelSignal.java} (95%) create mode 100644 core/src/main/java/me/stringfromjava/flixelgdx/signal/FlixelSignalData.java rename core/src/main/java/me/stringfromjava/{funkin/tween/FunkinEase.java => flixelgdx/tween/FlixelEase.java} (96%) rename core/src/main/java/me/stringfromjava/{funkin/tween/FunkinTween.java => flixelgdx/tween/FlixelTween.java} (84%) rename core/src/main/java/me/stringfromjava/{funkin/tween/FunkinTweenManager.java => flixelgdx/tween/FlixelTweenManager.java} (70%) rename core/src/main/java/me/stringfromjava/{funkin/tween/settings/FunkinTweenSettings.java => flixelgdx/tween/settings/FlixelTweenSettings.java} (66%) rename core/src/main/java/me/stringfromjava/{funkin/tween/settings/FunkinTweenType.java => flixelgdx/tween/settings/FlixelTweenType.java} (84%) rename core/src/main/java/me/stringfromjava/{funkin/util/Constants.java => flixelgdx/util/FlixelConstants.java} (61%) rename core/src/main/java/me/stringfromjava/{funkin/util/FunkinReflectUtil.java => flixelgdx/util/FlixelReflectUtil.java} (90%) rename core/src/main/java/me/stringfromjava/{funkin/util/FunkinRuntimeUtil.java => flixelgdx/util/FlixelRuntimeUtil.java} (92%) delete mode 100644 core/src/main/java/me/stringfromjava/funkin/game/InitScreen.java delete mode 100644 core/src/main/java/me/stringfromjava/funkin/game/menus/TitleScreen.java delete mode 100644 core/src/main/java/me/stringfromjava/funkin/graphics/sprite/FunkinObject.java create mode 100644 core/src/main/java/me/stringfromjava/funkin/init/InitScreen.java create mode 100644 core/src/main/java/me/stringfromjava/funkin/menus/TitleScreen.java create mode 100644 core/src/main/java/me/stringfromjava/funkin/util/FunkinConstants.java delete mode 100644 core/src/main/java/me/stringfromjava/funkin/util/signal/FunkinSignalData.java rename core/src/main/java/me/stringfromjava/{funkin => }/polyverse/Polyverse.java (91%) rename core/src/main/java/me/stringfromjava/{funkin => }/polyverse/script/type/Script.java (91%) rename core/src/main/java/me/stringfromjava/{funkin => }/polyverse/script/type/SystemScript.java (66%) diff --git a/.editorconfig b/.editorconfig index def4063..179b03d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -24,7 +24,7 @@ ij_java_names_count_to_use_import_on_demand = 999 ij_java_packages_to_use_import_on_demand = unset ij_java_blank_lines_before_class_end = 0 ij_java_blank_lines_after_class_header = 1 -ij_java_doc_align_param_comments = true +ij_java_doc_align_param_comments = false ij_java_doc_do_not_wrap_if_one_line = true ij_any_keep_simple_blocks_in_one_line = true diff --git a/android/src/main/java/me/stringfromjava/funkin/android/AndroidLauncher.java b/android/src/main/java/me/stringfromjava/funkin/android/AndroidLauncher.java index 0898de2..2b41d46 100644 --- a/android/src/main/java/me/stringfromjava/funkin/android/AndroidLauncher.java +++ b/android/src/main/java/me/stringfromjava/funkin/android/AndroidLauncher.java @@ -3,7 +3,10 @@ import android.os.Bundle; import com.badlogic.gdx.backends.android.AndroidApplication; import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration; +import me.stringfromjava.flixelgdx.FlixelGame; import me.stringfromjava.funkin.FunkinGame; +import me.stringfromjava.funkin.init.InitScreen; +import me.stringfromjava.funkin.util.FunkinConstants; /** Launches the Android application. */ public class AndroidLauncher extends AndroidApplication { @@ -11,8 +14,14 @@ public class AndroidLauncher extends AndroidApplication { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + FunkinGame game = new FunkinGame( + FunkinConstants.WINDOW_TITLE, + FunkinConstants.WINDOW_WIDTH, + FunkinConstants.WINDOW_HEIGHT, + new InitScreen() + ); AndroidApplicationConfiguration configuration = new AndroidApplicationConfiguration(); configuration.useImmersiveMode = true; // Recommended, but not required. - initialize(new FunkinGame(), configuration); + initialize(game, configuration); } } diff --git a/assets/another_test.groovy b/assets/another_test.groovy index b162bc9..a7a42b6 100644 --- a/assets/another_test.groovy +++ b/assets/another_test.groovy @@ -1,10 +1,10 @@ import com.badlogic.gdx.Gdx import com.badlogic.gdx.Input -import me.stringfromjava.funkin.Funkin -import me.stringfromjava.funkin.backend.Paths -import me.stringfromjava.funkin.graphics.sprite.FunkinSprite -import me.stringfromjava.funkin.polyverse.script.type.Script -import me.stringfromjava.funkin.util.Constants +import me.stringfromjava.flixelgdx.Flixel +import me.stringfromjava.flixelgdx.backend.FlixelPaths +import me.stringfromjava.flixelgdx.graphics.sprite.FlixelSprite +import me.stringfromjava.funkin.util.FunkinConstants +import me.stringfromjava.polyverse.script.type.Script class AnotherTestClass extends Script { @@ -16,9 +16,9 @@ class AnotherTestClass extends Script { void onCreate() { super.onCreate() - Funkin.info("AnotherTestClass", "Script has been created!") + Flixel.info("AnotherTestClass", "Script has been created!") - Funkin.playMusic("songs/bopeebo/Inst.ogg") + Flixel.playMusic("songs/bopeebo/Inst.ogg") } @Override @@ -26,13 +26,13 @@ class AnotherTestClass extends Script { super.onRender(delta) if (Gdx.input.isKeyJustPressed(Input.Keys.SPACE)) { - var sprite = new FunkinSprite().loadGraphic(Paths.sharedImageAsset("pauseAlt/bfLol")) - var randomPosX = new Random().nextInt(Constants.Display.WINDOW_WIDTH) - var randomPosY = new Random().nextInt(Constants.Display.WINDOW_HEIGHT) + var sprite = new FlixelSprite().loadGraphic(FlixelPaths.sharedImageAsset("pauseAlt/bfLol")) + var randomPosX = new Random().nextInt(FunkinConstants.WINDOW_WIDTH) + var randomPosY = new Random().nextInt(FunkinConstants.WINDOW_HEIGHT) sprite.setPosition(randomPosX, randomPosY) - Funkin.screen.add(sprite) + Flixel.screen.add(sprite) } } @@ -40,6 +40,6 @@ class AnotherTestClass extends Script { void onDispose() { super.onDispose() - Funkin.info("AnotherTestClass", "Script has been disposed!") + Flixel.info("AnotherTestClass", "Script has been disposed!") } } diff --git a/assets/shared/images/characters/Nene.xml b/assets/shared/images/characters/Nene.xml index 3b2d96c..0dd32df 100644 --- a/assets/shared/images/characters/Nene.xml +++ b/assets/shared/images/characters/Nene.xml @@ -1,6 +1,6 @@ @@ -119,4 +119,4 @@ Created with Funkin Packer v0.1.3 https://neeeoo.github.io/funkin-packer/ - \ No newline at end of file + diff --git a/assets/shared/images/characters/Pico_Death.xml b/assets/shared/images/characters/Pico_Death.xml index 152c157..6a980c0 100644 --- a/assets/shared/images/characters/Pico_Death.xml +++ b/assets/shared/images/characters/Pico_Death.xml @@ -1,6 +1,6 @@ @@ -67,4 +67,4 @@ Created with Funkin Packer v0.1.3 https://neeeoo.github.io/funkin-packer/ - \ No newline at end of file + diff --git a/assets/test.groovy b/assets/test.groovy index 452a41d..c16d287 100644 --- a/assets/test.groovy +++ b/assets/test.groovy @@ -1,12 +1,11 @@ import com.badlogic.gdx.Gdx import com.badlogic.gdx.Input import com.badlogic.gdx.graphics.Color -import com.badlogic.gdx.graphics.Texture -import me.stringfromjava.funkin.Funkin -import me.stringfromjava.funkin.graphics.screen.FunkinScreen -import me.stringfromjava.funkin.backend.Paths -import me.stringfromjava.funkin.graphics.sprite.FunkinSprite -import me.stringfromjava.funkin.polyverse.script.type.SystemScript +import me.stringfromjava.flixelgdx.Flixel +import me.stringfromjava.flixelgdx.graphics.screen.FlixelScreen +import me.stringfromjava.flixelgdx.backend.FlixelPaths +import me.stringfromjava.flixelgdx.graphics.sprite.FlixelSprite +import me.stringfromjava.polyverse.script.type.SystemScript class TestScript extends SystemScript { @@ -17,7 +16,7 @@ class TestScript extends SystemScript { @Override void onCreate() { super.onCreate() - Funkin.info("TestScript", "Script has been created!") + Flixel.info("TestScript", "Script has been created!") } @Override @@ -25,31 +24,31 @@ class TestScript extends SystemScript { super.onRender(delta) if (Gdx.input.isKeyJustPressed(Input.Keys.Q)) { - Funkin.setScreen(new TestScreen()) + Flixel.setScreen(new TestScreen()) } } @Override void onDispose() { super.onDispose() - Funkin.info("TestClass", "Script has been disposed!") + Flixel.info("TestClass", "Script has been disposed!") } } -class TestScreen extends FunkinScreen { +class TestScreen extends FlixelScreen { - private FunkinSprite test + private FlixelSprite test @Override void show() { super.show() - test = new FunkinSprite().loadGraphic(Paths.sharedImageAsset('NOTE_hold_assets')) + test = new FlixelSprite().loadGraphic(FlixelPaths.sharedImageAsset('NOTE_hold_assets')) add(test) bgColor = new Color(0, 1, 0, 1) - Funkin.playMusic('songs/darnell/Inst.ogg') + Flixel.playMusic('songs/darnell/Inst.ogg') } @Override diff --git a/core/src/main/java/me/stringfromjava/funkin/Funkin.java b/core/src/main/java/me/stringfromjava/flixelgdx/Flixel.java similarity index 75% rename from core/src/main/java/me/stringfromjava/funkin/Funkin.java rename to core/src/main/java/me/stringfromjava/flixelgdx/Flixel.java index f722901..8188577 100644 --- a/core/src/main/java/me/stringfromjava/funkin/Funkin.java +++ b/core/src/main/java/me/stringfromjava/flixelgdx/Flixel.java @@ -1,4 +1,4 @@ -package me.stringfromjava.funkin; +package me.stringfromjava.flixelgdx; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.assets.AssetManager; @@ -8,28 +8,27 @@ import games.rednblack.miniaudio.MASound; import games.rednblack.miniaudio.MiniAudio; import games.rednblack.miniaudio.loader.MASoundLoader; -import me.stringfromjava.funkin.graphics.screen.FunkinScreen; -import me.stringfromjava.funkin.util.Constants; -import me.stringfromjava.funkin.util.signal.FunkinSignal; -import me.stringfromjava.funkin.util.signal.FunkinSignalData.MusicPlayedSignalData; -import me.stringfromjava.funkin.util.signal.FunkinSignalData.RenderSignalData; -import me.stringfromjava.funkin.util.signal.FunkinSignalData.ScreenSwitchSignalData; -import me.stringfromjava.funkin.util.signal.FunkinSignalData.SoundPlayedSignalData; +import me.stringfromjava.flixelgdx.graphics.screen.FlixelScreen; +import me.stringfromjava.flixelgdx.util.FlixelConstants; +import me.stringfromjava.flixelgdx.signal.FlixelSignal; +import me.stringfromjava.flixelgdx.signal.FlixelSignalData.MusicPlayedSignalData; +import me.stringfromjava.flixelgdx.signal.FlixelSignalData.RenderSignalData; +import me.stringfromjava.flixelgdx.signal.FlixelSignalData.ScreenSwitchSignalData; +import me.stringfromjava.flixelgdx.signal.FlixelSignalData.SoundPlayedSignalData; import org.jetbrains.annotations.NotNull; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; /** - * Global manager and utility class for the game. + * Global manager and utility class for Flixel. * - *

This is where you want to do the main things, like switching screens, playing sounds/music, - * etc. + *

This is where you want to do the main things, like switching screens, playing sounds/music, etc. */ -public final class Funkin { +public final class Flixel { - /** The current {@code FunkinScreen} being displayed. */ - private static FunkinScreen screen; + /** The current {@code FlixelScreen} being displayed. */ + private static FlixelScreen screen; /** The main audio object used to create, */ private static MiniAudio engine; @@ -50,7 +49,7 @@ public final class Funkin { * The static instance used to access the core elements of the game. This includes the main loop, * setting the current screen, and more. */ - private static FunkinGame game; + private static FlixelGame game; /** Has the global manager been initialized yet? */ private static boolean initialized = false; @@ -63,7 +62,7 @@ public final class Funkin { * * @param gameInstance The instance of the game to use. */ - public static void initialize(FunkinGame gameInstance) { + public static void initialize(FlixelGame gameInstance) { if (initialized) { throw new IllegalStateException("FNF:JE has already been initialized!"); } @@ -80,12 +79,11 @@ public static void initialize(FunkinGame gameInstance) { } /** - * Sets the current screen to the provided screen. This is just a more direct version of {@code - * Funkin.game.setScreen(screen)} with some extra functionality put into it. + * Sets the current screen to the provided screen. * - * @param newScreen The new {@code FunkinScreen} to set as the current screen. + * @param newScreen The new {@code FlixelScreen} to set as the current screen. */ - public static void setScreen(FunkinScreen newScreen) { + public static void setScreen(FlixelScreen newScreen) { Signals.preScreenSwitch.dispatch(new ScreenSwitchSignalData(newScreen)); if (!initialized) { throw new IllegalStateException("FNF:JE has not been initialized yet!"); @@ -93,12 +91,12 @@ public static void setScreen(FunkinScreen newScreen) { if (newScreen == null) { throw new IllegalArgumentException("Screen cannot be null!"); } - if (Funkin.screen != null) { - Funkin.screen.hide(); - Funkin.screen.dispose(); + if (Flixel.screen != null) { + Flixel.screen.hide(); + Flixel.screen.dispose(); } - Funkin.screen = newScreen; - Funkin.screen.show(); + Flixel.screen = newScreen; + Flixel.screen.show(); Signals.postScreenSwitch.dispatch(new ScreenSwitchSignalData(newScreen)); } @@ -109,8 +107,8 @@ public static void setScreen(FunkinScreen newScreen) { * FileHandle} like so: * *

{@code
-   * // Notice how it uses the Paths class provided by Funkin'.
-   * Funkin.playSound(Paths.external("your/path/here").path());
+   * // Notice how it uses the FlixelPaths class provided by Flixel'.
+   * Flixel.playSound(FlixelPaths.external("your/path/here").path());
    * }
* * @param path The path to load the sound from. Note that if you're loading an external sound @@ -129,8 +127,8 @@ public static MASound playSound(String path) { * FileHandle} like so: * *
{@code
-   * // Notice how it uses the Paths class provided by Funkin'.
-   * Funkin.playSound(Paths.external("your/path/here").path(), 1);
+   * // Notice how it uses the FlixelPaths class provided by Flixel'.
+   * Flixel.playSound(FlixelPaths.external("your/path/here").path(), 1);
    * }
* * @param path The path to load the sound from. Note that if you're loading an external sound @@ -150,8 +148,8 @@ public static MASound playSound(String path, float volume) { * FileHandle} like so: * *
{@code
-   * // Notice how it uses the Paths class provided by Funkin'.
-   * Funkin.playSound(Paths.external("your/path/here").path(), 1, false);
+   * // Notice how it uses the FlixelPaths class provided by Flixel'.
+   * Flixel.playSound(FlixelPaths.external("your/path/here").path(), 1, false);
    * }
* * @param path The path to load the sound from. Note that if you're loading an external sound @@ -172,9 +170,9 @@ public static MASound playSound(String path, float volume, boolean looping) { * FileHandle} like so: * *
{@code
-   * // Notice how it uses the Paths class provided by Funkin'.
+   * // Notice how it uses the FlixelPaths class provided by Flixel'.
    * // If null is passed down for the group, then the default sound group will be used.
-   * Funkin.playSound(Paths.external("your/path/here").path(), 1, false, null);
+   * Flixel.playSound(FlixelPaths.external("your/path/here").path(), 1, false, null);
    * }
* * @param path The path to load the sound from. Note that if you're loading an external sound @@ -197,11 +195,11 @@ public static MASound playSound(String path, float volume, boolean looping, MAGr * FileHandle} like so: * *
{@code
-   * // Notice how it uses the Paths class provided by Funkin'.
+   * // Notice how it uses the FlixelPaths class provided by Flixel'.
    * // If null is passed down for the group, then the default sound group will be used.
    * // For the boolean attribuite "external", you only should make it true for mobile builds,
    * // otherwise just simply leave it be or make it "false" for other platforms like desktop.
-   * Funkin.playSound(Paths.external("your/path/here").path(), 1, false, null, true);
+   * Flixel.playSound(FlixelPaths.external("your/path/here").path(), 1, false, null, true);
    * }
* * @param path The path to load the sound from. Note that if you're loading an external sound @@ -231,8 +229,8 @@ public static MASound playSound(@NotNull String path, float volume, boolean loop * {@link FileHandle} like so: * *
{@code
-   * // Notice how it uses the Paths class provided by Funkin'.
-   * Funkin.playMusic(Paths.external("your/path/here").path());
+   * // Notice how it uses the FlixelPaths class provided by Flixel'.
+   * Flixel.playMusic(FlixelPaths.external("your/path/here").path());
    * }
* * @param path The path to load the music from. Note that if you're loading an external sound file @@ -250,8 +248,8 @@ public static void playMusic(String path) { * {@link FileHandle} like so: * *
{@code
-   * // Notice how it uses the Paths class provided by Funkin'.
-   * Funkin.playMusic(Paths.external("your/path/here").path(), 1);
+   * // Notice how it uses the FlixelPaths class provided by Flixel'.
+   * Flixel.playMusic(FlixelPaths.external("your/path/here").path(), 1);
    * }
* * @param path The path to load the music from. Note that if you're loading an external sound file @@ -270,8 +268,8 @@ public static void playMusic(String path, float volume) { * {@link FileHandle} like so: * *
{@code
-   * // Notice how it uses the Paths class provided by Funkin'.
-   * Funkin.playMusic(Paths.external("your/path/here").path(), 1, false);
+   * // Notice how it uses the FlixelPaths class provided by Flixel'.
+   * Flixel.playMusic(FlixelPaths.external("your/path/here").path(), 1, false);
    * }
* * @param path The path to load the music from. Note that if you're loading an external sound file @@ -291,10 +289,10 @@ public static void playMusic(String path, float volume, boolean looping) { * {@link FileHandle} like so: * *
{@code
-   * // Notice how it uses the Paths class provided by Funkin'.
+   * // Notice how it uses the FlixelPaths class provided by Flixel'.
    * // For the boolean attribuite "external", you only should make it true for mobile builds,
    * // otherwise just simply leave it be or make it "false" for other platforms like desktop.
-   * Funkin.playMusic(Paths.external("your/path/here").path(), 1, false, true);
+   * Flixel.playMusic(FlixelPaths.external("your/path/here").path(), 1, false, true);
    * }
* * @param path The path to load the music from. Note that if you're loading an external sound file @@ -320,7 +318,7 @@ public static void playMusic(String path, float volume, boolean looping, boolean * Sets the game master/global volume, which is automatically applied to all current sounds. * *

(This is just a helper method for creating a faster version of {@code - * Funkin.getAudioEngine().setMasterVolume(float)}). + * Flixel.getAudioEngine().setMasterVolume(float)}). * * @param volume The new master volume to set. */ @@ -338,23 +336,23 @@ public static boolean keyJustPressed(int key) { } public static void info(Object message) { - info(Constants.System.LOG_TAG, message); + info(FlixelConstants.System.LOG_TAG, message); } public static void info(String tag, Object message) { - outputLog(tag, message, FunkinLogLevel.INFO); + outputLog(tag, message, FlixelLogLevel.INFO); } public static void warn(Object message) { - warn(Constants.System.LOG_TAG, message); + warn(FlixelConstants.System.LOG_TAG, message); } public static void warn(String tag, Object message) { - outputLog(tag, message, FunkinLogLevel.WARN); + outputLog(tag, message, FlixelLogLevel.WARN); } public static void error(String message) { - error(Constants.System.LOG_TAG, message, null); + error(FlixelConstants.System.LOG_TAG, message, null); } public static void error(String tag, Object message) { @@ -363,10 +361,10 @@ public static void error(String tag, Object message) { public static void error(String tag, Object message, Throwable throwable) { String msg = (throwable != null) ? (message + " | Exception: " + throwable) : message.toString(); - outputLog(tag, msg, FunkinLogLevel.ERROR); + outputLog(tag, msg, FlixelLogLevel.ERROR); } - public static FunkinGame getGame() { + public static FlixelGame getGame() { return game; } @@ -374,7 +372,7 @@ public static Stage getStage() { return game.stage; } - public static FunkinScreen getScreen() { + public static FlixelScreen getScreen() { return screen; } @@ -398,6 +396,10 @@ public static MAGroup getSoundsGroup() { return soundsGroup; } + public static float getDelta() { + return Gdx.graphics.getDeltaTime(); + } + /** * Contains all the global events that get dispatched when something happens in the game. * @@ -410,19 +412,19 @@ public static MAGroup getSoundsGroup() { */ public static final class Signals { - public static final FunkinSignal preRender = new FunkinSignal<>(); - public static final FunkinSignal postRender = new FunkinSignal<>(); - public static final FunkinSignal preScreenSwitch = new FunkinSignal<>(); - public static final FunkinSignal postScreenSwitch = new FunkinSignal<>(); - public static final FunkinSignal preGameClose = new FunkinSignal<>(); - public static final FunkinSignal postGameClose = new FunkinSignal<>(); - public static final FunkinSignal windowFocused = new FunkinSignal<>(); - public static final FunkinSignal windowUnfocused = new FunkinSignal<>(); - public static final FunkinSignal windowMinimized = new FunkinSignal<>(); - public static final FunkinSignal preSoundPlayed = new FunkinSignal<>(); - public static final FunkinSignal postSoundPlayed = new FunkinSignal<>(); - public static final FunkinSignal preMusicPlayed = new FunkinSignal<>(); - public static final FunkinSignal postMusicPlayed = new FunkinSignal<>(); + public static final FlixelSignal preRender = new FlixelSignal<>(); + public static final FlixelSignal postRender = new FlixelSignal<>(); + public static final FlixelSignal preScreenSwitch = new FlixelSignal<>(); + public static final FlixelSignal postScreenSwitch = new FlixelSignal<>(); + public static final FlixelSignal preGameClose = new FlixelSignal<>(); + public static final FlixelSignal postGameClose = new FlixelSignal<>(); + public static final FlixelSignal windowFocused = new FlixelSignal<>(); + public static final FlixelSignal windowUnfocused = new FlixelSignal<>(); + public static final FlixelSignal windowMinimized = new FlixelSignal<>(); + public static final FlixelSignal preSoundPlayed = new FlixelSignal<>(); + public static final FlixelSignal postSoundPlayed = new FlixelSignal<>(); + public static final FlixelSignal preMusicPlayed = new FlixelSignal<>(); + public static final FlixelSignal postMusicPlayed = new FlixelSignal<>(); private Signals() {} } @@ -431,7 +433,7 @@ private Signals() {} // UTILITY FUNCTIONS, IGNORE BELOW // ====================================== - private static void outputLog(String tag, Object message, FunkinLogLevel level) { + private static void outputLog(String tag, Object message, FlixelLogLevel level) { StackWalker.StackFrame caller = StackWalker.getInstance() .walk(s -> s.skip(3).findFirst()) .orElse(null); @@ -444,12 +446,12 @@ private static void outputLog(String tag, Object message, FunkinLogLevel level) } String color = switch (level) { - case INFO -> Constants.AsciiCodes.WHITE; - case WARN -> Constants.AsciiCodes.YELLOW; - case ERROR -> Constants.AsciiCodes.RED; + case INFO -> FlixelConstants.AsciiCodes.WHITE; + case WARN -> FlixelConstants.AsciiCodes.YELLOW; + case ERROR -> FlixelConstants.AsciiCodes.RED; }; - boolean underline = (level == FunkinLogLevel.ERROR); + boolean underline = (level == FlixelLogLevel.ERROR); String timeAndDate = colorText(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + " ", color, true, @@ -464,24 +466,24 @@ private static void outputLog(String tag, Object message, FunkinLogLevel level) private static String colorText(String text, String color, boolean bold, boolean italic, boolean underline) { StringBuilder sb = new StringBuilder(); if (bold) { - sb.append(Constants.AsciiCodes.BOLD); + sb.append(FlixelConstants.AsciiCodes.BOLD); } if (italic) { - sb.append(Constants.AsciiCodes.ITALIC); + sb.append(FlixelConstants.AsciiCodes.ITALIC); } if (underline) { - sb.append(Constants.AsciiCodes.UNDERLINE); + sb.append(FlixelConstants.AsciiCodes.UNDERLINE); } sb.append(color); sb.append(text); - sb.append(Constants.AsciiCodes.RESET); + sb.append(FlixelConstants.AsciiCodes.RESET); return sb.toString(); } - private Funkin() {} + private Flixel() {} } -enum FunkinLogLevel { +enum FlixelLogLevel { INFO, WARN, ERROR diff --git a/core/src/main/java/me/stringfromjava/flixelgdx/FlixelGame.java b/core/src/main/java/me/stringfromjava/flixelgdx/FlixelGame.java new file mode 100644 index 0000000..654f80d --- /dev/null +++ b/core/src/main/java/me/stringfromjava/flixelgdx/FlixelGame.java @@ -0,0 +1,271 @@ +package me.stringfromjava.flixelgdx; + +import com.badlogic.gdx.Application; +import com.badlogic.gdx.ApplicationListener; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Input; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.OrthographicCamera; +import com.badlogic.gdx.graphics.Pixmap; +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.scenes.scene2d.Stage; +import com.badlogic.gdx.utils.ScreenUtils; +import com.badlogic.gdx.utils.viewport.FitViewport; +import me.stringfromjava.flixelgdx.graphics.screen.FlixelScreen; +import me.stringfromjava.flixelgdx.graphics.sprite.FlixelObject; +import me.stringfromjava.flixelgdx.graphics.sprite.FlixelSprite; +import me.stringfromjava.flixelgdx.tween.FlixelTween; +import me.stringfromjava.flixelgdx.util.FlixelConstants; +import me.stringfromjava.flixelgdx.util.FlixelRuntimeUtil; +import me.stringfromjava.funkin.util.FunkinConstants; +import me.stringfromjava.polyverse.Polyverse; +import me.stringfromjava.polyverse.script.type.Script; + +import static me.stringfromjava.flixelgdx.signal.FlixelSignalData.RenderSignalData; + +/** + * Flixel's enhanced game object used for containing the main loop and core elements of Flixel. + * + *

If you want to change what happens to the pre and window configurations, you might want to see + * {@code Lwjgl3Launcher} in the {@code lwjgl3} folder. + */ +public abstract class FlixelGame implements ApplicationListener { + + /** The title displayed on the game's window. */ + protected String title; + + /** The size of the game's starting window position and its viewport. */ + protected Vector2 size; + + /** The entry point screen the game starts in (which becomes null after the game is done setting up!). */ + protected FlixelScreen initialScreen; + + /** Is the game's window currently minimized? */ + protected boolean isMinimized = false; + + /** The main stage used for rendering all screens and sprites on screen. */ + protected Stage stage; + + /** The main viewport used to fit the world no matter the screen size. */ + protected FitViewport viewport; + + /** The main camera used to see the world. */ + protected OrthographicCamera camera; + + /** The main sprite batch used for rendering all sprites on screen. */ + protected SpriteBatch batch; + + /** The 1x1 texture used to draw the background color of the current screen. */ + protected Texture bgTexture; + + /** + * Creates a new game instance with the specified title, window width/height, and initial screen. This configures + * the game's core parts, such as the viewport, stage, etc. + * + * @param title The title of the game's window. + * @param width The starting width of the game's window and how wide the viewport should be. + * @param height The starting height of the game's window and how tall the viewport should be. + * @param initialScreen The initial screen to load when the game starts. + */ + public FlixelGame(String title, int width, int height, FlixelScreen initialScreen) { + this.title = title; + this.size = new Vector2(width, height); + this.initialScreen = initialScreen; + } + + @Override + public void create() { + configureCrashHandler(); // This should ALWAYS be called first no matter what! + + batch = new SpriteBatch(); + viewport = new FitViewport(size.x, size.y); + viewport.apply(); + + camera = new OrthographicCamera(); + camera.setToOrtho(false, size.x, size.y); + + stage = new Stage(viewport, batch); + + Pixmap pixmap = new Pixmap(1, 1, Pixmap.Format.RGBA8888); + pixmap.setColor(Color.WHITE); + pixmap.fill(); + bgTexture = new Texture(pixmap); + pixmap.dispose(); + + Flixel.setScreen(initialScreen); + initialScreen = null; + } + + @Override + public void resize(int width, int height) { + viewport.update(width, height, true); + } + + @Override + public void render() { + float delta = Gdx.graphics.getDeltaTime(); + FlixelScreen screen = Flixel.getScreen(); + + Flixel.Signals.preRender.dispatch(new RenderSignalData(delta)); + + if (Flixel.keyJustPressed(Input.Keys.F11)) { + toggleFullscreen(); + } + + // Update and render the current screen that's active. + ScreenUtils.clear(Color.BLACK); + viewport.apply(); + batch.setProjectionMatrix(camera.combined); + batch.begin(); + + if (screen != null) { + batch.setColor(screen.getBgColor()); + batch.draw(bgTexture, 0, 0, viewport.getWorldWidth(), viewport.getWorldHeight()); + batch.setColor(Color.WHITE); // Set color back to white so display objects aren't affected. + screen.render(delta); + var members = screen.members.begin(); + for (FlixelObject object : members) { + if (object instanceof FlixelSprite sprite) { + sprite.update(delta); + sprite.draw(batch); + } + } + screen.members.end(); + } + + batch.end(); + stage.act(delta); + stage.draw(); + + FlixelTween.getGlobalManager().update(delta); + + Flixel.Signals.postRender.dispatch(new RenderSignalData(delta)); + } + + @Override + public void pause() {} + + @Override + public void resume() {} + + /** Called when the user regains focus on the game's window. */ + public void onWindowFocused() { + Flixel.setMasterVolume(1); + Flixel.Signals.windowFocused.dispatch(); + Flixel.info("Game window has regained focus."); + } + + /** Called when the user loses focus on the game's window, while also not being minimized. */ + public void onWindowUnfocused() { + if (isMinimized) { + return; + } + Flixel.setMasterVolume(0.008f); + Flixel.Signals.windowUnfocused.dispatch(); + Flixel.info("Game window has lost focus."); + } + + /** + * Called when the user minimizes the game's window. + * + * @param iconified Whether the window is iconified (minimized) or not. This parameter is provided + * for compatibility with the window listener in the LWJGL3 (desktop) launcher. + */ + public void onWindowMinimized(boolean iconified) { + isMinimized = iconified; + if (!isMinimized) { + return; + } + Flixel.setMasterVolume(0); + Flixel.Signals.windowMinimized.dispatch(); + Flixel.info("Game window has been minimized."); + } + + /** Toggles fullscreen mode on or off, depending on the current state. */ + public void toggleFullscreen() { + boolean isFullscreen = Gdx.graphics.isFullscreen(); + if (isFullscreen) { + Gdx.graphics.setWindowedMode(FunkinConstants.WINDOW_WIDTH, FunkinConstants.WINDOW_HEIGHT); + Flixel.info("Exiting fullscreen mode."); + } else { + Gdx.graphics.setFullscreenMode(Gdx.graphics.getDisplayMode()); + Flixel.info("Entering fullscreen mode."); + } + } + + @Override + public void dispose() { + Flixel.warn("SHUTTING DOWN GAME AND DISPOSING ALL RESOURCES."); + + Flixel.Signals.preGameClose.dispatch(); + + Flixel.info("Disposing the screen display..."); + Flixel.getScreen().hide(); + Flixel.getScreen().dispose(); + stage.dispose(); + batch.dispose(); + bgTexture.dispose(); + + Flixel.info("Disposing all sounds from sound group and music..."); + if (Flixel.getMusic() != null) { + Flixel.getMusic().dispose(); + } + Flixel.getSoundsGroup().dispose(); + Flixel.getAudioEngine().dispose(); + + Flixel.info("Disposing and shutting down scripts..."); + Polyverse.forAllScripts(Script::onDispose); + + Flixel.Signals.postGameClose.dispatch(); + } + + public String getTitle() { + return title; + } + + public int[] getWindowSize() { + return new int[]{(int) size.x, (int) size.y}; + } + + public Stage getStage() { + return stage; + } + + public FitViewport getViewport() { + return viewport; + } + + public OrthographicCamera getCamera() { + return camera; + } + + public SpriteBatch getBatch() { + return batch; + } + + public Texture getBgTexture() { + return bgTexture; + } + + public boolean isMinimized() { + return isMinimized; + } + + /** + * Configurers Flixel's crash handler to safely catch uncaught exceptions and gracefully close the game. + */ + protected void configureCrashHandler() { + Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> { + String logs = FlixelRuntimeUtil.getFullExceptionMessage(throwable); + String msg = "There was an uncaught exception on thread \"" + thread.getName() + "\"!\n" + logs; + Flixel.error(FlixelConstants.System.LOG_TAG, msg); + dispose(); + // Only use Gdx.app.exit() on non-iOS platforms to avoid App Store guideline violations! + if (Gdx.app.getType() != Application.ApplicationType.iOS) { + Gdx.app.exit(); + } + }); + } +} diff --git a/core/src/main/java/me/stringfromjava/funkin/backend/Paths.java b/core/src/main/java/me/stringfromjava/flixelgdx/backend/FlixelPaths.java similarity index 86% rename from core/src/main/java/me/stringfromjava/funkin/backend/Paths.java rename to core/src/main/java/me/stringfromjava/flixelgdx/backend/FlixelPaths.java index d406bab..193b68e 100644 --- a/core/src/main/java/me/stringfromjava/funkin/backend/Paths.java +++ b/core/src/main/java/me/stringfromjava/flixelgdx/backend/FlixelPaths.java @@ -1,10 +1,10 @@ -package me.stringfromjava.funkin.backend; +package me.stringfromjava.flixelgdx.backend; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.files.FileHandle; /** Utility class for simplifying asset paths and libGDX {@link FileHandle}s. */ -public final class Paths { +public final class FlixelPaths { public static FileHandle asset(String path) { return Gdx.files.internal(path); @@ -26,5 +26,5 @@ public static FileHandle external(String path) { return Gdx.files.external(path); } - private Paths() {} + private FlixelPaths() {} } diff --git a/core/src/main/java/me/stringfromjava/funkin/graphics/screen/FunkinScreen.java b/core/src/main/java/me/stringfromjava/flixelgdx/graphics/screen/FlixelScreen.java similarity index 77% rename from core/src/main/java/me/stringfromjava/funkin/graphics/screen/FunkinScreen.java rename to core/src/main/java/me/stringfromjava/flixelgdx/graphics/screen/FlixelScreen.java index 69d390f..daa8efa 100644 --- a/core/src/main/java/me/stringfromjava/funkin/graphics/screen/FunkinScreen.java +++ b/core/src/main/java/me/stringfromjava/flixelgdx/graphics/screen/FlixelScreen.java @@ -1,21 +1,21 @@ -package me.stringfromjava.funkin.graphics.screen; +package me.stringfromjava.flixelgdx.graphics.screen; import com.badlogic.gdx.Screen; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.utils.SnapshotArray; -import me.stringfromjava.funkin.graphics.sprite.FunkinObject; +import me.stringfromjava.flixelgdx.graphics.sprite.FlixelObject; /** * Base class for creating a better screen display with more functionality than the default {@link * com.badlogic.gdx.Screen} interface. */ -public abstract class FunkinScreen implements Screen { +public abstract class FlixelScreen implements Screen { /** The background color of {@code this} current screen. */ protected Color bgColor; /** All display objects that are shown in {@code this} screen. */ - public final SnapshotArray members = new SnapshotArray<>(FunkinObject.class); + public final SnapshotArray members = new SnapshotArray<>(FlixelObject.class); @Override public void show() {} @@ -44,7 +44,7 @@ public void dispose() {} * * @param object The sprite to add to the screen. */ - public void add(FunkinObject object) { + public void add(FlixelObject object) { if (object != null) { members.add(object); } diff --git a/core/src/main/java/me/stringfromjava/flixelgdx/graphics/sprite/FlixelObject.java b/core/src/main/java/me/stringfromjava/flixelgdx/graphics/sprite/FlixelObject.java new file mode 100644 index 0000000..2cf3d08 --- /dev/null +++ b/core/src/main/java/me/stringfromjava/flixelgdx/graphics/sprite/FlixelObject.java @@ -0,0 +1,9 @@ +package me.stringfromjava.flixelgdx.graphics.sprite; + +import me.stringfromjava.flixelgdx.graphics.screen.FlixelScreen; + +/** + * An interface which allows any class that implements it to be added to a {@link + * FlixelScreen}. + */ +public interface FlixelObject {} diff --git a/core/src/main/java/me/stringfromjava/funkin/graphics/sprite/FunkinSprite.java b/core/src/main/java/me/stringfromjava/flixelgdx/graphics/sprite/FlixelSprite.java similarity index 95% rename from core/src/main/java/me/stringfromjava/funkin/graphics/sprite/FunkinSprite.java rename to core/src/main/java/me/stringfromjava/flixelgdx/graphics/sprite/FlixelSprite.java index deed5d3..c0bc65a 100644 --- a/core/src/main/java/me/stringfromjava/funkin/graphics/sprite/FunkinSprite.java +++ b/core/src/main/java/me/stringfromjava/flixelgdx/graphics/sprite/FlixelSprite.java @@ -1,4 +1,4 @@ -package me.stringfromjava.funkin.graphics.sprite; +package me.stringfromjava.flixelgdx.graphics.sprite; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.graphics.Texture; @@ -21,7 +21,7 @@ * *

It allows you to load animations, textures, and do much more with simplicity and ease. */ -public class FunkinSprite extends Sprite implements FunkinObject, Pool.Poolable { +public class FlixelSprite extends Sprite implements FlixelObject, Pool.Poolable { /** The texture image that {@code this} sprite uses. */ protected Texture texture; @@ -53,7 +53,7 @@ public class FunkinSprite extends Sprite implements FunkinObject, Pool.Poolable */ protected TextureRegion[][] frames; - public FunkinSprite() { + public FlixelSprite() { animations = new HashMap<>(); } @@ -77,7 +77,7 @@ public void update(float delta) { * @param path The directory of the {@code .png} to load onto {@code this} sprite. * @return {@code this} sprite for chaining. */ - public FunkinSprite loadGraphic(FileHandle path) { + public FlixelSprite loadGraphic(FileHandle path) { Texture texture = new Texture(path); return loadGraphic(texture, texture.getWidth(), texture.getHeight()); } @@ -89,7 +89,7 @@ public FunkinSprite loadGraphic(FileHandle path) { * @param frameWidth How wide the sprite should be. * @return {@code this} sprite for chaining. */ - public FunkinSprite loadGraphic(FileHandle path, int frameWidth) { + public FlixelSprite loadGraphic(FileHandle path, int frameWidth) { Texture texture = new Texture(path); return loadGraphic(texture, frameWidth, texture.getHeight()); } @@ -102,11 +102,11 @@ public FunkinSprite loadGraphic(FileHandle path, int frameWidth) { * @param frameHeight How tall the sprite should be. * @return {@code this} sprite for chaining. */ - public FunkinSprite loadGraphic(FileHandle path, int frameWidth, int frameHeight) { + public FlixelSprite loadGraphic(FileHandle path, int frameWidth, int frameHeight) { return loadGraphic(new Texture(path), frameWidth, frameHeight); } - public FunkinSprite loadGraphic(Texture texture, int frameWidth, int frameHeight) { + public FlixelSprite loadGraphic(Texture texture, int frameWidth, int frameHeight) { frames = TextureRegion.split(texture, frameWidth, frameHeight); // Set default visual to the first frame. setRegion(frames[0][0]); @@ -124,7 +124,7 @@ public FunkinSprite loadGraphic(Texture texture, int frameWidth, int frameHeight * the sparrow atlas. * @return {@code this} sprite for chaining. */ - public FunkinSprite loadSparrowFrames(FileHandle texture, FileHandle xmlFile) { + public FlixelSprite loadSparrowFrames(FileHandle texture, FileHandle xmlFile) { return loadSparrowFrames(new Texture(texture), new XmlReader().parse(xmlFile)); } @@ -137,7 +137,7 @@ public FunkinSprite loadSparrowFrames(FileHandle texture, FileHandle xmlFile) { * of the sparrow atlas. * @return {@code this} sprite for chaining. */ - public FunkinSprite loadSparrowFrames(Texture texture, XmlReader.Element xmlFile) { + public FlixelSprite loadSparrowFrames(Texture texture, XmlReader.Element xmlFile) { // We store regions in a list so we can filter them by prefix later. // TextureAtlas.AtlasRegion is used because it supports offsets. atlasRegions = new Array<>(); diff --git a/core/src/main/java/me/stringfromjava/funkin/graphics/text/FunkinText.java b/core/src/main/java/me/stringfromjava/flixelgdx/graphics/text/FlixelText.java similarity index 78% rename from core/src/main/java/me/stringfromjava/funkin/graphics/text/FunkinText.java rename to core/src/main/java/me/stringfromjava/flixelgdx/graphics/text/FlixelText.java index da42da5..138dbba 100644 --- a/core/src/main/java/me/stringfromjava/funkin/graphics/text/FunkinText.java +++ b/core/src/main/java/me/stringfromjava/flixelgdx/graphics/text/FlixelText.java @@ -1,7 +1,7 @@ -package me.stringfromjava.funkin.graphics.text; +package me.stringfromjava.flixelgdx.graphics.text; /** A display object for creating a piece of text to show on the screen. */ -public class FunkinText { +public class FlixelText { /** The text to be written onto the screen. */ public String text; @@ -15,7 +15,7 @@ public class FunkinText { /** * @param text The string to be displayed. */ - public FunkinText(String text) { + public FlixelText(String text) { this.text = text; x = 0; y = 0; diff --git a/core/src/main/java/me/stringfromjava/funkin/util/signal/FunkinSignal.java b/core/src/main/java/me/stringfromjava/flixelgdx/signal/FlixelSignal.java similarity index 95% rename from core/src/main/java/me/stringfromjava/funkin/util/signal/FunkinSignal.java rename to core/src/main/java/me/stringfromjava/flixelgdx/signal/FlixelSignal.java index 89d3af0..283cf0f 100644 --- a/core/src/main/java/me/stringfromjava/funkin/util/signal/FunkinSignal.java +++ b/core/src/main/java/me/stringfromjava/flixelgdx/signal/FlixelSignal.java @@ -1,4 +1,4 @@ -package me.stringfromjava.funkin.util.signal; +package me.stringfromjava.flixelgdx.signal; import java.util.concurrent.CopyOnWriteArrayList; @@ -6,12 +6,12 @@ * Utility class for creating objects that can execute multiple callbacks when it is dispatched (or * triggered). */ -public class FunkinSignal { +public class FlixelSignal { private final CopyOnWriteArrayList> callbacks; private final CopyOnWriteArrayList> tempCallbacks; // Callbacks that are added with addOnce(). - public FunkinSignal() { + public FlixelSignal() { callbacks = new CopyOnWriteArrayList<>(); tempCallbacks = new CopyOnWriteArrayList<>(); } diff --git a/core/src/main/java/me/stringfromjava/flixelgdx/signal/FlixelSignalData.java b/core/src/main/java/me/stringfromjava/flixelgdx/signal/FlixelSignalData.java new file mode 100644 index 0000000..466963d --- /dev/null +++ b/core/src/main/java/me/stringfromjava/flixelgdx/signal/FlixelSignalData.java @@ -0,0 +1,22 @@ +package me.stringfromjava.flixelgdx.signal; + +import games.rednblack.miniaudio.MASound; +import me.stringfromjava.flixelgdx.graphics.screen.FlixelScreen; +import me.stringfromjava.flixelgdx.Flixel; + +/** + * Convenience class for holding all signal data records used in the default signals stored in + * the global {@link Flixel} manager class. + */ +public final class FlixelSignalData { + + public record RenderSignalData(float delta) {} + + public record ScreenSwitchSignalData(FlixelScreen screen) {} + + public record SoundPlayedSignalData(MASound sound) {} + + public record MusicPlayedSignalData(MASound music) {} + + private FlixelSignalData() {} +} diff --git a/core/src/main/java/me/stringfromjava/funkin/tween/FunkinEase.java b/core/src/main/java/me/stringfromjava/flixelgdx/tween/FlixelEase.java similarity index 96% rename from core/src/main/java/me/stringfromjava/funkin/tween/FunkinEase.java rename to core/src/main/java/me/stringfromjava/flixelgdx/tween/FlixelEase.java index 5d89e43..170b26a 100644 --- a/core/src/main/java/me/stringfromjava/funkin/tween/FunkinEase.java +++ b/core/src/main/java/me/stringfromjava/flixelgdx/tween/FlixelEase.java @@ -1,7 +1,7 @@ -package me.stringfromjava.funkin.tween; +package me.stringfromjava.flixelgdx.tween; /** Class where all easer functions are stored, mostly used for tweening. */ -public final class FunkinEase { +public final class FlixelEase { // Easing constants for specific functions. private static final float PI2 = (float) Math.PI / 2; @@ -15,7 +15,7 @@ public final class FunkinEase { private static final float ELASTIC_AMPLITUDE = 1; private static final float ELASTIC_PERIOD = 0.4f; - private FunkinEase() {} + private FlixelEase() {} public static float linear(float t) { return t; @@ -205,17 +205,17 @@ public interface FunkinEaseFunction { @FunctionalInterface public interface FunkinEaseStartCallback { - void run(FunkinTween tween); + void run(FlixelTween tween); } @FunctionalInterface public interface FunkinEaseUpdateCallback { - void run(FunkinTween tween); + void run(FlixelTween tween); } @FunctionalInterface public interface FunkinEaseCompleteCallback { - void run(FunkinTween tween); + void run(FlixelTween tween); } } diff --git a/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTween.java b/core/src/main/java/me/stringfromjava/flixelgdx/tween/FlixelTween.java similarity index 84% rename from core/src/main/java/me/stringfromjava/funkin/tween/FunkinTween.java rename to core/src/main/java/me/stringfromjava/flixelgdx/tween/FlixelTween.java index 2f7b21c..4a9aba1 100644 --- a/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTween.java +++ b/core/src/main/java/me/stringfromjava/flixelgdx/tween/FlixelTween.java @@ -1,10 +1,10 @@ -package me.stringfromjava.funkin.tween; +package me.stringfromjava.flixelgdx.tween; import com.badlogic.gdx.utils.Pool; -import me.stringfromjava.funkin.Funkin; -import me.stringfromjava.funkin.tween.settings.FunkinTweenSettings; -import me.stringfromjava.funkin.util.Constants; -import me.stringfromjava.funkin.util.FunkinReflectUtil; +import me.stringfromjava.flixelgdx.Flixel; +import me.stringfromjava.flixelgdx.tween.settings.FlixelTweenSettings; +import me.stringfromjava.flixelgdx.util.FlixelConstants; +import me.stringfromjava.flixelgdx.util.FlixelReflectUtil; import org.jetbrains.annotations.NotNull; import java.lang.reflect.Field; @@ -19,25 +19,25 @@ * *

Note that this doesn't have to be used on sprites, it can be used on just about anything! */ -public class FunkinTween implements Pool.Poolable { +public class FlixelTween implements Pool.Poolable { /** The global tween manager for the entire game. */ - protected static FunkinTweenManager globalManager = new FunkinTweenManager(); + protected static FlixelTweenManager globalManager = new FlixelTweenManager(); /** The object to tween. */ protected Object object; /** The settings used for how the tween is handled and calculated (aka how it looks and animates). */ - protected FunkinTweenSettings tweenSettings; + protected FlixelTweenSettings tweenSettings; /** The parent manager that {@code this} tween gets updated in. */ - protected FunkinTweenManager manager; + protected FlixelTweenManager manager; /** How far the tween is tweening itself. This is what's used to actually tween the object! */ protected float scale = 0.0f; /** The update callback for {@code this} tween to change the objects values every update. */ - protected FunkinTween.FunkinTweenUpdateCallback updateCallback; + protected FlixelTween.FunkinTweenUpdateCallback updateCallback; /** How many seconds has elapsed since {@code this} tween started. */ protected float secondsSinceStart = 0.0f; @@ -67,14 +67,14 @@ public class FunkinTween implements Pool.Poolable { protected Field[] fieldsCache = null; /** Default constructor for pooling purposes. */ - protected FunkinTween() {} + protected FlixelTween() {} /** * Constructs a new tween. * *

Note that this does NOT add the tween to the global manager, it just assigns its main * values. That's it. If you wish to create a tween to automatically start working, you might want - * to see {@link FunkinTween#tween(Object object, FunkinTweenSettings tweenSettings, + * to see {@link FlixelTween#tween(Object object, FlixelTweenSettings tweenSettings, * FunkinTweenUpdateCallback updateCallback)}. * * @param object The object to tween values. @@ -82,8 +82,8 @@ protected FunkinTween() {} * for. * @param updateCallback Callback function for updating the objects values when the tween updates. */ - protected FunkinTween( - Object object, FunkinTweenSettings settings, FunkinTweenUpdateCallback updateCallback) { + protected FlixelTween( + Object object, FlixelTweenSettings settings, FunkinTweenUpdateCallback updateCallback) { this.object = object; this.tweenSettings = settings; this.updateCallback = updateCallback; @@ -97,9 +97,9 @@ protected FunkinTween( * @param updateCallback Callback function for updating the objects values when the tween updates. * @return The newly created and started tween. */ - public static FunkinTween tween( - Object object, FunkinTweenSettings tweenSettings, FunkinTweenUpdateCallback updateCallback) { - return new FunkinTween(object, tweenSettings, updateCallback).setManager(globalManager).start(); + public static FlixelTween tween( + Object object, FlixelTweenSettings tweenSettings, FunkinTweenUpdateCallback updateCallback) { + return new FlixelTween(object, tweenSettings, updateCallback).setManager(globalManager).start(); } public void update(float delta) { @@ -154,7 +154,7 @@ public void update(float delta) { var newValues = new HashMap(); var goals = tweenSettings.getGoalFields(); for (String field : goals) { - FunkinTweenSettings.FunkinTweenGoal goal = tweenSettings.getGoal(field); + FlixelTweenSettings.FunkinTweenGoal goal = tweenSettings.getGoal(field); if (goal == null) { continue; } @@ -189,23 +189,23 @@ public void update(float delta) { * * @return {@code this} tween. */ - public FunkinTween start() { + public FlixelTween start() { resetBasic(); running = true; finished = false; if (tweenSettings == null) { - Funkin.warn("FunkinTween", "No tween settings were provided for the tween."); + Flixel.warn("FlixelTween", "No tween settings were provided for the tween."); return this; } var neededFields = tweenSettings.getGoalFields(); if (neededFields == null || neededFields.isEmpty()) { - Funkin.warn("FunkinTween", "No fields were provided to tween on the object."); + Flixel.warn("FlixelTween", "No fields were provided to tween on the object."); return this; } if (fieldsCache == null) { - fieldsCache = FunkinReflectUtil.getAllFieldsAsArray(object.getClass()); + fieldsCache = FlixelReflectUtil.getAllFieldsAsArray(object.getClass()); } // Ensure that the fields provided actually exist on the object and are floating point values. @@ -215,7 +215,7 @@ public FunkinTween start() { for (String neededField : neededFields) { if (!fieldIds.contains(neededField)) { String message = "Field \"" + neededField + "\" does not exist on the given object."; - Funkin.error("FunkinTween", message); + Flixel.error("FlixelTween", message); throw new RuntimeException(message); } } @@ -234,8 +234,8 @@ public FunkinTween start() { } initialValues.put(fName, field.getFloat(object)); } catch (IllegalAccessException e) { - Funkin.error( - Constants.System.LOG_TAG, "Could not access field \"" + field.getName() + "\".", e); + Flixel.error( + FlixelConstants.System.LOG_TAG, "Could not access field \"" + field.getName() + "\".", e); } } return this; @@ -246,7 +246,7 @@ public FunkinTween start() { * * @return {@code this} tween. */ - public FunkinTween resume() { + public FlixelTween resume() { paused = false; return this; } @@ -256,7 +256,7 @@ public FunkinTween resume() { * * @return {@code this} tween. */ - public FunkinTween pause() { + public FlixelTween pause() { paused = true; running = false; return this; @@ -268,7 +268,7 @@ public FunkinTween pause() { * * @return {@code this} tween. */ - public FunkinTween stop() { + public FlixelTween stop() { running = false; finished = true; return this; @@ -280,7 +280,7 @@ public FunkinTween stop() { * * @return {@code this} tween. */ - public FunkinTween cancel() { + public FlixelTween cancel() { return cancel(true); } @@ -290,7 +290,7 @@ public FunkinTween cancel() { * @param remove Should {@code this} tween be removed from its manager? * @return {@code this} tween. */ - public FunkinTween cancel(boolean remove) { + public FlixelTween cancel(boolean remove) { reset(); if (remove) { manager.getTweenPool().free(this); @@ -320,20 +320,20 @@ public void resetBasic() { initialValues.clear(); } - public FunkinTweenSettings getTweenSettings() { + public FlixelTweenSettings getTweenSettings() { return tweenSettings; } - public FunkinTween setTweenSettings(@NotNull FunkinTweenSettings tweenSettings) { + public FlixelTween setTweenSettings(@NotNull FlixelTweenSettings tweenSettings) { this.tweenSettings = tweenSettings; return this; } - public static FunkinTweenManager getGlobalManager() { + public static FlixelTweenManager getGlobalManager() { return globalManager; } - public FunkinTween setManager(FunkinTweenManager newManager) { + public FlixelTween setManager(FlixelTweenManager newManager) { if (newManager != null) { if (manager != null) { int index = manager.getActiveTweens().indexOf(this, true); diff --git a/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTweenManager.java b/core/src/main/java/me/stringfromjava/flixelgdx/tween/FlixelTweenManager.java similarity index 70% rename from core/src/main/java/me/stringfromjava/funkin/tween/FunkinTweenManager.java rename to core/src/main/java/me/stringfromjava/flixelgdx/tween/FlixelTweenManager.java index a6c20e5..431f10e 100644 --- a/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTweenManager.java +++ b/core/src/main/java/me/stringfromjava/flixelgdx/tween/FlixelTweenManager.java @@ -1,19 +1,19 @@ -package me.stringfromjava.funkin.tween; +package me.stringfromjava.flixelgdx.tween; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Pool; -/** Core manager class for handling all {@link FunkinTween}s that are currently active. */ -public class FunkinTweenManager { +/** Manager class for handling a list of active {@link FlixelTween}s. */ +public class FlixelTweenManager { /** Array where all current active tweens are stored. */ - protected final Array activeTweens = new Array<>(FunkinTween.class); + protected final Array activeTweens = new Array<>(FlixelTween.class); /** A pool where all unused tweens are stored to preserve memory. */ - protected final Pool tweenPool = new Pool<>() { + protected final Pool tweenPool = new Pool<>() { @Override - protected FunkinTween newObject() { - return new FunkinTween(); + protected FlixelTween newObject() { + return new FlixelTween(); } }; @@ -50,11 +50,11 @@ public void update(float delta) { } } - public Array getActiveTweens() { + public Array getActiveTweens() { return activeTweens; } - public Pool getTweenPool() { + public Pool getTweenPool() { return tweenPool; } } diff --git a/core/src/main/java/me/stringfromjava/funkin/tween/settings/FunkinTweenSettings.java b/core/src/main/java/me/stringfromjava/flixelgdx/tween/settings/FlixelTweenSettings.java similarity index 66% rename from core/src/main/java/me/stringfromjava/funkin/tween/settings/FunkinTweenSettings.java rename to core/src/main/java/me/stringfromjava/flixelgdx/tween/settings/FlixelTweenSettings.java index 554cd2a..17598b2 100644 --- a/core/src/main/java/me/stringfromjava/funkin/tween/settings/FunkinTweenSettings.java +++ b/core/src/main/java/me/stringfromjava/flixelgdx/tween/settings/FlixelTweenSettings.java @@ -1,6 +1,7 @@ -package me.stringfromjava.funkin.tween.settings; +package me.stringfromjava.flixelgdx.tween.settings; -import me.stringfromjava.funkin.tween.FunkinEase; +import me.stringfromjava.flixelgdx.tween.FlixelTween; +import me.stringfromjava.flixelgdx.tween.FlixelEase; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -10,39 +11,39 @@ /** * Class for holding basic data, containing configurations to be used on a {@link - * me.stringfromjava.funkin.tween.FunkinTween}. + * FlixelTween}. */ -public class FunkinTweenSettings { +public class FlixelTweenSettings { private float duration; private float startDelay; private float loopDelay; private float framerate; - private FunkinTweenType type; - private FunkinEase.FunkinEaseFunction ease; - private FunkinEase.FunkinEaseStartCallback onStart; - private FunkinEase.FunkinEaseUpdateCallback onUpdate; - private FunkinEase.FunkinEaseCompleteCallback onComplete; + private FlixelTweenType type; + private FlixelEase.FunkinEaseFunction ease; + private FlixelEase.FunkinEaseStartCallback onStart; + private FlixelEase.FunkinEaseUpdateCallback onUpdate; + private FlixelEase.FunkinEaseCompleteCallback onComplete; private ArrayList goals; - public FunkinTweenSettings() { - this(FunkinTweenType.ONESHOT, FunkinEase::linear); + public FlixelTweenSettings() { + this(FlixelTweenType.ONESHOT, FlixelEase::linear); } /** * @param type The type of tween it should be. */ - public FunkinTweenSettings(@NotNull FunkinTweenType type) { - this(type, FunkinEase::linear); + public FlixelTweenSettings(@NotNull FlixelTweenType type) { + this(type, FlixelEase::linear); } /** * @param type The type of tween it should be. * @param ease The easer function the tween should use (aka how it should be animated). */ - public FunkinTweenSettings( - @NotNull FunkinTweenType type, - @Nullable FunkinEase.FunkinEaseFunction ease) { + public FlixelTweenSettings( + @NotNull FlixelTweenType type, + @Nullable FlixelEase.FunkinEaseFunction ease) { this.duration = 1.0f; this.startDelay = 0.0f; this.loopDelay = 0.0f; @@ -62,7 +63,7 @@ public FunkinTweenSettings( * @param value The value to tween the field to. * @return {@code this} tween settings object for chaining. */ - public FunkinTweenSettings addGoal(String field, float value) { + public FlixelTweenSettings addGoal(String field, float value) { goals.add(new FunkinTweenGoal(field, value)); return this; } @@ -73,7 +74,7 @@ public FunkinTweenSettings addGoal(String field, float value) { * @param duration The new value to set. * @return {@code this} tween settings object for chaining. */ - public FunkinTweenSettings setDuration(float duration) { + public FlixelTweenSettings setDuration(float duration) { this.duration = duration; return this; } @@ -82,23 +83,23 @@ public float getDuration() { return duration; } - public FunkinTweenType getType() { + public FlixelTweenType getType() { return type; } - public FunkinEase.FunkinEaseFunction getEase() { + public FlixelEase.FunkinEaseFunction getEase() { return ease; } - public FunkinEase.FunkinEaseStartCallback getOnStart() { + public FlixelEase.FunkinEaseStartCallback getOnStart() { return onStart; } - public FunkinEase.FunkinEaseUpdateCallback getOnUpdate() { + public FlixelEase.FunkinEaseUpdateCallback getOnUpdate() { return onUpdate; } - public FunkinEase.FunkinEaseCompleteCallback getOnComplete() { + public FlixelEase.FunkinEaseCompleteCallback getOnComplete() { return onComplete; } @@ -135,7 +136,7 @@ public float getFramerate() { return framerate; } - public FunkinTweenSettings setEase(FunkinEase.FunkinEaseFunction ease) { + public FlixelTweenSettings setEase(FlixelEase.FunkinEaseFunction ease) { this.ease = ease; return this; } @@ -144,22 +145,22 @@ public void clearGoals() { goals.clear(); } - public FunkinTweenSettings setStartDelay(float startDelay) { + public FlixelTweenSettings setStartDelay(float startDelay) { this.startDelay = startDelay; return this; } - public FunkinTweenSettings setLoopDelay(float loopDelay) { + public FlixelTweenSettings setLoopDelay(float loopDelay) { this.loopDelay = loopDelay; return this; } - public FunkinTweenSettings setFramerate(float framerate) { + public FlixelTweenSettings setFramerate(float framerate) { this.framerate = framerate; return this; } - public FunkinTweenSettings setType(@NotNull FunkinTweenType type) { + public FlixelTweenSettings setType(@NotNull FlixelTweenType type) { this.type = type; return this; } diff --git a/core/src/main/java/me/stringfromjava/funkin/tween/settings/FunkinTweenType.java b/core/src/main/java/me/stringfromjava/flixelgdx/tween/settings/FlixelTweenType.java similarity index 84% rename from core/src/main/java/me/stringfromjava/funkin/tween/settings/FunkinTweenType.java rename to core/src/main/java/me/stringfromjava/flixelgdx/tween/settings/FlixelTweenType.java index e5bf801..4dbcb34 100644 --- a/core/src/main/java/me/stringfromjava/funkin/tween/settings/FunkinTweenType.java +++ b/core/src/main/java/me/stringfromjava/flixelgdx/tween/settings/FlixelTweenType.java @@ -1,7 +1,7 @@ -package me.stringfromjava.funkin.tween.settings; +package me.stringfromjava.flixelgdx.tween.settings; /** Enum containing all different tween types that can determine */ -public enum FunkinTweenType { +public enum FlixelTweenType { /** Will stop and remove itself from the manager when it finishes. */ ONESHOT, diff --git a/core/src/main/java/me/stringfromjava/funkin/util/Constants.java b/core/src/main/java/me/stringfromjava/flixelgdx/util/FlixelConstants.java similarity index 61% rename from core/src/main/java/me/stringfromjava/funkin/util/Constants.java rename to core/src/main/java/me/stringfromjava/flixelgdx/util/FlixelConstants.java index d5d04db..5b1f95f 100644 --- a/core/src/main/java/me/stringfromjava/funkin/util/Constants.java +++ b/core/src/main/java/me/stringfromjava/flixelgdx/util/FlixelConstants.java @@ -1,32 +1,14 @@ -package me.stringfromjava.funkin.util; +package me.stringfromjava.flixelgdx.util; /** * Core class for holding static classes with values which do not change. It is NOT RECOMMENDED to * store things like {@link java.util.ArrayList}'s, as they can still be modified even if they are * initialized as {@code final}. */ -public final class Constants { - - /** Holds values for aspects of the game's display, such as the window's width and height. */ - public static final class Display { - - /** - * How wide the window's viewport is in pixels. This also affects how wide the window is when - * the game first starts up. - */ - public static final int WINDOW_WIDTH = 1280; - - /** - * How tall the window's viewport is in pixels. This also affects how tall the window is when - * the game first starts up. - */ - public static final int WINDOW_HEIGHT = 720; - - private Display() {} - } +public final class FlixelConstants { /** - * Stores constants for things related to the backend of Funkin'. This includes components like + * Stores constants for things related to the backend of Flixel. This includes components like * logging, folder paths, etc. */ public static final class System { @@ -35,7 +17,7 @@ public static final class System { * The default and globally recognized default tag for logs that are outputted inside the * console. */ - public static final String LOG_TAG = "Funkin"; + public static final String LOG_TAG = "Flixel"; private System() {} } diff --git a/core/src/main/java/me/stringfromjava/funkin/util/FunkinReflectUtil.java b/core/src/main/java/me/stringfromjava/flixelgdx/util/FlixelReflectUtil.java similarity index 90% rename from core/src/main/java/me/stringfromjava/funkin/util/FunkinReflectUtil.java rename to core/src/main/java/me/stringfromjava/flixelgdx/util/FlixelReflectUtil.java index ec53a0d..bef7dda 100644 --- a/core/src/main/java/me/stringfromjava/funkin/util/FunkinReflectUtil.java +++ b/core/src/main/java/me/stringfromjava/flixelgdx/util/FlixelReflectUtil.java @@ -1,6 +1,6 @@ -package me.stringfromjava.funkin.util; +package me.stringfromjava.flixelgdx.util; -import me.stringfromjava.funkin.Funkin; +import me.stringfromjava.flixelgdx.Flixel; import java.lang.reflect.Field; import java.lang.reflect.Modifier; @@ -13,7 +13,7 @@ * Backend utility class for obtaining and manipulating fields on objects through the usage of Java * reflection. */ -public class FunkinReflectUtil { +public class FlixelReflectUtil { /** * Checks if a field exists on a given object. @@ -65,7 +65,7 @@ public static Field[] getAllFieldsAsArray(Class type) { * Checks if a class of a certain package is final. * * @param classPath The package definition of the class to check if final. An example could be - * {@code "me.stringfromjava.funkin.Funkin"}. + * {@code "me.stringfromjava.flixelgdx.Flixel"}. * @return If the class provided is final. If there was an exception caught, then {@code false} is * automatically returned. */ @@ -77,7 +77,7 @@ public static boolean isClassFinal(String classPath) { } catch (ClassNotFoundException e) { // Treat non-existent class as non-final for safe binding, // though the user will hit an error later. - Funkin.error(Constants.System.LOG_TAG, "Failed to check if a class was final.", e); + Flixel.error(FlixelConstants.System.LOG_TAG, "Failed to check if a class was final.", e); return false; } } diff --git a/core/src/main/java/me/stringfromjava/funkin/util/FunkinRuntimeUtil.java b/core/src/main/java/me/stringfromjava/flixelgdx/util/FlixelRuntimeUtil.java similarity index 92% rename from core/src/main/java/me/stringfromjava/funkin/util/FunkinRuntimeUtil.java rename to core/src/main/java/me/stringfromjava/flixelgdx/util/FlixelRuntimeUtil.java index 6ab4507..2ce8b95 100644 --- a/core/src/main/java/me/stringfromjava/funkin/util/FunkinRuntimeUtil.java +++ b/core/src/main/java/me/stringfromjava/flixelgdx/util/FlixelRuntimeUtil.java @@ -1,10 +1,10 @@ -package me.stringfromjava.funkin.util; +package me.stringfromjava.flixelgdx.util; /** * Utility class for handling operation related to the runtime environment, including OS detection, * extracting runtime information, obtaining information from exceptions, and other related tasks. */ -public final class FunkinRuntimeUtil { +public final class FlixelRuntimeUtil { /** * Obtains a string representation of where an exception was thrown from, including the class, @@ -33,8 +33,7 @@ public static String getExceptionLocation(Throwable exception) { } /** - * Obtains a full detailed message from an exception, including its type, location, and stack - * trace. + * Obtains a full detailed message from an exception, including its type, location, and stack trace. * * @param exception The exception to obtain the message from. * @return A full detailed message from the exception. @@ -53,5 +52,5 @@ public static String getFullExceptionMessage(Throwable exception) { return messageBuilder.toString(); } - private FunkinRuntimeUtil() {} + private FlixelRuntimeUtil() {} } diff --git a/core/src/main/java/me/stringfromjava/funkin/FunkinGame.java b/core/src/main/java/me/stringfromjava/funkin/FunkinGame.java index f892d80..1d93000 100644 --- a/core/src/main/java/me/stringfromjava/funkin/FunkinGame.java +++ b/core/src/main/java/me/stringfromjava/funkin/FunkinGame.java @@ -1,224 +1,33 @@ package me.stringfromjava.funkin; -import com.badlogic.gdx.Application; -import com.badlogic.gdx.ApplicationListener; -import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.Input; -import com.badlogic.gdx.graphics.Color; -import com.badlogic.gdx.graphics.OrthographicCamera; -import com.badlogic.gdx.graphics.Pixmap; -import com.badlogic.gdx.graphics.Texture; -import com.badlogic.gdx.graphics.g2d.SpriteBatch; -import com.badlogic.gdx.scenes.scene2d.Stage; -import com.badlogic.gdx.utils.ScreenUtils; -import com.badlogic.gdx.utils.viewport.FitViewport; -import me.stringfromjava.funkin.backend.Paths; -import me.stringfromjava.funkin.game.InitScreen; -import me.stringfromjava.funkin.graphics.screen.FunkinScreen; -import me.stringfromjava.funkin.graphics.sprite.FunkinObject; -import me.stringfromjava.funkin.graphics.sprite.FunkinSprite; -import me.stringfromjava.funkin.polyverse.Polyverse; -import me.stringfromjava.funkin.polyverse.script.type.Script; -import me.stringfromjava.funkin.polyverse.script.type.SystemScript; -import me.stringfromjava.funkin.tween.FunkinTween; -import me.stringfromjava.funkin.util.Constants; -import me.stringfromjava.funkin.util.FunkinRuntimeUtil; - -import static me.stringfromjava.funkin.util.signal.FunkinSignalData.RenderSignalData; +import me.stringfromjava.flixelgdx.Flixel; +import me.stringfromjava.flixelgdx.FlixelGame; +import me.stringfromjava.flixelgdx.backend.FlixelPaths; +import me.stringfromjava.flixelgdx.graphics.screen.FlixelScreen; +import me.stringfromjava.polyverse.Polyverse; +import me.stringfromjava.polyverse.script.type.Script; +import me.stringfromjava.polyverse.script.type.SystemScript; /** - * The game object used for containing the main loop and core elements of Funkin'. - * - *

If you want to change what happens to the pre and window configurations, you might want to see - * {@code Lwjgl3Launcher} in the {@code lwjgl3} folder. + * The main Funkin' game class that initializes and runs the game. */ -public class FunkinGame implements ApplicationListener { - - /** Is the game's window currently minimized? */ - protected boolean isMinimized = false; - - /** The main stage used for rendering all screens and sprites on screen. */ - protected Stage stage; - - /** The main viewport used to fit the world no matter the screen size. */ - protected FitViewport viewport; - - /** The main camera used to see the world. */ - protected OrthographicCamera camera; - - /** The main sprite batch used for rendering all sprites on screen. */ - protected SpriteBatch batch; +public class FunkinGame extends FlixelGame { - /** The 1x1 texture used to draw the background color of the current screen. */ - protected Texture bgTexture; - - @Override - public void create() { - configureCrashHandler(); // Crash handler for uncaught exceptions. - configureWindow(); // Window and viewport. - configurePolyverse(); // Polyverse scripting and modding system. - Funkin.setScreen(new InitScreen()); + public FunkinGame(String title, int width, int height, FlixelScreen initialScreen) { + super(title, width, height, initialScreen); } @Override - public void resize(int width, int height) { - viewport.update(width, height, true); + public void create() { + super.create(); + configurePolyverse(); // Scripting and modding support. } @Override public void render() { - float delta = Gdx.graphics.getDeltaTime(); - FunkinScreen screen = Funkin.getScreen(); - - Funkin.Signals.preRender.dispatch(new RenderSignalData(delta)); - - if (Funkin.keyJustPressed(Input.Keys.F11)) { - toggleFullscreen(); - } - - // Update and render the current screen that's active. - ScreenUtils.clear(Color.BLACK); - viewport.apply(); - batch.setProjectionMatrix(camera.combined); - batch.begin(); - - if (screen != null) { - batch.setColor(screen.getBgColor()); - batch.draw(bgTexture, 0, 0, viewport.getWorldWidth(), viewport.getWorldHeight()); - batch.setColor(Color.WHITE); // Set color back to white so display objects aren't affected. - screen.render(delta); - var members = screen.members.begin(); - for (FunkinObject object : members) { - if (object instanceof FunkinSprite sprite) { - sprite.update(delta); - sprite.draw(batch); - } - } - screen.members.end(); - } - - batch.end(); - stage.act(delta); - stage.draw(); - - FunkinTween.getGlobalManager().update(delta); - Polyverse.forAllScripts(script -> script.onRender(delta)); - - Funkin.Signals.postRender.dispatch(new RenderSignalData(delta)); - } - - @Override - public void pause() {} - - @Override - public void resume() {} - - /** Called when the user regains focus on the game's window. */ - public void onWindowFocused() { - Funkin.setMasterVolume(1); - Funkin.Signals.windowFocused.dispatch(); - Funkin.info("Game window has regained focus."); - } - - /** Called when the user loses focus on the game's window, while also not being minimized. */ - public void onWindowUnfocused() { - if (isMinimized) { - return; - } - Funkin.setMasterVolume(0.008f); - Funkin.Signals.windowUnfocused.dispatch(); - Funkin.info("Game window has lost focus."); - } - - /** - * Called when the user minimizes the game's window. - * - * @param iconified Whether the window is iconified (minimized) or not. This parameter is provided - * for compatibility with the window listener in the LWJGL3 (desktop) launcher. - */ - public void onWindowMinimized(boolean iconified) { - isMinimized = iconified; - if (!isMinimized) { - return; - } - Funkin.setMasterVolume(0); - Funkin.Signals.windowMinimized.dispatch(); - Funkin.info("Game window has been minimized."); - } - - /** Toggles fullscreen mode on or off, depending on the current state. */ - public void toggleFullscreen() { - boolean isFullscreen = Gdx.graphics.isFullscreen(); - if (isFullscreen) { - Gdx.graphics.setWindowedMode(Constants.Display.WINDOW_WIDTH, Constants.Display.WINDOW_HEIGHT); - Funkin.info("Exiting fullscreen mode."); - } else { - Gdx.graphics.setFullscreenMode(Gdx.graphics.getDisplayMode()); - Funkin.info("Entering fullscreen mode."); - } - } - - @Override - public void dispose() { - Funkin.warn("SHUTTING DOWN GAME AND DISPOSING ALL RESOURCES."); - - Funkin.Signals.preGameClose.dispatch(); - - Funkin.info("Disposing the screen display..."); - Funkin.getScreen().hide(); - Funkin.getScreen().dispose(); - stage.dispose(); - batch.dispose(); - bgTexture.dispose(); - - Funkin.info("Disposing all sounds from sound group and music..."); - if (Funkin.getMusic() != null) { - Funkin.getMusic().dispose(); - } - Funkin.getSoundsGroup().dispose(); - Funkin.getAudioEngine().dispose(); - - Funkin.info("Disposing and shutting down scripts..."); - Polyverse.forAllScripts(Script::onDispose); - - Funkin.Signals.postGameClose.dispatch(); - } - - public boolean isMinimized() { - return isMinimized; - } - - private void configureCrashHandler() { - Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> { - String logs = FunkinRuntimeUtil.getFullExceptionMessage(throwable); - String msg = "There was an uncaught exception on thread \"" + thread.getName() + "\"!\n" + logs; - Funkin.error(Constants.System.LOG_TAG, msg); - dispose(); - // Only use Gdx.app.exit() on non-iOS platforms to avoid App Store guideline violations! - if (Gdx.app.getType() != Application.ApplicationType.iOS) { - Gdx.app.exit(); - } - }); - } - - private void configureWindow() { - var wWidth = Constants.Display.WINDOW_WIDTH; - var wHeight = Constants.Display.WINDOW_HEIGHT; - - batch = new SpriteBatch(); - viewport = new FitViewport(wWidth, wHeight); - viewport.apply(); - - camera = new OrthographicCamera(); - camera.setToOrtho(false, wWidth, wHeight); - - stage = new Stage(viewport, batch); + super.render(); - Pixmap pixmap = new Pixmap(1, 1, Pixmap.Format.RGBA8888); - pixmap.setColor(Color.WHITE); - pixmap.fill(); - bgTexture = new Texture(pixmap); - pixmap.dispose(); + Polyverse.forAllScripts(script -> script.onRender(Flixel.getDelta())); } private void configurePolyverse() { @@ -226,7 +35,7 @@ private void configurePolyverse() { Polyverse.registerScriptType(Script.class); // Master type, DO NOT REMOVE THIS! Polyverse.registerScriptType(SystemScript.class); - Polyverse.registerScript(Paths.asset("test.groovy")); - Polyverse.registerScript(Paths.asset("another_test.groovy")); + Polyverse.registerScript(FlixelPaths.asset("test.groovy")); + Polyverse.registerScript(FlixelPaths.asset("another_test.groovy")); } } diff --git a/core/src/main/java/me/stringfromjava/funkin/game/InitScreen.java b/core/src/main/java/me/stringfromjava/funkin/game/InitScreen.java deleted file mode 100644 index 6f67f69..0000000 --- a/core/src/main/java/me/stringfromjava/funkin/game/InitScreen.java +++ /dev/null @@ -1,14 +0,0 @@ -package me.stringfromjava.funkin.game; - -import me.stringfromjava.funkin.Funkin; -import me.stringfromjava.funkin.game.menus.TitleScreen; -import me.stringfromjava.funkin.graphics.screen.FunkinScreen; - -public class InitScreen extends FunkinScreen { - - @Override - public void show() { - super.show(); - Funkin.setScreen(new TitleScreen()); - } -} diff --git a/core/src/main/java/me/stringfromjava/funkin/game/menus/TitleScreen.java b/core/src/main/java/me/stringfromjava/funkin/game/menus/TitleScreen.java deleted file mode 100644 index ab95517..0000000 --- a/core/src/main/java/me/stringfromjava/funkin/game/menus/TitleScreen.java +++ /dev/null @@ -1,90 +0,0 @@ -package me.stringfromjava.funkin.game.menus; - -import com.badlogic.gdx.Input; -import games.rednblack.miniaudio.MASound; -import me.stringfromjava.funkin.Funkin; -import me.stringfromjava.funkin.backend.Paths; -import me.stringfromjava.funkin.graphics.screen.FunkinScreen; -import me.stringfromjava.funkin.graphics.sprite.FunkinSprite; -import me.stringfromjava.funkin.tween.FunkinEase; -import me.stringfromjava.funkin.tween.FunkinTween; -import me.stringfromjava.funkin.tween.settings.FunkinTweenSettings; -import me.stringfromjava.funkin.tween.settings.FunkinTweenType; - -public class TitleScreen extends FunkinScreen { - - private FunkinSprite logo; - - private FunkinTween tween; - private MASound tickleFight; - - @Override - public void show() { - super.show(); - - var t = Paths.sharedImageAsset("noteStrumline"); - var xml = Paths.shared("images/noteStrumline.xml"); - logo = new FunkinSprite().loadSparrowFrames(t, xml); - logo.addAnimationByPrefix("test", "confirmDown", 24, false); - add(logo); - - tickleFight = Funkin.playSound("shared/sounds/tickleFight.ogg"); -// Funkin.playMusic("preload/music/freakyMenu/freakyMenu.ogg", 0.5f); - - FunkinTweenSettings settings = new FunkinTweenSettings() - .addGoal("x", 600) - .addGoal("y", 40) - .addGoal("rotation", 180) - .setDuration(0.7f) - .setEase(FunkinEase::circInOut) - .setType(FunkinTweenType.PERSIST); - tween = FunkinTween.tween(logo, settings, values -> { - logo.setX(values.get("x")); - logo.setY(values.get("y")); - logo.setRotation(values.get("rotation")); - }).stop(); - } - - @Override - public void render(float elapsed) { - super.render(elapsed); - - float speed = 500 * elapsed; - if (Funkin.keyPressed(Input.Keys.W)) { - logo.setY(logo.getY() + speed); - } - if (Funkin.keyPressed(Input.Keys.S)) { - logo.setY(logo.getY() - speed); - } - if (Funkin.keyPressed(Input.Keys.A)) { - logo.setX(logo.getX() - speed); - } - if (Funkin.keyPressed(Input.Keys.D)) { - logo.setX(logo.getX() + speed); - } - - if (Funkin.keyJustPressed(Input.Keys.SPACE)) { - logo.playAnimation("test", true); - } - - if (Funkin.keyJustPressed(Input.Keys.T)) { - tween.start(); - } - - if (Funkin.keyJustPressed(Input.Keys.R)) { - tween.reset(); - } - - if (Funkin.keyJustPressed(Input.Keys.Y)) { - if (tween.paused) { - tween.resume(); - } else { - tween.pause(); - } - } - - if (Funkin.keyJustPressed(Input.Keys.Z)) { - tickleFight.play(); - } - } -} diff --git a/core/src/main/java/me/stringfromjava/funkin/graphics/sprite/FunkinObject.java b/core/src/main/java/me/stringfromjava/funkin/graphics/sprite/FunkinObject.java deleted file mode 100644 index f7ee545..0000000 --- a/core/src/main/java/me/stringfromjava/funkin/graphics/sprite/FunkinObject.java +++ /dev/null @@ -1,7 +0,0 @@ -package me.stringfromjava.funkin.graphics.sprite; - -/** - * An interface which allows any class that implements it to be added to a {@link - * me.stringfromjava.funkin.graphics.screen.FunkinScreen}. - */ -public interface FunkinObject {} diff --git a/core/src/main/java/me/stringfromjava/funkin/init/InitScreen.java b/core/src/main/java/me/stringfromjava/funkin/init/InitScreen.java new file mode 100644 index 0000000..1f89de2 --- /dev/null +++ b/core/src/main/java/me/stringfromjava/funkin/init/InitScreen.java @@ -0,0 +1,14 @@ +package me.stringfromjava.funkin.init; + +import me.stringfromjava.flixelgdx.graphics.screen.FlixelScreen; +import me.stringfromjava.flixelgdx.Flixel; +import me.stringfromjava.funkin.menus.TitleScreen; + +public class InitScreen extends FlixelScreen { + + @Override + public void show() { + super.show(); + Flixel.setScreen(new TitleScreen()); + } +} diff --git a/core/src/main/java/me/stringfromjava/funkin/menus/TitleScreen.java b/core/src/main/java/me/stringfromjava/funkin/menus/TitleScreen.java new file mode 100644 index 0000000..bd6715d --- /dev/null +++ b/core/src/main/java/me/stringfromjava/funkin/menus/TitleScreen.java @@ -0,0 +1,90 @@ +package me.stringfromjava.funkin.menus; + +import com.badlogic.gdx.Input; +import games.rednblack.miniaudio.MASound; +import me.stringfromjava.flixelgdx.Flixel; +import me.stringfromjava.flixelgdx.backend.FlixelPaths; +import me.stringfromjava.flixelgdx.graphics.screen.FlixelScreen; +import me.stringfromjava.flixelgdx.graphics.sprite.FlixelSprite; +import me.stringfromjava.flixelgdx.tween.FlixelEase; +import me.stringfromjava.flixelgdx.tween.FlixelTween; +import me.stringfromjava.flixelgdx.tween.settings.FlixelTweenSettings; +import me.stringfromjava.flixelgdx.tween.settings.FlixelTweenType; + +public class TitleScreen extends FlixelScreen { + + private FlixelSprite logo; + + private FlixelTween tween; + private MASound tickleFight; + + @Override + public void show() { + super.show(); + + var t = FlixelPaths.sharedImageAsset("noteStrumline"); + var xml = FlixelPaths.shared("images/noteStrumline.xml"); + logo = new FlixelSprite().loadSparrowFrames(t, xml); + logo.addAnimationByPrefix("test", "confirmDown", 24, false); + add(logo); + + tickleFight = Flixel.playSound("shared/sounds/tickleFight.ogg"); +// Flixel.playMusic("preload/music/freakyMenu/freakyMenu.ogg", 0.5f); + + FlixelTweenSettings settings = new FlixelTweenSettings() + .addGoal("x", 600) + .addGoal("y", 40) + .addGoal("rotation", 180) + .setDuration(0.7f) + .setEase(FlixelEase::circInOut) + .setType(FlixelTweenType.PERSIST); + tween = FlixelTween.tween(logo, settings, values -> { + logo.setX(values.get("x")); + logo.setY(values.get("y")); + logo.setRotation(values.get("rotation")); + }).stop(); + } + + @Override + public void render(float elapsed) { + super.render(elapsed); + + float speed = 500 * elapsed; + if (Flixel.keyPressed(Input.Keys.W)) { + logo.setY(logo.getY() + speed); + } + if (Flixel.keyPressed(Input.Keys.S)) { + logo.setY(logo.getY() - speed); + } + if (Flixel.keyPressed(Input.Keys.A)) { + logo.setX(logo.getX() - speed); + } + if (Flixel.keyPressed(Input.Keys.D)) { + logo.setX(logo.getX() + speed); + } + + if (Flixel.keyJustPressed(Input.Keys.SPACE)) { + logo.playAnimation("test", true); + } + + if (Flixel.keyJustPressed(Input.Keys.T)) { + tween.start(); + } + + if (Flixel.keyJustPressed(Input.Keys.R)) { + tween.reset(); + } + + if (Flixel.keyJustPressed(Input.Keys.Y)) { + if (tween.paused) { + tween.resume(); + } else { + tween.pause(); + } + } + + if (Flixel.keyJustPressed(Input.Keys.Z)) { + tickleFight.play(); + } + } +} diff --git a/core/src/main/java/me/stringfromjava/funkin/util/FunkinConstants.java b/core/src/main/java/me/stringfromjava/funkin/util/FunkinConstants.java new file mode 100644 index 0000000..ced76f0 --- /dev/null +++ b/core/src/main/java/me/stringfromjava/funkin/util/FunkinConstants.java @@ -0,0 +1,26 @@ +package me.stringfromjava.funkin.util; + +/** + * Constants used throughout Funkin'. + */ +public final class FunkinConstants { + + /** + * The default title for the game's window. + */ + public static final String WINDOW_TITLE = "Friday Night Funkin': Java Edition"; + + /** + * How wide the window's viewport is in pixels. This also affects how wide the window is when + * the game first starts up. + */ + public static final int WINDOW_WIDTH = 1280; + + /** + * How tall the window's viewport is in pixels. This also affects how tall the window is when + * the game first starts up. + */ + public static final int WINDOW_HEIGHT = 720; + + private FunkinConstants() {} +} diff --git a/core/src/main/java/me/stringfromjava/funkin/util/signal/FunkinSignalData.java b/core/src/main/java/me/stringfromjava/funkin/util/signal/FunkinSignalData.java deleted file mode 100644 index 93f71ad..0000000 --- a/core/src/main/java/me/stringfromjava/funkin/util/signal/FunkinSignalData.java +++ /dev/null @@ -1,32 +0,0 @@ -package me.stringfromjava.funkin.util.signal; - -import games.rednblack.miniaudio.MASound; -import me.stringfromjava.funkin.graphics.screen.FunkinScreen; - -/** - * Convenience class for holding all signal data records used in the default signals stored in - * the global {@link me.stringfromjava.funkin.Funkin} manager class. - */ -public final class FunkinSignalData { - - public record RenderSignalData(float delta) { - - } - - public record ScreenSwitchSignalData(FunkinScreen screen) { - - } - - public record SoundPlayedSignalData(MASound sound) { - - } - - public record MusicPlayedSignalData(MASound music) { - - } - - private FunkinSignalData() { - } - -} - diff --git a/core/src/main/java/me/stringfromjava/funkin/polyverse/Polyverse.java b/core/src/main/java/me/stringfromjava/polyverse/Polyverse.java similarity index 91% rename from core/src/main/java/me/stringfromjava/funkin/polyverse/Polyverse.java rename to core/src/main/java/me/stringfromjava/polyverse/Polyverse.java index 81c746c..6daace4 100644 --- a/core/src/main/java/me/stringfromjava/funkin/polyverse/Polyverse.java +++ b/core/src/main/java/me/stringfromjava/polyverse/Polyverse.java @@ -1,9 +1,9 @@ -package me.stringfromjava.funkin.polyverse; +package me.stringfromjava.polyverse; import com.badlogic.gdx.files.FileHandle; import groovy.lang.GroovyClassLoader; -import me.stringfromjava.funkin.Funkin; -import me.stringfromjava.funkin.polyverse.script.type.Script; +import me.stringfromjava.flixelgdx.Flixel; +import me.stringfromjava.polyverse.script.type.Script; import java.util.ArrayList; import java.util.Collections; @@ -12,7 +12,7 @@ import java.util.Map; import java.util.function.Consumer; -/** Core manager class for managing the Polyverse mod engine for FNF:JE. */ +/** Core manager class for managing the Polyverse scripting and modding engine. */ public final class Polyverse { /** A map that stores lists of scripts based on their parent class type. */ @@ -81,7 +81,7 @@ public static void registerScript(FileHandle handle) { var typeScripts = scripts.get(mostSpecificType); if (!typeScripts.contains(script)) { typeScripts.add(script); - Funkin.info( + Flixel.info( "Polyverse", "Registered Polyverse script \"" + script.getClass().getSimpleName() @@ -93,14 +93,14 @@ public static void registerScript(FileHandle handle) { script.onCreate(); } } catch (Exception e) { - Funkin.error("Polyverse", "Failed to load script: " + handle.path(), e); + Flixel.error("Polyverse", "Failed to load script: " + handle.path(), e); } } /** * Executes an action for each script of a certain type, with error handling. * - * @param type The class type of scripts to iterate over. + * @param type The class type of scripts to iterate over. * @param action The action to perform on each script. */ public static void forEachScript(Class type, Consumer action) { @@ -132,7 +132,7 @@ private static void executeScriptList( try { action.accept(script); } catch (Exception e) { - Funkin.error("Polyverse", "Error in " + script.getClass().getSimpleName(), e); + Flixel.error("Polyverse", "Error in " + script.getClass().getSimpleName(), e); } } } diff --git a/core/src/main/java/me/stringfromjava/funkin/polyverse/script/type/Script.java b/core/src/main/java/me/stringfromjava/polyverse/script/type/Script.java similarity index 91% rename from core/src/main/java/me/stringfromjava/funkin/polyverse/script/type/Script.java rename to core/src/main/java/me/stringfromjava/polyverse/script/type/Script.java index 8effbbb..64d3ae7 100644 --- a/core/src/main/java/me/stringfromjava/funkin/polyverse/script/type/Script.java +++ b/core/src/main/java/me/stringfromjava/polyverse/script/type/Script.java @@ -1,4 +1,4 @@ -package me.stringfromjava.funkin.polyverse.script.type; +package me.stringfromjava.polyverse.script.type; /** Base class for all Polyverse scripts to extend to. */ public abstract class Script { diff --git a/core/src/main/java/me/stringfromjava/funkin/polyverse/script/type/SystemScript.java b/core/src/main/java/me/stringfromjava/polyverse/script/type/SystemScript.java similarity index 66% rename from core/src/main/java/me/stringfromjava/funkin/polyverse/script/type/SystemScript.java rename to core/src/main/java/me/stringfromjava/polyverse/script/type/SystemScript.java index de43ac7..b37a5e0 100644 --- a/core/src/main/java/me/stringfromjava/funkin/polyverse/script/type/SystemScript.java +++ b/core/src/main/java/me/stringfromjava/polyverse/script/type/SystemScript.java @@ -1,4 +1,4 @@ -package me.stringfromjava.funkin.polyverse.script.type; +package me.stringfromjava.polyverse.script.type; public abstract class SystemScript extends Script { diff --git a/lwjgl3/src/main/java/me/stringfromjava/funkin/lwjgl3/Lwjgl3Launcher.java b/lwjgl3/src/main/java/me/stringfromjava/funkin/lwjgl3/Lwjgl3Launcher.java index a5968d9..cef9ed3 100644 --- a/lwjgl3/src/main/java/me/stringfromjava/funkin/lwjgl3/Lwjgl3Launcher.java +++ b/lwjgl3/src/main/java/me/stringfromjava/funkin/lwjgl3/Lwjgl3Launcher.java @@ -3,9 +3,10 @@ import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application; import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration; import com.badlogic.gdx.backends.lwjgl3.Lwjgl3WindowAdapter; -import me.stringfromjava.funkin.Funkin; +import me.stringfromjava.flixelgdx.Flixel; import me.stringfromjava.funkin.FunkinGame; -import me.stringfromjava.funkin.util.Constants; +import me.stringfromjava.funkin.init.InitScreen; +import me.stringfromjava.funkin.util.FunkinConstants; /** Launches the desktop (LWJGL3) application. */ public class Lwjgl3Launcher { @@ -18,26 +19,29 @@ public static void main(String[] args) { } private static void createApplication() { - FunkinGame game = new FunkinGame(); - Funkin.initialize(game); // This is VERY important to do before creating the application! + FunkinGame game = new FunkinGame( + FunkinConstants.WINDOW_TITLE, + FunkinConstants.WINDOW_WIDTH, + FunkinConstants.WINDOW_HEIGHT, + new InitScreen() + ); + Flixel.initialize(game); // This is VERY important to do before creating the application! new Lwjgl3Application(game, createWindowConfiguration()); } private static Lwjgl3ApplicationConfiguration createWindowConfiguration() { Lwjgl3ApplicationConfiguration configuration = new Lwjgl3ApplicationConfiguration(); - configuration.setTitle("Friday Night Funkin': Java Edition"); + configuration.setTitle("Friday Night Flixel': Java Edition"); // Vsync limits the frames per second to what your hardware can display, and helps eliminate // screen tearing. This setting doesn't always work on Linux, so the line after is a safeguard. configuration.useVsync(true); // Limits FPS to the refresh rate of the currently active monitor, plus 1 to try to match - // fractional - // refresh rates. The Vsync setting above should limit the actual FPS to match the monitor. + // fractional refresh rates. The Vsync setting above should limit the actual FPS to match the monitor. configuration.setForegroundFPS(Lwjgl3ApplicationConfiguration.getDisplayMode().refreshRate + 1); // If you remove the above line and set Vsync to false, you can get unlimited FPS, which can be // useful for testing performance, but can also be very stressful to some hardware. - // You may also need to configure GPU drivers to fully disable Vsync; this can cause screen - // tearing. - configuration.setWindowedMode(Constants.Display.WINDOW_WIDTH, Constants.Display.WINDOW_HEIGHT); + // You may also need to configure GPU drivers to fully disable Vsync; this can cause screen tearing. + configuration.setWindowedMode(FunkinConstants.WINDOW_WIDTH, FunkinConstants.WINDOW_HEIGHT); // You can change these files; they are in lwjgl3/src/main/resources/ . // They can also be loaded from the root of assets/ . configuration.setWindowIcon("icon128.png", "icon64.png", "icon32.png", "icon16.png"); @@ -49,19 +53,19 @@ private static Lwjgl3ApplicationConfiguration createWindowConfiguration() { @Override public void focusGained() { super.focusGained(); - Funkin.getGame().onWindowFocused(); + Flixel.getGame().onWindowFocused(); } @Override public void focusLost() { super.focusLost(); - Funkin.getGame().onWindowUnfocused(); + Flixel.getGame().onWindowUnfocused(); } @Override public void iconified(boolean isIconified) { super.iconified(isIconified); - Funkin.getGame().onWindowMinimized(isIconified); + Flixel.getGame().onWindowMinimized(isIconified); } }); From addc4f4cee7ae2e1bc0ed754e2ba950b70ec688d Mon Sep 17 00:00:00 2001 From: String Date: Thu, 22 Jan 2026 04:14:35 -0600 Subject: [PATCH 09/17] Add modding SDK task for future autocomplete with mod development --- .gitignore | 2 +- build.gradle | 1 - core/build.gradle | 24 +++++++++++-- lwjgl3/build.gradle | 84 ++++++++++++++++++++++----------------------- 4 files changed, 65 insertions(+), 46 deletions(-) diff --git a/.gitignore b/.gitignore index 159c8cd..54ac2f5 100644 --- a/.gitignore +++ b/.gitignore @@ -155,8 +155,8 @@ Thumbs.db *~ *.*# *#*# -/.kotlin/ /assets/assets.txt +/assets/sdk/ ## Special cases: diff --git a/build.gradle b/build.gradle index 199446c..a90df70 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,6 @@ buildscript { } dependencies { classpath "com.android.tools.build:gradle:8.7.3" - } } diff --git a/core/build.gradle b/core/build.gradle index c0c016b..ecb889e 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -11,10 +11,30 @@ dependencies { api "games.rednblack.miniaudio:miniaudio:$miniaudioVersion" // FNF:JE. - implementation "org.apache.groovy:groovy:4.0.12" - implementation "org.jetbrains:annotations:15.0" + implementation "org.apache.groovy:groovy:5.0.3" + implementation "org.jetbrains:annotations:26.0.2-1" if(enableGraalNative == 'true') { implementation "io.github.berstanio:gdx-svmhelper-annotations:$graalHelperVersion" } } + +tasks.register('exportModSDK', Copy) { + group = "modding" + description = "Copies all JARs needed for modding into the assets folder." + + into "$rootProject.projectDir/assets/sdk/libs" + + from(tasks.named('jar')) { + rename { "game-api.jar" } + } + + // Include every external dependency. + from(configurations.runtimeClasspath) { + // Exclude native files, since modders only need the Java interfaces. + exclude "**/*.dll", "**/*.so", "**/*.dylib", "**/*.jnilib" + } +} + +// Make sure the assets are ready before the game runs. +classes.dependsOn exportModSDK diff --git a/lwjgl3/build.gradle b/lwjgl3/build.gradle index 9e4435f..027d290 100644 --- a/lwjgl3/build.gradle +++ b/lwjgl3/build.gradle @@ -4,7 +4,7 @@ buildscript { } dependencies { classpath "io.github.fourlastor:construo:1.7.1" - if(enableGraalNative == 'true') { + if (enableGraalNative == 'true') { classpath "org.graalvm.buildtools.native:org.graalvm.buildtools.native.gradle.plugin:0.9.28" } } @@ -17,14 +17,14 @@ apply plugin: 'io.github.fourlastor.construo' import io.github.fourlastor.construo.Target -sourceSets.main.resources.srcDirs += [ rootProject.file('assets').path ] +sourceSets.main.resources.srcDirs += [rootProject.file('assets').path] mainClassName = 'me.stringfromjava.funkin.lwjgl3.Lwjgl3Launcher' application.setMainClass(mainClassName) eclipse.project.name = appName + '-lwjgl3' java.sourceCompatibility = 17 java.targetCompatibility = 17 if (JavaVersion.current().isJava9Compatible()) { - compileJava.options.release.set(17) + compileJava.options.release.set(17) } dependencies { @@ -34,7 +34,7 @@ dependencies { implementation "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop" implementation project(':core') - if(enableGraalNative == 'true') { + if (enableGraalNative == 'true') { implementation "io.github.berstanio:gdx-svmhelper-backend-lwjgl3:$graalHelperVersion" } } @@ -114,44 +114,44 @@ tasks.register("jarWin") { } construo { - // name of the executable - name.set(appName) - // human-readable name, used for example in the `.app` name for macOS - humanName.set(appName) - // Optional, defaults to project version property - version.set("$projectVersion") - - targets.configure { - register("linuxX64", Target.Linux) { - architecture.set(Target.Architecture.X86_64) - jdkUrl.set("https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_x64_linux_hotspot_17.0.15_6.tar.gz") - // Linux does not currently have a way to set the icon on the executable - } - register("macM1", Target.MacOs) { - architecture.set(Target.Architecture.AARCH64) - jdkUrl.set("https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_aarch64_mac_hotspot_17.0.15_6.tar.gz") - // macOS needs an identifier - identifier.set("me.stringfromjava.funkin." + appName) - // Optional: icon for macOS, as an ICNS file - macIcon.set(project.file("icons/logo.icns")) - } - register("macX64", Target.MacOs) { - architecture.set(Target.Architecture.X86_64) - jdkUrl.set("https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_x64_mac_hotspot_17.0.15_6.tar.gz") - // macOS needs an identifier - identifier.set("me.stringfromjava.funkin." + appName) - // Optional: icon for macOS, as an ICNS file - macIcon.set(project.file("icons/logo.icns")) - } - register("winX64", Target.Windows) { - architecture.set(Target.Architecture.X86_64) - // Optional: icon for Windows, as a PNG - icon.set(project.file("icons/logo.png")) - jdkUrl.set("https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_x64_windows_hotspot_17.0.15_6.zip") - // Uncomment the next line to show a console when the game runs, to print messages. - //useConsole.set(true) - } + // name of the executable + name.set(appName) + // human-readable name, used for example in the `.app` name for macOS + humanName.set(appName) + // Optional, defaults to project version property + version.set("$projectVersion") + + targets.configure { + register("linuxX64", Target.Linux) { + architecture.set(Target.Architecture.X86_64) + jdkUrl.set("https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_x64_linux_hotspot_17.0.15_6.tar.gz") + // Linux does not currently have a way to set the icon on the executable } + register("macM1", Target.MacOs) { + architecture.set(Target.Architecture.AARCH64) + jdkUrl.set("https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_aarch64_mac_hotspot_17.0.15_6.tar.gz") + // macOS needs an identifier + identifier.set("me.stringfromjava.funkin." + appName) + // Optional: icon for macOS, as an ICNS file + macIcon.set(project.file("icons/logo.icns")) + } + register("macX64", Target.MacOs) { + architecture.set(Target.Architecture.X86_64) + jdkUrl.set("https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_x64_mac_hotspot_17.0.15_6.tar.gz") + // macOS needs an identifier + identifier.set("me.stringfromjava.funkin." + appName) + // Optional: icon for macOS, as an ICNS file + macIcon.set(project.file("icons/logo.icns")) + } + register("winX64", Target.Windows) { + architecture.set(Target.Architecture.X86_64) + // Optional: icon for Windows, as a PNG + icon.set(project.file("icons/logo.png")) + jdkUrl.set("https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_x64_windows_hotspot_17.0.15_6.zip") + // Uncomment the next line to show a console when the game runs, to print messages. + //useConsole.set(true) + } + } } // Equivalent to the jar task; here for compatibility with gdx-setup. @@ -176,6 +176,6 @@ distributions { startScripts.dependsOn(':lwjgl3:jar') startScripts.classpath = project.tasks.jar.outputs.files -if(enableGraalNative == 'true') { +if (enableGraalNative == 'true') { apply from: file("nativeimage.gradle") } From 95052c31210daf36cf35fe8415c67c6de26622da Mon Sep 17 00:00:00 2001 From: String Date: Sat, 24 Jan 2026 04:42:17 -0600 Subject: [PATCH 10/17] Rename global package and add Android build CI task --- .github/FUNDING.yml | 2 +- .github/workflows/ci.yaml | 39 +++++++++++++++---- android/AndroidManifest.xml | 2 +- android/build.gradle | 6 +-- .../funkin/android/AndroidLauncher.java | 9 ++--- assets/another_test.groovy | 10 ++--- assets/test.groovy | 10 ++--- .../flixelgdx/Flixel.java | 16 ++++---- .../flixelgdx/FlixelGame.java | 24 ++++++------ .../flixelgdx/backend/FlixelPaths.java | 2 +- .../graphics/screen/FlixelScreen.java | 4 +- .../graphics/sprite/FlixelObject.java | 4 +- .../graphics/sprite/FlixelSprite.java | 2 +- .../flixelgdx/graphics/text/FlixelText.java | 2 +- .../flixelgdx/signal/FlixelSignal.java | 2 +- .../flixelgdx/signal/FlixelSignalData.java | 6 +-- .../flixelgdx/tween/FlixelEase.java | 2 +- .../flixelgdx/tween/FlixelTween.java | 10 ++--- .../flixelgdx/tween/FlixelTweenManager.java | 2 +- .../tween/settings/FlixelTweenSettings.java | 6 +-- .../tween/settings/FlixelTweenType.java | 2 +- .../flixelgdx/util/FlixelConstants.java | 2 +- .../flixelgdx/util/FlixelReflectUtil.java | 6 +-- .../flixelgdx/util/FlixelRuntimeUtil.java | 2 +- .../funkin/FunkinGame.java | 18 ++++----- .../stringdotjar/funkin/init/InitScreen.java | 14 +++++++ .../funkin/menus/TitleScreen.java | 18 ++++----- .../funkin/util/FunkinConstants.java | 2 +- .../polyverse/Polyverse.java | 6 +-- .../polyverse/script/type/Script.java | 2 +- .../polyverse/script/type/SystemScript.java | 2 +- .../funkin/init/InitScreen.java | 14 ------- lwjgl3/build.gradle | 6 +-- .../funkin/lwjgl3/Lwjgl3Launcher.java | 10 ++--- .../funkin/lwjgl3/StartupHelper.java | 2 +- 35 files changed, 144 insertions(+), 122 deletions(-) rename android/src/main/java/me/{stringfromjava => stringdotjar}/funkin/android/AndroidLauncher.java (76%) rename core/src/main/java/me/{stringfromjava => stringdotjar}/flixelgdx/Flixel.java (97%) rename core/src/main/java/me/{stringfromjava => stringdotjar}/flixelgdx/FlixelGame.java (92%) rename core/src/main/java/me/{stringfromjava => stringdotjar}/flixelgdx/backend/FlixelPaths.java (94%) rename core/src/main/java/me/{stringfromjava => stringdotjar}/flixelgdx/graphics/screen/FlixelScreen.java (91%) rename core/src/main/java/me/{stringfromjava => stringdotjar}/flixelgdx/graphics/sprite/FlixelObject.java (54%) rename core/src/main/java/me/{stringfromjava => stringdotjar}/flixelgdx/graphics/sprite/FlixelSprite.java (99%) rename core/src/main/java/me/{stringfromjava => stringdotjar}/flixelgdx/graphics/text/FlixelText.java (90%) rename core/src/main/java/me/{stringfromjava => stringdotjar}/flixelgdx/signal/FlixelSignal.java (98%) rename core/src/main/java/me/{stringfromjava => stringdotjar}/flixelgdx/signal/FlixelSignalData.java (76%) rename core/src/main/java/me/{stringfromjava => stringdotjar}/flixelgdx/tween/FlixelEase.java (99%) rename core/src/main/java/me/{stringfromjava => stringdotjar}/flixelgdx/tween/FlixelTween.java (97%) rename core/src/main/java/me/{stringfromjava => stringdotjar}/flixelgdx/tween/FlixelTweenManager.java (97%) rename core/src/main/java/me/{stringfromjava => stringdotjar}/flixelgdx/tween/settings/FlixelTweenSettings.java (96%) rename core/src/main/java/me/{stringfromjava => stringdotjar}/flixelgdx/tween/settings/FlixelTweenType.java (90%) rename core/src/main/java/me/{stringfromjava => stringdotjar}/flixelgdx/util/FlixelConstants.java (97%) rename core/src/main/java/me/{stringfromjava => stringdotjar}/flixelgdx/util/FlixelReflectUtil.java (94%) rename core/src/main/java/me/{stringfromjava => stringdotjar}/flixelgdx/util/FlixelRuntimeUtil.java (97%) rename core/src/main/java/me/{stringfromjava => stringdotjar}/funkin/FunkinGame.java (68%) create mode 100644 core/src/main/java/me/stringdotjar/funkin/init/InitScreen.java rename core/src/main/java/me/{stringfromjava => stringdotjar}/funkin/menus/TitleScreen.java (80%) rename core/src/main/java/me/{stringfromjava => stringdotjar}/funkin/util/FunkinConstants.java (94%) rename core/src/main/java/me/{stringfromjava => stringdotjar}/polyverse/Polyverse.java (97%) rename core/src/main/java/me/{stringfromjava => stringdotjar}/polyverse/script/type/Script.java (92%) rename core/src/main/java/me/{stringfromjava => stringdotjar}/polyverse/script/type/SystemScript.java (69%) delete mode 100644 core/src/main/java/me/stringfromjava/funkin/init/InitScreen.java rename lwjgl3/src/main/java/me/{stringfromjava => stringdotjar}/funkin/lwjgl3/Lwjgl3Launcher.java (92%) rename lwjgl3/src/main/java/me/{stringfromjava => stringdotjar}/funkin/lwjgl3/StartupHelper.java (99%) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index a5c556e..eafd405 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -3,7 +3,7 @@ github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username -ko_fi: stringfromjava +ko_fi: stringdotjar tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 51db3bd..005304b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -13,25 +13,48 @@ jobs: os: [windows-latest, macos-latest, ubuntu-latest] runs-on: ${{ matrix.os }} - + steps: - uses: actions/checkout@v4 - - # LibGDX typically uses Java 11 or newer + + # LibGDX typically uses Java 11 or newer. - name: Set up JDK 17 uses: actions/setup-java@v4 with: java-version: '17' distribution: 'temurin' - # Cache Gradle dependencies to speed up subsequent runs + # Cache Gradle dependencies to speed up subsequent runs. cache: 'gradle' - # Grant execute permission for gradlew (necessary for macOS and Linux) + # Grant execute permission for gradlew (necessary for macOS and Linux). - name: Grant execute permission for gradlew run: chmod +x gradlew - # Use an 'if' condition to only run this on non-Windows OS + # Use an 'if' condition to only run this on non-Windows OS. if: startsWith(runner.os, 'macOS') || startsWith(runner.os, 'Linux') - - # This task compiles all modules (core, desktop, etc.) + + # This task compiles all modules (core, desktop, etc.). - name: Build with Gradle run: ./gradlew build + + build-android: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + # LibGDX typically uses Java 11 or newer. + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + # Cache Gradle dependencies to speed up subsequent runs. + cache: 'gradle' + + # Grant execute permission for gradlew. + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + # Build Android module. + - name: Build Android with Gradle + run: ./gradlew android:build diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index f1a19ce..2bddab6 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -12,7 +12,7 @@ tools:ignore="UnusedAttribute" android:theme="@style/GdxTheme"> type) { * Checks if a class of a certain package is final. * * @param classPath The package definition of the class to check if final. An example could be - * {@code "me.stringfromjava.flixelgdx.Flixel"}. + * {@code "me.stringdotjar.flixelgdx.Flixel"}. * @return If the class provided is final. If there was an exception caught, then {@code false} is * automatically returned. */ diff --git a/core/src/main/java/me/stringfromjava/flixelgdx/util/FlixelRuntimeUtil.java b/core/src/main/java/me/stringdotjar/flixelgdx/util/FlixelRuntimeUtil.java similarity index 97% rename from core/src/main/java/me/stringfromjava/flixelgdx/util/FlixelRuntimeUtil.java rename to core/src/main/java/me/stringdotjar/flixelgdx/util/FlixelRuntimeUtil.java index 2ce8b95..4d8a48c 100644 --- a/core/src/main/java/me/stringfromjava/flixelgdx/util/FlixelRuntimeUtil.java +++ b/core/src/main/java/me/stringdotjar/flixelgdx/util/FlixelRuntimeUtil.java @@ -1,4 +1,4 @@ -package me.stringfromjava.flixelgdx.util; +package me.stringdotjar.flixelgdx.util; /** * Utility class for handling operation related to the runtime environment, including OS detection, diff --git a/core/src/main/java/me/stringfromjava/funkin/FunkinGame.java b/core/src/main/java/me/stringdotjar/funkin/FunkinGame.java similarity index 68% rename from core/src/main/java/me/stringfromjava/funkin/FunkinGame.java rename to core/src/main/java/me/stringdotjar/funkin/FunkinGame.java index 1d93000..0040de4 100644 --- a/core/src/main/java/me/stringfromjava/funkin/FunkinGame.java +++ b/core/src/main/java/me/stringdotjar/funkin/FunkinGame.java @@ -1,12 +1,12 @@ -package me.stringfromjava.funkin; - -import me.stringfromjava.flixelgdx.Flixel; -import me.stringfromjava.flixelgdx.FlixelGame; -import me.stringfromjava.flixelgdx.backend.FlixelPaths; -import me.stringfromjava.flixelgdx.graphics.screen.FlixelScreen; -import me.stringfromjava.polyverse.Polyverse; -import me.stringfromjava.polyverse.script.type.Script; -import me.stringfromjava.polyverse.script.type.SystemScript; +package me.stringdotjar.funkin; + +import me.stringdotjar.flixelgdx.Flixel; +import me.stringdotjar.flixelgdx.FlixelGame; +import me.stringdotjar.flixelgdx.backend.FlixelPaths; +import me.stringdotjar.flixelgdx.graphics.screen.FlixelScreen; +import me.stringdotjar.polyverse.Polyverse; +import me.stringdotjar.polyverse.script.type.Script; +import me.stringdotjar.polyverse.script.type.SystemScript; /** * The main Funkin' game class that initializes and runs the game. diff --git a/core/src/main/java/me/stringdotjar/funkin/init/InitScreen.java b/core/src/main/java/me/stringdotjar/funkin/init/InitScreen.java new file mode 100644 index 0000000..f73bf8c --- /dev/null +++ b/core/src/main/java/me/stringdotjar/funkin/init/InitScreen.java @@ -0,0 +1,14 @@ +package me.stringdotjar.funkin.init; + +import me.stringdotjar.flixelgdx.graphics.screen.FlixelScreen; +import me.stringdotjar.flixelgdx.Flixel; +import me.stringdotjar.funkin.menus.TitleScreen; + +public class InitScreen extends FlixelScreen { + + @Override + public void show() { + super.show(); + Flixel.setScreen(new TitleScreen()); + } +} diff --git a/core/src/main/java/me/stringfromjava/funkin/menus/TitleScreen.java b/core/src/main/java/me/stringdotjar/funkin/menus/TitleScreen.java similarity index 80% rename from core/src/main/java/me/stringfromjava/funkin/menus/TitleScreen.java rename to core/src/main/java/me/stringdotjar/funkin/menus/TitleScreen.java index bd6715d..5597818 100644 --- a/core/src/main/java/me/stringfromjava/funkin/menus/TitleScreen.java +++ b/core/src/main/java/me/stringdotjar/funkin/menus/TitleScreen.java @@ -1,15 +1,15 @@ -package me.stringfromjava.funkin.menus; +package me.stringdotjar.funkin.menus; import com.badlogic.gdx.Input; import games.rednblack.miniaudio.MASound; -import me.stringfromjava.flixelgdx.Flixel; -import me.stringfromjava.flixelgdx.backend.FlixelPaths; -import me.stringfromjava.flixelgdx.graphics.screen.FlixelScreen; -import me.stringfromjava.flixelgdx.graphics.sprite.FlixelSprite; -import me.stringfromjava.flixelgdx.tween.FlixelEase; -import me.stringfromjava.flixelgdx.tween.FlixelTween; -import me.stringfromjava.flixelgdx.tween.settings.FlixelTweenSettings; -import me.stringfromjava.flixelgdx.tween.settings.FlixelTweenType; +import me.stringdotjar.flixelgdx.Flixel; +import me.stringdotjar.flixelgdx.backend.FlixelPaths; +import me.stringdotjar.flixelgdx.graphics.screen.FlixelScreen; +import me.stringdotjar.flixelgdx.graphics.sprite.FlixelSprite; +import me.stringdotjar.flixelgdx.tween.FlixelEase; +import me.stringdotjar.flixelgdx.tween.FlixelTween; +import me.stringdotjar.flixelgdx.tween.settings.FlixelTweenSettings; +import me.stringdotjar.flixelgdx.tween.settings.FlixelTweenType; public class TitleScreen extends FlixelScreen { diff --git a/core/src/main/java/me/stringfromjava/funkin/util/FunkinConstants.java b/core/src/main/java/me/stringdotjar/funkin/util/FunkinConstants.java similarity index 94% rename from core/src/main/java/me/stringfromjava/funkin/util/FunkinConstants.java rename to core/src/main/java/me/stringdotjar/funkin/util/FunkinConstants.java index ced76f0..8fd5196 100644 --- a/core/src/main/java/me/stringfromjava/funkin/util/FunkinConstants.java +++ b/core/src/main/java/me/stringdotjar/funkin/util/FunkinConstants.java @@ -1,4 +1,4 @@ -package me.stringfromjava.funkin.util; +package me.stringdotjar.funkin.util; /** * Constants used throughout Funkin'. diff --git a/core/src/main/java/me/stringfromjava/polyverse/Polyverse.java b/core/src/main/java/me/stringdotjar/polyverse/Polyverse.java similarity index 97% rename from core/src/main/java/me/stringfromjava/polyverse/Polyverse.java rename to core/src/main/java/me/stringdotjar/polyverse/Polyverse.java index 6daace4..a8383af 100644 --- a/core/src/main/java/me/stringfromjava/polyverse/Polyverse.java +++ b/core/src/main/java/me/stringdotjar/polyverse/Polyverse.java @@ -1,9 +1,9 @@ -package me.stringfromjava.polyverse; +package me.stringdotjar.polyverse; import com.badlogic.gdx.files.FileHandle; import groovy.lang.GroovyClassLoader; -import me.stringfromjava.flixelgdx.Flixel; -import me.stringfromjava.polyverse.script.type.Script; +import me.stringdotjar.flixelgdx.Flixel; +import me.stringdotjar.polyverse.script.type.Script; import java.util.ArrayList; import java.util.Collections; diff --git a/core/src/main/java/me/stringfromjava/polyverse/script/type/Script.java b/core/src/main/java/me/stringdotjar/polyverse/script/type/Script.java similarity index 92% rename from core/src/main/java/me/stringfromjava/polyverse/script/type/Script.java rename to core/src/main/java/me/stringdotjar/polyverse/script/type/Script.java index 64d3ae7..6b41e28 100644 --- a/core/src/main/java/me/stringfromjava/polyverse/script/type/Script.java +++ b/core/src/main/java/me/stringdotjar/polyverse/script/type/Script.java @@ -1,4 +1,4 @@ -package me.stringfromjava.polyverse.script.type; +package me.stringdotjar.polyverse.script.type; /** Base class for all Polyverse scripts to extend to. */ public abstract class Script { diff --git a/core/src/main/java/me/stringfromjava/polyverse/script/type/SystemScript.java b/core/src/main/java/me/stringdotjar/polyverse/script/type/SystemScript.java similarity index 69% rename from core/src/main/java/me/stringfromjava/polyverse/script/type/SystemScript.java rename to core/src/main/java/me/stringdotjar/polyverse/script/type/SystemScript.java index b37a5e0..acc8b98 100644 --- a/core/src/main/java/me/stringfromjava/polyverse/script/type/SystemScript.java +++ b/core/src/main/java/me/stringdotjar/polyverse/script/type/SystemScript.java @@ -1,4 +1,4 @@ -package me.stringfromjava.polyverse.script.type; +package me.stringdotjar.polyverse.script.type; public abstract class SystemScript extends Script { diff --git a/core/src/main/java/me/stringfromjava/funkin/init/InitScreen.java b/core/src/main/java/me/stringfromjava/funkin/init/InitScreen.java deleted file mode 100644 index 1f89de2..0000000 --- a/core/src/main/java/me/stringfromjava/funkin/init/InitScreen.java +++ /dev/null @@ -1,14 +0,0 @@ -package me.stringfromjava.funkin.init; - -import me.stringfromjava.flixelgdx.graphics.screen.FlixelScreen; -import me.stringfromjava.flixelgdx.Flixel; -import me.stringfromjava.funkin.menus.TitleScreen; - -public class InitScreen extends FlixelScreen { - - @Override - public void show() { - super.show(); - Flixel.setScreen(new TitleScreen()); - } -} diff --git a/lwjgl3/build.gradle b/lwjgl3/build.gradle index 9e4435f..3e513cd 100644 --- a/lwjgl3/build.gradle +++ b/lwjgl3/build.gradle @@ -18,7 +18,7 @@ apply plugin: 'io.github.fourlastor.construo' import io.github.fourlastor.construo.Target sourceSets.main.resources.srcDirs += [ rootProject.file('assets').path ] -mainClassName = 'me.stringfromjava.funkin.lwjgl3.Lwjgl3Launcher' +mainClassName = 'me.stringdotjar.funkin.lwjgl3.Lwjgl3Launcher' application.setMainClass(mainClassName) eclipse.project.name = appName + '-lwjgl3' java.sourceCompatibility = 17 @@ -131,7 +131,7 @@ construo { architecture.set(Target.Architecture.AARCH64) jdkUrl.set("https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_aarch64_mac_hotspot_17.0.15_6.tar.gz") // macOS needs an identifier - identifier.set("me.stringfromjava.funkin." + appName) + identifier.set("me.stringdotjar.funkin." + appName) // Optional: icon for macOS, as an ICNS file macIcon.set(project.file("icons/logo.icns")) } @@ -139,7 +139,7 @@ construo { architecture.set(Target.Architecture.X86_64) jdkUrl.set("https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_x64_mac_hotspot_17.0.15_6.tar.gz") // macOS needs an identifier - identifier.set("me.stringfromjava.funkin." + appName) + identifier.set("me.stringdotjar.funkin." + appName) // Optional: icon for macOS, as an ICNS file macIcon.set(project.file("icons/logo.icns")) } diff --git a/lwjgl3/src/main/java/me/stringfromjava/funkin/lwjgl3/Lwjgl3Launcher.java b/lwjgl3/src/main/java/me/stringdotjar/funkin/lwjgl3/Lwjgl3Launcher.java similarity index 92% rename from lwjgl3/src/main/java/me/stringfromjava/funkin/lwjgl3/Lwjgl3Launcher.java rename to lwjgl3/src/main/java/me/stringdotjar/funkin/lwjgl3/Lwjgl3Launcher.java index cef9ed3..6a84013 100644 --- a/lwjgl3/src/main/java/me/stringfromjava/funkin/lwjgl3/Lwjgl3Launcher.java +++ b/lwjgl3/src/main/java/me/stringdotjar/funkin/lwjgl3/Lwjgl3Launcher.java @@ -1,12 +1,12 @@ -package me.stringfromjava.funkin.lwjgl3; +package me.stringdotjar.funkin.lwjgl3; import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application; import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration; import com.badlogic.gdx.backends.lwjgl3.Lwjgl3WindowAdapter; -import me.stringfromjava.flixelgdx.Flixel; -import me.stringfromjava.funkin.FunkinGame; -import me.stringfromjava.funkin.init.InitScreen; -import me.stringfromjava.funkin.util.FunkinConstants; +import me.stringdotjar.flixelgdx.Flixel; +import me.stringdotjar.funkin.FunkinGame; +import me.stringdotjar.funkin.init.InitScreen; +import me.stringdotjar.funkin.util.FunkinConstants; /** Launches the desktop (LWJGL3) application. */ public class Lwjgl3Launcher { diff --git a/lwjgl3/src/main/java/me/stringfromjava/funkin/lwjgl3/StartupHelper.java b/lwjgl3/src/main/java/me/stringdotjar/funkin/lwjgl3/StartupHelper.java similarity index 99% rename from lwjgl3/src/main/java/me/stringfromjava/funkin/lwjgl3/StartupHelper.java rename to lwjgl3/src/main/java/me/stringdotjar/funkin/lwjgl3/StartupHelper.java index 0a33663..b6ae7f1 100644 --- a/lwjgl3/src/main/java/me/stringfromjava/funkin/lwjgl3/StartupHelper.java +++ b/lwjgl3/src/main/java/me/stringdotjar/funkin/lwjgl3/StartupHelper.java @@ -13,7 +13,7 @@ * limitations under the License. */ // Note, the above license and copyright applies to this file only. -package me.stringfromjava.funkin.lwjgl3; +package me.stringdotjar.funkin.lwjgl3; import com.badlogic.gdx.Version; import com.badlogic.gdx.backends.lwjgl3.Lwjgl3NativesLoader; From 650ba475b0418089c4f4523f365dfa0feb7e38d6 Mon Sep 17 00:00:00 2001 From: String Date: Sat, 24 Jan 2026 05:20:54 -0600 Subject: [PATCH 11/17] Update build.gradle --- core/build.gradle | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/core/build.gradle b/core/build.gradle index ecb889e..c03b084 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -14,19 +14,19 @@ dependencies { implementation "org.apache.groovy:groovy:5.0.3" implementation "org.jetbrains:annotations:26.0.2-1" - if(enableGraalNative == 'true') { + if (enableGraalNative == 'true') { implementation "io.github.berstanio:gdx-svmhelper-annotations:$graalHelperVersion" } } tasks.register('exportModSDK', Copy) { group = "modding" - description = "Copies all JARs needed for modding into the assets folder." + description = "Copies all API JARs needed for modding into the assets folder." - into "$rootProject.projectDir/assets/sdk/libs" + into "$rootProject.projectDir/assets/sdk/api" from(tasks.named('jar')) { - rename { "game-api.jar" } + rename { "funkin-api.jar" } } // Include every external dependency. @@ -37,4 +37,6 @@ tasks.register('exportModSDK', Copy) { } // Make sure the assets are ready before the game runs. -classes.dependsOn exportModSDK +tasks.named('build').configure { + dependsOn exportModSDK +} From 4df18500a73437783784d3884caaa2807bdca9b0 Mon Sep 17 00:00:00 2001 From: String Date: Sat, 24 Jan 2026 05:30:50 -0600 Subject: [PATCH 12/17] Update build.gradle --- lwjgl3/build.gradle | 55 +++++++++++++-------------------------------- 1 file changed, 15 insertions(+), 40 deletions(-) diff --git a/lwjgl3/build.gradle b/lwjgl3/build.gradle index 0ce60aa..50f9fb6 100644 --- a/lwjgl3/build.gradle +++ b/lwjgl3/build.gradle @@ -17,7 +17,7 @@ apply plugin: 'io.github.fourlastor.construo' import io.github.fourlastor.construo.Target -sourceSets.main.resources.srcDirs += [ rootProject.file('assets').path ] +sourceSets.main.resources.srcDirs += [rootProject.file('assets').path] mainClassName = 'me.stringdotjar.funkin.lwjgl3.Lwjgl3Launcher' application.setMainClass(mainClassName) eclipse.project.name = appName + '-lwjgl3' @@ -114,49 +114,24 @@ tasks.register("jarWin") { } construo { - // name of the executable - name.set(appName) - // human-readable name, used for example in the `.app` name for macOS - humanName.set(appName) - // Optional, defaults to project version property - version.set("$projectVersion") - - targets.configure { - register("linuxX64", Target.Linux) { - architecture.set(Target.Architecture.X86_64) - jdkUrl.set("https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_x64_linux_hotspot_17.0.15_6.tar.gz") - // Linux does not currently have a way to set the icon on the executable - } - register("macM1", Target.MacOs) { - architecture.set(Target.Architecture.AARCH64) - jdkUrl.set("https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_aarch64_mac_hotspot_17.0.15_6.tar.gz") - // macOS needs an identifier - identifier.set("me.stringdotjar.funkin." + appName) - // Optional: icon for macOS, as an ICNS file - macIcon.set(project.file("icons/logo.icns")) - } - register("macX64", Target.MacOs) { - architecture.set(Target.Architecture.X86_64) - jdkUrl.set("https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_x64_mac_hotspot_17.0.15_6.tar.gz") - // macOS needs an identifier - identifier.set("me.stringdotjar.funkin." + appName) - // Optional: icon for macOS, as an ICNS file - macIcon.set(project.file("icons/logo.icns")) - } - register("winX64", Target.Windows) { - architecture.set(Target.Architecture.X86_64) - // Optional: icon for Windows, as a PNG - icon.set(project.file("icons/logo.png")) - jdkUrl.set("https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_x64_windows_hotspot_17.0.15_6.zip") - // Uncomment the next line to show a console when the game runs, to print messages. - //useConsole.set(true) - } + // name of the executable + name.set(appName) + // human-readable name, used for example in the `.app` name for macOS + humanName.set(appName) + // Optional, defaults to project version property + version.set("$projectVersion") + + targets.configure { + register("linuxX64", Target.Linux) { + architecture.set(Target.Architecture.X86_64) + jdkUrl.set("https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_x64_linux_hotspot_17.0.15_6.tar.gz") + // Linux does not currently have a way to set the icon on the executable } register("macM1", Target.MacOs) { architecture.set(Target.Architecture.AARCH64) jdkUrl.set("https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_aarch64_mac_hotspot_17.0.15_6.tar.gz") // macOS needs an identifier - identifier.set("me.stringfromjava.funkin." + appName) + identifier.set("me.stringdotjar.funkin." + appName) // Optional: icon for macOS, as an ICNS file macIcon.set(project.file("icons/logo.icns")) } @@ -164,7 +139,7 @@ construo { architecture.set(Target.Architecture.X86_64) jdkUrl.set("https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_x64_mac_hotspot_17.0.15_6.tar.gz") // macOS needs an identifier - identifier.set("me.stringfromjava.funkin." + appName) + identifier.set("me.stringdotjar.funkin." + appName) // Optional: icon for macOS, as an ICNS file macIcon.set(project.file("icons/logo.icns")) } From 8d46c305fa2d1acfa11f71db0ec507230d581bea Mon Sep 17 00:00:00 2001 From: String Date: Sat, 24 Jan 2026 05:37:29 -0600 Subject: [PATCH 13/17] =?UTF-8?q?Dude=20please=20work=20=F0=9F=99=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lwjgl3/build.gradle | 41 ----------------------------------------- 1 file changed, 41 deletions(-) diff --git a/lwjgl3/build.gradle b/lwjgl3/build.gradle index 50f9fb6..09b5ba1 100644 --- a/lwjgl3/build.gradle +++ b/lwjgl3/build.gradle @@ -113,47 +113,6 @@ tasks.register("jarWin") { } } -construo { - // name of the executable - name.set(appName) - // human-readable name, used for example in the `.app` name for macOS - humanName.set(appName) - // Optional, defaults to project version property - version.set("$projectVersion") - - targets.configure { - register("linuxX64", Target.Linux) { - architecture.set(Target.Architecture.X86_64) - jdkUrl.set("https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_x64_linux_hotspot_17.0.15_6.tar.gz") - // Linux does not currently have a way to set the icon on the executable - } - register("macM1", Target.MacOs) { - architecture.set(Target.Architecture.AARCH64) - jdkUrl.set("https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_aarch64_mac_hotspot_17.0.15_6.tar.gz") - // macOS needs an identifier - identifier.set("me.stringdotjar.funkin." + appName) - // Optional: icon for macOS, as an ICNS file - macIcon.set(project.file("icons/logo.icns")) - } - register("macX64", Target.MacOs) { - architecture.set(Target.Architecture.X86_64) - jdkUrl.set("https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_x64_mac_hotspot_17.0.15_6.tar.gz") - // macOS needs an identifier - identifier.set("me.stringdotjar.funkin." + appName) - // Optional: icon for macOS, as an ICNS file - macIcon.set(project.file("icons/logo.icns")) - } - register("winX64", Target.Windows) { - architecture.set(Target.Architecture.X86_64) - // Optional: icon for Windows, as a PNG - icon.set(project.file("icons/logo.png")) - jdkUrl.set("https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_x64_windows_hotspot_17.0.15_6.zip") - // Uncomment the next line to show a console when the game runs, to print messages. - //useConsole.set(true) - } - } -} - // Equivalent to the jar task; here for compatibility with gdx-setup. tasks.register('dist') { dependsOn 'jar' From e967e515e831ba34fb2f48e259e3482a3ce04578 Mon Sep 17 00:00:00 2001 From: String Date: Sat, 24 Jan 2026 06:06:16 -0600 Subject: [PATCH 14/17] =?UTF-8?q?I=20want=20to=20go=20to=20bed=20bro=20ple?= =?UTF-8?q?ase=20=F0=9F=98=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/build.gradle | 2 +- android/proguard-rules.pro | 17 +++++++++++++++++ core/build.gradle | 6 ++++-- lwjgl3/build.gradle | 3 --- 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index c340702..f1e7e80 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -47,7 +47,7 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } debug { - minifyEnabled true + minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } diff --git a/android/proguard-rules.pro b/android/proguard-rules.pro index 771fa1a..39a0674 100644 --- a/android/proguard-rules.pro +++ b/android/proguard-rules.pro @@ -24,6 +24,17 @@ -dontwarn android.support.** -dontwarn com.badlogic.gdx.backends.android.AndroidFragmentApplication +# Ignore missing AWT classes (desktop-only, not needed on Android) +-dontwarn java.awt.** +-dontwarn javax.swing.** + +# Ignore Java 9+ classes that don't exist on Android +-dontwarn java.lang.Runtime$Version +-dontwarn java.lang.reflect.InaccessibleObjectException + +# Ignore all Groovy warnings (Gradle's internal dependency) +-dontwarn org.codehaus.groovy.** + # Needed by the gdx-controllers official extension. -keep class com.badlogic.gdx.controllers.android.AndroidControllers @@ -39,6 +50,10 @@ float reportRayFixture(long, float, float, float, float, float); } +# Don't obfuscate code at all, since it's open source and we want people to mess with the game's +# internals and reverse engineer the code as much as possible. +-dontobfuscate + # Ignore missing Java Beans classes (Android doesn't have java.beans, Groovy handles this internally) -dontwarn java.beans.** @@ -61,6 +76,8 @@ # You will probably need this line in most cases: -keep public class com.badlogic.gdx.graphics.Color { *; } +-keep class me.stringdotjar.** { *; } + # These two lines are used with mapping files; see https://developer.android.com/build/shrink-code#retracing -keepattributes LineNumberTable,SourceFile -renamesourcefileattribute SourceFile diff --git a/core/build.gradle b/core/build.gradle index c03b084..4dd5eed 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -37,6 +37,8 @@ tasks.register('exportModSDK', Copy) { } // Make sure the assets are ready before the game runs. -tasks.named('build').configure { - dependsOn exportModSDK +if (project.name == 'lwjgl3') { + tasks.named('run') { + dependsOn(rootProject.tasks.named('processAssets')) + } } diff --git a/lwjgl3/build.gradle b/lwjgl3/build.gradle index 09b5ba1..aedb396 100644 --- a/lwjgl3/build.gradle +++ b/lwjgl3/build.gradle @@ -14,9 +14,6 @@ plugins { } apply plugin: 'io.github.fourlastor.construo' - -import io.github.fourlastor.construo.Target - sourceSets.main.resources.srcDirs += [rootProject.file('assets').path] mainClassName = 'me.stringdotjar.funkin.lwjgl3.Lwjgl3Launcher' application.setMainClass(mainClassName) From 24b55782903dc2000232b3db62d53a4f868739af Mon Sep 17 00:00:00 2001 From: String Date: Mon, 26 Jan 2026 20:52:06 -0600 Subject: [PATCH 15/17] Completely move FlixelGDX and Polyverse into separate modules --- .gitignore | 5 +- COMPILING.md | 6 +- CONTRIBUTING.md | 2 +- LIBGDX.md => PROJECT.md | 7 +- README.md | 19 +- android/build.gradle | 2 +- .../funkin/android/AndroidLauncher.java | 2 +- .../polyverse/script/type/SystemScript.java | 8 - {core => flixelgdx}/build.gradle | 9 +- .../me/stringdotjar/flixelgdx/Flixel.java | 11 +- .../me/stringdotjar/flixelgdx/FlixelGame.java | 39 ++-- .../flixelgdx/backend/FlixelPaths.java | 0 .../graphics/screen/FlixelScreen.java | 0 .../graphics/sprite/FlixelObject.java | 6 +- .../graphics/sprite/FlixelSprite.java | 20 +- .../flixelgdx/graphics/text/FlixelText.java | 0 .../flixelgdx/signal/FlixelSignal.java | 0 .../flixelgdx/signal/FlixelSignalData.java | 0 .../flixelgdx/tween/FlixelEase.java | 0 .../flixelgdx/tween/FlixelTween.java | 202 +++++------------- .../flixelgdx/tween/FlixelTweenManager.java | 0 .../tween/settings/FlixelTweenSettings.java | 17 ++ .../tween/settings/FlixelTweenType.java | 0 .../flixelgdx/tween/type/FlixelNumTween.java | 73 +++++++ .../flixelgdx/tween/type/FlixelVarTween.java | 165 ++++++++++++++ .../flixelgdx/util/FlixelConstants.java | 0 .../flixelgdx/util/FlixelMathUtil.java | 21 ++ .../flixelgdx/util/FlixelReflectUtil.java | 0 .../flixelgdx/util/FlixelRuntimeUtil.java | 0 funkin/build.gradle | 43 ++++ .../me/stringdotjar/funkin/FunkinGame.java | 32 ++- .../me/stringdotjar/funkin}/InitScreen.java | 2 +- .../funkin/menus/TitleScreen.java | 8 +- .../funkin/util/FunkinConstants.java | 0 lwjgl3/build.gradle | 3 +- .../funkin/lwjgl3/Lwjgl3Launcher.java | 11 +- polyverse/build.gradle | 9 + .../me/stringdotjar/polyverse/Polyverse.java | 0 .../polyverse/script/type/Script.java | 0 .../polyverse/script/type/SystemScript.java | 17 ++ settings.gradle | 2 +- 41 files changed, 516 insertions(+), 225 deletions(-) rename LIBGDX.md => PROJECT.md (80%) delete mode 100644 core/src/main/java/me/stringdotjar/polyverse/script/type/SystemScript.java rename {core => flixelgdx}/build.gradle (71%) rename {core => flixelgdx}/src/main/java/me/stringdotjar/flixelgdx/Flixel.java (98%) rename {core => flixelgdx}/src/main/java/me/stringdotjar/flixelgdx/FlixelGame.java (90%) rename {core => flixelgdx}/src/main/java/me/stringdotjar/flixelgdx/backend/FlixelPaths.java (100%) rename {core => flixelgdx}/src/main/java/me/stringdotjar/flixelgdx/graphics/screen/FlixelScreen.java (100%) rename {core => flixelgdx}/src/main/java/me/stringdotjar/flixelgdx/graphics/sprite/FlixelObject.java (62%) rename {core => flixelgdx}/src/main/java/me/stringdotjar/flixelgdx/graphics/sprite/FlixelSprite.java (95%) rename {core => flixelgdx}/src/main/java/me/stringdotjar/flixelgdx/graphics/text/FlixelText.java (100%) rename {core => flixelgdx}/src/main/java/me/stringdotjar/flixelgdx/signal/FlixelSignal.java (100%) rename {core => flixelgdx}/src/main/java/me/stringdotjar/flixelgdx/signal/FlixelSignalData.java (100%) rename {core => flixelgdx}/src/main/java/me/stringdotjar/flixelgdx/tween/FlixelEase.java (100%) rename {core => flixelgdx}/src/main/java/me/stringdotjar/flixelgdx/tween/FlixelTween.java (51%) rename {core => flixelgdx}/src/main/java/me/stringdotjar/flixelgdx/tween/FlixelTweenManager.java (100%) rename {core => flixelgdx}/src/main/java/me/stringdotjar/flixelgdx/tween/settings/FlixelTweenSettings.java (89%) rename {core => flixelgdx}/src/main/java/me/stringdotjar/flixelgdx/tween/settings/FlixelTweenType.java (100%) create mode 100644 flixelgdx/src/main/java/me/stringdotjar/flixelgdx/tween/type/FlixelNumTween.java create mode 100644 flixelgdx/src/main/java/me/stringdotjar/flixelgdx/tween/type/FlixelVarTween.java rename {core => flixelgdx}/src/main/java/me/stringdotjar/flixelgdx/util/FlixelConstants.java (100%) create mode 100644 flixelgdx/src/main/java/me/stringdotjar/flixelgdx/util/FlixelMathUtil.java rename {core => flixelgdx}/src/main/java/me/stringdotjar/flixelgdx/util/FlixelReflectUtil.java (100%) rename {core => flixelgdx}/src/main/java/me/stringdotjar/flixelgdx/util/FlixelRuntimeUtil.java (100%) create mode 100644 funkin/build.gradle rename {core => funkin}/src/main/java/me/stringdotjar/funkin/FunkinGame.java (59%) rename {core/src/main/java/me/stringdotjar/funkin/init => funkin/src/main/java/me/stringdotjar/funkin}/InitScreen.java (89%) rename {core => funkin}/src/main/java/me/stringdotjar/funkin/menus/TitleScreen.java (94%) rename {core => funkin}/src/main/java/me/stringdotjar/funkin/util/FunkinConstants.java (100%) create mode 100644 polyverse/build.gradle rename {core => polyverse}/src/main/java/me/stringdotjar/polyverse/Polyverse.java (100%) rename {core => polyverse}/src/main/java/me/stringdotjar/polyverse/script/type/Script.java (100%) create mode 100644 polyverse/src/main/java/me/stringdotjar/polyverse/script/type/SystemScript.java diff --git a/.gitignore b/.gitignore index 159c8cd..b909f8e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,9 +2,10 @@ .gradle/ gradle-app.setting /build/ +/funkin/build/ +/flixelgdx/build/ +/polyverse/build/ /android/build/ -/core/build/ -/lwjgl2/build/ /lwjgl3/build/ /html/build/ /teavm/build/ diff --git a/COMPILING.md b/COMPILING.md index 7c5f7be..465f4d7 100644 --- a/COMPILING.md +++ b/COMPILING.md @@ -1,7 +1,7 @@ # How to Compile FNF:JE > [!TIP] -> If you want some helpful info about the Gradle tasks (or the framework FNF:JE uses), consider taking a look at [LIBGDX.md](LIBGDX.md)! +> If you want some helpful info about the Gradle tasks (or the framework FNF:JE uses), consider taking a look at [LIBGDX.md](PROJECT.md)! There are two main ways you can download and compile the game's code: with GitHub Desktop or the terminal. @@ -12,13 +12,13 @@ In this guide, we'll use the GitHub Desktop method, since it the most user-frien - A [Java Development Kit (JDK)](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html) to compile the game's code. - An integrated development environment. - We recommend either one of the options listed below: - - [Intellij IDEA](https://www.jetbrains.com/idea/download/) + - [IntelliJ IDEA](https://www.jetbrains.com/idea/download/) - [Eclipse](https://www.eclipse.org/downloads/) - [VS Code](https://code.visualstudio.com/) - Some basic knowledge of programming (especially Gradle) and how to navigate an IDE. > [!TIP] -> Although every IDE listed is great for Java, we STRONGLY recommend Intellij IDEA, due to how beginner friendly and integrated it is with FNF:JE! +> Although every IDE listed is great for Java, we STRONGLY recommend IntelliJ IDEA, due to how beginner-friendly and integrated it is with FNF:JE! # Step-by-Step Guide diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8df22cc..a67cc45 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,7 +28,7 @@ hard to understand in general, then they won't be any use. Even too many comments are unnecessary, since your code should be self documented and easily readable. -***Formatting MUST match the [Google Java Style Guide](https://google.github.io/styleguide/javaguide.html).*** +***Formatting MUST match the configurations set in the [editor config file](.editorconfig), with the formatting based on the [Google Java Style Guide](https://google.github.io/styleguide/javaguide.html).*** ### Example of GOOD Comments (With Good Formatting) diff --git a/LIBGDX.md b/PROJECT.md similarity index 80% rename from LIBGDX.md rename to PROJECT.md index d0e041a..6681816 100644 --- a/LIBGDX.md +++ b/PROJECT.md @@ -4,8 +4,10 @@ FNF:JE is designed to run on multiple different platforms. Below are the different modules that hold the code for each one. -- `core`: Main module with the game's logic shared by all platforms. This is where you want to write your source code! -- `lwjgl3`: Primary desktop platform using [LWJGL3](https://www.lwjgl.org/). This is what launches the desktop versions of the game! +- `funkin`: The core part of the game's code. This is where all the game logic is implemented. +- `flixelgdx`: Custom framework that bridges [HaxeFlixel](https://haxeflixel.com/) and is based on libGDX. This is where the HaxeFlixel-like API is implemented. +- `polyverse`: Custom library for adding modding capabilities to the game. +- `lwjgl3`: Primary desktop platform using [LWJGL3](https://www.lwjgl.org/). This is what launches the desktop versions of the game! - `android`: Android mobile platform. This requires the Android SDK, which can be downloaded and configured simply by running the universal [setup file](setup/android_setup.sh)! > [!IMPORTANT] @@ -32,6 +34,7 @@ The Gradle wrapper was included, so you can run Gradle tasks using `gradlew.bat` - `clean`: removes `build` folders, which store compiled classes and built archives. - `eclipse`: generates Eclipse project data. - `idea`: generates IntelliJ project data. +- `funkin:exportModSDK`: Exports the game's API and its dependencies as `.jar` files to the assets folder. - `lwjgl3:jar`: builds the game's runnable jar, which can be found at `lwjgl3/build/libs`. - `lwjgl3:run`: starts the desktop version of the game. - `test`: runs unit tests (if any). diff --git a/README.md b/README.md index f72641c..fb8eac7 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,21 @@ Welcome to the official GitHub repository for the Java edition of Friday Night Funkin'! -### Please note that this unofficial version of the game is NOT completed! This has a long way to go, but with YOUR help, we can make this project come alive! <3 +> [!NOTICE] +> This is a fan-made project and is not affiliated with the original developers. You can play the original game [here](https://ninja-muffin24.itch.io/funkin). + +## Please note that this unofficial version of the game is NOT completed! This has a long way to go, but with YOUR help, we can make this project come alive! <3 + +# About the Project +Friday Night Funkin': Java Edition is an open-source project that aims to recreate the popular rhythm game [Friday Night Funkin'](https://github.com/FunkinCrew/Funkin) using Java. +It is built using its own custom framework based on libGDX, called FlixelGDX: a Java port of the HaxeFlixel framework used in the original game. + +The project is designed to have practically endless modding capabilities, empowering developers to use features for mods +which otherwise would be impossible to accomplish in the original game and HaxeFlixel. + +# Project Navigation + +- [Contributing](CONTRIBUTING.md) +- [Project Structure and Gradle Tasks](PROJECT.md) +- [Compiling the Game](COMPILING.md) +- [License](LICENSE.md) diff --git a/android/build.gradle b/android/build.gradle index c340702..9623ab7 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -63,7 +63,7 @@ configurations { natives } dependencies { coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5' implementation "com.badlogicgames.gdx:gdx-backend-android:$gdxVersion" - implementation project(':core') + implementation project(':funkin') api "games.rednblack.miniaudio:gdx-miniaudio-platform:$miniaudioVersion:natives-armeabi-v7a" api "games.rednblack.miniaudio:gdx-miniaudio-platform:$miniaudioVersion:natives-arm64-v8a" diff --git a/android/src/main/java/me/stringdotjar/funkin/android/AndroidLauncher.java b/android/src/main/java/me/stringdotjar/funkin/android/AndroidLauncher.java index 08aa5d8..999621a 100644 --- a/android/src/main/java/me/stringdotjar/funkin/android/AndroidLauncher.java +++ b/android/src/main/java/me/stringdotjar/funkin/android/AndroidLauncher.java @@ -4,7 +4,7 @@ import com.badlogic.gdx.backends.android.AndroidApplication; import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration; import me.stringdotjar.funkin.FunkinGame; -import me.stringdotjar.funkin.init.InitScreen; +import me.stringdotjar.funkin.InitScreen; import me.stringdotjar.funkin.util.FunkinConstants; /** Launches the Android application. */ diff --git a/core/src/main/java/me/stringdotjar/polyverse/script/type/SystemScript.java b/core/src/main/java/me/stringdotjar/polyverse/script/type/SystemScript.java deleted file mode 100644 index acc8b98..0000000 --- a/core/src/main/java/me/stringdotjar/polyverse/script/type/SystemScript.java +++ /dev/null @@ -1,8 +0,0 @@ -package me.stringdotjar.polyverse.script.type; - -public abstract class SystemScript extends Script { - - public SystemScript(String id) { - super(id); - } -} diff --git a/core/build.gradle b/flixelgdx/build.gradle similarity index 71% rename from core/build.gradle rename to flixelgdx/build.gradle index c0c016b..474d702 100644 --- a/core/build.gradle +++ b/flixelgdx/build.gradle @@ -1,8 +1,7 @@ [compileJava, compileTestJava]*.options*.encoding = 'UTF-8' -eclipse.project.name = appName + '-core' +eclipse.project.name = appName + '-flixelgdx' dependencies { - // libGDX. api "com.badlogicgames.gdx:gdx-freetype:$gdxVersion" api "com.badlogicgames.gdx:gdx:$gdxVersion" api "com.github.tommyettinger:anim8-gdx:$anim8Version" @@ -10,11 +9,9 @@ dependencies { api "io.github.libktx:ktx-freetype:$ktxVersion" api "games.rednblack.miniaudio:miniaudio:$miniaudioVersion" - // FNF:JE. - implementation "org.apache.groovy:groovy:4.0.12" - implementation "org.jetbrains:annotations:15.0" + implementation "org.jetbrains:annotations:26.0.2-1" - if(enableGraalNative == 'true') { + if (enableGraalNative == 'true') { implementation "io.github.berstanio:gdx-svmhelper-annotations:$graalHelperVersion" } } diff --git a/core/src/main/java/me/stringdotjar/flixelgdx/Flixel.java b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/Flixel.java similarity index 98% rename from core/src/main/java/me/stringdotjar/flixelgdx/Flixel.java rename to flixelgdx/src/main/java/me/stringdotjar/flixelgdx/Flixel.java index 5a3d895..615053f 100644 --- a/core/src/main/java/me/stringdotjar/flixelgdx/Flixel.java +++ b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/Flixel.java @@ -40,15 +40,12 @@ public final class Flixel { private static MAGroup soundsGroup; /** The sound for playing music throughout the game. */ - public static MASound music; + private static MASound music; /** The current master volume that is set. */ private static float masterVolume = 1; - /** - * The static instance used to access the core elements of the game. This includes the main loop, - * setting the current screen, and more. - */ + /** The static instance used to access the core elements of the game. */ private static FlixelGame game; /** Has the global manager been initialized yet? */ @@ -323,7 +320,7 @@ public static void playMusic(String path, float volume, boolean looping, boolean * @param volume The new master volume to set. */ public static void setMasterVolume(float volume) { - engine.setMasterVolume(volume); + engine.setMasterVolume(!(volume > 1.0) ? volume : 1.0f); masterVolume = volume; } @@ -458,7 +455,7 @@ private static void outputLog(String tag, Object message, FlixelLogLevel level) false, underline); String formattedTag = colorText("[" + level + "] [" + tag + "] [" + file + "] [" + method + "] ", color, true, false, underline); - String formattedMessage = colorText(message.toString(), color, false, true, underline); + String formattedMessage = colorText((message != null) ? message.toString() : null, color, false, true, underline); System.out.println(timeAndDate + formattedTag + formattedMessage); } diff --git a/core/src/main/java/me/stringdotjar/flixelgdx/FlixelGame.java b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/FlixelGame.java similarity index 90% rename from core/src/main/java/me/stringdotjar/flixelgdx/FlixelGame.java rename to flixelgdx/src/main/java/me/stringdotjar/flixelgdx/FlixelGame.java index e3d526b..36b8078 100644 --- a/core/src/main/java/me/stringdotjar/flixelgdx/FlixelGame.java +++ b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/FlixelGame.java @@ -15,13 +15,9 @@ import com.badlogic.gdx.utils.viewport.FitViewport; import me.stringdotjar.flixelgdx.graphics.screen.FlixelScreen; import me.stringdotjar.flixelgdx.graphics.sprite.FlixelObject; -import me.stringdotjar.flixelgdx.graphics.sprite.FlixelSprite; import me.stringdotjar.flixelgdx.tween.FlixelTween; import me.stringdotjar.flixelgdx.util.FlixelConstants; import me.stringdotjar.flixelgdx.util.FlixelRuntimeUtil; -import me.stringdotjar.funkin.util.FunkinConstants; -import me.stringdotjar.polyverse.Polyverse; -import me.stringdotjar.polyverse.script.type.Script; import static me.stringdotjar.flixelgdx.signal.FlixelSignalData.RenderSignalData; @@ -37,11 +33,14 @@ public abstract class FlixelGame implements ApplicationListener { protected String title; /** The size of the game's starting window position and its viewport. */ - protected Vector2 size; + protected Vector2 windowSize; /** The entry point screen the game starts in (which becomes null after the game is done setting up!). */ protected FlixelScreen initialScreen; + /** Is the game's window currently focused? */ + protected boolean isFocused = true; + /** Is the game's window currently minimized? */ protected boolean isMinimized = false; @@ -71,7 +70,7 @@ public abstract class FlixelGame implements ApplicationListener { */ public FlixelGame(String title, int width, int height, FlixelScreen initialScreen) { this.title = title; - this.size = new Vector2(width, height); + this.windowSize = new Vector2(width, height); this.initialScreen = initialScreen; } @@ -80,11 +79,11 @@ public void create() { configureCrashHandler(); // This should ALWAYS be called first no matter what! batch = new SpriteBatch(); - viewport = new FitViewport(size.x, size.y); + viewport = new FitViewport(windowSize.x, windowSize.y); viewport.apply(); camera = new OrthographicCamera(); - camera.setToOrtho(false, size.x, size.y); + camera.setToOrtho(false, windowSize.x, windowSize.y); stage = new Stage(viewport, batch); @@ -127,10 +126,11 @@ public void render() { screen.render(delta); var members = screen.members.begin(); for (FlixelObject object : members) { - if (object instanceof FlixelSprite sprite) { - sprite.update(delta); - sprite.draw(batch); + if (object == null) { + continue; } + object.update(delta); + object.draw(batch); } screen.members.end(); } @@ -152,7 +152,7 @@ public void resume() {} /** Called when the user regains focus on the game's window. */ public void onWindowFocused() { - Flixel.setMasterVolume(1); + isFocused = true; Flixel.Signals.windowFocused.dispatch(); Flixel.info("Game window has regained focus."); } @@ -162,7 +162,7 @@ public void onWindowUnfocused() { if (isMinimized) { return; } - Flixel.setMasterVolume(0.008f); + isFocused = false; Flixel.Signals.windowUnfocused.dispatch(); Flixel.info("Game window has lost focus."); } @@ -178,7 +178,7 @@ public void onWindowMinimized(boolean iconified) { if (!isMinimized) { return; } - Flixel.setMasterVolume(0); + isFocused = false; Flixel.Signals.windowMinimized.dispatch(); Flixel.info("Game window has been minimized."); } @@ -187,7 +187,7 @@ public void onWindowMinimized(boolean iconified) { public void toggleFullscreen() { boolean isFullscreen = Gdx.graphics.isFullscreen(); if (isFullscreen) { - Gdx.graphics.setWindowedMode(FunkinConstants.WINDOW_WIDTH, FunkinConstants.WINDOW_HEIGHT); + Gdx.graphics.setWindowedMode((int) windowSize.x, (int) windowSize.y); Flixel.info("Exiting fullscreen mode."); } else { Gdx.graphics.setFullscreenMode(Gdx.graphics.getDisplayMode()); @@ -216,7 +216,6 @@ public void dispose() { Flixel.getAudioEngine().dispose(); Flixel.info("Disposing and shutting down scripts..."); - Polyverse.forAllScripts(Script::onDispose); Flixel.Signals.postGameClose.dispatch(); } @@ -225,8 +224,12 @@ public String getTitle() { return title; } - public int[] getWindowSize() { - return new int[]{(int) size.x, (int) size.y}; + public Vector2 getWindowSize() { + return windowSize; + } + + public boolean isFocused() { + return isFocused; } public Stage getStage() { diff --git a/core/src/main/java/me/stringdotjar/flixelgdx/backend/FlixelPaths.java b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/backend/FlixelPaths.java similarity index 100% rename from core/src/main/java/me/stringdotjar/flixelgdx/backend/FlixelPaths.java rename to flixelgdx/src/main/java/me/stringdotjar/flixelgdx/backend/FlixelPaths.java diff --git a/core/src/main/java/me/stringdotjar/flixelgdx/graphics/screen/FlixelScreen.java b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/graphics/screen/FlixelScreen.java similarity index 100% rename from core/src/main/java/me/stringdotjar/flixelgdx/graphics/screen/FlixelScreen.java rename to flixelgdx/src/main/java/me/stringdotjar/flixelgdx/graphics/screen/FlixelScreen.java diff --git a/core/src/main/java/me/stringdotjar/flixelgdx/graphics/sprite/FlixelObject.java b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/graphics/sprite/FlixelObject.java similarity index 62% rename from core/src/main/java/me/stringdotjar/flixelgdx/graphics/sprite/FlixelObject.java rename to flixelgdx/src/main/java/me/stringdotjar/flixelgdx/graphics/sprite/FlixelObject.java index 9d576f9..11219fc 100644 --- a/core/src/main/java/me/stringdotjar/flixelgdx/graphics/sprite/FlixelObject.java +++ b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/graphics/sprite/FlixelObject.java @@ -1,9 +1,13 @@ package me.stringdotjar.flixelgdx.graphics.sprite; +import com.badlogic.gdx.graphics.g2d.Batch; import me.stringdotjar.flixelgdx.graphics.screen.FlixelScreen; /** * An interface which allows any class that implements it to be added to a {@link * FlixelScreen}. */ -public interface FlixelObject {} +public interface FlixelObject { + void update(float delta); + void draw(Batch batch); +} diff --git a/core/src/main/java/me/stringdotjar/flixelgdx/graphics/sprite/FlixelSprite.java b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/graphics/sprite/FlixelSprite.java similarity index 95% rename from core/src/main/java/me/stringdotjar/flixelgdx/graphics/sprite/FlixelSprite.java rename to flixelgdx/src/main/java/me/stringdotjar/flixelgdx/graphics/sprite/FlixelSprite.java index ea71acb..ca82924 100644 --- a/core/src/main/java/me/stringdotjar/flixelgdx/graphics/sprite/FlixelSprite.java +++ b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/graphics/sprite/FlixelSprite.java @@ -5,6 +5,7 @@ import com.badlogic.gdx.graphics.g2d.Animation; import com.badlogic.gdx.graphics.g2d.Batch; import com.badlogic.gdx.graphics.g2d.Sprite; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.math.Rectangle; @@ -62,11 +63,11 @@ public FlixelSprite() { * * @param delta The amount of time that has passed since the last frame update. */ + @Override public void update(float delta) { if (animations.containsKey(currentAnim)) { stateTime += delta; - currentFrame = - (TextureAtlas.AtlasRegion) animations.get(currentAnim).getKeyFrame(stateTime, looping); + currentFrame = (TextureAtlas.AtlasRegion) animations.get(currentAnim).getKeyFrame(stateTime, looping); setRegion(currentFrame); } } @@ -286,17 +287,14 @@ public void draw(Batch batch) { // Adjust drawing position by the Sparrow offsets float drawX = getX() + currentFrame.offsetX; - float drawY = - getY() - + (currentFrame.originalHeight - currentFrame.getRegionHeight() - currentFrame.offsetY); + float drawY = getY() + (currentFrame.originalHeight - currentFrame.getRegionHeight() - currentFrame.offsetY); batch.draw( currentFrame, drawX, drawY, originX - currentFrame.offsetX, - originY - - (currentFrame.originalHeight - currentFrame.getRegionHeight() - currentFrame.offsetY), + originY - (currentFrame.originalHeight - currentFrame.getRegionHeight() - currentFrame.offsetY), currentFrame.getRegionWidth(), currentFrame.getRegionHeight(), getScaleX(), @@ -335,6 +333,14 @@ public void reset() { frames = null; } + public void changeX(float x) { + setX(getX() + x); + } + + public void changeY(float y) { + setY(getY() + y); + } + public Map> getAnimations() { return animations; } diff --git a/core/src/main/java/me/stringdotjar/flixelgdx/graphics/text/FlixelText.java b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/graphics/text/FlixelText.java similarity index 100% rename from core/src/main/java/me/stringdotjar/flixelgdx/graphics/text/FlixelText.java rename to flixelgdx/src/main/java/me/stringdotjar/flixelgdx/graphics/text/FlixelText.java diff --git a/core/src/main/java/me/stringdotjar/flixelgdx/signal/FlixelSignal.java b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/signal/FlixelSignal.java similarity index 100% rename from core/src/main/java/me/stringdotjar/flixelgdx/signal/FlixelSignal.java rename to flixelgdx/src/main/java/me/stringdotjar/flixelgdx/signal/FlixelSignal.java diff --git a/core/src/main/java/me/stringdotjar/flixelgdx/signal/FlixelSignalData.java b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/signal/FlixelSignalData.java similarity index 100% rename from core/src/main/java/me/stringdotjar/flixelgdx/signal/FlixelSignalData.java rename to flixelgdx/src/main/java/me/stringdotjar/flixelgdx/signal/FlixelSignalData.java diff --git a/core/src/main/java/me/stringdotjar/flixelgdx/tween/FlixelEase.java b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/tween/FlixelEase.java similarity index 100% rename from core/src/main/java/me/stringdotjar/flixelgdx/tween/FlixelEase.java rename to flixelgdx/src/main/java/me/stringdotjar/flixelgdx/tween/FlixelEase.java diff --git a/core/src/main/java/me/stringdotjar/flixelgdx/tween/FlixelTween.java b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/tween/FlixelTween.java similarity index 51% rename from core/src/main/java/me/stringdotjar/flixelgdx/tween/FlixelTween.java rename to flixelgdx/src/main/java/me/stringdotjar/flixelgdx/tween/FlixelTween.java index 3c6652e..36e0430 100644 --- a/core/src/main/java/me/stringdotjar/flixelgdx/tween/FlixelTween.java +++ b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/tween/FlixelTween.java @@ -1,32 +1,25 @@ package me.stringdotjar.flixelgdx.tween; import com.badlogic.gdx.utils.Pool; -import me.stringdotjar.flixelgdx.Flixel; import me.stringdotjar.flixelgdx.tween.settings.FlixelTweenSettings; -import me.stringdotjar.flixelgdx.util.FlixelConstants; -import me.stringdotjar.flixelgdx.util.FlixelReflectUtil; +import me.stringdotjar.flixelgdx.tween.type.FlixelNumTween; +import me.stringdotjar.flixelgdx.tween.type.FlixelVarTween; import org.jetbrains.annotations.NotNull; -import java.lang.reflect.Field; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - /** - * Core class for creating new tweens to add nice and smooth animations to visual objects. + * Core class for creating new tweens to add nice and smooth animations. + * + *

Please note that while this isn't an abstract class, it is advised to NOT create instances + * of this class, since it does not implement the actual tweening logic. Instead, you should use one of + * its subclasses, such as {@link FlixelVarTween}, or create your own subclass and add your own functionality. * - *

Note that this doesn't have to be used on sprites, it can be used on just about anything! + *

The only reason this class is not abstract is to allow pooling of generic tweens when needed to save memory. */ public class FlixelTween implements Pool.Poolable { /** The global tween manager for the entire game. */ protected static FlixelTweenManager globalManager = new FlixelTweenManager(); - /** The object to tween. */ - protected Object object; - /** The settings used for how the tween is handled and calculated (aka how it looks and animates). */ protected FlixelTweenSettings tweenSettings; @@ -36,9 +29,6 @@ public class FlixelTween implements Pool.Poolable { /** How far the tween is tweening itself. This is what's used to actually tween the object! */ protected float scale = 0.0f; - /** The update callback for {@code this} tween to change the objects values every update. */ - protected FlixelTween.FunkinTweenUpdateCallback updateCallback; - /** How many seconds has elapsed since {@code this} tween started. */ protected float secondsSinceStart = 0.0f; @@ -57,69 +47,63 @@ public class FlixelTween implements Pool.Poolable { /** Is {@code this} tween tweening backwards? */ protected boolean backward = false; - /** The initial values of the fields being tweened. */ - protected final Map initialValues = new HashMap<>(); - - /** - * Cache of the fields being tweened for faster access so they aren't accessed every time the - * {@link #start()} method is called. - */ - protected Field[] fieldsCache = null; - /** Default constructor for pooling purposes. */ protected FlixelTween() {} + protected FlixelTween(FlixelTweenSettings tweenSettings) { + this.tweenSettings = tweenSettings; + } + /** - * Constructs a new tween. - * - *

Note that this does NOT add the tween to the global manager, it just assigns its main - * values. That's it. If you wish to create a tween to automatically start working, you might want - * to see {@link FlixelTween#tween(Object object, FlixelTweenSettings tweenSettings, - * FunkinTweenUpdateCallback updateCallback)}. + * Creates a new tween with the provided settings and starts it in the global tween manager. * - * @param object The object to tween values. - * @param settings The settings that configure and determine how the tween should animate and last - * for. + * @param object The object to tween its values. + * @param tweenSettings The settings that configure and determine how the tween should animate. * @param updateCallback Callback function for updating the objects values when the tween updates. + * @return The newly created and started tween. */ - protected FlixelTween( - Object object, FlixelTweenSettings settings, FunkinTweenUpdateCallback updateCallback) { - this.object = object; - this.tweenSettings = settings; - this.updateCallback = updateCallback; + public static FlixelTween tween(Object object, FlixelTweenSettings tweenSettings, FlixelVarTween.FunkinVarTweenUpdateCallback updateCallback) { + return new FlixelVarTween(object, tweenSettings, updateCallback) + .setManager(globalManager) + .start(); + } + + public static FlixelTween num(float from, float to, FlixelTweenSettings tweenSettings, FlixelNumTween.FlixelNumTweenUpdateCallback updateCallback) { + return new FlixelNumTween(from, to, tweenSettings, updateCallback) + .setManager(globalManager) + .start(); } /** - * Creates a new tween with the provided settings and starts it in the global tween manager. + * Starts {@code this} tween and resets every value to its initial state. * - * @param object The object to tween its values. - * @param tweenSettings The settings that configure and determine how the tween should animate. - * @param updateCallback Callback function for updating the objects values when the tween updates. - * @return The newly created and started tween. + * @return {@code this} tween. */ - public static FlixelTween tween( - Object object, FlixelTweenSettings tweenSettings, FunkinTweenUpdateCallback updateCallback) { - return new FlixelTween(object, tweenSettings, updateCallback).setManager(globalManager).start(); + public FlixelTween start() { + resetBasic(); + running = true; + finished = false; + return this; } + /** + * Updates {@code this} tween by the given delta time. + * + * @param delta How much time has passed since the last update. + */ public void update(float delta) { - if (paused || finished) { - return; - } - if (object == null) { + if (paused || finished || !running) { return; } if (tweenSettings == null) { return; } - if (updateCallback == null) { - return; - } var ease = tweenSettings.getEase(); var duration = tweenSettings.getDuration(); var onStart = tweenSettings.getOnStart(); var onUpdate = tweenSettings.getOnUpdate(); + var onComplete = tweenSettings.getOnComplete(); var framerate = tweenSettings.getFramerate(); float preTick = secondsSinceStart; @@ -150,33 +134,13 @@ public void update(float delta) { } } - // Update the object's fields based on the tween progress. - var newValues = new HashMap(); - var goals = tweenSettings.getGoalFields(); - for (String field : goals) { - FlixelTweenSettings.FunkinTweenGoal goal = tweenSettings.getGoal(field); - if (goal == null) { - continue; - } - if (initialValues.isEmpty()) { - continue; - } - if (!initialValues.containsKey(field)) { - continue; - } - float startValue = initialValues.get(field); - float goalValue = goal.value(); - float newValue = startValue + (goalValue - startValue) * scale; - newValues.put(field, newValue); - } - if (!newValues.isEmpty() && initialValues.keySet().containsAll(newValues.keySet())) { - updateCallback.update(newValues); - } - // Check if the tween has finished. if (secondsSinceStart >= duration + delay) { scale = (backward) ? 0 : 1; finished = true; + if (onComplete != null) { + onComplete.run(this); + } } else { if (postTick > preTick && onUpdate != null) { onUpdate.run(this); @@ -184,63 +148,6 @@ public void update(float delta) { } } - /** - * Starts {@code this} tween and resets every value to its initial state. - * - * @return {@code this} tween. - */ - public FlixelTween start() { - resetBasic(); - running = true; - finished = false; - - if (tweenSettings == null) { - Flixel.warn("FlixelTween", "No tween settings were provided for the tween."); - return this; - } - var neededFields = tweenSettings.getGoalFields(); - if (neededFields == null || neededFields.isEmpty()) { - Flixel.warn("FlixelTween", "No fields were provided to tween on the object."); - return this; - } - - if (fieldsCache == null) { - fieldsCache = FlixelReflectUtil.getAllFieldsAsArray(object.getClass()); - } - - // Ensure that the fields provided actually exist on the object and are floating point values. - // If there are fields that the tween is trying to tween that don't exist, then throw an error. - Set fieldIds = - Arrays.stream(fieldsCache).map(Field::getName).collect(Collectors.toSet()); - for (String neededField : neededFields) { - if (!fieldIds.contains(neededField)) { - String message = "Field \"" + neededField + "\" does not exist on the given object."; - Flixel.error("FlixelTween", message); - throw new RuntimeException(message); - } - } - - for (Field field : fieldsCache) { - try { - String fName = field.getName(); - if (!field.trySetAccessible()) { - continue; - } - if (!neededFields.contains(fName)) { - continue; - } - if (field.getType() != float.class) { - continue; - } - initialValues.put(fName, field.getFloat(object)); - } catch (IllegalAccessException e) { - Flixel.error( - FlixelConstants.System.LOG_TAG, "Could not access field \"" + field.getName() + "\".", e); - } - } - return this; - } - /** * Resumes {@code this} tween if it was previously paused. * @@ -270,13 +177,11 @@ public FlixelTween pause() { */ public FlixelTween stop() { running = false; - finished = true; return this; } /** - * Cancels {@code this} tween and automatically defaults its values, removing it from the manager - * by default. + * Cancels {@code this} tween and automatically defaults its values, removing it from its manager by default. * * @return {@code this} tween. */ @@ -302,7 +207,6 @@ public FlixelTween cancel(boolean remove) { public void reset() { resetBasic(); manager = null; - fieldsCache = null; } /** @@ -317,7 +221,6 @@ public void resetBasic() { running = false; finished = false; backward = false; - initialValues.clear(); } public FlixelTweenSettings getTweenSettings() { @@ -333,6 +236,10 @@ public static FlixelTweenManager getGlobalManager() { return globalManager; } + public FlixelTweenManager getManager() { + return manager; + } + public FlixelTween setManager(FlixelTweenManager newManager) { if (newManager != null) { if (manager != null) { @@ -345,17 +252,4 @@ public FlixelTween setManager(FlixelTweenManager newManager) { } return this; } - - /** Callback interface for changing an objects values when a tween updates its values. */ - @FunctionalInterface - public interface FunkinTweenUpdateCallback { - - /** - * A callback method that is called when the tween updates its values during its tweening (or - * animating) process. - * - * @param values The new current values of the fields being tweened during the animation. - */ - void update(Map values); - } } diff --git a/core/src/main/java/me/stringdotjar/flixelgdx/tween/FlixelTweenManager.java b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/tween/FlixelTweenManager.java similarity index 100% rename from core/src/main/java/me/stringdotjar/flixelgdx/tween/FlixelTweenManager.java rename to flixelgdx/src/main/java/me/stringdotjar/flixelgdx/tween/FlixelTweenManager.java diff --git a/core/src/main/java/me/stringdotjar/flixelgdx/tween/settings/FlixelTweenSettings.java b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/tween/settings/FlixelTweenSettings.java similarity index 89% rename from core/src/main/java/me/stringdotjar/flixelgdx/tween/settings/FlixelTweenSettings.java rename to flixelgdx/src/main/java/me/stringdotjar/flixelgdx/tween/settings/FlixelTweenSettings.java index 70f22dd..1f202bb 100644 --- a/core/src/main/java/me/stringdotjar/flixelgdx/tween/settings/FlixelTweenSettings.java +++ b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/tween/settings/FlixelTweenSettings.java @@ -59,6 +59,8 @@ public FlixelTweenSettings( /** * Adds a new goal to tween an objects value to. * + *

Note that this is only used on a {@link me.stringdotjar.flixelgdx.tween.type.FlixelVarTween}. + * * @param field The field to tween. * @param value The value to tween the field to. * @return {@code this} tween settings object for chaining. @@ -165,6 +167,21 @@ public FlixelTweenSettings setType(@NotNull FlixelTweenType type) { return this; } + public FlixelTweenSettings setOnStart(FlixelEase.FunkinEaseStartCallback onStart) { + this.onStart = onStart; + return this; + } + + public FlixelTweenSettings setOnUpdate(FlixelEase.FunkinEaseUpdateCallback onUpdate) { + this.onUpdate = onUpdate; + return this; + } + + public FlixelTweenSettings setOnComplete(FlixelEase.FunkinEaseCompleteCallback onComplete) { + this.onComplete = onComplete; + return this; + } + /** * A record containing basic info for a tween goal (aka a field to tween a numeric value to). * diff --git a/core/src/main/java/me/stringdotjar/flixelgdx/tween/settings/FlixelTweenType.java b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/tween/settings/FlixelTweenType.java similarity index 100% rename from core/src/main/java/me/stringdotjar/flixelgdx/tween/settings/FlixelTweenType.java rename to flixelgdx/src/main/java/me/stringdotjar/flixelgdx/tween/settings/FlixelTweenType.java diff --git a/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/tween/type/FlixelNumTween.java b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/tween/type/FlixelNumTween.java new file mode 100644 index 0000000..40cc7d1 --- /dev/null +++ b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/tween/type/FlixelNumTween.java @@ -0,0 +1,73 @@ +package me.stringdotjar.flixelgdx.tween.type; + +import me.stringdotjar.flixelgdx.tween.FlixelTween; +import me.stringdotjar.flixelgdx.tween.settings.FlixelTweenSettings; + +/** + * Tween type that tweens one numerical value to another. + */ +public class FlixelNumTween extends FlixelTween { + + /** The starting value of the tween. */ + protected float start; + + /** The target value of the tween. */ + protected float end; + + /** The current value of the tween. */ + protected float value; + + /** The range between the start and end values. */ + protected float range; + + /** Callback function for updating the value when the tween updates. */ + protected FlixelNumTweenUpdateCallback updateCallback; + + /** + * Constructs a new numerical tween, which will tween a simple starting number to an ending value. + * + * @param start The starting value. + * @param end The ending value. + * @param settings The settings that configure and determine how the tween should animate. + * @param updateCallback Callback function for updating any variable that needs the current value when the tween updates. + */ + public FlixelNumTween(float start, float end, FlixelTweenSettings settings, FlixelNumTweenUpdateCallback updateCallback) { + super(settings); + this.start = start; + this.end = end; + this.value = start; + this.range = end - start; + this.updateCallback = updateCallback; + } + + @Override + public void update(float delta) { + super.update(delta); + + if (paused || finished || !running) { + return; + } + if (updateCallback == null) { + return; + } + + value = start + range * scale; + + updateCallback.update(value); + } + + /** + * Functional interface for updating the numerical value when the tween updates. This is for updating any + * variable that needs the current value of the tween. + */ + @FunctionalInterface + public interface FlixelNumTweenUpdateCallback { + + /** + * A callback method that is called when the tween updates its value during its tweening (or animating) process. + * + * @param value The new current value of the numerical tween during the animation. + */ + void update(float value); + } +} diff --git a/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/tween/type/FlixelVarTween.java b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/tween/type/FlixelVarTween.java new file mode 100644 index 0000000..3f14829 --- /dev/null +++ b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/tween/type/FlixelVarTween.java @@ -0,0 +1,165 @@ +package me.stringdotjar.flixelgdx.tween.type; + +import me.stringdotjar.flixelgdx.Flixel; +import me.stringdotjar.flixelgdx.tween.FlixelTween; +import me.stringdotjar.flixelgdx.tween.settings.FlixelTweenSettings; +import me.stringdotjar.flixelgdx.util.FlixelConstants; +import me.stringdotjar.flixelgdx.util.FlixelReflectUtil; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Tween type for tweening specific fields on an object using reflection. + */ +public class FlixelVarTween extends FlixelTween { + + /** The object to tween. */ + protected Object object; + + /** The initial values of the fields being tweened. */ + protected final Map initialValues = new HashMap<>(); + + /** + * Cache of the fields being tweened for faster access so they aren't accessed every time the + * {@link #start()} method is called. + */ + protected Field[] fieldsCache = null; + + /** The update callback for {@code this} tween to change the objects values every update. */ + protected FlixelVarTween.FunkinVarTweenUpdateCallback updateCallback; + + /** + * Constructs a new object tween using reflection. + * + *

Note that this does NOT add the tween to the global manager, it just assigns its main + * values. That's it. If you wish to create a tween to automatically start working, you might want + * to see {@link FlixelTween#tween(Object object, FlixelTweenSettings tweenSettings, + * FunkinVarTweenUpdateCallback updateCallback)}. + * + * @param object The object to tween values. + * @param settings The settings that configure and determine how the tween should animate and last for. + * @param updateCallback Callback function for updating the objects values when the tween updates. + */ + public FlixelVarTween(Object object, FlixelTweenSettings settings, FunkinVarTweenUpdateCallback updateCallback) { + super(settings); + this.object = object; + this.updateCallback = updateCallback; + } + + @Override + public FlixelTween start() { + super.start(); + + if (tweenSettings == null) { + Flixel.warn("FlixelTween", "No tween settings were provided for the tween."); + return this; + } + var neededFields = tweenSettings.getGoalFields(); + if (neededFields == null || neededFields.isEmpty()) { + Flixel.warn("FlixelTween", "No fields were provided to tween on the object."); + return this; + } + + if (fieldsCache == null) { + fieldsCache = FlixelReflectUtil.getAllFieldsAsArray(object.getClass()); + } + + // Ensure that the fields provided actually exist on the object and are floating point values. + // If there are fields that the tween is trying to tween that don't exist, then throw an error. + Set fieldIds = Arrays.stream(fieldsCache).map(Field::getName).collect(Collectors.toSet()); + for (String neededField : neededFields) { + if (!fieldIds.contains(neededField)) { + String message = "Field \"" + neededField + "\" does not exist on the given object."; + Flixel.error("FlixelTween", message); + throw new RuntimeException(message); + } + } + + for (Field field : fieldsCache) { + try { + String fName = field.getName(); + if (!field.trySetAccessible()) { + continue; + } + if (!neededFields.contains(fName)) { + continue; + } + if (field.getType() != float.class) { + continue; + } + initialValues.put(fName, field.getFloat(object)); + } catch (IllegalAccessException e) { + Flixel.error(FlixelConstants.System.LOG_TAG, "Could not access field \"" + field.getName() + "\".", e); + } + } + return this; + } + + @Override + public void update(float delta) { + super.update(delta); + + if (object == null) { + return; + } + if (paused || finished) { + return; + } + if (updateCallback == null) { + return; + } + + // Update the object's fields based on the tween progress. + var newValues = new HashMap(); + var goals = tweenSettings.getGoalFields(); + for (String field : goals) { + FlixelTweenSettings.FunkinTweenGoal goal = tweenSettings.getGoal(field); + if (goal == null) { + continue; + } + if (initialValues.isEmpty()) { + continue; + } + if (!initialValues.containsKey(field)) { + continue; + } + float startValue = initialValues.get(field); + float goalValue = goal.value(); + float newValue = startValue + (goalValue - startValue) * scale; + newValues.put(field, newValue); + } + if (!newValues.isEmpty() && initialValues.keySet().containsAll(newValues.keySet())) { + updateCallback.update(newValues); + } + } + + @Override + public void reset() { + super.reset(); + fieldsCache = null; + } + + @Override + public void resetBasic() { + super.resetBasic(); + initialValues.clear(); + } + + /** Callback interface for changing an objects values when a var tween updates its values. */ + @FunctionalInterface + public interface FunkinVarTweenUpdateCallback { + + /** + * A callback method that is called when the tween updates its values during its tweening (or + * animating) process. + * + * @param values The new current values of the fields being tweened during the animation. + */ + void update(Map values); + } +} diff --git a/core/src/main/java/me/stringdotjar/flixelgdx/util/FlixelConstants.java b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/util/FlixelConstants.java similarity index 100% rename from core/src/main/java/me/stringdotjar/flixelgdx/util/FlixelConstants.java rename to flixelgdx/src/main/java/me/stringdotjar/flixelgdx/util/FlixelConstants.java diff --git a/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/util/FlixelMathUtil.java b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/util/FlixelMathUtil.java new file mode 100644 index 0000000..bd28994 --- /dev/null +++ b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/util/FlixelMathUtil.java @@ -0,0 +1,21 @@ +package me.stringdotjar.flixelgdx.util; + +/** + * Utility class for various math related functions used in FlixelGDX. + */ +public final class FlixelMathUtil { + + /** + * Rounds a float value to a specified number of decimal places. + * + * @param value The float value to round. + * @param decimalPlaces The number of decimal places to round to. + * @return The rounded float value. + */ + public static float round(float value, int decimalPlaces) { + float scale = (float) Math.pow(10, decimalPlaces); + return Math.round(value * scale) / scale; + } + + private FlixelMathUtil() {} +} diff --git a/core/src/main/java/me/stringdotjar/flixelgdx/util/FlixelReflectUtil.java b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/util/FlixelReflectUtil.java similarity index 100% rename from core/src/main/java/me/stringdotjar/flixelgdx/util/FlixelReflectUtil.java rename to flixelgdx/src/main/java/me/stringdotjar/flixelgdx/util/FlixelReflectUtil.java diff --git a/core/src/main/java/me/stringdotjar/flixelgdx/util/FlixelRuntimeUtil.java b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/util/FlixelRuntimeUtil.java similarity index 100% rename from core/src/main/java/me/stringdotjar/flixelgdx/util/FlixelRuntimeUtil.java rename to flixelgdx/src/main/java/me/stringdotjar/flixelgdx/util/FlixelRuntimeUtil.java diff --git a/funkin/build.gradle b/funkin/build.gradle new file mode 100644 index 0000000..b1991df --- /dev/null +++ b/funkin/build.gradle @@ -0,0 +1,43 @@ +[compileJava, compileTestJava]*.options*.encoding = 'UTF-8' +eclipse.project.name = appName + '-funkin' + +dependencies { + api "com.badlogicgames.gdx:gdx-freetype:$gdxVersion" + api "com.badlogicgames.gdx:gdx:$gdxVersion" + api "com.github.tommyettinger:anim8-gdx:$anim8Version" + api "com.github.tommyettinger:libgdx-utils:$utilsVersion" + api "io.github.libktx:ktx-freetype:$ktxVersion" + api "games.rednblack.miniaudio:miniaudio:$miniaudioVersion" + api project(":flixelgdx") + api project(":polyverse") + + implementation "org.jetbrains:annotations:26.0.2-1" + + if (enableGraalNative == 'true') { + implementation "io.github.berstanio:gdx-svmhelper-annotations:$graalHelperVersion" + } +} + +tasks.register('exportModSDK', Copy) { + group = "modding" + description = "Copies all API JARs needed for modding into the assets folder." + + into "$rootProject.projectDir/assets/sdk/api" + + from(tasks.named('jar')) { + rename { "funkin-api.jar" } + } + + // Include every external dependency. + from(configurations.runtimeClasspath) { + // Exclude native files, since modders only need the Java interfaces. + exclude "**/*.dll", "**/*.so", "**/*.dylib", "**/*.jnilib" + } +} + +// Make sure the assets are ready before the game runs. +if (project.name == 'lwjgl3') { + tasks.named('run') { + dependsOn(rootProject.tasks.named('processAssets')) + } +} diff --git a/core/src/main/java/me/stringdotjar/funkin/FunkinGame.java b/funkin/src/main/java/me/stringdotjar/funkin/FunkinGame.java similarity index 59% rename from core/src/main/java/me/stringdotjar/funkin/FunkinGame.java rename to funkin/src/main/java/me/stringdotjar/funkin/FunkinGame.java index 0040de4..cfdce50 100644 --- a/core/src/main/java/me/stringdotjar/funkin/FunkinGame.java +++ b/funkin/src/main/java/me/stringdotjar/funkin/FunkinGame.java @@ -13,6 +13,8 @@ */ public class FunkinGame extends FlixelGame { + private float lastVolume = 1.0f; + public FunkinGame(String title, int width, int height, FlixelScreen initialScreen) { super(title, width, height, initialScreen); } @@ -26,10 +28,38 @@ public void create() { @Override public void render() { super.render(); - Polyverse.forAllScripts(script -> script.onRender(Flixel.getDelta())); } + @Override + public void dispose() { + super.dispose(); + Polyverse.forAllScripts(Script::onDispose); + } + + @Override + public void onWindowFocused() { + super.onWindowFocused(); + Flixel.setMasterVolume(lastVolume); + Polyverse.forEachScript(SystemScript.class, SystemScript::onWindowFocused); + } + + @Override + public void onWindowUnfocused() { + super.onWindowUnfocused(); + lastVolume = Flixel.getMasterVolume(); + Flixel.setMasterVolume(0.008f); + Polyverse.forEachScript(SystemScript.class, SystemScript::onWindowUnfocused); + } + + @Override + public void onWindowMinimized(boolean iconified) { + super.onWindowMinimized(iconified); + lastVolume = Flixel.getMasterVolume(); + Flixel.setMasterVolume(0); + Polyverse.forEachScript(SystemScript.class, script -> script.onWindowMinimized(iconified)); + } + private void configurePolyverse() { // Register Polyverse script types. Polyverse.registerScriptType(Script.class); // Master type, DO NOT REMOVE THIS! diff --git a/core/src/main/java/me/stringdotjar/funkin/init/InitScreen.java b/funkin/src/main/java/me/stringdotjar/funkin/InitScreen.java similarity index 89% rename from core/src/main/java/me/stringdotjar/funkin/init/InitScreen.java rename to funkin/src/main/java/me/stringdotjar/funkin/InitScreen.java index f73bf8c..e90d619 100644 --- a/core/src/main/java/me/stringdotjar/funkin/init/InitScreen.java +++ b/funkin/src/main/java/me/stringdotjar/funkin/InitScreen.java @@ -1,4 +1,4 @@ -package me.stringdotjar.funkin.init; +package me.stringdotjar.funkin; import me.stringdotjar.flixelgdx.graphics.screen.FlixelScreen; import me.stringdotjar.flixelgdx.Flixel; diff --git a/core/src/main/java/me/stringdotjar/funkin/menus/TitleScreen.java b/funkin/src/main/java/me/stringdotjar/funkin/menus/TitleScreen.java similarity index 94% rename from core/src/main/java/me/stringdotjar/funkin/menus/TitleScreen.java rename to funkin/src/main/java/me/stringdotjar/funkin/menus/TitleScreen.java index 5597818..99c78e2 100644 --- a/core/src/main/java/me/stringdotjar/funkin/menus/TitleScreen.java +++ b/funkin/src/main/java/me/stringdotjar/funkin/menus/TitleScreen.java @@ -51,16 +51,16 @@ public void render(float elapsed) { float speed = 500 * elapsed; if (Flixel.keyPressed(Input.Keys.W)) { - logo.setY(logo.getY() + speed); + logo.changeY(speed); } if (Flixel.keyPressed(Input.Keys.S)) { - logo.setY(logo.getY() - speed); + logo.changeY(-speed); } if (Flixel.keyPressed(Input.Keys.A)) { - logo.setX(logo.getX() - speed); + logo.changeX(-speed); } if (Flixel.keyPressed(Input.Keys.D)) { - logo.setX(logo.getX() + speed); + logo.changeX(speed); } if (Flixel.keyJustPressed(Input.Keys.SPACE)) { diff --git a/core/src/main/java/me/stringdotjar/funkin/util/FunkinConstants.java b/funkin/src/main/java/me/stringdotjar/funkin/util/FunkinConstants.java similarity index 100% rename from core/src/main/java/me/stringdotjar/funkin/util/FunkinConstants.java rename to funkin/src/main/java/me/stringdotjar/funkin/util/FunkinConstants.java diff --git a/lwjgl3/build.gradle b/lwjgl3/build.gradle index 3e513cd..9555ef9 100644 --- a/lwjgl3/build.gradle +++ b/lwjgl3/build.gradle @@ -32,7 +32,8 @@ dependencies { implementation "com.badlogicgames.gdx:gdx-backend-lwjgl3:$gdxVersion" implementation "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-desktop" implementation "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop" - implementation project(':core') + implementation project(':funkin') + implementation project(":flixelgdx") if(enableGraalNative == 'true') { implementation "io.github.berstanio:gdx-svmhelper-backend-lwjgl3:$graalHelperVersion" diff --git a/lwjgl3/src/main/java/me/stringdotjar/funkin/lwjgl3/Lwjgl3Launcher.java b/lwjgl3/src/main/java/me/stringdotjar/funkin/lwjgl3/Lwjgl3Launcher.java index 6a84013..7222c22 100644 --- a/lwjgl3/src/main/java/me/stringdotjar/funkin/lwjgl3/Lwjgl3Launcher.java +++ b/lwjgl3/src/main/java/me/stringdotjar/funkin/lwjgl3/Lwjgl3Launcher.java @@ -5,7 +5,7 @@ import com.badlogic.gdx.backends.lwjgl3.Lwjgl3WindowAdapter; import me.stringdotjar.flixelgdx.Flixel; import me.stringdotjar.funkin.FunkinGame; -import me.stringdotjar.funkin.init.InitScreen; +import me.stringdotjar.funkin.InitScreen; import me.stringdotjar.funkin.util.FunkinConstants; /** Launches the desktop (LWJGL3) application. */ @@ -26,12 +26,13 @@ private static void createApplication() { new InitScreen() ); Flixel.initialize(game); // This is VERY important to do before creating the application! - new Lwjgl3Application(game, createWindowConfiguration()); + var size = game.getWindowSize(); + new Lwjgl3Application(game, createWindowConfiguration(game.getTitle(), (int) size.x, (int) size.y)); } - private static Lwjgl3ApplicationConfiguration createWindowConfiguration() { + private static Lwjgl3ApplicationConfiguration createWindowConfiguration(String title, int width, int height) { Lwjgl3ApplicationConfiguration configuration = new Lwjgl3ApplicationConfiguration(); - configuration.setTitle("Friday Night Flixel': Java Edition"); + configuration.setTitle(title); // Vsync limits the frames per second to what your hardware can display, and helps eliminate // screen tearing. This setting doesn't always work on Linux, so the line after is a safeguard. configuration.useVsync(true); @@ -41,7 +42,7 @@ private static Lwjgl3ApplicationConfiguration createWindowConfiguration() { // If you remove the above line and set Vsync to false, you can get unlimited FPS, which can be // useful for testing performance, but can also be very stressful to some hardware. // You may also need to configure GPU drivers to fully disable Vsync; this can cause screen tearing. - configuration.setWindowedMode(FunkinConstants.WINDOW_WIDTH, FunkinConstants.WINDOW_HEIGHT); + configuration.setWindowedMode(width, height); // You can change these files; they are in lwjgl3/src/main/resources/ . // They can also be loaded from the root of assets/ . configuration.setWindowIcon("icon128.png", "icon64.png", "icon32.png", "icon16.png"); diff --git a/polyverse/build.gradle b/polyverse/build.gradle new file mode 100644 index 0000000..cc873dd --- /dev/null +++ b/polyverse/build.gradle @@ -0,0 +1,9 @@ +[compileJava, compileTestJava]*.options*.encoding = 'UTF-8' +eclipse.project.name = appName + '-polyverse' + +dependencies { + api project(":flixelgdx") + + implementation "org.apache.groovy:groovy:5.0.3" + implementation "org.jetbrains:annotations:26.0.2-1" +} diff --git a/core/src/main/java/me/stringdotjar/polyverse/Polyverse.java b/polyverse/src/main/java/me/stringdotjar/polyverse/Polyverse.java similarity index 100% rename from core/src/main/java/me/stringdotjar/polyverse/Polyverse.java rename to polyverse/src/main/java/me/stringdotjar/polyverse/Polyverse.java diff --git a/core/src/main/java/me/stringdotjar/polyverse/script/type/Script.java b/polyverse/src/main/java/me/stringdotjar/polyverse/script/type/Script.java similarity index 100% rename from core/src/main/java/me/stringdotjar/polyverse/script/type/Script.java rename to polyverse/src/main/java/me/stringdotjar/polyverse/script/type/Script.java diff --git a/polyverse/src/main/java/me/stringdotjar/polyverse/script/type/SystemScript.java b/polyverse/src/main/java/me/stringdotjar/polyverse/script/type/SystemScript.java new file mode 100644 index 0000000..1024e0e --- /dev/null +++ b/polyverse/src/main/java/me/stringdotjar/polyverse/script/type/SystemScript.java @@ -0,0 +1,17 @@ +package me.stringdotjar.polyverse.script.type; + +public abstract class SystemScript extends Script { + + public SystemScript(String id) { + super(id); + } + + /** Called when the game window gains focus. */ + public void onWindowFocused() {} + + /** Called when the game window loses focus. */ + public void onWindowUnfocused() {} + + /** Called when the game window is minimized. */ + public void onWindowMinimized(boolean iconified) {} +} diff --git a/settings.gradle b/settings.gradle index bf5968d..b3a82d8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,4 +5,4 @@ plugins { // A list of which subprojects to load as part of the same larger project. // You can remove Strings from the list and reload the Gradle project // if you want to temporarily disable a subproject. -include 'lwjgl3', 'android', 'core' +include 'funkin', 'flixelgdx', 'polyverse', 'lwjgl3', 'android' From 1e1042772674784f8824e8a1f153de5420b40dc7 Mon Sep 17 00:00:00 2001 From: String Date: Mon, 26 Jan 2026 21:08:50 -0600 Subject: [PATCH 16/17] Update FlixelObject.java --- .../stringdotjar/flixelgdx/graphics/sprite/FlixelObject.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/graphics/sprite/FlixelObject.java b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/graphics/sprite/FlixelObject.java index 11219fc..6ebd5c2 100644 --- a/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/graphics/sprite/FlixelObject.java +++ b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/graphics/sprite/FlixelObject.java @@ -3,10 +3,7 @@ import com.badlogic.gdx.graphics.g2d.Batch; import me.stringdotjar.flixelgdx.graphics.screen.FlixelScreen; -/** - * An interface which allows any class that implements it to be added to a {@link - * FlixelScreen}. - */ +/** An interface which allows any class that implements it to be added to a {@link FlixelScreen}. */ public interface FlixelObject { void update(float delta); void draw(Batch batch); From ceb0c30a2668e986e49d2ea2659d5b5e4fd26753 Mon Sep 17 00:00:00 2001 From: String Date: Mon, 26 Jan 2026 21:30:01 -0600 Subject: [PATCH 17/17] Update notice formatting in README.md Signed-off-by: String --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fb8eac7..cdb21a4 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Welcome to the official GitHub repository for the Java edition of Friday Night Funkin'! -> [!NOTICE] +> [!NOTE] > This is a fan-made project and is not affiliated with the original developers. You can play the original game [here](https://ninja-muffin24.itch.io/funkin). ## Please note that this unofficial version of the game is NOT completed! This has a long way to go, but with YOUR help, we can make this project come alive! <3