本文将说一下我在初步学习制作多线程游戏和使用缓冲绘图过程中的一些感悟,欢迎各位指导!
我把主体都写在了Game.java 这个类中。
首先,我先做了一个窗体JFrame ,用来显示整体的布局。
JSQFrame.java
import javax.swing.JFrame;
public class JSQFrame extends JFrame {
public void initFrame() {
this.setTitle("Game");
this.setSize(800, 600);
this.setDefaultCloseOperation(3);
this.setLocationRelativeTo(null);
this.setVisible(true);
}
}
在Game.java 中用初始化的方法做好窗体。
jf = new JSQFrame();
jf.initFrame();
下面开始绘图的准备工作。
首先设计的是,一个移动的背景、一个人物、一只移动的鸟。
背景:恒定移动速率向左运动
人物:绘制但不移动位置
鸟:绘制,上下移动
经过归纳,发现以上事件需要的是“绘制”和“移动”两种方法。只是在具体实现中存在差异。所以,可以把不同对象实现两种方法分别的放到不同的队列里。绘制和移动可以用接口来实现。
Drawable.java
import java.awt.Graphics;
public interface Drawable {
public void draw(Graphics g);
}
Moveable.java
public interface Moveable {
public void move();
}
用 GameData.java 来创建两个模板
public class GameData {
public static ArrayList<Drawable> drawList=new ArrayList<Drawable>();
public static ArrayList<Moveable> movelist=new ArrayList<Moveable>();
}
创建完毕之后,可以在Game.java 中开始把元素对象加入到这两个队列中了。
Background b = new Background();
GameData.drawList.add(b);
GameData.movelist.add(b);
GameData.drawList.add(new Person());
Bird bird = new Bird();
GameData.drawList.add(bird);
GameData.movelist.add(bird);
对于移动而言,由于各个元素之间的移动是需要同时进行的,所以需要用多进程。创建一个MoveThread.java 来实现多线程。
public class MoveThread extends Thread{
public void run(){
while(true){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i=0;i<GameData.movelist.size();i++){
Moveable m=GameData.movelist.get(i);
m.move();
}
}
}
}
在Game.java 中创建并运行。
new MoveThread().start();
由于各种元素对象的移动方式不同,所以需要创建不同的类来分别补全move()方法。
(1)背景Background
在这里使用了引入图片,javax.swing.ImageIcon 对象名 =new ImageIcon("路径");
Background.java
import java.awt.Graphics;
import javax.swing.ImageIcon;
public class Background implements Drawable,Moveable{
ImageIcon bjIcon =new ImageIcon("images/backg.png");
int x,y;
public void draw(Graphics g){
g.drawImage(bjIcon.getImage(),x,y,null);
g.drawImage(bjIcon.getImage(),x+bjIcon.getIconWidth(),y,null);
}
public void move(){
x-=2;
if(x+bjIcon.getIconWidth()<=0){
x=0;
}
}
}
(2)人物Person
Person.java
import java.awt.Graphics;
import javax.swing.ImageIcon;
public class Person implements Drawable{
ImageIcon personIcon=new ImageIcon("images/person.gif");
public void draw(Graphics g){
g.drawImage(personIcon.getImage(), 200,360,null);
}
}
(3)鸟Bird
Bird.java
import java.awt.Graphics;
import javax.swing.ImageIcon;
public class Bird implements Drawable,Moveable{
ImageIcon icon =new ImageIcon("images/bird.gif");
int y=100;
int vy=3;
public void draw(Graphics g){
g.drawImage(icon.getImage(),400,y,null);
}
public void move(){
y+=vy;
if(y>200)
vy=-3;
if(y<100)
vy=3;
}
}
通过用不同方式对draw方法进行补全,我们得到了三种移动方法。
最后就要执行draw方法了。
Game.java 中执行
Draw d = new Draw(jf);
while (true) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
d.draw(jf.getGraphics());
}
用Draw类创建对象并运行draw方法,需要传入画布。使用线程休眠来控制帧。
由于我做的这个程序数据量很大,绘图可能需要一些时间才能完成,所以会出现闪烁现象,为了解决这些问题,下面采用缓冲绘图来实现。在内存中创建一个与屏幕绘图区域一致的对象,先将图形绘制到内存中的这个对象上,再一次性将这个对象上的图形拷贝到屏幕上,这样能大大加快绘图的速度。从而避免背景色填充,出现闪烁。双缓冲实现过程如下:
1、在内存中创建与画布一致的缓冲区
2、在缓冲区画图
3、将缓冲区位图拷贝到当前画布上
4、释放内存缓冲区
先由Game传来JFrame,然后用构造器方法接收。这样做是为了下面获取窗体宽高、背景色。
private int x,y;
private JFrame jf;
public Draw(JFrame jf){
this.jf=jf;
}
draw方法需要Game传入画布。
public void draw(Graphics g)
使用图像缓冲,参数分别代表宽、高、该图像具有整数像素的 8 位 RGB 颜色
BufferedImage buffer = new BufferedImage(
jf.getWidth(),
jf.getHeight(),
BufferedImage.TYPE_INT_RGB
);
获取图像缓冲的画布
Graphics gg = buffer.getGraphics();
默认全部填充背景色
gg.setColor(jf.getBackground());
gg.fillRect(0, 0, buffer.getWidth(), buffer.getHeight());
用for循环来画出队列中全部的元素。
for(int i=0; i<GameData.drawList.size(); i++){
Drawable d = GameData.drawList.get(i);
d.draw(gg);
}
最后,拷贝到当前画布上即可。
g.drawImage(buffer, 0, 0, null);
最后写出main,完成。
public static void main(String[] args) {
new Game().initUI();
}
通过本程序,初步的学习了Java编程的整体思路,通过对任务的分块来逐步实现功能。同时学习了缓冲绘图的方法,可以用到之后的程序中,使界面更加的流畅。