@@ -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 a3adf17..0a291d2 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 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.polyverse.script.type.SystemScript
+import me.stringdotjar.flixelgdx.Flixel
+import me.stringdotjar.flixelgdx.graphics.screen.FlixelScreen
+import me.stringdotjar.flixelgdx.backend.FlixelPaths
+import me.stringdotjar.flixelgdx.graphics.sprite.FlixelSprite
+import me.stringdotjar.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 Sprite test
+ private FlixelSprite test
@Override
void show() {
super.show()
- test = new Sprite(new Texture(Paths.image('NOTE_hold_assets')))
+ test = new FlixelSprite().loadGraphic(FlixelPaths.sharedImageAsset('NOTE_hold_assets'))
add(test)
bgColor = new Color(0, 1, 0, 1)
- Funkin.playMusic('songs/guns/Inst.ogg')
+ Flixel.playMusic('songs/darnell/Inst.ogg')
}
@Override
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/src/main/java/me/stringfromjava/funkin/Funkin.java b/core/src/main/java/me/stringfromjava/funkin/Funkin.java
deleted file mode 100644
index 8290389..0000000
--- a/core/src/main/java/me/stringfromjava/funkin/Funkin.java
+++ /dev/null
@@ -1,258 +0,0 @@
-package me.stringfromjava.funkin;
-
-import com.badlogic.gdx.Gdx;
-import com.badlogic.gdx.audio.Music;
-import com.badlogic.gdx.audio.Sound;
-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.util.Constants;
-
-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.
- *
- * This is where you want to do the main things, like switching screens, playing sounds/music,
- * etc.
- */
-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;
-
- /**
- * 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 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;
-
- /** Has the global manager been initialized yet? */
- private static boolean initialized = false;
-
- /**
- * Initializes the global manager.
- *
- * This can only be called once. If attempted to be executed again, the game will throw an
- * exception.
- *
- * @param gameInstance The instance of the game to use.
- */
- public static void initialize(FunkinGame gameInstance) {
- if (initialized) {
- throw new IllegalStateException("FNF:JE has already been initialized!");
- }
- game = gameInstance;
- initialized = true;
- }
-
- /**
- * 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.
- */
- public static void setScreen(FunkinScreen screen) {
- Signals.preScreenSwitch.dispatch(new Signals.ScreenSwitchSignalData(screen));
- if (!initialized) {
- throw new IllegalStateException("FNF:JE has not been initialized yet!");
- }
- if (screen == 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));
- }
-
- /**
- * Plays a sound. (Duh.)
- *
- * @param path The path to play the sound from.
- * @return The sound instance itself, as a {@link FunkinSound}.
- */
- 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;
- }
-
- /**
- * Plays new music. (Duh.)
- *
- * @param path The path to play the music from.
- * @return The music instance itself.
- */
- public static Music playMusic(String path) {
- return playMusic(path, 1.0f, true);
- }
-
- /**
- * Plays new music. (Duh.)
- *
- * @param path The path to play the music from.
- * @param volume The volume to play the music at.
- * @return The music instance itself.
- */
- public static Music playMusic(String path, float volume) {
- return playMusic(path, volume, true);
- }
-
- /**
- * Plays new music. (Duh.)
- *
- * @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.
- */
- 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 void info(Object message) {
- info("Funkin", message);
- }
-
- public static void info(String tag, Object message) {
- outputLog(tag, message, FunkinLogLevel.INFO);
- }
-
- public static void warn(Object message) {
- warn("Funkin", message);
- }
-
- public static void warn(String tag, Object message) {
- outputLog(tag, message, FunkinLogLevel.WARN);
- }
-
- public static void error(String message) {
- error("Funkin", message);
- }
-
- public static void error(String tag, Object message) {
- outputLog(tag, message, FunkinLogLevel.ERROR);
- }
-
- public static FunkinGame getGame() {
- return game;
- }
-
- // ======================================
- // 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);
- }
-
-
- 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();
- }
-
- /**
- * Contains all the global events that get dispatched when something happens in the game.
- *
- *
This includes anything from the screen being switched, the game updating every frame, and
- * just about everything you can think of.
- *
- *
IMPORTANT DETAIL!: Anything with the {@code pre} and {@code post} prefixes always mean the
- * 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 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 preSoundPlayed = new FunkinSignal<>();
- public static final FunkinSignal postSoundPlayed = new FunkinSignal<>();
-
- public record RenderSignalData(float delta) {}
-
- public record ScreenSwitchSignalData(FunkinScreen screen) {}
-
- public record SoundPlayedSignalData(FunkinSound sound) {}
-
- private Signals() {}
- }
-
- private Funkin() {}
-}
-
-enum FunkinLogLevel {
- INFO,
- WARN,
- ERROR
-}
diff --git a/core/src/main/java/me/stringfromjava/funkin/FunkinGame.java b/core/src/main/java/me/stringfromjava/funkin/FunkinGame.java
deleted file mode 100644
index 0cac92f..0000000
--- a/core/src/main/java/me/stringfromjava/funkin/FunkinGame.java
+++ /dev/null
@@ -1,123 +0,0 @@
-package me.stringfromjava.funkin;
-
-import com.badlogic.gdx.Game;
-import com.badlogic.gdx.Gdx;
-import com.badlogic.gdx.audio.Sound;
-import me.stringfromjava.funkin.backend.system.Paths;
-import me.stringfromjava.funkin.game.InitScreen;
-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 java.util.Set;
-
-import static me.stringfromjava.funkin.Funkin.Signals.RenderSignalData;
-
-/**
- * An enhanced version of libGDX's {@link Game} object.
- *
- * 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 {
-
- /** Is the game's window currently minimized? */
- protected boolean isMinimized = false;
-
- @Override
- public void create() {
- configurePolyverse();
-
- setScreen(new InitScreen());
- }
-
- @Override
- public void render() {
- super.render();
- float delta = Gdx.graphics.getDeltaTime();
-
- Funkin.Signals.preRender.dispatch(new RenderSignalData(delta));
-
- FunkinTween.globalManager.update(delta);
- Polyverse.forAllScripts(script -> script.onRender(delta));
-
- Funkin.Signals.postRender.dispatch(new RenderSignalData(delta));
- }
-
- /** Called when the user regains focus on the game's window. */
- public void onWindowFocused() {
- Funkin.masterVolume = 1.0f;
- Funkin.music.setVolume(1);
- 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.masterVolume = 0.008f;
- Funkin.music.setVolume(0.008f);
- 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.masterVolume = 0.0f;
- Funkin.music.setVolume(0);
- Funkin.info("Game window has been minimized.");
- }
-
- @Override
- public void dispose() {
- Funkin.warn("SHUTTING DOWN GAME AND DISPOSING ALL RESOURCES.");
-
- Funkin.Signals.preGameClose.dispatch();
-
- // Dispose of all sounds and the music (if there is any playing).
- Funkin.info("Disposing music...");
- 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);
-
- Funkin.Signals.postGameClose.dispatch();
- }
-
- public boolean isMinimized() {
- return isMinimized;
- }
-
- private void configurePolyverse() {
- // Register Polyverse script types.
- 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"));
- }
-}
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 77c199e..0000000
--- a/core/src/main/java/me/stringfromjava/funkin/audio/FunkinSound.java
+++ /dev/null
@@ -1,189 +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.system.Paths;
-
-/**
- * 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 Sound thisSound;
- 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) {
- thisSound = Gdx.audio.newSound(path);
- ID = thisSound.play();
- volume = 1.0f;
- pitch = 1.0f;
- pan = 0.0f;
- looping = false;
- isPaused = false;
- thisSound.stop();
- Funkin.soundPool.put(ID, thisSound);
- }
-
- @Override
- public long play() {
- return thisSound.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 Funkin.Signals.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));
- return thisSound.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 Funkin.Signals.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));
- return thisSound.loop(volume, pitch, pan);
- }
-
- @Override
- public void stop() {
- isPaused = false;
- thisSound.stop();
- }
-
- @Override
- public void pause() {
- isPaused = true;
- thisSound.pause();
- }
-
- @Override
- public void resume() {
- isPaused = false;
- thisSound.resume();
- }
-
- @Override
- public void dispose() {
- volume = 1.0f;
- pitch = 1.0f;
- pan = 0.0f;
- looping = false;
- isPaused = false;
- thisSound.dispose();
- Funkin.soundPool.remove(ID);
- }
-
- @Override
- public void stop(long soundId) {
- isPaused = false;
- thisSound.stop();
- }
-
- @Override
- public void pause(long soundId) {
- isPaused = true;
- thisSound.pause(soundId);
- }
-
- @Override
- public void resume(long soundId) {
- isPaused = false;
- thisSound.resume();
- }
-
- @Override
- public void setLooping(long soundId, boolean looping) {
- this.looping = looping;
- thisSound.setLooping(soundId, looping);
- }
-
- @Override
- public void setPitch(long soundId, float pitch) {
- this.pitch = pitch;
- thisSound.setPitch(soundId, pitch);
- }
-
- @Override
- public void setVolume(long soundId, float volume) {
- float v = volume * Funkin.masterVolume;
- this.volume = v;
- thisSound.setVolume(soundId, v);
- }
-
- public void setPan(long soundId, float pan) {
- setPan(soundId, pan, 1.0f);
- }
-
- @Override
- public void setPan(long soundId, float pan, float volume) {
- thisSound.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/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/backend/system/Paths.java b/core/src/main/java/me/stringfromjava/funkin/backend/system/Paths.java
deleted file mode 100644
index 6a1f874..0000000
--- a/core/src/main/java/me/stringfromjava/funkin/backend/system/Paths.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package me.stringfromjava.funkin.backend.system;
-
-import com.badlogic.gdx.Gdx;
-import com.badlogic.gdx.files.FileHandle;
-
-/** Utility class for simplifying asset paths. */
-public final class Paths {
-
- public static FileHandle asset(String path) {
- return Gdx.files.internal(path);
- }
-
- public static FileHandle shared(String path) {
- return asset(String.format("shared/%s", path));
- }
-
- public static FileHandle image(String path) {
- return shared(String.format("images/%s.png", path));
- }
-
- private Paths() {}
-}
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 27e5748..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.backend.display.FunkinScreen;
-import me.stringfromjava.funkin.game.menus.TitleScreen;
-
-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 7e2e032..0000000
--- a/core/src/main/java/me/stringfromjava/funkin/game/menus/TitleScreen.java
+++ /dev/null
@@ -1,83 +0,0 @@
-package me.stringfromjava.funkin.game.menus;
-
-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.tween.FunkinTween;
-import me.stringfromjava.funkin.tween.FunkinEase;
-import me.stringfromjava.funkin.tween.settings.FunkinTweenSettings;
-
-public class TitleScreen extends FunkinScreen {
-
- private Sprite logo;
- private FunkinSound tickleFight;
-
- private FunkinTween tween;
-
- @Override
- public void show() {
- super.show();
-
- logo = new Sprite(new Texture(Paths.image("stage_light")));
- add(logo);
-
- tickleFight = new FunkinSound("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);
- 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 delta) {
- super.render(delta);
-
- float speed = 200 * delta;
- if (Gdx.input.isKeyPressed(Input.Keys.W)) {
- logo.setY(logo.getY() + speed);
- }
- if (Gdx.input.isKeyPressed(Input.Keys.S)) {
- logo.setY(logo.getY() - speed);
- }
- if (Gdx.input.isKeyPressed(Input.Keys.A)) {
- logo.setX(logo.getX() - speed);
- }
- if (Gdx.input.isKeyPressed(Input.Keys.D)) {
- logo.setX(logo.getX() + speed);
- }
-
- if (Gdx.input.isKeyJustPressed(Input.Keys.T)) {
- tween.start();
- }
-
- if (Gdx.input.isKeyJustPressed(Input.Keys.R)) {
- tween.reset();
- }
-
- if (Gdx.input.isKeyJustPressed(Input.Keys.Y)) {
- if (tween.paused) {
- tween.resume();
- } else {
- tween.pause();
- }
- }
-
- if (Gdx.input.isKeyJustPressed(Input.Keys.Z)) {
- tickleFight.play(1.0f);
- }
- }
-}
diff --git a/core/src/main/java/me/stringfromjava/funkin/polyverse/script/type/SystemScript.java b/core/src/main/java/me/stringfromjava/funkin/polyverse/script/type/SystemScript.java
deleted file mode 100644
index de43ac7..0000000
--- a/core/src/main/java/me/stringfromjava/funkin/polyverse/script/type/SystemScript.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package me.stringfromjava.funkin.polyverse.script.type;
-
-public abstract class SystemScript extends Script {
-
- public SystemScript(String id) {
- super(id);
- }
-}
diff --git a/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTween.java b/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTween.java
deleted file mode 100644
index 6928584..0000000
--- a/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTween.java
+++ /dev/null
@@ -1,282 +0,0 @@
-package me.stringfromjava.funkin.tween;
-
-import me.stringfromjava.funkin.tween.settings.FunkinTweenSettings;
-import org.jetbrains.annotations.NotNull;
-
-import java.lang.reflect.Field;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * 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 {
-
- /** The global tween manager for the entire game. */
- public static FunkinTweenManager globalManager = new FunkinTweenManager();
-
- /** 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;
-
- /** The parent manager that {@code this} tween gets updated in. */
- protected FunkinTweenManager 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;
-
- /** How many seconds has elapsed since {@code this} tween started. */
- protected float secondsSinceStart = 0.0f;
-
- /** How many times {@code this} tween has updated. */
- protected int executions = 0;
-
- /** Is {@code this} tween currently paused? */
- public boolean paused = false;
-
- /** Is {@code this} tween currently active? */
- public boolean running = false;
-
- /** Is {@code this} tween finished tweening? */
- public boolean finished = false;
-
- /** Is {@code this} tween tweening backwards? */
- protected boolean backward = false;
-
- /** The initial values of the fields being tweened. */
- protected final Map initialValues = new HashMap<>();
-
- /**
- * @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) {
- this.object = object;
- this.tweenSettings = settings;
- this.updateCallback = updateCallback;
- }
-
- /**
- * 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 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 void update(float delta) {
- if (paused || finished) {
- return;
- }
-
- var ease = tweenSettings.getEase();
- var duration = tweenSettings.getDuration();
- var onStart = tweenSettings.getOnStart();
- var onUpdate = tweenSettings.getOnUpdate();
- var framerate = tweenSettings.getFramerate();
-
- float preTick = secondsSinceStart;
- secondsSinceStart += delta;
- float postTick = secondsSinceStart;
-
- float delay = (executions > 0) ? tweenSettings.getLoopDelay() : tweenSettings.getStartDelay();
- if (secondsSinceStart < delay) {
- return;
- }
-
- if (framerate > 0) {
- preTick = Math.round(preTick * framerate) / framerate;
- postTick = Math.round(postTick * framerate) / framerate;
- }
-
- scale = Math.max((postTick - delay), 0.0f) / duration;
- if (ease != null) {
- scale = ease.compute(scale);
- }
- if (backward) {
- scale = 1 - scale;
- }
- if (secondsSinceStart >= delay && !running) {
- running = true;
- if (onStart != null) {
- onStart.run(this);
- }
- }
-
- 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()) {
- FunkinTweenSettings.FunkinTweenGoal goal = tweenSettings.getGoal(field);
- float startValue = initialValues.get(field);
- float goalValue = goal.value();
- float newValue = startValue + (goalValue - startValue) * scale;
- newValues.put(field, newValue);
- }
- updateCallback.update(newValues);
-
- // Check if the tween has finished.
- 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);
- }
- }
- }
-
- /**
- * Starts {@code this} tween and resets every value to its initial state.
- *
- * @return {@code this} tween.
- */
- public FunkinTween start() {
- reset();
- running = true;
- finished = false;
-
- // Ensure that the fields provided actually exist on the object and are floating point values.
- var allFields = object.getClass().getDeclaredFields();
- var neededFields = tweenSettings.getGoalFields();
- for (Field field : allFields) {
- 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) {
- e.printStackTrace();
- }
- }
- return this;
- }
-
- /**
- * Resumes {@code this} tween if it was previously paused.
- *
- * @return {@code this} tween.
- */
- public FunkinTween resume() {
- paused = false;
- return this;
- }
-
- /**
- * Pauses {@code this} tween, stopping it from updating until resumed.
- *
- * @return {@code this} tween.
- */
- public FunkinTween pause() {
- paused = true;
- running = false;
- return this;
- }
-
- /**
- * Stops {@code this} tween. Note that this does not remove the tween from the active tweens in
- * its manager.
- *
- * @return {@code this} tween.
- */
- public FunkinTween stop() {
- running = false;
- finished = true;
- return this;
- }
-
- /**
- * Cancels {@code this} tween and immediately removes it from the active tweens in its manager.
- *
- * @return {@code this} tween.
- */
- public FunkinTween cancel() {
- reset();
- manager.activeTweens.remove(this);
- return this;
- }
-
- /**
- * Resets {@code this} tween back to its initial state without removing it from its manager.
- *
- * @return {@code this} tween.
- */
- public FunkinTween reset() {
- paused = false;
- backward = false;
- running = false;
- finished = true;
- scale = 0.0f;
- secondsSinceStart = 0.0f;
- executions = 0;
- initialValues.clear();
- return this;
- }
-
- public FunkinTweenSettings getTweenSettings() {
- return tweenSettings;
- }
-
- public FunkinTween setTweenSettings(@NotNull FunkinTweenSettings tweenSettings) {
- this.tweenSettings = tweenSettings;
- return this;
- }
-
- public FunkinTween setManager(FunkinTweenManager manager) {
- if (manager != null) {
- this.manager = manager;
- this.manager.activeTweens.add(this);
- }
- 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/stringfromjava/funkin/tween/FunkinTweenManager.java b/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTweenManager.java
deleted file mode 100644
index 709be1d..0000000
--- a/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTweenManager.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package me.stringfromjava.funkin.tween;
-
-import java.util.concurrent.CopyOnWriteArrayList;
-
-/** Core manager class for handling all {@link FunkinTween}s that are currently active. */
-public final class FunkinTweenManager {
-
- /** A list where all current active tweens are stored. */
- public final CopyOnWriteArrayList activeTweens = new CopyOnWriteArrayList<>();
-
- public void update(float delta) {
- for (FunkinTween tween : activeTweens) {
- if (tween == null) {
- continue;
- }
- tween.update(delta);
- }
- }
-}
diff --git a/core/build.gradle b/flixelgdx/build.gradle
similarity index 68%
rename from core/build.gradle
rename to flixelgdx/build.gradle
index 9b3110a..474d702 100644
--- a/core/build.gradle
+++ b/flixelgdx/build.gradle
@@ -1,19 +1,17 @@
[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"
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"
- 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/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/Flixel.java b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/Flixel.java
new file mode 100644
index 0000000..615053f
--- /dev/null
+++ b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/Flixel.java
@@ -0,0 +1,487 @@
+package me.stringdotjar.flixelgdx;
+
+import com.badlogic.gdx.Gdx;
+import com.badlogic.gdx.assets.AssetManager;
+import com.badlogic.gdx.files.FileHandle;
+import com.badlogic.gdx.scenes.scene2d.Stage;
+import games.rednblack.miniaudio.MAGroup;
+import games.rednblack.miniaudio.MASound;
+import games.rednblack.miniaudio.MiniAudio;
+import games.rednblack.miniaudio.loader.MASoundLoader;
+import me.stringdotjar.flixelgdx.graphics.screen.FlixelScreen;
+import me.stringdotjar.flixelgdx.util.FlixelConstants;
+import me.stringdotjar.flixelgdx.signal.FlixelSignal;
+import me.stringdotjar.flixelgdx.signal.FlixelSignalData.MusicPlayedSignalData;
+import me.stringdotjar.flixelgdx.signal.FlixelSignalData.RenderSignalData;
+import me.stringdotjar.flixelgdx.signal.FlixelSignalData.ScreenSwitchSignalData;
+import me.stringdotjar.flixelgdx.signal.FlixelSignalData.SoundPlayedSignalData;
+import org.jetbrains.annotations.NotNull;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * Global manager and utility class for Flixel.
+ *
+ * This is where you want to do the main things, like switching screens, playing sounds/music, etc.
+ */
+public final class Flixel {
+
+ /** The current {@code FlixelScreen} being displayed. */
+ private static FlixelScreen 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 all sound effects, including the current music. */
+ private static MAGroup soundsGroup;
+
+ /** The sound for playing music throughout the game. */
+ 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. */
+ private static FlixelGame game;
+
+ /** Has the global manager been initialized yet? */
+ private static boolean initialized = false;
+
+ /**
+ * Initializes the global manager.
+ *
+ *
This can only be called once. If attempted to be executed again, the game will throw an
+ * exception.
+ *
+ * @param gameInstance The instance of the game to use.
+ */
+ public static void initialize(FlixelGame gameInstance) {
+ if (initialized) {
+ 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;
+ }
+
+ /**
+ * Sets the current screen to the provided screen.
+ *
+ * @param newScreen The new {@code FlixelScreen} to set as the current screen.
+ */
+ public static void setScreen(FlixelScreen newScreen) {
+ Signals.preScreenSwitch.dispatch(new ScreenSwitchSignalData(newScreen));
+ if (!initialized) {
+ throw new IllegalStateException("FNF:JE has not been initialized yet!");
+ }
+ if (newScreen == null) {
+ throw new IllegalArgumentException("Screen cannot be null!");
+ }
+ if (Flixel.screen != null) {
+ Flixel.screen.hide();
+ Flixel.screen.dispose();
+ }
+ Flixel.screen = newScreen;
+ Flixel.screen.show();
+ Signals.postScreenSwitch.dispatch(new ScreenSwitchSignalData(newScreen));
+ }
+
+ /**
+ * 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 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
+ * 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) {
+ return playSound(path, 1, false, null, false);
+ }
+
+ /**
+ * 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 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
+ * 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 MASound playSound(String path, float volume) {
+ return playSound(path, volume, false, null, false);
+ }
+
+ /**
+ * 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 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
+ * 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 MASound playSound(String path, float volume, boolean looping) {
+ return playSound(path, volume, looping, null, false);
+ }
+
+ /**
+ * 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 FlixelPaths class provided by Flixel'.
+ * // If null is passed down for the group, then the default sound group will be used.
+ * 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
+ * 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 MASound playSound(String path, float volume, boolean looping, MAGroup group) {
+ return playSound(path, volume, looping, group, false);
+ }
+
+ /**
+ * 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 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.
+ * 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
+ * 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);
+ 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 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
+ * 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 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
+ * 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 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
+ * 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 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.
+ * 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
+ * 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.
+ *
+ * (This is just a helper method for creating a faster version of {@code
+ * Flixel.getAudioEngine().setMasterVolume(float)}).
+ *
+ * @param volume The new master volume to set.
+ */
+ public static void setMasterVolume(float volume) {
+ engine.setMasterVolume(!(volume > 1.0) ? volume : 1.0f);
+ masterVolume = volume;
+ }
+
+ 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(FlixelConstants.System.LOG_TAG, message);
+ }
+
+ public static void info(String tag, Object message) {
+ outputLog(tag, message, FlixelLogLevel.INFO);
+ }
+
+ public static void warn(Object message) {
+ warn(FlixelConstants.System.LOG_TAG, message);
+ }
+
+ public static void warn(String tag, Object message) {
+ outputLog(tag, message, FlixelLogLevel.WARN);
+ }
+
+ public static void error(String message) {
+ error(FlixelConstants.System.LOG_TAG, message, null);
+ }
+
+ public static void error(String tag, Object message) {
+ error(tag, message, null);
+ }
+
+ public static void error(String tag, Object message, Throwable throwable) {
+ String msg = (throwable != null) ? (message + " | Exception: " + throwable) : message.toString();
+ outputLog(tag, msg, FlixelLogLevel.ERROR);
+ }
+
+ public static FlixelGame getGame() {
+ return game;
+ }
+
+ public static Stage getStage() {
+ return game.stage;
+ }
+
+ public static FlixelScreen getScreen() {
+ return screen;
+ }
+
+ public static MASound getMusic() {
+ return music;
+ }
+
+ public static MiniAudio getAudioEngine() {
+ return engine;
+ }
+
+ public static float getMasterVolume() {
+ return masterVolume;
+ }
+
+ public static AssetManager getAssetManager() {
+ return assetManager;
+ }
+
+ 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.
+ *
+ *
This includes anything from the screen being switched, the game updating every frame, and
+ * just about everything you can think of.
+ *
+ *
IMPORTANT DETAIL!: Anything with the {@code pre} and {@code post} prefixes always mean the
+ * 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 final class Signals {
+
+ 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() {}
+ }
+
+ // ======================================
+ // UTILITY FUNCTIONS, IGNORE BELOW
+ // ======================================
+
+ private static void outputLog(String tag, Object message, FlixelLogLevel 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 -> FlixelConstants.AsciiCodes.WHITE;
+ case WARN -> FlixelConstants.AsciiCodes.YELLOW;
+ case ERROR -> FlixelConstants.AsciiCodes.RED;
+ };
+
+ boolean underline = (level == FlixelLogLevel.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 formattedMessage = colorText((message != null) ? message.toString() : null, 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(FlixelConstants.AsciiCodes.BOLD);
+ }
+ if (italic) {
+ sb.append(FlixelConstants.AsciiCodes.ITALIC);
+ }
+ if (underline) {
+ sb.append(FlixelConstants.AsciiCodes.UNDERLINE);
+ }
+ sb.append(color);
+ sb.append(text);
+ sb.append(FlixelConstants.AsciiCodes.RESET);
+ return sb.toString();
+ }
+
+ private Flixel() {}
+}
+
+enum FlixelLogLevel {
+ INFO,
+ WARN,
+ ERROR
+}
diff --git a/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/FlixelGame.java b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/FlixelGame.java
new file mode 100644
index 0000000..36b8078
--- /dev/null
+++ b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/FlixelGame.java
@@ -0,0 +1,274 @@
+package me.stringdotjar.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.stringdotjar.flixelgdx.graphics.screen.FlixelScreen;
+import me.stringdotjar.flixelgdx.graphics.sprite.FlixelObject;
+import me.stringdotjar.flixelgdx.tween.FlixelTween;
+import me.stringdotjar.flixelgdx.util.FlixelConstants;
+import me.stringdotjar.flixelgdx.util.FlixelRuntimeUtil;
+
+import static me.stringdotjar.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 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;
+
+ /** 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.windowSize = 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(windowSize.x, windowSize.y);
+ viewport.apply();
+
+ camera = new OrthographicCamera();
+ camera.setToOrtho(false, windowSize.x, windowSize.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 == null) {
+ continue;
+ }
+ object.update(delta);
+ object.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() {
+ isFocused = true;
+ 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;
+ }
+ isFocused = false;
+ 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;
+ }
+ isFocused = false;
+ 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((int) windowSize.x, (int) windowSize.y);
+ 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...");
+
+ Flixel.Signals.postGameClose.dispatch();
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public Vector2 getWindowSize() {
+ return windowSize;
+ }
+
+ public boolean isFocused() {
+ return isFocused;
+ }
+
+ 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/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/backend/FlixelPaths.java b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/backend/FlixelPaths.java
new file mode 100644
index 0000000..f349622
--- /dev/null
+++ b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/backend/FlixelPaths.java
@@ -0,0 +1,30 @@
+package me.stringdotjar.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 FlixelPaths {
+
+ public static FileHandle asset(String path) {
+ return Gdx.files.internal(path);
+ }
+
+ public static FileHandle shared(String path) {
+ return asset(String.format("shared/%s", path));
+ }
+
+ public static FileHandle xmlAsset(String path) {
+ return asset(String.format("%s.xml", 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 FlixelPaths() {}
+}
diff --git a/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/graphics/screen/FlixelScreen.java b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/graphics/screen/FlixelScreen.java
new file mode 100644
index 0000000..8f00693
--- /dev/null
+++ b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/graphics/screen/FlixelScreen.java
@@ -0,0 +1,56 @@
+package me.stringdotjar.flixelgdx.graphics.screen;
+
+import com.badlogic.gdx.Screen;
+import com.badlogic.gdx.graphics.Color;
+import com.badlogic.gdx.utils.SnapshotArray;
+import me.stringdotjar.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 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<>(FlixelObject.class);
+
+ @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(FlixelObject object) {
+ if (object != null) {
+ members.add(object);
+ }
+ }
+
+ public Color getBgColor() {
+ return (bgColor != null) ? bgColor : Color.BLACK;
+ }
+}
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
new file mode 100644
index 0000000..6ebd5c2
--- /dev/null
+++ b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/graphics/sprite/FlixelObject.java
@@ -0,0 +1,10 @@
+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 {
+ void update(float delta);
+ void draw(Batch batch);
+}
diff --git a/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/graphics/sprite/FlixelSprite.java b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/graphics/sprite/FlixelSprite.java
new file mode 100644
index 0000000..ca82924
--- /dev/null
+++ b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/graphics/sprite/FlixelSprite.java
@@ -0,0 +1,371 @@
+package me.stringdotjar.flixelgdx.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.SpriteBatch;
+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;
+
+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 FlixelSprite extends Sprite implements FlixelObject, 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;
+
+ /** 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 FlixelSprite() {
+ animations = new HashMap<>();
+ }
+
+ /**
+ * Updates {@code this} sprite.
+ *
+ * @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);
+ 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 FlixelSprite 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 FlixelSprite 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 FlixelSprite loadGraphic(FileHandle path, int frameWidth, int frameHeight) {
+ return loadGraphic(new Texture(path), frameWidth, 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]);
+ 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 FlixelSprite 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 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<>();
+
+ 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);
+ }
+
+ 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());
+ 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) {
+ 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;
+ }
+ }
+
+ @Override
+ public void draw(Batch batch) {
+ 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);
+
+ batch.draw(
+ currentFrame,
+ drawX,
+ drawY,
+ originX - currentFrame.offsetX,
+ originY - (currentFrame.originalHeight - currentFrame.getRegionHeight() - currentFrame.offsetY),
+ currentFrame.getRegionWidth(),
+ currentFrame.getRegionHeight(),
+ getScaleX(),
+ getScaleY(),
+ getRotation());
+ }
+
+ public boolean isAnimationFinished() {
+ Animation anim = animations.get(currentAnim);
+ if (anim == null) return true;
+ 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 void changeX(float x) {
+ setX(getX() + x);
+ }
+
+ public void changeY(float y) {
+ setY(getY() + y);
+ }
+
+ 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/backend/display/text/DisplayText.java b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/graphics/text/FlixelText.java
similarity index 77%
rename from core/src/main/java/me/stringfromjava/funkin/backend/display/text/DisplayText.java
rename to flixelgdx/src/main/java/me/stringdotjar/flixelgdx/graphics/text/FlixelText.java
index f1b628c..cd72e43 100644
--- a/core/src/main/java/me/stringfromjava/funkin/backend/display/text/DisplayText.java
+++ b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/graphics/text/FlixelText.java
@@ -1,7 +1,7 @@
-package me.stringfromjava.funkin.backend.display.text;
+package me.stringdotjar.flixelgdx.graphics.text;
/** A display object for creating a piece of text to show on the screen. */
-public class DisplayText {
+public class FlixelText {
/** 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 FlixelText(String text) {
this.text = text;
x = 0;
y = 0;
diff --git a/core/src/main/java/me/stringfromjava/funkin/backend/system/FunkinSignal.java b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/signal/FlixelSignal.java
similarity index 85%
rename from core/src/main/java/me/stringfromjava/funkin/backend/system/FunkinSignal.java
rename to flixelgdx/src/main/java/me/stringdotjar/flixelgdx/signal/FlixelSignal.java
index 59efa42..3fe63b8 100644
--- a/core/src/main/java/me/stringfromjava/funkin/backend/system/FunkinSignal.java
+++ b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/signal/FlixelSignal.java
@@ -1,4 +1,4 @@
-package me.stringfromjava.funkin.backend.system;
+package me.stringdotjar.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<>();
}
@@ -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/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/signal/FlixelSignalData.java b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/signal/FlixelSignalData.java
new file mode 100644
index 0000000..84f010b
--- /dev/null
+++ b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/signal/FlixelSignalData.java
@@ -0,0 +1,22 @@
+package me.stringdotjar.flixelgdx.signal;
+
+import games.rednblack.miniaudio.MASound;
+import me.stringdotjar.flixelgdx.graphics.screen.FlixelScreen;
+import me.stringdotjar.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/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/tween/FlixelEase.java
similarity index 78%
rename from core/src/main/java/me/stringfromjava/funkin/tween/FunkinEase.java
rename to flixelgdx/src/main/java/me/stringdotjar/flixelgdx/tween/FlixelEase.java
index f6cfc5c..7bed09f 100644
--- a/core/src/main/java/me/stringfromjava/funkin/tween/FunkinEase.java
+++ b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/tween/FlixelEase.java
@@ -1,7 +1,7 @@
-package me.stringfromjava.funkin.tween;
+package me.stringdotjar.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;
@@ -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
@@ -205,16 +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/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/tween/FlixelTween.java b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/tween/FlixelTween.java
new file mode 100644
index 0000000..36e0430
--- /dev/null
+++ b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/tween/FlixelTween.java
@@ -0,0 +1,255 @@
+package me.stringdotjar.flixelgdx.tween;
+
+import com.badlogic.gdx.utils.Pool;
+import me.stringdotjar.flixelgdx.tween.settings.FlixelTweenSettings;
+import me.stringdotjar.flixelgdx.tween.type.FlixelNumTween;
+import me.stringdotjar.flixelgdx.tween.type.FlixelVarTween;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * 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.
+ *
+ *
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 settings used for how the tween is handled and calculated (aka how it looks and animates). */
+ protected FlixelTweenSettings tweenSettings;
+
+ /** The parent manager that {@code this} tween gets updated in. */
+ 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;
+
+ /** How many seconds has elapsed since {@code this} tween started. */
+ protected float secondsSinceStart = 0.0f;
+
+ /** How many times {@code this} tween has updated. */
+ protected int executions = 0;
+
+ /** Is {@code this} tween currently paused? */
+ public boolean paused = false;
+
+ /** Is {@code this} tween currently active? */
+ public boolean running = false;
+
+ /** Is {@code this} tween finished tweening? */
+ public boolean finished = false;
+
+ /** Is {@code this} tween tweening backwards? */
+ protected boolean backward = false;
+
+ /** Default constructor for pooling purposes. */
+ protected FlixelTween() {}
+
+ protected FlixelTween(FlixelTweenSettings tweenSettings) {
+ this.tweenSettings = tweenSettings;
+ }
+
+ /**
+ * 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 updateCallback Callback function for updating the objects values when the tween updates.
+ * @return The newly created and started tween.
+ */
+ 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();
+ }
+
+ /**
+ * Starts {@code this} tween and resets every value to its initial state.
+ *
+ * @return {@code this} tween.
+ */
+ 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 || !running) {
+ return;
+ }
+ if (tweenSettings == 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;
+ secondsSinceStart += delta;
+ float postTick = secondsSinceStart;
+
+ float delay = (executions > 0) ? tweenSettings.getLoopDelay() : tweenSettings.getStartDelay();
+ if (secondsSinceStart < delay) {
+ return;
+ }
+
+ if (framerate > 0) {
+ preTick = Math.round(preTick * framerate) / framerate;
+ postTick = Math.round(postTick * framerate) / framerate;
+ }
+
+ scale = Math.max((postTick - delay), 0.0f) / duration;
+ if (ease != null) {
+ scale = ease.compute(scale);
+ }
+ if (backward) {
+ scale = 1 - scale;
+ }
+ if (secondsSinceStart >= delay && !running) {
+ running = true;
+ if (onStart != null) {
+ onStart.run(this);
+ }
+ }
+
+ // 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);
+ }
+ }
+ }
+
+ /**
+ * Resumes {@code this} tween if it was previously paused.
+ *
+ * @return {@code this} tween.
+ */
+ public FlixelTween resume() {
+ paused = false;
+ return this;
+ }
+
+ /**
+ * Pauses {@code this} tween, stopping it from updating until resumed.
+ *
+ * @return {@code this} tween.
+ */
+ public FlixelTween pause() {
+ paused = true;
+ running = false;
+ return this;
+ }
+
+ /**
+ * Stops {@code this} tween. Note that this does not remove the tween from the active tweens in
+ * its manager.
+ *
+ * @return {@code this} tween.
+ */
+ public FlixelTween stop() {
+ running = false;
+ return this;
+ }
+
+ /**
+ * Cancels {@code this} tween and automatically defaults its values, removing it from its manager by default.
+ *
+ * @return {@code this} tween.
+ */
+ public FlixelTween cancel() {
+ return cancel(true);
+ }
+
+ /**
+ * 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 FlixelTween cancel(boolean remove) {
+ reset();
+ if (remove) {
+ manager.getTweenPool().free(this);
+ }
+ return this;
+ }
+
+ @Override
+ public void reset() {
+ resetBasic();
+ manager = null;
+ }
+
+ /**
+ * 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;
+ paused = false;
+ running = false;
+ finished = false;
+ backward = false;
+ }
+
+ public FlixelTweenSettings getTweenSettings() {
+ return tweenSettings;
+ }
+
+ public FlixelTween setTweenSettings(@NotNull FlixelTweenSettings tweenSettings) {
+ this.tweenSettings = tweenSettings;
+ return this;
+ }
+
+ public static FlixelTweenManager getGlobalManager() {
+ return globalManager;
+ }
+
+ public FlixelTweenManager getManager() {
+ return manager;
+ }
+
+ public FlixelTween setManager(FlixelTweenManager 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.getActiveTweens().add(this);
+ }
+ return this;
+ }
+}
diff --git a/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/tween/FlixelTweenManager.java b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/tween/FlixelTweenManager.java
new file mode 100644
index 0000000..813ca42
--- /dev/null
+++ b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/tween/FlixelTweenManager.java
@@ -0,0 +1,60 @@
+package me.stringdotjar.flixelgdx.tween;
+
+import com.badlogic.gdx.utils.Array;
+import com.badlogic.gdx.utils.Pool;
+
+/** 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<>(FlixelTween.class);
+
+ /** A pool where all unused tweens are stored to preserve memory. */
+ protected final Pool tweenPool = new Pool<>() {
+ @Override
+ protected FlixelTween newObject() {
+ return new FlixelTween();
+ }
+ };
+
+ /**
+ * 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 (int i = activeTweens.size - 1; i >= 0; i--) {
+ var 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);
+ }
+ case PERSIST -> {} // Do nothing, let it be.
+ }
+ }
+ }
+ }
+
+ public Array getActiveTweens() {
+ return activeTweens;
+ }
+
+ public Pool getTweenPool() {
+ return tweenPool;
+ }
+}
diff --git a/core/src/main/java/me/stringfromjava/funkin/tween/settings/FunkinTweenSettings.java b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/tween/settings/FlixelTweenSettings.java
similarity index 58%
rename from core/src/main/java/me/stringfromjava/funkin/tween/settings/FunkinTweenSettings.java
rename to flixelgdx/src/main/java/me/stringdotjar/flixelgdx/tween/settings/FlixelTweenSettings.java
index 2f2ccaf..1f202bb 100644
--- a/core/src/main/java/me/stringfromjava/funkin/tween/settings/FunkinTweenSettings.java
+++ b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/tween/settings/FlixelTweenSettings.java
@@ -1,6 +1,7 @@
-package me.stringfromjava.funkin.tween.settings;
+package me.stringdotjar.flixelgdx.tween.settings;
-import me.stringfromjava.funkin.tween.FunkinEase;
+import me.stringdotjar.flixelgdx.tween.FlixelTween;
+import me.stringdotjar.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;
@@ -58,11 +59,13 @@ public FunkinTweenSettings(
/**
* 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.
*/
- public FunkinTweenSettings addGoal(String field, float value) {
+ public FlixelTweenSettings addGoal(String field, float value) {
goals.add(new FunkinTweenGoal(field, value));
return this;
}
@@ -73,7 +76,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 +85,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 +138,7 @@ public float getFramerate() {
return framerate;
}
- public FunkinTweenSettings setEase(FunkinEase.FunkinEaseFunction ease) {
+ public FlixelTweenSettings setEase(FlixelEase.FunkinEaseFunction ease) {
this.ease = ease;
return this;
}
@@ -144,21 +147,41 @@ 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 FlixelTweenSettings setType(@NotNull FlixelTweenType type) {
+ this.type = 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/stringfromjava/funkin/tween/settings/FunkinTweenType.java b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/tween/settings/FlixelTweenType.java
similarity index 84%
rename from core/src/main/java/me/stringfromjava/funkin/tween/settings/FunkinTweenType.java
rename to flixelgdx/src/main/java/me/stringdotjar/flixelgdx/tween/settings/FlixelTweenType.java
index e5bf801..e2a2012 100644
--- a/core/src/main/java/me/stringfromjava/funkin/tween/settings/FunkinTweenType.java
+++ b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/tween/settings/FlixelTweenType.java
@@ -1,7 +1,7 @@
-package me.stringfromjava.funkin.tween.settings;
+package me.stringdotjar.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/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/stringfromjava/funkin/util/Constants.java b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/util/FlixelConstants.java
similarity index 56%
rename from core/src/main/java/me/stringfromjava/funkin/util/Constants.java
rename to flixelgdx/src/main/java/me/stringdotjar/flixelgdx/util/FlixelConstants.java
index 7eade3f..149dda7 100644
--- a/core/src/main/java/me/stringfromjava/funkin/util/Constants.java
+++ b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/util/FlixelConstants.java
@@ -1,31 +1,31 @@
-package me.stringfromjava.funkin.util;
+package me.stringdotjar.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 {
+public final class FlixelConstants {
- /** Static subclass for holding values for components such as the window's width and height. */
- public static final class Display {
+ /**
+ * Stores constants for things related to the backend of Flixel. This includes components like
+ * logging, folder paths, etc.
+ */
+ public static final class System {
/**
- * How wide the window's viewport is in pixels. This also affects how wide the window is when
- * the game first starts up.
+ * The default and globally recognized default tag for logs that are outputted inside the
+ * console.
*/
- public static final int WINDOW_WIDTH = 1280;
+ public static final String LOG_TAG = "Flixel";
- /**
- * 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() {}
+ private System() {}
}
- public static final class Colors {
+ /**
+ * 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,8 +40,6 @@ 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/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/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/util/FlixelReflectUtil.java b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/util/FlixelReflectUtil.java
new file mode 100644
index 0000000..2c0ec5c
--- /dev/null
+++ b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/util/FlixelReflectUtil.java
@@ -0,0 +1,84 @@
+package me.stringdotjar.flixelgdx.util;
+
+import me.stringdotjar.flixelgdx.Flixel;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+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
+ * reflection.
+ */
+public class FlixelReflectUtil {
+
+ /**
+ * 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}.
+ *
+ * @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;
+ }
+
+ /**
+ * 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.
+ *
+ * @param classPath The package definition of the class to check if final. An example could be
+ * {@code "me.stringdotjar.flixelgdx.Flixel"}.
+ * @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.
+ Flixel.error(FlixelConstants.System.LOG_TAG, "Failed to check if a class was final.", e);
+ return false;
+ }
+ }
+}
diff --git a/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/util/FlixelRuntimeUtil.java b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/util/FlixelRuntimeUtil.java
new file mode 100644
index 0000000..4d8a48c
--- /dev/null
+++ b/flixelgdx/src/main/java/me/stringdotjar/flixelgdx/util/FlixelRuntimeUtil.java
@@ -0,0 +1,56 @@
+package me.stringdotjar.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 FlixelRuntimeUtil {
+
+ /**
+ * 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).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 FlixelRuntimeUtil() {}
+}
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/funkin/src/main/java/me/stringdotjar/funkin/FunkinGame.java b/funkin/src/main/java/me/stringdotjar/funkin/FunkinGame.java
new file mode 100644
index 0000000..cfdce50
--- /dev/null
+++ b/funkin/src/main/java/me/stringdotjar/funkin/FunkinGame.java
@@ -0,0 +1,71 @@
+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.
+ */
+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);
+ }
+
+ @Override
+ public void create() {
+ super.create();
+ configurePolyverse(); // Scripting and modding support.
+ }
+
+ @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!
+ Polyverse.registerScriptType(SystemScript.class);
+
+ Polyverse.registerScript(FlixelPaths.asset("test.groovy"));
+ Polyverse.registerScript(FlixelPaths.asset("another_test.groovy"));
+ }
+}
diff --git a/funkin/src/main/java/me/stringdotjar/funkin/InitScreen.java b/funkin/src/main/java/me/stringdotjar/funkin/InitScreen.java
new file mode 100644
index 0000000..e90d619
--- /dev/null
+++ b/funkin/src/main/java/me/stringdotjar/funkin/InitScreen.java
@@ -0,0 +1,14 @@
+package me.stringdotjar.funkin;
+
+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/funkin/src/main/java/me/stringdotjar/funkin/menus/TitleScreen.java b/funkin/src/main/java/me/stringdotjar/funkin/menus/TitleScreen.java
new file mode 100644
index 0000000..99c78e2
--- /dev/null
+++ b/funkin/src/main/java/me/stringdotjar/funkin/menus/TitleScreen.java
@@ -0,0 +1,90 @@
+package me.stringdotjar.funkin.menus;
+
+import com.badlogic.gdx.Input;
+import games.rednblack.miniaudio.MASound;
+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 {
+
+ 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.changeY(speed);
+ }
+ if (Flixel.keyPressed(Input.Keys.S)) {
+ logo.changeY(-speed);
+ }
+ if (Flixel.keyPressed(Input.Keys.A)) {
+ logo.changeX(-speed);
+ }
+ if (Flixel.keyPressed(Input.Keys.D)) {
+ logo.changeX(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/funkin/src/main/java/me/stringdotjar/funkin/util/FunkinConstants.java b/funkin/src/main/java/me/stringdotjar/funkin/util/FunkinConstants.java
new file mode 100644
index 0000000..8fd5196
--- /dev/null
+++ b/funkin/src/main/java/me/stringdotjar/funkin/util/FunkinConstants.java
@@ -0,0 +1,26 @@
+package me.stringdotjar.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/gradle.properties b/gradle.properties
index 5f61b4a..d3f3f53 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -14,10 +14,10 @@ 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
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..2038bf9 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"
}
}
@@ -14,29 +14,27 @@ plugins {
}
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'
+sourceSets.main.resources.srcDirs += [rootProject.file('assets').path]
+mainClassName = 'me.stringdotjar.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 {
+ 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"
- implementation project(':core')
+ implementation project(':funkin')
+ implementation project(":flixelgdx")
- if(enableGraalNative == 'true') {
+ if (enableGraalNative == 'true') {
implementation "io.github.berstanio:gdx-svmhelper-backend-lwjgl3:$graalHelperVersion"
-
- }
+ }
}
def os = System.properties['os.name'].toLowerCase()
@@ -113,47 +111,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.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.
tasks.register('dist') {
dependsOn 'jar'
@@ -176,6 +133,6 @@ distributions {
startScripts.dependsOn(':lwjgl3:jar')
startScripts.classpath = project.tasks.jar.outputs.files
-if(enableGraalNative == 'true') {
+if (enableGraalNative == 'true') {
apply from: file("nativeimage.gradle")
}
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 68%
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 a5968d9..7222c22 100644
--- a/lwjgl3/src/main/java/me/stringfromjava/funkin/lwjgl3/Lwjgl3Launcher.java
+++ b/lwjgl3/src/main/java/me/stringdotjar/funkin/lwjgl3/Lwjgl3Launcher.java
@@ -1,11 +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.funkin.Funkin;
-import me.stringfromjava.funkin.FunkinGame;
-import me.stringfromjava.funkin.util.Constants;
+import me.stringdotjar.flixelgdx.Flixel;
+import me.stringdotjar.funkin.FunkinGame;
+import me.stringdotjar.funkin.InitScreen;
+import me.stringdotjar.funkin.util.FunkinConstants;
/** Launches the desktop (LWJGL3) application. */
public class Lwjgl3Launcher {
@@ -18,26 +19,30 @@ 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!
- new Lwjgl3Application(game, createWindowConfiguration());
+ 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!
+ 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 Funkin': 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);
// 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(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");
@@ -49,19 +54,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);
}
});
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;
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/stringfromjava/funkin/polyverse/Polyverse.java b/polyverse/src/main/java/me/stringdotjar/polyverse/Polyverse.java
similarity index 72%
rename from core/src/main/java/me/stringfromjava/funkin/polyverse/Polyverse.java
rename to polyverse/src/main/java/me/stringdotjar/polyverse/Polyverse.java
index e9759fe..a8383af 100644
--- a/core/src/main/java/me/stringfromjava/funkin/polyverse/Polyverse.java
+++ b/polyverse/src/main/java/me/stringdotjar/polyverse/Polyverse.java
@@ -1,9 +1,9 @@
-package me.stringfromjava.funkin.polyverse;
+package me.stringdotjar.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.stringdotjar.flixelgdx.Flixel;
+import me.stringdotjar.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. */
@@ -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.
*/
@@ -80,19 +81,19 @@ public static void registerScript(FileHandle handle) {
var typeScripts = scripts.get(mostSpecificType);
if (!typeScripts.contains(script)) {
typeScripts.add(script);
- Funkin.info(
- "Polyverse",
- "Registered Polyverse script \""
- + script.getClass().getSimpleName()
- + "\" of script type \""
- + mostSpecificType.getSimpleName()
- + "\".");
+ Flixel.info(
+ "Polyverse",
+ "Registered Polyverse script \""
+ + script.getClass().getSimpleName()
+ + "\" of script type \""
+ + mostSpecificType.getSimpleName()
+ + "\".");
}
}
script.onCreate();
}
} catch (Exception e) {
- Funkin.error("Polyverse", "Failed to load script: " + handle.path());
+ Flixel.error("Polyverse", "Failed to load script: " + handle.path(), e);
}
}
@@ -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());
- }
- }
- }
+ 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 extends Script> type : scripts.keySet()) {
+ executeScriptList(scripts.get(type), action);
+ }
+ }
+
+ private static void executeScriptList(
+ List extends Script> 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());
- }
+ T script = (T) scriptList.get(i);
+ if (script != null) {
+ try {
+ action.accept(script);
+ } catch (Exception 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/polyverse/src/main/java/me/stringdotjar/polyverse/script/type/Script.java
similarity index 91%
rename from core/src/main/java/me/stringfromjava/funkin/polyverse/script/type/Script.java
rename to polyverse/src/main/java/me/stringdotjar/polyverse/script/type/Script.java
index 8effbbb..6b41e28 100644
--- a/core/src/main/java/me/stringfromjava/funkin/polyverse/script/type/Script.java
+++ b/polyverse/src/main/java/me/stringdotjar/polyverse/script/type/Script.java
@@ -1,4 +1,4 @@
-package me.stringfromjava.funkin.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/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'