Java的图像处理能力相对较弱是一个不争的事实,因为jre需要兼顾不同系统间的相同功能实现,所以并非所有图形操作都可以利用java进行。但对于绝大多数的图形开发而言,java已经足够强大了,尤其是对2d图形游戏而言,其简单快捷的编码风格在某些时候完全可以应用到实际的游戏开发中去。
在以前的blog文章中,我曾经历居过一些简单的实例,此系列中我会进行比以前更深入的用例讲解。
以前的文章中,我总是喜欢以窗体模式的frame显示数据,这样做有例有弊。好处在于不会消耗额外的系统资源,而坏处在于若不进行相关代码调整则窗体内图像大小总是固定的,无论显示器实际分辨率如何窗体中图形总是会以默认大小显示,而且在窗体环绕中的游戏临场感也不够强。
实际上Java中的frame是可以转化为全屏方式显示的,只是我们善于利用GraphicsEnvironment类,就可以对系统显示环境作相应的调整操作。
ScreenManager.java
package org.loon.game.test;
import java.awt.Color;
import java.awt.DisplayMode;
import java.awt.EventQueue;
import java.awt.Frame;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.lang.reflect.InvocationTargetException;
/** *//**
*
* <p>Title: LoonFramework</p>
* <p>Description:用于进行屏幕管理</p>
* <p>Copyright: Copyright (c) 2008</p>
* <p>Company: LoonFramework</p>
* <p>License: [url]http://www.apache.org/licenses/LICENSE-2.0</p[/url]>
* @author chenpeng
* @email:
[email protected]
* @version 0.1
*/
public class ScreenManager ...{
private GraphicsDevice device;
public ScreenManager() ...{
GraphicsEnvironment environment = GraphicsEnvironment
.getLocalGraphicsEnvironment();
device = environment.getDefaultScreenDevice();
}
/** *//**
* 返回系统支持的显示模式数组
*
* @return
*/
public DisplayMode[] getCompatibleDisplayModes() ...{
return device.getDisplayModes();
}
/** *//**
* 返回与指定数组兼容的显示模式清单
*
* @param modes
* @return
*/
public DisplayMode findFirstCompatibleMode(DisplayMode modes[]) ...{
DisplayMode goodModes[] = device.getDisplayModes();
for (int i = 0; i < modes.length; i++) ...{
for (int j = 0; j < goodModes.length; j++) ...{
if (displayModesMatch(modes[i], goodModes[j])) ...{
return modes[i];
}
}
}
return null;
}
/** *//**
* 返回目前采用的显示模式
*
* @return
*/
public DisplayMode getCurrentDisplayMode() ...{
return device.getDisplayMode();
}
/** *//**
* 匹配两个指定的显示模式
*
* @param mode1
* @param mode2
* @return
*/
public boolean displayModesMatch(DisplayMode mode1, DisplayMode mode2)
...{
if (mode1.getWidth() != mode2.getWidth()
|| mode1.getHeight() != mode2.getHeight()) ...{
return false;
}
if (mode1.getBitDepth() != DisplayMode.BIT_DEPTH_MULTI
&& mode2.getBitDepth() != DisplayMode.BIT_DEPTH_MULTI
&& mode1.getBitDepth() != mode2.getBitDepth()) ...{
return false;
}
if (mode1.getRefreshRate() != DisplayMode.REFRESH_RATE_UNKNOWN
&& mode2.getRefreshRate() != DisplayMode.REFRESH_RATE_UNKNOWN
&& mode1.getRefreshRate() != mode2.getRefreshRate()) ...{
return false;
}
return true;
}
/** *//**
* 设置一个默认窗体的全屏模式
*
* @param displayMode
*/
public void setFullScreen(DisplayMode displayMode) ...{
final Frame frame = new Frame();
frame.setBackground(Color.BLACK);
setFullScreen(displayMode, frame);
}
/** *//**
* 设定指定窗体的全屏模式
*
* @param displayMode
* @param window
*/
public void setFullScreen(DisplayMode displayMode, final Frame window) ...{
window.addWindowListener(new WindowAdapter() ...{
public void windowClosing(WindowEvent e) ...{
System.exit(0);
}
});
window.setUndecorated(true);
window.setResizable(false);
window.setIgnoreRepaint(false);
device.setFullScreenWindow(window);
if (displayMode != null && device.isDisplayChangeSupported()) ...{
try ...{
device.setDisplayMode(displayMode);
} catch (IllegalArgumentException ex) ...{
}
window.setSize(displayMode.getWidth(), displayMode.getHeight());
}
try ...{
EventQueue.invokeAndWait(new Runnable() ...{
public void run() ...{
window.createBufferStrategy(2);
}
});
} catch (InterruptedException ex) ...{
} catch (InvocationTargetException ex) ...{
}
}
/** *//**
* 取得当前的Graphics2D模式背景
*
* @return
*/
public Graphics2D getGraphics() ...{
Window window = device.getFullScreenWindow();
if (window != null) ...{
BufferStrategy strategy = window.getBufferStrategy();
return (Graphics2D) strategy.getDrawGraphics();
} else ...{
return null;
}
}
/** *//**
* 刷新显示的数据
*
*/
public void update() ...{
Window window = device.getFullScreenWindow();
if (window != null) ...{
BufferStrategy strategy = window.getBufferStrategy();
if (!strategy.contentsLost()) ...{
strategy.show();
}
}
// 同步
Toolkit.getDefaultToolkit().sync();
}
/** *//**
* 返回当前窗口
*
* @return
*/
public Frame getFullScreenWindow() ...{
return (Frame) device.getFullScreenWindow();
}
public int getWidth() ...{
Window window = device.getFullScreenWindow();
if (window != null) ...{
return window.getWidth();
} else ...{
return 0;
}
}
public int getHeight() ...{
Window window = device.getFullScreenWindow();
if (window != null) ...{
return window.getHeight();
} else ...{
return 0;
}
}
/** *//**
* 恢复屏幕的显示模式
*
*/
public void restoreScreen() ...{
Window window = device.getFullScreenWindow();
if (window != null) ...{
window.dispose();
}
device.setFullScreenWindow(null);
}
/** *//**
* 创建一个与现有显示模式兼容的bufferedimage
*
* @param w
* @param h
* @param transparancy
* @return
*/
public BufferedImage createCompatibleImage(int w, int h, int transparancy) ...{
Window window = device.getFullScreenWindow();
if (window != null) ...{
GraphicsConfiguration gc = window.getGraphicsConfiguration();
return gc.createCompatibleImage(w, h, transparancy);
}
return null;
}
}
上面我所给出的,是一个Screen的管理类,他集合了对窗体Graphics的操作处理,用例如下。
ScreenTest .java
package org.loon.game.test;
import java.awt.Color;
import java.awt.DisplayMode;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
public class ScreenTest extends Frame ...{
/** *//**
*
*/
private static final long serialVersionUID = 1L;
private static final long TIME = 9000;
public static void main(String[] args) ...{
// 创建一个显示模式及设定参数,分别为:宽、高、比特位数、刷新率(赫兹)
DisplayMode displayMode = new DisplayMode(800, 600, 16,
DisplayMode.REFRESH_RATE_UNKNOWN);
ScreenTest test = new ScreenTest();
test.run(displayMode);
}
public void run(DisplayMode displayMode) ...{
setBackground(Color.black);
setForeground(Color.white);
setFont(new Font("Dialog", 0, 24));
ScreenManager screen = new ScreenManager();
try ...{
screen.setFullScreen(displayMode, this);
try ...{
Thread.sleep(TIME);
} catch (InterruptedException ex) ...{
}
} finally ...{
screen.restoreScreen();
}
}
public void paint(Graphics g) ...{
g.drawString("Hello World!", 50, 50);
}
}
效果图如下:
我们可以看到,此时一个全屏显示的Hello World被创建出来。以此为基础,我们更可以轻松的创建全屏状态下的动画效果。
Animation.java
package org.loon.game.test;
import java.awt.Image;
import java.util.ArrayList;
public class Animation ...{
// 缓存动画面板
private ArrayList _frames;
private int _frameIndex;
private long _time;
private long _total;
private class AnimationFrame ...{
Image image;
long endTime;
public AnimationFrame(Image image, long endTime) ...{
this.image = image;
this.endTime = endTime;
}
}
public Animation() ...{
_frames = new ArrayList();
_total = 0;
start();
}
/** *//**
* 增加一个指定的动画图片并设置动画时间
*
* @param image
* @param duration
*/
public synchronized void addFrame(Image image, long duration) ...{
_total += duration;
_frames.add(new AnimationFrame(image, _total));
}
/** *//**
* 以默认播放时间载入图像数组
*
* @param image
*/
public void addFrame(Image[] image) ...{
for (int i = 0; i < image.length; i++) ...{
addFrame(image[i], 500);
}
}
/** *//**
* 开始执行动画
*
*/
public synchronized void start() ...{
_time = 0;
_frameIndex = 0;
}
/** *//**
* 更新此动画播放时间
*
* @param time
*/
public synchronized void update(long time) ...{
if (_frames.size() > 1) ...{
_time += time;
if (_time >= _total) ...{
_time = _time % _total;
_frameIndex = 0;
}
while (_time > getFrame(_frameIndex).endTime) ...{
_frameIndex++;
}
}
}
/** *//**
* 获得当前动画image
*
* @return
*/
public synchronized Image getImage() ...{
if (_frames.size() == 0) ...{
return null;
} else ...{
return getFrame(_frameIndex).image;
}
}
/** *//**
* 返回指定frame
*
* @param i
* @return
*/
private AnimationFrame getFrame(int i) ...{
return (AnimationFrame) _frames.get(i);
}
}
上面是一个动画面板,用于记录一组连续的画面。
AnimationSimple.java
...
package org.loon.game.test;
import java.awt.DisplayMode;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.awt.image.MemoryImageSource;
import java.awt.image.PixelGrabber;
import javax.swing.ImageIcon;
/** *//**
*
* <p>
* Title: LoonFramework
* </p>
* <p>
* Description:动画演示
* </p>
* <p>
* Copyright: Copyright (c) 2008
* </p>
* <p>
* Company: LoonFramework
* </p>
* <p>
* License: [url]http://www.apache.org/licenses/LICENSE-2.0[/url]
* </p>
*
* @author chenpeng
* @email:
[email protected]
* @version 0.1
*/
public class AnimationSimple ...{
// 动作时间
private static final long TIME = 20000;
final static private int WIDTH = 800;
final static private int HEIGHT = 600;
private ScreenManager _screen;
private Image _bgImage;
private Animation _animation1;
private Animation _animation2;
private Image _cacheImage;
private Graphics _graphics;
public static void main(String args[]) ...{
// 创建一个显示模式及设定参数,分别为:宽、高、比特位数、刷新率(赫兹)PS:frame中图像会自动放大
DisplayMode displayMode = new DisplayMode(WIDTH, HEIGHT, 16, 0);
AnimationSimple test = new AnimationSimple();
test.run(displayMode);
// System.out.println((0 << 16) | (0 << 8) | 0);
}
public void loadImages() ...{
// 为保持图片同步加载,构建一个cache作为二级缓存
_cacheImage = new BufferedImage(WIDTH, HEIGHT, 2);
_graphics = _cacheImage.getGraphics();
// 加载图片
_bgImage = loadImage("test_images/bg01.png", false);
Image[] players1 = new Image[9];
for (int i = 0; i < 9; i++) ...{
players1[i] = loadImage("test_images/marisa_0" + i + ".png", true);
}
Image[] players2 = new Image[7];
for (int i = 1; i < 8; i++) ...{
players2[i - 1] = loadImage("test_images/reimu2_0" + i + ".png",
true);
}
// 创建动画
_animation1 = new Animation();
_animation1.addFrame(players1);
_animation2 = new Animation();
_animation2.addFrame(players2);
}
/** *//**
* 加载图象
*
* @param fileName
* @param isfiltration
* @return
*/
private Image loadImage(String fileName, boolean isfiltration) ...{
// 当ImageIcon使用jar包本身资源时,需要通过jvm获得路径
ClassLoader classloader = getClass().getClassLoader();
Image img = new ImageIcon(classloader.getResource(fileName)).getImage();
// 为了演示例子没有使用偶的loonframework-game包(那天整合下准备发alpha了),而是直接处理了图像
if (isfiltration) ...{
int width = img.getWidth(null);
int height = img.getHeight(null);
// 创建一个PixelGrabber
PixelGrabber pg = new PixelGrabber(img, 0, 0, width, height, true);
try ...{
pg.grabPixels();
} catch (InterruptedException e) ...{
e.printStackTrace();
}
// 获取其中像素
int pixels[] = (int[]) pg.getPixels();
// 遍历过滤像素
for (int i = 0; i < pixels.length; i++) ...{
// -16777216为0,0,0即纯黑
if (pixels[i] == -16777216) ...{
// 转为透明色
// 16777215也就是255,255,255即纯白 处理为 (255 << 16) | (255 << 8) |
// 255 后可得此结果
pixels[i] = 16777215;
}
}
// 在内存中生成图像后映射到image
img = Toolkit.getDefaultToolkit().createImage(
new MemoryImageSource(width, height, pixels, 0, width));
}
return img;
}
public void run(DisplayMode displayMode) ...{
_screen = new ScreenManager();
try ...{
_screen.setFullScreen(displayMode);
// 初始化图像加载
loadImages();
// 动画循环播放
animationLoop();
} finally ...{
_screen.restoreScreen();
}
}
public void animationLoop() ...{
long startTime = System.currentTimeMillis();
long currTime = startTime;
// 每次比较工作时间,无效时将退出运作
while (currTime - startTime < TIME) ...{
long elapsedTime = System.currentTimeMillis() - currTime;
currTime += elapsedTime;
// 改变动画1
_animation1.update(elapsedTime);
// 改变动画2
_animation2.update(elapsedTime);
// 绘制窗体
Graphics2D g = _screen.getGraphics();
draw(g);
g.dispose();
// 更新显示内容
_screen.update();
try ...{
Thread.sleep(20);
} catch (InterruptedException ex) ...{
}
}
}
public void draw(Graphics g) ...{
// 绘制背景0,0座标
_graphics.drawImage(_bgImage, 0, 0, null);
// 绘制动画于0,0座标
_graphics.drawImage(_animation2.getImage(), 0, 0, null);
// 绘制动画于400,0座标
_graphics.drawImage(_animation1.getImage(), 400, 0, null);
// 将缓存图绘制于窗体0,0座标之上
g.drawImage(_cacheImage, 0, 0, null);
}
}
上面是一个动画演示用例,创建了一个背景及两个动画角色,效果图如下:
实际上无论多么复杂的画面效果,也是以这些最基础的环节拼凑而成的。
但是我们也都知道,举凡有利也就有弊,首先这种全屏显示方式无疑增加了系统资源损耗,而且由于分辨率改变造成的图像位置偏移也可能会影响到游戏效果,也增加了潜在错误的产生几率。比如国产的纯java网游《海天英雄传》中,虽然游戏初期只提供了全屏的游戏方式,但后来却突然增加窗口模式,我想或多或少也是与此有关的。
所以我个人建议在java游戏开发中应当订制全屏及视窗两种显示模式,由用户选择使用。当然,我并非专业的游戏开发人员,具体的抉择还是应当实际需要而来。