entities) {
+ this.entities = entities;
+
+ boolean dontHaveColor = this.entities.stream().anyMatch(e -> !e.hasComponent(ColorComponent.class));
+
+ if (dontHaveColor) {
+ throw new IllegalArgumentException("All entities must have ColorComponent");
+ }
+
+ return new ColorAnimationBuilder(this);
+ }
}
\ No newline at end of file
diff --git a/fxgl/src/main/java/com/almasb/fxgl/net/MultiServer.java b/fxgl/src/main/java/com/almasb/fxgl/net/MultiServer.java
deleted file mode 100644
index 1e0a33ae2..000000000
--- a/fxgl/src/main/java/com/almasb/fxgl/net/MultiServer.java
+++ /dev/null
@@ -1,313 +0,0 @@
-/*
- * FXGL - JavaFX Game Library. The MIT License (MIT).
- * Copyright (c) AlmasB (almaslvl@gmail.com).
- * See LICENSE for details.
- */
-
-package com.almasb.fxgl.net;///*
-// * The MIT License (MIT)
-// *
-// * FXGL - JavaFX Game Library
-// *
-// * Copyright (c) 2015-2017 AlmasB (almaslvl@gmail.com)
-// *
-// * Permission is hereby granted, free of charge, to any person obtaining a copy
-// * of this software and associated documentation files (the "Software"), to deal
-// * in the Software without restriction, including without limitation the rights
-// * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// * copies of the Software, and to permit persons to whom the Software is
-// * furnished to do so, subject to the following conditions:
-// *
-// * The above copyright notice and this permission notice shall be included in
-// * all copies or substantial portions of the Software.
-// *
-// * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// * SOFTWARE.
-// */
-//
-//package com.almasb.fxgl.net;
-//
-//import org.apache.logging.log4j.LogManager;
-//import org.apache.logging.log4j.Logger;
-//
-//import java.io.*;
-//import java.net.*;
-//import java.util.ArrayList;
-//import java.util.Collections;
-//import java.util.List;
-//
-///**
-// * MultiServer for multiple concurrent network connections (clients)
-// *
-// * Since there isn't a 1 to 1 connection, {@link #stop()} must be explicitly
-// * called to attempt a clean shutdown of the server
-// *
-// * @author Almas Baimagambetov (AlmasB) (almaslvl@gmail.com)
-// */
-//public final class MultiServer extends NetworkConnection {
-//
-// private static final Logger log = LogManager.getLogger(MultiServer.class);
-//
-// private TCPConnectionThread tcpThread = new TCPConnectionThread();
-// private UDPConnectionThread udpThread = new UDPConnectionThread();
-//
-// private final List tcpThreads = Collections.synchronizedList(new ArrayList<>());
-//
-// private final List addresses = Collections.synchronizedList(new ArrayList<>());
-// private int tcpPort, udpPort;
-//
-// /**
-// * Constructs and configures a multi server with default ports
-// * No network operation is done at this point.
-// */
-// public MultiServer() {
-// this(NetworkConfig.DEFAULT_TCP_PORT, NetworkConfig.DEFAULT_UDP_PORT);
-// }
-//
-// /**
-// * Constructs and configures a multi server with specified ports
-// * No network operation is done at this point.
-// *
-// * @param tcpPort tcp port to use
-// * @param udpPort udp port to use
-// */
-// public MultiServer(int tcpPort, int udpPort) {
-// this.tcpPort = tcpPort;
-// this.udpPort = udpPort;
-//
-// tcpThread.setDaemon(true);
-// udpThread.setDaemon(true);
-// }
-//
-// /**
-// * Starts the server. This performs an actual network operation
-// * of binding to ports and listening for incoming connections.
-// */
-// public void start() {
-// tcpThread.start();
-// udpThread.start();
-// }
-//
-// /**
-// * Sends a message to all connected clients that
-// * the server is about to shut down. Then stops the server
-// * and the connection threads.
-// *
-// * Further calls to {@link #send(Serializable)} will
-// * throw IllegalStateException
-// */
-// public void stop() {
-// sendClosingMessage();
-//
-// tcpThread.running = false;
-// try {
-// tcpThread.server.close();
-// } catch (IOException ignored) {
-// }
-//
-// tcpThreads.forEach(t -> t.running = false);
-// udpThread.running = false;
-// }
-//
-// @Override
-// public void close() {
-// stop();
-// }
-//
-// @Override
-// protected void sendUDP(Serializable data) throws Exception {
-// if (udpThread.running) {
-// byte[] buf = toByteArray(data);
-// synchronized (addresses) {
-// for (FullInetAddress addr : addresses) {
-// try {
-// udpThread.outSocket.send(new DatagramPacket(buf, buf.length, addr.address, addr.port));
-// } catch (Exception e) {
-// log.warn("Failed to send UDP message: " + e.getMessage());
-// }
-// }
-// }
-// } else {
-// throw new IllegalStateException("UDP connection not active");
-// }
-// }
-//
-// @Override
-// protected void sendTCP(Serializable data) throws Exception {
-// synchronized (tcpThreads) {
-// tcpThreads.stream().filter(tcpThread -> tcpThread.running).forEach(tcpThread -> {
-// try {
-// tcpThread.outputStream.writeObject(data);
-// } catch (Exception e) {
-// log.warn("Failed to send TCP message: " + e.getMessage());
-// }
-// });
-// }
-// }
-//
-// private class TCPConnectionThread extends Thread {
-// private boolean running = true;
-// private ServerSocket server;
-//
-// @Override
-// public void run() {
-// try {
-// server = new ServerSocket(tcpPort);
-// } catch (Exception e) {
-// log.warn("Exception during TCP connection creation: " + e.getMessage());
-// running = false;
-// return;
-// }
-//
-// while (running) {
-// try {
-// Socket socket = server.accept();
-// socket.setTcpNoDelay(true);
-//
-// TCPThread t = new TCPThread(socket);
-// t.setDaemon(true);
-// tcpThreads.add(t);
-// t.start();
-// } catch (Exception e) {
-// log.warn("Exception during TCP connection execution: " + e.getMessage());
-// }
-// }
-//
-// try {
-// server.close();
-// } catch (Exception ignored) {
-// }
-// log.debug("TCP connection closed normally");
-// }
-// }
-//
-// private class TCPThread extends Thread {
-// private boolean running = false;
-// private ObjectOutputStream outputStream;
-// private Socket socket;
-//
-// public TCPThread(Socket socket) {
-// this.socket = socket;
-// }
-//
-// @Override
-// public void run() {
-// try (ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
-// ObjectInputStream in = new ObjectInputStream(socket.getInputStream())) {
-// outputStream = out;
-// socket.setTcpNoDelay(true);
-// running = true;
-//
-// while (running) {
-// Object data = in.readObject();
-// if (data == ConnectionMessage.CLOSE) {
-// running = false;
-// break;
-// }
-// if (data == ConnectionMessage.CLOSING) {
-// outputStream.writeObject(ConnectionMessage.CLOSE);
-// running = false;
-// break;
-// }
-//
-// parsers.getOrDefault(data.getClass(), d -> {
-// }).parse((Serializable) data);
-// }
-// } catch (Exception e) {
-// log.warn("Exception during TCP connection execution: " + e.getMessage());
-// running = false;
-// tcpThreads.remove(this);
-// try {
-// socket.close();
-// } catch (IOException ignored) {
-// }
-// return;
-// }
-//
-// tcpThreads.remove(this);
-// try {
-// socket.close();
-// } catch (IOException ignored) {
-// }
-// log.debug("TCP connection closed normally");
-// }
-// }
-//
-// private class UDPConnectionThread extends Thread {
-// private DatagramSocket outSocket;
-// private boolean running = false;
-//
-// @Override
-// public void run() {
-// try (DatagramSocket socket = new DatagramSocket(udpPort)) {
-// outSocket = socket;
-// running = true;
-//
-// while (running) {
-// try {
-// byte[] buf = new byte[16384];
-// DatagramPacket datagramPacket = new DatagramPacket(buf, buf.length);
-// socket.receive(datagramPacket);
-//
-// try (ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(datagramPacket.getData()))) {
-// Object data = in.readObject();
-// FullInetAddress addr = new FullInetAddress(datagramPacket.getAddress(), datagramPacket.getPort());
-//
-// if (data == ConnectionMessage.OPEN) {
-// if (!addresses.contains(addr)) {
-// addresses.add(addr);
-// }
-// }
-// if (data == ConnectionMessage.CLOSE) {
-// addresses.remove(addr);
-// continue;
-// }
-// if (data == ConnectionMessage.CLOSING) {
-// byte[] sendBuf = toByteArray(ConnectionMessage.CLOSE);
-// udpThread.outSocket.send(new DatagramPacket(sendBuf, sendBuf.length, addr.address, addr.port));
-// continue;
-// }
-//
-// parsers.getOrDefault(data.getClass(), d -> {
-// }).parse((Serializable) data);
-// }
-// } catch (Exception e) {
-// log.warn("Exception during UDP connection execution: " + e.getMessage());
-// }
-// }
-// } catch (Exception e) {
-// log.warn("Exception during UDP connection execution: " + e.getMessage());
-// running = false;
-// return;
-// }
-//
-// log.debug("UDP connection closed normally");
-// }
-// }
-//
-// private static class FullInetAddress {
-// private InetAddress address;
-// private int port;
-//
-// public FullInetAddress(InetAddress address, int port) {
-// this.address = address;
-// this.port = port;
-// }
-//
-// @Override
-// public boolean equals(Object obj) {
-// if (obj instanceof FullInetAddress) {
-// FullInetAddress other = (FullInetAddress) obj;
-// return this.address.getHostAddress().equals(
-// other.address.getHostAddress())
-// && this.port == other.port;
-// }
-// return false;
-// }
-// }
-//}
diff --git a/fxgl/src/main/java/com/almasb/fxgl/physics/PhysicsWorld.java b/fxgl/src/main/java/com/almasb/fxgl/physics/PhysicsWorld.java
index dd048a811..43d1147dd 100644
--- a/fxgl/src/main/java/com/almasb/fxgl/physics/PhysicsWorld.java
+++ b/fxgl/src/main/java/com/almasb/fxgl/physics/PhysicsWorld.java
@@ -261,12 +261,11 @@ private void postStep() {
}
/**
- * Resets physics world.
+ * Clears collidable entities and active collisions.
* Does not clear collision handlers.
*/
- @Override
- public void onWorldReset() {
- log.debug("Resetting physics world");
+ public void clear() {
+ log.debug("Clearing physics world");
entities.clear();
collisions.clear();
diff --git a/fxgl/src/main/java/com/almasb/fxgl/scene/GameScene.java b/fxgl/src/main/java/com/almasb/fxgl/scene/GameScene.java
index 1669eaaf8..89fb12ac0 100644
--- a/fxgl/src/main/java/com/almasb/fxgl/scene/GameScene.java
+++ b/fxgl/src/main/java/com/almasb/fxgl/scene/GameScene.java
@@ -38,7 +38,7 @@
* Represents the scene that shows game objects on the screen during "play" mode.
* Contains 3 layers. From bottom to top:
*
- * - Entities and their render layers
+ * - Entities and their render layers (game view)
* - Particles
* - UI Overlay
*
@@ -202,6 +202,20 @@ public void removeGameView(EntityView view) {
getRenderGroup(view.getRenderLayer()).getChildren().remove(view);
}
+ /**
+ * Removes all nodes from the game view layer.
+ */
+ public void clearGameViews() {
+ gameRoot.getChildren().clear();
+ }
+
+ /**
+ * Removes all nodes from the UI overlay.
+ */
+ public void clearUINodes() {
+ uiRoot.getChildren().clear();
+ }
+
/**
* Set true if UI elements should forward mouse events
* to the game layer.
@@ -260,6 +274,8 @@ private Group getRenderGroup(RenderLayer layer) {
@Override
public void onWorldUpdate(double tpf) {
+ getViewport().onUpdate(tpf);
+
particlesGC.setGlobalAlpha(1);
particlesGC.setGlobalBlendMode(BlendMode.SRC_OVER);
particlesGC.clearRect(0, 0, getWidth(), getHeight());
@@ -277,9 +293,8 @@ public void onWorldUpdate(double tpf) {
}
}
- @Override
- public void onWorldReset() {
- log.debug("Resetting game scene");
+ public void clear() {
+ log.debug("Clearing game scene");
getViewport().unbind();
drawables.clear();
diff --git a/fxgl/src/main/java/com/almasb/fxgl/scene/menu/FXGLDefaultMenu.java b/fxgl/src/main/java/com/almasb/fxgl/scene/menu/FXGLDefaultMenu.java
index c131bd535..f54a33f24 100644
--- a/fxgl/src/main/java/com/almasb/fxgl/scene/menu/FXGLDefaultMenu.java
+++ b/fxgl/src/main/java/com/almasb/fxgl/scene/menu/FXGLDefaultMenu.java
@@ -114,10 +114,6 @@ protected MenuBox createMenuBodyMainMenu() {
box.add(itemContinue);
itemContinue.disableProperty().bind(listener.hasSavesProperty().not());
-
-// app.getEventBus().addEventHandler(ProfileSelectedEvent.ANY, event -> {
-// itemContinue.setDisable(!event.hasSaves());
-// });
}
MenuButton itemNewGame = new MenuButton("NEW GAME");
diff --git a/fxgl/src/main/java/com/almasb/fxgl/scene/menu/MenuStyle.java b/fxgl/src/main/java/com/almasb/fxgl/scene/menu/MenuStyle.java
deleted file mode 100644
index 91b3a9c95..000000000
--- a/fxgl/src/main/java/com/almasb/fxgl/scene/menu/MenuStyle.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * FXGL - JavaFX Game Library. The MIT License (MIT).
- * Copyright (c) AlmasB (almaslvl@gmail.com).
- * See LICENSE for details.
- */
-package com.almasb.fxgl.scene.menu;
-
-/**
- * FXGL built-in menu styles. NOT COMPLETED YET.
- *
- * @author Almas Baimagambetov (AlmasB) (almaslvl@gmail.com)
- */
-public enum MenuStyle {
- FXGL_DEFAULT("fxgl_dark.css"),
-
- GTA5("fxgl_gta5.css"),
-
- CCTR("fxgl_cctr.css"),
-
- WARCRAFT3("fxgl_war3.css");
-
- private String css;
-
- public String getCSSFileName() {
- return css;
- }
-
- MenuStyle(String css) {
- this.css = css;
- }
-}
diff --git a/fxgl/src/main/java/com/almasb/fxgl/service/AudioPlayer.java b/fxgl/src/main/java/com/almasb/fxgl/service/AudioPlayer.java
index 0d801112c..beb0b9a48 100644
--- a/fxgl/src/main/java/com/almasb/fxgl/service/AudioPlayer.java
+++ b/fxgl/src/main/java/com/almasb/fxgl/service/AudioPlayer.java
@@ -103,6 +103,15 @@ default void playPositionalSound(String assetName, Point2D soundPosition, Point2
*/
void playPositionalSound(Sound sound, Point2D soundPosition, Point2D earPosition, double maxDistance);
+ /**
+ * @param bgmName name of the background music file to loop
+ */
+ default void loopBGM(String bgmName) {
+ Music music = FXGL.getAssetLoader().loadMusic(bgmName);
+ music.setCycleCount(Integer.MAX_VALUE);
+ playMusic(music);
+ }
+
/**
* Convenience method to play the music given its filename.
*
diff --git a/fxgl/src/main/java/com/almasb/fxgl/settings/GameSettings.java b/fxgl/src/main/java/com/almasb/fxgl/settings/GameSettings.java
index 063b9fe1c..66bb09224 100644
--- a/fxgl/src/main/java/com/almasb/fxgl/settings/GameSettings.java
+++ b/fxgl/src/main/java/com/almasb/fxgl/settings/GameSettings.java
@@ -6,7 +6,6 @@
package com.almasb.fxgl.settings;
import com.almasb.fxgl.app.ApplicationMode;
-import com.almasb.fxgl.scene.menu.MenuStyle;
import com.almasb.fxgl.service.ServiceType;
import com.almasb.fxgl.util.Credits;
import javafx.scene.input.KeyCode;
@@ -137,15 +136,6 @@ public void setApplicationMode(ApplicationMode mode) {
this.appMode = mode;
}
- /**
- * Set the menu style to use.
- *
- * @param style menu style
- */
- public void setMenuStyle(MenuStyle style) {
- this.menuStyle = style;
- }
-
/**
* Set the key that will trigger in-game menu.
*
diff --git a/fxgl/src/main/java/com/almasb/fxgl/settings/ReadOnlyGameSettings.java b/fxgl/src/main/java/com/almasb/fxgl/settings/ReadOnlyGameSettings.java
index 026d4e755..6c9d74d61 100644
--- a/fxgl/src/main/java/com/almasb/fxgl/settings/ReadOnlyGameSettings.java
+++ b/fxgl/src/main/java/com/almasb/fxgl/settings/ReadOnlyGameSettings.java
@@ -6,7 +6,6 @@
package com.almasb.fxgl.settings;
import com.almasb.fxgl.app.ApplicationMode;
-import com.almasb.fxgl.scene.menu.MenuStyle;
import com.almasb.fxgl.service.ServiceType;
import com.almasb.fxgl.util.Credits;
import javafx.scene.input.KeyCode;
@@ -33,7 +32,6 @@ public class ReadOnlyGameSettings {
protected boolean profilingEnabled = true;
protected boolean closeConfirmation = true;
protected ApplicationMode appMode = ApplicationMode.DEVELOPER;
- protected MenuStyle menuStyle = MenuStyle.FXGL_DEFAULT;
protected KeyCode menuKey = KeyCode.ESCAPE;
protected Credits credits = new Credits(Collections.emptyList());
protected List > services = new ArrayList<>();
@@ -64,7 +62,6 @@ public class ReadOnlyGameSettings {
this.profilingEnabled = copy.profilingEnabled;
this.closeConfirmation = copy.closeConfirmation;
this.appMode = copy.appMode;
- this.menuStyle = copy.menuStyle;
this.menuKey = copy.menuKey;
this.credits = new Credits(copy.credits);
this.services = copy.services;
@@ -111,10 +108,6 @@ public final ApplicationMode getApplicationMode() {
return appMode;
}
- public final MenuStyle getMenuStyle() {
- return menuStyle;
- }
-
public final KeyCode getMenuKey() {
return menuKey;
}
@@ -142,7 +135,6 @@ public String toString() {
"Menus: " + menuEnabled + '\n' +
"Profiling: " + profilingEnabled + '\n' +
"App Mode: " + appMode + '\n' +
- "Menu Style: " + menuStyle + '\n' +
"Menu Key: " + menuKey + '\n' +
"Services: " + services;
}
diff --git a/fxgl/src/main/kotlin/com/almasb/fxgl/animation/AnimatedValue.kt b/fxgl/src/main/kotlin/com/almasb/fxgl/animation/AnimatedValue.kt
index 850297a21..a621e3e74 100644
--- a/fxgl/src/main/kotlin/com/almasb/fxgl/animation/AnimatedValue.kt
+++ b/fxgl/src/main/kotlin/com/almasb/fxgl/animation/AnimatedValue.kt
@@ -9,6 +9,7 @@ package com.almasb.fxgl.animation
import com.almasb.fxgl.core.math.FXGLMath
import javafx.animation.Interpolator
import javafx.geometry.Point2D
+import javafx.scene.paint.Color
import javafx.scene.shape.CubicCurve
import javafx.scene.shape.QuadCurve
@@ -19,7 +20,7 @@ import javafx.scene.shape.QuadCurve
* @author Almas Baimagambetov (almaslvl@gmail.com)
*/
open class AnimatedValue
-@JvmOverloads constructor(val from: T, val to: T, val interpolator: Interpolator = Interpolator.LINEAR) {
+@JvmOverloads constructor(val from: T, val to: T, var interpolator: Interpolator = Interpolator.LINEAR) {
fun getValue(progress: Double): T {
return animate(from, to, progress, interpolator)
@@ -65,4 +66,18 @@ class AnimatedCubicBezierPoint2D
progress
)
}
+}
+
+class AnimatedColor
+@JvmOverloads constructor(from: Color, to: Color, interpolator: Interpolator = Interpolator.LINEAR)
+ : AnimatedValue(from, to, interpolator) {
+
+ override fun animate(val1: Color, val2: Color, progress: Double, interpolator: Interpolator): Color {
+ return Color.color(
+ FXGLMath.interpolate(val1.red, val2.red, progress, interpolator),
+ FXGLMath.interpolate(val1.green, val2.green, progress, interpolator),
+ FXGLMath.interpolate(val1.blue, val2.blue, progress, interpolator),
+ FXGLMath.interpolate(val1.opacity, val2.opacity, progress, interpolator)
+ )
+ }
}
\ No newline at end of file
diff --git a/fxgl/src/main/kotlin/com/almasb/fxgl/animation/Animation.kt b/fxgl/src/main/kotlin/com/almasb/fxgl/animation/Animation.kt
index ee74836f2..abeb84be7 100644
--- a/fxgl/src/main/kotlin/com/almasb/fxgl/animation/Animation.kt
+++ b/fxgl/src/main/kotlin/com/almasb/fxgl/animation/Animation.kt
@@ -9,6 +9,7 @@ package com.almasb.fxgl.animation
import com.almasb.fxgl.app.FXGL
import com.almasb.fxgl.app.State
import com.almasb.fxgl.app.listener.StateListener
+import com.almasb.fxgl.entity.animation.AnimationBuilder
import com.almasb.fxgl.util.EmptyRunnable
import javafx.util.Duration
@@ -23,6 +24,14 @@ abstract class Animation
var cycleCount: Int = 1,
val animatedValue: AnimatedValue): StateListener {
+ constructor(animationBuilder: AnimationBuilder, animatedValue: AnimatedValue) : this(animationBuilder.delay,
+ animationBuilder.duration,
+ animationBuilder.times,
+ animatedValue) {
+ onFinished = animationBuilder.onFinished
+ isAutoReverse = animationBuilder.isAutoReverse
+ }
+
var isAutoReverse = false
var onFinished: Runnable = EmptyRunnable
@@ -38,6 +47,10 @@ abstract class Animation
var isPaused = false
private set
+ /**
+ * True between start and stop.
+ * Pauses have no effect on this flag.
+ */
var isAnimating = false
private set
diff --git a/fxgl/src/main/kotlin/com/almasb/fxgl/animation/Interpolators.kt b/fxgl/src/main/kotlin/com/almasb/fxgl/animation/Interpolators.kt
new file mode 100644
index 000000000..d8aa4594a
--- /dev/null
+++ b/fxgl/src/main/kotlin/com/almasb/fxgl/animation/Interpolators.kt
@@ -0,0 +1,293 @@
+/*
+ * FXGL - JavaFX Game Library. The MIT License (MIT).
+ * Copyright (c) AlmasB (almaslvl@gmail.com).
+ * See LICENSE for details.
+ */
+
+package com.almasb.fxgl.animation
+
+import javafx.animation.Interpolator
+
+/**
+ * Adapted from https://github.com/grapefrukt/juicy-breakout/tree/master/lib/com/gskinner/motion/easing
+ *
+ * @author Almas Baimagambetov (almaslvl@gmail.com)
+ */
+enum class Interpolators : EasingInterpolator {
+
+ LINEAR {
+ override fun easeIn(ratio: Double): Double {
+ return ratio
+ }
+
+ override fun easeOut(ratio: Double): Double {
+ return ratio
+ }
+
+ override fun easeInOut(ratio: Double): Double {
+ return ratio
+ }
+ },
+
+ QUADRATIC {
+ override fun easeIn(ratio: Double): Double {
+ return ratio*ratio
+ }
+
+ override fun easeOut(ratio: Double): Double {
+ return -ratio*(ratio-2)
+ }
+
+ override fun easeInOut(ratio: Double): Double {
+ return if (ratio < 0.5)
+ 2*ratio*ratio
+ else
+ -2*ratio*(ratio-2)-1
+ }
+ },
+
+ CUBIC {
+ override fun easeIn(ratio: Double): Double {
+ return ratio*ratio*ratio
+ }
+
+ override fun easeOut(ratio: Double): Double {
+ val r = ratio - 1
+ return r*r*r+1
+ }
+
+ override fun easeInOut(ratio: Double): Double {
+ val r = ratio - 1
+
+ return if (ratio < 0.5)
+ 4*ratio*ratio*ratio
+ else
+ 4*r*r*r+1
+ }
+ },
+
+ QUARTIC {
+ override fun easeIn(ratio: Double): Double {
+ return ratio*ratio*ratio*ratio
+ }
+
+ override fun easeOut(ratio: Double): Double {
+ val r = ratio - 1
+
+ return 1-r*r*r*r
+ }
+
+ override fun easeInOut(ratio: Double): Double {
+ val r = ratio - 1
+
+ return if (ratio < 0.5)
+ 8*ratio*ratio*ratio*ratio
+ else
+ -8*r*r*r*r+1
+ }
+ },
+
+ QUINTIC {
+ override fun easeIn(ratio: Double): Double {
+ return ratio*ratio*ratio*ratio*ratio
+ }
+
+ override fun easeOut(ratio: Double): Double {
+ val r = ratio - 1
+
+ return 1 + r*r*r*r*r
+ }
+
+ override fun easeInOut(ratio: Double): Double {
+ val r = ratio - 1
+
+ return if (ratio < 0.5)
+ 16*ratio*ratio*ratio*ratio*ratio
+ else
+ 16 * r*r*r*r*r + 1
+ }
+ },
+
+ EXPONENTIAL {
+ override fun easeIn(ratio: Double): Double {
+ return if (ratio == 0.0) 0.0 else Math.pow(2.0, 10 * (ratio - 1))
+ }
+
+ override fun easeOut(ratio: Double): Double {
+ return if (ratio == 1.0) 1.0 else 1 - Math.pow(2.0, -10 * ratio)
+ }
+
+ override fun easeInOut(ratio: Double): Double {
+ if (ratio == 0.0 || ratio == 1.0)
+ return ratio
+
+ val r = ratio * 2 - 1
+ if (r < 0)
+ return 0.5*Math.pow(2.0, 10*r)
+
+ return 1 - 0.5*Math.pow(2.0, -10*r)
+ }
+ },
+
+ SINE {
+ override fun easeIn(ratio: Double): Double {
+ return 1 - Math.cos(ratio * (Math.PI / 2))
+ }
+
+ override fun easeOut(ratio: Double): Double {
+ return Math.sin(ratio * (Math.PI / 2))
+ }
+
+ override fun easeInOut(ratio: Double): Double {
+ return -0.5 * (Math.cos(ratio * Math.PI) - 1)
+ }
+ },
+
+ CIRCULAR {
+ override fun easeIn(ratio: Double): Double {
+ return -(Math.sqrt(1 - ratio*ratio) - 1)
+ }
+
+ override fun easeOut(ratio: Double): Double {
+ return Math.sqrt(1 - (ratio - 1) * (ratio - 1))
+ }
+
+ override fun easeInOut(ratio: Double): Double {
+ val r = ratio * 2
+ val r2 = r - 2
+
+ return if (r < 1)
+ -0.5 * (Math.sqrt(1 - r * r) - 1)
+ else
+ 0.5 * (Math.sqrt(1 - r2 * r2) + 1)
+ }
+ },
+
+ SMOOTH {
+ override fun easeIn(ratio: Double): Double {
+ return Interpolator.EASE_IN.interpolate(0.0, 1.0, ratio)
+ }
+
+ override fun easeOut(ratio: Double): Double {
+ return Interpolator.EASE_OUT.interpolate(0.0, 1.0, ratio)
+ }
+
+ override fun easeInOut(ratio: Double): Double {
+ return Interpolator.EASE_BOTH.interpolate(0.0, 1.0, ratio)
+ }
+ },
+
+ BOUNCE {
+ override fun easeIn(ratio: Double): Double {
+ return 1 - easeOut(1 - ratio)
+ }
+
+ override fun easeOut(ratio: Double): Double {
+ if (ratio < 1/2.75) {
+ return 7.5625*ratio*ratio
+ } else if (ratio < 2/2.75) {
+ val r = ratio - 1.5/2.75
+ return 7.5625*r*r+0.75
+ } else if (ratio < 2.5/2.75) {
+ val r = ratio-2.25/2.75
+ return 7.5625*r*r+0.9375
+ } else {
+ val r = ratio - 2.625/2.75
+ return 7.5625*r*r+0.984375
+ }
+ }
+
+ override fun easeInOut(ratio: Double): Double {
+ val r = ratio * 2
+
+ return if (r < 1)
+ 0.5 * easeIn(r)
+ else
+ 0.5 * easeOut(r - 1) + 0.5
+ }
+ },
+
+ ELASTIC {
+ private val a = 1
+ private val p = 0.3
+ private val s = p / 4
+
+ override fun easeIn(ratio: Double): Double {
+ if (ratio == 0.0 || ratio == 1.0)
+ return ratio
+
+ val r = ratio - 1
+
+ return -(a * Math.pow(2.0, 10 * r) * Math.sin((r - s) * (2 * Math.PI) / p))
+ }
+
+ override fun easeOut(ratio: Double): Double {
+ if (ratio == 0.0 || ratio == 1.0)
+ return ratio
+
+ return a * Math.pow(2.0, -10 * ratio) * Math.sin((ratio - s) * (2 * Math.PI) / p) + 1
+ }
+
+ override fun easeInOut(ratio: Double): Double {
+ if (ratio == 0.0 || ratio == 1.0)
+ return ratio
+
+ val r = ratio*2 - 1
+
+ if (r < 0) {
+ return -0.5 * (a * Math.pow(2.0, 10 * r) * Math.sin((r - s*1.5) * (2 * Math.PI) /(p*1.5)))
+ }
+
+ return 0.5 * a * Math.pow(2.0, -10 * r) * Math.sin((r - s*1.5) * (2 * Math.PI) / (p*1.5)) + 1
+ }
+ },
+
+ BACK {
+ private val s = 1.70158
+
+ override fun easeIn(ratio: Double): Double {
+ return ratio * ratio * ((s+1) * ratio - s)
+ }
+
+ override fun easeOut(ratio: Double): Double {
+ val r = ratio - 1
+
+ return r * r * ((s+1) * r + s) + 1
+ }
+
+ override fun easeInOut(ratio: Double): Double {
+ val r = ratio * 2
+ val r2 = r - 2
+
+ return if (r < 1)
+ 0.5*(r*r*((s*1.525+1)*r-s*1.525))
+ else
+ 0.5*(r2 * r2 * ((s*1.525 + 1) * r2 + s*1.525)+2)
+ }
+ }
+}
+
+interface EasingInterpolator {
+
+ fun EASE_IN(): Interpolator = object : Interpolator() {
+ override fun curve(t: Double): Double {
+ return easeIn(t)
+ }
+ }
+
+ fun EASE_OUT(): Interpolator = object : Interpolator() {
+ override fun curve(t: Double): Double {
+ return easeOut(t)
+ }
+ }
+
+ fun EASE_IN_OUT(): Interpolator = object : Interpolator() {
+ override fun curve(t: Double): Double {
+ return easeInOut(t)
+ }
+ }
+
+ fun easeIn(ratio: Double): Double
+ fun easeOut(ratio: Double): Double
+ fun easeInOut(ratio: Double): Double
+}
\ No newline at end of file
diff --git a/fxgl/src/main/kotlin/com/almasb/fxgl/animation/ParallelAnimation.kt b/fxgl/src/main/kotlin/com/almasb/fxgl/animation/ParallelAnimation.kt
new file mode 100644
index 000000000..720a12451
--- /dev/null
+++ b/fxgl/src/main/kotlin/com/almasb/fxgl/animation/ParallelAnimation.kt
@@ -0,0 +1,136 @@
+/*
+ * FXGL - JavaFX Game Library. The MIT License (MIT).
+ * Copyright (c) AlmasB (almaslvl@gmail.com).
+ * See LICENSE for details.
+ */
+
+package com.almasb.fxgl.animation
+
+import com.almasb.fxgl.app.FXGL
+import com.almasb.fxgl.app.State
+import com.almasb.fxgl.app.listener.StateListener
+import com.almasb.fxgl.util.EmptyRunnable
+
+/**
+ * @author Almas Baimagambetov (almaslvl@gmail.com)
+ */
+class ParallelAnimation(var cycleCount: Int = 1, vararg animations: Animation<*>) : StateListener {
+
+ constructor(vararg animations: Animation<*>) : this(1, *animations)
+
+ private val animations: List> = animations.toList()
+
+ var isAutoReverse = false
+ var onFinished: Runnable = EmptyRunnable
+
+ private var count = 0
+
+ var isReverse = false
+ private set
+
+ var isPaused = false
+ private set
+
+ /**
+ * True between start and stop.
+ * Pauses have no effect on this flag.
+ */
+ var isAnimating = false
+ private set
+
+ private var checkDelay = true
+
+ init {
+ if (animations.isEmpty())
+ throw IllegalArgumentException("Animation list is empty!")
+ }
+
+ /**
+ * State in which we are animating.
+ */
+ private lateinit var state: State
+
+ fun startInPlayState() {
+ start(FXGL.getApp().stateMachine.playState)
+ }
+
+ fun startReverse(state: State) {
+ if (!isAnimating) {
+ isReverse = true
+ start(state)
+ }
+ }
+
+ fun start(state: State) {
+ if (!isAnimating) {
+ this.state = state
+ isAnimating = true
+ state.addStateListener(this)
+
+ // reset animations, then start
+ if (isReverse) {
+ animations.forEach {
+ (it as Animation).onProgress(it.animatedValue.getValue(1.0))
+ it.startReverse(state)
+ }
+ } else {
+ animations.forEach {
+ (it as Animation).onProgress(it.animatedValue.getValue(0.0))
+ it.start(state)
+ }
+ }
+ }
+ }
+
+ fun stop() {
+ if (isAnimating) {
+ isAnimating = false
+ state.removeStateListener(this)
+ count = 0
+ isReverse = false
+ checkDelay = true
+ }
+ }
+
+ fun pause() {
+ isPaused = true
+ }
+
+ fun resume() {
+ isPaused = false
+ }
+
+ override fun onUpdate(tpf: Double) {
+ if (isPaused)
+ return
+
+ val isFinished = animations.none { it.isAnimating }
+
+ if (isFinished) {
+ count++
+
+ if (count >= cycleCount) {
+ onFinished.run()
+ stop()
+ return
+ } else {
+ if (isAutoReverse) {
+ isReverse = !isReverse
+
+ // reset animations, then start
+ if (isReverse) {
+ animations.forEach {
+ (it as Animation).onProgress(it.animatedValue.getValue(1.0))
+ it.startReverse(state)
+ }
+ } else {
+ animations.forEach {
+ (it as Animation).onProgress(it.animatedValue.getValue(0.0))
+ it.start(state)
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/fxgl/src/main/kotlin/com/almasb/fxgl/animation/SequentialAnimation.kt b/fxgl/src/main/kotlin/com/almasb/fxgl/animation/SequentialAnimation.kt
new file mode 100644
index 000000000..c33c7a664
--- /dev/null
+++ b/fxgl/src/main/kotlin/com/almasb/fxgl/animation/SequentialAnimation.kt
@@ -0,0 +1,141 @@
+/*
+ * FXGL - JavaFX Game Library. The MIT License (MIT).
+ * Copyright (c) AlmasB (almaslvl@gmail.com).
+ * See LICENSE for details.
+ */
+
+package com.almasb.fxgl.animation
+
+import com.almasb.fxgl.app.FXGL
+import com.almasb.fxgl.app.State
+import com.almasb.fxgl.app.listener.StateListener
+import com.almasb.fxgl.util.EmptyRunnable
+
+/**
+ * @author Almas Baimagambetov (almaslvl@gmail.com)
+ */
+class SequentialAnimation
+@JvmOverloads constructor(var cycleCount: Int = 1, vararg animations: Animation<*>) : StateListener {
+
+ private val animations: List> = animations.toList()
+
+ var isAutoReverse = false
+ var onFinished: Runnable = EmptyRunnable
+
+ private var count = 0
+
+ var isReverse = false
+ private set
+
+ var isPaused = false
+ private set
+
+ /**
+ * True between start and stop.
+ * Pauses have no effect on this flag.
+ */
+ var isAnimating = false
+ private set
+
+ private var checkDelay = true
+
+ private var animationIndex = 0
+
+ init {
+ if (animations.isEmpty())
+ throw IllegalArgumentException("Animation list is empty!")
+ }
+
+ /**
+ * State in which we are animating.
+ */
+ private lateinit var state: State
+
+ fun startInPlayState() {
+ start(FXGL.getApp().stateMachine.playState)
+ }
+
+ fun startReverse(state: State) {
+ if (!isAnimating) {
+ isReverse = true
+ start(state)
+ }
+ }
+
+ fun start(state: State) {
+ if (!isAnimating) {
+ this.state = state
+ isAnimating = true
+ state.addStateListener(this)
+
+ animationIndex = if (isReverse) animations.size-1 else 0
+
+ // reset animations, then start
+ if (isReverse) {
+ animations.forEach { (it as Animation).onProgress(it.animatedValue.getValue(1.0)) }
+ animations[animationIndex].startReverse(state)
+ } else {
+ animations.forEach { (it as Animation).onProgress(it.animatedValue.getValue(0.0)) }
+ animations[animationIndex].start(state)
+ }
+ }
+ }
+
+ fun stop() {
+ if (isAnimating) {
+ isAnimating = false
+ state.removeStateListener(this)
+ count = 0
+ isReverse = false
+ checkDelay = true
+ }
+ }
+
+ fun pause() {
+ isPaused = true
+ }
+
+ fun resume() {
+ isPaused = false
+ }
+
+ override fun onUpdate(tpf: Double) {
+ if (isPaused)
+ return
+
+ val anim = animations[animationIndex]
+
+ if (!anim.isAnimating) {
+ animationIndex += if (isReverse) -1 else 1
+
+ if ((!isReverse && animationIndex == animations.size) || (isReverse && animationIndex == -1)) {
+
+ count++
+
+ if (count >= cycleCount) {
+ onFinished.run()
+ stop()
+ return
+ } else {
+ if (isAutoReverse) {
+ isReverse = !isReverse
+ }
+ }
+
+ animationIndex = if (isReverse) animations.size-1 else 0
+
+ if (isReverse) {
+ animations.forEach { (it as Animation).onProgress(it.animatedValue.getValue(1.0)) }
+ } else {
+ animations.forEach { (it as Animation).onProgress(it.animatedValue.getValue(0.0)) }
+ }
+ }
+
+ if (isReverse) {
+ animations[animationIndex].startReverse(state)
+ } else {
+ animations[animationIndex].start(state)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/fxgl/src/main/kotlin/com/almasb/fxgl/app/DSL.kt b/fxgl/src/main/kotlin/com/almasb/fxgl/app/DSL.kt
index ec8bace3a..9d0e3c03c 100644
--- a/fxgl/src/main/kotlin/com/almasb/fxgl/app/DSL.kt
+++ b/fxgl/src/main/kotlin/com/almasb/fxgl/app/DSL.kt
@@ -6,22 +6,143 @@
package com.almasb.fxgl.app
+import com.almasb.fxgl.texture.Texture
+
+import com.almasb.fxgl.app.FXGL.Companion.getApp
+import com.almasb.fxgl.app.FXGL.Companion.getAssetLoader
+import com.almasb.fxgl.app.FXGL.Companion.getAudioPlayer
+import com.almasb.fxgl.app.FXGL.Companion.getInput
+import com.almasb.fxgl.core.math.FXGLMath.*
+import com.almasb.fxgl.ecs.Entity
+import com.almasb.fxgl.input.UserAction
+import com.almasb.fxgl.physics.CollisionHandler
+import javafx.beans.property.*
+import javafx.geometry.Point2D
+import javafx.scene.input.KeyCode
+import java.util.function.BiConsumer
+
/**
* This API is experimental.
+ * Using this API results in more concise but less readable code.
+ * Use with care.
*
* @author Almas Baimagambetov (almaslvl@gmail.com)
*/
-fun set(varName: String, value: Any) {
- FXGL.getApp().gameState.setValue(varName, value)
+/* VARS */
+
+fun set(varName: String, value: Any) = getApp().gameState.setValue(varName, value)
+
+fun geti(varName: String): Int = getApp().gameState.getInt(varName)
+
+fun getd(varName: String): Double = getApp().gameState.getDouble(varName)
+
+fun getb(varName: String): Boolean = getApp().gameState.getBoolean(varName)
+
+fun gets(varName: String): String = getApp().gameState.getString(varName)
+
+fun geto(varName: String): T = getApp().gameState.getObject(varName)
+
+fun getip(varName: String): IntegerProperty = getApp().gameState.intProperty(varName)
+
+fun getdp(varName: String): DoubleProperty = getApp().gameState.doubleProperty(varName)
+
+fun getbp(varName: String): BooleanProperty = getApp().gameState.booleanProperty(varName)
+
+fun getsp(varName: String): StringProperty = getApp().gameState.stringProperty(varName)
+
+fun getop(varName: String): ObjectProperty = getApp().gameState.objectProperty(varName)
+
+fun inc(varName: String, value: Int) = getApp().gameState.increment(varName, value)
+
+fun inc(varName: String, value: Double) = getApp().gameState.increment(varName, value)
+
+/* ASSET LOADING */
+
+fun texture(assetName: String): Texture = getAssetLoader().loadTexture(assetName)
+
+fun texture(assetName: String, width: Double, height: Double): Texture = getAssetLoader().loadTexture(assetName, width, height)
+
+fun text(assetName: String) = getAssetLoader().loadText(assetName)
+
+fun jsonAs(name: String, type: Class): T = getAssetLoader().loadJSON(name, type)
+
+/* AUDIO */
+
+fun loopBGM(assetName: String) = getAudioPlayer().loopBGM(assetName)
+
+fun play(assetName: String) {
+ if (assetName.endsWith(".wav")) {
+ getAudioPlayer().playSound(assetName)
+ } else if (assetName.endsWith(".mp3")) {
+ getAudioPlayer().playMusic(assetName)
+ } else {
+ throw IllegalArgumentException("Unsupported audio format: $assetName")
+ }
+}
+
+/* INPUT */
+
+fun onKeyDown(key: KeyCode, actionName: String, action: Runnable) {
+ getInput().addAction(object : UserAction(actionName) {
+ override fun onActionBegin() {
+ action.run()
+ }
+ }, key)
+}
+
+fun onKey(key: KeyCode, actionName: String, action: Runnable) {
+ getInput().addAction(object : UserAction(actionName) {
+ override fun onAction() {
+ action.run()
+ }
+ }, key)
+}
+
+fun onKeyUp(key: KeyCode, actionName: String, action: Runnable) {
+ getInput().addAction(object : UserAction(actionName) {
+ override fun onActionEnd() {
+ action.run()
+ }
+ }, key)
}
-fun geti(varName: String): Int = FXGL.getApp().gameState.getInt(varName)
+/* GAME WORLD */
-fun getd(varName: String): Double = FXGL.getApp().gameState.getDouble(varName)
+fun spawn(entityName: String): Entity = getApp().gameWorld.spawn(entityName)
+
+fun spawn(entityName: String, x: Double, y: Double): Entity = getApp().gameWorld.spawn(entityName, x, y)
+
+fun spawn(entityName: String, position: Point2D): Entity = getApp().gameWorld.spawn(entityName, position)
+
+/* PHYSICS */
+
+fun onCollisionBegin(typeA: Enum<*>, typeB: Enum<*>, action: BiConsumer) {
+ getApp().physicsWorld.addCollisionHandler(object : CollisionHandler(typeA, typeB) {
+ override fun onCollisionBegin(a: Entity, b: Entity) {
+ action.accept(a, b)
+ }
+ })
+}
+
+fun onCollision(typeA: Enum<*>, typeB: Enum<*>, action: BiConsumer) {
+ getApp().physicsWorld.addCollisionHandler(object : CollisionHandler(typeA, typeB) {
+ override fun onCollision(a: Entity, b: Entity) {
+ action.accept(a, b)
+ }
+ })
+}
+
+fun onCollisionEnd(typeA: Enum<*>, typeB: Enum<*>, action: BiConsumer) {
+ getApp().physicsWorld.addCollisionHandler(object : CollisionHandler(typeA, typeB) {
+ override fun onCollisionEnd(a: Entity, b: Entity) {
+ action.accept(a, b)
+ }
+ })
+}
-fun getb(varName: String): Boolean = FXGL.getApp().gameState.getBoolean(varName)
+/* MATH */
-fun gets(varName: String): String = FXGL.getApp().gameState.getString(varName)
+fun rand() = random()
-fun geto(varName: String): T = FXGL.getApp().gameState.getObject(varName)
\ No newline at end of file
+fun rand(min: Int, max: Int) = random(min, max)
\ No newline at end of file
diff --git a/fxgl/src/main/kotlin/com/almasb/fxgl/app/LoadingState.kt b/fxgl/src/main/kotlin/com/almasb/fxgl/app/LoadingState.kt
index 36d7d316b..6f96d3699 100644
--- a/fxgl/src/main/kotlin/com/almasb/fxgl/app/LoadingState.kt
+++ b/fxgl/src/main/kotlin/com/almasb/fxgl/app/LoadingState.kt
@@ -99,9 +99,11 @@ internal class LoadingState
private fun clearPreviousGame() {
log.debug("Clearing previous game")
- app.gameWorld.reset()
- app.gameState.clear()
+ app.gameWorld.clear()
+ app.physicsWorld.clear()
app.physicsWorld.clearCollisionHandlers()
+ app.gameScene.clear()
+ app.gameState.clear()
app.masterTimer.clear()
}
diff --git a/fxgl/src/main/kotlin/com/almasb/fxgl/app/MenuEventHandler.kt b/fxgl/src/main/kotlin/com/almasb/fxgl/app/MenuEventHandler.kt
index 7c3661c02..531b814b6 100644
--- a/fxgl/src/main/kotlin/com/almasb/fxgl/app/MenuEventHandler.kt
+++ b/fxgl/src/main/kotlin/com/almasb/fxgl/app/MenuEventHandler.kt
@@ -87,6 +87,7 @@ internal class MenuEventHandler(private val app: GameApplication) : MenuEventLis
saveLoadManager
.saveTask(dataFile, saveFile)
+ .onSuccessKt { hasSaves.value = true }
.executeAsyncWithDialogFX(ProgressDialog("Saving data: $saveFileName"))
}
diff --git a/fxgl/src/main/kotlin/com/almasb/fxgl/app/PauseMenuSubState.kt b/fxgl/src/main/kotlin/com/almasb/fxgl/app/PauseMenuSubState.kt
index f66345b41..ddc385f8e 100644
--- a/fxgl/src/main/kotlin/com/almasb/fxgl/app/PauseMenuSubState.kt
+++ b/fxgl/src/main/kotlin/com/almasb/fxgl/app/PauseMenuSubState.kt
@@ -7,6 +7,7 @@
package com.almasb.fxgl.app
import com.almasb.fxgl.animation.Animation
+import com.almasb.fxgl.animation.Interpolators
import com.almasb.fxgl.input.UserAction
import com.almasb.fxgl.texture.Texture
import com.almasb.fxgl.util.EmptyRunnable
@@ -60,7 +61,8 @@ internal object PauseMenuSubState : SubState() {
animation = FXGL.getUIFactory().translate(content,
Point2D(FXGL.getAppWidth() / 2.0 - 125, -400.0),
Point2D(FXGL.getAppWidth() / 2.0 - 125, FXGL.getAppHeight() / 2.0 - 200),
- Duration.seconds(0.33))
+ Duration.seconds(0.5))
+ animation.animatedValue.interpolator = Interpolators.BACK.EASE_OUT()
}
override fun onEnter(prevState: State) {
diff --git a/fxgl/src/main/kotlin/com/almasb/fxgl/asset/FXGLAssets.kt b/fxgl/src/main/kotlin/com/almasb/fxgl/asset/FXGLAssets.kt
index 94d453faf..39abbaf5a 100644
--- a/fxgl/src/main/kotlin/com/almasb/fxgl/asset/FXGLAssets.kt
+++ b/fxgl/src/main/kotlin/com/almasb/fxgl/asset/FXGLAssets.kt
@@ -44,22 +44,9 @@ class FXGLAssets {
UI_FONT = loader.loadFont(getString("ui.font"))
UI_MONO_FONT = loader.loadFont(getString("ui.mono.font"))
- UI_CSS = loadCSS()
+ UI_CSS = loader.loadCSS(getString("ui.css"))
UI_ICON_NAME = getString("ui.icon.name")
UI_ICON = loader.loadAppIcon(UI_ICON_NAME)
}
-
- private fun loadCSS(): CSS {
- val cssExternalForm = getString("ui.css")
-
- return FXGL.getAssetLoader().loadCSS(
-
- // if default css, then use menu style css
- if (cssExternalForm.endsWith("fxgl.css"))
- FXGL.getSettings().menuStyle.cssFileName
- else
- cssExternalForm
- )
- }
}
}
\ No newline at end of file
diff --git a/fxgl/src/main/kotlin/com/almasb/fxgl/devtools/DeveloperPane.kt b/fxgl/src/main/kotlin/com/almasb/fxgl/devtools/DeveloperPane.kt
index f48432e74..ebd2abded 100644
--- a/fxgl/src/main/kotlin/com/almasb/fxgl/devtools/DeveloperPane.kt
+++ b/fxgl/src/main/kotlin/com/almasb/fxgl/devtools/DeveloperPane.kt
@@ -98,6 +98,4 @@ class DeveloperPane : VBox(25.0), EntityWorldListener {
}
override fun onWorldUpdate(tpf: Double) { }
-
- override fun onWorldReset() { }
}
\ No newline at end of file
diff --git a/fxgl/src/main/kotlin/com/almasb/fxgl/ecs/component/IrremovableComponent.kt b/fxgl/src/main/kotlin/com/almasb/fxgl/ecs/component/IrremovableComponent.kt
new file mode 100644
index 000000000..876085a08
--- /dev/null
+++ b/fxgl/src/main/kotlin/com/almasb/fxgl/ecs/component/IrremovableComponent.kt
@@ -0,0 +1,12 @@
+package com.almasb.fxgl.ecs.component
+
+import com.almasb.fxgl.ecs.Component
+
+/**
+ * Marks an entity that cannot be removed from the game world.
+ * To remove such entity, this component must be removed first.
+ *
+ * @author Almas Baimagambetov (almaslvl@gmail.com)
+ */
+class IrremovableComponent : Component() {
+}
\ No newline at end of file
diff --git a/fxgl/src/main/kotlin/com/almasb/fxgl/ecs/component/TimeComponent.kt b/fxgl/src/main/kotlin/com/almasb/fxgl/ecs/component/TimeComponent.kt
new file mode 100644
index 000000000..4a6c9a19e
--- /dev/null
+++ b/fxgl/src/main/kotlin/com/almasb/fxgl/ecs/component/TimeComponent.kt
@@ -0,0 +1,9 @@
+package com.almasb.fxgl.ecs.component
+
+/**
+ * @author Almas Baimagambetov (almaslvl@gmail.com)
+ */
+class TimeComponent
+@JvmOverloads constructor(value: Double = 1.0) : DoubleComponent(value) {
+
+}
\ No newline at end of file
diff --git a/fxgl/src/main/kotlin/com/almasb/fxgl/entity/animation/ColorAnimationBuilder.kt b/fxgl/src/main/kotlin/com/almasb/fxgl/entity/animation/ColorAnimationBuilder.kt
new file mode 100644
index 000000000..2f8f7e065
--- /dev/null
+++ b/fxgl/src/main/kotlin/com/almasb/fxgl/entity/animation/ColorAnimationBuilder.kt
@@ -0,0 +1,48 @@
+/*
+ * FXGL - JavaFX Game Library. The MIT License (MIT).
+ * Copyright (c) AlmasB (almaslvl@gmail.com).
+ * See LICENSE for details.
+ */
+
+package com.almasb.fxgl.entity.animation
+
+import com.almasb.fxgl.animation.AnimatedColor
+import com.almasb.fxgl.animation.Animation
+import com.almasb.fxgl.entity.component.ColorComponent
+import javafx.scene.paint.Color
+
+/**
+ * @author Almas Baimagambetov (almaslvl@gmail.com)
+ */
+class ColorAnimationBuilder(private val animationBuilder: AnimationBuilder) {
+
+ private var startColor = Color.TRANSPARENT
+ private var endColor = Color.TRANSPARENT
+
+ fun fromColor(startColor: Color): ColorAnimationBuilder {
+ this.startColor = startColor
+ return this
+ }
+
+ fun toColor(endColor: Color): ColorAnimationBuilder {
+ this.endColor = endColor
+ return this
+ }
+
+ fun build(): Animation<*> {
+ return object : Animation(animationBuilder, AnimatedColor(startColor, endColor, animationBuilder.interpolator)) {
+
+ override fun onProgress(value: Color) {
+ animationBuilder.entities
+ .map { it.getComponent(ColorComponent::class.java) }
+ .forEach { it.value = value }
+ }
+ }
+ }
+
+ fun buildAndPlay(): Animation<*> {
+ val anim = build()
+ anim.startInPlayState()
+ return anim
+ }
+}
\ No newline at end of file
diff --git a/fxgl/src/main/kotlin/com/almasb/fxgl/entity/animation/RotationAnimationBuilder.kt b/fxgl/src/main/kotlin/com/almasb/fxgl/entity/animation/RotationAnimationBuilder.kt
index ace481e35..6bb85af74 100644
--- a/fxgl/src/main/kotlin/com/almasb/fxgl/entity/animation/RotationAnimationBuilder.kt
+++ b/fxgl/src/main/kotlin/com/almasb/fxgl/entity/animation/RotationAnimationBuilder.kt
@@ -30,8 +30,7 @@ class RotationAnimationBuilder(private val animationBuilder: AnimationBuilder) {
}
fun build(): Animation<*> {
- return object : Animation(animationBuilder.delay, animationBuilder.duration, animationBuilder.times,
- AnimatedValue(startAngle, endAngle, animationBuilder.interpolator)) {
+ return object : Animation(animationBuilder, AnimatedValue(startAngle, endAngle, animationBuilder.interpolator)) {
override fun onProgress(value: Double) {
animationBuilder.entities.forEach { it.rotation = value }
diff --git a/fxgl/src/main/kotlin/com/almasb/fxgl/entity/animation/ScaleAnimationBuilder.kt b/fxgl/src/main/kotlin/com/almasb/fxgl/entity/animation/ScaleAnimationBuilder.kt
index 59167d880..daf6d4fb9 100644
--- a/fxgl/src/main/kotlin/com/almasb/fxgl/entity/animation/ScaleAnimationBuilder.kt
+++ b/fxgl/src/main/kotlin/com/almasb/fxgl/entity/animation/ScaleAnimationBuilder.kt
@@ -31,8 +31,7 @@ class ScaleAnimationBuilder(private val animationBuilder: AnimationBuilder) {
}
fun build(): Animation<*> {
- return object : Animation(animationBuilder.delay, animationBuilder.duration, animationBuilder.times,
- AnimatedPoint2D(startScale, endScale, animationBuilder.interpolator)) {
+ return object : Animation(animationBuilder, AnimatedPoint2D(startScale, endScale, animationBuilder.interpolator)) {
override fun onProgress(value: Point2D) {
animationBuilder.entities.forEach {
diff --git a/fxgl/src/main/kotlin/com/almasb/fxgl/entity/animation/TranslationAnimationBuilder.kt b/fxgl/src/main/kotlin/com/almasb/fxgl/entity/animation/TranslationAnimationBuilder.kt
index a1191a29e..aff1db93a 100644
--- a/fxgl/src/main/kotlin/com/almasb/fxgl/entity/animation/TranslationAnimationBuilder.kt
+++ b/fxgl/src/main/kotlin/com/almasb/fxgl/entity/animation/TranslationAnimationBuilder.kt
@@ -52,8 +52,7 @@ class TranslationAnimationBuilder(private val animationBuilder: AnimationBuilder
}
private fun makeAnim(animValue: AnimatedValue): Animation {
- return object : Animation(animationBuilder.delay, animationBuilder.duration, animationBuilder.times,
- animValue) {
+ return object : Animation(animationBuilder, animValue) {
override fun onProgress(value: Point2D) {
animationBuilder.entities.forEach { it.position = value }
diff --git a/fxgl/src/main/kotlin/com/almasb/fxgl/entity/component/ColorComponent.kt b/fxgl/src/main/kotlin/com/almasb/fxgl/entity/component/ColorComponent.kt
new file mode 100644
index 000000000..3cb181501
--- /dev/null
+++ b/fxgl/src/main/kotlin/com/almasb/fxgl/entity/component/ColorComponent.kt
@@ -0,0 +1,16 @@
+/*
+ * FXGL - JavaFX Game Library. The MIT License (MIT).
+ * Copyright (c) AlmasB (almaslvl@gmail.com).
+ * See LICENSE for details.
+ */
+
+package com.almasb.fxgl.entity.component
+
+import com.almasb.fxgl.ecs.component.ObjectComponent
+import javafx.scene.paint.Color
+
+/**
+ * @author Almas Baimagambetov (almaslvl@gmail.com)
+ */
+class ColorComponent : ObjectComponent(Color.TRANSPARENT) {
+}
\ No newline at end of file
diff --git a/fxgl/src/main/kotlin/com/almasb/fxgl/entity/component/HighlightableComponent.kt b/fxgl/src/main/kotlin/com/almasb/fxgl/entity/component/HighlightableComponent.kt
new file mode 100644
index 000000000..5f93a2e7a
--- /dev/null
+++ b/fxgl/src/main/kotlin/com/almasb/fxgl/entity/component/HighlightableComponent.kt
@@ -0,0 +1,117 @@
+/*
+ * FXGL - JavaFX Game Library. The MIT License (MIT).
+ * Copyright (c) AlmasB (almaslvl@gmail.com).
+ * See LICENSE for details.
+ */
+
+package com.almasb.fxgl.entity.component
+
+import com.almasb.fxgl.animation.Animation
+import com.almasb.fxgl.animation.SequentialAnimation
+import com.almasb.fxgl.app.FXGL
+import com.almasb.fxgl.ecs.Component
+import com.almasb.fxgl.ecs.Entity
+import com.almasb.fxgl.ecs.component.Required
+import com.almasb.fxgl.entity.Entities
+import javafx.geometry.Point2D
+import javafx.scene.Node
+import javafx.scene.Parent
+import javafx.scene.paint.Color
+import javafx.scene.shape.Rectangle
+import javafx.util.Duration
+import jfxtras.util.NodeUtil
+
+/**
+ * @author Almas Baimagambetov (almaslvl@gmail.com)
+ */
+@Required(ViewComponent::class)
+class HighlightableComponent : Component() {
+
+ companion object {
+ private val SIZE = 2.0
+
+ private class HighlightView : Parent() {
+
+ private val animations = arrayListOf>()
+
+ fun startForView(view: Node) {
+ children.clear()
+
+ val light = Rectangle(view.layoutBounds.maxX, view.layoutBounds.maxY, null)
+ with(light) {
+ translateX = -1.0
+ translateY = -1.0
+ stroke = Color.YELLOW
+ strokeWidth = 2.0
+ }
+
+ val particle = makeParticle(0.0, 0.0)
+
+ children.addAll(light, particle)
+
+ val speed = 240.0
+
+ val dist1 = Point2D(-SIZE, -SIZE).distance(Point2D(view.layoutBounds.maxX, (-SIZE)))
+ val dist2 = Point2D(view.layoutBounds.maxX, (-SIZE)).distance(Point2D(view.layoutBounds.maxX, view.layoutBounds.maxY))
+ val dist3 = Point2D(view.layoutBounds.maxX, view.layoutBounds.maxY).distance(Point2D((-SIZE), view.layoutBounds.maxY))
+ val dist4 = Point2D((-SIZE), view.layoutBounds.maxY).distance(Point2D(-SIZE, -SIZE))
+
+ SequentialAnimation(1,
+ FXGL.getUIFactory().translate(particle, Point2D(-SIZE, -SIZE), Point2D(view.layoutBounds.maxX, (-SIZE)), Duration.seconds(dist1 / speed)),
+ FXGL.getUIFactory().translate(particle, Point2D(view.layoutBounds.maxX, (-SIZE)), Point2D(view.layoutBounds.maxX, view.layoutBounds.maxY), Duration.seconds(dist2 / speed)),
+ FXGL.getUIFactory().translate(particle, Point2D(view.layoutBounds.maxX, view.layoutBounds.maxY), Point2D((-SIZE), view.layoutBounds.maxY), Duration.seconds(dist3 / speed)),
+ FXGL.getUIFactory().translate(particle, Point2D((-SIZE), view.layoutBounds.maxY), Point2D(-SIZE, -SIZE), Duration.seconds(dist4 / speed))
+
+ ).startInPlayState()
+
+// animations.forEach { it.stop() }
+// animations.clear()
+//
+// children.addAll(
+// makeParticle((-SIZE), (-SIZE)),
+// makeParticle(view.layoutBounds.maxX, (-SIZE)),
+// makeParticle(view.layoutBounds.maxX, view.layoutBounds.maxY),
+// makeParticle((-SIZE), view.layoutBounds.maxY)
+// )
+//
+// for (i in 0..children.size-1) {
+// val animation = FXGL.getUIFactory()
+// .translate(children[i],
+// Point2D(children[if (i == children.size-1) 0 else i + 1].translateX, children[if (i == children.size-1) 0 else i + 1].translateY),
+// Duration.seconds(1.0))
+//
+// animation.cycleCount = Integer.MAX_VALUE
+// animations.add(animation)
+// animation.startInPlayState()
+// }
+ }
+
+ private fun makeParticle(x: Double, y: Double): Node {
+ //val particle = FXGL.getAssetLoader().loadTexture("highlight_particle.png", SIZE, SIZE).multiplyColor(Color.DARKRED)
+ val particle = Rectangle(SIZE, SIZE, Color.color(0.7, 0.5, 0.3, 0.75))
+ particle.translateX = x
+ particle.translateY = y
+ return particle
+ }
+ }
+
+ private val HIGHLIGHT = HighlightView()
+ }
+
+ override fun onAdded(entity: Entity) {
+ val view = Entities.getView(entity)
+
+ view.view.setOnMouseEntered {
+ if (HIGHLIGHT.scene != null) {
+ NodeUtil.removeFromParent(HIGHLIGHT)
+ }
+
+ HIGHLIGHT.startForView(view.view)
+ view.view.addNode(HIGHLIGHT)
+ }
+
+ view.view.setOnMouseExited {
+ view.view.removeNode(HIGHLIGHT)
+ }
+ }
+}
\ No newline at end of file
diff --git a/fxgl/src/main/kotlin/com/almasb/fxgl/io/FS.kt b/fxgl/src/main/kotlin/com/almasb/fxgl/io/FS.kt
index 840910916..3ac7e105b 100644
--- a/fxgl/src/main/kotlin/com/almasb/fxgl/io/FS.kt
+++ b/fxgl/src/main/kotlin/com/almasb/fxgl/io/FS.kt
@@ -6,7 +6,7 @@
package com.almasb.fxgl.io
-import org.apache.logging.log4j.LogManager
+import com.almasb.fxgl.core.logging.FXGLLogger
import java.io.*
import java.nio.file.*
import java.nio.file.attribute.BasicFileAttributes
@@ -17,15 +17,15 @@ import java.util.stream.Collectors
*
* @author Almas Baimagambetov (almaslvl@gmail.com)
*/
-class FS {
+class FS
+private constructor() {
companion object {
- private val log = LogManager.getLogger(FS::class.java)
+ private val log = FXGLLogger.get(FS::class.java)
private fun errorIfAbsent(path: Path) {
if (!Files.exists(path)) {
- log.warn ( "Path $path does not exist" )
throw FileNotFoundException("Path $path does not exist")
}
}
@@ -43,12 +43,12 @@ class FS {
// if file.parent is null we will use current dir, which exists
if (file.parent != null && !Files.exists(file.parent)) {
- log.debug ( "Creating directories to: ${file.parent}" )
+ log.debug("Creating directories to: ${file.parent}")
Files.createDirectories(file.parent)
}
ObjectOutputStream(Files.newOutputStream(file)).use {
- log.debug ( "Writing to: $file" )
+ log.debug("Writing to: $file")
it.writeObject(data)
}
})
@@ -68,7 +68,7 @@ class FS {
errorIfAbsent(file)
ObjectInputStream(Files.newInputStream(file)).use {
- log.debug ( "Reading from: $file" )
+ log.debug("Reading from: $file")
return@taskOf it.readObject() as T
}
}
@@ -172,7 +172,7 @@ class FS {
errorIfAbsent(file)
- log.debug ( "Deleting file: $file" )
+ log.debug("Deleting file: $file")
Files.delete(file)
}
@@ -191,17 +191,17 @@ class FS {
Files.walkFileTree(dir, object : SimpleFileVisitor() {
override fun visitFile(file: Path, p1: BasicFileAttributes): FileVisitResult {
- log.debug ( "Deleting file: $file" )
+ log.debug("Deleting file: $file")
Files.delete(file)
return FileVisitResult.CONTINUE
}
- override fun postVisitDirectory(dir: Path, e: IOException?): FileVisitResult {
+ override fun postVisitDirectory(directory: Path, e: IOException?): FileVisitResult {
if (e == null) {
- log.debug ( "Deleting directory: $dir" )
+ log.debug("Deleting directory: $directory")
- Files.delete(dir)
+ Files.delete(directory)
return FileVisitResult.CONTINUE
} else {
throw e
diff --git a/fxgl/src/main/kotlin/com/almasb/fxgl/scene/SceneFactory.kt b/fxgl/src/main/kotlin/com/almasb/fxgl/scene/SceneFactory.kt
index 6d4da9ea2..7ad656b02 100644
--- a/fxgl/src/main/kotlin/com/almasb/fxgl/scene/SceneFactory.kt
+++ b/fxgl/src/main/kotlin/com/almasb/fxgl/scene/SceneFactory.kt
@@ -47,12 +47,7 @@ open class SceneFactory {
* @return main menu
*/
open fun newMainMenu(app: GameApplication): FXGLMenu {
- when (app.settings.menuStyle) {
- MenuStyle.GTA5 -> return GTAVMenu(app, MenuType.MAIN_MENU)
- MenuStyle.CCTR -> return CCTRMenu(app, MenuType.MAIN_MENU)
- MenuStyle.WARCRAFT3 -> return Warcraft3Menu(app, MenuType.MAIN_MENU)
- else -> return FXGLDefaultMenu(app, MenuType.MAIN_MENU)
- }
+ return FXGLDefaultMenu(app, MenuType.MAIN_MENU)
}
/**
@@ -63,11 +58,6 @@ open class SceneFactory {
* @return game menu
*/
open fun newGameMenu(app: GameApplication): FXGLMenu {
- when (app.settings.menuStyle) {
- MenuStyle.GTA5 -> return GTAVMenu(app, MenuType.GAME_MENU)
- MenuStyle.CCTR -> return CCTRMenu(app, MenuType.GAME_MENU)
- MenuStyle.WARCRAFT3 -> return Warcraft3Menu(app, MenuType.GAME_MENU)
- else -> return FXGLDefaultMenu(app, MenuType.GAME_MENU)
- }
+ return FXGLDefaultMenu(app, MenuType.GAME_MENU)
}
}
\ No newline at end of file
diff --git a/fxgl/src/main/kotlin/com/almasb/fxgl/scene/Viewport.kt b/fxgl/src/main/kotlin/com/almasb/fxgl/scene/Viewport.kt
index e34bd8968..39bffaae1 100644
--- a/fxgl/src/main/kotlin/com/almasb/fxgl/scene/Viewport.kt
+++ b/fxgl/src/main/kotlin/com/almasb/fxgl/scene/Viewport.kt
@@ -6,6 +6,8 @@
package com.almasb.fxgl.scene
+import com.almasb.fxgl.core.math.FXGLMath
+import com.almasb.fxgl.core.math.Vec2
import com.almasb.fxgl.ecs.Entity
import com.almasb.fxgl.entity.component.BoundingBoxComponent
import com.almasb.fxgl.entity.component.PositionComponent
@@ -16,7 +18,7 @@ import javafx.geometry.Point2D
import javafx.geometry.Rectangle2D
/**
- * Game scene viewport.
+ * Scene viewport.
*
* @author Almas Baimagambetov (AlmasB) (almaslvl@gmail.com)
*/
@@ -170,4 +172,40 @@ class Viewport
this.maxX.set(maxX)
this.maxY.set(maxY)
}
+
+ // adapted from https://gamedev.stackexchange.com/questions/1828/realistic-camera-screen-shake-from-explosion
+ private var shakePower = 0.0f
+ private var shakeAngle = 0.0f
+ private val originBeforeShake = Vec2()
+ private val offset = Vec2()
+ private var shake = false
+
+ fun shake(power: Double) {
+ shakePower = power.toFloat()
+ shakeAngle = FXGLMath.random() * FXGLMath.PI2
+
+ // only record origin if not shaking, so that we don't record 'false' origin
+ if (!shake)
+ originBeforeShake.set(x.floatValue(), y.floatValue())
+
+ shake = true
+ }
+
+ fun onUpdate(tpf: Double) {
+ if (!shake)
+ return
+
+ shakePower *= 0.9f * 60 * tpf.toFloat()
+ shakeAngle += 180 + FXGLMath.random() * FXGLMath.PI2 / 6
+ offset.set(shakePower * FXGLMath.cos(shakeAngle), shakePower * FXGLMath.sin(shakeAngle))
+
+ setX(offset.x + originBeforeShake.x.toDouble())
+ setY(offset.y + originBeforeShake.y.toDouble())
+
+ if (FXGLMath.abs(offset.x) < 0.5 && FXGLMath.abs(offset.y) < 0.5) {
+ setX(originBeforeShake.x.toDouble())
+ setY(originBeforeShake.y.toDouble())
+ shake = false
+ }
+ }
}
\ No newline at end of file
diff --git a/fxgl/src/main/kotlin/com/almasb/fxgl/ui/LevelText.kt b/fxgl/src/main/kotlin/com/almasb/fxgl/ui/LevelText.kt
new file mode 100644
index 000000000..bfa161da8
--- /dev/null
+++ b/fxgl/src/main/kotlin/com/almasb/fxgl/ui/LevelText.kt
@@ -0,0 +1,61 @@
+/*
+ * FXGL - JavaFX Game Library. The MIT License (MIT).
+ * Copyright (c) AlmasB (almaslvl@gmail.com).
+ * See LICENSE for details.
+ */
+
+package com.almasb.fxgl.ui
+
+import com.almasb.fxgl.animation.ParallelAnimation
+import com.almasb.fxgl.app.FXGL
+import javafx.geometry.Point2D
+import javafx.scene.layout.StackPane
+import javafx.scene.paint.Color
+import javafx.scene.paint.CycleMethod
+import javafx.scene.paint.LinearGradient
+import javafx.scene.paint.Stop
+import javafx.scene.shape.Rectangle
+import javafx.scene.text.Text
+import javafx.util.Duration
+
+/**
+ * By default level text is invisible and [animateIn] is used to reveal the text
+ * via an animation.
+ * Then [animateOut] can be used to make this node invisible again.
+ *
+ * @author Almas Baimagambetov (almaslvl@gmail.com)
+ */
+class LevelText(levelName: String) : StackPane() {
+
+ private val text: Text = FXGL.getUIFactory().newText(levelName, Color.WHITESMOKE, 46.0)
+ private val bg = Rectangle(text.layoutBounds.width + 100, text.layoutBounds.height + 20)
+
+ init {
+ translateX = FXGL.getAppWidth() / 2 - text.layoutBounds.width / 2
+ translateY = FXGL.getAppHeight() / 3.0
+ opacity = 0.0
+
+ with(bg) {
+ arcWidth = 35.0
+ arcHeight = 35.0
+ fill = LinearGradient(0.0, 0.0, 1.0, 0.0, true, CycleMethod.NO_CYCLE,
+ Stop(0.0, Color.TRANSPARENT),
+ Stop(0.5, Color.color(0.0, 0.0, 0.0, 0.85)),
+ Stop(1.0, Color.TRANSPARENT)
+ )
+ }
+
+ children.addAll(bg, text)
+ }
+
+ fun animateIn() {
+ ParallelAnimation(
+ FXGL.getUIFactory().fadeIn(this@LevelText, Duration.seconds(1.0)),
+ FXGL.getUIFactory().translate(text, Point2D(-20.0, 0.0), Point2D.ZERO, Duration.seconds(1.0))
+ ).startInPlayState()
+ }
+
+ fun animateOut() {
+ FXGL.getUIFactory().fadeOut(this, Duration.seconds(1.0)).startInPlayState()
+ }
+}
\ No newline at end of file
diff --git a/fxgl/src/main/resources/com/almasb/fxgl/app/system.properties b/fxgl/src/main/resources/com/almasb/fxgl/app/system.properties
index 48e31d868..70f78af4a 100644
--- a/fxgl/src/main/resources/com/almasb/fxgl/app/system.properties
+++ b/fxgl/src/main/resources/com/almasb/fxgl/app/system.properties
@@ -6,6 +6,7 @@
#
# FXGL System Properties
+# Mostly used to change things that don't require recompilation, e.g. sounds / css / fonts
#
# Supported value types:
@@ -51,8 +52,8 @@ sound.menu.press = menu/press.wav
ui.font = Copperplate_Gothic_Light_Regular.ttf
ui.mono.font = lucida_console.ttf
-# default CSS loaded from ui/css, empty
-ui.css = fxgl.css
+# default CSS loaded from ui/css
+ui.css = fxgl_dark.css
# default icon loaded from ui/icons, this is displayed in task bar and window title
ui.icon.name = fxgl_icon.png
diff --git a/fxgl/src/test/java/com/almasb/fxgl/app/MockGameApplication.java b/fxgl/src/test/java/com/almasb/fxgl/app/MockGameApplication.java
index cb5bf6e04..76d1eb9d1 100644
--- a/fxgl/src/test/java/com/almasb/fxgl/app/MockGameApplication.java
+++ b/fxgl/src/test/java/com/almasb/fxgl/app/MockGameApplication.java
@@ -5,7 +5,6 @@
*/
package com.almasb.fxgl.app;
-import com.almasb.fxgl.scene.menu.MenuStyle;
import com.almasb.fxgl.service.ServiceType;
import com.almasb.fxgl.settings.GameSettings;
import com.almasb.fxgl.util.Credits;
@@ -41,7 +40,6 @@ protected void initSettings(GameSettings settings) {
settings.setProfilingEnabled(false);
settings.setCloseConfirmation(false);
settings.setMenuKey(KeyCode.ENTER);
- settings.setMenuStyle(MenuStyle.CCTR);
settings.setCredits(new Credits(Arrays.asList("TestCredit1", "TestCredit2")));
settings.addServiceType(new ServiceType() {
@Override
diff --git a/fxgl/src/test/java/com/almasb/fxgl/ecs/EntityTest.java b/fxgl/src/test/java/com/almasb/fxgl/ecs/EntityTest.java
index 51e5623a7..fb2f62dc4 100644
--- a/fxgl/src/test/java/com/almasb/fxgl/ecs/EntityTest.java
+++ b/fxgl/src/test/java/com/almasb/fxgl/ecs/EntityTest.java
@@ -389,6 +389,7 @@ public void testActiveCallbacks() {
assertThat(hp.value, is(30.0));
world.removeEntity(entity);
+ world.onUpdate(0);
assertThat(hp.value, is(-50.0));
assertFalse(entity.activeProperty().get());
@@ -413,6 +414,7 @@ public void testIntegrity() {
world.addEntity(entity);
world.removeEntity(entity);
+ world.onUpdate(0);
try {
entity.addComponent(new HPComponent(23));
diff --git a/fxgl/src/test/kotlin/com/almasb/fxgl/animation/AnimatedValueTest.kt b/fxgl/src/test/kotlin/com/almasb/fxgl/animation/AnimatedValueTest.kt
new file mode 100644
index 000000000..8e782545e
--- /dev/null
+++ b/fxgl/src/test/kotlin/com/almasb/fxgl/animation/AnimatedValueTest.kt
@@ -0,0 +1,36 @@
+/*
+ * FXGL - JavaFX Game Library. The MIT License (MIT).
+ * Copyright (c) AlmasB (almaslvl@gmail.com).
+ * See LICENSE for details.
+ */
+
+package com.almasb.fxgl.animation
+
+import javafx.geometry.Point2D
+import org.hamcrest.CoreMatchers.`is`
+import org.junit.Assert.assertThat
+import org.junit.Test
+
+/**
+ * @author Almas Baimagambetov (almaslvl@gmail.com)
+ */
+class AnimatedValueTest {
+
+ @Test
+ fun `Point2D`() {
+ val anim = AnimatedPoint2D(Point2D(0.0, 0.0), Point2D(100.0, 100.0))
+
+ assertThat(anim.getValue(0.0), `is`(Point2D(0.0, 0.0)))
+ assertThat(anim.getValue(1.0), `is`(Point2D(100.0, 100.0)))
+ assertThat(anim.getValue(0.5), `is`(Point2D(50.0, 50.0)))
+ }
+
+ @Test
+ fun `Double`() {
+ val anim = AnimatedValue(100.0, 200.0)
+
+ assertThat(anim.getValue(0.0), `is`(100.0))
+ assertThat(anim.getValue(1.0), `is`(200.0))
+ assertThat(anim.getValue(0.5), `is`(150.0))
+ }
+}
\ No newline at end of file
diff --git a/fxgl/src/test/kotlin/com/almasb/fxgl/ecs/GameWorldTest.kt b/fxgl/src/test/kotlin/com/almasb/fxgl/ecs/GameWorldTest.kt
index e554d2090..096f82fad 100644
--- a/fxgl/src/test/kotlin/com/almasb/fxgl/ecs/GameWorldTest.kt
+++ b/fxgl/src/test/kotlin/com/almasb/fxgl/ecs/GameWorldTest.kt
@@ -10,6 +10,7 @@ import com.almasb.fxgl.annotation.Spawns
import com.almasb.fxgl.app.FXGL
import com.almasb.fxgl.app.MockApplicationModule
import com.almasb.fxgl.core.collection.Array
+import com.almasb.fxgl.ecs.component.IrremovableComponent
import com.almasb.fxgl.entity.*
import com.almasb.fxgl.entity.component.IDComponent
import com.almasb.fxgl.entity.component.PositionComponent
@@ -292,6 +293,17 @@ class GameWorldTest {
gameWorld.removeEntity(e)
}
+ @Test
+ fun `Do not remove if entity has IrremovableComponent`() {
+ val e = Entity()
+ e.addComponent(IrremovableComponent())
+
+ gameWorld.addEntity(e)
+ gameWorld.removeEntity(e)
+
+ assertThat(gameWorld.entities, hasItems(e))
+ }
+
@Test
fun `Remove multiple entities`() {
assertThat(gameWorld.entities, hasItems(e1, e2))
@@ -304,11 +316,22 @@ class GameWorldTest {
fun `Reset`() {
assertThat(gameWorld.entities.size, `is`(not(0)))
- gameWorld.reset()
+ gameWorld.clear()
assertThat(gameWorld.entities.size, `is`(0))
}
+ @Test
+ fun `Reset does not remove if entity has IrremovableComponent`() {
+ val e = Entity()
+ e.addComponent(IrremovableComponent())
+
+ gameWorld.addEntity(e)
+ gameWorld.clear()
+
+ assertThat(gameWorld.entities, hasItems(e))
+ }
+
@Test
fun `Set level`() {
val e = Entity()
diff --git a/fxgl/src/test/kotlin/com/almasb/fxgl/io/FSTest.kt b/fxgl/src/test/kotlin/com/almasb/fxgl/io/FSTest.kt
new file mode 100644
index 000000000..31daf2098
--- /dev/null
+++ b/fxgl/src/test/kotlin/com/almasb/fxgl/io/FSTest.kt
@@ -0,0 +1,70 @@
+package com.almasb.fxgl.io
+
+import com.almasb.fxgl.app.FXGL
+import com.almasb.fxgl.app.MockApplicationModule
+import org.hamcrest.CoreMatchers
+import org.hamcrest.CoreMatchers.hasItems
+import org.junit.AfterClass
+import org.junit.Assert
+import org.junit.Assert.*
+import org.junit.BeforeClass
+import org.junit.Test
+import java.nio.file.Files
+import java.nio.file.Paths
+
+/**
+ * @author Almas Baimagambetov (almaslvl@gmail.com)
+ */
+class FSTest {
+
+ companion object {
+ @BeforeClass
+ @JvmStatic fun before() {
+ cleanUp()
+
+ Files.createDirectories(Paths.get("testdir"))
+ Files.createDirectories(Paths.get("testdir/testsubdir"))
+
+ Files.createFile(Paths.get("testdir/testfile.txt"))
+ Files.createFile(Paths.get("testdir/testfile.json"))
+
+ assertTrue("test file is not present before", Files.exists(Paths.get("testdir/testfile.txt")))
+ assertTrue("test file is not present before", Files.exists(Paths.get("testdir/testfile.json")))
+ }
+
+ @AfterClass
+ @JvmStatic fun cleanUp() {
+ // ensure previous tests have been cleared
+ Files.deleteIfExists(Paths.get("testdir/testfile.txt"))
+ Files.deleteIfExists(Paths.get("testdir/testfile.json"))
+ Files.deleteIfExists(Paths.get("testdir/testsubdir"))
+ Files.deleteIfExists(Paths.get("testdir/somefile"))
+ Files.deleteIfExists(Paths.get("testdir"))
+
+ assertTrue("test dir is present before", !Files.exists(Paths.get("testdir")))
+ }
+ }
+
+ @Test
+ fun `Load file names from a dir`() {
+ val fileNames = FS.loadFileNamesTask("testdir", false).execute()
+
+ assertThat(fileNames, hasItems("testfile.txt", "testfile.json"))
+ }
+
+ @Test
+ fun `Load dir names from a dir`() {
+ val dirNames = FS.loadDirectoryNamesTask("testdir", false).execute()
+
+ assertThat(dirNames, hasItems("testsubdir"))
+ }
+
+ @Test
+ fun `File not present after delete`() {
+ Files.createFile(Paths.get("testdir/somefile"))
+ assertTrue(Files.exists(Paths.get("testdir/somefile")))
+
+ FS.deleteFileTask("testdir/somefile").execute()
+ assertFalse(Files.exists(Paths.get("testdir/somefile")))
+ }
+}
\ No newline at end of file
diff --git a/fxgl/src/test/kotlin/com/almasb/fxgl/settings/GameSettingsTest.kt b/fxgl/src/test/kotlin/com/almasb/fxgl/settings/GameSettingsTest.kt
index c53cebd5c..33b65082e 100644
--- a/fxgl/src/test/kotlin/com/almasb/fxgl/settings/GameSettingsTest.kt
+++ b/fxgl/src/test/kotlin/com/almasb/fxgl/settings/GameSettingsTest.kt
@@ -70,7 +70,6 @@ class GameSettingsTest {
assertThat(settings.profilingEnabled, `is`(false))
assertThat(settings.closeConfirmation, `is`(false))
assertThat(settings.menuKey, `is`(KeyCode.ENTER))
- assertThat(settings.menuStyle, `is`(com.almasb.fxgl.scene.menu.MenuStyle.CCTR))
assertThat(settings.credits.list, hasItems("TestCredit1", "TestCredit2"))
assertThat(settings.appMode, `is`(ApplicationMode.RELEASE))
}
diff --git a/pom.xml b/pom.xml
index 68e3e0ed0..09097bed0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
com.github.almasb
fxgl-framework
- 0.3.5
+ 0.3.6
pom
${project.groupId}:${project.artifactId}
@@ -12,11 +12,14 @@
http://almasb.github.io/FXGL/
+
fxgl
-
+
+ fxgl-extra
+
+
fxgl-samples
- fxgl-games
@@ -72,8 +75,8 @@
4.5.3
- 1.1.2-2
- 0.14.1
+ 1.1.3-2
+ 0.16
4.1.0
8.0-r5
8.40.12