相信不少人都玩过太空大战,版本不同,原理相同。没玩过的看下图了解一下:
首先先分解一下游戏的构成:
- 大批敌军
- 子弹
- 保卫机
这三个角色之间基本的互动请看流程图
基本流程图
以上是基本的操作流程图,具体实现的源码将在文章最后的小结
部分给大家
代码实现难点讲解
我把我认为可能需要考虑的问题归类如下:
-
如何实现并发?
- 实现多线程总是绕不开
Runnable
和Thread
,但是我们今天不调用Thread,只用runnable,而且不是继承。除此之外,今天用的package不是大家常知的java.awt.*
而是javafx.scene.*
。
- 实现多线程总是绕不开
-
三个角色运动状态不同,如何初始化用同一个类调用?
- 很简单,虽然运动轨迹不同,但是基本的变量:当前位置,速度,尺寸都是可以从同一个构造函数初始的。这里我们介绍以下源码作者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]);
}
}
}
界面效果
小编之前说了,如果你想引用现有的图像做你的角色....
原理和上面的代码差不多,不拼积木肯定比拼积木轻松的,所以我们只要创造图像类。
以下是小编的源码
/**
* @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]);
}
}
}
界面效果
补充
最后至于图形界面框那部分,以前鼠标操控的办法,请参考原作者的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 finneyj
Oracle-avafx.scene.image Javadoc:javafx.scene.image