塔防游戏:移动屏幕上的敌人

时间:2015-05-05 05:25:10

标签: image javafx move

我正在尝试使用javafx开发一个塔防游戏,我在如何制作它以使敌人在屏幕上移动时遇到了麻烦。我应该使用哪些类和方法来解决这个问题?

1 个答案:

答案 0 :(得分:1)

塔防游戏太多,无法覆盖SO。我有一点闲暇时间并修改了我在this thread创建的引擎。

这是游戏循环的主要类,其中加载游戏,检查输入,移动精灵,检查碰撞,更新分数等。与其他引擎相反,您不需要键盘输入。而是使用鼠标单击来定位塔。我添加了4个初始塔。

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.scene.text.TextBoundsType;
import javafx.stage.Stage;

public class Game extends Application {

    Random rnd = new Random();

    Pane playfieldLayer;
    Pane scoreLayer;

    Image playerImage;
    Image enemyImage;

    List<Tower> towers = new ArrayList<>();;
    List<Enemy> enemies = new ArrayList<>();;

    Text scoreText = new Text();
    int score = 0;

    Scene scene;

    @Override
    public void start(Stage primaryStage) {

        Group root = new Group();

        // create layers
        playfieldLayer = new Pane();
        scoreLayer = new Pane();

        root.getChildren().add( playfieldLayer);
        root.getChildren().add( scoreLayer);

        playfieldLayer.addEventFilter(MouseEvent.MOUSE_CLICKED, e -> {
            createTower(e.getX(), e.getY());
        });

        scene = new Scene( root, Settings.SCENE_WIDTH, Settings.SCENE_HEIGHT);

        primaryStage.setScene( scene);
        primaryStage.show();

        loadGame();

        createScoreLayer();
        createTowers();

        AnimationTimer gameLoop = new AnimationTimer() {

            @Override
            public void handle(long now) {

                // add random enemies
                spawnEnemies( true);

                // check if target is still valid
                towers.forEach( tower -> tower.checkTarget());

                // tower movement: find target
                for( Tower tower: towers) {
                    tower.findTarget( enemies);
                }

                // movement
                towers.forEach(sprite -> sprite.move());
                enemies.forEach(sprite -> sprite.move());

                // check collisions
                checkCollisions();

                // update sprites in scene
                towers.forEach(sprite -> sprite.updateUI());
                enemies.forEach(sprite -> sprite.updateUI());

                // check if sprite can be removed
                enemies.forEach(sprite -> sprite.checkRemovability());

                // remove removables from list, layer, etc
                removeSprites( enemies);

                // update score, health, etc
                updateScore();
            }

        };
        gameLoop.start();

    }

    private void loadGame() {
        playerImage = new Image( getClass().getResource("player.png").toExternalForm());
        enemyImage = new Image( getClass().getResource("enemy.png").toExternalForm());
    }

    private void createScoreLayer() {


        scoreText.setFont( Font.font( null, FontWeight.BOLD, 48));
        scoreText.setStroke(Color.BLACK);
        scoreText.setFill(Color.RED);

        scoreLayer.getChildren().add( scoreText);

        scoreText.setText( String.valueOf( score));

        double x = (Settings.SCENE_WIDTH - scoreText.getBoundsInLocal().getWidth()) / 2;
        double y = 0;
        scoreText.relocate(x, y);

        scoreText.setBoundsType(TextBoundsType.VISUAL);


    }
    private void createTowers() {

        // position initial towers
        List<Point2D> towerPositionList = new ArrayList<>();
        towerPositionList.add(new Point2D( 100, 200));
        towerPositionList.add(new Point2D( 100, 400));
        towerPositionList.add(new Point2D( 800, 200));
        towerPositionList.add(new Point2D( 800, 600));

        for( Point2D pos: towerPositionList) {

            createTower( pos.getX(), pos.getY());

        }

    }

    private void createTower( double x, double y) {

        Image image = playerImage;

        // center image at position
        x -= image.getWidth() / 2;
        y -= image.getHeight() / 2;

        // create player
        Tower player = new Tower(playfieldLayer, image, x, y, 0, 0, 0, 0, Settings.PLAYER_SHIP_HEALTH, 0, Settings.PLAYER_SHIP_SPEED);

        // register player
        towers.add( player);

    }

    private void spawnEnemies( boolean random) {

        if( random && rnd.nextInt(Settings.ENEMY_SPAWN_RANDOMNESS) != 0) {
            return;
        }

        // image
        Image image = enemyImage;

        // random speed
        double speed = rnd.nextDouble() * 1.0 + 2.0;

        // x position range: enemy is always fully inside the screen, no part of it is outside
        // y position: right on top of the view, so that it becomes visible with the next game iteration
        double x = rnd.nextDouble() * (Settings.SCENE_WIDTH - image.getWidth());
        double y = -image.getHeight();

        // create a sprite
        Enemy enemy = new Enemy( playfieldLayer, image, x, y, 0, 0, speed, 0, 1,1);

        // manage sprite
        enemies.add( enemy);

    }

    private void removeSprites(  List<? extends SpriteBase> spriteList) {
        Iterator<? extends SpriteBase> iter = spriteList.iterator();
        while( iter.hasNext()) {
            SpriteBase sprite = iter.next();

            if( sprite.isRemovable()) {

                // remove from layer
                sprite.removeFromLayer();

                // remove from list
                iter.remove();
            }
        }
    }

    private void checkCollisions() {

        for( Tower tower: towers) {
            for( Enemy enemy: enemies) {
                if( tower.hitsTarget( enemy)) {

                    enemy.getDamagedBy( tower);

                    // TODO: explosion
                    if( !enemy.isAlive()) {

                        enemy.setRemovable(true);

                        // increase score
                        score++;

                    }
                }
            }
        }
    }

    private void updateScore() {
        scoreText.setText( String.valueOf( score));
    }

    public static void main(String[] args) {
        launch(args);
    }

}

然后你需要一个基础类为你的精灵。你可以将它用于敌人和塔楼。

import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;

public abstract class SpriteBase {

    Image image;
    ImageView imageView;

    Pane layer;

    double x;
    double y;
    double r;

    double dx;
    double dy;
    double dr;

    double health;
    double damage;

    boolean removable = false;

    double w;
    double h;

    boolean canMove = true;

    public SpriteBase(Pane layer, Image image, double x, double y, double r, double dx, double dy, double dr, double health, double damage) {

        this.layer = layer;
        this.image = image;
        this.x = x;
        this.y = y;
        this.r = r;
        this.dx = dx;
        this.dy = dy;
        this.dr = dr;

        this.health = health;
        this.damage = damage;

        this.imageView = new ImageView(image);
        this.imageView.relocate(x, y);
        this.imageView.setRotate(r);

        this.w = image.getWidth(); // imageView.getBoundsInParent().getWidth();
        this.h = image.getHeight(); // imageView.getBoundsInParent().getHeight();

        addToLayer();

    }

    public void addToLayer() {
        this.layer.getChildren().add(this.imageView);
    }

    public void removeFromLayer() {
        this.layer.getChildren().remove(this.imageView);
    }

    public Pane getLayer() {
        return layer;
    }

    public void setLayer(Pane layer) {
        this.layer = layer;
    }

    public double getX() {
        return x;
    }

    public void setX(double x) {
        this.x = x;
    }

    public double getY() {
        return y;
    }

    public void setY(double y) {
        this.y = y;
    }

    public double getR() {
        return r;
    }

    public void setR(double r) {
        this.r = r;
    }

    public double getDx() {
        return dx;
    }

    public void setDx(double dx) {
        this.dx = dx;
    }

    public double getDy() {
        return dy;
    }

    public void setDy(double dy) {
        this.dy = dy;
    }

    public double getDr() {
        return dr;
    }

    public void setDr(double dr) {
        this.dr = dr;
    }

    public double getHealth() {
        return health;
    }

    public double getDamage() {
        return damage;
    }

    public void setDamage(double damage) {
        this.damage = damage;
    }

    public void setHealth(double health) {
        this.health = health;
    }

    public boolean isRemovable() {
        return removable;
    }

    public void setRemovable(boolean removable) {
        this.removable = removable;
    }

    public void move() {

        if( !canMove)
            return;

        x += dx;
        y += dy;
        r += dr;

    }

    public boolean isAlive() {
        return Double.compare(health, 0) > 0;
    }

    public ImageView getView() {
        return imageView;
    }

    public void updateUI() {

        imageView.relocate(x, y);
        imageView.setRotate(r);

    }

    public double getWidth() {
        return w;
    }

    public double getHeight() {
        return h;
    }

    public double getCenterX() {
        return x + w * 0.5;
    }

    public double getCenterY() {
        return y + h * 0.5;
    }

    // TODO: per-pixel-collision
    public boolean collidesWith( SpriteBase otherSprite) {

        return ( otherSprite.x + otherSprite.w >= x && otherSprite.y + otherSprite.h >= y && otherSprite.x <= x + w && otherSprite.y <= y + h);

    }

    /**
     * Reduce health by the amount of damage that the given sprite can inflict
     * @param sprite
     */
    public void getDamagedBy( SpriteBase sprite) {
        health -= sprite.getDamage();
    }

    /**
     * Set health to 0
     */
    public void kill() {
        setHealth( 0);
    }

    /**
     * Set flag that the sprite can be removed from the UI.
     */
    public void remove() {
        setRemovable(true);
    }

    /**
     * Set flag that the sprite can't move anymore.
     */
    public void stopMovement() {
        this.canMove = false;
    }

    public abstract void checkRemovability();

}

塔是精灵基类的子类。在这里你需要一点点数学运算,因为你希望塔能够向敌人旋转,当敌人在范围内时让塔点火。

import java.util.List;

import javafx.scene.effect.ColorAdjust;
import javafx.scene.image.Image;
import javafx.scene.layout.Pane;

public class Tower extends SpriteBase {

    SpriteBase target; // TODO: use weakreference

    double turnRate = 0.6;

    double speed;

    double targetRange = 300; // distance within tower can lock to enemy

    ColorAdjust colorAdjust;

    double rotationLimitDeg=0.0;
    double rotationLimitRad =  Math.toDegrees( this.rotationLimitDeg);
    double roatationEasing = 10;
    double targetAngle = 0;
    double currentAngle = 0;

    boolean withinFiringRange = false;

    public Tower(Pane layer, Image image, double x, double y, double r, double dx, double dy, double dr, double health, double damage, double speed) {

        super(layer, image, x, y, r, dx, dy, dr, health, damage);

        this.speed = speed;

        this.setDamage(Settings.TOWER_DAMAGE);

        init();
    }


    private void init() {

        // red colorization (simulate "angry")
        colorAdjust = new ColorAdjust();
        colorAdjust.setContrast(0.0);
        colorAdjust.setHue(-0.2);

    }

    @Override
    public void move() {

        SpriteBase follower = this;

        // reset within firing range
        withinFiringRange = false;

        // rotate towards target
        if( target != null)
        {
            // parts of code used from shane mccartney (http://lostinactionscript.com/page/3/)
            double xDist = target.getCenterX() - follower.getCenterX();
            double yDist = target.getCenterY() - follower.getCenterY();

            this.targetAngle = Math.atan2(yDist, xDist) - Math.PI / 2;

            this.currentAngle = Math.abs(this.currentAngle) > Math.PI * 2 ? (this.currentAngle < 0 ? (this.currentAngle % Math.PI * 2 + Math.PI * 2) : (this.currentAngle % Math.PI * 2)) : (this.currentAngle);
            this.targetAngle = this.targetAngle + (Math.abs(this.targetAngle - this.currentAngle) < Math.PI ? (0) : (this.targetAngle - this.currentAngle > 0 ? ((-Math.PI) * 2) : (Math.PI * 2)));
            this.currentAngle = this.currentAngle + (this.targetAngle - this.currentAngle) / roatationEasing;  // give easing when rotation comes closer to the target point

            // check if the rotation limit has to be kept
            if( (this.targetAngle-this.currentAngle) > this.rotationLimitRad) {
                this.currentAngle+=this.rotationLimitRad;
            } else if( (this.targetAngle-this.currentAngle) < -this.rotationLimitRad) {
                this.currentAngle-=this.rotationLimitRad;
            }

            follower.r = Math.toDegrees(currentAngle);

            // determine if the player ship is within firing range; currently if the player ship is within 10 degrees (-10..+10)
            withinFiringRange = Math.abs( Math.toDegrees( this.targetAngle-this.currentAngle)) < 20;

        } 

        super.move();

    }

    public void checkTarget() {

        if( target == null) {
            return;
        }


        if( !target.isAlive() || target.isRemovable()) {
            setTarget( null);
            return;
        }

        //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( Double.compare( distanceTotal, targetRange) > 0) {
            setTarget( null);
        }

    }

    public void findTarget( List<? extends SpriteBase> 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);            

            // check if enemy is within range
            if( Double.compare( distanceTotal, targetRange) > 0) {
                continue;
            }

            if (closestTarget == null) {

                closestTarget = target;
                closestDistance = distanceTotal;

            } else if (Double.compare(distanceTotal, closestDistance) < 0) {

                closestTarget = target;
                closestDistance = distanceTotal;

            }
        }

        setTarget(closestTarget);

    }

    public SpriteBase getTarget() {
        return target;
    }

    public void setTarget(SpriteBase target) {
        this.target = target;
    }



    @Override
    public void checkRemovability() {

        if( Double.compare( health, 0) < 0) {
            setTarget(null);
            setRemovable(true);
        }

    }

    public boolean hitsTarget( SpriteBase enemy) {

        return target == enemy && withinFiringRange;

    }

    public void updateUI() {

        if( withinFiringRange) {
            imageView.setEffect(colorAdjust);
        } else {
            imageView.setEffect(null);
        }

        super.updateUI();

    }
}

敌人阶级更容易。它只需要运动。但是,在你的最终版本中,敌人应该在移动过程中考虑障碍物。在这个例子中,我在敌人上方添加了一个健康栏来显示健康状况。

import javafx.scene.image.Image;
import javafx.scene.layout.Pane;

public class Enemy extends SpriteBase {

    HealthBar healthBar;

    double healthMax;

    public Enemy(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);

        healthMax = Settings.ENEMY_HEALTH;

        setHealth(healthMax);

    }

    @Override
    public void checkRemovability() {

        if( Double.compare( getY(), Settings.SCENE_HEIGHT) > 0) {
            setRemovable(true);
        }

    }

    public void addToLayer() {

        super.addToLayer();

        // create health bar; has to be created here because addToLayer is called in super constructor
        // and it wouldn't exist yet if we'd create it as class member
        healthBar = new HealthBar();

        this.layer.getChildren().add(this.healthBar);

    }

    public void removeFromLayer() {

        super.removeFromLayer();

        this.layer.getChildren().remove(this.healthBar);

    }

    /**
     * Health as a value from 0 to 1.
     * @return
     */
    public double getRelativeHealth() {
        return getHealth() / healthMax;
    }


    public void updateUI() {

        super.updateUI();

        // update health bar
        healthBar.setValue( getRelativeHealth());

        // locate healthbar above enemy, centered horizontally
        healthBar.relocate(x + (imageView.getBoundsInLocal().getWidth() - healthBar.getBoundsInLocal().getWidth()) / 2, y - healthBar.getBoundsInLocal().getHeight() - 4);      
    }
}

健康栏

import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.StrokeType;

public class HealthBar extends Pane {

    Rectangle outerHealthRect;
    Rectangle innerHealthRect;

    public HealthBar() {

        double height = 10;

        double outerWidth = 60;
        double innerWidth = 40;

        double x=0.0;
        double y=0.0;

        outerHealthRect = new Rectangle( x, y, outerWidth, height);
        outerHealthRect.setStroke(Color.BLACK);
        outerHealthRect.setStrokeWidth(2);
        outerHealthRect.setStrokeType( StrokeType.OUTSIDE);
        outerHealthRect.setFill(Color.RED);

        innerHealthRect = new Rectangle( x, y, innerWidth, height);
        innerHealthRect.setStrokeType( StrokeType.OUTSIDE);
        innerHealthRect.setFill(Color.LIMEGREEN);

        getChildren().addAll( outerHealthRect, innerHealthRect);

    }

    public void setValue( double value) {
        innerHealthRect.setWidth( outerHealthRect.getWidth() * value);
    }

}

然后你需要一些像这样的全局设置

public class Settings {

    public static double SCENE_WIDTH = 1024;
    public static double SCENE_HEIGHT = 768;

    public static double TOWER_DAMAGE = 1;

    public static double PLAYER_SHIP_SPEED = 4.0;
    public static double PLAYER_SHIP_HEALTH = 100.0;

    public static int ENEMY_HEALTH = 100;
    public static int ENEMY_SPAWN_RANDOMNESS = 50;

}

这些是图片:

player.png

enter image description here

enemy.png

enter image description here

总结现在的游戏玩法:

  • 点击屏幕放置一个塔(即笑脸)
  • 当一个敌人在射程内时,笑脸会变得生气(我只是将颜色改为红色),在你的最终版本中,塔会被射击
  • 只要塔向敌人射击,健康就会减少。我是通过根据健康状况更改健康栏来实现的。
  • 当健康状况耗尽时,敌人将被杀死,分数会更新;你必须添加爆炸

总而言之,制作塔防游戏并不是那么容易。希望它有助于开始。

Heres'截图:

enter image description here