相信不少人都玩过太空大战,版本不同,原理相同。没玩过的看下图了解一下:

首先先分解一下游戏的构成:

  1. 大批敌军
  2. 子弹
  3. 保卫机

这三个角色之间基本的互动请看流程图

基本流程图

射击游戏模块架构图片 射击游戏流程图_i++


以上是基本的操作流程图,具体实现的源码将在文章最后的小结部分给大家

代码实现难点讲解

我把我认为可能需要考虑的问题归类如下:

  1. 如何实现并发?
  • 实现多线程总是绕不开RunnableThread,但是我们今天不调用Thread,只用runnable,而且不是继承。除此之外,今天用的package不是大家常知的java.awt.*而是javafx.scene.*
  1. 三个角色运动状态不同,如何初始化用同一个类调用?
  • 很简单,虽然运动轨迹不同,但是基本的变量:当前位置,速度,尺寸都是可以从同一个构造函数初始的。这里我们介绍以下源码作者Joe Finney的构造办法:
public class Shape
{
    //在这之前你需要要个最基础的图形构造类,除非你不想创造复杂的图形或者直接想调用图像
    //这边的长方形可以理解位积木
    private Rectangle[] p = new Rectangle[50];
    private int number = 0;

    private double xPosition = 0.0;
    private double yPosition = 0.0;
    //给图形声明初始的位置,需要考虑一下原本图像的尺寸,位置太高或太低都会看不到完整的图形
    public Shape(double x, double y)
    {
        this.xPosition = x;
        this.yPosition = y;
    }
    //把新的积木拼到原有的积木上,也可以使用arraylist实现
    public void addRectangle(Rectangle r)
    {
        if (number < p.length)
        {
            p[number] = p;
            number++;
        }    
    }
    //以上都是静态的而且不会在画面上显示
    //addShapeTo方法帮助我们把积木们放到屏幕里
    public void addShapeTo(GameArena a)
    {
        for (int i = 0 ; i < number; i++)
        {
            p[i].setXPosition(p[i].getXPosition() + xPosition);
            p[i].setYPosition(p[i].getYPosition() + yPosition);
            a.addRectangle(p[i]);
        }
    }
    //移动积木堆们,根据我们给定的速度。积木堆们以整体的形式运动
    public void move(double x, double y)
    {
        xPosition += x;
        yPosition += y;

        for (int i=0; i<numberParts; i++)
        {
            p[i].setXPosition(p[i].getXPosition() + x);//注意,在屏幕里表示向右
            p[i].setYPosition(p[i].getYPosition() + y);//注意,在屏幕里表示向下
        }
    }
    //如名字所写,带有目的性,表示直接移动到指定的位置
    //在这边的作用是子弹打出去后最后又回到原来的位置,因为速度很快,所以屏幕上看不到回来的效果
    //感觉子弹好辛苦,但是不想给子弹单独建个数组了,麻烦子弹了...
    public void moveTo(double x, double y)
    {
        move(x-xPosition, y-yPosition);
        xPosition = x;
        yPosition = y;
    }
    //获取第i个积木
    public Rectangle getP(int i)
    {
        return p[i];
    }
    //判断两个积木快是否相撞
    boolean collides (Shape c)
    {
        for (int i=0; i<number; i++)
        {
            for (int j=0; j<c.number; j++)
            {
                if (p[i].collides(c.getP(j)))//这个调用的是Rectangle里的方法,不是这个类里的
                    return true;
            }
        }

        return false;
    }

    public double getXPosition()
    {
        return xPosition;
    }

    public double getYPosition()
    {
        return yPosition;
    }

    public void removeShapeFrom(GameArena a)
    {
        for (int i=0; i<number; i++)
        {
            a.removeRectangle(p[i]);
        }
    }
}

界面效果

射击游戏模块架构图片 射击游戏流程图_游戏_02

小编之前说了,如果你想引用现有的图像做你的角色....

射击游戏模块架构图片 射击游戏流程图_java_03

原理和上面的代码差不多,不拼积木肯定比拼积木轻松的,所以我们只要创造图像类。
以下是小编的源码

/**
* @author Hephaest
*/
//没错!什么类都没import!
public class ImageView 
{
    private double xPosition;            
    private double yPosition;            
    private double width;                
    private double height;                
    private String url;
    private ImageView[] part = new ImageView[100];
    private int numberPart = 0;

    public double getXPosition()
    {
        return xPosition;
    }


    public double getYPosition()
    {
        return yPosition;
    }


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

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

    public double getWidth()
    {
        return width;
    }

    public double getHeight()
    {
        return height;
    }

    public String getUrl()
    {
        return url;
    }
    //定义图像的时候需要位置,尺寸,还有哪里来的图
    public ImageView(double x, double y, double w, double h, String url)
    {
        xPosition = x;
        yPosition = y;
        width = w;
        height = h;
        this.url = url;
    }    
    //碰撞的算法小编没优化
    public boolean collides(ImageView i)
    {
        return (xPosition < i.xPosition + i.width &&
            xPosition + width > i.xPosition &&
            yPosition < i.yPosition + i.height &&
            yPosition + height > i.yPosition);
    }
    //运动的方法和之前的一致
    public void move(double x, double y)
    {
        xPosition += x;
        yPosition += y;
        for (int i=0; i<numberPart; i++)
        {
            part[i].setXPosition(part[i].getXPosition() + x);
            part[i].setYPosition(part[i].getYPosition() + y);
        }
    }
    
    public void moveTo(double x, double y)
    {
        move(x-xPosition, y-yPosition);
        xPosition = x;
        yPosition = y;
    }
    
    public void removeShapeFrom(GameArena a)
    {
        for (int i=0; i<numberPart; i++)
        {
            a.removeImage(part[i]);
        }
    }
}

界面效果

补充

射击游戏模块架构图片 射击游戏流程图_游戏_04

最后至于图形界面框那部分,以前鼠标操控的办法,请参考原作者的javafx使用办法。小编为了利用javafx实现图像移动绞尽脑汁。不过最终还是找到办法了:

/**
*@author Joe Finney
*override by hephaest
*/
//用hashmap 创造ImageView的数组对象,因为ImageView是节点
  private Map<ImageView, javafx.scene.image.ImageView> images = new HashMap<>();
  @override
  private void frameUpdate ()
    {
        if (!this.exiting)
        {
            // Remove any deleted objects from the scene.
            synchronized (this)
            {
                for (Object o: removeList)
                {
                    if (o instanceof Rectangle)
                    {
                        Rectangle r = (Rectangle) o;
                        javafx.scene.shape.Rectangle rectangle = rectangles.get(r);
                        root.getChildren().remove(rectangle);

                        rectangles.remove(r);
                    }
                    //我自己添加的
                    if (o instanceof ImageView)
                    {
                        ImageView i = (ImageView) o;
                        javafx.scene.image.ImageView image = images.get(i);
                        root.getChildren().remove(image);
    
                        images.remove(i);
                    }
                }

                removeList.clear();

                // Add any new objects to the scene.
                for (Object o: addList)
                {
                    if (o instanceof Rectangle)
                    {
                        Rectangle r = (Rectangle) o;
                        javafx.scene.shape.Rectangle rectangle = new javafx.scene.shape.Rectangle(0, 0, r.getWidth(), r.getHeight());
                        root.getChildren().add(rectangle);
                        rectangles.put(r, rectangle);
                    }
                    //我自己添加的
                    if (o instanceof ImageView)
                    {
                        ImageView i = (ImageView) o;
                        javafx.scene.image.ImageView image = new javafx.scene.image.ImageView(i.getUrl());
                        root.getChildren().add(image);
                        images.put(i, image);
                    }
                }

                addList.clear();
            }

            for(Map.Entry<Rectangle, javafx.scene.shape.Rectangle> entry : rectangles.entrySet())
            {
                Rectangle r = entry.getKey();
                javafx.scene.shape.Rectangle rectangle = entry.getValue();

                rectangle.setTranslateX(r.getXPosition() - r.getWidth()/2);
                rectangle.setTranslateY(r.getYPosition() - r.getHeight()/2);
                rectangle.setFill(getColourFromString(r.getColour()));
            }
            //我自己添加的
            for(Map.Entry<ImageView, javafx.scene.image.ImageView> entry : images.entrySet())
            {
                ImageView i = entry.getKey();
                javafx.scene.image.ImageView image = entry.getValue();
                image.setTranslateX(i.getXPosition() - i.getWidth()/2);
                image.setTranslateY(i.getYPosition()- i.getHeight()/2);
                image.setImage(new Image(i.getUrl()));
            }
        }
    }

除此之外,addImage和removeaddImage只要改个变量名字就好了。

小结

这几天摸索的经验总结:

  • API很重要!学会查javadoc!
  • 如果想做游戏开发还是用java.awt.*吧

相关代码分享

github完整源码链接:Thanks to finneyjOracle-avafx.scene.image Javadoc:javafx.scene.image