Commits

Anonymous committed 8563187

Moved to a GamePulse style animation.

Comments (0)

Files changed (8)

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

 import javafx.event.ActionEvent;
 import javafx.event.EventHandler;
 import javafx.scene.Parent;
+import javafx.scene.shape.Path;
+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.
  */
-public class Enemy extends Parent {
+public class Enemy extends Parent implements GamePulseListener {
+
+    private static final Logger log = LoggerFactory.getLogger(Enemy.class);
+
+
     /**
      * The maximum life of this enemy.
      */
     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.
+     */
+    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; }
+
+    /**
+     * 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; }
+
+    /**
      * When life reaches 0 (or goes negative), the enemy is destroyed.
      */
     private final ReadOnlyDoubleWrapper life = new ReadOnlyDoubleWrapper(this, "life", 100);
     }
 
     // ... etc
+
+
+    @Override
+    public void pulse() {
+
+        // todo move forward along the path - currently dodgy
+
+        setTranslateX(getTranslateX() + 1);
+        setTranslateY(getTranslateY() + 1);
+
+        if (getTranslateX() > 800) {
+            destinationReached();
+        }
+    }
+
+    protected void destinationReached() {
+        // done
+        log.info("Enemy made it to the destination");
+        // todo handle this better
+        getLevel().removeEnemy(this);
+    }
 }

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

  */
 package com.fxexperience.games.defender.games.simple;
 
-import javafx.animation.Animation;
-import javafx.animation.AnimationTimer;
-import javafx.animation.Interpolator;
-import javafx.animation.PathTransition;
-import javafx.beans.InvalidationListener;
-import javafx.beans.Observable;
-import javafx.collections.ListChangeListener;
-import javafx.event.ActionEvent;
-import javafx.event.EventHandler;
-import javafx.geometry.Bounds;
 import javafx.geometry.Insets;
-import javafx.geometry.Point2D;
 import javafx.scene.Node;
 import javafx.scene.layout.Pane;
 import javafx.scene.layout.Region;
-import javafx.scene.shape.LineTo;
-import javafx.scene.shape.MoveTo;
 import javafx.scene.shape.Path;
 import javafx.scene.shape.Rectangle;
-import javafx.util.Duration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
 
 /**
  * This UI represents the game board. It has all the tiles, towers, enemies, background, and so forth.
  * (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 {
+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;
-    protected Pane enemyLayer;
+    private Pane enemyLayer;
     private Pane towerLayer;
     private Pane projectileLayer;
 
-    private final Game game;
     private LinkedList<Wave> waves;
+    private Wave wave;
     private Path path;
-    private boolean running = false;
+    private List<Enemy> enemies;
+    private List<Tower> towers;
 
-    private Set<Animator> animators = new HashSet<>();
-    private Set<Animation> animations = new HashSet<>();
+    public LevelPanel(final Path path, Wave... waves) {
 
-    public LevelPanel(final Game game, final Path path, Wave... w) {
-        this.game = game;
-        game.paused.set(true); // For safety sake
-        game.paused.addListener(new InvalidationListener() {
-            @Override
-            public void invalidated(Observable observable) {
-                // If we are paused, then stop any timers
-                // If we are not paused, start the timers. If there are no timers, start a wave.
-                if (game.paused.get()) {
-                    for (Animator a : animators) a.pause();
-                    for (Animation a : animations) a.pause();
-                } else if (!running) {
-                    startWave();
-                } else {
-                    for (Animator a : animators) a.start();
-                    for (Animation a : animations) a.play();
-                }
-            }
-        });
         getStyleClass().add("game-board");
-        this.waves = new LinkedList<>(Arrays.asList(w));
+        this.waves = new LinkedList<>(Arrays.asList(waves));
         this.path = path;
 
         background = new Region();
         background.getStyleClass().add("background");
+        enemies = new ArrayList<>();
         enemyLayer = new Pane();
+        towers = new ArrayList<>();
         towerLayer = new Pane();
         projectileLayer = new Pane();
         getChildren().addAll(background, enemyLayer, towerLayer, projectileLayer);
 
         clip = new Rectangle();
         setClip(clip);
-
-        placeTower(); // hack
-
-        // The level is complete when the spawner has finished and all enemies have been destroyed
-        enemyLayer.getChildren().addListener(new ListChangeListener<Node>() {
-            @Override
-            public void onChanged(Change<? extends Node> change) {
-                if (enemyLayer.getChildren().isEmpty() && !running) {
-                    System.out.println("WAVE COMPLETE");
-                    if (waves.isEmpty()) {
-                        System.out.println("LEVEL COMPLETE");
-                    } else {
-                        startWave();
-                    }
-                }
-            }
-        });
     }
 
-    private void startWave() {
-        running = true;
-        final Wave wave = waves.removeFirst();
-        final Animator spawner = new Animator(wave.getDelay()) {
-            @Override
-            protected void pulse() {
-                final Enemy e = wave.nextEnemy();
-                e.setLayoutX(-20);
-                e.setLayoutY(-20);
-                enemyLayer.getChildren().add(e);
-                final PathTransition tx = new PathTransition(Duration.seconds(e.getSpeed()* 3), path, e);
-                tx.setInterpolator(Interpolator.LINEAR);
-                tx.setOrientation(PathTransition.OrientationType.ORTHOGONAL_TO_TANGENT);
-                tx.setOnFinished(new EventHandler<ActionEvent>() {
-                    @Override
-                    public void handle(ActionEvent actionEvent) {
-                        if (!enemyLayer.getChildren().isEmpty()) {
-                            Enemy removed = (Enemy) enemyLayer.getChildren().remove(0); // because the enemy that exited the area is always at the head
-                            assert e == removed;
-                        }
-                        animations.remove(tx);
-                    }
-                });
-                e.setOnDeath(new EventHandler<ActionEvent>() {
-                    @Override
-                    public void handle(ActionEvent actionEvent) {
-                        tx.stop();
-                        enemyLayer.getChildren().remove(e);
-                    }
-                });
-                animations.add(tx);
-                tx.play();
-                if (!wave.hasMoreEnemies()) {
-                    stop();
-                    running = false;
-                    animators.remove(this);
-                }
+    @Override
+    public void pulse() {
+
+        // process waves and spawning
+        if (wave != null && wave.hasMoreEnemies()) {
+            wave.pulse();
+        } else {
+            // todo need more smarts in here around detecting end of wave, level, etc
+            // probably would like to pause at the end of a wave and show a 'success' ui, then next wave only
+            // starts once the player says go
+            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");
+                //
+                //        // The level is complete when the spawner has finished and all enemies have been destroyed
+                //        enemyLayer.getChildren().addListener(new ListChangeListener<Node>() {
+                //            @Override
+                //            public void onChanged(Change<? extends Node> change) {
+                //                if (enemyLayer.getChildren().isEmpty() && !complte) {
+                //                    System.out.println("WAVE COMPLETE");
+                //                    if (waves.isEmpty()) {
+                //                        System.out.println("LEVEL COMPLETE");
+                //                    } else {
+                //                        startWave();
+                //                    }
+                //                }
+                //            }
+                //        });
             }
-        };
-        spawner.start();
-        animators.add(spawner);
+        }
+
+        // pulse all our enemies
+        for (Enemy enemy : enemies) {
+            enemy.pulse();
+        }
+
+        // pulse all our towers
+        for (Tower tower : towers) {
+            tower.pulse();
+        }
     }
 
-    public void placeTower() {
-        final Tower tower = new Tower(){{
-            Region r = new Region();
-            r.getStyleClass().add("tower");
-            getChildren().add(r);
-            setLayoutY(200);
-            setLayoutX(300);
+    public void addEnemy(Enemy enemy) {
+        enemies.add(enemy);
+        enemyLayer.getChildren().add(enemy);
+    }
 
-            final Animator t = new Animator(Duration.seconds(fireRate.get())) {
-                @Override
-                protected void pulse() {
-                    // TODO can enable arbitrarily complex target acquisition logic here. For example,
-                    // the tower should calculate what enemy it can damage the most, and destroy before
-                    // passing outside its bounds.
-                    // Check to see if any enemies are within the range of this guy.
-                    Bounds bounds = getBoundsInParent();
-                    Point2D center = new Point2D(
-                            bounds.getMinX() + (bounds.getWidth()/2.0),
-                            bounds.getMinY() + (bounds.getHeight()/2.0));
+    public void removeEnemy(Enemy enemy) {
+        enemies.remove(enemy);
+        enemyLayer.getChildren().remove(enemy);
+    }
 
-                    List<Enemy> enemies = (List<Enemy>)(List)enemyLayer.getChildren();
-                    for (final Enemy e : enemies) {
-                        Bounds enemyBounds = e.getBoundsInParent();
-                        Point2D enemyCenter = new Point2D(
-                                enemyBounds.getMinX() + (enemyBounds.getWidth()/2.0),
-                                enemyBounds.getMinY() + (enemyBounds.getHeight()/2.0));
-
-                        double x = Math.max(enemyCenter.getX(), center.getX()) - Math.min(enemyCenter.getX(), center.getX());
-                        double y = Math.max(enemyCenter.getX(), center.getX()) - Math.min(enemyCenter.getX(), center.getX());
-                        double hyp = Math.sqrt((x*x) + (y*y));
-                        if (hyp < range.get() + (Math.max(enemyBounds.getHeight(), enemyBounds.getWidth()))) {
-                            // Lock in a firing solution
-                            final Region projectile = new Region();
-                            projectile.getStyleClass().add("bullet");
-                            projectile.setTranslateY(center.getY());
-                            projectile.setTranslateX(center.getX());
-                            projectileLayer.getChildren().add(projectile);
-                            Path firePath = new Path(new MoveTo(center.getX(), center.getY()), new LineTo(enemyCenter.getX(), enemyCenter.getY()));
-                            final PathTransition tx = new PathTransition(Duration.seconds(.2), firePath, projectile);
-                            tx.setOnFinished(new EventHandler<ActionEvent>() {
-                                @Override
-                                public void handle(ActionEvent actionEvent) {
-                                    e.damage(10);
-                                    projectileLayer.getChildren().remove(projectile);
-                                    animations.remove(tx);
-                                }
-                            });
-                            tx.play();
-                            animations.add(tx);
-                            break;
-                        }
-                    }
-                    delay = Duration.millis(fireRate.get());
-                }
-            };
-            t.start();
-            animators.add(t);
-        }};
+    public void addTower(Tower tower) {
+        towers.add(tower);
         towerLayer.getChildren().add(tower);
     }
 
+    public void removeTower(Tower tower) {
+        towers.remove(tower);
+        towerLayer.getChildren().remove(tower);
+    }
 
     @Override
     protected double computeMinWidth(double v) {
             }
         }
     }
-
-    public static abstract class Animator extends AnimationTimer {
-        private long last;
-        Duration delay;
-        private long pausedAt;
-
-        public Animator(Duration delay) {
-            this.delay = delay;
-        }
-
-        @Override
-        public final void handle(long l) {
-            if (pausedAt != 0) {
-                last = System.nanoTime() - (pausedAt - last);
-                pausedAt = 0;
-            }
-            if ((l - last)/1000000 > delay.toMillis()) {
-                last = l;
-                pulse();
-            }
-        }
-
-        public void pause() {
-            super.stop();
-            pausedAt = System.nanoTime();
-        }
-
-        protected abstract void pulse();
-    }
 }

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

 package com.fxexperience.games.defender.games.simple;
 
 import com.fxexperience.games.defender.games.simple.levels.FirstLevel;
+import com.fxexperience.games.defender.games.simple.towers.BasicTower;
 import javafx.scene.Scene;
 import javafx.scene.layout.BorderPane;
 import javafx.scene.layout.StackPane;
         getStylesheets().add("/styles/games/simple/styles.css");
         BorderPane root = (BorderPane) getRoot();
         StackPane p = new StackPane();
-        p.getChildren().add(new FirstLevel(game));
+        FirstLevel level = new FirstLevel();
+
+        // hard code in a tower for now
+        Tower tower = new BasicTower();
+        tower.setLayoutY(200);
+        tower.setLayoutX(300);
+        level.addTower(tower);
+
+        p.getChildren().add(level);
+        game.addGamePulseListener(level);
         root.setCenter(p);
         root.setRight(new GameControlPanel(game));
     }

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

 
 /**
  */
-public class Tower extends Parent {
+public class Tower extends Parent implements GamePulseListener {
     public final IntegerProperty level = new SimpleIntegerProperty(this, "level");
     public final StringProperty name = new SimpleStringProperty(this, "name");
     public final DoubleProperty range = new SimpleDoubleProperty(this, "range", 200);
 
     // Need a method which is called whenever the tower should shoot, which will give it the list
     // of possible targets so it can choose which target to fire at?
+
+    @Override
+    public void pulse() {
+    }
 }

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

 package com.fxexperience.games.defender.games.simple;
 
+import javafx.scene.shape.Path;
 import javafx.util.Duration;
 
 import java.util.Arrays;
 import java.util.LinkedList;
 
-/**
- * User: rbair
- * Date: 12/13/12
- * Time: 8:09 PM
- */
-public class Wave {
+
+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;
 
     public Wave(Duration delay, Enemy... enemies) {
         this.delay = delay;
         this.enemies.addAll(Arrays.asList(enemies));
     }
 
-    public Duration getDelay() {
-        return delay;
+    public void setLevel(LevelPanel level) {
+        this.level = level;
     }
 
-    public int getNumEnemies() {
-        return enemies.size();
+    public void setPath(Path path) {
+        this.path = path;
     }
 
-    public Enemy nextEnemy() {
-        return enemies.removeFirst();
+    @Override
+    public void pulse() {
+        if (level != null) {
+            long now = System.currentTimeMillis();
+            if (now - lastSpawn > delay.toMillis()) {
+                lastSpawn = now;
+                spawn();
+            }
+        }
     }
 
     public boolean hasMoreEnemies() {
         return !enemies.isEmpty();
     }
+
+    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/GreenEnemy.java

 import com.fxexperience.games.defender.games.simple.Enemy;
 import javafx.scene.layout.Region;
 
-/**
- * User: rbair
- * Date: 12/13/12
- * Time: 6:17 PM
- */
 public class GreenEnemy extends Enemy {
     public GreenEnemy() {
         super(300, 30);

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

  */
 package com.fxexperience.games.defender.games.simple.levels;
 
-import com.fxexperience.games.defender.games.simple.Game;
 import com.fxexperience.games.defender.games.simple.LevelPanel;
 import com.fxexperience.games.defender.games.simple.Wave;
 import com.fxexperience.games.defender.games.simple.enemies.BlackEnemy;
 import javafx.scene.shape.Path;
 import javafx.util.Duration;
 
-/**
- * User: rbair
- * Date: 12/13/12
- * Time: 6:25 PM
- */
 public class FirstLevel extends LevelPanel {
-    public FirstLevel(Game game) {
-        super(game, new Path(
+    public FirstLevel() {
+        super(new Path(
                 new MoveTo(-20, -20),
                 new LineTo(100, 100),
                 new LineTo(600, 150),

defender/src/main/java/com/fxexperience/games/defender/games/simple/towers/BasicTower.java

  */
 package com.fxexperience.games.defender.games.simple.towers;
 
+import com.fxexperience.games.defender.games.simple.LevelPanel;
 import com.fxexperience.games.defender.games.simple.Tower;
+import javafx.scene.layout.Region;
 
 public class BasicTower extends Tower {
+
+    private LevelPanel level;
+
     public BasicTower() {
         name.set("Basic Tower");
+        Region body = new Region();
+        body.getStyleClass().add("tower");
+        getChildren().add(body);
+    }
+
+    @Override
+    public void pulse() {
+//
+//        long now = System.currentTimeMillis();
+//        if (now - lastSpawn > delay.toMillis()) {
+//        }
+//
+//            // TODO can enable arbitrarily complex target acquisition logic here. For example,
+//        // the tower should calculate what enemy it can damage the most, and destroy before
+//        // passing outside its bounds.
+//        // Check to see if any enemies are within the range of this guy.
+//        Bounds bounds = getBoundsInParent();
+//        Point2D center = new Point2D(
+//                bounds.getMinX() + (bounds.getWidth()/2.0),
+//                bounds.getMinY() + (bounds.getHeight()/2.0));
+//
+//        List<Enemy> enemies = (List<Enemy>)(List)enemyLayer.getChildren();
+//        for (final Enemy e : enemies) {
+//            Bounds enemyBounds = e.getBoundsInParent();
+//            Point2D enemyCenter = new Point2D(
+//                    enemyBounds.getMinX() + (enemyBounds.getWidth()/2.0),
+//                    enemyBounds.getMinY() + (enemyBounds.getHeight()/2.0));
+//
+//            double x = Math.max(enemyCenter.getX(), center.getX()) - Math.min(enemyCenter.getX(), center.getX());
+//            double y = Math.max(enemyCenter.getX(), center.getX()) - Math.min(enemyCenter.getX(), center.getX());
+//            double hyp = Math.sqrt((x*x) + (y*y));
+//            if (hyp < range.get() + (Math.max(enemyBounds.getHeight(), enemyBounds.getWidth()))) {
+//                // Lock in a firing solution
+//                final Region projectile = new Region();
+//                projectile.getStyleClass().add("bullet");
+//                projectile.setTranslateY(center.getY());
+//                projectile.setTranslateX(center.getX());
+//                projectileLayer.getChildren().add(projectile);
+//                Path firePath = new Path(new MoveTo(center.getX(), center.getY()), new LineTo(enemyCenter.getX(), enemyCenter.getY()));
+//                final PathTransition tx = new PathTransition(Duration.seconds(.2), firePath, projectile);
+//                tx.setOnFinished(new EventHandler<ActionEvent>() {
+//                    @Override
+//                    public void handle(ActionEvent actionEvent) {
+//                        e.damage(10);
+//                        projectileLayer.getChildren().remove(projectile);
+//                        animations.remove(tx);
+//                    }
+//                });
+//                tx.play();
+//                animations.add(tx);
+//                break;
+//            }
+//        }
+//        delay = Duration.millis(fireRate.get());
+
     }
 }