Jose Martinez avatar Jose Martinez committed f2ec8e3

- Moved Path from LevelPanel variable to a wave variable. So LevelPanel no longer hold an instance to Path.
- LevelPanel gets Wave's from setter instead of constructor.
- Path is still created in FirstLevel but is passed to Wave along with the path distance.
- Wave sets the Path and path distance onto the Enemy
- Enemy uses speed variable and path distance to figure out what the duration of the PathTransition should be.
- Enemy moves itsself along its Path in the pulse method using PathTransition's interpolate method.

Comments (0)

Files changed (8)

defender/src/main/java/com/fxexperience/games/defender/games/simple/Enemy.java

  */
 package com.fxexperience.games.defender.games.simple;
 
+import javafx.animation.Interpolator;
+import javafx.animation.PathTransition;
 import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.ReadOnlyDoubleProperty;
 import javafx.beans.property.ReadOnlyDoubleWrapper;
 import javafx.geometry.Point2D;
 import javafx.scene.Parent;
 import javafx.scene.shape.Path;
+import javafx.util.Duration;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * An enemy. Has life, speed, and some path that it is to follow. It might have different
- * buffs depending on what type of weapon hits it, proximity to other enemies (they might
- * make each other stronger) or whatever else you might come up with.
+ * An enemy. Has life, speed, and some path that it is to follow. It might have
+ * different buffs depending on what type of weapon hits it, proximity to other
+ * enemies (they might make each other stronger) or whatever else you might come
+ * up with.
  */
 public class Enemy extends Parent implements GamePulseListener {
 
     private static final Logger log = LoggerFactory.getLogger(Enemy.class);
-
-
     /**
      * The maximum life of this enemy.
      */
     private final double maximumLife;
-    public final double getMaximumLife() { return maximumLife; }
 
+    public final double getMaximumLife() {
+        return maximumLife;
+    }
     /**
-     * The Path that the enemy must follow. If the enemy reaches the end of the Path without being destroyed then
-     * the player loses a life and the enemy destroys itself.
+     * The Path that the enemy must follow. If the enemy reaches the end of the
+     * Path without being destroyed then the player loses a life and the enemy
+     * destroys itself.
      */
     private final ObjectProperty<Path> path = new SimpleObjectProperty<>(this, "path");
-    public final Path getPath() { return path.get(); }
-    public final void setPath(Path path) { this.path.set(path); }
-    public final ObjectProperty<Path> pathProperty() { return path; }
+    private PathTransition pathTransition;
+    private long startTime = 0;
 
+    public final Path getPath() {
+        return path.get();
+    }
+
+    public final void setPath(Path path, double distance) {
+        this.path.set(path);
+        pathTransition = new PathTransition(Duration.seconds(distance / speed.getValue()), path, this);
+        pathTransition.setInterpolator(Interpolator.LINEAR);
+        pathTransition.setOrientation(PathTransition.OrientationType.ORTHOGONAL_TO_TANGENT);
+    }
+
+    public final ObjectProperty<Path> pathProperty() {
+        return path;
+    }
     /**
-     * The Path that the enemy must follow. If the enemy reaches the end of the Path without being destroyed then
-     * the player loses a life and the enemy destroys itself.
+     * The Path that the enemy must follow. If the enemy reaches the end of the
+     * Path without being destroyed then the player loses a life and the enemy
+     * destroys itself.
      */
     private final ObjectProperty<LevelPanel> level = new SimpleObjectProperty<>(this, "level");
-    public final LevelPanel getLevel() { return level.get(); }
-    public final void setLevel(LevelPanel level) { this.level.set(level); }
-    public final ObjectProperty<LevelPanel> levelProperty() { return level; }
 
+    public final LevelPanel getLevel() {
+        return level.get();
+    }
+
+    public final void setLevel(LevelPanel level) {
+        this.level.set(level);
+    }
+
+    public final ObjectProperty<LevelPanel> levelProperty() {
+        return level;
+    }
     /**
      * When life reaches 0 (or goes negative), the enemy is destroyed.
      */
     private final ReadOnlyDoubleWrapper life = new ReadOnlyDoubleWrapper(this, "life", 100);
-    public final double getLife() { return life.get(); }
-    public final ReadOnlyDoubleProperty lifeProperty() { return life.getReadOnlyProperty(); }
 
+    public final double getLife() {
+        return life.get();
+    }
+
+    public final ReadOnlyDoubleProperty lifeProperty() {
+        return life.getReadOnlyProperty();
+    }
     /**
      * The natural speed at which the enemy moves. This is the speed at which
      * the enemy will move in the absence of any buffs.
      */
     private final double naturalSpeed;
-    public final double getNaturalSpeed() { return naturalSpeed; }
 
+    public final double getNaturalSpeed() {
+        return naturalSpeed;
+    }
     /**
      * The speed at which the enemy is moving. This can go up or down depending
-     * on different game dynamics (for example, a tower with some "slow down" buff
-     * could cause the speed to decrease for some period of time).
+     * on different game dynamics (for example, a tower with some "slow down"
+     * buff could cause the speed to decrease for some period of time).
      */
     private final ReadOnlyDoubleWrapper speed = new ReadOnlyDoubleWrapper(this, "speed");
-    public final double getSpeed() { return speed.get(); }
-    public final ReadOnlyDoubleProperty speedProperty() { return speed.getReadOnlyProperty(); }
 
+    public final double getSpeed() {
+        return speed.get();
+    }
+
+    public final ReadOnlyDoubleProperty speedProperty() {
+        return speed.getReadOnlyProperty();
+    }
     /**
      * Called when the enemy dies.
      */
     private ObjectProperty<EventHandler<ActionEvent>> onDeath = new SimpleObjectProperty<>(this, "onDeath");
 
-    public final EventHandler<ActionEvent> getOnDeath() { return onDeath.get(); }
-    public final void setOnDeath(EventHandler<ActionEvent> value) { onDeath.set(value); }
-    public final ObjectProperty<EventHandler<ActionEvent>> onDeathProperty() { return onDeath; }
+    public final EventHandler<ActionEvent> getOnDeath() {
+        return onDeath.get();
+    }
+
+    public final void setOnDeath(EventHandler<ActionEvent> value) {
+        onDeath.set(value);
+    }
+
+    public final ObjectProperty<EventHandler<ActionEvent>> onDeathProperty() {
+        return onDeath;
+    }
 
     /**
-     * Creates a new Enemy. You must specify the natural speed of the enemy and its max life.
+     * Creates a new Enemy. You must specify the natural speed of the enemy and
+     * its max life.
+     *
      * @param maximumLife must be positive
      * @param naturalSpeed must be positive. Measured in pixels per second.
      */
     }
 
     // ... etc
-
-
     @Override
     public void pulse() {
+        if (this.startTime == 0) {
+            startTime = System.currentTimeMillis();
+            pathTransition.playFromStart();
+            pathTransition.pause();
+        }
 
-        // todo move forward along the path - currently dodgy
-
-        setTranslateX(getTranslateX() + 1);
-        setTranslateY(getTranslateY() + 1);
-
-        if (getTranslateX() > 800) {
+        double fraction = (System.currentTimeMillis() - startTime) / pathTransition.getDuration().toMillis();
+        pathTransition.interpolate(fraction);
+        if (fraction >= 1.0) {
             destinationReached();
         }
     }
     public Point2D getCenter() {
         Bounds bounds = getBoundsInParent();
         return new Point2D(
-                bounds.getMinX() + (bounds.getWidth()/2.0),
-                bounds.getMinY() + (bounds.getHeight()/2.0));
+                bounds.getMinX() + (bounds.getWidth() / 2.0),
+                bounds.getMinY() + (bounds.getHeight() / 2.0));
     }
 
     protected void destinationReached() {

defender/src/main/java/com/fxexperience/games/defender/games/simple/LevelPanel.java

 import java.util.Arrays;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.ListIterator;
+import javafx.scene.shape.LineTo;
+import javafx.scene.shape.MoveTo;
 
 /**
- * This UI represents the game board. It has all the tiles, towers, enemies, background, and so forth.
- * The GameControlPanelUI is separate, and has the money values etc. A single Level has a single path
- * (or set of paths) on which the enemies roam. Multiple waves of enemies will come following one or
- * more of those paths.
+ * This UI represents the game board. It has all the tiles, towers, enemies,
+ * background, and so forth. The GameControlPanelUI is separate, and has the
+ * money values etc. A single Level has a single path (or set of paths) on which
+ * the enemies roam. Multiple waves of enemies will come following one or more
+ * of those paths.
  */
 public class LevelPanel extends Region implements GamePulseListener {
+
     private static final Logger log = LoggerFactory.getLogger(LevelPanel.class);
-
     private static final double LEVEL_HEIGHT = 600;
     private static final double LEVEL_WIDTH = 800;
-
     private Region background;
     private Rectangle clip;
     private Pane enemyLayer;
     private Pane towerLayer;
     private Pane projectileLayer;
-
     private LinkedList<Wave> waves;
     private Wave wave;
     private Path path;
     private List<Tower> towers;
     private List<Projectile> projectiles;
 
-    public LevelPanel(final Path path, Wave... waves) {
-
+    public LevelPanel() {
         getStyleClass().add("game-board");
-        this.waves = new LinkedList<>(Arrays.asList(waves));
-        this.path = path;
 
         background = new Region();
         background.getStyleClass().add("background");
             if (!waves.isEmpty()) {
                 wave = waves.remove();
                 wave.setLevel(this);
-                wave.setPath(path);
             } else {
                 // todo spawning finished - when the remaining enemies are done we have finished the level...
                 log.info("No more enemies to spawn");
         projectileLayer.getChildren().remove(projectile);
     }
 
+    ///////////////////////////////////////////////////////////////////////////
+    //protected functions
+    protected final double getPathDistance(Point2D[] points) {
+        Point2D p2 = null;
+        double distance = 0;
+        for (Point2D p1 : points) {
+            if (p2 != null) {
+                distance += Math.sqrt(Math.pow((p2.getX() - p1.getX()), 2) + Math.pow((p2.getY() - p1.getY()), 2));
+            }
+            p2 = p1;
+        }
+        return distance;
+    }
+
+    //...output:  a path that consists of all the points
+    protected Path getPath(Point2D... points) {
+        Path _path = new Path();
+        boolean firstPoint = true;
+        for (Point2D pnt : points) {
+            if (firstPoint) {
+                MoveTo mt = new MoveTo(pnt.getX(), pnt.getY());
+                _path.getElements().add(mt);
+                firstPoint = false;
+            } else {
+                _path.getElements().add(new LineTo(pnt.getX(), pnt.getY()));
+            }
+        }
+        return _path;
+    }
+
+    protected final void setPath(Path path) {
+        this.path = path;
+    }
+
+    protected final void setWaves(Wave... waves) {
+        this.waves = new LinkedList<>(Arrays.asList(waves));
+    }
+
     @Override
     protected double computeMinWidth(double v) {
         return computePrefWidth(v);

defender/src/main/java/com/fxexperience/games/defender/games/simple/Wave.java

 package com.fxexperience.games.defender.games.simple;
 
+import java.util.Arrays;
+import java.util.LinkedList;
 import javafx.scene.shape.Path;
 import javafx.util.Duration;
 
-import java.util.Arrays;
-import java.util.LinkedList;
+public class Wave implements GamePulseListener {
 
-
-public class Wave implements GamePulseListener {
     private final LinkedList<Enemy> enemies = new LinkedList<>();
     private Path path;
     private final Duration delay;
     private LevelPanel level;
     private long lastSpawn;
+    private double pathDistance;
 
-    public Wave(Duration delay, Enemy... enemies) {
+    public Wave(Path path, Duration delay, double pathDistance, Enemy... enemies) {
+        this.path = path;
         this.delay = delay;
+        this.pathDistance = pathDistance;
         this.enemies.addAll(Arrays.asList(enemies));
+        for (Enemy e : enemies) {
+            e.setPath(path, pathDistance);
+        }
     }
 
     public void setLevel(LevelPanel level) {
         this.level = level;
     }
 
-    public void setPath(Path path) {
-        this.path = path;
-    }
-
     @Override
     public void pulse() {
         if (level != null) {
     protected void spawn() {
         if (!enemies.isEmpty()) {
             Enemy enemy = enemies.remove();
-            enemy.setPath(path);
             level.addEnemy(enemy);
         }
     }

defender/src/main/java/com/fxexperience/games/defender/games/simple/enemies/BlackEnemy.java

  */
 public class BlackEnemy extends Enemy {
     public BlackEnemy() {
-        super(20, 4);
+        super(20, 40);
         Region r = new Region();
         r.getStyleClass().addAll("enemy", "black-enemy");
         getChildren().add(r);

defender/src/main/java/com/fxexperience/games/defender/games/simple/enemies/BlueEnemy.java

  */
 public class BlueEnemy extends Enemy {
     public BlueEnemy() {
-        super(100, 10);
+        super(100, 60);
         Region r = new Region();
         r.getStyleClass().addAll("enemy", "blue-enemy");
         getChildren().add(r);

defender/src/main/java/com/fxexperience/games/defender/games/simple/enemies/GreenEnemy.java

 
 public class GreenEnemy extends Enemy {
     public GreenEnemy() {
-        super(300, 30);
+        super(300, 70);
         Region r = new Region();
         r.getStyleClass().addAll("enemy", "green-enemy");
         getChildren().add(r);

defender/src/main/java/com/fxexperience/games/defender/games/simple/enemies/SpriteEnemy.java

  */
 public class SpriteEnemy extends Enemy {
 
+    //////////////////////////////////////////////////////////////////////////
+    //variables
+    
+    ///////////////////////////////////////////////////////////////////////////
+    //constructor
     public SpriteEnemy() {
-        super(300, 10);
-
+        super(300, 70);
         AnimatedImageNode animatedNode = new AnimatedImageNode(new Image("flier.png"), 80, 63, 2, 20);
         getChildren().add(animatedNode);
         animatedNode.playFromStart();

defender/src/main/java/com/fxexperience/games/defender/games/simple/levels/FirstLevel.java

 import com.fxexperience.games.defender.games.simple.enemies.BlueEnemy;
 import com.fxexperience.games.defender.games.simple.enemies.GreenEnemy;
 import com.fxexperience.games.defender.games.simple.enemies.SpriteEnemy;
+import javafx.geometry.Point2D;
 import javafx.scene.shape.LineTo;
 import javafx.scene.shape.MoveTo;
 import javafx.scene.shape.Path;
 import javafx.util.Duration;
 
 public class FirstLevel extends LevelPanel {
+
     public FirstLevel() {
-        super(new Path(
-                new MoveTo(-20, -20),
-                new LineTo(100, 100),
-                new LineTo(600, 150),
-                new LineTo(640, 300),
-                new LineTo(100, 340),
-                new LineTo(1024, 768)
-        ), new Wave(
-                Duration.seconds(1),
+
+
+        Point2D[] points = {
+            new Point2D(-20, -20),
+            new Point2D(100, 100),
+            new Point2D(600, 150),
+            new Point2D(640, 300),
+            new Point2D(100, 340),
+            new Point2D(1024, 768)
+        };
+
+        double pathDist = getPathDistance(points);
+        Path path = getPath(points);
+
+        this.setWaves(new Wave(
+                path, Duration.seconds(1), pathDist,
                 new SpriteEnemy(),
                 new SpriteEnemy(),
                 new SpriteEnemy(),
                 new SpriteEnemy(),
                 new SpriteEnemy(),
                 new SpriteEnemy(),
-                new SpriteEnemy()
-        ), new Wave(
-                Duration.seconds(1),
+                new SpriteEnemy()),
+                new Wave(
+                path, Duration.seconds(2), pathDist,
                 new GreenEnemy(),
                 new GreenEnemy(),
                 new GreenEnemy(),
                 new GreenEnemy(),
                 new GreenEnemy(),
                 new GreenEnemy(),
-                new GreenEnemy()
-        ), new Wave(
-                Duration.seconds(3),
+                new GreenEnemy()),
+                new Wave(
+                path, Duration.seconds(3), pathDist,
                 new BlackEnemy(),
                 new BlackEnemy(),
                 new BlackEnemy(),
                 new BlackEnemy(),
                 new BlackEnemy(),
                 new BlackEnemy(),
-                new BlackEnemy()
-        ), new Wave(
-                Duration.seconds(1),
+                new BlackEnemy()), new Wave(
+                path, Duration.seconds(1), pathDist,
                 new BlackEnemy(),
                 new BlackEnemy(),
                 new GreenEnemy(),
                 new BlackEnemy(),
                 new BlueEnemy(),
                 new BlueEnemy(),
-                new BlackEnemy()
-        ));
+                new BlackEnemy()));
     }
+    ///////////////////////////////////////////////////////////////////////////
+    //private functions
+    //
 }
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.