Desktop Java

Tower Defense in JavaFX (2)

In the last part we’ve created a simple editor that let’s us place turrets. Now we’ll add a spawnpoint where the enemies originate, and define an attack target for them. First I’ll add some more information to the map via an Object Layer. That’s standar TMX, so we can do it in the TileMap Editor:
 
 
 
 
 
 
 
 
Bildschirmfoto-2013-08-06-um-17.41.17

In order to calculate the attack path for our enemies, we’ll use the A* Algorithm, which is part of the tilengine module:

So let’s get the spawnpoint and target and store them for our algorithm:

ArrayList objectGroups = tileMap.getObjectGroups();
for (ObjectGroup objectGroup : objectGroups) {
for (final TObject tObject : objectGroup.getObjectLIst()) {
if (tObject.getName().equals("spawnpoint")) {

spawnpointX = tObject.getX() / turrets.getTilewidth();
spawnpointY = tObject.getY() / turrets.getTileheight();

}

if (tObject.getName().equals("target")) {

targetX = tObject.getX() / turrets.getTilewidth();
targetY = tObject.getY() / turrets.getTileheight();

}
}
}

With these values we can initialize the A* algorithm that calculates the shortest path for our enemies:

AStar.AStarTile start = new AStar.AStarTile((int) spawnpointX, (int) spawnpointY);
AStar.AStarTile end = new AStar.AStarTile((int) targetX, (int) targetY);
attackPath = AStar.getPath(tileMap, platformLayer, start, end);

In order to see the result , we’ll add a debug layer to the GameCanvas:

private class AStarLayer extends Layer {
public AStarLayer() {
}
Color pathColor = Color.rgb(255, 100, 100, .2);

@Override
public void draw(GraphicsContext graphicsContext, double x, double y, double width, double height) {
AStar.PathNode start = attackPath;
if (start != null) {
graphicsContext.setFill(pathColor);
graphicsContext.fillRect(start.getX() * tileMap.getTilewidth(), start.getY() * tileMap.getTileheight(), tileMap.getTilewidth(), tileMap.getTileheight());
while (start.getParent() != null) {
start = start.getParent();
graphicsContext.fillRect(start.getX() * tileMap.getTilewidth(), start.getY() * tileMap.getTileheight(), tileMap.getTilewidth(), tileMap.getTileheight());
}
}
}
}

The result looks like this:

Bildschirmfoto-2013-08-07-um-08.12.45

You see the shortest path in red color. Since the algorithm doesn’t “see” the structures of the background image, it calculates the path accordingly, and the enemies would simply ignore the structures of the ship (the background is supposed to be a part of a spaceship). To fix this, we’ll add some invisible Tiles later. For larger games it’s better to use an invisible collision layer though, which gives you better performance and more ways to implement things like locked passages. For us the transparent-tile-approach is better, because we don’t need an extra layer, and it’s easier if the user can edit the layout.

Now we need to send the enemies down this path. In order to animate the Sprite I combined the animation phases into a single image:

Bildschirmfoto-2013-08-07-um-08.05.59

Now we can use the Tiled editor to create a TileSet from it:

Bildschirmfoto-2013-08-07-um-08.10.43

I also used Tiled to add two additional properties to the spawnpoint:

Bildschirmfoto-2013-08-06-um-17.41.271

The first one defines how many enemies I want to spawn of each type, the second one defines the pause between their spawning. I doubt that they’ll stand the test of time unchanged, but for now let’s work with them. Inside the code for reading the ObjectGroups we can access the Properties:

if (tObject.getName().equals("spawnpoint")) {

Properties properties = tObject.getProperties();
evaluationInterval = Long.parseLong(properties.getProperty("delay"));
spawnpointX = tObject.getX() / turrets.getTilewidth();
spawnpointY = tObject.getY() / turrets.getTileheight();

}

For now we only have one type of monster, so we can ignore that and only use the delay. First we’ll create a SpriteAnimation from our TileSet:

final TileSet enemy1 = tileMap.getTileSet("enemy1");
final TileSetAnimation tileSetAnimation = new TileSetAnimation(enemy1, new int[]{0, 1, 2, 3, 4, 5}, 10f);

In order to Spawn a monster we’ll define a Behavior. That’s simply a timed method call. The API will probably be changed a bit here in order to support Lambda expressions:

Behavior monsterSpawnBehavior = new Behavior() {
int enemyCount = 0;

@Override
public boolean perform(GameCanvas canvas, long nanos) {
new Sprite(canvas, tileSetAnimation, "enemy" + (enemyCount++), ((int)spawnpointTileX) * tileMap.getTilewidth(), ((int)spawnpointTileY) * tileMap.getTileheight(), 46, 46, Lookup.EMPTY);
return false;
}
};
monsterSpawnBehavior.setEvaluationInterval(evaluationInterval);
canvas.addBehaviour(monsterSpawnBehavior);

So now every soandso nanoseconds a new Enemy will be added to the playfield. We’ll probably create an EnemySprite class later to encapsulate the Behavior. But for now let’s stick with this Sprite and add Behaviour to it:

sprite.addBehaviour(new SpriteBehavior() {
AStar.PathNode start = attackPath;

@Override
public boolean perform(Sprite sprite) {
double x = sprite.getX();
double y = sprite.getY();
double pathX = start.getX() * tileMap.getTilewidth();
double pathY = start.getY() * tileMap.getTileheight();
if (Math.abs(pathX- x) 1) {
sprite.setVelocityX(.5);
} else if (pathX- x < -1) { sprite.setVelocityX(-.5); } else { sprite.setVelocityX(0); } if (pathY - y > 1) {
sprite.setVelocityY(.5);
} else if (pathY - y < -1) {
sprite.setVelocityY(-.5);
} else {
sprite.setVelocityY(0);
}
return true;
}
});

And here’s the result:

That’s it for now. As you can see, it’s pretty simple to add AI to the sprites via Behaviors and AStar comes pretty handy. In the next part we’ll take care that our enemies point in the right direction, and add some Behavior to the turrets.
 

Reference: Tower Defense in JavaFX (2) from our JCG partner Toni Epple at the Eppleton blog.

Toni Epple

Anton is a consultant worldwide for a wide variety of companies, ranging from startups to Fortune 500 companies, in many areas, including finance institutions and aerospace. His main interest is Client side development, and he has authored books and numerous articles on this topic. He is a member of the NetBeans Dream Team and a Oracle Java Champion. In 2013 he was elected as a JavaONE Rockstar, in 2014 he received a Duke’s Choice Award for his work on DukeScript.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button