From 5d78a2e798d0aa2374fde74979fce2e1af2cec03 Mon Sep 17 00:00:00 2001 From: String Date: Sun, 14 Dec 2025 15:01:54 -0600 Subject: [PATCH] Add new custom tweening functionality --- .gitignore | 3 +- core/build.gradle | 1 + .../funkin/game/menus/TitleScreen.java | 2 +- .../funkin/tween/FunkinTween.java | 56 ++++- .../funkin/tween/FunkinTweenManager.java | 18 ++ .../tween/accessors/SpriteAccessor.java | 53 ----- .../tween/settings/FunkinTweenEase.java | 210 ++++++++++++++++++ .../tween/settings/FunkinTweenSettings.java | 149 +++++++++++++ .../tween/settings/FunkinTweenType.java | 32 +++ 9 files changed, 466 insertions(+), 58 deletions(-) create mode 100644 core/src/main/java/me/stringfromjava/funkin/tween/FunkinTweenManager.java delete mode 100644 core/src/main/java/me/stringfromjava/funkin/tween/accessors/SpriteAccessor.java create mode 100644 core/src/main/java/me/stringfromjava/funkin/tween/settings/FunkinTweenEase.java create mode 100644 core/src/main/java/me/stringfromjava/funkin/tween/settings/FunkinTweenSettings.java create mode 100644 core/src/main/java/me/stringfromjava/funkin/tween/settings/FunkinTweenType.java diff --git a/.gitignore b/.gitignore index d100851..159c8cd 100644 --- a/.gitignore +++ b/.gitignore @@ -88,6 +88,7 @@ www-test/ ## NetBeans: + /nbproject/private/ /android/nbproject/private/ /core/nbproject/private/ @@ -144,7 +145,7 @@ nbactions.xml nb-configuration.xml # VS Code -/.vscode +.vscode/ ## OS-Specific: .DS_Store diff --git a/core/build.gradle b/core/build.gradle index 9055240..ce9e3bf 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -10,6 +10,7 @@ dependencies { api "org.mini2Dx:universal-tween-engine:$universalTweenVersion" implementation "org.luaj:luaj-jse:3.0.1" + implementation 'org.jetbrains:annotations:15.0' if(enableGraalNative == 'true') { implementation "io.github.berstanio:gdx-svmhelper-annotations:$graalHelperVersion" diff --git a/core/src/main/java/me/stringfromjava/funkin/game/menus/TitleScreen.java b/core/src/main/java/me/stringfromjava/funkin/game/menus/TitleScreen.java index c2dd06b..f82a76b 100644 --- a/core/src/main/java/me/stringfromjava/funkin/game/menus/TitleScreen.java +++ b/core/src/main/java/me/stringfromjava/funkin/game/menus/TitleScreen.java @@ -16,7 +16,7 @@ public class TitleScreen extends FunkinScreen { @Override public void show() { super.show(); - tickleFight = Funkin.playSound("shared/sounds/tickleFight.ogg"); + tickleFight = new FunkinSound("shared/sounds/tickleFight.ogg"); Funkin.playMusic("preload/music/freakyMenu/freakyMenu.ogg", 0.5f); } diff --git a/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTween.java b/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTween.java index d2dea11..05dd7d6 100644 --- a/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTween.java +++ b/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTween.java @@ -1,10 +1,60 @@ package me.stringfromjava.funkin.tween; +import me.stringfromjava.funkin.tween.settings.FunkinTweenSettings; + +import java.lang.reflect.Field; +import java.util.ArrayList; + /** - * Core manager class for creating new tweens. + * 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 final class FunkinTween { +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; + + private final ArrayList cachedFields; + + /** + * @param object The object to tween values. + * @param settings The settings that configure and determine how the tween should animate and last for. + */ + public FunkinTween(Object object, FunkinTweenSettings settings) { + this.tweenSettings = settings; + this.cachedFields = new ArrayList<>(); + + Field[] allFields = object.getClass().getFields(); + var neededFields = settings.getGoalFields(); + + // Due to how costly it is using Java reflect, we cache the fields we need + // before we actually do the real tweening functionality. This is so it + // doesn't cause lag while multiple tweens are being active. + for (Field field : allFields) { + String fName = field.getName(); + + if (!field.trySetAccessible() && !neededFields.contains(fName)) { + continue; + } + + cachedFields.add(field); + } + } - private FunkinTween() { + public FunkinTweenSettings getTweenSettings() { + return tweenSettings; } } diff --git a/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTweenManager.java b/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTweenManager.java new file mode 100644 index 0000000..ccc99b6 --- /dev/null +++ b/core/src/main/java/me/stringfromjava/funkin/tween/FunkinTweenManager.java @@ -0,0 +1,18 @@ +package me.stringfromjava.funkin.tween; + +import java.util.ArrayList; + +/** + * 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 ArrayList activeTweens = new ArrayList<>(); + + public void update(float delta) { + // TODO: Iterate through every active tween and update them! + } +} diff --git a/core/src/main/java/me/stringfromjava/funkin/tween/accessors/SpriteAccessor.java b/core/src/main/java/me/stringfromjava/funkin/tween/accessors/SpriteAccessor.java deleted file mode 100644 index 643e664..0000000 --- a/core/src/main/java/me/stringfromjava/funkin/tween/accessors/SpriteAccessor.java +++ /dev/null @@ -1,53 +0,0 @@ -package me.stringfromjava.funkin.tween.accessors; - -import aurelienribon.tweenengine.TweenAccessor; -import com.badlogic.gdx.graphics.g2d.Sprite; - -/** - * Tween accessor for {@link Sprite} objects. - */ -public class SpriteAccessor implements TweenAccessor { - - public static final int X = 1; - public static final int Y = 2; - public static final int XY = 3; - public static final int ALPHA = 4; - - @Override - public int getValues(Sprite target, int tweenType, float[] returnValues) { - switch (tweenType) { - case X: - returnValues[0] = target.getX(); - return 1; - case Y: - returnValues[0] = target.getY(); - return 1; - case XY: - returnValues[0] = target.getX(); - returnValues[1] = target.getY(); - return 2; - case ALPHA: - returnValues[0] = target.getColor().a; - return 1; - default: - assert false; - return -1; - } - } - - @Override - public void setValues(Sprite target, int tweenType, float[] newValues) { - switch (tweenType) { - case X -> target.setX(newValues[0]); - case Y -> target.setY(newValues[0]); - case XY -> { - target.setX(newValues[0]); - target.setY(newValues[1]); - } - case ALPHA -> target.setColor(target.getColor().r, target.getColor().g, target.getColor().b, newValues[0]); - default -> { - assert false; - } - } - } -} diff --git a/core/src/main/java/me/stringfromjava/funkin/tween/settings/FunkinTweenEase.java b/core/src/main/java/me/stringfromjava/funkin/tween/settings/FunkinTweenEase.java new file mode 100644 index 0000000..3f416ed --- /dev/null +++ b/core/src/main/java/me/stringfromjava/funkin/tween/settings/FunkinTweenEase.java @@ -0,0 +1,210 @@ +package me.stringfromjava.funkin.tween.settings; + +import me.stringfromjava.funkin.tween.FunkinTween; + +/** + * Class where all easer functions are stored, mostly used for tweening. + */ +public final class FunkinTweenEase { + + // Easing constants for specific functions. + private static final float PI2 = (float) Math.PI / 2; + private static final float EL = (float) ((float) 2 * Math.PI / .45); + private static final float B1 = (float) ((float) 1 / 2.75); + private static final float B2 = (float) ((float) 2 / 2.75); + private static final float B3 = (float) ((float) 1.5 / 2.75); + private static final float B4 = (float) ((float) 2.5 / 2.75); + private static final float B5 = (float) ((float) 2.25 / 2.75); + private static final float B6 = (float) ((float) 2.625 / 2.75); + private static final float ELASTIC_AMPLITUDE = 1; + private static final float ELASTIC_PERIOD = 0.4f; + + private FunkinTweenEase() { + } + + public static float linear(float t) { + return t; + } + + public static float quadIn(float t) { + return t * t; + } + + public static float quadOut(float t) { + return -t * (t - 2); + } + + public static float quadInOut(float t) { + return t <= .5 ? t * t * 2 : 1 - (--t) * t * 2; + } + + public static float cubeIn(float t) { + return t * t * t; + } + + public static float cubeOut(float t) { + return 1 + (--t) * t * t; + } + + public static float cubeInOut(float t) { + return t <= .5 ? t * t * t * 4 : 1 + (--t) * t * t * 4; + } + + public static float quartIn(float t) { + return t * t * t * t; + } + + public static float quartOut(float t) { + return 1 - (t -= 1) * t * t * t; + } + + public static float quartInOut(float t) { + return t <= .5 ? t * t * t * t * 8 : (float) ((1 - (t = t * 2 - 2) * t * t * t) / 2 + .5); + } + + public static float quintIn(float t) { + return t * t * t * t * t; + } + + public static float quintOut(float t) { + return (t = t - 1) * t * t * t * t + 1; + } + + public static float quintInOut(float t) { + return ((t *= 2) < 1) ? (t * t * t * t * t) / 2 : ((t -= 2) * t * t * t * t + 2) / 2; + } + + public static float smoothStepIn(float t) { + return 2 * smoothStepInOut(t / 2); + } + + public static float smoothStepOut(float t) { + return 2 * smoothStepInOut((float) (t / 2 + 0.5)) - 1; + } + + public static float smoothStepInOut(float t) { + return t * t * (t * -2 + 3); + } + + public static float smootherStepIn(float t) { + return 2 * smootherStepInOut(t / 2); + } + + public static float smootherStepOut(float t) { + return 2 * smootherStepInOut((float) (t / 2 + 0.5)) - 1; + } + + public static float smootherStepInOut(float t) { + return t * t * t * (t * (t * 6 - 15) + 10); + } + + public static float sineIn(float t) { + return (float) (-Math.cos(PI2 * t) + 1); + } + + public static float sineOut(float t) { + return (float) Math.sin(PI2 * t); + } + + public static float sineInOut(float t) { + return (float) (-Math.cos(Math.PI * t) / 2 + .5); + } + + public static float bounceIn(float t) { + return 1 - bounceOut(1 - t); + } + + public static float bounceOut(float t) { + if (t < B1) + return (float) (7.5625 * t * t); + if (t < B2) + return (float) (7.5625 * (t - B3) * (t - B3) + .75); + if (t < B4) + return (float) (7.5625 * (t - B5) * (t - B5) + .9375); + return (float) (7.5625 * (t - B6) * (t - B6) + .984375); + } + + public static float bounceInOut(float t) { + return t < 0.5 + ? (1 - bounceOut(1 - 2 * t)) / 2 + : (1 + bounceOut(2 * t - 1)) / 2; + } + + public static float circIn(float t) { + return (float) -(Math.sqrt(1 - t * t) - 1); + } + + public static float circOut(float t) { + return (float) Math.sqrt(1 - (t - 1) * (t - 1)); + } + + 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); + } + + public static float expoIn(float t) { + return (float) Math.pow(2, 10 * (t - 1)); + } + + public static float expoOut(float t) { + return (float) (-Math.pow(2, -10 * t) + 1); + } + + 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); + } + + public static float backIn(float t) { + return (float) (t * t * (2.70158 * t - 1.70158)); + } + + public static float backOut(float t) { + return (float) (1 - (--t) * (t) * (-2.70158 * t - 1.70158)); + } + + public static float backInOut(float t) { + t *= 2; + if (t < 1) + return (float) (t * t * (2.70158 * t - 1.70158) / 2); + t--; + return (float) ((1 - (--t) * (t) * (-2.70158 * t - 1.70158)) / 2 + .5); + } + + 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)); + } + + 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); + } + + 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))); + } + return (float) (Math.pow(2, -10 * (t -= 0.5f)) * Math.sin((t - (ELASTIC_PERIOD / 4)) * (2 * Math.PI) / ELASTIC_PERIOD) * 0.5 + 1); + } + + @FunctionalInterface + public interface FunkinTweenEaseFunction { + float compute(float t); + } + + @FunctionalInterface + public interface FunkinTweenEaseStartCallback { + void run(FunkinTween tween); + } + + @FunctionalInterface + public interface FunkinTweenEaseUpdateCallback { + void run(FunkinTween tween); + } + + @FunctionalInterface + public interface FunkinTweenEaseCompleteCallback { + void run(FunkinTween tween); + } +} diff --git a/core/src/main/java/me/stringfromjava/funkin/tween/settings/FunkinTweenSettings.java b/core/src/main/java/me/stringfromjava/funkin/tween/settings/FunkinTweenSettings.java new file mode 100644 index 0000000..4297a1f --- /dev/null +++ b/core/src/main/java/me/stringfromjava/funkin/tween/settings/FunkinTweenSettings.java @@ -0,0 +1,149 @@ +package me.stringfromjava.funkin.tween.settings; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.stream.Collectors; + +/** + * Class for holding basic data, containing configurations to be used on a {@link me.stringfromjava.funkin.tween.FunkinTween}. + */ +public class FunkinTweenSettings { + + private float duration; + private FunkinTweenType type; + private FunkinTweenEase.FunkinTweenEaseFunction ease; + private FunkinTweenEase.FunkinTweenEaseStartCallback onStart; + private FunkinTweenEase.FunkinTweenEaseUpdateCallback onUpdate; + private FunkinTweenEase.FunkinTweenEaseCompleteCallback onComplete; + private ArrayList goals; + + /** + * @param type The type of tween it should be. + */ + public FunkinTweenSettings(@NotNull FunkinTweenType type) { + this(type, null, null, null, null); + } + + /** + * @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 FunkinTweenEase.FunkinTweenEaseFunction ease) { + this(type, ease, null, null, null); + } + + /** + * @param type The type of tween it should be. + * @param ease The easer function the tween should use (aka how it should be animated). + * @param onStart Callback for when the tween starts. + */ + public FunkinTweenSettings(@NotNull FunkinTweenType type, @Nullable FunkinTweenEase.FunkinTweenEaseFunction ease, @Nullable FunkinTweenEase.FunkinTweenEaseStartCallback onStart) { + this(type, ease, onStart, null, null); + } + + /** + * @param type The type of tween it should be. + * @param ease The easer function the tween should use (aka how it should be animated). + * @param onStart Callback for when the tween starts. + * @param onUpdate Callback for when the tween updates. + */ + public FunkinTweenSettings(@NotNull FunkinTweenType type, @Nullable FunkinTweenEase.FunkinTweenEaseFunction ease, @Nullable FunkinTweenEase.FunkinTweenEaseStartCallback onStart, @Nullable FunkinTweenEase.FunkinTweenEaseUpdateCallback onUpdate) { + this(type, ease, onStart, onUpdate, null); + } + + /** + * @param type The type of tween it should be. + * @param ease The easer function the tween should use (aka how it should be animated). + * @param onStart Callback for when the tween starts. + * @param onUpdate Callback for when the tween updates. + * @param onComplete Callback for when the tween has been completed. + */ + public FunkinTweenSettings(@NotNull FunkinTweenType type, @Nullable FunkinTweenEase.FunkinTweenEaseFunction ease, @Nullable FunkinTweenEase.FunkinTweenEaseStartCallback onStart, @Nullable FunkinTweenEase.FunkinTweenEaseUpdateCallback onUpdate, @Nullable FunkinTweenEase.FunkinTweenEaseCompleteCallback onComplete) { + this.duration = 1.0f; + this.type = type; + this.ease = ease; + this.onStart = onStart; + this.onUpdate = onUpdate; + this.onComplete = onComplete; + this.goals = new ArrayList<>(); + } + + /** + * Adds a new goal to tween an objects value to. + * + * @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) { + goals.add(new FunkinTweenGoal(field, value)); + return this; + } + + /** + * Sets the duration of how long the tween should last for. + * + * @param duration The new value to set. + * @return {@code this} tween settings object for chaining. + */ + public FunkinTweenSettings setDuration(float duration) { + this.duration = duration; + return this; + } + + public float getDuration() { + return duration; + } + + public FunkinTweenType getType() { + return type; + } + + public FunkinTweenEase.FunkinTweenEaseFunction getEase() { + return ease; + } + + public FunkinTweenEase.FunkinTweenEaseStartCallback getOnStart() { + return onStart; + } + + public FunkinTweenEase.FunkinTweenEaseUpdateCallback getOnUpdate() { + return onUpdate; + } + + public FunkinTweenEase.FunkinTweenEaseCompleteCallback getOnComplete() { + return onComplete; + } + + public ArrayList getGoals() { + return new ArrayList<>(goals); + } + + public Collection getGoalFields() { + return goals.stream() + .map(FunkinTweenGoal::field) + .collect(Collectors.toList()); + } + + public Collection getGoalValues() { + return goals.stream() + .map(FunkinTweenGoal::value) + .collect(Collectors.toList()); + } + + public void clearGoals() { + goals.clear(); + } + + /** + * A record containing basic info for a tween goal (aka a field to tween a numeric value to). + * + * @param field The field to tween. + * @param value The value to tween the field to. + */ + public record FunkinTweenGoal(@NotNull String field, float value) { + } +} diff --git a/core/src/main/java/me/stringfromjava/funkin/tween/settings/FunkinTweenType.java b/core/src/main/java/me/stringfromjava/funkin/tween/settings/FunkinTweenType.java new file mode 100644 index 0000000..35243ac --- /dev/null +++ b/core/src/main/java/me/stringfromjava/funkin/tween/settings/FunkinTweenType.java @@ -0,0 +1,32 @@ +package me.stringfromjava.funkin.tween.settings; + +/** + * Enum containing all different tween types that can determine + */ +public enum FunkinTweenType { + + /** + * Will stop and remove itself from the manager when it finishes. + */ + ONESHOT, + + /** + * Will stop when it finishes but remain in the manager. + */ + PERSIST, + + /** + * Will play tween in reverse direction + */ + BACKWARD, + + /** + * Will restart immediately when it finishes. + */ + LOOPING, + + /** + * "To and from", will play tween hither and thither. Also loops indefinitely. + */ + PINGPONG; +}