diff --git a/src/main/java/de/edgelord/saltyengine/components/DeflectionOnMouseOverComponent.java b/src/main/java/de/edgelord/saltyengine/components/DeflectionOnMouseOverComponent.java index 363797b7..8c932fb4 100644 --- a/src/main/java/de/edgelord/saltyengine/components/DeflectionOnMouseOverComponent.java +++ b/src/main/java/de/edgelord/saltyengine/components/DeflectionOnMouseOverComponent.java @@ -16,25 +16,24 @@ package de.edgelord.saltyengine.components; -import de.edgelord.saltyengine.core.animation.LinearKeyframeAnimation; +import de.edgelord.saltyengine.core.animation.KeyframeAnimation; +import de.edgelord.saltyengine.core.animation.TransitionFunction; import de.edgelord.saltyengine.core.event.CollisionEvent; import de.edgelord.saltyengine.core.graphics.SaltyGraphics; import de.edgelord.saltyengine.core.stereotypes.ComponentContainer; import de.edgelord.saltyengine.transform.Vector2f; /** - * Uses a {@link LinearKeyframeAnimation} to make its parent grow and shrink - * again when the cursor touches is. + * Uses a {@link KeyframeAnimation} to make its parent grow and shrink again + * when the cursor touches is. */ public class DeflectionOnMouseOverComponent extends Component { /** * The animation that is used. */ - private final LinearKeyframeAnimation keyframeAnimation = new LinearKeyframeAnimation(); - + private final KeyframeAnimation keyframeAnimation = new KeyframeAnimation(TransitionFunction.easeInOutSine()); private final Vector2f returnPosition; - private float totalDeflection = 0f; /** @@ -42,11 +41,8 @@ public class DeflectionOnMouseOverComponent extends Component - * Usage: - * - *
- *     {@code
- *
- *     public class Player extends GameObject {
- *
- *         // [...]
- *
- *         public void initialize() {
- *             LinearTransformAnimations animationX = new LinearTransformAnimations(this, "animX", LinearTransformAnimations.Control.X_POS);
- *
- *             // Adds a keyframe at 1000 with a value of 200
- *             animationX.addKeyframe(1000, 200);
- *
- *             // Adds the component to the list
- *             addComponent(animationX);
- *
- *             // Let the animation start. during the next 1000 fixed ticks, the Player will slide to the right by 200 pixels.
- *             animationX.start();
- *         }
- *     }
- *     }
- * 
- *

- * NOTE: Take care of the hitbox of the {@link #getParent()}, these animations - * won't change it, you have to do that manually if necessary! - */ -public class LinearTransformAnimations extends Component { - - /** - * The keyframe animation used to animate the {@link - * de.edgelord.saltyengine.transform.Transform}. - */ - private final LinearKeyframeAnimation animation = new LinearKeyframeAnimation(); - /** - * What the animation should control - */ - private final Control control; - /** - * If this is true, the {@link #animation} will be recalculated using {@link - * LinearKeyframeAnimation#calculateAnimation()} at the next fixed tick, and - * this boolean will be reset to false again. - */ - private boolean recalculateOnNextStep = true; - /** - * Whether the animation should be looped or not. Default getters and - * setters exist. - */ - private boolean loop = false; - - /** - * {@inheritDoc} - * - * @param control what this component should animate - */ - public LinearTransformAnimations(final ComponentContainer parent, final String name, final Control control) { - super(parent, name, Components.ANIMATION_COMPONENT); - - this.control = control; - disable(); - } - - /** - * Makes the animation start at 0 again by setting {@link - * #recalculateOnNextStep} to true. - */ - public void startOver() { - recalculateOnNextStep = true; - } - - /** - * Starts the animation by enabling this component using {@link #enable()}. - */ - public void start() { - enable(); - } - - /** - * Pauses the animations by using {@link #disable()}. - */ - public void pause() { - disable(); - } - - /** - * Stops the animation by calling {@link #animation}.setCurrentFrame(0) and - * disabling this component using {@link #disable()}. - */ - public void stop() { - animation.setCurrentFrame(0); - disable(); - } - - @Override - public void onFixedTick() { - - if (recalculateOnNextStep) { - animation.calculateAnimation(); - recalculateOnNextStep = false; - } - - if (animation.animationEnded()) { - if (isLoop()) { - animation.restart(); - } else { - remove(); - } - } - - final float delta = animation.nextDelta(); - - switch (control) { - - case WIDTH: - getParent().setWidth(getParent().getWidth() + delta); - break; - case HEIGHT: - getParent().setHeight(getParent().getHeight() + delta); - break; - case X_POS: - getParent().moveX(delta); - break; - case Y_POS: - getParent().moveY(delta); - break; - - case ROTATION: - getParent().getTransform().setRotationDegrees(getParent().getTransform().getRotationDegrees() + delta); - break; - } - } - - @Override - public void draw(final SaltyGraphics saltyGraphics) { - - } - - @Override - public void onCollision(final CollisionEvent e) { - - } - - /** - * Adds the given {@link Keyframe} to the {@link #animation} by using {@link - * LinearKeyframeAnimation#add(Keyframe)}. - * - * @param keyframe the keyframe to be added - */ - public void addKeyframe(final Keyframe keyframe) { - animation.add(keyframe); - } - - /** - * Adds anew {@link Keyframe} with the given timing and value to the {@link - * #animation}. - * - * @param timing the timing of the new keyframe - * @param value the value of the keyframe - */ - public void addKeyframe(final int timing, final float value) { - animation.add(timing, value); - } - - /** - * @return the value of {@link #loop}. - */ - public boolean isLoop() { - return loop; - } - - /** - * Sets the value of {@link #loop} to the given boolean. - * - * @param loop the new value for {@link #loop}. - */ - public void setLoop(final boolean loop) { - this.loop = loop; - } - - /** - * Describes which part of the {@link de.edgelord.saltyengine.transform.Transform} - * is to be animated. - * - * WIDTH meaning the width of it - * ({@link de.edgelord.saltyengine.transform.Transform#setWidth(float)}) - * HEIGHT meaning the height of - * it ({@link de.edgelord.saltyengine.transform.Transform#setHeight(float)}) - * X_POS meaning the x position - * of it ({@link de.edgelord.saltyengine.transform.Transform#setX(float)}) - * Y_POS meaning the y position - * of it ({@link de.edgelord.saltyengine.transform.Transform#setY(float)}) - * ROTATION meaning the rotation - * of it ({@link de.edgelord.saltyengine.transform.Transform#setRotationDegrees(float)}) - */ - public enum Control { - WIDTH, - HEIGHT, - X_POS, - Y_POS, - ROTATION - } -} diff --git a/src/main/java/de/edgelord/saltyengine/components/gfx/scene/SceneFade.java b/src/main/java/de/edgelord/saltyengine/components/gfx/scene/SceneFade.java deleted file mode 100644 index aaeb409a..00000000 --- a/src/main/java/de/edgelord/saltyengine/components/gfx/scene/SceneFade.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright 2019 Malte Dostal - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package de.edgelord.saltyengine.components.gfx.scene; - -import de.edgelord.saltyengine.core.Game; -import de.edgelord.saltyengine.core.animation.KeyframeAnimation; -import de.edgelord.saltyengine.core.animation.LinearKeyframeAnimation; -import de.edgelord.saltyengine.core.graphics.SaltyGraphics; -import de.edgelord.saltyengine.gameobject.DrawingRoutine; -import de.edgelord.saltyengine.scene.Scene; -import de.edgelord.saltyengine.transform.Vector2f; -import de.edgelord.saltyengine.utils.ColorUtil; - -import java.awt.*; - -/** - * A {@link SceneGFXComponent} that draws a fade. The fade is either {@link - * Fade#IN} or {@link Fade#OUT}. - *

- *
- * Usage:
- * - * - * SceneFade fade = new SceneFade("myFade", ColorUtil.BLACK, 2000, - * SceneFade.Fade.OUT, myScene);
fade.startGFX(); - *
- */ -public abstract class SceneFade extends SceneGFXComponent { - - /** - * The DrawingRoutine that is used to draw the fade. - */ - private final DrawingRoutine drawingRoutine; - /** - * The KeyframeAnimation used to animated the fade. - */ - private final KeyframeAnimation animation; - /** - * The Scene that contains the fade. - */ - private final Scene container; - /** - * The current alpha value of the fade. - */ - private float currentAlpha; - - /** - * The constructor. - * - * @param name the name of the component - * @param color the Color of the fade - * @param duration the duration of the fade in amount of fixed ticks - * @param mode the mode of the fade - * @param container the container of the fade - */ - public SceneFade(final String name, final Color color, final int duration, final Fade mode, final Scene container) { - super(name); - - this.container = container; - - animation = new LinearKeyframeAnimation(); - - switch (mode) { - - case IN: - currentAlpha = 1f; - animation.add(0, 1f); - animation.add(duration, 0f); - animation.calculateAnimation(); - break; - case OUT: - currentAlpha = 0f; - animation.add(0, 0f); - animation.add(duration, 1f); - animation.calculateAnimation(); - break; - } - - drawingRoutine = new DrawingRoutine(DrawingRoutine.DrawingPosition.LAST) { - @Override - public void draw(final SaltyGraphics saltyGraphics) { - - if (isEnabled()) { - saltyGraphics.setColor(ColorUtil.withAlpha(color, currentAlpha)); - saltyGraphics.drawRect(Vector2f.zero(), Game.getGameDimensions()); - } - } - }; - - container.addDrawingRoutine(drawingRoutine); - } - - /** - * This method is called when the fade is finished, to e.g. load the next - * Scene or activate the content. - */ - public abstract void onFadeFinish(); - - /** - * Empty implementation because the drawing is done with the {@link - * #drawingRoutine}. - * - * @param saltyGraphics the graphics to draw to - */ - @Override - public void draw(final SaltyGraphics saltyGraphics) { - - } - - /** - * Updates the {@link #currentAlpha} or calls {@link #onFadeFinish()} and - * cleans up when the fade ended. - */ - @Override - public void onFixedTick() { - - if (animation.animationEnded()) { - onFadeFinish(); - container.removeDrawingRoutine(drawingRoutine); - remove(); - } else { - currentAlpha += animation.nextDelta(); - } - } - - /** - * The mode of the fade. - */ - public enum Fade { - - /** - * Fade in, meaning that the fade is fully visible at th beginning and - * fully invisible at the end. - */ - IN, - - /** - * Fade out, meaning that the fade is fully invisible at the beginning - * and fully visible at the end. - */ - OUT - } -} diff --git a/src/main/java/de/edgelord/saltyengine/core/animation/KeyframeAnimation.java b/src/main/java/de/edgelord/saltyengine/core/animation/KeyframeAnimation.java index 8279b2a0..35efc99a 100644 --- a/src/main/java/de/edgelord/saltyengine/core/animation/KeyframeAnimation.java +++ b/src/main/java/de/edgelord/saltyengine/core/animation/KeyframeAnimation.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Malte Dostal + * Copyright 2021 Malte Dostal * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,206 +16,155 @@ package de.edgelord.saltyengine.core.animation; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; -/** - * An abstract implementation of the - * Keyframe animation concept. - */ -// fixme HORRRRRRRIBLEEEEEEEEEEEEEEEEEEE -public abstract class KeyframeAnimation { - - // v = u0 * (1 - x)^3 + 3 * u1 * (1 - x)^2 * x + 3 * u2 * (1 - x) * x^2 + u3 * x^3 +public class KeyframeAnimation { - /** - * The list of Keyframes - */ - private List keyframes; - - /** - * A map of relative values to timecode generated by {@link - * #calculateAnimation()} - */ - private Map animation = new HashMap<>(); - - /** - * The timecode of the last {@link Keyframe} - */ - private int end = 0; - - /** - * The current frame, manipulable by {@link #setCurrentFrame(int)} and - * {@link #restart()} - */ - private int currentFrame = -1; + private TransitionFunction transition; + private List frames; + private int head; + private int duration; + private int frameIndex = 0; + private boolean loop = false; - /** - * Stores whether there were changes made to {@link #keyframes the list of - * KeyFrames} since the last {@link #calculateAnimation() calculation} or - * not - */ - private boolean unCalculatedChanges = false; + public KeyframeAnimation(final TransitionFunction transition, final List frames) { + this.transition = transition; + this.frames = frames; - /** - * A constructor. - * - * @param keyframes the initial list of - * Keyframes - */ - public KeyframeAnimation(final List keyframes) { - this.keyframes = keyframes; - add(0, 0); + computeDuration(); } - /** - * A constructor to initialize and empty - * KeyframeAnimation - */ - public KeyframeAnimation() { - this(new ArrayList<>()); + public KeyframeAnimation(final TransitionFunction transition) { + this(transition, new ArrayList<>()); } - /** - * A constructor that initializes the {@link #keyframes} list with the given - * vararg of {@link Keyframe}s. - * - * @param frames the {@link Keyframe frames} to initialize the animation - * with - */ - public KeyframeAnimation(final Keyframe... frames) { - this(new ArrayList<>(Arrays.asList(frames))); + public KeyframeAnimation(final TransitionFunction transition, final Keyframe... frames) { + this(transition, new ArrayList<>(Arrays.asList(frames))); } - /** - * Important: This method only returns a delta value! - * - * @return the next delta-step of the linear keyframe animation - */ - public float nextDelta() { - if (unCalculatedChanges) { - System.out.println("Warning: There are un-calculated changes in a KeyframeAnimation!"); - } + private void computeDuration() { + duration = frames.isEmpty() ? 0 : frames.get(frames.size() - 1).getTimecode(); + } - if (!animationEnded()) { - currentFrame++; - return animation.get(currentFrame); + public float nextValue() { + final Keyframe frame = frames.get(frameIndex); + final Keyframe prevFrame = frameIndex > 0 ? frames.get(frameIndex - 1) : null; + final float prevValue = prevFrame == null ? 0 : prevFrame.getValue(); + final float prevTimecode = prevFrame == null ? 0 : prevFrame.getTimecode(); + if (head >= frame.getTimecode()) { + if (frameIndex == frames.size() - 1) { + if (loop) { + reset(); + return nextValue(); + } else { + return frames.get(frameIndex).getValue(); + } + } else { + frameIndex++; + return nextValue(); + } } else { - // If there is a request out of the available timeline, there should be no delta, so return 0 - return 0f; + head++; + return prevValue + (frame.getValue() - prevValue) * transition.apply((head - 1 - prevTimecode) / (frame.getTimecode() - prevTimecode)); } } - /** - * Fills the {@link #animation} with timecode-to-delta values. - */ - public abstract void calculateAnimation(); - - /** - * Sorts the {@link #keyframes} by their timecode and sets the {@link - * #end}. - */ - protected void prepareAnimation() { - currentFrame = -1; - unCalculatedChanges = false; - keyframes.sort(Comparator.comparingInt(Keyframe::getTimecode)); - this.end = keyframes.get(keyframes.size() - 1).getTimecode(); - getAnimation().clear(); + public boolean finished() { + return head >= duration; } - /** - * Return true if and only if the - *
{@link #currentFrame} is smaller than - * the {@link #end} - 1 - * - * @return true if the animation - * has ended and therefore reached the last frame and false if - * not. - */ - public boolean animationEnded() { - return !(currentFrame < end - 1); + public void reset() { + head = 0; + frameIndex = 0; } - /** - * Adds the given Keyframe to the {@link #keyframes list}. - * - * @param keyframe the new Keyframe - */ - public void add(final Keyframe keyframe) { - keyframes.add(keyframe); - unCalculatedChanges = true; + public void appendFrame(final Keyframe frame) { + frames.add(frame); + computeDuration(); } - /** - * Adds a new Keyframe to the {@link #keyframes list} with the - * given timecode and value. - * - * @param timecode the timecode for the new - * Keyframe - * @param value the value of the new - * Keyframe - */ - public void add(final int timecode, final float value) { - keyframes.add(new Keyframe(timecode, value)); - unCalculatedChanges = true; + public void appendFrame(final int t, final float v) { + appendFrame(new Keyframe(t, v)); } - public void remove(final Keyframe keyframe) { - keyframes.remove(keyframe); + public void removeFrame(final Keyframe frame) { + frames.remove(frame); + computeDuration(); } - public void removeByTimecode(final int timing) { - keyframes.removeIf(keyframe -> keyframe.getTimecode() == timing); + public void removeFrame(final int timecode) { + frames.removeIf(f -> f.getTimecode() == timecode); + computeDuration(); } - public void removeByKey(final float key) { - keyframes.removeIf(keyframe -> keyframe.getValue() == key); + public void removeFrame(final float value) { + frames.removeIf(f -> f.getValue() == value); + computeDuration(); } - public void restart() { - currentFrame = -1; + /** + * Gets {@link #transition}. + * + * @return the value of {@link #transition} + */ + public TransitionFunction getTransition() { + return transition; } - protected Map getAnimation() { - return animation; + /** + * Sets {@link #transition}. + * + * @param transition the new value of {@link #transition} + */ + public void setTransition(final TransitionFunction transition) { + this.transition = transition; } - protected void setAnimation(final Map animation) { - this.animation = animation; + /** + * Sets {@link #frames}. + * + * @param frames the new value of {@link #frames} + */ + public void setFrames(final List frames) { + this.frames = frames; + duration = frames.get(frames.size() - 1).getTimecode(); } /** - * Gets {@link #keyframes}. + * Gets {@link #head}. * - * @return the value of {@link #keyframes} + * @return the value of {@link #head} */ - public List getKeyframes() { - return keyframes; + public int getHead() { + return head; } /** - * Sets {@link #keyframes}. + * Sets {@link #head}. * - * @param keyframes the new value of {@link #keyframes} + * @param head the new value of {@link #head} */ - public void setKeyframes(final List keyframes) { - this.keyframes = keyframes; + public void setHead(final int head) { + this.head = head; } /** - * Gets {@link #currentFrame}. + * Gets {@link #loop}. * - * @return the value of {@link #currentFrame} + * @return the value of {@link #loop} */ - public int getCurrentFrame() { - return currentFrame; + public boolean isLoop() { + return loop; } /** - * Sets {@link #currentFrame}. + * Sets {@link #loop}. * - * @param currentFrame the new value of {@link #currentFrame} + * @param loop the new value of {@link #loop} */ - public void setCurrentFrame(final int currentFrame) { - this.currentFrame = currentFrame; + public void setLoop(final boolean loop) { + this.loop = loop; } } diff --git a/src/main/java/de/edgelord/saltyengine/core/animation/LinearKeyframeAnimation.java b/src/main/java/de/edgelord/saltyengine/core/animation/LinearKeyframeAnimation.java deleted file mode 100644 index 939f36ac..00000000 --- a/src/main/java/de/edgelord/saltyengine/core/animation/LinearKeyframeAnimation.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2018 Malte Dostal - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package de.edgelord.saltyengine.core.animation; - -import java.util.List; - -/** - * A {@link KeyframeAnimation} implementation which uses linear transitions - * between values. - */ -// fixme PLEEEEEEAAAASEEE god why did I do it with a Map?????????? -public class LinearKeyframeAnimation extends KeyframeAnimation { - - /** - * A constructor. - * - * @param keyframes the initial list of - * Keyframes - */ - public LinearKeyframeAnimation(final List keyframes) { - super(keyframes); - } - - /** - * A constructor that initializes an empty animation. - */ - public LinearKeyframeAnimation() { - super(); - } - - /** - * A constructor that initializes the keyframes list with the given vararg - * of {@link Keyframe}s. - * - * @param frames the {@link Keyframe frames} to initialize the animation - * with - */ - public LinearKeyframeAnimation(final Keyframe... frames) { - super(frames); - } - - @Override - public void calculateAnimation() { - prepareAnimation(); - - int index = 0; - final List keyframes = getKeyframes(); - while (index < getKeyframes().size() - 1) { - int i = 0; - final int duration = keyframes.get(index + 1).getTimecode() - keyframes.get(index).getTimecode(); - final float step = (keyframes.get(index + 1).getValue() - keyframes.get(index).getValue()) / duration; - - while (i < duration) { - getAnimation().put(i + keyframes.get(index).getTimecode(), step); - i++; - } - index++; - } - } -} diff --git a/src/main/java/de/edgelord/saltyengine/core/animation/LinearVector2fKeyframeAnimation.java b/src/main/java/de/edgelord/saltyengine/core/animation/LinearVector2fKeyframeAnimation.java deleted file mode 100644 index ed5244fe..00000000 --- a/src/main/java/de/edgelord/saltyengine/core/animation/LinearVector2fKeyframeAnimation.java +++ /dev/null @@ -1,11 +0,0 @@ -package de.edgelord.saltyengine.core.animation; - -/** - * An implementation of {@link Vector2fKeyframeAnimation} that uses two - * instances of {@link LinearKeyframeAnimation} to animate. - */ -public class LinearVector2fKeyframeAnimation extends Vector2fKeyframeAnimation { - public LinearVector2fKeyframeAnimation() { - super(new LinearKeyframeAnimation(), new LinearKeyframeAnimation()); - } -} diff --git a/src/main/java/de/edgelord/saltyengine/core/animation/TransitionFunction.java b/src/main/java/de/edgelord/saltyengine/core/animation/TransitionFunction.java new file mode 100644 index 00000000..5b33c799 --- /dev/null +++ b/src/main/java/de/edgelord/saltyengine/core/animation/TransitionFunction.java @@ -0,0 +1,116 @@ +/* + * Copyright 2021 Malte Dostal + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.edgelord.saltyengine.core.animation; + +import java.util.function.Function; + +import static java.lang.Math.*; + +/** + * A Function of type float that returns for x of 0 to 1 a value between 0 and + * 1. + *

+ * Used for transitions in {@link KeyframeAnimation}s. + *

+ * Provides transitions of type sine, expo, bounce and elastic, inspired by + * easings.net (GPT3) + */ +public interface TransitionFunction extends Function { + + float N1 = 7.5625f; + float D1 = 2.75f; + float C4 = (float) (2.0 * PI / 3.0); + float C5 = (float) (2.0 * PI / 4.5); + + /** + * Converts in to out and vice versa + * + * @return the inverse (in to out and v.v.) of this transition + */ + default TransitionFunction inverse() { + return x -> 1 - apply(1 - x); + } + + /** + * Returns a linear transition of form
{@code x -> x} + * + * @return a linear transition + */ + static TransitionFunction linear() { + return x -> x; + } + + static TransitionFunction easeInSine() { + return x -> 1f - (float) cos(x * PI / 2f); + } + + static TransitionFunction easeInOutSine() { + return x -> -((float) cos(PI * x) - 1) / 2f; + } + + static TransitionFunction easeInExpo() { + return x -> x == 0 ? 0 : (float) pow(2, 10 * x - 10); + } + + static TransitionFunction easeInOutExpo() { + return x -> { + if (x == 0) return 0f; + if (x == 1) return 1f; + if (x < .5f) return (float) pow(2, 20 * x - 10) / 2f; + return (float) (2 - pow(2, -20 * x + 10)) / 2f; + }; + } + + static TransitionFunction easeOutBounce() { + return x -> { + if (x < 1f / D1) { + return N1 * x * x; + } else if (x < 2f / D1) { + return N1 * (x -= 1.5f / D1) * x + 0.75f; + } else if (x < 2.5f / D1) { + return N1 * (x -= 2.25f / D1) * x + 0.9375f; + } else { + return N1 * (x -= 2.625f / D1) * x + 0.984375f; + } + }; + } + + static TransitionFunction easeInOutBounce() { + final TransitionFunction easeOutBounce = easeOutBounce(); + return x -> x < .5f + ? (1 - easeOutBounce.apply(1 - 2f * x)) / 2f + : (1 + easeOutBounce.apply(2f * x - 1)) / 2f; + } + + static TransitionFunction easeInElastic() { + return x -> { + if (x == 0) return 0f; + if (x == 1) return 1f; + return (float) (-pow(2, 10 * x - 10) * sin((x * 10 - 10.75f) * C4)); + }; + } + + static TransitionFunction easeInOutElastic() { + return x -> { + if (x == 0) return 0f; + if (x == 1) return 1f; + if (x < .5f) + return (float) (-(pow(2, 20 * x - 10) * sin((20 * x - 11.125) * C5)) / 2.0); + return (float) ((pow(2, -20 * x + 10) * sin((20 * x - 11.125) * C5)) / 2.0 + 1); + }; + } +} diff --git a/src/main/java/de/edgelord/saltyengine/core/animation/Vector2fKeyframeAnimation.java b/src/main/java/de/edgelord/saltyengine/core/animation/Vector2fKeyframeAnimation.java deleted file mode 100644 index 62b49ce1..00000000 --- a/src/main/java/de/edgelord/saltyengine/core/animation/Vector2fKeyframeAnimation.java +++ /dev/null @@ -1,179 +0,0 @@ -package de.edgelord.saltyengine.core.animation; - -import de.edgelord.saltyengine.transform.Vector2f; - -/** - * Packages two {@link KeyframeAnimation}s into one for animating positions - * based on {@link de.edgelord.saltyengine.transform.Vector2f}s. - */ -public class Vector2fKeyframeAnimation { - - /** - * The KeyframeAnimation for the x direction - */ - private final KeyframeAnimation animationX; - /** - * The KeyframeAnimation for the y direction - */ - private final KeyframeAnimation animationY; - - /** - * Constructs a new instance by setting {@link #animationX} and {@link - * #animationY} to the two given ones, respectively. - * - * @param animationX the x animation - * @param animationY the y animation - */ - public Vector2fKeyframeAnimation(KeyframeAnimation animationX, KeyframeAnimation animationY) { - this.animationX = animationX; - this.animationY = animationY; - } - - /** - * Returns the {@link KeyframeAnimation#nextDelta()} of both x and y - * animations stored in a {@link Vector2f} instance. For animating, one - * would want to add the returned vector to the object's position {@code - * object.getPosition().add(animation.nextDelta())} - * - * @return the next delta of the 2D movement stored in a {@link Vector2f} - */ - public Vector2f nextDelta() { - return new Vector2f(animationX.nextDelta(), animationY.nextDelta()); - } - - /** - * Adds a new Keyframe to the animation at the given timecode with the given - * x and y values, respectively. - * - * @param timecode the timecode of the new Keyframe - * @param valueX the x value for the new Keyframe - * @param valueY the y value for the new Keyframe - */ - public void add(final int timecode, final float valueX, final float valueY) { - animationX.add(timecode, valueX); - animationY.add(timecode, valueY); - } - - /** - * Adds a new Keyframe to the animation at the given timecode with the same, - * given value for both x and y components of this animation. - * - * @param timecode the timecode of the new Keyframe - * @param value the value for both x and y for the new Keyframe - */ - public void add(final int timecode, final float value) { - animationX.add(timecode, value); - animationY.add(timecode, value); - } - - /** - * Calculates the animation in its current state by calling the according - * method for both individual KeyframeAnimations. - * - * @see KeyframeAnimation#calculateAnimation() - */ - public void calculateAnimation() { - animationX.calculateAnimation(); - animationY.calculateAnimation(); - } - - /** - * Checks whether or not this animation has reached its end, which is the - * case when both individual animations have ended. - * - * @return has this animation ended yet? - * @see KeyframeAnimation#animationEnded() - */ - public boolean animationEnded() { - return animationX.animationEnded() && animationY.animationEnded(); - } - - /** - * Adds the given Keyframe to both individual animations - * - * @param keyframe the Keyframe to be added to the animation - * - * @see KeyframeAnimation#add(Keyframe) - */ - public void add(Keyframe keyframe) { - animationX.add(keyframe); - animationY.add(keyframe); - } - - /** - * Removes the given Keyframe from both individual animations - * - * @param keyframe the keyframe to remove from the animations - * - * @see KeyframeAnimation#remove(Keyframe) - */ - public void remove(Keyframe keyframe) { - animationX.remove(keyframe); - animationY.remove(keyframe); - } - - /** - * Removes the Keyframe at the given timecode from both individual - * animations. - * - * @param timing the timecode at which to remove a Keyframe - * - * @see KeyframeAnimation#removeByTimecode(int) - */ - public void removeByTimecode(int timing) { - animationX.removeByTimecode(timing); - animationY.removeByTimecode(timing); - } - - /** - * Removes all Keyframes in both animations that have the given value. - * - * @param key the value by which to remove all Keyframes - * - * @see KeyframeAnimation#removeByKey(float) - */ - public void removeByKey(float key) { - animationX.removeByKey(key); - animationY.removeByKey(key); - } - - /** - * Restarts the animation by restarting both individual animations. - * - * @see KeyframeAnimation#restart() - */ - public void restart() { - animationX.restart(); - animationY.restart(); - } - - /** - * Sets the current frame to the given one in both individual animations - * - * @param currentFrame the current frame to be - * - * @see KeyframeAnimation#setCurrentFrame(int) - */ - public void setCurrentFrame(int currentFrame) { - animationX.setCurrentFrame(currentFrame); - animationY.setCurrentFrame(currentFrame); - } - - /** - * Gets {@link #animationX}. - * - * @return {@link #animationX} - */ - public KeyframeAnimation getXAnimation() { - return animationX; - } - - /** - * Gets {@link #animationY}. - * - * @return {@link #animationY} - */ - public KeyframeAnimation getYAnimation() { - return animationY; - } -} diff --git a/src/main/java/de/edgelord/saltyengine/example/KeyframeAnimationExample.java b/src/main/java/de/edgelord/saltyengine/example/KeyframeAnimationExample.java new file mode 100644 index 00000000..e708a625 --- /dev/null +++ b/src/main/java/de/edgelord/saltyengine/example/KeyframeAnimationExample.java @@ -0,0 +1,99 @@ +/* + * Copyright 2021 Malte Dostal + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.edgelord.saltyengine.example; + +import de.edgelord.saltyengine.core.Game; +import de.edgelord.saltyengine.core.GameConfig; +import de.edgelord.saltyengine.core.animation.KeyframeAnimation; +import de.edgelord.saltyengine.core.animation.TransitionFunction; +import de.edgelord.saltyengine.core.graphics.SaltyGraphics; +import de.edgelord.saltyengine.gameobject.EmptyGameObject; +import de.edgelord.saltyengine.input.Input; +import de.edgelord.saltyengine.input.MouseInputAdapter; +import de.edgelord.saltyengine.scene.Scene; +import de.edgelord.saltyengine.utils.ColorUtil; + +import java.awt.event.MouseEvent; + +import static de.edgelord.saltyengine.transform.TransformCreator.t; + +/** + * Showcases {@link de.edgelord.saltyengine.core.animation.KeyframeAnimation}s + * and different transition functions + */ +public class KeyframeAnimationExample extends Game { + + private static boolean move = false; + + public static void main(String[] args) { + init(GameConfig.config(1920, 1080, "Keyframe Animations!", 5)); + + Input.addMouseInputHandler(new MouseInputAdapter() { + @Override + public void mouseClicked(final MouseEvent e) { + move = !move; + } + }); + start(60, new Scene() { + @Override + public void initialize() { + addGameObject(new Point(100, TransitionFunction.easeInSine(), "ease in sine")); + addGameObject(new Point(200, TransitionFunction.easeInSine().inverse(), "ease out sine")); + addGameObject(new Point(300, TransitionFunction.easeInOutSine(), "ease inout sine")); + addGameObject(new Point(400, TransitionFunction.easeInExpo(), "ease in expo")); + addGameObject(new Point(500, TransitionFunction.easeInExpo().inverse(), "ease out sine")); + addGameObject(new Point(600, TransitionFunction.easeInOutExpo(), "ease inout expo")); + addGameObject(new Point(700, TransitionFunction.easeInElastic(), "ease in elastic")); + addGameObject(new Point(800, TransitionFunction.easeInOutElastic(), "ease inout elasto")); + addGameObject(new Point(900, TransitionFunction.easeOutBounce().inverse(), "ease in bounce")); + addGameObject(new Point(1000, TransitionFunction.easeInOutBounce(), "ease inout bounce")); + } + }); + } + + private static class Point extends EmptyGameObject { + + private final KeyframeAnimation animation; + private final String transitionName; + + public Point(final float y, final TransitionFunction transition, final String transitionName) { + super(t(0, y, 1, 1), "point"); + + this.transitionName = transitionName; + animation = new KeyframeAnimation(transition); + animation.appendFrame(0, 0); + animation.appendFrame(500, Game.getGameWidth()); + animation.appendFrame(1000, 0); + animation.setLoop(true); + } + + @Override + public void onFixedTick() { + if (move) { + setX(animation.nextValue()); + } + } + + @Override + public void draw(final SaltyGraphics saltyGraphics) { + saltyGraphics.setColor(ColorUtil.GRAY); + saltyGraphics.drawPoint(getPosition(), 35); + saltyGraphics.setFont(saltyGraphics.getFont().deriveFont(25f)); + saltyGraphics.drawText(transitionName, 100, getY() - 25, SaltyGraphics.TextAnchor.BOTTOM_LEFT_CORNER); + } + } +} diff --git a/src/main/java/de/edgelord/saltyengine/ui/elements/TextButton.java b/src/main/java/de/edgelord/saltyengine/ui/elements/TextButton.java index 3fab1dbe..3a092887 100644 --- a/src/main/java/de/edgelord/saltyengine/ui/elements/TextButton.java +++ b/src/main/java/de/edgelord/saltyengine/ui/elements/TextButton.java @@ -19,7 +19,7 @@ import de.edgelord.saltyengine.core.Game; import de.edgelord.saltyengine.core.animation.Keyframe; import de.edgelord.saltyengine.core.animation.KeyframeAnimation; -import de.edgelord.saltyengine.core.animation.LinearKeyframeAnimation; +import de.edgelord.saltyengine.core.animation.TransitionFunction; import de.edgelord.saltyengine.core.graphics.SaltyGraphics; import de.edgelord.saltyengine.displaymanager.DisplayManager; import de.edgelord.saltyengine.transform.Transform; @@ -34,8 +34,8 @@ */ public abstract class TextButton extends Button { - private KeyframeAnimation enterAnimation = new LinearKeyframeAnimation(); - private KeyframeAnimation exitAnimation = new LinearKeyframeAnimation(); + private KeyframeAnimation enterAnimation = new KeyframeAnimation(TransitionFunction.easeInSine()); + private KeyframeAnimation exitAnimation = new KeyframeAnimation(TransitionFunction.easeInSine()); private Keyframe enterAnimationKeyframe = new Keyframe(20, 15); private Keyframe exitAnimationKeyframe = new Keyframe(5, -15); @@ -86,10 +86,10 @@ private void calculateSize() { @Override public void onFixedTick() { super.onFixedTick(); - if (!enterAnimation.animationEnded()) { - fontSize += enterAnimation.nextDelta(); - } else if (!exitAnimation.animationEnded()) { - fontSize += exitAnimation.nextDelta(); + if (!enterAnimation.finished()) { + fontSize = enterAnimation.nextValue(); + } else if (!exitAnimation.finished()) { + fontSize = exitAnimation.nextValue(); } else if (!mouseHoversOver()) { fontSize = requestedFontSize; } @@ -105,14 +105,16 @@ public void drawBackground(final SaltyGraphics saltyGraphics) { @Override public void mouseEntered(final Transform cursor) { fontSize = requestedFontSize; - enterAnimation = new LinearKeyframeAnimation(enterAnimationKeyframe); - enterAnimation.calculateAnimation(); + enterAnimation = new KeyframeAnimation(TransitionFunction.easeInSine().inverse()); + enterAnimation.appendFrame(0, requestedFontSize); + enterAnimation.appendFrame(20, requestedFontSize + 15); } @Override public void mouseExited(final Transform cursor) { - exitAnimation = new LinearKeyframeAnimation(exitAnimationKeyframe); - exitAnimation.calculateAnimation(); + exitAnimation = new KeyframeAnimation(TransitionFunction.easeInSine()); + enterAnimation.appendFrame(0, requestedFontSize + 15); + enterAnimation.appendFrame(5, requestedFontSize); } @Override diff --git a/src/main/java/testing/BirdPlayer.java b/src/main/java/testing/BirdPlayer.java index 06eb1bef..db1c2cee 100644 --- a/src/main/java/testing/BirdPlayer.java +++ b/src/main/java/testing/BirdPlayer.java @@ -19,7 +19,6 @@ import de.edgelord.saltyengine.components.DeflectionOnMouseOverComponent; import de.edgelord.saltyengine.components.FixedRate; import de.edgelord.saltyengine.components.animation.AnimationRender; -import de.edgelord.saltyengine.components.animation.LinearTransformAnimations; import de.edgelord.saltyengine.core.Game; import de.edgelord.saltyengine.core.event.CollisionEvent; import de.edgelord.saltyengine.core.graphics.SaltyGraphics; @@ -36,10 +35,6 @@ public class BirdPlayer extends GameObject implements Serializable { private static final float speed = 2500f; - private final LinearTransformAnimations keyFrameAnimationX = new LinearTransformAnimations(this, "mySuperAnimationX", LinearTransformAnimations.Control.X_POS); - private final LinearTransformAnimations keyFrameAnimationRotation = new LinearTransformAnimations(this, "mySuperAnimationRotation", LinearTransformAnimations.Control.ROTATION); - private final LinearTransformAnimations keyFrameAnimationWidth = new LinearTransformAnimations(this, "mySuperAnimationWidth", LinearTransformAnimations.Control.WIDTH); - private final LinearTransformAnimations keyFrameAnimationHeight = new LinearTransformAnimations(this, "mySuperAnimationHeight", LinearTransformAnimations.Control.HEIGHT); private final AnimationRender animationRender; private final FixedRate soundTiming = new FixedRate(this, "soundTiming", 350); private SpritesheetAnimation spritesheetAnimation; @@ -69,31 +64,6 @@ private void initAnimations(final SaltyImage spriteSheetImage) { spritesheetAnimation.setFrames(spritesheet.getFrames(new Coordinates(0, 0), new Coordinates(1, 1), new Coordinates(2, 1), new Coordinates(3, 0))); - keyFrameAnimationX.addKeyframe(3000, 0); - keyFrameAnimationX.addKeyframe(9000, 700); - keyFrameAnimationX.addKeyframe(10000, 1000); - - keyFrameAnimationRotation.addKeyframe(1500, 0); - keyFrameAnimationRotation.addKeyframe(5000, 360); - keyFrameAnimationRotation.addKeyframe(7500, 180); - keyFrameAnimationRotation.addKeyframe(9000, 0); - - keyFrameAnimationWidth.addKeyframe(1000, 0); - keyFrameAnimationWidth.addKeyframe(5000, 150); - - keyFrameAnimationHeight.addKeyframe(1000, 0); - keyFrameAnimationHeight.addKeyframe(5000, 101); - - - addComponent(keyFrameAnimationX); - addComponent(keyFrameAnimationRotation); - addComponent(keyFrameAnimationWidth); - addComponent(keyFrameAnimationHeight); - keyFrameAnimationX.start(); - keyFrameAnimationRotation.start(); - keyFrameAnimationWidth.start(); - keyFrameAnimationHeight.start(); - setMass(0.5f); } diff --git a/src/main/java/testing/TestingScene.java b/src/main/java/testing/TestingScene.java index 422dbea1..46e2522e 100644 --- a/src/main/java/testing/TestingScene.java +++ b/src/main/java/testing/TestingScene.java @@ -16,7 +16,6 @@ package testing; -import de.edgelord.saltyengine.components.gfx.scene.SceneFade; import de.edgelord.saltyengine.core.Game; import de.edgelord.saltyengine.factory.ImageFactory; import de.edgelord.saltyengine.graphics.geom.EnumShape; @@ -60,22 +59,6 @@ public void initialize() { //TODO: disableGravity(); getUI().addElement(new UninstallButton()); - - final SceneFade fadeIn = new SceneFade("fade-in", Color.BLACK, 3500, SceneFade.Fade.IN, this) { - @Override - public void onFadeFinish() { - System.out.println("Fading finished!"); - } - }; - - final SceneFade fade = new SceneFade("fade-out", ColorUtil.BLACK, 2000, SceneFade.Fade.OUT, this) { - @Override - public void onFadeFinish() { - fadeIn.startGFX(); - } - }; - fade.startGFX(); - RectangleCreator.init(); /*