The heat seeking missiles have the following characteristics:
- spawn upon user request
- similar to the bullets we already use
- find the closest target
- follow the target once it has locked on to it
- have a limited amount of fuel
The code is very similar to the one we already used with the player's bullets. The main difference is the algorithm to find a target and to move towards the target using rotation.
Let's get coding.
We already have the secondary weapon control considered in the input class. All that's left is to use it. Then there's the question about where we want the missiles to spawn. In the middle would be boring, that's already occupied by the cannon. Spawning it at the sides would be the solution. But we don't want it to be one-sided, so we have to spawn them left and right. I like the idea of having them spwan alternatively, i. e. left, then right, then left and so on.
We extend the player class with these code fragments:
double missileChargeTime = 10.0; double missileChargeCounterDelta = 1.0; double missileChargeCounter = missileChargeTime; double missileSpeed = 4.0; int missileSlot = 0; ... public void chargeSecondaryWeapon() { // limit fire // --------------------------- // charge weapon: increase a counter by some delta. once it reaches a limit, the weapon is considered charged missileChargeCounter += missileChargeCounterDelta; if( missileChargeCounter > missileChargeTime) { missileChargeCounter = missileChargeTime; } } public double getSecondaryWeaponX() { if( missileSlot == 0) { return x + 10; // just a value that looked right in relation to the sprite image } else { return x + 34; // just a value that looked right in relation to the sprite image } } public double getSecondaryWeaponY() { return y + 10; // just a value that looked right in relation to the sprite image } public double getSecondaryWeaponMissileSpeed() { return missileSpeed; } public void unchargeSecondaryWeapon() { // player bullet uncharged missileChargeCounter = 0; // next slot missileSlot++; missileSlot = missileSlot % 2; }
As I said: Very similar to the cannon code and as easy to use. Now we create the Missile class itself:
public class Missile extends SpriteBase { SpriteBase target; double turnRate = 0.6; double missileSpeed = 4.0; double missileHealth = 100; public Missile(Pane layer, Image image, double x, double y) { super(layer, image, x, y, 0, 0, 0, 0, 1, 1); init(); } public Missile(Pane layer, Image image, double x, double y, double r, double dx, double dy, double dr, double health, double damage) { super(layer, image, x, y, r, dx, dy, dr, health, damage); init(); } private void init() { // initially move upwards => dy = -speed setDy( -missileSpeed); // limit missile alive time (consider it as fuel) setHealth( missileHealth); } public SpriteBase getTarget() { return target; } public void setTarget(SpriteBase target) { this.target = target; } /** * Find closest target * @param targetList */ public void findTarget( List targetList) { // we already have a target if( getTarget() != null) { return; } SpriteBase closestTarget = null; double closestDistance = 0.0; for (SpriteBase target: targetList) { if (!target.isAlive()) continue; //get distance between follower and target double distanceX = target.getCenterX() - getCenterX(); double distanceY = target.getCenterY() - getCenterY(); //get total distance as one number double distanceTotal = Math.sqrt(distanceX * distanceX + distanceY * distanceY); if (closestTarget == null) { closestTarget = target; closestDistance = distanceTotal; } else if (Double.compare(distanceTotal, closestDistance) < 0) { closestTarget = target; closestDistance = distanceTotal; } } setTarget(closestTarget); } @Override public void move() { SpriteBase follower = this; if( target != null) { //get distance between follower and target double distanceX = target.getCenterX() - follower.getCenterX(); double distanceY = target.getCenterY() - follower.getCenterY(); //get total distance as one number double distanceTotal = Math.sqrt(distanceX * distanceX + distanceY * distanceY); //calculate how much to move double moveDistanceX = this.turnRate * distanceX / distanceTotal; double moveDistanceY = this.turnRate * distanceY / distanceTotal; //increase current speed follower.dx += moveDistanceX; follower.dy += moveDistanceY; //get total move distance double totalmove = Math.sqrt(follower.dx * follower.dx + follower.dy * follower.dy); //apply easing follower.dx = missileSpeed * follower.dx/totalmove; follower.dy = missileSpeed * follower.dy/totalmove; } //move follower follower.x += follower.dx; follower.y += follower.dy; //rotate follower toward target double angle = Math.atan2(follower.dy, follower.dx); double degrees = Math.toDegrees(angle) + 90; follower.r = degrees; } @Override public void checkRemovability() { health--; // TODO: let it explode on health 0 if( Double.compare( health, 0) < 0) { setTarget(null); setRemovable(true); } } }
The parts you'll be most interested are the findTarget() method and the move() method. It's just basic math to calculate the distance and the angle.
Now let's integrate the missiles into our game, similar to the bullets.
Image playerMissileImage; ListplayerMissileList = new ArrayList<>(); private void loadResources() { ... // missiles playerMissileImage = new Image( getClass().getResource( "assets/bullets/missile.png").toExternalForm()); ... } private void createGameLoop() { gameLoop = new AnimationTimer() { @Override public void handle(long l) { ... // non-player AI // -------------------------- // for every missile find an enemy target for (Missile missile : playerMissileList) { missile.findTarget( enemyList); } // spawn bullets of players // --------------------------- for( Player player: playerList) { spawnSecondaryWeaponObjects( player); } // move sprites internally // --------------------------- ... moveSprites( playerMissileList); // move sprites on screen // --------------------------- ... updateSpritesUI(playerMissileList); // check if sprite can be removed // ------------------------------ ... checkRemovability( playerMissileList); // remove removables from list, layer, etc // ------------------------------ ... removeSprites( playerMissileList); } }; } private void spawnSecondaryWeaponObjects( Player player) { player.chargeSecondaryWeapon(); if( player.isFireSecondaryWeapon()) { Image image = playerMissileImage; double x = player.getSecondaryWeaponX() - image.getWidth() / 2.0; double y = player.getSecondaryWeaponY(); Missile missile = new Missile( bulletLayer, image, x, y); playerMissileList.add( missile); player.unchargeSecondaryWeapon(); } }
Now we have nice heat seeking missiles which turn as they follow the enemy. Awesome! The game looks like this:
No comments:
Post a Comment