`
stchou
  • 浏览: 205215 次
  • 性别: Icon_minigender_1
  • 来自: 长沙
社区版块
存档分类
最新评论

JAVA双缓冲机制

阅读更多

以前都不了解双缓冲是什么个东东,

 

但是为了解决我的坦克大战疯狂的闪屏

 后,终于对其有了一个了解。

package My2;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.List;

import javax.tools.JavaCompiler;




public class tankGame extends java.awt.Frame implements java.awt.event.KeyListener{
    //窗体宽度
    public static final int WINDOW_WIDTH = 800;
    //窗体高度
    public static final int WINDOW_HEIGHT = 600;
    //窗体刷新率
    public static final int REFRESH_RATE = 50;
    //玩家坦克
    static Tank myTank = null;
    // 坦克起始位置左上角坐标
    public static final int Tank_X = 385;
    public static final int Tank_Y = 550;
    //敌方坦克同时出现的最大数量
    private int tankMaxNum = 10;
    //当前敌方坦克数量
    private int tankNum = 0;
    //存放敌方坦克的容器
    static List<Tank> enemyTanks = new ArrayList<Tank>();
    //存放墙的容器
    static List<Wall> wallList = new ArrayList<Wall>();
    static int wallNum=35;
    //屏幕画布
	static java.awt.Graphics g = null;
	Image offScreenImage = null;

    
    
	public static void main(String[] args){
		tankGame game = new tankGame();
		
		game.showUI();
		game.Init();
		game.addKeyListener(game);
	}
	
	
	public void paint(java.awt.Graphics g){
		drawTank();
	}
	
	
	public static void drawTank(){

		if(myTank==null) return ;
		g.fillRect(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);	
		myTank.draw(g);
		for (int i=0;i<enemyTanks.size();i++){
			enemyTanks.get(i).draw(g);
		}
		
		for (int i=0;i<wallList.size();i++){
			wallList.get(i).draw(g);
		}
	}
	
	public void Init(){
		//加入我方坦克
		myTank = new Tank(Tank_X,Tank_Y,true,0);
		myTank.draw(g);
		
        //加入敌方坦克
        for (int i = 0; i < tankMaxNum; i++) {
            Tank enemyTank = new Tank(200 + 50 * i, 100, false, 1);
            enemyTanks.add(enemyTank);
            tankNum++;
        }
        
//        加入城墙
        for (int i=0;i<wallNum;i++){
        	Wall wall = new Wall(100+i*18,300);
        	wallList.add(wall);
        	wall = new Wall(100+i*18,318);
        	wallList.add(wall);
        }
		
		 // 启动重画线程
        PaintThread pt = new PaintThread();
        Thread t = new Thread(pt);
        t.start();
	}
	
	/**
	 * 显示窗体
	 */
	public void showUI(){
		this.setTitle("我打坦克大战");
		this.setLocation(200, 100);
		this.setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
	       this.setResizable(false);
	        this.addWindowListener(
	            new WindowAdapter() {
	                public void windowClosing(WindowEvent e) {
	                    setVisible(false);
	                    System.exit(0);
	                }
	            }
	        );
	    this.setBackground(Color.BLACK);
		this.setVisible(true);
		g=this.getGraphics();
	}
	
	
	  /**
     * 重写update方法,先将窗体上的图形画在图片对象上,再一次性显示
     */
//    public void update(Graphics g) {
//    	 if (offScreenImage == null) {
//    		   offScreenImage = this.createImage(WINDOW_WIDTH,
//    				   WINDOW_HEIGHT);
//    		  }
//    		  Graphics gOffScreen = offScreenImage.getGraphics();// 获取图片内的所有图形,形成虚拟窗口
//    		  Color c = gOffScreen.getColor();
//    		  gOffScreen.setColor(Color.BLACK);// 设置屏幕窗口的颜色
//    		  gOffScreen.fillRect(0, 0, WINDOW_WIDTH,WINDOW_HEIGHT);
//    		  gOffScreen.setColor(c);
//    		  paint(gOffScreen); // 把虚拟窗口画在图片上
//    		  g.drawImage(offScreenImage, 0, 0, null);// 把图片画在窗口上
//    	//	  drawTank();
//
//   }
	
	
	 /**
     * 用线程重画,每隔一段时间重画窗体
     * @author Magci
     *
     */
    public class PaintThread implements Runnable {

        /**
         * 每隔REFRESH_RATE毫秒重画一次窗体
         */
        public void run() {
            while (true) {
            	repaint();
                try {
                    Thread.sleep(REFRESH_RATE);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
	public void keyPressed(KeyEvent e) {
		if(e.getKeyCode()==java.awt.event.KeyEvent.VK_UP){//如果按键为向上键
			myTank.move(0);//向上
		}
		if(e.getKeyCode()==java.awt.event.KeyEvent.VK_DOWN){//如果按键为向下键
			myTank.move(1);	
		}
		if(e.getKeyCode()==java.awt.event.KeyEvent.VK_LEFT){//如果按键为向左键
			myTank.move(2);	
		}
		if(e.getKeyCode()==java.awt.event.KeyEvent.VK_RIGHT){//如果按键为向右键
			myTank.move(3);
		}if(e.getKeyCode()==java.awt.event.KeyEvent.VK_SPACE){
			myTank.fire(g);
		}
		//drawTank();
	}


	public void keyReleased(KeyEvent e) {
	}


	public void keyTyped(KeyEvent e) {
	}

}

 

 

能是能画出一个坦克,但是闪得你受不了,

 

其主要的原因是,要了解paint闪烁的原因

  每一个paint的过后,程序会自行的调用repaint的方法,但是repaint方法中的绘制有的分配一个与原来窗口一样的的内存空间,但里面是没有存储东西的,所以一次次的paint,repaint的交替就会产生闪烁

 

解决方法:双缓冲技术的工作原理:先在内存中分配一个和窗口一样大的空间(在内存中的空间我门是看不到的),然后利用getGraphics()方法去获得该空间并将它全部一次性的显示到屏幕上.这样显示出来就非常的流畅了.避免了闪烁效果.

package My4;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.List;

public class TankClient extends Frame implements java.awt.event.KeyListener{

    public static final int WINDOW_WIDTH = 800;
    public static final int WINDOW_HEIGHT = 600;
    //窗体刷新率
    public static final int REFRESH_RATE = 50;
    Image offScreenImage = null;
    //屏幕画布
	static java.awt.Graphics g = null;
    //玩家坦克
    static Tank myTank = null;
    // 坦克起始位置左上角坐标
    public static final int Tank_X = 385;
    public static final int Tank_Y = 550;
    //敌方坦克同时出现的最大数量
    private int tankMaxNum = 10;
    //当前敌方坦克数量
    private int tankNum = 0;
    //存放敌方坦克的容器
    static List<Tank> enemyTanks = new ArrayList<Tank>();
    //存放墙的容器
    static List<Wall> wallList = new ArrayList<Wall>();
    static int wallNum=35;
    //草容器
    static List<Grass> grassList = new ArrayList<Grass>();
    //方块容器
    static List<Stone> stoneList = new ArrayList<Stone>();
    
    static boolean GameBegin=false;
    
    public static void main(String[] args) {

        // 创建一个窗体
    	TankClient main =new TankClient();
    	main.showUI();
    	main.addKeyListener(main);
    }

    public void showUI() {
    	this.setTitle("我打坦克大战");
        this.setBounds(200, 100, WINDOW_WIDTH, WINDOW_HEIGHT);
        this.setResizable(false);
        this.setBackground(Color.GREEN);
        this.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                setVisible(false);
                System.exit(0);
            }
        });
        this.setVisible(true);
        g=this.getGraphics();

    }

    
    
	public void Init(){
		if(GameBegin==false){
			return;
		}
		//加入我方坦克
		myTank = new Tank(Tank_X,Tank_Y,true,0);
		myTank.draw(g);
		//清空各种容器
		enemyTanks.clear();
		wallList.clear();
		grassList.clear();
		stoneList.clear();
        //加入敌方坦克
        for (int i = 0; i < tankMaxNum; i++) {
            Tank enemyTank = new Tank(200 + 50 * i, 50, false, 1);
            enemyTanks.add(enemyTank);
            enemyTanks.get(i).Go();
            tankNum++;
        }
        
        //加入城墙
        for (int i=0;i<10;i++){
        	Wall wall = new Wall(100+i*18,100);
        	wallList.add(wall);
        	wall = new Wall(100+i*18,118);
        	wallList.add(wall);
        }
        for (int i=0;i<16;i++){
        	Wall wall = new Wall(450+i*18,100);
        	wallList.add(wall);
        	wall = new Wall(450+i*18,118);
        	wallList.add(wall);
        }
        for (int i=1;i<=13;i++){
        	Wall wall = new Wall(450+18*7,100+i*18);
        	wallList.add(wall);
        	wall = new Wall(450+18*8,100+i*18);
        	wallList.add(wall);
        }
        for (int i=1;i<=2;i++){
        	Wall wall = new Wall(100-18*i,136);
        	wallList.add(wall);
        	wall = new Wall(100-18*i,154);
        	wallList.add(wall);
        }
        for (int i=1;i<=2;i++){
        	Wall wall = new Wall(100-18*i,172);
        	wallList.add(wall);
        	wall = new Wall(100-18*i,180);
        	wallList.add(wall);
        }
        for (int i=0;i<8;i++){
        	Wall wall = new Wall(100+i*18,198);
        	wallList.add(wall);
        	wall = new Wall(100+i*18,216);
        	wallList.add(wall);
        }
        for (int i=8;i<=9;i++){
        	Wall wall = new Wall(100+18*i,234);
        	wallList.add(wall);
        	wall = new Wall(100+18*i,252);
        	wallList.add(wall);
        }
        for (int i=8;i<=9;i++){
        	Wall wall = new Wall(100+18*i,270);
        	wallList.add(wall);
        	wall = new Wall(100+18*i,288);
        	wallList.add(wall);
        }
        for (int i=-2;i<8;i++){
        	Wall wall = new Wall(100+i*18,306);
        	wallList.add(wall);
        	wall = new Wall(100+i*18,324);
        	wallList.add(wall);
        }

        for (int i=-2;i<35;i++){
        	Wall wall = new Wall(100+i*18,400);
        	wallList.add(wall);
        	wall = new Wall(100+i*18,418);
        	wallList.add(wall);
        }
        //画草
        for (int i=1;i<=13;i++){
        	Grass grass = new Grass(250+18*7,100+i*18);
        	grassList.add(grass);
        	grass = new Grass(250+18*8,100+i*18);
        	grassList.add(grass);
        }
        for (int i=1;i<=13;i++){
        	Grass grass = new Grass(250+18*5,100+i*18);
        	grassList.add(grass);
        	grass = new Grass(250+18*6,100+i*18);
        	grassList.add(grass);
        }
        //画出石头
        for (int i=-2;i<35;i++){
        	Stone stone = new Stone(100+i*18,382);
        	stoneList.add(stone);
        	stone = new Stone(100+i*18,364);
        	stoneList.add(stone);
        }
        
		 // 启动重画线程
        PaintThread pt = new PaintThread();
        Thread t = new Thread(pt);
        t.start();
	}
	/**
	 * 绘制方法
	 */
    public void paint(Graphics g) {
    	g.setColor(Color.BLACK);//设置黑色背景
    	g.fillRect(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
    	if(GameBegin==false){//游戏开始提示标语
    		g.setColor(Color.WHITE);
    		g.drawString("请按F1开始游戏", 100, 100);
    	}
    	
    	
    	if(myTank==null) return ;
        myTank.draw(g);
        	
		myTank.draw(g);
		for (int i=0;i<enemyTanks.size();i++){
			enemyTanks.get(i).draw(g);
		}
		//画出城墙
		for (int i=0;i<wallList.size();i++){
			wallList.get(i).draw(g);
		}
		//画出草地
		for (int i=0;i<grassList.size();i++){
			grassList.get(i).draw(g);
		}
		//画出钢快
		for (int i=0;i<stoneList.size();i++){
			stoneList.get(i).draw(g);
		}
    }

    
    
    // 重写update方法,先将窗体上的图形画在图片对象上,再一次性显示
    public void update(Graphics g) {
        if (offScreenImage == null) {
            offScreenImage = this.createImage(WINDOW_WIDTH, WINDOW_HEIGHT);
        }
        Graphics gImage = offScreenImage.getGraphics();
        Color c = gImage.getColor();
        gImage.setColor(Color.GREEN);
        gImage.fillRect(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
        gImage.setColor(c);
        paint(gImage);
        g.drawImage(offScreenImage, 0, 0, null);
    }

    // 用线程重画,每隔一段时间重画窗体
    private class PaintThread implements Runnable {

        public void run() {
            while (true) {
                repaint();
                try {
                    Thread.sleep(REFRESH_RATE);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }


	public void keyPressed(KeyEvent e) {
		if(e.getKeyCode()==java.awt.event.KeyEvent.VK_UP){//如果按键为向上键
			myTank.move(0);//向上
		}
		if(e.getKeyCode()==java.awt.event.KeyEvent.VK_DOWN){//如果按键为向下键
			myTank.move(1);	
		}
		if(e.getKeyCode()==java.awt.event.KeyEvent.VK_LEFT){//如果按键为向左键
			myTank.move(2);	
		}
		if(e.getKeyCode()==java.awt.event.KeyEvent.VK_RIGHT){//如果按键为向右键
			myTank.move(3);
		}if(e.getKeyCode()==java.awt.event.KeyEvent.VK_SPACE){
			myTank.fire();
		}if(e.getKeyCode()==java.awt.event.KeyEvent.VK_F1){//游戏开始
			GameBegin=true;
			Init();
		}
		//drawTank();
	}


	public void keyReleased(KeyEvent e) {
	}


	public void keyTyped(KeyEvent e) {
	}
}

 

 

就不会再次闪烁了~

主要就是重写了update方法,制定了其刷新的方式~

 

原理:
 1.建立一个Image对象DbBuffer,通过DbBuffer=createrImage(int width,int height)来在内存中开辟一个长为width 宽为heithr空间.次空间的大小可以和你动画窗口的大小保持一致,也可以利用getwidth()和getheight()来获得动画窗口的大小.
 2.建立一个Graphics 对象GraImage通过GraImage=DbBuffer.getGraphics();去把要绘制的对象并存放到分配好的内存空间中.
 3.利用paint(GraImage);将其全部绘制带内存之中,最后调用我门的paint(Graphics g)方法中的g.drawImage(DbBuffer,0,0,null)将DbBuffer全部一次性的绘制到我门的动画窗口,然后把我门内存中分配的空间窗口关闭调用dispose()方法.

// 重写update方法,先将窗体上的图形画在图片对象上,再一次性显示
    public void update(Graphics g) {
        if (offScreenImage == null) {
            offScreenImage = this.createImage(WINDOW_WIDTH, WINDOW_HEIGHT);
        }
        Graphics gImage = offScreenImage.getGraphics();
        Color c = gImage.getColor();
        gImage.setColor(Color.GREEN);
        gImage.fillRect(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
        gImage.setColor(c);
        paint(gImage);
        g.drawImage(offScreenImage, 0, 0, null);
    }

 

分享到:
评论
2 楼 无心流泪wan 2013-09-19  
说的不错。。。。。
1 楼 malijie3414 2012-10-12  
双缓冲在马士兵的坦克视频中有用到,楼主的代码与马的视频里的代码基本一致,

相关推荐

    Java双缓冲技术原理详细讲解例子Java实用源码整理learns

    Java双缓冲技术是一种图形渲染优化策略,主要用于提高GUI(图形用户界面)的性能和减少屏幕闪烁,提升用户体验。在Java中,它主要应用于Swing和JavaFX等图形库。本教程将深入探讨Java双缓冲技术的原理,并通过实例...

    Java双缓冲技术原理详细讲解例子

    `paintComponent()`方法的调用顺序至关重要,`super.paintComponent(g)`会开启Swing的默认双缓冲机制。 双缓冲不仅可以应用于Swing组件,还可以用于游戏开发或者其他需要频繁更新图形的场景。通过使用双缓冲,我们...

    JAVA双缓冲绘图源码

    此外,Java Swing提供了更好的绘图组件JComponent,它的 paintComponent 方法已经内置了双缓冲机制,所以如果你使用Swing,无需手动实现双缓冲,只需重写`paintComponent(Graphics g)`方法进行绘图即可。 通过以上...

    JAVA人物动画.rar_JAVA画人像_java人物动画_动漫人物java_双缓冲

    在这个名为"JAVA人物动画.rar"的压缩包中,我们重点探讨的是如何利用Java来实现生动的人物动画,特别是针对"java画人像"、"java人物动画"、"动漫人物java"和"双缓冲"这些标签所涉及的技术。 首先,让我们深入理解...

    俄罗斯方块1.0.1(双缓冲绘图)

    而双缓冲机制则在后台创建了一个额外的帧缓冲区,游戏逻辑和渲染过程都在这个隐藏的缓冲区进行,只有当一帧完全绘制完毕后,才会一次性将其复制到前台显示,从而确保用户看到的是完整、平滑的画面。 在1.0.1版本的...

    双缓冲画坐标系

    在VC++环境中,利用双缓冲机制来绘制坐标系可以显著提高用户界面的平滑度和视觉效果。本文将详细讲解如何在VC++中实现双缓冲画坐标系,以及其工作原理。 首先,我们要理解双缓冲的基本概念。在单缓冲系统中,图形的...

    服务器端双缓冲队列s/c

    在IT行业中,服务器端双缓冲队列是一种优化数据处理效率的设计模式,特别是在高并发和实时性要求较高的场景下。双缓冲队列(Double Buffering Queue)借鉴了图形处理中的双缓冲技术,通过两个独立的数据缓冲区来提高...

    利用双缓冲的方法解决界面屏闪

    例如,Java的AWT/Swing库、C#的Windows Forms或WPF、Python的Pygame等都有相应的双缓冲机制。在提供的压缩文件"用双缓冲解决闪烁问题"中,可能包含了使用特定编程语言或库实现双缓冲的示例代码,供开发者参考学习。 ...

    利用双缓冲做的时钟程序1 .rar_双缓冲

    【标题】"利用双缓冲做的...学习这个程序,可以深入了解双缓冲机制以及如何在Java环境下实现。此外,这也提供了一个很好的案例,对于想要提升自己GUI编程技能,尤其是解决屏闪问题的人来说,这是一个很好的学习资源。

    双缓冲绘图实例.zip

    这个“双缓冲绘图实例”是一个使用Java实现的示例,旨在展示如何在GUI应用程序中有效地利用双缓冲来提高图形绘制的性能和视觉效果。 双缓冲的基本原理是将绘图操作先在一个“后台缓冲区”完成,然后一次性将整个...

    商业编程-源码-实例解说双缓冲.zip

    这个"商业编程-源码-实例解说双缓冲.zip"压缩包文件显然包含了一些源代码示例,用于解释和展示如何在实际项目中实现双缓冲机制。下面,我们将深入探讨双缓冲的工作原理、其重要性以及如何在编程中应用。 双缓冲是一...

    双缓冲解决屏幕闪烁问题

    双缓冲机制通过创建一个后台缓冲区,将所有的绘图操作先执行在缓冲区上,然后再一次性将完成的图像刷新到屏幕上,从而避免了直接在屏幕上连续修改像素引发的闪烁。 在Windows API或Java Swing等GUI库中,`OnDraw`...

    java对象缓冲实现中间件

    Java对象缓冲实现中间件是一种高效的数据存储和访问机制,它通过在内存中创建一个对象池来减少对硬盘或网络资源的频繁访问,从而提高应用程序的性能。这种中间件设计的核心理念是利用内存的高速访问特性,将常用或者...

    双缓冲效果

    而双缓冲机制则引入了两个缓冲区:一个是用于绘图的后台缓冲区,另一个是用于显示的前台缓冲区。当后台缓冲区完成绘图后,再一次性将整个图像内容复制到前台缓冲区,最后更新到屏幕上。这样,用户看到的始终是一次...

    scrollview 冻结列 双缓冲

    在双缓冲机制下,组件首先在后台缓冲区进行所有的绘图操作,然后一次性将完成的图像刷新到前台缓冲区,即用户看到的实际屏幕上。这种方法避免了连续更新屏幕导致的闪烁,提高了用户体验。 在`ScrollView`中应用双...

    Java智能卡内存调度机制研究与分析 (1).pdf

    双缓冲区内存调度机制的实现可以分为两个阶段:首先,在解释器工作时,使用双缓冲区来存储指令,并对指令进行分类和优化;其次,在执行指令时,使用缓冲区中的指令来减少对外部存储器的访问次数。 5. 算法实现的...

    J2ME实现双缓冲解决屏幕闪烁代码!!!!

    本文将详细解释双缓冲的概念、工作原理,并提供一个简单的J2ME实现双缓冲解决屏幕闪烁的代码示例。 双缓冲是一种图形绘制优化技术,主要目的是减少屏幕闪烁和图像撕裂。在单缓冲系统中,每次图形绘制完成后立即显示...

    Java 编程课后答案 双语教学

    9. **反射**:Java反射机制的使用,包括获取类信息、创建对象、访问私有成员、动态调用方法等,以及在实际开发中的应用场景。 10. **JVM**:对Java虚拟机的理解,包括内存管理(堆、栈、方法区)、垃圾回收机制、类...

    java源码包---java 源码 大量 实例

     Java生成密钥、保存密钥的实例源码,通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥...

    应用源码之VIEW双缓冲与SurfaceView比较.zip

    本资料包"应用源码之VIEW双缓冲与SurfaceView比较.zip"聚焦于这两者的原理及对比,旨在帮助开发者深入理解它们的工作机制,以便在实际项目中做出合适的选择。 **一、View双缓冲技术** 双缓冲是一种优化UI渲染的...

Global site tag (gtag.js) - Google Analytics