众所周知,Java应用的运行速度虽然不慢,却也谈不上快,以最新的JRE1.6表现来说,至多也就是优胜于一些纯粹的解释型语言,距离C/C++等编译型的执行效率还有一定差距。


平心而论,如果我们使用Java去制作一些简单的桌面应用,那么目前Java组件的绘图速度勉强还能接受;但如果用Java来进行游戏开发,尤其是制作一些需要高FPS才能满足的动态效果,就必须利用技术手段对其加以优化,解决其绘图效率问题,其它才有的谈。


什么是FPS


这里所说的FPS,并非指射击游戏,而是指俗称的“帧率”(Frames per Second,缩写:FPS)或“赫兹”(Hz)。笔者在以前的博文中曾给出过专门的解释及Java实现示例,此处不再赘述。


受到人类眼球的生理结构制约,通常情况下只要我们所见画面高于每秒16帧,就会认为画面是连贯的,生物学上称此现象为视觉暂留,这也就是为什么电影胶片是一格一格拍摄出来,然而我们却认为它是连续的一段。当然,所谓的16帧也只是一个中值,并非放之四海而皆准,根据具体视觉对象不同,帧率的具体标准会有所变化。在最坏情况下,动画不低于每秒12帧,现代电影不低于24帧,动作游戏不低于30帧,在人眼中的画面才是连续不间断的。


总之,FPS数值越高则画面流畅度便越高,越低则越显停滞,要提高Java游戏运行效率,那么焦点就必然要集中在如何提高程序的FPS之上。


我们该从什么地方入手,才能提高FPS


Java绘图最终要通过native,这是地球人都知道的事情。这意味着除非我们学习SWT重写本地图形接口,否则显示部分我们无法干涉;这样便决定了我们对FPS的优化必然要集中在本地图形系统调用之前,而非之后。也就是说,提高Java应用的FPS,还要老生常谈的从Image与Graphics及其相关子类入手。


那么,对这些尽人皆知的Java绘图组件,我们究竟能改进那些部分呢?


我在不久前发布的博文《Java游戏开发中应始终坚持的10项基本原则》中,曾经就如何构建一个高效的Java游戏发表过一些见解,其中第7点“始终双缓冲游戏图像”,它不但是解决Java图像闪烁问题的关键所在,而且同样是提高Java游戏帧率的制胜法宝之一。是的,Java绘图机能的优化与改进,关键就在于如何对其缓冲区域进行优化处理。


下面,我将展示三种不同的Java缓冲绘图方式。


1、采用BufferedImage对象进行缓冲


   这种方法是最简单,同时也是最常用的双缓冲构建方式,也就是构建一个BufferedImage缓冲当前绘图,所有Graphics操作在其上进行,仅在需要时才将贴图paint于窗体之上,使用上再简单不过,但效率如何呢?文章进行到此处时尚不得而知。


2、采用BufferStrategy构建缓冲区


使用BufferStrategy构建缓冲能够获得系统所提供的硬件加速,Java系统会根据本地环境选择最适合的BufferStrategy。要创建 BufferStrategy ,需要使用 createBufferStrategy() 方法告诉系统你所期望的缓冲区数目(通常使用双缓冲,也就是填入“2”),并使用 getDrawGraphics() 方法在缓冲区之间进行交换,该方法返回下一个要使用的缓冲区。BufferStrategy最大的缺点与优点都在于其受本地图形环境影响,既不会出现很快的图形环境跑出很慢的FPS,也别指望很慢的图形环境跑出很快的FPS。


3、完全在BufferedImageDataBuffer中进行图像处理


每个BufferedImage都有一个与之对应得WritableRaster对象(getRaster方法获得),通过它我们获得指定BufferedImage的DataBuffer(getDataBuffer方法获得),与方法1类似,我们同样构建一个BufferedImage缓冲当前所有绘图,所有操作都在其上进行,仅在需要时才将贴图paint于窗体之上。但区别在于,由于DataBuffer可以转化为描述BufferedImage象素点的int[],byte[]或short[]等数组集合,因此我们不再使用Java提供的Graphics对象,而是直接操作像素点进行所有绘图的实现。 但是,这样进行数组操作会快吗?


现在我们为其各自构建三个示例,尽量以比较趋同的处理流程构建,分别测算三种方法的具体效率。

 

PS:写完才突然想起,这台2002年买的电脑实在不适合测试FPS-_-|||),较新机型的FPS至少能达到此测试结果的2倍以上,特此声明……)


PS:以下图像素材源自成都汉森信息技术有限公司首页,特此声明)

 

以下开始将分别对三种方法分别进行绘制1名角色、绘制100名角色、绘制1000名角色的简单FPS绘图效率测算。


一、采用BufferedImage对象进行缓冲


代码如下:


1、DoubleBufferGameFrame.Java

package test1;

import java.awt.Dimension;
import java.awt.Frame;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

/**
 * @author chenpeng
 * @email:​​​[email]ceponline@yahoo.com.cn[/email]​​​ */
public class DoubleBufferGameFrame extends Frame implements Runnable {

 /**
  *
  */
 private static final long serialVersionUID = 1L;

 private int width = 480;
 
 private int height = 360;
 
 private DoubleBufferCanvas canvas = null;

 public DoubleBufferGameFrame() {
  super("AWT标准Graphics2D使用测试");
  this.setPreferredSize(new Dimension(width + 5, height + 25));
  this.requestFocus();
  this.addWindowListener(new WindowAdapter() {
   public void windowClosing(WindowEvent e) {
    System.exit(0);
   }
  });
  canvas = new DoubleBufferCanvas();
  this.add(canvas);
  this.pack();
  this.setResizable(false);
  this.setLocationRelativeTo(null);
  this.setIgnoreRepaint(true);
  Thread thread = new Thread(this);
  thread.start();
 }

 public void run() {
  for (;;) {
   canvas.drawScreen();
  }
 }

 public static void main(String[] args) {
  DoubleBufferGameFrame frm = new DoubleBufferGameFrame();
  frm.setVisible(true);
 }

}


2、DoubleBufferCanvas.Java


package test1;

import java.awt.Canvas;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.p_w_picpath.BufferedImage;
import java.util.Random;

public class DoubleBufferCanvas extends Canvas {

 /**
  *
  */
 private static final long serialVersionUID = 1L;

 private boolean isFPS = true;

 private boolean initFlag;

 private int frames = 0;

 private long totalTime = 0;

 private long curTime = System.currentTimeMillis();

 private long lastTime = curTime;

 private Random rand = new Random();

 private int fps;

 private Image backImage = ImageUtils.loadImage("back.gif");

 private Image hero_idle_0 = ImageUtils.loadImage("idle0_0.png",true);

 private BufferedImage bufferImage;

 private Graphics2D graphics2D;

 public DoubleBufferCanvas() {

 }

 public void update(Graphics g) {
  paint(g);
 }

 public void paint(Graphics g) {
  if (initFlag) {
   graphics2D.drawImage(backImage, 0, 0, null);
   for (int i = 0; i < 1000; ++i) {
    int x = rand.nextInt(480);
    int y = rand.nextInt(360);
    graphics2D.drawImage(hero_idle_0, x-100, y-100, null);
   }
   graphics2D.drawString(("当前FPS:" + fps).intern(), 15, 15);
   g.drawImage(bufferImage, 0, 0, null);
   g.dispose();
  }
 }

 private synchronized void initializtion() {
  bufferImage = ImageUtils.createImage(getWidth(), getHeight(), true);
  graphics2D = bufferImage.createGraphics();
  initFlag = true;
 }

 public synchronized void drawScreen() {
  getGraphics2D();
  repaint();
  if (isFPS) {
   lastTime = curTime;
   curTime = System.currentTimeMillis();
   totalTime += curTime - lastTime;
   if (totalTime > 1000) {
    totalTime -= 1000;
    fps = frames;
    frames = 0;
   }
   ++frames;
  }
  Thread.yield();
 }

 public synchronized Graphics2D getGraphics2D() {
  if (!initFlag) {
   initializtion();
  }
  return graphics2D;
 }

}

 

场景图1、角×××1 FPS 60 - 70

 

Java游戏开发中怎样才能获得更快的FPS?_java


场景图1、角×××100 FPS 20 - 25

 


Java游戏开发中怎样才能获得更快的FPS?_game_02


场景图1、角×××1000 FPS 13 - 17

 


Java游戏开发中怎样才能获得更快的FPS?_buffering_03

 

 

二、采用BufferStrategy构建缓冲区(双缓冲) 


1、 BufferStrategyGameFrame.java

 

package test1;


import java.awt.Dimension;
import java.awt.Frame;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

/**
 * @author chenpeng
 * @email:​​[email]ceponline@yahoo.com.cn[/email]​​ */
public class BufferStrategyGameFrame extends Frame implements Runnable {

 /**
  *
  */
 private static final long serialVersionUID = 1L;

 private BufferStrategyCanvas canvas = null;

 private int width = 480;
 
 private int height = 360;
 
 public BufferStrategyGameFrame() {
  super("AWT标准Graphics2D(BufferStrategy)使用测试");
  this.setPreferredSize(new Dimension(width + 5, height + 25));
  this.requestFocus();
  this.addWindowListener(new WindowAdapter() {
   public void windowClosing(WindowEvent e) {
    System.exit(0);
   }
  });
  canvas = new BufferStrategyCanvas();
  this.add(canvas);
  this.pack();
  this.setResizable(false);
  this.setLocationRelativeTo(null);
  this.setIgnoreRepaint(true);
  Thread thread = new Thread(this);
  thread.start();
 }

 public void run() {
  for (;;) {
   canvas.drawScreen();
  }
 }

 public static void main(String[] args) {
  BufferStrategyGameFrame frm = new BufferStrategyGameFrame();
  frm.setVisible(true);
 }

}

 

2、 BufferStrategyCanvas.java

 

package test1;

import java.awt.Canvas;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.p_w_picpath.BufferStrategy;
import java.awt.p_w_picpath.BufferedImage;
import java.util.Random;

public class BufferStrategyCanvas extends Canvas {

 /**
  *
  */
 private static final long serialVersionUID = 1L;

 private boolean isFPS = true;

 private boolean initFlag;

 private int frames = 0;

 private long totalTime = 0;

 private long curTime = System.currentTimeMillis();

 private long lastTime = curTime;

 private Random rand = new Random();

 private int fps;

 private Image backImage = ImageUtils.loadImage("back.gif");

 private Image hero_idle_0 = ImageUtils.loadImage("idle0_0.png", true);

 private BufferedImage screenImage;

 final static GraphicsEnvironment environment = GraphicsEnvironment
   .getLocalGraphicsEnvironment();

 final static GraphicsDevice graphicsDevice = environment
   .getDefaultScreenDevice();

 final static GraphicsConfiguration graphicsConfiguration = graphicsDevice
   .getDefaultConfiguration();

 private Graphics canvasGraphics = null;

 private BufferStrategy bufferImage;

 private Graphics2D graphics2d;

 public BufferStrategyCanvas() {

 }

 public void createBufferGraphics() {
  createBufferStrategy(2);
  bufferImage = getBufferStrategy();
  screenImage = graphicsConfiguration.createCompatibleImage(getWidth(),
    getHeight());
  graphics2d = screenImage.createGraphics();
 }

 public synchronized void paintScreen() {
  if (initFlag) {
   graphics2d.drawImage(backImage, 0, 0, null);
   for (int i = 0; i < 1000; ++i) {
    int x = rand.nextInt(480);
    int y = rand.nextInt(360);
    graphics2d.drawImage(hero_idle_0, x - 100, y - 100, null);
   }
   graphics2d.drawString(("当前FPS:" + fps).intern(), 15, 15);
   canvasGraphics = bufferImage.getDrawGraphics();
   canvasGraphics.drawImage(screenImage, 0, 0, null);
   bufferImage.show();
   canvasGraphics.dispose();
  }
 }

 private synchronized void initializtion() {
  createBufferGraphics();
  initFlag = true;
 }

 public synchronized void drawScreen() {
  loadGraphics();
  paintScreen();
  if (isFPS) {
   lastTime = curTime;
   curTime = System.currentTimeMillis();
   totalTime += curTime - lastTime;
   if (totalTime > 1000) {
    totalTime -= 1000;
    fps = frames;
    frames = 0;
   }
   ++frames;
  }
  Thread.yield();

 }

 public synchronized Graphics2D loadGraphics() {
  if (!initFlag) {
   initializtion();
  }
  return graphics2d;
 }

}

 场景图1、角×××1 FPS 80 - 90  

Java游戏开发中怎样才能获得更快的FPS?_java_04

 场景图1、角×××100 FPS 25 - 35 

Java游戏开发中怎样才能获得更快的FPS?_fps_05

 

场景图1、角×××1000 FPS 3 - 6

 

Java游戏开发中怎样才能获得更快的FPS?_bufferedimage_06

 

三、完全在BufferedImageDataBuffer中进行图像处理


实际上在Java网游海盗时代、Java网游暗影世界等网络游戏、还有netbaens以及其它种种不算慢的Java应用中,都存在大量使用DataBuffer进行的图像渲染;但在使用方式上,却只有暗影使用的JGnet引擎与常用的Image与Graphics搭配模式较为接近,所以此示例使用了JGnet的部分代码构建。


PS:实际上汉森的JGnet客户端引擎目前并未开源,此部分代码得自其主页homepage.jar的反编译,是前天我看csdnCTO栏目专访汉森老总后去其首页发现的;视频中说JGnet有部分代码准备开源,我估计应该是homepage.jar里这部分,因为没有混淆且功能较少,其网站地址[url]http://www.handseeing.com[/url],纯Applet站点)。


为给不愿反编译的看客提供方便,这里我稍微对JGnet进行一点分析(homepage.jar中的那部分)。


就代码来看,JGnet中所有UI继承自GUIWidget(GUIWidget继承自Container),所有JGnet产生的GUI组件也都应当继承它。JGnet提供了一个GUIRoot为底层面板,所有GUIWidget衍生组件都应附着于GUIRoot之上,另外该引擎提供有AbstractGameClient、ClientContext接口及相关实现用以调度相关组件,JGnet的GUIRoot载体可以使用Applet或者Frame。


JGnet中大部分资源采用zip打包,当然也可以读取单图,JGnet资源加载依赖于其提供的ImageFactory类,ImageFactory的操作可分为如下两步:


第一步是addArchive,进行此步骤时指定的zip资源会被加载,其后将read图像资源为ShortBufferImage,再根据zip中该资源对应路径缓存到daff.utill包自备的HashMap中。


第二步是getImage,即获得指定路径的ShortBufferImage对象,此步骤以缓存优先,如果获得失败会尝试调用loadImage方法再次加载,并缓存到daff包自备的HashMap对象中。其中loadImage方法会根据后缀判定加载的资源类型,其中jpg、gif或者png后缀文件会调用daff包中提供的AwtImageLoader类转化为ShortBufferImage,而非此后缀文件会直接按照ShortBufferImage格式进行加载。


JGnet的图像绘制主要使用其内部提供的ShortBufferImage及ShortBufferGraphics以及相关衍生类。ShortBufferImage是一个抽象类,没有父类,所有JGnet中图形资源都将表现为ShortBufferImage,绘制ShortBufferImage需要使用daff.gui包提供的专用画布ShortBufferGraphics,实际上ShortBufferGraphics是一个针对于ShortBufferImage的象素渲染器,内部以short数组形式保存有一个空白BufferedImage的DataBuffer,所有绘制基于其上。


同大多数网游一样,JGnet中大量使用自定义图形格式(后缀.img),其解释与转化通过CompressedImage类得以完成,由于自定义格式通常能更简洁的声明图像中各元素关系,所以JGnet处理其自定义格式资源会比加载普通图像资源更快。


JGnet会根据环境的不同会采用MsJvmGraphics或SunJvmGraphics两套绘图组件(皆继承自ShortBufferGraphics),据此我们可以初步断定其运行环境上兼容微软jvm,也就是最低可运行于JRE1.1之上。


讲解结束,下面我以JGnet进行针对DataBuffer进行渲染的FPS测试(对其某些细节进行了使用习惯上的扩展)。

 

1、DataBufferGameFrame.java

package test1;

import java.awt.Dimension;
import java.awt.Frame;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

/**
 * @author chenpeng
 * @email:​​​[email]ceponline@yahoo.com.cn[/email]​​​ */
public class DataBufferGameFrame extends Frame implements Runnable {

 /**
  *
  */
 private static final long serialVersionUID = 1L;

 private DataBufferCanvas canvas = null;

 private int width = 480;
 
 private int height = 360;
 
 public DataBufferGameFrame() {
  super("JGnet中ShortBufferGraphics使用测试");
  this.setPreferredSize(new Dimension(width + 5, height + 25));
  this.requestFocus();
  this.addWindowListener(new WindowAdapter() {
   public void windowClosing(WindowEvent e) {
    System.exit(0);
   }
  });
  canvas = new DataBufferCanvas();
  this.add(canvas);
  this.pack();
  this.setResizable(false);
  this.setLocationRelativeTo(null);
  this.setIgnoreRepaint(true);
  Thread thread = new Thread(this);
  thread.start();
 }

 public void run() {
  for (;;) {
   canvas.drawScreen();
  }
 }

 public static void main(String[] args) {
  DataBufferGameFrame frm = new DataBufferGameFrame();
  frm.setVisible(true);
 }

}

2、DataBufferCanvas.java


package test1;

import impl.gui.SunJvmGraphics;

import java.awt.*;
import java.util.Random;

import daff.gui.ImageFactory;
import daff.gui.ShortBufferFont;
import daff.gui.ShortBufferGraphics;
import daff.gui.ShortBufferImage;

public class DataBufferCanvas extends Canvas {

 /**
  *
  */
 private static final long serialVersionUID = 1L;

 private boolean isFPS = true;

 private ShortBufferGraphics shortBufferGraphics;

 private MyColor color = new MyColor(255, 255, 255);

 private ShortBufferFont font;

 private boolean initFlag;

 private static ImageFactory factory = new ImageFactory(20);

 private int frames = 0;

 private long totalTime = 0;

 private long curTime = System.currentTimeMillis();

 private long lastTime = curTime;

 private Random rand = new Random();

 private int fps;

 private ShortBufferImage hero_idle_0 = factory
   .getImage("p_w_picpath/hero/idle/idle0_0.img");

 private ShortBufferImage backImage = factory.getImage("back.gif");

 static {
  factory.addArchive("pack/hero.zip");
 }

 public DataBufferCanvas() {

 }

 public void update(Graphics g) {
  paint(g);
 }

 public void paint(Graphics g) {
  if (initFlag) {
   shortBufferGraphics.drawImage(backImage, 0, 0);
   for (int i = 0; i < 1000; ++i) {
    int x = rand.nextInt(480);
    int y = rand.nextInt(360);
    shortBufferGraphics.drawImage(hero_idle_0, x - 100, y - 100);
   }
   shortBufferGraphics.drawString(font, color, ("当前FPS:" + fps)
     .intern(), 15, 15);
   shortBufferGraphics.drawTo(g, 0, 0);
   g.dispose();
  }
 }

 private synchronized void initializtion() {
  shortBufferGraphics = (ShortBufferGraphics) new SunJvmGraphics();
  shortBufferGraphics.createGraphics(getWidth(), getHeight(), this);
  font = new ShortBufferFont("Dialog", 0, 12, this);
  initFlag = true;
 }

 public synchronized void drawScreen() {
  loadGraphics().reset();
  repaint();
  if (isFPS) {
   lastTime = curTime;
   curTime = System.currentTimeMillis();
   totalTime += curTime - lastTime;
   if (totalTime > 1000) {
    totalTime -= 1000;
    fps = frames;
    frames = 0;
   }
   ++frames;
  }
  Thread.yield();
 }

 public synchronized ShortBufferGraphics loadGraphics() {
  if (!initFlag) {
   initializtion();
  }
  return shortBufferGraphics;
 }

}

 

场景图1、角×××1 FPS 140 - 160

 

Java游戏开发中怎样才能获得更快的FPS?_java_07

 

场景图1、角×××100 FPS 115 - 125

Java游戏开发中怎样才能获得更快的FPS?_game_08

 

场景图1、角×××1000 FPS 55 - 70

 


Java游戏开发中怎样才能获得更快的FPS?_java_09


通过简单比较后我们可以发现,Graphics绘图无论在直接BufferedImage双缓冲或者利用BufferStrategy以换取硬件加速的前提下,都无法与使用DataBuffer直接进行像素绘图的ShortBufferGraphics相比,BufferStrategy在绘制千人时更出现了令笔者整个人都斯巴达了的4帧|||,即使在测试中笔者改BufferedImage为VolatileImage也收效甚微,在配置较高的微机上应当会有些许改善,但用户使用机型却并非我们所能控制的。

由于JGnet中直接获得图像DataBuffer的short[]形式,并以此进行图像绘制,所以 ShortBufferImage及ShortBufferGraphics称得上是一组先天的Java图形缓冲对象,就效率上讲,使用ShortBufferGraphics绘图获得的FPS明显高于使用Graphics2D绘图,这是数组操作先天性的效率优势所决定的。但缺点在于,ShortBufferImage及ShortBufferGraphics其并非直接继承自Image与Graphics,两者间函数不能完全对应,有些常用方法尚待实现,很多常用方法尚需ShortBufferGraphics使用者自行解决。


单就效率而言,应该说采取DataBuffer进行缓冲绘图明显优于单纯利用BufferStrategy或者BufferedImage获得Graphics后进行绘图。


总体上看,如果我们追求最高FPS,则使用BufferedImage产生DataBuffer进行象素处理最为高效的,与方法二混用后效率还将更高,但遗憾的是目前缺少相关开源组件,可用资源上略显不足,有待相关公司或者个人提供,另外我们也可以考虑功能混用,即主体部分使用DataBuffer渲染,未实现部分以Graphics补足,但这肯定会对运行效率产生影响。


由于JGnet目前属于闭源项目,所以我无法公开其任何代码实现。为此我自制了一个非常简单的DataBuffer中图像处理类SimpleIntBufferImage,以供各位看客参考。


SimpleIntBufferImage.java 源码如下:

 

package test1;

import java.awt.p_w_picpath.BufferedImage;
import java.awt.p_w_picpath.DataBufferInt;
import java.awt.p_w_picpath.PixelGrabber;
import java.io.File;
import java.io.IOException;

import javax.p_w_picpathio.ImageIO;

/**
* Copyright 2008 - 2009
*    
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*    
* [url]http://www.apache.org/licenses/LICENSE-2.0[/url]
*    
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*    
* @project loonframework
* @author chenpeng
* @email:[email]ceponline@yahoo.com.cn[/email]
* @version 0.1
*/
public class SimpleIntBufferImage {

  private BufferedImage bufferImage;

  private int width;

  private int height;

  final private int[] pixels;

  final private int[] clear;

  public SimpleIntBufferImage(final String fileName) {
    try {
      BufferedImage tmpImage = ImageIO.read(new File(fileName));
      width = tmpImage.getWidth(null);
      height = tmpImage.getHeight(null);
      clear = new int[width * height];
      bufferImage = new BufferedImage(width, height,
          BufferedImage.TYPE_INT_ARGB);
      pixels = ((DataBufferInt) bufferImage.getRaster().getDataBuffer())
          .getData();
      PixelGrabber pgr = new PixelGrabber(tmpImage, 0, 0, width, height,
          pixels, 0, width);
      try {
        pgr.grabPixels();
      } catch (InterruptedException ex) {
      }
    } catch (IOException ex) {
      throw new RuntimeException(ex);
    }
  }

  /**
    * 清空pixels数据
    *    
    */
  public void clearImage() {
    System.arraycopy(clear, 0, pixels, 0, clear.length);
  }

  /**
    * 删除指定颜色
    *    
    * @param rgbs
    */
  public void clearRGBs(final int[] rgbs) {
    for (int i = 0; i < pixels.length; i++) {
      int npixel = (255 << 24) + (rgbs[0] << 16) + (rgbs[1] << 8)
          + rgbs[2];
      if (pixels[i] == npixel) {
        pixels[i] = 0;
      }
    }
  }

  /**
    * 转换当前贴图为灰白位图
    *    
    */
  public void convertToGray() {
    for (int i = 0; i < pixels.length; i++) {
      pixels[i] = colorToGray(pixels[i]);
    }
  }

  /**
    * 转换当前贴图为异色(反色)位图
    *    
    */
  public void convertToXor() {
    for (int i = 0; i < pixels.length; i++) {
      pixels[i] = colorToXor(pixels[i]);
    }
  }

  /**
    * 获得r,g,b
    *    
    * @param pixel
    * @return
    */
  private int[] getRGBs(final int pixel) {
    int[] rgbs = new int[3];
    rgbs[0] = (pixel >> 16) & 0xff;
    rgbs[1] = (pixel >> 8) & 0xff;
    rgbs[2] = (pixel) & 0xff;
    return rgbs;
  }

  /**
    * 转换指定像素为灰白
    *    
    * @param pixel
    * @return
    */
  private int colorToGray(final int pixel) {
    int[] rgbs = getRGBs(pixel);
    int value = (int) (0.299 * rgbs[0] + 0.587 * rgbs[1] + 0.114 * rgbs[2]);
    int npixel = (255 << 24) + (value << 16) + (value << 8) + value;
    return npixel;
  }

  /**
    * 异或指定像素
    *    
    * @param pixel
    * @return
    */
  private int colorToXor(final int pixel) {
    int[] rgbs = getRGBs(pixel);
    int r = rgbs[0] ^ 0xff;
    int g = rgbs[1] ^ 0xff;
    int b = rgbs[2] ^ 0xff;
    int npixel = (255 << 24) + (r << 16) + (g << 8) + b;
    return npixel;
  }

  /**
    * copy指定贴图于本身位图之上
    *    
    * @param simpleImage
    * @param left
    * @param top
    */
  public void copyImage(final SimpleIntBufferImage simpleImage,
      final int left, final int top) {
    int[] src = simpleImage.getPixels();
    int srcWidth = simpleImage.getWidth();
    int srcHeight = simpleImage.getHeight();
    for (int x = 0, x1 = left; x < srcWidth && x < width && x1 < width; x++, x1++) {
      for (int y = 0, y1 = top; y < srcHeight && x < height
          && y1 < height; y++, y1++) {
        int npixels = src[x + y * srcWidth];
        if (npixels != -16777216) {
          pixels[x1 + y1 * width] = npixels;
        }
      }
    }
  }

  /**
    * 截取指定范围内像素点数组
    *    
    * @param x
    * @param y
    * @param w
    * @param h
    * @return
    */
  public int[] getSubPixels(final int x, final int y, final int w, final int h) {
    int[] pixels = new int[2 + w * h];
    pixels[0] = w;
    pixels[1] = h;
    if (pixels == null) {
      return null;
    }
    for (int i = 0; i < h; i++) {
      for (int j = 0; j < w; j++) {
        pixels[2 + x + j + (y + i) * w] = getPixel(x + j, y + i);
      }
    }
    return pixels;
  }

  /**
    * 截取指定范围内像素点
    *    
    * @param x
    * @param y
    * @return
    */
  public int getPixel(final int x, final int y) {
    return pixels[x + y * width];
  }

  public int[] getPixels() {
    return pixels;
  }

  public BufferedImage getBufferedImage() {
    return bufferImage;
  }

  public int getHeight() {
    return height;
  }

  public void setHeight(int height) {
    this.height = height;
  }

  public int getWidth() {
    return width;
  }

  public void setWidth(int width) {
    this.width = width;
  }

}

 

简单的应用示例:

package test1;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

/**
*    
* Copyright 2008 - 2009
*    
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*    
* [url]http://www.apache.org/licenses/LICENSE-2.0[/url]
*    
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*    
* @project loonframework
* @author chenpeng
* @email:[email]ceponline@yahoo.com.cn[/email]
* @version 0.1
*/
public class Sample extends Frame implements Runnable{

  /**
    *    
    */
  private static final long serialVersionUID = 1L;

  private SimpleIntBufferImage simple1 = new SimpleIntBufferImage("back.gif");

  private SimpleIntBufferImage simple2 = new SimpleIntBufferImage(
      "idle0_0.png");

  private int width = 480;

  private int height = 360;

  public Sample() {
    super("自制SimpleIntBufferImage示例");
    this.setBackground(Color.black);
    simple1.convertToXor();
    simple1.convertToXor();
    this.setPreferredSize(new Dimension(width, height));
    this.requestFocus();
    this.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        System.exit(0);
      }
    });
    this.pack();
    this.setResizable(false);
    this.setLocationRelativeTo(null);
    this.setIgnoreRepaint(true);
    Thread thread = new Thread(this);
    thread.start();
  }
    
  public void run() {
    for(;;){
      repaint();
      try {
        Thread.sleep(20L);
      } catch (InterruptedException e) {
      }
    }
  }
    
  public void update(Graphics g) {
    paint(g);
  }

  public void paint(Graphics g) {
    simple1.copyImage(simple2,15,50);
    g.drawImage(simple1.getBufferedImage(), 0, 0, null);
  }

  public static void main(String[] args) {
    Sample frm = new Sample();
    frm.setVisible(true);
  }

}

 

效果图如下:

 

Java游戏开发中怎样才能获得更快的FPS?_game_10