From 60dc5a1aa18d7e9eac5dd870b1375e590e12d785 Mon Sep 17 00:00:00 2001 From: Andrew Dey Date: Fri, 6 May 2022 20:14:38 -0400 Subject: [PATCH] Add late-behavior initialization (#201) - (#172) add mid-game init calls --- .../examples/bullethell/scenes/GameScene.java | 3 +- .../bullethell/scripts/PlayerCannon.java | 4 +- .../java/tech/fastj/engine/FastJEngine.java | 9 ++-- .../tech/fastj/engine/internals/Timer.java | 11 ++--- .../tech/fastj/graphics/game/GameObject.java | 27 ++++++++++-- .../graphics/game/GameObjectTests.java | 41 +++++++++++++++++++ 6 files changed, 76 insertions(+), 19 deletions(-) diff --git a/examples/java/tech/fastj/examples/bullethell/scenes/GameScene.java b/examples/java/tech/fastj/examples/bullethell/scenes/GameScene.java index 87151b28..2406ce2e 100644 --- a/examples/java/tech/fastj/examples/bullethell/scenes/GameScene.java +++ b/examples/java/tech/fastj/examples/bullethell/scenes/GameScene.java @@ -165,7 +165,6 @@ private void newWave() { enemyCount = calculateEnemyCount(wave); for (int i = 0; i < enemyCount; i++) { Model2D enemy = createEnemy(); - enemy.initBehaviors(); enemies.put(enemy.getID(), enemy); } @@ -183,7 +182,7 @@ private Model2D createEnemy() { ); Model2D enemy = Model2D.fromPolygons(ModelUtil.loadModel(Path.of(FilePaths.PathToResources + "enemy.psdf"))); - enemy.addBehavior(new EnemyMovement(this), this); + enemy.addLateBehavior(new EnemyMovement(this), this); enemy.setTranslation(randomPosition); drawableManager.addGameObject(enemy); return enemy; diff --git a/examples/java/tech/fastj/examples/bullethell/scripts/PlayerCannon.java b/examples/java/tech/fastj/examples/bullethell/scripts/PlayerCannon.java index a33960e0..2b14fe68 100644 --- a/examples/java/tech/fastj/examples/bullethell/scripts/PlayerCannon.java +++ b/examples/java/tech/fastj/examples/bullethell/scripts/PlayerCannon.java @@ -58,12 +58,10 @@ private void createBullet(GameObject player) { Polygon2D bullet = (Polygon2D) Polygon2D.fromPoints(bulletMesh) .setFill(Color.red) - .addBehavior(bulletMovementScript, gameScene) + .addLateBehavior(bulletMovementScript, gameScene) .addTag(Tags.Bullet, gameScene); gameScene.drawableManager.addGameObject(bullet); - bullet.initBehaviors(); - bulletCount++; } diff --git a/src/main/java/tech/fastj/engine/FastJEngine.java b/src/main/java/tech/fastj/engine/FastJEngine.java index 88043ede..05183341 100644 --- a/src/main/java/tech/fastj/engine/FastJEngine.java +++ b/src/main/java/tech/fastj/engine/FastJEngine.java @@ -654,17 +654,16 @@ private static void initEngine() { /** Runs the game loop -- the heart of the engine. */ private static void gameLoop() { float elapsedTime; + float elapsedFixedTime; float accumulator = 0f; float updateInterval = 1f / targetUPS; while (display.getWindow().isVisible()) { elapsedTime = deltaTimer.evalDeltaTime(); - FastJEngine.log("Delta: {}", FastJEngine.getDeltaTime()); accumulator += elapsedTime; while (accumulator >= updateInterval) { - fixedDeltaTimer.evalDeltaTime(); - FastJEngine.log("Fixed Delta: {}", FastJEngine.getFixedDeltaTime()); + elapsedFixedTime = fixedDeltaTimer.evalDeltaTime(); gameManager.fixedUpdate(canvas); gameManager.updateBehaviors(); @@ -677,7 +676,7 @@ private static void gameLoop() { AfterUpdateList.clear(); } - accumulator -= updateInterval; + accumulator -= elapsedFixedTime; } gameManager.processInputEvents(); @@ -712,7 +711,7 @@ private static void gameLoop() { private static void sync() { final float loopSlot = 1f / targetFPS; final double endTime = deltaTimer.getLastTimestamp() + loopSlot; - final double currentTime = deltaTimer.getTime(); + final double currentTime = deltaTimer.getCurrentTime(); if (currentTime < endTime) { try { TimeUnit.MILLISECONDS.sleep((long) ((endTime - currentTime) * 1000L)); diff --git a/src/main/java/tech/fastj/engine/internals/Timer.java b/src/main/java/tech/fastj/engine/internals/Timer.java index 8a9fc5b8..f197f5ad 100644 --- a/src/main/java/tech/fastj/engine/internals/Timer.java +++ b/src/main/java/tech/fastj/engine/internals/Timer.java @@ -1,9 +1,10 @@ package tech.fastj.engine.internals; /** - * Timer that accurately specifies time. + * Timekeeping class, primarily used to track the time between the previous and current game frames. *

- * This class is based on Antonio Hernández Bejarano's Timer class: https://ahbejarano.gitbook.io/lwjglgamedev/ + * This class is based on Antonio Hernández Bejarano's Timer class: + * https://ahbejarano.gitbook.io/lwjglgamedev/ *

* Didn't make too many changes. * @@ -17,7 +18,7 @@ public class Timer { /** Initializes the Timer. */ public void init() { - lastTimestamp = getTime(); + lastTimestamp = getCurrentTime(); deltaTime = 0f; } @@ -26,7 +27,7 @@ public void init() { * * @return The current time (nanoseconds) as a double. */ - public double getTime() { + public double getCurrentTime() { return System.nanoTime() / 1_000_000_000d; } @@ -36,7 +37,7 @@ public double getTime() { * @return The time elapsed since the last time evaluation. */ public float evalDeltaTime() { - double time = getTime(); + double time = getCurrentTime(); deltaTime = (float) (time - lastTimestamp); lastTimestamp = time; return deltaTime; diff --git a/src/main/java/tech/fastj/graphics/game/GameObject.java b/src/main/java/tech/fastj/graphics/game/GameObject.java index 9b496bd7..850b4438 100644 --- a/src/main/java/tech/fastj/graphics/game/GameObject.java +++ b/src/main/java/tech/fastj/graphics/game/GameObject.java @@ -86,12 +86,31 @@ public GameObject addBehavior(Behavior behavior, BehaviorHandler behaviorHandler return this; } + /** + * Adds the specified {@link Behavior} to the {@code GameObject}'s list of {@code Behavior}s, and initializes it. + *

+ * This does not check to make sure the game is running -- it only initializes the given behavior. + *

+ * {@code Behavior}s can be added as many times as needed. + * + * @param behavior {@code Behavior} parameter to be added. + * @param behaviorHandler Handler that the {@code GameObject} will be added to, as a behavior listener. + * @return the {@code GameObject} is returned for method chaining. + */ + public GameObject addLateBehavior(Behavior behavior, BehaviorHandler behaviorHandler) { + behaviors.add(behavior); + behaviorHandler.addBehaviorListener(this); + behavior.init(this); + + return this; + } + /** * Removes the specified {@link Behavior} from the {@code GameObject}'s list of {@code Behavior}s. * * @param behavior {@code Behavior} parameter to be removed from. - * @param behaviorHandler Handler that, if the {@code GameObject} no longer has any Behaviors, the {@code - * GameObject} will be removed from as a behavior listener. + * @param behaviorHandler Handler that, if the {@code GameObject} no longer has any Behaviors, the + * {@code GameObject} will be removed from as a behavior listener. * @return the {@code GameObject} is returned for method chaining. */ public GameObject removeBehavior(Behavior behavior, BehaviorHandler behaviorHandler) { @@ -127,8 +146,8 @@ protected void destroyTheRest(Scene origin) { } /** - * Destroys all references of the {@code GameObject}'s behaviors and removes its references from the {@code - * SimpleManager}. + * Destroys all references of the {@code GameObject}'s behaviors and removes its references from the + * {@code SimpleManager}. * * @param origin {@code SimpleManager} parameter that will have all references to this {@code GameObject} removed. */ diff --git a/src/test/java/unittest/testcases/graphics/game/GameObjectTests.java b/src/test/java/unittest/testcases/graphics/game/GameObjectTests.java index d290539a..433ffdb2 100644 --- a/src/test/java/unittest/testcases/graphics/game/GameObjectTests.java +++ b/src/test/java/unittest/testcases/graphics/game/GameObjectTests.java @@ -141,6 +141,47 @@ void checkDestroyBehaviors_shouldMakePointfNull() { assertNull(mockBehavior.getPointf(), "After destroying the GameObject's behaviors, the Pointf should be null."); } + @Test + void checkInitBehavior_withAddLateBehavior_shouldInitializePointf() { + GameObject gameObject = new MockGameObject(); + MockBehavior mockBehavior = new MockBehavior(); + Scene mockScene = new MockEmptyScene(); + + gameObject.addLateBehavior(mockBehavior, mockScene); + + assertNotNull(mockBehavior.getPointf(), "After initializing the GameObject's behaviors, its Pointf should not be null."); + } + + @Test + void checkUpdateBehavior_withAddLateBehavior_shouldIncrementPointf() { + GameObject gameObject = new MockGameObject(); + MockBehavior mockBehavior = new MockBehavior(); + Scene mockScene = new MockEmptyScene(); + + gameObject.addLateBehavior(mockBehavior, mockScene); + + int expectedIncrement = 15; + for (int i = 0; i < expectedIncrement; i++) { + gameObject.updateBehaviors(); + } + + boolean condition = Maths.floatEquals(expectedIncrement, mockBehavior.getPointf().x) && Maths.floatEquals(expectedIncrement, mockBehavior.getPointf().y); + assertTrue(condition, "After updating, the behavior's Pointf should have incremented."); + } + + @Test + void checkDestroyBehavior_withAddLateBehavior_shouldMakePointfNull() { + GameObject gameObject = new MockGameObject(); + MockBehavior mockBehavior = new MockBehavior(); + Scene mockScene = new MockEmptyScene(); + + gameObject.addLateBehavior(mockBehavior, mockScene); // pointf is not null here + assertNotNull(mockBehavior.getPointf()); + + gameObject.destroyAllBehaviors(); // pointf is null here + assertNull(mockBehavior.getPointf(), "After destroying the GameObject's behaviors, the Pointf should be null."); + } + @Test void tryUpdateBehaviorWithoutInitializing_shouldThrowNullPointerException() { GameObject gameObject = new MockGameObject();