日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java游戏编程不完全详解-2

發布時間:2023/12/20 java 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java游戏编程不完全详解-2 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

三種Java游戲類型

使用Java我們可以創建三種類型的游戲:applet游戲,窗體游戲和全屏幕游戲。

  • applet游戲—是運行在瀏覽器中的應用。它的好處理用戶不需要安裝應用。但是用戶必須安裝JRE并且必須在web瀏覽器中運行。另外,applet小程序還有安全限制,以保證它不惡意破壞本地代碼。比如applet程序不能把游戲保存到用戶機的硬盤中去。它只能通過網絡連接一個服務器
  • 窗體游戲—該類型的游戲沒有applet流程的安全限制,它與普通的應用一樣,有標量欄、關閉按鈕等。但是它不吸收用戶,特別是當我們沉浸在游戲中時。
  • 全屏幕游戲—沒有桌面元素,比如標題欄、任務欄和菜單欄,這樣玩家可完全沉浸在游戲情節當中。

全屏幕繪圖

在計算機中有兩部分顯示硬件:顯卡和顯示器。顯卡保存屏幕的內容,這些內容是在顯卡的內存中存在的,它會呼叫一些函數來修改顯示內容,另外顯卡在顯示器背后工作,它把內存中的內容push到顯示器來呈現。而顯示器只是簡單的呈現顯卡告訴它的內容。

屏幕布局—顯示器屏幕被分解大小相等的像素(color pixel)。像素來自術語圖片元素(picture element)的概念,它是由顯卡顯示的單個亮點。水平和垂直的像素組成了屏幕(screen)布局。

屏幕的原點是屏幕的左上角,像素存貯在顯卡的內存中,它從左上角開始從左到右讀,從上到下讀取。屏幕中的位置表示 (x,y)座標,x表示從原點開始的水平方向的像素個數,y表示從原點開始的垂直方向的像素個數。

屏幕顯示的效果依賴于顯卡和顯示器的能力,一般解決方案有640x480, 800x600, 1024x768, 和 1280x1024布局方式。

一般顯示器的尺寸比率是4:3,這表示高度顯示是寬度的四分之三。一般寬屏使用16:9的比率。老式的CRT顯示器可完成實現以上策略,因為它使用電子光柵來表示像素。現代的LCD顯示器,因為它比電子管亮,所以它有自己的顯示策略,它的失真比較明顯,所以我們的游戲必須保證兩種或者三種顯示策略中能夠運行。

像素顏色和位層次(Bit Depth)

我們都知道三種基本顏色:紅、黃和蘭色。黃色+蘭色=綠色,三種顏色的不同組合會產生自己想要的顏色,去掉所有的色值就是白色。同樣顯示也使用這三種基色來產生需要的顏色,顯示器發亮,所以RGB顏色模型是一個加法模型,也就是添加所有的色值就會產生白色。顯示器的顏色值依賴于位層次(bit depth)來表示色值,一般位層次分為8, 15, 16, 24和32位。

  • 8位顏色有2的8次方為256顏色,也就是一次只能顯示256種顏色,這些顏色基于顏色面板。

刷新率(Refresh Rate)—雖然我們的顯示器看起來像是顯示一個固定的圖片,每個像素實際上會在幾毫秒中消失。所以顯示器會不間斷的刷新以彌補像素消失效果。那么刷新的頻率就是刷新率,單位是Hertz—即一秒鐘中之內重復的次數。

Now that you know all about resolutions, bit depths, and refresh rates

不幸的是,當前的Java版本(它指的是JDK 1.4版本)不能修改調色板(我沒有試過更高版本的,因為我開游戲客戶端是使用C++來實現的,因為實際開發中不用的話,我就不研究image-20210325194659853.png),也不能描述這些是什么。Java運行時可以使用一個web-safe調色板來表示顏色:對于紅綠蘭色每種都6個色值(6x6x6=216 )。

  • 15位的紅綠蘭有2的15次方值32,768顏色
  • 16位的紅綠蘭有2的15次方值65,536顏色
  • 24位紅綠蘭有2的15次方值16,777,216顏色
  • 32位顏色與24一樣,但是有8位的填充像素

大多數現代的顯卡支持8位、16位和32位模型,因為人類的眼睛可以看到1千萬種顏色,24位是理想的,16位會比24位顯示速度快,但是質量不好。

控制全屏幕顯示模型

Window對象—Window對象是被顯示屏幕的抽象。我們可以把它想像成一個畫布,在Java的API中是使用JFrame來抽象表示的,該類是Window類(Window 對象是一個沒有邊界和菜單欄的頂層窗口。窗口的默認布局是 BorderLayout)。JFrame是Window類的子類,它可以被使用在窗體應用中。

  • DisplayMode對象—顯示模型對象指定屏幕顯示策略、位層次和顯示的刷新率
  • GraphicsDevice對象—圖形設備對象可以修改顯示模型,我們可把它當成顯卡來使用。該對象從GraphicsEnvironment對象獲取。

SimpleScreenManager類

import java.awt.*; import javax.swing.*;/**功能:書寫一個實現全屏幕顯示的類作者:技術大黍*/ public class SimpleScreenManager{//聲明一個顯卡對象--該類是描述了特定圖形環境中使用的圖形設備。這些設備包括屏幕和打印設備。//因為GraphicsEnviornment實例中可以有許多屏幕和打印機,所以每個圖形設備有一個或者多個與之//關聯的GraphicsConfiguration對象。這些配置對象可以指定GraphicsDevice對象的不同配置。//在多屏幕環境中,GraphicsConfiguration對象可以用于多個屏幕上的組件呈現。private GraphicsDevice device;/**在構造方法中初始化成員變量*/public SimpleScreenManager(){//GraphicsEnvironment類描述了應用程序在特定平臺上可以的GraphicsDevice對象和Font對象集合//因此該資源可以是本地資源,也可以位于遠程機器上的資源。GraphicsDevice對象可以屏幕、打印//機或者圖像緩沖區,并且都是Graphics2D對象的繪制目標。每個GraphicsDevice都有許多與之相關的//GraphicsConfiguration對象,這些配置對象指定了GraphicsDevice所需的不同配置。GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment();device = environment.getDefaultScreenDevice();}/**功能:公開一個設置指定窗體的全屏幕顯示模型的方法--該是方法是一個控制是否全屏幕核心方法不使用雙緩存顯示策略來顯示圖片。參數:DisplayMode displayMode是顯示模型JFrame window是被設置的窗體對象*/public void setFullScreen(DisplayMode displayMode, JFrame window){window.setUndecorated(true);//不需要裝飾區域window.setResizable(false); //不允許縮放//把window對象作為參數傳給顯卡對象--告訴顯卡在顯示時使用全屏幕模型//注意:GraphicsDevice的setFullScreenWindow是執行全屏幕顯示的語句//當GraphicsDevice設置完了window對象之后,它會呼叫window的paint()方法//setFullScreenWindow方法會讓當前屏幕顯示進入全屏幕模型,或者返回容器化模型狀態。//進入全屏幕模型是獨占的,也可以模擬的。只有isFullScreentSupported返回true值,//這時電腦屏幕才會進入獨占模型。獨占模型意味著:(1)Windows無法重疊全屏幕窗口,因此//當已存在全屏幕窗口時,再調用此方法會導致現面的全屏幕窗口返回窗口化模型!(2)禁用//輸入方法窗體,只能呼叫Component.enableInputMethods(false)方法可以讓一個非客戶組件//作為輸入方法窗體使用。如果w作為全屏幕窗口,那么當設置w為null時返回窗口化模型。如果//一些平臺希望全屏幕窗口成為頂層組件(Frame),那么最好使用java.awt.Frame類,而不JFrame類。device.setFullScreenWindow(window);//如果顯示模型不為null,并且顯卡可以支持顯示該屏幕if(displayMode != null && device.isDisplayChangeSupported()){try{device.setDisplayMode(displayMode);//那么設置顯示模型}catch(IllegalArgumentException ex){ex.printStackTrace();//否則顯示異常信息}}}/**功能:返回當前的全屏幕的窗體模型對象*/public Window getFullScreenWindow(){return device.getFullScreenWindow();}/**功能:恢復屏幕的顯示模型*/public void restoreScreen(){Window window = device.getFullScreenWindow();//如果window不為nullif(window != null){window.dispose(); //那么讓窗體釋放占有的資源}device.setFullScreenWindow(null);//把窗體設置為窗體模型null}//使用Graphics2D來渲染顯示文字public Graphics getAntiAliasing(Graphics g){if(g instanceof Graphics2D){Graphics2D g2 = (Graphics2D)g;g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_ON);}return g;} }

SimpleScreenManagerTest類

import java.awt.*; import javax.swing.*;/**功能:書寫一個實現全屏幕顯示的類作者:技術大黍*/public class SimpleScreenManagerTest extends JFrame{private DisplayMode displayMode;private static final long DEMO_TIME = 5000;public SimpleScreenManagerTest(String[] args){//設置指定的顯示模型if(args.length == 3){displayMode = new DisplayMode(Integer.parseInt(args[0]),Integer.parseInt(args[1]),Integer.parseInt(args[2]),DisplayMode.REFRESH_RATE_UNKNOWN);}else{//否則使用默認顯示模式displayMode = new DisplayMode(800,600,16,DisplayMode.REFRESH_RATE_UNKNOWN);}//運行全屏幕顯示run(displayMode);}private void run(DisplayMode displayMode){setBackground(Color.red);setForeground(Color.white);setFont(new Font("Dialog",Font.PLAIN, 24));//使用全屏幕管理器SimpleScreenManager screen = new SimpleScreenManager();try{//呼叫它的setFullScreen方法,讓它把當前的屏幕變成全屏幕--核心代碼就是這里screen.setFullScreen(displayMode, this);//getDefaultWindow();try{Thread.sleep(DEMO_TIME);}catch(InterruptedException ex){ex.printStackTrace();}}finally{screen.restoreScreen();}}/**功能:重寫paint方法--繪制容器。該方法將 paint 轉發給任意一個此容器子組件的輕量級組件在窗體中顯示字符串。在顯示全屏幕之后,在屏幕中繪制文字!Graphics類是圖形上下文的抽象基類,它允許應用程序組件,以及閉屏圖像上進行繪制。該類封裝了Java支持的基本呈現操作所需要的狀態信息:1、需要在其上繪制的Component對象2、呈現和剪貼坐標的轉換原點3、當前的剪貼區4、當前的顏色5、當前的字體6、當前的邏輯像素操作函數(XOR或者Paint)7、當前XOR交替顏色坐標是無限細分的,并且位于輸出設備的像素之間。繪制圖形輪廓的操作是通過使用像素大小的畫筆遍歷像素間無限細分路徑的操作,畫筆從路徑上的錨點向下和向右繪制,填充圖形的操作是填充圖形內部區域無限細分路徑操作。呈現水平文本的操作是呈現字符字形完全位于基線坐標之后的上升部分。圖形畫筆從要遍歷的路徑向下和向右繪制的含義如下:1、如果繪制一個覆蓋給定矩形的圖形,那么該圖形與填充被相同矩形所限定的圖形相比,在右和底邊多占一和像素2、如果沿著與一行文本基線相同的y坐標繪制一條水平線,那么除了文字的所有下降部分外,該線完全在文本的下面。所有作為此Graphics對象方法的參數而出現的坐標,都是相對于調用該方法前的此Graphics對象轉換原點的。所有呈現操作僅修改當前剪貼區域內的像素,此剪貼區域是由空間中的shape指定的,并且通過使用Gaphics對象的程序來控制。此用戶剪貼區被轉換到設備空間中,并與設備剪貼區組合。后者是通過窗口可見性和設備范圍定義的。用戶剪貼區和設備剪貼區的組合定義為復合剪貼區,復合剪貼區確定最終的剪貼區域。用戶剪貼區不由呈現系統修改,所以得由復合剪貼區來修改。在用戶剪貼區只通過setClip和clipREct方法修改。所有的繪制或寫入都以當前的顏色、當前繪圖模型和當前字體完成。*/public void paint(Graphics g){g = new SimpleScreenManager().getAntiAliasing(g);g.drawString("Hello World!(你好: Java!) ^_^", 20, 50);repaint();}private void getDefaultWindow(){//使用JFrame封裝的行為來顯示窗體setSize(200,200);Container container = getContentPane();container.add(new JButton("測試第二個"),BorderLayout.SOUTH);setDefaultCloseOperation(EXIT_ON_CLOSE);setVisible(true);}public static void main(String[] args){new SimpleScreenManagerTest(args);} }

SimpleFullScreentTest類是使用try/finally塊來完成全屏幕的顯示,在finally語句塊中恢復窗體顯示模型,如果本地沒有顯卡沒有恰當的顯示模型支持,那么拋出異常。

另外,在Graphics對象在paint方法中使用,該對象提供所有功能:繪制文本、線條、矩形、橢圓、多邊形、圖形等。大多數方法都的自明的(self-explanatory),所以,如果需要使用,可以查看Java API規范來使用。

那么paint方法是怎樣被呼叫呢?當JFrame被顯示時,Java的Abstratct Window Toolkit會呼叫組件的paint方法。如果需要強制呼叫paint方法,那么需要我們呼叫repaint方法即可,因為該方法會給AWT一個信號,然后讓AWT來呼叫paint方法。AWT會發送paint事件在兩個線程,所以如果我們需要發送repaint事件,并且等等繪制完成,那么應該這樣書寫:

public class MyComponent extends SomeComponent{…public synchronized void repaintAndWait(){repaint();try{wait();}catch(InterruptedException ex){}}public synchronized void paint(){//do painting here…notifyAll();//通知等待的線程...} }

在VS Code中的運行效果

在SimpleFullScreenTest中的字樣有鋸齒狀態。

在SimpleFullScreenTest中的字樣有鋸齒狀態。

如果希望顯示字符比較平滑,那么需要使用Graphics2D對象來處理。


圖片

除了在屏幕繪制文本以外,我們還會在屏幕中繪制圖片,繪制圖片需要我們知道兩個基本概念:透明度類型和文件格式。 圖片的背景依賴于圖片的透明度來表示,我們可以使用三種圖片透明度:不透明(opaque)、透明(transparent)和半透明(translucent):

  • opapque—圖片中的每個像素都是可見的
  • transparent—圖片中像素中完成可見或者可以看穿透的。對于白色背景在透明時,可以從它看到它下面的像素
  • translucent—半透明,它用于一個圖片的邊緣和Anti-aliasing圖片

文件格式

圖片格式有兩種基本類型:raster(光柵)和vector(矢量)。光柵類型使用像素來描述圖片;矢量圖片格式描述幾何圖形,它可以縮放后不會變形。 Java運行時有三種內置的光柵格式:GIF, PNG和JPEG.

  • GIF—該格式圖片可以不透明或者透明,它是8位顏色。
  • PNG—該格式可以有不透明、透明和半透明,它支持24位顏色
  • JPEG—只能是不透明,它是24位圖片

讀取圖片

讀取圖片我們使用ToolKit類的getImage()方法,它解析文件,然后返回Image對象:

Toolkit toolkit = Toolkit.getDefaultToolkit(); Image image = toolkit.getImage(fileName);

上面的代碼并不是實際裝載該圖片,該圖片只是被裝載另外一個線程中去了。我們可以使用MediaTracker對象來檢查該圖片,并且等待它裝載完畢,但是我們還有更簡單化的解決方案—使用ImageIcon類,該類使用MediaTracker來幫助我們裝載圖片。

ImageIcon icon = new ImageIcon(fileName); Image image = icon.getImage();

ImageFrameTest類

import java.awt.*; import javax.swing.*;/**功能:書寫一個測試類用來說明怎樣裝載圖片作者:技術大黍*/ public class ImageFrameTest extends JFrame{/*DisplayMode類封裝了GraphicsDevice的位深、高度、寬度和刷新頻率。修改圖形設置的顯示模式的能力是與平臺和配置有關的。可能并不總是可用的。*/private DisplayMode displayMode;private static final int FONT_SIZE = 24;private static final long DEMO_TIME = 20000;private SimpleScreenManager screen; //使用圖片管理器private Image bgImage; //表示背景圖片--其中抽象類Image是表示圖形圖像//的所有類的超類,必須特定于平臺的方式獲取圖像private Image opaqueImage; //表示不透明圖片private Image transparentImage; //表示透明圖片private Image translucentImage; //表示半透明圖片private Image antiAliasedImage; //表示反鋸齒狀圖片private boolean imagesLoaded; //圖片是否裝載完成private void run(DisplayMode displayMode){setBackground(Color.blue);setForeground(Color.white);setFont(new Font("Dialog", Font.PLAIN, FONT_SIZE));imagesLoaded = false;//使用屏幕管理器screen = new SimpleScreenManager();try{screen.setFullScreen(displayMode, this);//注意這里是關鍵代碼loadImages();//裝載圖片try{Thread.sleep(DEMO_TIME);//讓主線程睡覺2分鐘}catch(InterruptedException ex){ex.printStackTrace();}}finally{screen.restoreScreen();}}/*裝載圖片之后,呼叫repaint方法,讓swing框架呼叫paint方法,以便顯示出來。*/private void loadImages(){bgImage = loadImage("images/background.jpg");opaqueImage = loadImage("images/opaque.png");transparentImage = loadImage("images/transparent.png");translucentImage = loadImage("images/translucent.png");antiAliasedImage = loadImage("images/antialiased.png");imagesLoaded = true;//裝載完成之后讓AWT刷新畫面重新paint一次當前畫面repaint();}/**繪制該容器(JFrame對象)。該方法將paint轉發給任意一個此容器組件的輕量級組件,如果重新實現此方法,那么JVM應該調用super.paint(g)方法,從而正確呈現輕量級組件。如果通過g中的當前剪切設置完全剪切某個子組件,那么不會將paint轉換給這個子組件。*/public void paint(Graphics g){g = screen.getAntiAliasing(g);//繪制圖片,如果裝載完畢if(imagesLoaded){//那么在屏幕中繪制出來g.drawImage(bgImage,0,0,null);drawImage(g,opaqueImage,0,0,"不透明");drawImage(g,transparentImage,320,0,"透明");drawImage(g,translucentImage,0,300,"半透明");drawImage(g,antiAliasedImage,320,300,"半透明(Anti-Aliased)");}else{//否則顯示等信息g.drawString("裝載圖片...", 5, FONT_SIZE);}}/*具體呼叫Graphics的drawImage方法來繪制圖形到屏幕中。其中Graphics的drawImage方法是用來繪制指定圖像中當前可用的圖像,圖像的左上角位于該圖形上下文坐標空間的(x,y)。圖像中的透明像素不處已存在的像素,此方法在任何情況下都立刻返回,甚至在圖像尚未完整加載的情況,并且還沒有針對當前輸出設備完成抖動和轉換的情況也是如此。如果圖像已經完整加載,并且其像素不再發生更改,那么drawImage返回true值;否則drawImage返回false值,并且隨著更多的圖像可以用或者到了繪制動畫另一幀的時候,加載圖像的進程將通知指定的圖像觀察者。*/private void drawImage(Graphics g, Image image, int x, int y, String caption){g.drawImage(image,x,y,null);g.drawString(caption,x + 5, y + FONT_SIZE + image.getHeight(null));}/*呼叫ImageIcon對象的getImage方法初始化Image對象。因為ImageIcon是一個Icon接口的實現類,它根據Image來繪制Icon對象。我們可以使用MediaTracker對象來跟蹤該圖像的加載狀態。注意:該類序列化對象與以后的Swing版本不兼容。*/private Image loadImage(String fileName){return new ImageIcon(fileName).getImage();}/**在構造方法初始化成員變量*/public ImageFrameTest(String[] args){if(args.length == 3){displayMode = new DisplayMode(Integer.parseInt(args[0]),Integer.parseInt(args[1]),Integer.parseInt(args[2]),DisplayMode.REFRESH_RATE_UNKNOWN);}else{displayMode = new DisplayMode(1024,768,16,DisplayMode.REFRESH_RATE_UNKNOWN);}//執行顯示方法run(displayMode);}public static void main(String[] args){new ImageFrameTest(args);} }

運行效果


關鍵代碼

關鍵代碼是讓主線程等待2分鐘,系統去裝載圖片,并且在裝載之后重新刷新畫面,讓圖片顯示出來。


硬件加速圖片顯示

硬件加速圖片顯示(hardware-accelerated image)是圖片被存貯在顯示內存,而不是系統內存中,所以使用硬件加速的圖片顯示速度非常快。使用Toolkit的getImage()方法,Java使用主動使用硬件加速圖片功能,所以我們不必擔心圖片的硬件加速問題。

只是使用我們需要注意:

  • 如果我們連續不斷的修改圖片顯示內容,那么Java不使用加速功能
  • JDK 1.4.1不加速半透明圖片顯示,只加速不透明和透明
  • 不是每個操作系統都具有硬件加速功能

如果我們需要強制使用硬件的圖形加速顯示功能,那么我們需要使用VolatileImage類來完成。因為VolatileImage是被存貯在顯存中的圖形。該類的對象使用Component的createVolatileImage方法創建,或者使用GraphicsConfiguration的createCompatibleVolatileImage方法來創建。這種類型的圖片只能是不透明的,因為它是volatile的圖片,所以需要我們經常重繪這種類型的圖片。

我們可以使用validate()和contentsLost()方法來判斷顯示的圖片內容是否有丟失。前者方法可以判斷圖片是否與當前的顯示模型匹配;后者返回顯示的圖片內容是否有丟失。下面是示例代碼:

import static java.lang.System.*; import java.awt.*; import javax.swing.*;/**功能:書寫一個類用來演示怎樣使用硬件加速作者:技術大黍備注:沒有使用強制模型來實現硬件加速顯示。*/ public class ImageSpeedFrameTest extends JFrame{private DisplayMode displayMode;private static final int FONT_SIZE = 24;private static final long TIME_PER_IMAGE = 2500;private SimpleScreenManager screen;private Image bgImage;private Image oqaqueImage;private Image transparentImage;private Image translucentImage;private Image antiAliasedImage;private boolean imagesLoaded;private void run(DisplayMode displayMode){setBackground(Color.blue);setForeground(Color.white);setFont(new Font("Dialog", Font.PLAIN, FONT_SIZE));imagesLoaded = false; //標識該圖片是否已經被裝載//使用SimpleScreenManager類封裝過的方法--實現全屏幕顯示的功能screen = new SimpleScreenManager();try{screen.setFullScreen(displayMode,this);synchronized(this){//注意這里需要同步loadImages(); //裝載圖片try{wait(); //讓主線程等待IO操作完畢}catch(InterruptedException ex){ex.printStackTrace();}}}finally{screen.restoreScreen();//恢復顯示狀態}}//讓系統裝載需要的圖片private void loadImages(){bgImage = loadImage("images/background.jpg");oqaqueImage = loadImage("images/funfreebird.png");transparentImage = loadImage("images/cat.png");translucentImage = loadImage("images/books.png");antiAliasedImage = loadImage("images/apple.png");imagesLoaded = true;//讓AWT重繪窗體repaint();//讓系統呼叫paint方法來顯示圖形}private final Image loadImage(String fileName){return new ImageIcon(fileName).getImage();}public void paint(Graphics g){//要求顯示時反鋸齒功能顯示g = screen.getAntiAliasing(g);if(imagesLoaded){//如果圖片裝載到內存完畢//那么才能繪制該圖片到屏幕上drawImage(g,oqaqueImage,"不透明");drawImage(g,transparentImage,"透明");drawImage(g,translucentImage,"半透明");drawImage(g,antiAliasedImage,"半透明(anti-aliased)");//繪制完成之后,通知其它線程顯示完畢,然后讓CPU喚醒這些線程。synchronized(this){notify();}}else{g.drawString("裝載圖片中。。。", 5, FONT_SIZE);}}/*快速顯示圖形的核心代碼。在2.5秒之內快速繪制圖片在屏幕中*/private void drawImage(Graphics g, Image image, String name){int width = screen.getFullScreenWindow().getWidth() - image.getWidth(null);int height = screen.getFullScreenWindow().getHeight() - image.getHeight(null);int numImages = 0;g.drawImage(bgImage,0,0,null);long startTime = currentTimeMillis();//如果有限定的時間長度2.5秒之內while(currentTimeMillis() - startTime < TIME_PER_IMAGE){int x = Math.round((float)Math.random() * width);int y = Math.round((float)Math.random() * height);out.println("當前x = " + x + ", y = " + y);//隨機繪制圖形g.drawImage(image,x,y,null); numImages++;}long time = currentTimeMillis() - startTime;float speed = numImages * 1000f / time;out.println(name + ": " + speed + " 圖片/秒");}public ImageSpeedFrameTest(String[] args){if(args.length == 3){displayMode = new DisplayMode(Integer.parseInt(args[0]),Integer.parseInt(args[1]),Integer.parseInt(args[2]),DisplayMode.REFRESH_RATE_UNKNOWN);}else{displayMode = new DisplayMode(800,600,16,DisplayMode.REFRESH_RATE_UNKNOWN);}//運行run(displayMode);}public static void main(String[] args){new ImageSpeedFrameTest(args);} }

不過以上代碼OpenJDK 15運行失敗了


之前使用JDK 1.4版本運行成功的。


各位可以換成JDK 8來試一下。

動畫

動畫中的圖片可以被看成幀(frame),每一幀在一個確定的時間中顯示,但是在相同的時間內部中幀不需要顯示。比如第一幀可能顯示200毫秒,第二幀顯示75毫秒等。

  • mage–圖片
  • Time–時間
  • millisecond–毫秒

動畫循環

動畫效果是由循環顯示圖片呈現出來的,這種循環就是動畫循環。動畫循環遵守步驟如下:

  • Updates any animations–更新動畫
  • Draws to the screen–繪制到屏幕
  • Optionally sleeps for a short period–確定每個動作的睡覺時間
  • Starts over with step 1–回到第一步循環

我們整個代碼實現的結構圖片如下:


無雙緩存實現動畫

import static java.lang.System.*; import java.awt.*; import javax.swing.*; /**功能:書寫一個測試動畫循環的類作者:技術大黍備注:沒有實現雙緩存技術*/public class AnimationFrameTestOne extends JFrame{private DisplayMode displayMode;private static final long DEMO_TIME = 5000;private SimpleScreenManager screen;private Image bgImage;private Animation animation;public AnimationFrameTestOne(String[] args){if(args.length == 3){displayMode = new DisplayMode(Integer.parseInt(args[0]),Integer.parseInt(args[1]),Integer.parseInt(args[2]),DisplayMode.REFRESH_RATE_UNKNOWN);}else{displayMode = new DisplayMode(800,600,16,DisplayMode.REFRESH_RATE_UNKNOWN);}run(displayMode);}private void run(DisplayMode displayMode){screen = new SimpleScreenManager();try{screen.setFullScreen(displayMode, new JFrame());loadImages();//裝載圖片animationLoop();//實現動畫}finally{screen.restoreScreen();}}private void loadImages(){bgImage = loadImage("images/background.jpg");Image player1 = loadImage("images/player1.png");Image player2 = loadImage("images/player2.png");Image player3 = loadImage("images/player3.png");//創建動畫--一張圖片使用一個ArrayList中的AnimationFrame類,該類//使用Image屬性和long來保存圖片對象,以及圖片顯示結束時間。animation = new Animation();animation.addFrame(player1,250);animation.addFrame(player2,150);animation.addFrame(player1,150);animation.addFrame(player2,150);animation.addFrame(player3,200);animation.addFrame(player2,150);}//讀取圖片private Image loadImage(String fileName){return new ImageIcon(fileName).getImage();}/**功能實現動畫效果:1、更新動畫2、繪制到屏幕3、確定睡覺的時間4、回到第一步循環*/private void animationLoop(){//獲取動畫開始時間long startTime = currentTimeMillis();//把它設置為當前時間long currentTime = startTime;//如果流逝的時間小于指定的時間while(currentTime - startTime < DEMO_TIME){//在指定的總體動畫時間中long elapsedTime = currentTimeMillis() - currentTime;currentTime += elapsedTime;//1. 根據指定的elapsedTime來決定更換顯示顯示的圖片,從而達到更新動畫的效果。animation.update(elapsedTime);//2. 繪制到屏幕Graphics g = screen.getFullScreenWindow().getGraphics();draw(g);//這里是核心代碼,把圖片繪制在屏幕中。g.dispose();//釋放資源//3. 暫停一下try{Thread.sleep(20);}catch(InterruptedException e){e.printStackTrace();}}}/*功能:該方法是核心方法,它主要完成兩個功能:1、繪制背景圖片2、繪制動畫圖片,在指定的(0,0)坐標處理繪制注意:核心方法的呼叫--呼叫Animation對象的getImage方法*/private void draw(Graphics g){//繪制背景g.drawImage(bgImage,0,0,null);//繪制動畫--這是實現動畫的關鍵方法g.drawImage(animation.getImage(),0,0,null);}public static void main(String[] args){new AnimationFrameTestOne(args);} }

雙緩存實現動畫

import static java.lang.System.*; import java.awt.*; import javax.swing.*;/**功能:書寫一個使用雙緩存技術實現的動畫效果作者:技術大黍*/ public class AnimationTestTwo{public AnimationTestTwo(){run();}public static void main(String[] args){new AnimationTestTwo();}private static final DisplayMode POSSIBLE_MODES[] = {new DisplayMode(1280,800,32,0),new DisplayMode(1280,800,24,0),new DisplayMode(1280,800,16,0),new DisplayMode(1024,768,32,0),new DisplayMode(1024,768,24,0),new DisplayMode(1024,768,16,0),new DisplayMode(800,600,32,0),new DisplayMode(800,600,24,0),new DisplayMode(800,600,16,0)};private static final long DEMO_TIME = 10000;private ScreenManager screen;private Image bgImage;private Animation animation;public void loadImages(){//裝載圖片bgImage = loadImage("images/background.jpg");Image player1 = loadImage("images/player1.png");Image player2 = loadImage("images/player2.png");Image player3 = loadImage("images/player3.png");//創建動畫animation = new Animation();animation.addFrame(player1,250);animation.addFrame(player2,150);animation.addFrame(player1,150);animation.addFrame(player2,150);animation.addFrame(player3,200);animation.addFrame(player2,150);/*animation = new Animation();Image player1 = loadImage("images/01.png");Image player2 = loadImage("images/02.png");Image player3 = loadImage("images/03.png");Image player4 = loadImage("images/04.png");Image player5 = loadImage("images/05.png");Image player6 = loadImage("images/06.png");animation.addFrame(player1,150);animation.addFrame(player2,150);animation.addFrame(player3,150);animation.addFrame(player4,150);animation.addFrame(player5,150);animation.addFrame(player6,150);*/}private Image loadImage(String fileName){return new ImageIcon(fileName).getImage();}private void run(){screen = new ScreenManager();try{DisplayMode displayMode = screen.findFirstCompatibleMode(POSSIBLE_MODES);//呼叫screen對象的setFullScreen方法是實現雙緩存技術顯示的關鍵代碼!screen.setFullScreen(displayMode);loadImages();animationLoop();}finally{screen.restoreScreen();}}/*實現雙緩存動畫的核心方法。*/private void animationLoop(){long startTime = currentTimeMillis();long currentTime = startTime;while(currentTime - startTime < DEMO_TIME){long elapsedTime = currentTimeMillis() - currentTime;currentTime += elapsedTime;//更新動畫--確定需要顯示的圖形animation.update(elapsedTime);//繪制和刷新屏幕--繪制圖形在緩存對象中--關鍵是使用Graphics2D類來實現雙緩存顯示!Graphics2D g = screen.getGraphics();//在屏幕中繪制背景和動畫--在屏幕中繪制出現(離屏繪制)draw(g);g.dispose();//然后在緩存中繪制圖形--實現雙緩存的關鍵代碼--繪制到屏幕中screen.update();//停頓一下try{Thread.sleep(20);}catch(InterruptedException ex){ex.printStackTrace();}}}/**注意:g.drawImage(animation.getImage(),0,0,null)方法是在固定的坐標繪制雙緩存的圖形所以只有動畫效果,但是沒有小怪的移動動作。*/public void draw(Graphics g){g.drawImage(bgImage, 0,0, null);//繪制背景g.drawImage(animation.getImage(),620,350,null);//繪制圖片} }

主動呈現

主動呈現(active rendering)是一個實現動畫的術語。我們必須使用一種有效方式來不斷刷新屏幕,從而產生動畫。前面的例子是使用paint方法來呈現,我們呼叫repaint方法,向AWT事件分發線程發送信號,讓它重新刷新屏幕,但是這種做法會產生延遲,因為AWT的線程可能會在忙于別的事情。而解決方法就是使用主動呈現的方式,該方式是在主線程中直接繪制圖片!這樣我們讓屏幕直接繪制,并且代碼簡單:

Graphics g = screen.getFullScreenWindow().getGraphics(); draw(g); //主線程中直接繪制 g.dispose();

去掉閃爍

在AnimationFrameTestOne示例我們看動畫閃爍—很煩!這是因為我們不斷刷新屏幕,這樣的結果是我們擦除背景,然后重新繪制它。這樣的結果會有時候出現閃爍,有時候不出現閃爍現象。這個類似于,我們在屏幕使用筆畫圖,然后用戶會看見怎么畫圖的全過程。


我們使用雙緩存(Double Buffering)技術解決這個問題。所謂buffer就是一個在內存中繪制圖片,也就是當我們使用雙緩存技術時,需要我們不能直接把圖形畫到屏幕中去,需要我們先畫到buffer中,然后把整個畫面拷貝到屏幕中去。這樣整個屏幕只刷新一次。

  • back buffer–備份緩存
  • copy–復制
  • screen–屏幕呈現

Back buffer可以是普通的Java圖形,所以我們使用Component的createImage()方法來創建緩存圖片,然后在update方法中呼叫paint方法呈現:

完整代碼參見ScreenManger類。

ScreenManager類

import java.awt.*; import java.awt.image.*; import javax.swing.*;/**功能:書寫一個屏幕管理器,用來初始化和全屏幕顯示功能作者:技術大黍*/ public class ScreenManager{private GraphicsDevice device; //指定顯卡/**在構造方法中初始化成員變量, 其中GraphicsEnvironment抽象類,它是抽象了平臺上可以使用的GraphicsDevice對象和Font對象的集合。該對象的中的資源可以本地資源,也可以遠程機器上的。GraphicsDevice對象可以屏幕、打印機或圖像緩存,并且都Graphics2D繪圖方法的目標。每個GraphicsDevice有許多與之相關的GraphicsConfiguration對象,這些對象指定使用GraphicsDevice所需的不同配置。*/public ScreenManager(){//初始化顯示設置環境對象GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment();//然后從該環境顯示確定的顯卡device = environment.getDefaultScreenDevice();}/**功能:獲取當前的所有的顯示模型*/public DisplayMode[] getCompatibleDsiplayModes(){return device.getDisplayModes();}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; //否則找不到時返回null}/**功能:比較兩個模型是否相同*/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;}//否則返回truereturn true;}/**功能:輸入全屏幕模型,然后修改顯示模型,如果指定的顯示模型是null或者不兼容,那么使用當前的顯示模型。顯示時使用BufferStratgey的雙緩存技術實現。*/public void setFullScreen(DisplayMode displayMode){JFrame frame = new JFrame();frame.setUndecorated(true);//不顯示標題欄frame.setIgnoreRepaint(true); //能呼叫repain方法frame.setResizable(false); //不允許縮放//呼叫顯卡設置全屏幕device.setFullScreenWindow(frame);//如果有確定的顯示模型if(displayMode != null && device.isDisplayChangeSupported()){//那么設置該顯示模型try{device.setDisplayMode(displayMode);}catch(IllegalArgumentException ex){ex.printStackTrace();}}//使用雙緩存技術顯示全屏幕--這是關鍵代碼。該方法為被呼叫的組件創建一個新的緩沖//策略--雙緩沖策略。該方法中根據提供的緩沖區數來創建可用的最佳策略。它將始終根據//該緩沖區數創建BufferStrategy--首先濃度page-flipping策略,然后是使用加速緩沖區//blitting策略,最后使用不加速的blitting策略。Bit BLIT (which stands for bit-block [image] //transfer but is pronounced bit blit) is a computer graphics operation in which //several bitmaps are combined into one using a raster operator.//必須首先在這里設置device顯示模型為雙緩存方式,然后參見getGraphics()方法中的//BufferStrategy strategy = window.getBufferStrategy()語句在屏幕繪制使用雙緩存//技術顯示frame.createBufferStrategy(2);}/**功能:取得圖形上下文來進行顯示,ScreenManager使用雙緩存實現,所以應用程序必須呼叫update()方法來顯示任何繪制的圖形。然后應用程序必須釋放圖形對象。*/public Graphics2D getGraphics(){Window window = device.getFullScreenWindow(); //從顯卡取得全屏幕的窗體對象//如果該對象不為nullif(window != null){//取得在setFullScreen方法設置的雙緩存設置策略,然后初始化BufferStrategy對象BufferStrategy strategy = window.getBufferStrategy(); //最后把圖形繪制在Graphics2D對象中,參見AnimationTestTwo類中的animationLoop()//方法中的Graphics2D g = screen.getGraphics();語句。return (Graphics2D)strategy.getDrawGraphics();}else{return null; //否則返回null}}/**功能:更新顯示--使用雙緩存策略來顯示。實現雙緩存最關鍵的對象不device成員變量!!*/public void update(){Window window = device.getFullScreenWindow(); //從顯卡取得窗體對象//如果該窗體不為nullif(window != null){//那么使用雙緩存策略對象處理BufferStrategy strategy = window.getBufferStrategy();//如果內容沒有丟失if(!strategy.contentsLost()){//那么在屏幕上顯出來--此處代碼是在屏幕中顯示的核心關鍵代碼!//屏蔽該語句之后,不會在屏幕中顯示出來畫面來--black一片。strategy.show();}}//否則在系統同步顯示出現--下面這句話是fix Linux中的事件階段問題Toolkit.getDefaultToolkit().sync();}/**功能:返回當前全屏幕的當前窗體,如果沒有全屏幕模型,那么返回null*/public Window getFullScreenWindow(){return device.getFullScreenWindow(); //返回全屏幕對象窗體}/**功能:返回當前全屏幕的寬度值。如果沒有不全屏幕,那么回返0*/public int getWidth(){Window window = device.getFullScreenWindow();//從顯卡取得窗體對象//如果該窗體對象不為nullif(window != null){return window.getWidth(); //那么返回該窗體對象的寬度}else{return 0; //否則返回0}}/**功能:返回當前全屏幕的高度值。如果沒有不全屏幕,那么回返0*/public int getHeight(){Window window = device.getFullScreenWindow();//從顯卡取得窗體對象//如果該窗體對象不為nullif(window != null){return window.getHeight(); //那么返回該窗體對象的高度}else{return 0; //否則返回0}}public void restoreScreen(){Window window = device.getFullScreenWindow();//從顯卡取得窗體對象//如果該窗體對象不為nullif(window != null){window.dispose(); //那么翻譯該資源}//然后取消全屏幕顯示device.setFullScreenWindow(null);}public BufferedImage createCompatibleImage(int w, int h, int transparency){Window window = device.getFullScreenWindow();//從顯卡取得窗體對象//如果該窗體不為nullif(window != null){//那么取得圖形的配置對象GraphicsConfiguration configuration = window.getGraphicsConfiguration();//然后返回兼容的圖形return configuration.createCompatibleImage(w,h,transparency);}return null; //否則返回null} }

使用BufferStrategy類(源碼追蹤)

該類是一個抽象類,它用來表示特定的Canvas或者Window上組織復雜的內存機制。硬件與軟件限制了決定是否能夠使用特定的緩存策略,以及它如何實現。從創建Canvas和Window對象所使用GraphicsConfiguration的性能可以發現這些限制的存在。

**注意:**術語buffer與surface的相同:視頻設置內存或系統內存的連續內存區域。在實際開發中,雙緩存、分頁和等待顯示器重新刷新都是使用該類來實現。總之一句話,該類幫助我們完成這些物理上的動作。使用顯示器刷新的缺點是,如果顯示器的刷新頻率是75HZ,也就是每秒75個窗體畫面,那么我們不可能運行每秒200個畫面的游戲了。類Canvas和類Window對象都有BufferStrategy可以使用,并且使用createBufferStrategy()方法創建該策略對象。


我們源代碼追蹤一下:

frame類繼承Window類,而Window類繼承自Container容器類,并且實現Accessible接口


在createBufferStrategy方法中吃了super類的createBufferStrategy方法

我們再進行源代碼追蹤,在Component類的3837行
該方法完整的代碼我們扣出來展示如下:

void createBufferStrategy(int numBuffers) {BufferCapabilities bufferCaps;if (numBuffers > 1) {// Try to create a page-flipping strategybufferCaps = new BufferCapabilities(new ImageCapabilities(true),new ImageCapabilities(true),BufferCapabilities.FlipContents.UNDEFINED);try {createBufferStrategy(numBuffers, bufferCaps);return; // Success} catch (AWTException e) {// Failed}}// Try a blitting (but still accelerated) strategybufferCaps = new BufferCapabilities(new ImageCapabilities(true),new ImageCapabilities(true),null);try {createBufferStrategy(numBuffers, bufferCaps);return; // Success} catch (AWTException e) {// Failed}// Try an unaccelerated blitting strategybufferCaps = new BufferCapabilities(new ImageCapabilities(false),new ImageCapabilities(false),null);try {createBufferStrategy(numBuffers, bufferCaps);return; // Success} catch (AWTException e) {// Code should never reach here (an unaccelerated blitting// strategy should always work)throw new InternalError("Could not create a buffer strategy", e);}}

我們看到Java開源組織書寫了一大堆的封裝類,根據策略模式實現了底層的雙緩存機制,并且提供給我們第三方開發人員快捷的使用–我們只是簡單的使用Frame.createBufferStrategy方法來調用BufferStrategy策略對象就完成了我們想的實現雙緩存效果,從而加快了開發效率。

創建屏幕管理器

下面我們改進SimpleScreenManager類,新增一些功能:

  • 使用BufferStrategy來實現雙緩存技術和分頁刷新
  • 使用getGraphics()方法獲取顯示的圖片上下文
  • 使用update()來更新顯示
  • 使用getCompatibleDisplayModes()來獲取可兼容的顯示模型
  • 使用getCurrentDisplayMode()來獲取當前的顯示模型
  • 使用findFirstCompatibleMode()方法來獲取模型列表中的第一個顯示模型
  • 如果不需要主動呈現,那么沒有必須給JFrame使用全屏幕顯示,這時需要我們關閉它 frame.ignoreRepaint(true); 但是,它不會關閉repaint事件,所以可以使用paint事件。

    下面我們列出AnimationTestTwo類,以幫助我們理解

    AnimationTestTwo類

    該類與ScreenManger類一起配合使用

    import static java.lang.System.*; import java.awt.*; import javax.swing.*;/**功能:書寫一個使用雙緩存技術實現的動畫效果作者:技術大黍*/ public class AnimationTestTwo{public AnimationTestTwo(){run();}public static void main(String[] args){new AnimationTestTwo();}private static final DisplayMode POSSIBLE_MODES[] = {new DisplayMode(1280,800,32,0),new DisplayMode(1280,800,24,0),new DisplayMode(1280,800,16,0),new DisplayMode(1024,768,32,0),new DisplayMode(1024,768,24,0),new DisplayMode(1024,768,16,0),new DisplayMode(800,600,32,0),new DisplayMode(800,600,24,0),new DisplayMode(800,600,16,0)};private static final long DEMO_TIME = 10000;private ScreenManager screen;private Image bgImage;private Animation animation;public void loadImages(){//裝載圖片bgImage = loadImage("images/background.jpg");Image player1 = loadImage("images/player1.png");Image player2 = loadImage("images/player2.png");Image player3 = loadImage("images/player3.png");//創建動畫animation = new Animation();animation.addFrame(player1,250);animation.addFrame(player2,150);animation.addFrame(player1,150);animation.addFrame(player2,150);animation.addFrame(player3,200);animation.addFrame(player2,150);/*animation = new Animation();Image player1 = loadImage("images/01.png");Image player2 = loadImage("images/02.png");Image player3 = loadImage("images/03.png");Image player4 = loadImage("images/04.png");Image player5 = loadImage("images/05.png");Image player6 = loadImage("images/06.png");animation.addFrame(player1,150);animation.addFrame(player2,150);animation.addFrame(player3,150);animation.addFrame(player4,150);animation.addFrame(player5,150);animation.addFrame(player6,150);*/}private Image loadImage(String fileName){return new ImageIcon(fileName).getImage();}private void run(){screen = new ScreenManager();try{DisplayMode displayMode = screen.findFirstCompatibleMode(POSSIBLE_MODES);//呼叫screen對象的setFullScreen方法是實現雙緩存技術顯示的關鍵代碼!screen.setFullScreen(displayMode);loadImages();animationLoop();}finally{screen.restoreScreen();}}/*實現雙緩存動畫的核心方法。*/private void animationLoop(){long startTime = currentTimeMillis();long currentTime = startTime;while(currentTime - startTime < DEMO_TIME){long elapsedTime = currentTimeMillis() - currentTime;currentTime += elapsedTime;//更新動畫--確定需要顯示的圖形animation.update(elapsedTime);//繪制和刷新屏幕--繪制圖形在緩存對象中--關鍵是使用Graphics2D類來實現雙緩存顯示!Graphics2D g = screen.getGraphics();//在屏幕中繪制背景和動畫--在屏幕中繪制出現(離屏繪制)draw(g);g.dispose();//然后在緩存中繪制圖形--實現雙緩存的關鍵代碼--繪制到屏幕中screen.update();//停頓一下try{Thread.sleep(20);}catch(InterruptedException ex){ex.printStackTrace();}}}/**注意:g.drawImage(animation.getImage(),0,0,null)方法是在固定的坐標繪制雙緩存的圖形所以只有動畫效果,但是沒有小怪的移動動作。*/public void draw(Graphics g){g.drawImage(bgImage, 0,0, null);//繪制背景g.drawImage(animation.getImage(),620,350,null);//繪制圖片} }

    屏幕管理效果


    妖怪(sprite)

    現在使用雙緩存技術畫面運行流暢,但是沒有其它的動畫出現,所以我們需要需要創建一個妖怪在屏幕中運動。因為妖怪也是一個圖片,只不過它是獨立在屏幕中的,所以該妖怪也是一個動畫效果,并且它可以一邊動畫一邊移動。

    現在,因為動畫問題已經解決,那么我們需要解決妖怪移動的問題—也就是一個怪由兩個事情組成:位置和速率(velocity)。速率是速度和方向的組成,這樣我們把速度分成水平和垂直兩個方向,我們是每秒多少像素來計算移動速度。可能我們會問:“為什么不通過更新多個frame中的怪物的位置來實現動畫,而非得使用速率?”如果,這樣做,那么這個怪物在不同的機器上移動的速度就會不一樣!性能好的機器上的怪物運行比較快,而性能慢的機器上的怪物運行比較慢。而怪物的動畫我們使用主動呈現的技術來實現。

    注意,我們把妖怪的位置值使用浮點來計算,而不是整數,這是因為如果使用整數,那么每隔10毫秒更新時,有一毫秒的時間圖片不會移動。該類非常簡單,只有getter和setter方法,以及update方法更新動畫與移動位置。

    Sprite類

    import static java.lang.System.*; import java.awt.Image;/**功能:書寫一個妖怪類用來演示游戲中人物角色的動畫實現方式作者:技術大黍備注:妖怪由兩個部分組成:動畫和移動效果*/ public class Sprite{//使用動畫管理器來管理妖怪的動畫效果--用Animation來封裝妖怪的圖形的樣子。private Animation animation;//設置妖怪的顯示位置--實現妖怪的移動需要兩種元素:當前妖怪的位置和它的移動速率。private float x;private float y;//計算妖怪的速率private float dx;private float dy;public Sprite(Animation animation){this.animation = animation;}/**功能:更新該妖怪的動畫和位置,更新時根據它的速率來計算。該方法是妖怪移動的核心方法*/public void update(long elapsedTime){//根據流失的時間值重新計算妖怪圖片的x和y的值x += dx * elapsedTime;y += dy * elapsedTime;//根據流失的時間值來控制動畫效果animation.update(elapsedTime);}public float getX(){return x;}public float getY(){return y;}public void setX(float x){this.x = x;}public void setY(float y){this.y = y;}/**獲取該圖片的寬度*/public float getWidth(){return animation.getImage().getWidth(null);}/**獲取該圖片的高度*/public float getHeight(){return animation.getImage().getHeight(null);}public float getVelocityX(){return dx;}public float getVelocityY(){return dy;}public void setVelocityX(float dx){this.dx = dx;}public void setVelocityY(float dy){this.dy = dy;}public Image getImage(){return animation.getImage();} }

    SprinteTest類

    import static java.lang.System.*; import java.awt.*; import javax.swing.ImageIcon; /**功能:書寫一個測試類,用來測試妖怪的動畫效果作者:技術大黍*/public class SpriteTest{private ScreenManager screen;private Image bgImage;private Sprite sprite;private static final long DEMO_TIME = 20000;private Animation animation;private static final DisplayMode POSSIBLE_MODES[] = {new DisplayMode(1280,800,32,0),new DisplayMode(1280,800,24,0),new DisplayMode(1280,800,16,0),new DisplayMode(1024,768,32,0),new DisplayMode(1024,768,24,0),new DisplayMode(1024,768,16,0),new DisplayMode(800,600,32,0),new DisplayMode(800,600,24,0),new DisplayMode(800,600,16,0)};public SpriteTest(){run();}/**這里妖怪移動的核心實現方法*/public void loadImages(){//裝載圖片bgImage = loadImage("images/background.jpg");Image player1 = loadImage("images/player1.png");Image player2 = loadImage("images/player2.png");Image player3 = loadImage("images/player3.png");//1.創建動畫--裝載背景畫面animation = new Animation();animation.addFrame(player1,250);//其中250表示該圖片的顯示結束時間animation.addFrame(player2,150);animation.addFrame(player1,150);animation.addFrame(player2,150);animation.addFrame(player3,200);animation.addFrame(player2,150);//2.裝載妖怪圖片sprite = new Sprite(animation);//3.開始移動妖怪sprite.setVelocityX(0.3f);sprite.setVelocityY(0.3f);}private Image loadImage(String fileName){return new ImageIcon(fileName).getImage();}private void run(){screen = new ScreenManager();try{DisplayMode displayMode = screen.findFirstCompatibleMode(POSSIBLE_MODES);screen.setFullScreen(displayMode);loadImages();animationLoop();}finally{screen.restoreScreen();}}/*實現動畫最關鍵的如何獲取當前時間與肇始啟始時間的差值(elapsed time),然后把這個賦給需要被實現動畫的對象去計算移動的位置。*/private void animationLoop(){long startTime = currentTimeMillis();long currentTime = startTime;while(currentTime - startTime < DEMO_TIME){//計算當前時間與啟始時間之間差值long elpasedTime = currentTimeMillis() - currentTime;//根據流失的時間值更新當前時間currentTime += elpasedTime;//更新怪物--根據流失時間來移動妖怪update(elpasedTime);//1.第一個步驟計算小怪的當前坐標值//在屏幕中繪制妖怪Graphics2D g = screen.getGraphics();draw(g); //2. 根據當前小怪的坐標值來實現小怪移動(動態繪制在內存中)g.dispose();//然后刷新屏幕畫面screen.update();//3. 使用雙緩存計算來顯示圖形(最后一次性顯示在屏幕中)try{Thread.sleep(20);}catch(InterruptedException e){e.printStackTrace();}}}/*實現動畫與移動的核心方法,計算當前小怪的坐標值*/private void update(long elapsedTime){if(sprite.getX() < 0){//如果妖怪的x坐標小于0sprite.setVelocityX(Math.abs(sprite.getVelocityX()));//那么讓速率}else if(sprite.getX() + sprite.getWidth() >= screen.getWidth()){sprite.setVelocityX(-Math.abs(sprite.getVelocityX()));}if(sprite.getY() < 0){sprite.setVelocityY(Math.abs(sprite.getVelocityY()));}else if(sprite.getY() + sprite.getHeight() >= screen.getHeight()){sprite.setVelocityY(-Math.abs(sprite.getVelocityY()));}//更新妖怪--更新妖怪圖片的坐標值sprite.update(elapsedTime);}/**注意:g.drawImage(sprite.getImage(),Math.round(sprite.getX()),Math.round(sprite.getY()),null);方法,它是讓小怪移動的關鍵方法。這里與AnimationTestTwo類的publi void draw()方法的主要區別。*/public void draw(Graphics g){g.drawImage(bgImage,0,0,null);g.drawImage(sprite.getImage(),Math.round(sprite.getX()),Math.round(sprite.getY()),null);}public static void main(String[] args){new SpriteTest();} }

    運行效果

    在VS Code的運行效果如下:

    簡單特效—圖片變換

    我們可以使用sprite類來創建不同的小妖,只需要以下幾段代碼變可以實現:

    for(int i = 0; i < sprites.length; i++){sprites[i].update(elapsedTime);g.drawImage(sprites[i].getImage(),Math.round(sprite[i].getX()),Math.round(sprite[i].getY()),null); }

    **注意:**每個sprite只更新自己的動畫效果,所以sprites不可能共享相同的Animation對象;而動畫對象可以多次被更新。如果我們需要相同的動畫對象,那么可以使用clone方法來復制后得到。

    比較cool的圖片特效是圖片的旋轉和縮放,這種效果我們叫做圖片的轉換(image transform)。圖片轉換可以讓我們旋轉、上下翻轉、縮放圖片,甚至飛行圖片。在Java中有AffineTransform類可以描述圖片的轉換,該類提供了rotate(), scale()和translate()方法來實現圖片的置換。Graphics2D對象是圖片實際轉換的地方,該類使用drawImage方法帶一個AffineTransform對象可以實現圖片的轉換。比如

    AffineTransform transform = new AffineTransform();transform.scale(2,2);transform.translate(100,100);g.drawImage(image, transform, null);

    下面我們演示圖片的轉換效果。


    總結
    我們到書寫可以重用的類:ScreenManager, Animation和Sprite類。

    這些類不可能是最好的類—實現動畫的基類,對于我們設計的游戲來說,應該根據自己的觀點來設計工具類,不一定按照以上三類的限制來書寫代碼。

    Java的標準API把復雜的底層渲染呈現機制給深度封裝了,我們第三方的Java程序員只需要讀懂封裝的過程和代碼就行了,然后就是愉快使用它們來幫助我們快速開發游戲。

    圖片來源:http://www.hp91.cn/ h5游戲

    總結

    以上是生活随笔為你收集整理的Java游戏编程不完全详解-2的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 午夜精品久久99蜜桃的功能介绍 | 男生操女生在线观看 | 黄网在线观看视频 | 无码精品人妻一区二区三区湄公河 | 无码人妻精品一区二区三区99v | 成全世界免费高清观看 | 最好看的2019年中文视频 | 国产在线精品播放 | 国产成人精品一区二区三区免费 | 老妇裸体性猛交视频 | 精品国产影院 | 日韩精品视频网站 | av免播放器 | 免费看黄在线看 | 色先锋在线| 成人aaa| 猫咪av在线 | 在线视频日韩欧美 | 中文字幕观看在线 | 精品久久无码中文字幕 | 99自拍 | 小镇姑娘国语版在线观看免费 | 欧美成人乱码一区二区三区 | www国产视频| 国产乱淫a∨片免费视频 | 免费看欧美黑人毛片 | 日韩一区网站 | 黄片毛片在线观看 | www国产亚洲精品久久网站 | 91爱爱爱| 国产又色又爽又黄的 | 国产综合视频在线 | 亚洲天堂影院在线观看 | 亚洲视频123 | 毛片a | 精品无码在线观看 | 黄色录像大片 | 日韩欧美一卡 | 日本3级网站 | 成人做爰www看视频软件 | 欧美亚洲一区二区三区四区 | 亚洲无色 | 男人的天堂avav | 俺也去网站 | 国产性av| 日韩色综合 | 成年人的黄色片 | 爱情岛论坛成人av | 小小姑娘电影大全免费播放 | 北条麻妃av在线播放 | 国产美女网| 99热99精品| 国产午夜福利精品 | 久久久久综合网 | 午夜久久精品 | 1024手机在线看片 | 免费处女在线破视频 | 国产精品午夜电影 | 亚洲欧美强伦一区二区 | 国产精品色视频 | 毛片久久久久久 | 成人网在线免费观看 | a v视频在线播放 | 国产亚洲欧美在线视频 | 成人a毛片久久免费播放 | 67194在线免费观看 | 特级黄色片 | 久久图库 | 欧美日韩一区二区视频观看 | 一级片中文字幕 | 污污网站在线免费观看 | 手机在线看永久av片免费 | 成人av片免费看 | 午夜在线观看视频网站 | 午夜一区二区三区免费观看 | 国产第56页 | 国产91九色 | 日本在线精品视频 | 日本三级影院 | 亚州欧美日韩 | 欧美性猛交乱大交3 | 日韩精品卡通动漫网站 | 国产精品电影一区二区 | 精品一区二区三区电影 | 黑人精品一区二区三区不 | 天使色吧| 日本在线观看一区二区 | 久久精品国产精品亚洲色婷婷 | www婷婷| 蜜桃视频导航 | 欧美精品123 | 精品一区在线视频 | 长河落日电视连续剧免费观看01 | 国产又粗又大又黄 | 免费a级网站 | 青青超碰 | 久久精品无码一区二区三区免费 | 日本黄xxxxxxxxx100| 天天摸天天插 |