Tower Defense in JavaFX (6)
So we’re at part 6 of this tutorial already, and the game has come a long way in the meantime. In this part we’ll finally add a Layer that shows the score, the number of Enemies that have reached their target, a button to start the next Wave, and the money left to buy new turrets. Speaking of money, we haven’t got the logic for that yet, so we should fix that first. I didn’t want to store the price in the application code though, because it makes it harder for my imaginary level designer (who doesn’t know programming) to finetune the variables. Also I wanted to keep everything in one place, so if we decide to add a new turret, it shouldn’t be required to make changes in many different places. Fortunately we can store properties in a TileSet, so we’ll do it like that.
The TMX format for Tilemaps is really a great way to separate the design from the programming internals (business logic sounds really wrong, when it comes to games). As a result graphic designers can create the assets, level designers can create the levels, and even users can mod the game and create their own levels very easily. So far we used the Tiled editor to create a level, and also stored some meta information about the spawning of the enemies and the attack path in one of the layers. Now we add some properties to individual Tiles of the TileSets. This only works with internal TileSets, so in Tiled you have to define a new Tileset via “Map -> new Tileset”. If you now rightclick a Tile it has an action to define the Tile Properties. I’ve defined some for my tower-bases:
I’ve added a couple of properties including a range for the weapon, the rate it’s firing, the damge it causes, name and description to display later on, and a type, I want to use to define the tiles to use as a bullet. Maybe there will also beweapons like a laser that have to be implemented in a different way. Then I can decide to use different logic tepending on that type. I get the properties from the Tile via the Tileset like this:
Properties properties = tileSet.getTileList().get(selectedIndex).getProperties();
Inside the CannonSprite I can use them like this:
String rangeProperty = properties.getProperty("range"); if (rangeProperty != null) { range = Integer.parseInt(rangeProperty); } String damageProperty = properties.getProperty("damage"); if (damageProperty != null) { damage = Integer.parseInt(damageProperty); } String rateProperty = properties.getProperty("firerate"); if (rateProperty != null) { rate = Float.parseFloat(rateProperty); }//....
We’ll do the same thing with the EnemySprites, so we can determine the points you get for destroying them, their resistance to damage, maybe a recovery rate, etc.. One of the nice things of this approach is, that it’s also very easy to extend. In case I decide later to add a new turret that creates a magnetic field to confuse the enemy and make them fly slower, I can do that by simply adding a new Property to that particular turret. I do not have to update my old level designs or break the custom levels of my users. It’s similar to one of my favorite programming techniques, “composition over inheritance” in that aspect.
We can now use these properties and e.g. have the turrets fire at different rates by modifying the evaluation interval of their FireBehavior:
@Override public long getEvaluationInterval() { return (long)(2000000000*rate); }
And we get this as a result:
The second turret now fires at a higher rate, while causing less damage with each of it’s bullets. If we combine that with different prices for the turrets and the limited space available to place turrets, we already have some of the strategic elements that make Tower Defense an interesting game. Now our level designer is in charge to design the levels, set the prices and other properties, to make the game interesting to play, while we continue adding more features.
Now let’s start with the HUD Layer and simply display the score:
private class HUD extends Layer { @Override public void draw(GraphicsContext graphicsContext, double x, double y, double width, double height) { graphicsContext.setFill(Color.RED); graphicsContext.setFont(Font.font("OricNeo", 12)); graphicsContext.fillText("Score: "+score, 10, 16); } }
The score is defined as an IntegerProperty in my game (this is NOT a JavaFX property!) and passed to the EnemySprites:
private IntegerProperty score = new IntegerProperty(0);
So in the EnemySprites “die” method, we simply increase that value:
@Override public void die() { super.die(); getParent().addSprite(new Sprite(getParent(), explosionAnimation, "explosion", getX() - 30, getY() - 80, 128, 128, Lookup.EMPTY)); score.set(score.integerValue()+killPoints); }
Now add the HUD as the Top Layer, and you can see the score:
What we need next is some way to start the wave. In order to do that, it would be great to have some kind of simple control, like a button. We could add that in different ways. The simplest way would probably be to put the canvas in a StackPane add an AnchorPane on top and add a JavaFX Node or Control to that. But we want to do it with FXGameEngine features only, so we’ll use a Sprite as a Button:
final Sprite button = new Sprite(canvas, "button", tileMap.getWidthInPixels()- 30, 20, 20, 20, Lookup.EMPTY); button.setOnMouseClicked(new MouseEventHandler() { @Override public void handle(MouseClick click) { startWave(); button.die(); } }); canvas.addSprite(button);
I’m pretty sure that the API for EventHandling will still change a bit, but it’ll stay an abstraction similar to this. This Sprite has no renderer, so a default renderer will be used that simply paints a Rectangle:
That’s it for today. In the next part we’ll add money to the game, so it get’s a bit more interesting…
Reference: | Tower Defense in JavaFX (6) from our JCG partner Toni Epple at the Eppleton blog. |