1. 程式人生 > 實用技巧 >Java遊戲影象處理入門[0]——全屏顯示及動畫處理

Java遊戲影象處理入門[0]——全屏顯示及動畫處理

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.p_w_picpath.BufferStrategy;
import java.awt.p_w_picpath.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][email protected][/email]
* @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);
} /** *//**
* 建立一個與現有顯示模式相容的bufferedp_w_picpath
*
* @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 p_w_picpath; long endTime; public AnimationFrame(Image p_w_picpath, long endTime) ...{
this.p_w_picpath = p_w_picpath;
this.endTime = endTime;
}
} public Animation() ...{
_frames = new ArrayList();
_total = 0;
start();
} /** *//**
* 增加一個指定的動畫圖片並設定動畫時間
*
* @param p_w_picpath
* @param duration
*/
public synchronized void addFrame(Image p_w_picpath, long duration) ...{
_total += duration;
_frames.add(new AnimationFrame(p_w_picpath, _total));
} /** *//**
* 以預設播放時間載入影象陣列
*
* @param p_w_picpath
*/
public void addFrame(Image[] p_w_picpath) ...{
for (int i = 0; i < p_w_picpath.length; i++) ...{
addFrame(p_w_picpath[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++;
}
}
} /** *//**
* 獲得當前動畫p_w_picpath
*
* @return
*/
public synchronized Image getImage() ...{
if (_frames.size() == 0) ...{
return null;
} else ...{
return getFrame(_frameIndex).p_w_picpath;
}
} /** *//**
* 返回指定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.p_w_picpath.BufferedImage;
import java.awt.p_w_picpath.MemoryImageSource;
import java.awt.p_w_picpath.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][email protected][/email]
* @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_p_w_picpaths/bg01.png", false);
Image[] players1 = new Image[9];
for (int i = 0; i < 9; i++) ...{
players1[i] = loadImage("test_p_w_picpaths/marisa_0" + i + ".png", true);
}
Image[] players2 = new Image[7];
for (int i = 1; i < 8; i++) ...{
players2[i - 1] = loadImage("test_p_w_picpaths/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;
}
}
// 在記憶體中生成影象後對映到p_w_picpath
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遊戲開發中應當訂製全屏及視窗兩種顯示模式,由使用者選擇使用。當然,我並非專業的遊戲開發人員,具體的抉擇還是應當實際需要而來。