javafx_JavaFX 2 GameTutorial第2部分
javafx
介紹?他的是一系列與一個(gè)JavaFX 2游戲教程博客條目的第二批。 如果您尚未閱讀第1部分,請(qǐng)參閱JavaFX 2游戲教程的簡(jiǎn)介部分。 在第1部分中,我提到了游戲的一些方面以及原型飛船的簡(jiǎn)單演示(原型由簡(jiǎn)單的形狀組成),它可以通過(guò)鼠標(biāo)進(jìn)行導(dǎo)航。 免責(zé)聲明 :這是一個(gè)很長(zhǎng)的教程,因此如果您只想運(yùn)行演示,請(qǐng)單擊“ 此處” 。 該演示稱為Atom Smasher,您可以在其中生成碰撞的原子(球體)。 您可以凍結(jié)游戲以添加更多原子。 目的是使一個(gè)以上的原子活著并彈跳。 文本顯示當(dāng)前漂浮的原子數(shù)。 在開(kāi)始討論游戲循環(huán)之前,我想向您介紹一些有關(guān)游戲和動(dòng)畫的背景歷史。
歷史
早在20世紀(jì)80年代至90年代期間,許多嘗試制作圖像動(dòng)畫的游戲程序員都曾遇到過(guò)臭名昭著的屏幕閃爍問(wèn)題。 這是您的精靈 (圖形圖像)經(jīng)常閃爍的地方,并使游戲看起來(lái)很糟糕。 所有監(jiān)視器的刷新率均以一定間隔重繪像素(稱為垂直回描CRT )。 例如,如果刷新率是80 Hz,則刷新率大約是每秒80次,屏幕將重新繪制。 如果要在屏幕上進(jìn)行修改,則由于處于刷新間隔的中間,通常可能會(huì)不同步。 您應(yīng)該怎么做? 好吧,實(shí)際上有兩件事可以幫助解決這個(gè)問(wèn)題(雙緩沖和知道周期何時(shí)發(fā)生)。 一些聰明的開(kāi)發(fā)人員創(chuàng)建了一種稱為雙緩沖的技術(shù)。 雙重緩沖是一種由兩個(gè)表面組成的技術(shù),其中兩個(gè)表面依次輪流變?yōu)榭娠@示表面,另一個(gè)表面外區(qū)域(緩沖表面)。 此技術(shù)實(shí)際上是一種數(shù)字技巧,開(kāi)發(fā)人員可以在其中預(yù)先計(jì)算要在屏幕外表面繪制的精靈及其位置。 一旦在屏幕外緩沖區(qū)上完成繪制,代碼便會(huì)將其切換為可顯示表面。 需要指出的重要一點(diǎn)是,由于在刷新間隔即將開(kāi)始重繪過(guò)程時(shí)需要通知我們,因此仍然存在問(wèn)題。 在Java中,此功能是通過(guò)BufferStrategy API內(nèi)置的。 那么,我要去哪里呢? 有時(shí)解釋過(guò)去的策略將有助于我們欣賞今天的狀況。 我們需要在JavaFX中執(zhí)行此操作嗎? 不。 不用擔(dān)心JavaFX在這里! 我已經(jīng)提到的所有問(wèn)題都通過(guò)使用JavaFX的場(chǎng)景圖API來(lái)解決。 但是,大多數(shù)游戲仍會(huì)使用舊的方式來(lái)制作圖形動(dòng)畫和更新游戲世界,稱為“ 游戲循環(huán)” 。
游戲循環(huán)
簡(jiǎn)單地說(shuō),游戲循環(huán)負(fù)責(zé)更新子畫面(圖形),檢查碰撞和清理。 較早的游戲循環(huán)將在循環(huán)中檢查按鍵和鼠標(biāo)事件。 由于JavaFX對(duì)事件進(jìn)行抽象以允許Scene或單個(gè)節(jié)點(diǎn)處理事件,因此在我們的游戲循環(huán)中不必監(jiān)聽(tīng)低級(jí)事件。 下面顯示的是一個(gè)典型游戲循環(huán)的源代碼片段,它將在每個(gè)周期更新精靈,檢查碰撞和清理精靈。 您會(huì)注意到JavaFX 2.x中的Duration對(duì)象,該對(duì)象表示60除以1000毫秒或每秒60幀(FPS)。 每個(gè)幀都會(huì)調(diào)用JavaFX的EventHandler接口的handle()方法,以更新游戲世界。 假設(shè)地,我創(chuàng)建了三個(gè)方法updateSprites() , checkCollisions()和cleanupSprites() ,它們將被調(diào)用以處理游戲中的精靈。
final Duration oneFrameAmt = Duration.millis(1000/60);final KeyFrame oneFrame = new KeyFrame(oneFrameAmt,new EventHandler() {@Overridepublic void handle(javafx.event.ActionEvent event) {// update actorsupdateSprites();// check for collisioncheckCollisions();// removed dead thingscleanupSprites();}}); // oneFrame// sets the game world's game loop (Timeline)TimelineBuilder.create().cycleCount(Animation.INDEFINITE).keyFrames(oneFrame).build().play();上面的代碼片段實(shí)際上是創(chuàng)建簡(jiǎn)單游戲或動(dòng)畫所需的全部。 但是,您可能希望將事情帶到一個(gè)新的高度。 您可能想要?jiǎng)?chuàng)建一個(gè)可以管理精靈和游戲世界狀態(tài)的游戲引擎。
游戲引擎
游戲引擎是負(fù)責(zé)封裝游戲世界,運(yùn)行游戲循環(huán),管理精靈,物理等的實(shí)用程序或庫(kù)的奇特名稱。這實(shí)際上是一個(gè)小型游戲框架,允許您擴(kuò)展或重用,因此您無(wú)需從頭開(kāi)始創(chuàng)建2D游戲時(shí)必須重新發(fā)明輪子。 為了快速前進(jìn),我創(chuàng)建了游戲引擎設(shè)計(jì)的UML類圖。
下面顯示的是圖1 JavaFX Game Engine類圖。
| 圖1. JavaFX 2游戲引擎設(shè)計(jì) |
在圖1 JavaFX 2游戲引擎設(shè)計(jì)中,您會(huì)注意到GameWorld , SpriteManager和Sprite這三個(gè)類。 GameWorld類負(fù)責(zé)初始化游戲狀態(tài),執(zhí)行游戲循環(huán),更新精靈,處理精靈碰撞以及清理。 接下來(lái)是SpriteManager類,該類負(fù)責(zé)通過(guò)添加,刪除以及其他內(nèi)部沖突管理來(lái)管理Sprite。 最后是Sprite類,該類負(fù)責(zé)維護(hù)圖像(演員)的狀態(tài)。 在2D世界中,子畫面可以包含對(duì)象的速度,旋轉(zhuǎn),場(chǎng)景節(jié)點(diǎn)或最終在每個(gè)周期(關(guān)鍵幀/秒)渲染的圖像。
請(qǐng)快速提醒一下UML表示法:
- 加號(hào)“ + ”表示班級(jí)成員是公開(kāi)的。
- 減號(hào)“ – ”表示班級(jí)成員是私人的
- 哈希符號(hào)“ # ”表示類成員受到保護(hù)。
游戲世界
下面是GameWorld類的源代碼實(shí)現(xiàn)。 單擊以展開(kāi)。 稍后,您將看到一個(gè)類圖,描述了將擴(kuò)展GameWorld類的簡(jiǎn)單演示游戲(請(qǐng)參閱AtomSmasher)。
package carlfx.gameengine;import javafx.animation.Animation; import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.animation.TimelineBuilder; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.scene.Group; import javafx.scene.Scene; import javafx.stage.Stage; import javafx.util.Duration;/*** This application demonstrates a JavaFX 2.x Game Loop.* Shown below are the methods which comprise of the fundamentals to a* simple game loop in JavaFX: ** <strong>initialize()</strong> - Initialize the game world.* <strong>beginGameLoop()</strong> - Creates a JavaFX Timeline object containing the game life cycle.* <strong>updateSprites()</strong> - Updates the sprite objects each period (per frame)* <strong>checkCollisions()</strong> - Method will determine objects that collide with each other.* <strong>cleanupSprites()</strong> - Any sprite objects needing to be removed from play.** @author cdea*/ public abstract class GameWorld {/** The JavaFX Scene as the game surface */private Scene gameSurface;/** All nodes to be displayed in the game window. */private Group sceneNodes;/** The game loop using JavaFX's <code>Timeline</code> API.*/private static Timeline gameLoop;/** Number of frames per second. */private final int framesPerSecond;/** Title in the application window.*/private final String windowTitle;/*** The sprite manager.*/private final SpriteManager spriteManager = new SpriteManager();/*** Constructor that is called by the derived class. This will* set the frames per second, title, and setup the game loop.* @param fps - Frames per second.* @param title - Title of the application window.*/public GameWorld(final int fps, final String title) {framesPerSecond = fps;windowTitle = title;// create and set timeline for the game loopbuildAndSetGameLoop();}/*** Builds and sets the game loop ready to be started.*/protected final void buildAndSetGameLoop() {final Duration oneFrameAmt = Duration.millis(1000/getFramesPerSecond());final KeyFrame oneFrame = new KeyFrame(oneFrameAmt,new EventHandler() {@Overridepublic void handle(javafx.event.ActionEvent event) {// update actorsupdateSprites();// check for collisioncheckCollisions();// removed dead thingscleanupSprites();}}); // oneFrame// sets the game world's game loop (Timeline)setGameLoop(TimelineBuilder.create().cycleCount(Animation.INDEFINITE).keyFrames(oneFrame).build());}/*** Initialize the game world by update the JavaFX Stage.* @param primaryStage*/public abstract void initialize(final Stage primaryStage);/**Kicks off (plays) the Timeline objects containing one key frame* that simply runs indefinitely with each frame invoking a method* to update sprite objects, check for collisions, and cleanup sprite* objects.**/public void beginGameLoop() {getGameLoop().play();}/*** Updates each game sprite in the game world. This method will* loop through each sprite and passing it to the handleUpdate()* method. The derived class should override handleUpdate() method.**/protected void updateSprites() {for (Sprite sprite:spriteManager.getAllSprites()){handleUpdate(sprite);}}/** Updates the sprite object's information to position on the game surface.* @param sprite - The sprite to update.*/protected void handleUpdate(Sprite sprite) {}/*** Checks each game sprite in the game world to determine a collision* occurred. The method will loop through each sprite and* passing it to the handleCollision()* method. The derived class should override handleCollision() method.**/protected void checkCollisions() {// check other sprite's collisionsspriteManager.resetCollisionsToCheck();// check each sprite against other sprite objects.for (Sprite spriteA:spriteManager.getCollisionsToCheck()){for (Sprite spriteB:spriteManager.getAllSprites()){if (handleCollision(spriteA, spriteB)) {// The break helps optimize the collisions// The break statement means one object only hits another// object as opposed to one hitting many objects.// To be more accurate comment out the break statement.break;}}}}/*** When two objects collide this method can handle the passed in sprite* objects. By default it returns false, meaning the objects do not* collide.* @param spriteA - called from checkCollision() method to be compared.* @param spriteB - called from checkCollision() method to be compared.* @return boolean True if the objects collided, otherwise false.*/protected boolean handleCollision(Sprite spriteA, Sprite spriteB) {return false;}/*** Sprites to be cleaned up.*/protected void cleanupSprites() {spriteManager.cleanupSprites();}/*** Returns the frames per second.* @return int The frames per second.*/protected int getFramesPerSecond() {return framesPerSecond;}/*** Returns the game's window title.* @return String The game's window title.*/public String getWindowTitle() {return windowTitle;}/*** The game loop (Timeline) which is used to update, check collisions, and* cleanup sprite objects at every interval (fps).* @return Timeline An animation running indefinitely representing the game* loop.*/protected static Timeline getGameLoop() {return gameLoop;}/*** The sets the current game loop for this game world.* @param gameLoop Timeline object of an animation running indefinitely* representing the game loop.*/protected static void setGameLoop(Timeline gameLoop) {GameWorld.gameLoop = gameLoop;}/*** Returns the sprite manager containing the sprite objects to* manipulate in the game.* @return SpriteManager The sprite manager.*/protected SpriteManager getSpriteManager() {return spriteManager;}/*** Returns the JavaFX Scene. This is called the game surface to* allow the developer to add JavaFX Node objects onto the Scene.* @return*/public Scene getGameSurface() {return gameSurface;}/*** Sets the JavaFX Scene. This is called the game surface to* allow the developer to add JavaFX Node objects onto the Scene.* @param gameSurface The main game surface (JavaFX Scene).*/protected void setGameSurface(Scene gameSurface) {this.gameSurface = gameSurface;}/*** All JavaFX nodes which are rendered onto the game surface(Scene) is* a JavaFX Group object.* @return Group The root containing many child nodes to be displayed into* the Scene area.*/public Group getSceneNodes() {return sceneNodes;}/*** Sets the JavaFX Group that will hold all JavaFX nodes which are rendered* onto the game surface(Scene) is a JavaFX Group object.* @param sceneNodes The root container having many children nodes* to be displayed into the Scene area.*/protected void setSceneNodes(Group sceneNodes) {this.sceneNodes = sceneNodes;}}精靈管理器
Sprite Manager類是幫助程序類,可幫助游戲循環(huán)跟蹤Sprite。 通常,一個(gè)精靈管理器將包含所有精靈,每個(gè)精靈都包含一個(gè)JavaFX節(jié)點(diǎn),該節(jié)點(diǎn)顯示在“場(chǎng)景”圖上。
下面顯示的是源代碼。 單擊以展開(kāi)。
雪碧
Sprite類表示要顯示在JavaFX Scene圖形上的圖像或節(jié)點(diǎn)。 在2D游戲中,子畫面將包含其他信息,例如當(dāng)對(duì)象在場(chǎng)景區(qū)域中移動(dòng)時(shí)對(duì)象的速度。 游戲循環(huán)將在關(guān)鍵幀的每個(gè)時(shí)間間隔調(diào)用update()和collide()方法。
下面顯示的是源代碼。 單擊以展開(kāi)。
JavaFX 2游戲循環(huán)演示– Atom Smasher
ew! 如果您已經(jīng)走了這么遠(yuǎn),那么您就是一個(gè)勇敢的靈魂。 讓我們休息一下,嘗試使用上面的游戲引擎創(chuàng)建的演示。
下面顯示的是一個(gè)Java Webstart按鈕,用于啟動(dòng)游戲演示。 稍后,您將看到設(shè)計(jì)和源代碼,詳細(xì)說(shuō)明了它是如何創(chuàng)建的。
要求:
- Java 7或更高版本
- JavaFX 2.0.2 2.1或更高版本
- Windows XP或更高版本(應(yīng)該很快可用于Linux / MacOS)
| 演示版 |
GameLoopPart2設(shè)計(jì)
下面是名為Atom Smasher的游戲演示的類圖,該類使用前面提到的游戲引擎框架。
下面顯示的是圖2 Atom Smasher類圖。
| 圖2. Atom Smasher類圖 |
GameLoopPart2
GameLoopPart2是運(yùn)行游戲的驅(qū)動(dòng)程序或主要JavaFX應(yīng)用程序。 這將創(chuàng)建一個(gè)要初始化的GameWorld對(duì)象,并開(kāi)始游戲循環(huán)。
下面顯示的是源代碼。 單擊以展開(kāi)。
原子粉碎機(jī)
AtomSmasher是GameWorld類的派生類。 它創(chuàng)建許多以隨機(jī)速度,顏色和位置進(jìn)行動(dòng)畫處理的球。 按鈕控件使用戶可以生成更多的“原子”(JavaFX Circle節(jié)點(diǎn))。 當(dāng)每個(gè)原子相互碰撞時(shí),它們將調(diào)用implode()方法,該方法生成淡入淡出過(guò)渡動(dòng)畫。 您將注意到,僅通過(guò)實(shí)現(xiàn)initialize(),handleUpdate(),handleCollision()和cleanupSprites()方法即可輕松實(shí)現(xiàn)此游戲。 一旦實(shí)施,游戲引擎就會(huì)完成其余的工作。 initialize()方法為用戶創(chuàng)建按鈕控件。 要更新精靈的位置或更改游戲狀態(tài),您將實(shí)現(xiàn)handleUpdate()方法。 要比較所有精靈相互碰撞的情況,您將實(shí)現(xiàn)handleCollision() 。 游戲循環(huán)生命周期的最后一部分是清理精靈。 清理意味著更新精靈管理器并更新JavaFX Scene(刪除節(jié)點(diǎn))。
下面顯示的是源代碼。 單擊以展開(kāi)。
原子
Atom類是Sprite類的擴(kuò)展。 原子是一個(gè)精靈,看起來(lái)像一個(gè)在場(chǎng)景中移動(dòng)的球形物體。 原子將具有隨機(jī)的半徑,顏色和速度。 當(dāng)每個(gè)原子精靈與另一個(gè)原子碰撞時(shí),它們將為淡入淡出過(guò)渡設(shè)置動(dòng)畫(implode()方法)。
下面顯示的是源代碼。 單擊以展開(kāi)。
結(jié)論
希望您有機(jī)會(huì)了解游戲循環(huán)的基礎(chǔ),并在以后通過(guò)實(shí)施強(qiáng)大的游戲引擎來(lái)應(yīng)用這些知識(shí)。 雖然,我簡(jiǎn)要提到了碰撞,但是我將其保存在這些教程的第4部分中。 請(qǐng)繼續(xù)關(guān)注第3部分,我們將在此使用鍵盤或鼠標(biāo)進(jìn)行輸入。 隨時(shí)嘗試。 讓我知道你的想法。
要獲取源代碼,請(qǐng)使用瀏覽器中的“ 將鏈接另存為 ”選項(xiàng), 將鏈接下載到以下jar文件中。 如果您使用的是Windows系統(tǒng),則可以將擴(kuò)展名從jar更改為zip以便輕松擴(kuò)展。 它將包含帶有源代碼的目錄“ src ”。
源代碼位置 :
http://www.jroller.com/carldea/resource/javafx2.0_games/part2source_code.jar
源代碼的發(fā)布版本位于GitHub上,名為( JFXGen ),供您克隆并復(fù)制到您的心中內(nèi)容(可以在其中使用自己的項(xiàng)目)。 請(qǐng)享用。
https://github.com/carldea/JFXGen
git clonegit@github.com:carldea / JFXGen.git
參考:來(lái)自我們的JCG合作伙伴 Carl Dea的JavaFX 2 GameTutorial第2部分 ,位于Carl's FX Blog博客上。
翻譯自: https://www.javacodegeeks.com/2012/05/javafx-2-gametutorial-part-2.html
javafx
總結(jié)
以上是生活随笔為你收集整理的javafx_JavaFX 2 GameTutorial第2部分的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 科技一周大事(9 月 4 日-10 日)
- 下一篇: Devoxx的Red Hat Engin