1. Richard Bair
  2. fx-games

Commits

zonski  committed e55acb3 Merge

Merged in zonski/fx-games (pull request #4: GamePulse logic and related changes, plus basic Tower Placement, plus Screen flow)

  • Participants
  • Parent commits 9d10c1b, 808e3ce
  • Branches default

Comments (0)

Files changed (27)

File defender/pom.xml

View file
 
     </dependencies>
 
-</project>
+</project>

File defender/src/main/java/com/fxexperience/games/defender/DefenderApp.java

View file
  */
 package com.fxexperience.games.defender;
 
-import com.fxexperience.games.defender.games.simple.SimpleGameScene;
+import com.fxexperience.games.defender.games.simple.Game;
 import javafx.application.Application;
+import javafx.scene.Scene;
 import javafx.stage.Stage;
 
 /**
  */
 public class DefenderApp extends Application {
     public void start(Stage stage) throws Exception {
-        // Right now we're hard-coding the creation of the main view to be the simple
-        // version of the app "SimpleGameScene" instance. However, we need to expand this
-        // so that we can load alternate games, perhaps by using reflection, or by
-        // just hard-coding a set of application parameter -> game view instances.
+        Game game = new Game();
+        Scene scene = new Scene(game, 1200, 700);
+        scene.getStylesheets().add("/styles/games/simple/styles.css");
         stage.setTitle("JavaFX Tower Defender");
-        stage.setScene(new SimpleGameScene());
+        stage.setScene(scene);
         stage.show();
+
+        game.showWelcomePanel();
     }
 
     public static void main(String[] args) {

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

View file
  */
 package com.fxexperience.games.defender.games.simple;
 
-import javafx.beans.property.*;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.ReadOnlyDoubleProperty;
+import javafx.beans.property.ReadOnlyDoubleWrapper;
+import javafx.beans.property.SimpleObjectProperty;
 import javafx.event.ActionEvent;
 import javafx.event.EventHandler;
+import javafx.geometry.Bounds;
+import javafx.geometry.Point2D;
 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();
+        }
+    }
+
+    public Point2D getCenter() {
+        Bounds bounds = getBoundsInParent();
+        return new Point2D(
+                bounds.getMinX() + (bounds.getWidth()/2.0),
+                bounds.getMinY() + (bounds.getHeight()/2.0));
+    }
+
+    protected void destinationReached() {
+        // done
+        log.info("Enemy made it to the destination");
+        // todo handle this better
+        getLevel().removeEnemy(this);
+    }
 }

File defender/src/main/java/com/fxexperience/games/defender/games/simple/Game.java

View file
  */
 package com.fxexperience.games.defender.games.simple;
 
+import com.fxexperience.games.defender.games.simple.ui.GameControlPanel;
+import com.fxexperience.games.defender.games.simple.ui.NewGamePanel;
+import com.fxexperience.games.defender.games.simple.ui.WelcomePanel;
+import javafx.animation.AnimationTimer;
 import javafx.beans.property.*;
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+import javafx.scene.layout.BorderPane;
+import javafx.scene.layout.StackPane;
 
-/**
- * User: rbair
- * Date: 12/13/12
- * Time: 12:50 PM
- */
-public class Game {
-    public final BooleanProperty paused = new SimpleBooleanProperty(this, "paused", false);
+import java.util.ArrayList;
+import java.util.List;
+
+public class Game extends StackPane {
+    public final BooleanProperty paused = new SimpleBooleanProperty(this, "paused", true);
     public final DoubleProperty money = new SimpleDoubleProperty(this, "money", 100);
     public final IntegerProperty lives = new SimpleIntegerProperty(this, "lives", 10);
+
+    private final ObjectProperty<LevelPanel> currentLevel = new SimpleObjectProperty<>(this, "currentLevel");
+
+    public ObjectProperty<LevelPanel> currentLevelProperty() { return currentLevel; }
+    public LevelPanel getCurrentLevel() { return this.currentLevel.get(); }
+    public void setCurrentLevel(LevelPanel level) { this.currentLevel.set(level); }
+
+    private AnimationTimer animationTimer;
+    private List<GamePulseListener> gamePulseListeners;
+
+    private BorderPane gamePanel;
+    private final WelcomePanel welcomePanel;
+    private final NewGamePanel newGamePanel;
+
+    public Game() {
+
+        gamePanel = new BorderPane();
+        gamePanel.setRight(new GameControlPanel(this));
+
+        welcomePanel = new WelcomePanel(this);
+        newGamePanel = new com.fxexperience.games.defender.games.simple.ui.NewGamePanel(this);
+
+        gamePulseListeners = new ArrayList<>();
+
+        animationTimer = new AnimationTimer() {
+            @Override
+            public void handle(long currentTime) {
+                pulse();
+            }
+        };
+
+        currentLevel.addListener(new ChangeListener<LevelPanel>() {
+            @Override
+            public void changed(ObservableValue<? extends LevelPanel> source, LevelPanel oldLevel, LevelPanel newLevel) {
+                if (oldLevel != null) {
+                    gamePulseListeners.remove(oldLevel);
+                    gamePanel.setCenter(null);
+                }
+                if (newLevel != null) {
+                    gamePulseListeners.add(newLevel);
+                    gamePanel.setCenter(newLevel);
+                }
+            }
+        });
+
+        paused.addListener(new ChangeListener<Boolean>() {
+            @Override
+            public void changed(ObservableValue<? extends Boolean> source, Boolean wasPaused, Boolean isPaused) {
+                if (isPaused) {
+                    animationTimer.stop();
+                } else {
+                    animationTimer.start();
+                }
+            }
+        });
+    }
+
+    public void showWelcomePanel() {
+        welcomePanel.reset();
+        getChildren().setAll(gamePanel, welcomePanel);
+        welcomePanel.playWelcome();
+    }
+
+    public void showNewGamePanel() {
+        newGamePanel.reset();
+        getChildren().setAll(gamePanel, newGamePanel);
+        newGamePanel.playEntryAnimation();
+    }
+
+    public void addGamePulseListener(GamePulseListener listener) {
+        gamePulseListeners.add(listener);
+    }
+
+    public void removeGamePulseListener(GamePulseListener listener) {
+        gamePulseListeners.remove(listener);
+    }
+
+    protected void pulse() {
+        for (GamePulseListener listener : gamePulseListeners) {
+            listener.pulse();
+        }
+    }
+
 }

File defender/src/main/java/com/fxexperience/games/defender/games/simple/GameControlPanel.java

-package com.fxexperience.games.defender.games.simple;
-
-import com.fxexperience.games.defender.games.simple.towers.BasicTower;
-import java.util.Arrays;
-import java.util.List;
-import javafx.event.ActionEvent;
-import javafx.event.EventHandler;
-import javafx.fxml.FXMLLoader;
-import javafx.scene.Node;
-import javafx.scene.Parent;
-import javafx.scene.control.Button;
-import javafx.scene.control.ToggleButton;
-import javafx.scene.control.ToggleGroup;
-import javafx.scene.layout.GridPane;
-import javafx.scene.layout.StackPane;
-
-/**
- */
-public class GameControlPanel extends StackPane {
-    public GameControlPanel(final Game game) {
-        getStyleClass().add("game-control-panel");
-        List<? extends Tower> towers = Arrays.asList(
-                new BasicTower()
-        );
-
-        try {
-            Parent content = FXMLLoader.load(getClass().getResource("/styles/games/simple/GameControlPanel.fxml"));
-            getChildren().add(content);
-
-            ToggleGroup grp = new ToggleGroup();
-            GridPane towerGrid = (GridPane)content.lookup("#tower-grid");
-            List<Node> towerButtons = towerGrid.getChildren();
-            for (int i=0; i<towerButtons.size(); i++) {
-                ToggleButton btn = (ToggleButton)towerButtons.get(i);
-                btn.setToggleGroup(grp);
-                if (i < towers.size()) {
-                    Tower tower = towers.get(i);
-                    btn.setText(tower.name.get());
-                } else {
-                    // might want to just set disabled instead, and have each button hard-coded to
-                    // be of a certain tower type!
-                    btn.setVisible(false);
-                }
-            }
-
-            final Button playButton = (Button)content.lookup("#play-button");
-            playButton.setOnAction(new EventHandler<ActionEvent>() {
-                @Override
-                public void handle(ActionEvent actionEvent) {
-                    game.paused.set(!game.paused.get());
-                    playButton.setText(game.paused.get() ? "Send The Enemies!" : "Pause");
-                }
-            });
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-    }
-}

File defender/src/main/java/com/fxexperience/games/defender/games/simple/GamePulseListener.java

View file
+/*
+* Copyright (c) 2012, FX Experience. All rights  reserved.
+*
+* Redistribution and use in source and binary forms, with or without modification, are
+* permitted provided that the following conditions are met:
+*
+* Redistributions of source code must retain the above copyright notice, this list of
+* conditions and the following disclaimer. 
+*
+* Redistributions in binary form must reproduce the above copyright notice, this list of 
+* conditions and the following disclaimer in the documentation and/or other materials provided 
+* with the distribution.    
+*
+* Neither the name of the author nor the names of its contributors may be used to endorse or
+* promote products derived from this software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
+* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+package com.fxexperience.games.defender.games.simple;
+
+public interface GamePulseListener {
+
+    void pulse();
+}

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

View file
  */
 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 abstract 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 List<Projectile> projectiles;
 
-    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<>();
+        projectiles = 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 our sprites (cache to list to avoid concurrent modification problems - maybe could be done smarter)
+        List<GamePulseListener> pulsers = new ArrayList<>();
+        pulsers.addAll(enemies);
+        pulsers.addAll(towers);
+        pulsers.addAll(projectiles);
+        for (GamePulseListener pulser : pulsers) {
+            pulser.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 List<Enemy> findEnemiesInArea(Point2D center, int radius) {
+        List<Enemy> matches = new ArrayList<>();
+        for (Enemy enemy : enemies) {
+            Bounds bounds = enemy.getBoundsInParent();
+            if (center.getX() - radius <= bounds.getMaxX() && center.getX() + radius >= bounds.getMinX()
+                    && center.getY() - radius <= bounds.getMaxY() && center.getY() + radius >= bounds.getMinY()) {
+                matches.add(enemy);
+            }
+        }
+        return matches;
+    }
 
-            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 addEnemy(Enemy enemy) {
+        enemy.setLevel(this);
+        enemies.add(enemy);
+        enemyLayer.getChildren().add(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));
+    public void removeEnemy(Enemy enemy) {
+        enemy.setLevel(null);
+        enemies.remove(enemy);
+        enemyLayer.getChildren().remove(enemy);
+    }
 
-                        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) {
+        tower.setLevel(this);
+        towers.add(tower);
         towerLayer.getChildren().add(tower);
     }
 
+    public void removeTower(Tower tower) {
+        tower.setLevel(null);
+        towers.remove(tower);
+        towerLayer.getChildren().remove(tower);
+    }
+
+    public void addProjectile(Projectile projectile) {
+        projectile.setLevel(this);
+        projectiles.add(projectile);
+        projectileLayer.getChildren().add(projectile);
+    }
+
+    public void removeProjectile(Projectile projectile) {
+        projectile.setLevel(null);
+        projectiles.remove(projectile);
+        projectileLayer.getChildren().remove(projectile);
+    }
 
     @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();
-    }
 }

File defender/src/main/java/com/fxexperience/games/defender/games/simple/NewGamePanel.java

-/*
- * Copyright (c) 2012, FX Experience. All rights  reserved.
- *
- * Redistribution and use in source and binary forms, with or without modification, are
- * permitted provided that the following conditions are met:
- *
- * Redistributions of source code must retain the above copyright notice, this list of
- * conditions and the following disclaimer.
- *
- * Redistributions in binary form must reproduce the above copyright notice, this list of
- * conditions and the following disclaimer in the documentation and/or other materials provided
- * with the distribution.
- *
- * Neither the name of the author nor the names of its contributors may be used to endorse or
- * promote products derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
- * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
- * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
- * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.fxexperience.games.defender.games.simple;
-
-import javafx.beans.property.ObjectProperty;
-import javafx.beans.property.ReadOnlyObjectProperty;
-import javafx.beans.property.ReadOnlyObjectWrapper;
-import javafx.beans.property.SimpleObjectProperty;
-import javafx.event.ActionEvent;
-import javafx.event.EventHandler;
-import javafx.geometry.Insets;
-import javafx.scene.control.Button;
-import javafx.scene.layout.Region;
-import javafx.scene.layout.VBox;
-
-/**
- * Shown when it is time to start a new game
- */
-public class NewGamePanel extends Region {
-    private ObjectProperty<EventHandler<ActionEvent>> onAction = new SimpleObjectProperty<>(this, "onAction");
-    private ReadOnlyObjectWrapper<Difficulty> difficulty = new ReadOnlyObjectWrapper<>(this, "difficulty");
-
-    public final EventHandler<ActionEvent> getOnAction() { return onAction.get(); }
-    public final void setOnAction(EventHandler<ActionEvent> value) { onAction.set(value); }
-    public final ObjectProperty<EventHandler<ActionEvent>> onActionProperty() { return onAction; }
-
-    private final void setDifficulty(Difficulty value) { difficulty.set(value); }
-    public final Difficulty getDifficulty() { return difficulty.get(); }
-    public final ReadOnlyObjectProperty<Difficulty> difficultyProperty() { return difficulty.getReadOnlyProperty(); }
-
-    private final VBox buttons;
-
-    public NewGamePanel() {
-        getStyleClass().add("new-game-panel");
-
-        buttons = new VBox();
-        buttons.getStyleClass().add("button-box");
-        getChildren().add(buttons);
-
-        for (final Difficulty difficulty : Difficulty.values()) {
-            String text = difficulty.name().toLowerCase();
-            text = text.substring(0, 1).toUpperCase() + text.substring(1);
-
-            Button button = new Button(text);
-            button.getStyleClass().setAll("game-difficulty-button");
-            button.setMaxWidth(Double.MAX_VALUE); // have to do this or cannot size buttons from CSS
-            button.setOnAction(new EventHandler<ActionEvent>() {
-                @Override
-                public void handle(ActionEvent actionEvent) {
-                    setDifficulty(difficulty);
-                    EventHandler<ActionEvent> onAction = NewGamePanel.this.getOnAction();
-                    if (onAction != null) {
-                        onAction.handle(new ActionEvent(NewGamePanel.this, NewGamePanel.this));
-                    }
-                }
-            });
-            buttons.getChildren().add(button);
-        }
-    }
-
-    @Override
-    protected void layoutChildren() {
-        final Insets insets = getInsets();
-
-        // round up and cast to int to align on pixel boundaries without truncating text
-        double w = (int) (buttons.prefWidth(-1) + 1);
-        double h = (int) (buttons.prefHeight(-1) + 1);
-        buttons.resize(w, h);
-
-        buttons.relocate(
-                (int)((getWidth() - insets.getLeft() - insets.getRight() - w) / 2.0),
-                (int)((getHeight() - insets.getTop() - insets.getBottom() - h) / 2.0));
-    }
-}

File defender/src/main/java/com/fxexperience/games/defender/games/simple/Projectile.java

View file
+/*
+ * Copyright (c) 2012, FX Experience. All rights  reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * Neither the name of the author nor the names of its contributors may be used to endorse or
+ * promote products derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.fxexperience.games.defender.games.simple;
+
+import javafx.geometry.Bounds;
+import javafx.geometry.Point2D;
+import javafx.scene.Parent;
+
+public class Projectile extends Parent implements GamePulseListener {
+
+    protected LevelPanel level;
+
+    public void setLevel(LevelPanel level) {
+        this.level = level;
+    }
+
+    @Override
+    public void pulse() {
+
+    }
+
+    public Point2D getCenter() {
+        Bounds bounds = getBoundsInParent();
+        return new Point2D(
+                bounds.getMinX() + (bounds.getWidth()/2.0),
+                bounds.getMinY() + (bounds.getHeight()/2.0));
+    }
+}

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

-/*
- * Copyright (c) 2012, FX Experience. All rights  reserved.
- *
- * Redistribution and use in source and binary forms, with or without modification, are
- * permitted provided that the following conditions are met:
- *
- * Redistributions of source code must retain the above copyright notice, this list of
- * conditions and the following disclaimer.
- *
- * Redistributions in binary form must reproduce the above copyright notice, this list of
- * conditions and the following disclaimer in the documentation and/or other materials provided
- * with the distribution.
- *
- * Neither the name of the author nor the names of its contributors may be used to endorse or
- * promote products derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
- * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
- * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
- * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.fxexperience.games.defender.games.simple;
-
-import com.fxexperience.games.defender.games.simple.levels.FirstLevel;
-import javafx.scene.Scene;
-import javafx.scene.layout.BorderPane;
-import javafx.scene.layout.StackPane;
-
-/**
- * The Scene which will be used for the simple version of the game. This is
- * created and used by the DefenderApp application instance.
- */
-public class SimpleGameScene extends Scene {
-    public SimpleGameScene() {
-        super(new BorderPane(), 1200, 700);
-
-        final Game game = new Game();
-        getStylesheets().add("/styles/games/simple/styles.css");
-        BorderPane root = (BorderPane) getRoot();
-        StackPane p = new StackPane();
-        p.getChildren().add(new FirstLevel(game));
-        root.setCenter(p);
-        root.setRight(new GameControlPanel(game));
-    }
-}

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

View file
  */
 package com.fxexperience.games.defender.games.simple;
 
-import javafx.beans.property.*;
+import javafx.geometry.Bounds;
+import javafx.geometry.Point2D;
 import javafx.scene.Parent;
 
 /**
  */
-public class Tower extends Parent {
-    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);
-    public final DoubleProperty fireRate = new SimpleDoubleProperty(this, "fireRate", 300); // measured in shots per millisecond
+public class Tower extends Parent implements GamePulseListener {
+
+    protected String name;
+    protected LevelPanel level;
+
+    public Tower(String name) {
+        this.name = name;
+    }
 
     // Want to create an event that happens when the tower bullet hits an enemy, such that
     // different types of towers can do different things depending on what kind of enemy
     // is hit. For example, if the blue tower hits a blue enemy, it has 2x damage, but
     // if the tower hits a green enemy, it has .5x damage.
 
-    // Need an event for when a shot is fired off, so the UI can create a projectile and fly it to the target
+    public void setLevel(LevelPanel level) {
+        this.level = level;
+    }
 
-    // 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() {
+    }
+
+    public Point2D getCenter() {
+        Bounds bounds = getBoundsInParent();
+        return new Point2D(
+            bounds.getMinX() + (bounds.getWidth()/2.0),
+            bounds.getMinY() + (bounds.getHeight()/2.0));
+    }
 }

File defender/src/main/java/com/fxexperience/games/defender/games/simple/TowerFactory.java

View file
+/*
+* Copyright (c) 2012, FX Experience. All rights  reserved.
+*
+* Redistribution and use in source and binary forms, with or without modification, are
+* permitted provided that the following conditions are met:
+*
+* Redistributions of source code must retain the above copyright notice, this list of
+* conditions and the following disclaimer. 
+*
+* Redistributions in binary form must reproduce the above copyright notice, this list of 
+* conditions and the following disclaimer in the documentation and/or other materials provided 
+* with the distribution.    
+*
+* Neither the name of the author nor the names of its contributors may be used to endorse or
+* promote products derived from this software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
+* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+package com.fxexperience.games.defender.games.simple;
+
+public interface TowerFactory {
+
+    String getTowerName();
+
+    Tower createTower();
+}

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

View file
 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);
+        }
+    }
 }

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

View file
 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);

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

View file
  */
 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),

File defender/src/main/java/com/fxexperience/games/defender/games/simple/projectiles/BasicProjectile.java

View file
+/*
+ * Copyright (c) 2012, FX Experience. All rights  reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * Neither the name of the author nor the names of its contributors may be used to endorse or
+ * promote products derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.fxexperience.games.defender.games.simple.projectiles;
+
+import com.fxexperience.games.defender.games.simple.Projectile;
+import javafx.geometry.Point2D;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Circle;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class BasicProjectile extends Projectile {
+
+    private static final Logger log = LoggerFactory.getLogger(BasicProjectile.class);
+
+    private Point2D destination;
+    private double xstep;
+    private double ystep;
+    private long lastStep;
+    private long stepDelay;
+
+    public BasicProjectile(Point2D start, Point2D destination, double speed) {
+        this.destination = destination;
+
+        getChildren().add(new Circle(5, Color.RED));
+
+        setTranslateX(start.getX());
+        setTranslateY(start.getY());
+
+        getStyleClass().add("bullet");
+        setTranslateY(start.getY());
+        setTranslateX(start.getX());
+
+        double xdist = destination.getX() - start.getX();
+        double ydist = destination.getY() - start.getY();
+
+        xstep = xdist / 5;
+        ystep = ydist / 5;
+        stepDelay = 30;
+
+        log.debug("BasicProjectile firing from ({}, {}) to ({}, {}), step size = ({}, {})", new Object[]{
+                start.getX(), start.getY(), destination.getX(), destination.getY(), xstep, ystep
+        });
+    }
+
+    @Override
+    public void pulse() {
+
+        long now = System.currentTimeMillis();
+        if (now - lastStep > stepDelay) {
+            setTranslateX(getTranslateX() + xstep);
+            setTranslateY(getTranslateY() + ystep);
+            if (Math.abs(getTranslateX() - destination.getX()) < 2 && Math.abs(getTranslateY() - destination.getY()) < 2) {
+                destinationReached();
+            }
+            lastStep = now;
+        }
+    }
+
+    private void destinationReached() {
+        log.debug("BasicProjectile reached target destination");
+        level.removeProjectile(this);
+    }
+}

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

View file
  */
 package com.fxexperience.games.defender.games.simple.towers;
 
+import com.fxexperience.games.defender.games.simple.Enemy;
+import com.fxexperience.games.defender.games.simple.Projectile;
 import com.fxexperience.games.defender.games.simple.Tower;
+import com.fxexperience.games.defender.games.simple.projectiles.BasicProjectile;
+import javafx.scene.layout.Region;
+import javafx.util.Duration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
-/**
- * User: rbair
- * Date: 12/13/12
- * Time: 8:52 PM
- */
+import java.util.List;
+
 public class BasicTower extends Tower {
-    public BasicTower() {
-        name.set("Basic Tower");
+
+    private static final Logger log = LoggerFactory.getLogger(BasicTower.class);
+
+    private Duration shotDelay;
+    private long lastShot;
+    private int range;
+    private int damage;
+
+    public BasicTower(String name, String bodyStyle, Duration shotDelay, int range, int damage) {
+        super(name);
+        this.shotDelay = shotDelay;
+        this.range = range;
+        this.damage = damage;
+
+        Region body = new Region();
+        body.getStyleClass().addAll("tower", bodyStyle) ;
+        getChildren().add(body);
+    }
+
+    @Override
+    public void pulse() {
+
+        long now = System.currentTimeMillis();
+        if (level != null && now - lastShot > shotDelay.toMillis()) {
+
+            List<Enemy> enemies = level.findEnemiesInArea(getCenter(), range);
+            if (enemies.size() > 0) {
+                // 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.
+                shootAt(enemies.get(0));
+                lastShot = now;
+            }
+        }
+
+//        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());
+
+    }
+
+    protected void shootAt(Enemy enemy) {
+        log.debug("Shooting at: {}", enemy);
+        Projectile projectile = new BasicProjectile(getCenter(), enemy.getCenter(), 5);
+        level.addProjectile(projectile);
     }
 }

File defender/src/main/java/com/fxexperience/games/defender/games/simple/towers/BasicTowerFactory.java

View file
+/*
+ * Copyright (c) 2012, FX Experience. All rights  reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * Neither the name of the author nor the names of its contributors may be used to endorse or
+ * promote products derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.fxexperience.games.defender.games.simple.towers;
+
+import com.fxexperience.games.defender.games.simple.Tower;
+import com.fxexperience.games.defender.games.simple.TowerFactory;
+import javafx.util.Duration;
+
+public class BasicTowerFactory implements TowerFactory {
+
+    private String name;
+    private String bodyStyle;
+    private Duration shotDelay;
+    private int range;
+    private int damage;
+
+    public BasicTowerFactory(String name, String bodyStyle, Duration shotDelay, int range, int damage) {
+        this.name = name;
+        this.bodyStyle = bodyStyle;
+        this.shotDelay = shotDelay;
+        this.range = range;
+        this.damage = damage;
+    }
+
+    @Override
+    public String getTowerName() {
+        return name;
+    }
+
+    @Override
+    public Tower createTower() {
+        return new BasicTower(name, bodyStyle, shotDelay, range, damage);
+    }
+}

File defender/src/main/java/com/fxexperience/games/defender/games/simple/ui/FxmlHelper.java

View file
+/*
+ * Copyright (c) 2012, FX Experience. All rights  reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * Neither the name of the author nor the names of its contributors may be used to endorse or
+ * promote products derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.fxexperience.games.defender.games.simple.ui;
+
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Node;
+
+import java.io.IOException;
+
+public class FxmlHelper {
+
+    public static void load(String fxml, Node node) {
+        FXMLLoader fxmlLoader = new FXMLLoader(FxmlHelper.class.getResource(fxml));
+        fxmlLoader.setRoot(node);
+        fxmlLoader.setController(node);
+        try {
+            fxmlLoader.load();
+        } catch (IOException e) {
+            throw new RuntimeException("Error loading FXML file: " + fxml, e);
+        }
+    }
+}

File defender/src/main/java/com/fxexperience/games/defender/games/simple/ui/GameControlPanel.java

View file
+package com.fxexperience.games.defender.games.simple.ui;
+
+import com.fxexperience.games.defender.games.simple.Game;
+import com.fxexperience.games.defender.games.simple.LevelPanel;
+import com.fxexperience.games.defender.games.simple.Tower;
+import com.fxexperience.games.defender.games.simple.TowerFactory;
+import com.fxexperience.games.defender.games.simple.towers.BasicTowerFactory;
+import javafx.event.ActionEvent;
+import javafx.event.EventHandler;
+import javafx.fxml.FXML;
+import javafx.scene.control.Button;
+import javafx.scene.control.ToggleButton;
+import javafx.scene.control.ToggleGroup;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.layout.GridPane;
+import javafx.scene.layout.VBox;
+import javafx.util.Duration;
+
+/**
+ */
+public class GameControlPanel extends VBox {
+
+    private Game game;
+
+    @FXML private GridPane towerGrid;
+    @FXML private Button playButton;
+
+    public GameControlPanel(final Game game) {
+        this.game = game;
+        FxmlHelper.load("/fxml/GameControlPanel.fxml", this);
+
+        // add allowed towers - todo eventually this should be derived off players allowed/baught towers
+        ToggleGroup toggleGroup = new ToggleGroup();
+        TowerFactory pelletGunFactory = new BasicTowerFactory("Pellet Gun", "pellet-gun", Duration.millis(1000), 100, 5);
+        towerGrid.getChildren().add(createTowerButton(toggleGroup, game, pelletGunFactory, 0, 0));
+        TowerFactory rapidFireGunFactory = new BasicTowerFactory("Rapid Fire Gun", "rapid-fire-gun", Duration.millis(200), 100, 5);
+        towerGrid.getChildren().add(createTowerButton(toggleGroup, game, rapidFireGunFactory, 1, 0));
+    }
+
+    public void togglePlaying() {
+        game.paused.set(!game.paused.get());
+        playButton.setText(game.paused.get() ? "Send The Enemies!" : "Pause");
+    }
+
+
+    protected ToggleButton createTowerButton(ToggleGroup toggleGroup, final Game game, final TowerFactory towerFactory, int column, int row) {
+        final ToggleButton towerButton = new ToggleButton(towerFactory.getTowerName());
+        toggleGroup.getToggles().add(towerButton);
+        GridPane.setConstraints(towerButton, column, row);
+        towerButton.setOnAction(new EventHandler<ActionEvent>() {
+            @Override
+            public void handle(ActionEvent actionEvent) {
+                final LevelPanel level = game.getCurrentLevel();
+                if (level != null) {
+                    final Tower tower = towerFactory.createTower();
+                    tower.setTranslateX(-100);
+                    tower.setTranslateY(-100);
+                    level.setOnMouseReleased(new EventHandler<MouseEvent>() {
+                        @Override
+                        public void handle(MouseEvent event) {
+                            tower.setTranslateX(event.getX());
+                            tower.setTranslateY(event.getY());
+                            level.addTower(tower);
+                            towerButton.setSelected(false);
+                            level.setOnMouseReleased(null);
+                        }
+                    });
+                }
+            }
+        });
+        return towerButton;
+    }
+}
+

File defender/src/main/java/com/fxexperience/games/defender/games/simple/ui/NewGamePanel.java

View file
+/*
+ * Copyright (c) 2012, FX Experience. All rights  reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * Neither the name of the author nor the names of its contributors may be used to endorse or
+ * promote products derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.fxexperience.games.defender.games.simple.ui;
+
+import com.fxexperience.games.defender.games.simple.Game;
+import com.fxexperience.games.defender.games.simple.levels.FirstLevel;
+import javafx.animation.FadeTransitionBuilder;
+import javafx.animation.ParallelTransition;
+import javafx.animation.ParallelTransitionBuilder;
+import javafx.animation.TranslateTransitionBuilder;
+import javafx.event.ActionEvent;
+import javafx.event.EventHandler;
+import javafx.fxml.FXML;
+import javafx.scene.layout.Pane;
+import javafx.scene.layout.StackPane;
+import javafx.util.Duration;
+
+public class NewGamePanel extends StackPane {
+
+    private final Game game;
+
+    @FXML private Pane dialog;
+    @FXML private Pane glassPane;
+
+    public NewGamePanel(final Game game) {
+        this.game = game;
+        FxmlHelper.load("/fxml/NewGamePanel.fxml", this);
+    }
+
+    public void reset() {
+        dialog.setTranslateY(-this.game.getHeight());
+        glassPane.setOpacity(0);
+    }
+
+    public void playEntryAnimation() {
+
+        ParallelTransition transition = ParallelTransitionBuilder.create()
+                .children(
+
+                        TranslateTransitionBuilder.create()
+                                .node(dialog)
+                                .duration(Duration.millis(1000))
+                                .fromY(-game.getHeight())
+                                .toY(0)
+                                .build(),
+
+                        FadeTransitionBuilder.create()
+                                .node(glassPane)
+                                .duration(Duration.millis(500))
+                                .fromValue(0)
+                                .toValue(1)
+                                .build()
+
+                )
+                .build();
+
+        transition.play();
+    }
+
+    public void startGame() {
+        game.paused.set(true);
+        game.setCurrentLevel(new FirstLevel());
+        playExitAnimation();
+    }
+
+    public void playExitAnimation() {
+        ParallelTransition transition = ParallelTransitionBuilder.create()
+                .children(
+
+                        TranslateTransitionBuilder.create()
+                                .node(dialog)
+                                .duration(Duration.millis(1000))
+                                .fromY(0)
+                                .toY(-game.getHeight())
+                                .build(),
+
+                        FadeTransitionBuilder.create()
+                                .node(glassPane)
+                                .duration(Duration.millis(500))
+                                .fromValue(1)
+                                .toValue(0)
+                                .build()
+
+                )
+                .build();
+
+        transition.setOnFinished(new EventHandler<ActionEvent>() {
+            @Override
+            public void handle(ActionEvent actionEvent) {
+                game.getChildren().remove(NewGamePanel.this);
+            }
+        });
+
+        transition.play();
+    }
+}

File defender/src/main/java/com/fxexperience/games/defender/games/simple/ui/WelcomePanel.java

View file
+/*
+ * Copyright (c) 2012, FX Experience. All rights  reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * Neither the name of the author nor the names of its contributors may be used to endorse or
+ * promote products derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.fxexperience.games.defender.games.simple.ui;
+
+import com.fxexperience.games.defender.games.simple.Game;
+import javafx.animation.*;
+import javafx.event.ActionEvent;