您是否喜歡讓程序搞一些惡作劇,讓別人感到不舒服?如果您回答“是”,那麼這個月的技巧文章一定對您的胃口。使用 J2SE 1.4,您的 Java 程序現在可以更改視頻方式並獲得對屏幕的絕對控制。您不必讓別人隨心所欲地玩電腦;您差不多可以擁有整個控制權。感謝新的全屏幕獨占模式(FEM)API 為我們提供了這個無與倫比的強大功能。
即使您回答“不”,不想以惹惱他人來取樂,您也將發現 FEM API 提供了許多幫助。通過直接對顯存進行寫操作,FEM API 提供了對顯示的完全控制 ― 這對於游戲開發來說十分理想,雖然還有許多其它應用。例如,一些程序只有用特定大小的屏幕看上去才更好,並且才能更好地工作。請繼續讀下去,以發掘您內心有關控制方面的奇思怪想。
更改顯示方式
讓我們先從研究 FEM API 的 java.awt.DislayMode 類開始,該類包裝了特定顯示方式的屏幕大小和刷新頻率。受支持的方式特定於系統的硬件支持。
要找出特定系統的受支持方式,請查看 GraphicsEnvironment 。通過該環境,您可以獲得缺省屏幕設備 GraphicDevice ,通過該屏幕設備可以獲得顯示方式,如清單 1 所示:
清單 1. 查找顯示方式
GraphicsEnvironment graphicsEnvironment =
GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice graphicsDevice =
graphicsEnvironment.getDefaultScreenDevice();
DisplayMode displayModes[] =
graphicsDevice.getDisplayModes();
還可以使用 getDisplayMode() 方法獲得當前的顯示模式,如清單 2 所示:
清單 2. 獲得當前的顯示方式
DisplayMode originalDisplayMode =
graphicsDevice.getDisplayMode();
更改方式經證實相對容易些,但必須先利用 GraphicsDevice 的 isDisplayChangeSupported() 方法詢問所涉及的圖形設備是否支持更改。
一旦知道了這一點,要更改方式,使用 setDisplayMode() 方法,以傳入新方式。顯示方式更改一般在 try / finally 塊中發生,以便於 finally 塊將代碼復位成初始方式。雖然該過程不是絕對必需的,但它可以確保在程序完成時有一個安全的方式。清單 3 顯示了用於更改顯示方式的典型模式:
清單 3. 更改方式
GraphicsDevice graphicsDevice = ...
DisplayMode originalDisplayMode = graphicsDevice.getDisplayMode();
DisplayMode newDisplayMode = ...
try {
if (graphicsDevice.isDisplayChangeSupported()) {
graphicsDevice.setDisplayMode(newDisplayMode);
}
} finally {
graphicsDevice.setDisplayMode(originalDisplayMode);
}
使用全屏幕窗口
使用 FEM API,進入全屏幕窗口是輕而易舉的:只要將 window 傳遞給 GraphicsDevice 的 setFullScreenWindow() 方法,如清單 4 所示。當您想要返回非全屏幕方式時,將 null 傳遞給該方法。當然,先通過使用 isFullScreenSupported() 方法來檢查 GraphicsDevice 是否支持全屏幕方式。
清單 4. 進入全屏幕方式
GraphicsDevice graphicsDevice = ...
Window window = ...
try {
if (graphicsDevice.isFullScreenSupported()) {
graphicsDevice.setFullScreenWindow(window);
}
} finally {
graphicsDevice.setFullScreenWindow(null);
}
為了說明目前為止您所學的所有內容,清單 5 包含了一個完整示例。清單 5 中的代碼獲得顯示方式,隨機選擇一個方式進行更改,然後顯示一個帶有文本“Hello, World!”的全屏幕窗口。該示例顯示新顯示方式的特征,這樣您可以看到特定的屏幕大小、刷新頻率和色深(bit depth)。
清單 5. 方式更改示例
import java.awt.*;
import javax.swing.*;
import java.util.Random;
public class DisplayModes {
public static void main(String args[]) {
GraphicsEnvironment graphicsEnvironment =
GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice graphicsDevice =
graphicsEnvironment.getDefaultScreenDevice();
DisplayMode displayModes[] = graphicsDevice.getDisplayModes();
DisplayMode originalDisplayMode = graphicsDevice.getDisplayMode();
JWindow window = new JWindow() {
public void paint(Graphics g) {
g.setColor(Color.blue);
g.drawString("Hello, World!", 50, 50);
}
};
try {
if (graphicsDevice.isFullScreenSupported()) {
graphicsDevice.setFullScreenWindow(window);
}
Random random = new Random();
int mode = random.nextInt(displayModes.length);
DisplayMode displayMode = displayModes[mode];
System.out.println(displayMode.getWidth() + "x" +
displayMode.getHeight() + " \t" + displayMode.getRefreshRate() +
" / " + displayMode.getBitDepth());
if (graphicsDevice.isDisplayChangeSupported()) {
graphicsDevice.setDisplayMode(displayMode);
}
Thread.sleep(1000);
} catch (InterruptedException e) {
} finally {
graphicsDevice.setDisplayMode(originalDisplayMode);
graphicsDevice.setFullScreenWindow(null);
}
System.exit(0);
}
}
全屏幕呈現
請注意:在清單 5 中,用於全屏幕的窗口包括了十分有用的 paint() 方法。然而,因為窗口處於全屏幕方式,所以 paint() 方法所需的開銷、該方法如何處理剪裁以及其它與顯示處理相關的行為都是不必要的。實際上,已經證明了標准的被動呈現開銷太大,它減慢了全屏幕應用程序的速度。例如,您不必處理如重疊窗口或調整窗口大小之類的任務。而是可以采取更主動的方法,創建一個處理呈現窗口本身的緊湊循環(tight loop)。
如果您熟悉 雙緩沖,那麼您知道它在內存中管理兩個 Image 對象,並根據哪個對象擁有 當前的顯示信息來在這兩個對象之間進行交換。在一個 Image 顯示的同時,您繪制另一個 Image 並在各個“場景”之間交換 Image 對象。
顯卡利用了類似的概念,但不是使用實際的 Java Image 對象,而是交換內存頁。當交換內存頁時顯示就發生更改,所以您不需要將 Image 對象從程序內存復制到顯示內存;您只要更改視頻指針,顯示就會發生更改。盡管現在雙緩沖概念仍然存在,但已不是對程序空間中的 Image 進行寫操作,而是直接繪制到顯示內存空間。
BufferStrategy 類隱藏了使用的是上述兩種雙緩沖方法中的哪一種這一事實,並且它允許您利用系統所提供的任何基於硬件的緩沖。要創建 BufferStrategy ,使用 createBufferStrategy() 方法告訴系統您所期望的緩沖區數目,並使用 getDrawGraphics() 方法在緩沖區之間進行交換,該方法返回下一個要使用的緩沖區。從概念上講僅此而已,但如清單 6 所示,工作代碼需要花更多的精力:
清單 6. 使用 BufferStrategy
JFrame frame = ...
frame.createBufferStrategy(2); // Number of buffers to have
BufferStrategy bufferStrategy = frame.getBufferStrategy();
while (!done()) { // Some condition to end
Graphics g = null;
try {
g = bufferStrategy.getDrawGraphics();
drawNextScene(g); // Method to draw to
} finally {
// Free resources
if (g != null) {
g.dispose();
}
}
bufferStrategy.show();
}
使用 BufferStrategy 時,您不能假設繪制到緩沖區的最後一項仍有效;必須使用 contentsLost() 方法進行詢問。如果丟失該項,則必須重新繪制整個緩沖區。否則,您只需繪制最後一次使用後發生的更改的項。
除了 BufferStrategy 的緩沖區支持外, BufferCapabilities 類允許您使用 getCapabilities() 方法來發現某個策略支持哪些能力,如全屏幕方式。
工作示例
將所有代碼段放在一起可產生清單 7 中的示例程序。由於我的美術技能有限,所以不要指望有什麼復雜的花樣。然而,您將發現一個使用 BufferStrategy 和全屏幕繪制方式的完整工作示例。示例程序通過記住什麼東西沒有繪制到當前緩沖區來有意 不讓緩沖區同步 ― 以便讓您更清楚地了解同時工作的多個緩沖區。程序的確隨意地將 100 個矩形繪制到屏幕,兩次繪制之間有 1/10 秒的延遲。
清單 7. 工作示例
import java.awt.*;
import java.awt.image.*;
import javax.swing.*;
import java.util.Random;
public class MultipleBuffers {
public static void main(String args[]) {
GraphicsEnvironment graphicsEnvironment =
GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice graphicsDevice =
graphicsEnvironment.getDefaultScreenDevice();
DisplayMode displayModes[] = graphicsDevice.getDisplayModes();
DisplayMode originalDisplayMode = graphicsDevice.getDisplayMode();
JFrame frame = new JFrame();
frame.setUndecorated(true);
frame.setIgnoreRepaint(true);
try {
if (graphicsDevice.isFullScreenSupported()) {
graphicsDevice.setFullScreenWindow(frame);
}
Random random = new Random();
int mode = random.nextInt(displayModes.length);
DisplayMode displayMode = displayModes[mode];
System.out.println(displayMode.getWidth() + "x" +
displayMode.getHeight() + " \t" + displayMode.getRefreshRate() +
" / " + displayMode.getBitDepth());
if (graphicsDevice.isDisplayChangeSupported()) {
graphicsDevice.setDisplayMode(displayMode);
}
frame.createBufferStrategy(2);
BufferStrategy bufferStrategy = frame.getBufferStrategy();
int width = frame.getWidth();
int height = frame.getHeight();
for (int i=0; i<100; i++) {
Graphics g = bufferStrategy.getDrawGraphics();
g.setColor(new Color(random.nextInt()));
g.fillRect(random.nextInt(width),
random.nextInt(height), 100, 100);
bufferStrategy.show();
g.dispose();
Thread.sleep(100);
}
} catch (InterruptedException e) {
} finally {
graphicsDevice.setDisplayMode(originalDisplayMode);
graphicsDevice.setFullScreenWindow(null);
}
System.exit(0);
}
}
示例程序將受益於一些增強功能:如使緩沖區保持同步,或者檢查當內容丟失時是否必須重新繪制整個緩沖區。前一個任務只需記住最後繪制的矩形(和顏色),而後者要求全都記住。
管中窺豹,可見一斑
有了新的全屏幕獨占模式 API,Java 開發就能成為游戲開發的主流選項。該 API 取代了使用特定於平台的 API(如 DirectX 或 OpenGL)的需求,而是依賴於跨所有支持 Java 平台的標准 API。這遠遠超出那些熱衷於編程的專家的想象。