Tower Defense in JavaFX (4)
Ok, so far we’ve created a TileMap, displayed it on screen and made it editable in the first part. In the second part we implemented calculation of the attack path using the A* algorithm and made the enemies follow that path. In part three we created some custom TileSetAnimations so we can rotate the Insectoids around their center by an angle. We then applied this to the Insectoids, so they look forward when they fly, and to the turrets, so they always aim at the closest target. Time to make the turrets shoot at their enemies.
First we need TileSets for an explosion and a bullet for the Pellet gun. I found a nice free explosion sheet here. It’s a bit larger (128*128) than the sprites, and the explosion doesn’t start in the center, but after a bit of fiddling with the position relative to the exploding insectoid it works quite nicely. I created the bullet myself and I know I have to come up with something better :-), but at least it’s something that is visible on the screen. After some fiddling with the correct initial position I created this BulletLaunching Behavior:
new SpriteBehavior() { @Override public boolean perform(Sprite sprite) { double angle = rotateAnimation.getAngle(); double xVelocity = Math.cos(Math.toRadians(angle)); double yVelocity = Math.sin(Math.toRadians(angle)); final double centerX = x + (width / 2); final double centerY = y + (height / 2); double startX = centerX + (xVelocity * (width / 2)) - 4; double startY = centerY + (yVelocity * (height / 2)) - 4; Sprite bullet = new Sprite(getParent(), shoot, "bullet" + (bulletCounter++), startX, startY, 8, 8, Lookup.EMPTY); bullet.setVelocityX(xVelocity); bullet.setVelocityY(yVelocity); // add bullet behavior return true; } @Override public long getEvaluationInterval() { return 2000000000; //To change body of generated methods, choose Tools | Templates. } });
Most of the code is calculating the initial position and making sure the bullet heads off in the right direction. Now we need to add some collision detection. Some systems do have a centralized collision system, and allow to add listeners. I prefer to again do this via behaviors, because I find it more natural and intuitive that the bullet itself checks if it has hit something:
bullet.addBehaviour(new SpriteBehavior() { private double range = 75; @Override public boolean perform(Sprite sprite) { Collection checkCollisions = sprite.getParent().checkCollisions(sprite); for (Collision collision : checkCollisions) { if (collision.getSpriteOne() instanceof EnemySprite) { sprite.getParent().removeSprite(sprite); ((EnemySprite) collision.getSpriteOne()).hit(6); return false; } else if (collision.getSpriteTwo() instanceof EnemySprite) { sprite.getParent().removeSprite(sprite); ((EnemySprite) collision.getSpriteTwo()).hit(6); return false; } } if (distance(sprite.getX(), sprite.getY(), centerX, centerY) > range) { sprite.getParent().removeSprite(sprite); return false; } return true; } });
What we do here is simply ask the GameCanvas for collisions of this specific Sprite and try to cause damage if it’s an enemy. The anonymous inner bullet Sprite and the Behavior will be converted later to regular classes to make the code nicer and to make them easier to create and configure. On the side of the Enemy Sprite we need to implement the “hit” method:
public void hit(int impact) { power = power - impact; if (power getParent().removeSprite(this); getParent().addSprite(new Sprite(getParent(), explosionAnimation, "explosion", getX() - 30, getY() - 80, 128, 128, Lookup.EMPTY)); } }
Very simple: in case the hit was deadly, we remove the Sprite and add an Explosion Sprite. If the sprite sizes would match, we could have simply set the explosionAnimation on the existing Sprite. If you can create your own SpriteSheets, you should do that, it makes live much easier. The ExplosionAnimation is configured to run only once, and it has an EventHandler that removes the Sprite, once the Animation is complete:
explosionAnimation = new TileSetAnimation(explosion, 100f); explosionAnimation.setRepeat(1); explosionAnimation.setOnFinished(new AnimationEventHandler() { @Override public void handleEvent(AnimationEvent event) { Sprite target = event.getTarget(); target.getParent().removeSprite(target); getParent().removeSprite(EnemySprite.this); } });
That’s it. Our Turrets will now fire Bullets at the Enemies and try to hurt them until they explode:
In the video you also see a DebugLayer. Currently it tracks some performance data, mainly FPS and if the time between two pulses is too long. I’ve also added a bullet to the top of the screen, to visually detect stuttering animations. You can safely ignore that…
So we’ve got almost everything we need for a Tower Defense Type game now. In the next part of this Tutorial, we’ll add damage indicators to the enemies, and a HUD with the score and a control to start the next wave.