?他是與一個六個部分組成的系列的第3部分的JavaFX 2游戲教程。 如果您錯過了第1部分和第2部分 ,建議您在開始本教程之前先進行閱讀。 回顧第二部分,我討論了游戲循環的內部工作原理,其中我們使用動畫(JavaFX Timeline )更新精靈,檢查碰撞并清理游戲世界元素,然后我不得不創建一個簡單的游戲引擎以簡化操作開發2D游戲。 本教程是關于使用游戲引擎并使用鼠標和鍵盤來演示輸入的。在本教程中,我將為您提供一些背景歷史記錄,事件處理基礎知識,演示游戲以及最后的實現。 該演示將展示一艘能夠在漂浮的球體上射擊的飛船,類似于電子游戲“ 小行星” 。 如果要運行演示,請向下滾動并單擊下面的WebStart按鈕。 在啟動游戲之前,請先閱讀要求。

歷史
在上世紀80年代那年小時候,這里有拱廊中心,保齡球館,披薩店和7家11家商店,我花了大量時間在玻璃展示區擺放著四分之一的空間,以便與玻璃陳列室相鄰。目前正在玩激烈的電子游戲的家伙。 當每個人都擁擠在他身邊時,看著他擊敗了所有的最高分,當我們看到偉大時,我們所有人都為之歡呼。 其中的一個令人難以置信的真棒街機游戲是“ 小行星 ”由Atari公司創建(打參觀play.vg )
說到高分,并不是很多人都知道,但是斯科特·賽峰 ( Scott Safran) (1967年2月3日至1989年3月27日)是有史以來玩小行星的最高記錄。 他在當地的7-11便利店里連續玩了大約二十個小時才做到了這一點。 在生命的后期(還很小的時候),他在1989年3月27日的一次不幸事故中去世。為了紀念Scott,我創建了本教程。 我希望人們會記得他是有史以來最偉大的視頻游戲玩家之一(我敢肯定,他也是個好兄弟)。
關于游戲,小行星基于矢量的硬件用于渲染形狀,而不是柵格圖形(位圖)。 另外,使用光柵圖形創建了Midway Inc.的Space Invaders。 令人興奮的是,有關于JavaFX 2.x能夠使用稱為JavaFX Canvas Node的位圖的討論,該位圖可以提供柵格圖形以使開發人員能夠利用像素級操縱。 這些街機風格的機柜的結構仍然讓我感到驚訝,這些機柜內裝有CRT,主板和諸如按鈕,操縱桿,跟蹤球和旋鈕之類的控制器(輸入設備)。
經典街機游戲
以下是一些具有多種輸入設備的經典街機游戲 :
- 僅按鈕 :小行星,太空侵略者,翻錄,鳳凰
- 僅操縱桿 :Q * bert,PacMan
- 僅旋鈕 :Pong
- 僅軌跡球 :大理石瘋狂
- 操縱桿和按鈕 : 星球大戰 ,桿位,間諜獵人
- 自行車車把 :特技自行車,紙男孩
- 按鈕和油門桿 :月球著陸器
- 潛望鏡和按鈕 :海狼
- 按鈕和軛 :Tron,戰區
- 按鈕,旋鈕和撥叉 :《星際迷航》,《暴風雨》
- 按鈕和軌跡球 :導彈司令部,enti
- 按鈕和操縱桿 :防御者,護手,蛙人,喬斯特,狂風,馬里奧兄弟,金剛,西捷,加拉加,功夫,魂斗羅,街頭霸王,雙龍,忍者魔法(或精神),挖土,龍之巢穴。
輸入/(鼠標,鍵盤)
拋開過去,我們目前遇到了新型的輸入設備,例如觸摸屏,加速度計,紅外接收器,照相機等。當今臺式機上最常見的輸入是鼠標和鍵盤。 當然,觸摸屏已廣泛應用于移動設備和平板電腦,但是在本教程中,我們將僅著眼于“ 鼠標 ”和“ 鍵盤 ”作為控制游戲的輸入。 基于JavaFX路線圖 ,正在進行多點觸摸輸入 (在您閱讀本文時,它已經實現了)。
偵聽鍵盤和鼠標事件時,JavaFX 2.x具有許多類型的事件,這為開發人員提供了機會來實現偵聽觸發事件的事件處理程序。 用于節點或場景的JavaFX 2.x API包含許多帶有前綴“ on”的方法,例如onMousePressProperty()或onKeyPressProperty()方法。 無論何時實現這些方法,都將使用Java的泛型類型簡單地實現handle()方法,以指定要傳遞給查詢的事件對象。 因此,當實例化EventHandler <MouseEvent>類時,將實現一個handle()方法,該方法將MouseEvent作為要傳入的參數。
下面顯示的代碼段將兩個事件處理程序添加到JavaFX Scene。 第一個處理程序將響應鼠標事件。 在我們的簡單游戲中,當發生鼠標按下時,該處理程序將通過發射武器或在飛船上進行響應。 下面顯示的第二個處理程序將響應按鍵事件。 當按下一個鍵時,此處理程序將處理KeyEvent對象。 在我們的游戲中,按鍵“ 2 ”會將您的輔助武器變成更大的沖擊波(更慢)。 其他任何擊鍵將默認返回到較小的沖擊波(更快)。
移動船和火武器
EventHandler fireOrMove = new EventHandler<MouseEvent>() {@Overridepublic void handle(MouseEvent event) {if (event.getButton() == MouseButton.PRIMARY) {// Fire weapon systems. On Windows left mouse button} else if (event.getButton() == MouseButton.SECONDARY) {// Navigate ship thrust. On Windows right mouse button}}};primaryStage.getScene().setOnMousePressed(fireOrMove);
更換武器
EventHandler changeWeapons = new EventHandler<KeyEvent>() {@Overridepublic void handle(KeyEvent event) {myShip.changeWeapon(event.getCode());}};primaryStage.getScene().setOnKeyPressed(changeWeapons);
JavaFX 2輸入演示–“擴展”
簡單的演示游戲將是《星際爭霸》和《小行星》之間的混合體。 使用鼠標導航飛船時,它將類似于《星際爭霸》的《 戰艦巡洋艦》 。 如果您還記得本系列文章的第2部分,那么我創建了球體反彈。 我重用了第2部分“原子粉碎機”中的代碼,像著名的街機游戲一樣充當小行星。 除了在這個游戲中,您根本不會受到傷害。 目的是在您的武器撞到撞擊后會爆裂的其他球體之前向它們發射武器。 由于這是一個簡單的教程,甚至是處于開發初期的游戲,因此該游戲無法跟蹤得分。 我鼓勵您去GitHub下載代碼并增強游戲。 稍后,您將看到一個高級UML類圖,該圖描述了組成游戲的類。 為了簡潔起見,我不會詳細介紹每個類,但是我相信您會在這里訪問GitHub: https : //github.com/carldea/JFXGen,以獲取所有演示和源代碼。
要求 :
- Java 7或更高版本
- JavaFX 2.1或更高版本
- Windows XP或更高版本(應該很快可用于Linux / MacOS)
一個簡單的小行星類型游戲,名為“ The Expanse”。
說明:
- 右鍵單擊(在Windows上)以飛船。
- 單擊鼠標左鍵(在Windows鼠標上單擊鼠標左鍵)即可射擊武器。
- 按鍵'2? 變成大型導彈。(藍色圓形彈丸)
- 其他按鍵默認為較小的導彈。 (紅色圓形彈丸)
![]() |
第三部分“廣闊” |
下面顯示的是高級類圖的圖2,該圖描述了為此演示創建的所有類。 GameWorld和Sprite類是上一篇文章的游戲引擎的一部分。 其余的類是新的,它們構成了本教程的演示。

InputPart3
InputPart3是運行游戲的驅動程序或主要JavaFX應用程序。 這將創建一個要初始化的GameWorld對象,并開始游戲循環。
下面顯示的是主要JavaFX應用程序Input Part3的源代碼。
import carlfx.gameengine.GameWorld;
package carlfx.demos.navigateship;import javafx.application.Application;
import javafx.stage.Stage;/*** The main driver of the game.* @author cdea*/
public class InputPart3 extends Application {GameWorld gameWorld = new TheExpanse(59, "JavaFX 2 GameTutorial Part 3 - Input");/*** @param args the command line arguments*/public static void main(String[] args) {launch(args);}@Overridepublic void start(Stage primaryStage) {// setup title, scene, stats, controls, and actors.gameWorld.initialize(primaryStage);// kick off the game loopgameWorld.beginGameLoop();// display windowprimaryStage.show();}}
廣袤
TheExpanse類繼承自GameWorld類。 這實際上與第2部分的“ AtomSmasher”相同,在該部分中,驅動程序應用程序將調用GameWorld實例的initialize()方法來設置所有游戲元素,例如input , spaceship和那些討厭的浮動球體 。 此類任務是確保小行星或球體從墻壁反彈,并清除到達屏幕邊緣的所有導彈。 負責任的主要是管理資產并創建新級別。 當沒有移動物體并且玩家在屏幕上移動飛船時,將為下一個級別生成新的球體。該類的關鍵是setupInput()方法。 我創建的setupInput()方法負責建立事件處理程序,使其能夠偵聽鍵事件和鼠標事件。
package carlfx.demos.navigateship;import carlfx.gameengine.GameWorld;
import carlfx.gameengine.Sprite;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.CacheHint;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;import java.util.Random;/*** This is a simple game world simulating a bunch of spheres looking* like atomic particles colliding with each other. When the game loop begins* the user will notice random spheres (atomic particles) floating and* colliding. The user will navigate his/her ship by right clicking the mouse to* trust forward and left click to fire weapon to atoms.** @author cdea*/
public class TheExpanse extends GameWorld {// mouse pt labelLabel mousePtLabel = new Label();// mouse press pt labelLabel mousePressPtLabel = new Label();TextField xCoordinate = new TextField("234");TextField yCoordinate = new TextField("200");Button moveShipButton = new Button("Rotate ship");Ship myShip = new Ship();public TheExpanse(int fps, String title) {super(fps, title);}/*** Initialize the game world by adding sprite objects.** @param primaryStage The game window or primary stage.*/@Overridepublic void initialize(final Stage primaryStage) {// Sets the window titleprimaryStage.setTitle(getWindowTitle());//primaryStage.setFullScreen(true);// Create the scenesetSceneNodes(new Group());setGameSurface(new Scene(getSceneNodes(), 800, 600));getGameSurface().setFill(Color.BLACK);primaryStage.setScene(getGameSurface());// Setup Game inputsetupInput(primaryStage);// Create many spheresgenerateManySpheres(2);// Display the number of spheres visible.// Create a button to add more spheres.// Create a button to freeze the game loop.//final Timeline gameLoop = getGameLoop();getSpriteManager().addSprites(myShip);getSceneNodes().getChildren().add(myShip.node);// mouse pointVBox stats = new VBox();HBox row1 = new HBox();mousePtLabel.setTextFill(Color.WHITE);row1.getChildren().add(mousePtLabel);HBox row2 = new HBox();mousePressPtLabel.setTextFill(Color.WHITE);row2.getChildren().add(mousePressPtLabel);stats.getChildren().add(row1);stats.getChildren().add(row2);// mouse pointHBox enterCoord1 = new HBox();enterCoord1.getChildren().add(xCoordinate);enterCoord1.getChildren().add(yCoordinate);enterCoord1.getChildren().add(moveShipButton);stats.getChildren().add(enterCoord1);moveShipButton.setOnAction(new EventHandler() {@Overridepublic void handle(ActionEvent actionEvent) {double x = Double.parseDouble(xCoordinate.getText());double y = Double.parseDouble(yCoordinate.getText());myShip.plotCourse(x, y, false);}});// ===================================================// Debugging purposes// uncomment to test mouse press and rotation angles.//getSceneNodes().getChildren().add(stats);}/*** Sets up the mouse input.** @param primaryStage The primary stage (app window).*/private void setupInput(Stage primaryStage) {System.out.println("Ship's center is (" + myShip.getCenterX() + ", " + myShip.getCenterY() + ")");EventHandler fireOrMove = new EventHandler() {@Overridepublic void handle(MouseEvent event) {mousePressPtLabel.setText("Mouse Press PT = (" + event.getX() + ", " + event.getY() + ")");if (event.getButton() == MouseButton.PRIMARY) {// AimmyShip.plotCourse(event.getX(), event.getY(), false);// fireMissile m1 = myShip.fire();getSpriteManager().addSprites(m1);getSceneNodes().getChildren().add(0, m1.node);} else if (event.getButton() == MouseButton.SECONDARY) {// determine when all atoms are not on the game surface. Ship should be one sprite left.if (getSpriteManager().getAllSprites().size() generateManySpheres(30);}// stop ship from moving forwardmyShip.applyTheBrakes(event.getX(), event.getY());// move forward and rotate shipmyShip.plotCourse(event.getX(), event.getY(), true);}}};// Initialize inputprimaryStage.getScene().setOnMousePressed(fireOrMove);//addEventHandler(MouseEvent.MOUSE_PRESSED, me);// set up statsEventHandler changeWeapons = new EventHandler() {@Overridepublic void handle(KeyEvent event) {myShip.changeWeapon(event.getCode());}};primaryStage.getScene().setOnKeyPressed(changeWeapons);// set up statsEventHandler showMouseMove = new EventHandler() {@Overridepublic void handle(MouseEvent event) {mousePtLabel.setText("Mouse PT = (" + event.getX() + ", " + event.getY() + ")");}};primaryStage.getScene().setOnMouseMoved(showMouseMove);}/*** Make some more space spheres (Atomic particles)** @param numSpheres The number of random sized, color, and velocity atoms to generate.*/private void generateManySpheres(int numSpheres) {Random rnd = new Random();Scene gameSurface = getGameSurface();for (int i = 0; i < numSpheres; i++) { Color c = Color.rgb(rnd.nextInt(255), rnd.nextInt(255), rnd.nextInt(255)); Atom b = new Atom(rnd.nextInt(15) + 5, c, true); Circle circle = b.getAsCircle(); // random 0 to 2 + (.0 to 1) * random (1 or -1) b.vX = (rnd.nextInt(2) + rnd.nextDouble()) * (rnd.nextBoolean() ? 1 : -1); b.vY = (rnd.nextInt(2) + rnd.nextDouble()) * (rnd.nextBoolean() ? 1 : -1); // random x between 0 to width of scene double newX = rnd.nextInt((int) gameSurface.getWidth()); // check for the right of the width newX is greater than width // minus radius times 2(width of sprite) if (newX > (gameSurface.getWidth() - (circle.getRadius() * 2))) {newX = gameSurface.getWidth() - (circle.getRadius() * 2);}// check for the bottom of screen the height newY is greater than height// minus radius times 2(height of sprite)double newY = rnd.nextInt((int) gameSurface.getHeight());if (newY > (gameSurface.getHeight() - (circle.getRadius() * 2))) {newY = gameSurface.getHeight() - (circle.getRadius() * 2);}circle.setTranslateX(newX);circle.setTranslateY(newY);circle.setVisible(true);circle.setId(b.toString());circle.setCache(true);circle.setCacheHint(CacheHint.SPEED);circle.setManaged(false);// add to actors in play (sprite objects)getSpriteManager().addSprites(b);// add sprite'sgetSceneNodes().getChildren().add(0, b.node);}}/*** Each sprite will update it's velocity and bounce off wall borders.** @param sprite - An atomic particle (a sphere).*/@Overrideprotected void handleUpdate(Sprite sprite) {// advance objectsprite.update();if (sprite instanceof Missile) {removeMissiles((Missile) sprite);} else {bounceOffWalls(sprite);}}/*** Change the direction of the moving object when it encounters the walls.** @param sprite The sprite to update based on the wall boundaries.* TODO The ship has got issues.*/private void bounceOffWalls(Sprite sprite) {// bounce off the walls when outside of boundariesNode displayNode;if (sprite instanceof Ship) {displayNode = sprite.node;//((Ship)sprite).getCurrentShipImage();} else {displayNode = sprite.node;}// Get the group node's X and Y but use the ImageView to obtain the width.if (sprite.node.getTranslateX() > (getGameSurface().getWidth() - displayNode.getBoundsInParent().getWidth()) ||displayNode.getTranslateX() < 0) { // bounce the opposite direction sprite.vX = sprite.vX * -1; } // Get the group node's X and Y but use the ImageView to obtain the height. if (sprite.node.getTranslateY() > getGameSurface().getHeight() - displayNode.getBoundsInParent().getHeight() ||sprite.node.getTranslateY() < 0) { sprite.vY = sprite.vY * -1; } } /** * Remove missiles when they reach the wall boundaries. * * @param missile The missile to remove based on the wall boundaries. */ private void removeMissiles(Missile missile) { // bounce off the walls when outside of boundaries if (missile.node.getTranslateX() > (getGameSurface().getWidth() -missile.node.getBoundsInParent().getWidth()) ||missile.node.getTranslateX() < 0) { getSpriteManager().addSpritesToBeRemoved(missile); getSceneNodes().getChildren().remove(missile.node); } if (missile.node.getTranslateY() > getGameSurface().getHeight() -missile.node.getBoundsInParent().getHeight() ||missile.node.getTranslateY() < 0) {getSpriteManager().addSpritesToBeRemoved(missile);getSceneNodes().getChildren().remove(missile.node);}}/*** How to handle the collision of two sprite objects. Stops the particle* by zeroing out the velocity if a collision occurred.** @param spriteA Sprite from the first list.* @param spriteB Sprite from the second list.* @return boolean returns a true if the two sprites have collided otherwise false.*/@Overrideprotected boolean handleCollision(Sprite spriteA, Sprite spriteB) {if (spriteA != spriteB) {if (spriteA.collide(spriteB)) {if (spriteA instanceof Atom && spriteB instanceof Atom) {((Atom) spriteA).implode(this); // will remove from the Scene onFinish()((Atom) spriteB).implode(this);getSpriteManager().addSpritesToBeRemoved(spriteA, spriteB);return true;}}}return false;}}
船
Ship類代表我們很酷的太空飛船。 Ship類繼承自Sprite類,以幫助我們包含速度信息(向量)。 此類還將包含一個雙向鏈接列表,該列表包含32個ImageView ( RotatedShipImage )實例,這些實例表示模擬船繞其中心(質心)旋轉的每個方向。 在某些時候,我想通過旋轉單個SVGPath對象來更改此設置(我知道有一些折衷方案)。 在本教程中,我通過將ImageView的對象從0到360度均勻旋轉32方向來實現飛船。 下面的圖3中顯示的是使用32個ImageView實例和飛船圖像的單個Image對象的所有32個方向,以模擬圍繞其中心(樞軸點)的旋轉。

在給船旋轉動畫時,我只需使用ImageView節點上的setVisible(true)方法將當前圖像以外的所有圖像設置為可見。
免責聲明 :在游戲中,您不可避免地會遇到數學(三角學)。 如果您有興趣并想進一步深入,請查看TheExpanse類的initialize()方法的源代碼。 在方法的末尾取消注釋該語句: getSceneNodes()。getChildren()。add(stats); 。 這將顯示控件,使您可以用來調試和檢查鼠標按下的坐標。 此外,您還可以在控制臺(stdout)中看到與角度,向量等有關的輸出。
船舶的成員變量 :
- turnDirection –枚舉DIRECTION,順時針,逆時針,且均不
- u – Vec對象,該對象包含相對于船舶坐標中心的矢量,表示船舶開始旋轉的起始方向
- directionalShips – RotatedShipImage對象的列表,每個對象具有對其他RotatedShipImage對象的上一個和下一個引用。 零度(uInde??x = 0)是太空飛船朝東的時間。 當旋轉JavaFX節點時,逆時針方向為正數,以度為單位
- uInde??x –當前RotatedShipImage在directionShips列表中將顯示的索引
- vIndex –旋轉動畫結束時將顯示的directionalShips列表中RotatedShipImage的索引
- stopArea –一個JavaFX Circle,其半徑為船舶知道何時停止船舶移動
- flipBook –包含所有RotatedShipImage對象的JavaFX組(32)。 該組將在場景上渲染。 就像動畫中的翻書一樣,每個RotatedShipImage都將根據uInde??x和vIndex確定要顯示
- keyCode – JavaFX KeyCode將幫助確定是否按鍵可以幫助您改變武器(字符“ 2”?)
該船的會員職能 :
- update() –更新船只的速度和方向。 還將確定何時停止移動。
- getCurrentShipImage() –基于uInde??x,它返回ImageView,該ImageView是正在顯示的當前船方向圖像
- getCenterX() –返回屏幕中心的X坐標
- getCenterY() –返回屏幕上船中心的X坐標
- plotCourse(double screenX,double screenY,boolean推力) –用戶在屏幕上單擊鼠標后,此方法將計算旋轉船的角度并更改速度以將坐標推向目標點。 使用Vec對象時,屏幕坐標將轉換為直角坐標,以確定兩個向量(U和V)之間的角度。
- turnShip() – plotCourse()方法調用turnShip()方法來執行船舶旋轉的實際動畫
- applyTheBrakes(double screenX,double screenY) –用戶選擇(右鍵單擊)船只將導航至的位置applyTheBrakes()方法只需設置stopArea ( Circle )對象即可讓船只知道何時停止
- fire() –返回供游戲引擎放入場景中的導彈(Sprite)對象。 每個導彈都以增加的速度(增加的速度)包含與船相同的方向。 應該比飛船飛得更快。
- changeWeapon(KeyCode keyCode) –用戶(玩家)擊鍵為'2? 武器將改變以產生更大的導彈射彈,但速度稍慢。 其他任何按鍵操作都將是默認的武器,它可以產生速度更快的小型導彈彈丸。
下面顯示的是類圖的圖4,顯示了Ship類的成員。
船級圖

下面顯示的是Ship類的源代碼。
package carlfx.demos.navigateship;import carlfx.gameengine.Sprite;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.animation.TimelineBuilder;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.CacheHint;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.image.Image;
import javafx.scene.input.KeyCode;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.util.Duration;
import java.util.ArrayList;
import java.util.List;/*** A space ship with 32 directions* When two atoms collide each will fade and become removed from the scene. The* method called implode() implements a fade transition effect.** @author cdea*/
public class Ship extends Sprite {/*** 360 degree turn*/private final static int TWO_PI_DEGREES = 360;/*** Number of ship frames and directions the ship is pointing nose*/private final static int NUM_DIRECTIONS = 32;/*** The angle of one direction (adjacent directions) (11.25 degrees)*/private final static float UNIT_ANGLE_PER_FRAME = ((float) TWO_PI_DEGREES / NUM_DIRECTIONS);/*** Amount of time it takes the ship to move 180 degrees in milliseconds.*/private final static int MILLIS_TURN_SHIP_180_DEGREES = 300;/*** When the ship turns on each direction one amount of time for one frame or turn of the ship. (18.75 milliseconds)*/private final static float MILLIS_PER_FRAME = (float) MILLIS_TURN_SHIP_180_DEGREES / (NUM_DIRECTIONS / 2);/*** All possible turn directions Clockwise, Counter Clockwise, or Neither when the user clicks mouse around ship*/private enum DIRECTION {CLOCKWISE, COUNTER_CLOCKWISE, NEITHER}/*** Velocity amount used vector when ship moves forward. scale vector of ship. See flipBook translateX and Y.*/private final static float THRUST_AMOUNT = 3.3f;/***/private final static float MISSILE_THRUST_AMOUNT = 6.3F;/*** Angle in degrees to rotate ship.*//*** Current turning direction. default is NEITHER. Clockwise and Counter Clockwise.*/private DIRECTION turnDirection = DIRECTION.NEITHER;/*** The current starting position of the vector or coordinate where the nose of the ship is pointing towards.*/private Vec u; // current or start vector/*** All ImageViews of all the possible image frames for each direction the ship is pointing. ie: 32 directions.*/private final List directionalShips = new ArrayList<>();/*** The Timeline instance to animate the ship rotating using images. This is an optical illusion similar to page* flipping as each frame is displayed the previous visible attribute is set to false. No rotation is happening.*/private Timeline rotateShipTimeline;/*** The current index into the list of ImageViews representing each direction of the ship. Zero is the ship* pointing to the right or zero degrees.*/private int uIndex = 0;/*** The end index into the list of ImageViews representing each direction of the ship. Zero is the ship* pointing to the right or zero degrees.*/private int vIndex = 0;/*** The spot where the user has right clicked letting the engine check the ship's center is in this area.*/private final Circle stopArea = new Circle();/*** A group contain all of the ship image view nodes.*/private final Group flipBook = new Group();/*** A key code will be used for weapon selection.*/private KeyCode keyCode;public Ship() {// Load one image.Image shipImage = new Image(getClass().getClassLoader().getResource("ship.png").toExternalForm(), true);stopArea.setRadius(40);RotatedShipImage prev = null;// create all the number of directions based on a unit angle. 360 divided by NUM_DIRECTIONSfor (int i = 0; i < NUM_DIRECTIONS; i++) {RotatedShipImage imageView = new RotatedShipImage();imageView.setImage(shipImage);imageView.setRotate(-1 * i * UNIT_ANGLE_PER_FRAME);imageView.setCache(true);imageView.setCacheHint(CacheHint.SPEED);imageView.setManaged(false);imageView.prev = prev;imageView.setVisible(false);directionalShips.add(imageView);if (prev != null) {prev.next = imageView;}prev = imageView;flipBook.getChildren().add(imageView);}RotatedShipImage firstShip = directionalShips.get(0);firstShip.prev = prev;prev.next = firstShip;// set javafx node to an imagefirstShip.setVisible(true);node = flipBook;flipBook.setTranslateX(200);flipBook.setTranslateY(300);}/*** Change the velocity of the atom particle.*/@Overridepublic void update() {flipBook.setTranslateX(flipBook.getTranslateX() + vX);flipBook.setTranslateY(flipBook.getTranslateY() + vY);if (stopArea.contains(getCenterX(), getCenterY())) {vX = 0;vY = 0;}}private RotatedShipImage getCurrentShipImage() {return directionalShips.get(uIndex);}/*** The center X coordinate of the current visible image. See <code>getCurrentShipImage()</code> method.** @return The scene or screen X coordinate.*/public double getCenterX() {RotatedShipImage shipImage = getCurrentShipImage();return node.getTranslateX() + (shipImage.getBoundsInLocal().getWidth() / 2);}/*** The center Y coordinate of the current visible image. See <code>getCurrentShipImage()</code> method.** @return The scene or screen Y coordinate.*/public double getCenterY() {RotatedShipImage shipImage = getCurrentShipImage();return node.getTranslateY() + (shipImage.getBoundsInLocal().getHeight() / 2);}/*** Determines the angle between it's starting position and ending position (Similar to a clock's second hand).* When the user is shooting the ship nose will point in the direction of the mouse press using the primary button.* When the user is thrusting to a location on the screen the right click mouse will pass true to the thrust* parameter.** @param screenX The mouse press' screen x coordinate.* @param screenY The mouse press' screen ycoordinate.* @param thrust Thrust ship forward or not. True move forward otherwise false.*/public void plotCourse(double screenX, double screenY, boolean thrust) {// get center of shipdouble sx = getCenterX();double sy = getCenterY();// get user's new turn position based on mouse clickVec v = new Vec(screenX, screenY, sx, sy);if (u == null) {u = new Vec(1, 0);}double atan2RadiansU = Math.atan2(u.y, u.x);double atan2DegreesU = Math.toDegrees(atan2RadiansU);double atan2RadiansV = Math.atan2(v.y, v.x);double atan2DegreesV = Math.toDegrees(atan2RadiansV);double angleBetweenUAndV = atan2DegreesV - atan2DegreesU;// if abs value is greater than 180 move counter clockwise//(or opposite of what is determined)double absAngleBetweenUAndV = Math.abs(angleBetweenUAndV);boolean goOtherWay = false;if (absAngleBetweenUAndV > 180) {if (angleBetweenUAndV < 0) { turnDirection = DIRECTION.COUNTER_CLOCKWISE; goOtherWay = true; } else if (angleBetweenUAndV > 0) {turnDirection = DIRECTION.CLOCKWISE;goOtherWay = true;} else {turnDirection = Ship.DIRECTION.NEITHER;}} else {if (angleBetweenUAndV < 0) { turnDirection = Ship.DIRECTION.CLOCKWISE; } else if (angleBetweenUAndV > 0) {turnDirection = Ship.DIRECTION.COUNTER_CLOCKWISE;} else {turnDirection = Ship.DIRECTION.NEITHER;}}double degreesToMove = absAngleBetweenUAndV;if (goOtherWay) {degreesToMove = TWO_PI_DEGREES - absAngleBetweenUAndV;}//int q = v.quadrant();uIndex = Math.round((float) (atan2DegreesU / UNIT_ANGLE_PER_FRAME));if (uIndex < 0) {uIndex = NUM_DIRECTIONS + uIndex;}vIndex = Math.round((float) (atan2DegreesV / UNIT_ANGLE_PER_FRAME));if (vIndex < 0) { vIndex = NUM_DIRECTIONS + vIndex; } String debugMsg = turnDirection + " U [m(" + u.mx + ", " + u.my + ") => c(" + u.x + ", " + u.y + ")] " +" V [m(" + v.mx + ", " + v.my + ") => c(" + v.x + ", " + v.y + ")] " +" start angle: " + atan2DegreesU +" end angle:" + atan2DegreesV +" Angle between: " + degreesToMove +" Start index: " + uIndex +" End index: " + vIndex;System.out.println(debugMsg);if (thrust) {vX = Math.cos(atan2RadiansV) * THRUST_AMOUNT;vY = -Math.sin(atan2RadiansV) * THRUST_AMOUNT;}turnShip();u = v;}private void turnShip() {final Duration oneFrameAmt = Duration.millis(MILLIS_PER_FRAME);RotatedShipImage startImage = directionalShips.get(uIndex);RotatedShipImage endImage = directionalShips.get(vIndex);List frames = new ArrayList<>();RotatedShipImage currImage = startImage;int i = 1;while (true) {final Node displayNode = currImage;KeyFrame oneFrame = new KeyFrame(oneFrameAmt.multiply(i),new EventHandler() {@Overridepublic void handle(javafx.event.ActionEvent event) {// make all ship images invisiblefor (RotatedShipImage shipImg : directionalShips) {shipImg.setVisible(false);}// make current ship image visibledisplayNode.setVisible(true);// update the current index//uIndex = directionalShips.indexOf(displayNode);}}); // oneFrameframes.add(oneFrame);if (currImage == endImage) {break;}if (turnDirection == DIRECTION.CLOCKWISE) {currImage = currImage.prev;}if (turnDirection == DIRECTION.COUNTER_CLOCKWISE) {currImage = currImage.next;}i++;}if (rotateShipTimeline != null) {rotateShipTimeline.stop();rotateShipTimeline.getKeyFrames().clear();rotateShipTimeline.getKeyFrames().addAll(frames);} else {// sets the game world's game loop (Timeline)rotateShipTimeline = TimelineBuilder.create().keyFrames(frames).build();}rotateShipTimeline.playFromStart();}/*** Stops the ship from thrusting forward.** @param screenX the screen's X coordinate to stop the ship.* @param screenY the screen's Y coordinate to stop the ship.*/public void applyTheBrakes(double screenX, double screenY) {stopArea.setCenterX(screenX);stopArea.setCenterY(screenY);}public Missile fire() {Missile m1;float slowDownAmt = 0;if (KeyCode.DIGIT2 == keyCode) {m1 = new Missile(10, Color.BLUE);slowDownAmt = 2.3f;} else {m1 = new Missile(Color.RED);}// velocity vector of the missilem1.vX = Math.cos(Math.toRadians(uIndex * UNIT_ANGLE_PER_FRAME)) * (MISSILE_THRUST_AMOUNT - slowDownAmt);m1.vY = -Math.sin(Math.toRadians(uIndex * UNIT_ANGLE_PER_FRAME)) * (MISSILE_THRUST_AMOUNT - slowDownAmt);// make the missile launch in the direction of the current direction of the ship nose. based on the// current frame (uIndex) into the list of image view nodes.RotatedShipImage shipImage = directionalShips.get(uIndex);// start to appear in the center of the ship to come out the direction of the nose of the ship.double offsetX = (shipImage.getBoundsInLocal().getWidth() - m1.node.getBoundsInLocal().getWidth()) / 2;double offsetY = (shipImage.getBoundsInLocal().getHeight() - m1.node.getBoundsInLocal().getHeight()) / 2;// initial launch of the missilem1.node.setTranslateX(node.getTranslateX() + offsetX + m1.vX);m1.node.setTranslateY(node.getTranslateY() + offsetY + m1.vY);return m1;}public void changeWeapon(KeyCode keyCode) {this.keyCode = keyCode;}}
Vec
Vec類是一個簡單的幫助程序容器類,可幫助保持鼠標單擊的屏幕坐標,并根據子畫面,圖像或形狀的中心將其轉換為笛卡爾坐標。 此類用于幫助確定兩個向量[Math.atan2(y,x)]之間的角度。 通過確定角度,船可以執行子畫面圖像的旋轉動畫。
下面顯示的是Vec類的源代碼。
package carlfx.demos.navigateship;/*** This class represents a container class to hold a Vector in space and direction* the ship will move. Assuming the center of the ship is the origin the angles can* be determined by a unit circle via Cartesian coordinates.* When the user clicks on the screen the mouse coordinates or screen coordinates* will be stored into the mx and my instance variables.* The x and y data members are converted to cartesian coordinates before storing.** I purposefully left out getters and setters. In gaming just keep things minimalistic.* @author cdea*/
public class Vec {public double mx;public double my;public double x;public double y;/*** This is a default constructor which will take a Cartesian coordinate.* @param x X coordinate of a point on a Cartesian system.* @param y Y coordinate of a point on a Cartesian system.*/public Vec(float x, float y) {this.x = x;this.y = y;}/*** Constructor will convert mouse click points into Cartesian coordinates based on the sprite's center point as* the origin.* @param mx Mouse press' screen X coordinate.* @param my Mouse press' screen Y coordinate.* @param centerX Screen X coordinate of the center of the ship sprite.* @param centerY Screen Y coordinate of the center of the ship sprite.*/public Vec(double mx, double my, double centerX, double centerY) {this.x = convertX(mx, centerX);this.y = convertY(my, centerY);this.mx = mx;this.my = my;}/*** Returns a Cartesian coordinate system's quadrant from 1 to 4.
** first quadrant - 1 upper right* second quadrant - 2 upper left* third quadrant - 3 lower left* fourth quadrant - 4 lower right** @return int quadrant number 1 through 4*/public int quadrant() {int q = 0;if (x > 0 && y > 0) {q =1;} else if (x < 0 && y > 0) {q = 2;} else if (x < 0 && y < 0) { q = 3; } else if (x > 0 && y < 0) {q = 4;}return q;}@Overridepublic String toString(){return "(" + x + "," + y + ") quadrant=" + quadrant();}/*** Converts point's X screen coordinate into a Cartesian system.* @param mouseX Converts the mouse X coordinate into Cartesian system based on the ship center X (originX).* @param originX The ship center point's X coordinate.* @return double value of a Cartesian system X coordinate based on the origin X.*/static double convertX(double mouseX, double originX) {return mouseX - originX;}/*** Converts point's Y screen coordinate into a Cartesian system.* @param mouseY Converts the mouse Y coordinate into Cartesian system based on the ship center Y (originY).* @param originY The ship center point's Y coordinate.* @return double value of a Cartesian system Y coordinate based on the origin Y.*/static double convertY(double mouseY, double originY) {return originY - mouseY;}}
RotatedShipImage
RotatedShipImage類繼承自JavaFX的ImageView類,但還包含對上一個和下一個RotatedShipImage實例的引用, 這些實例構成了一個雙向鏈接列表。 圖3描繪了在每個RotatedShipImage中呈現的“ ship.png”的32個圖像,它們全部放置在JavaFX Group節點中。 當船似乎在旋轉時,一次只顯示一幅圖像。
下面顯示的是RotatedShipImage類的源代碼。
package carlfx.demos.navigateship;import javafx.scene.image.ImageView;/*** Represents a double link list to assist in the rotation of the ship.* This helps to move clockwise and counter clockwise.*/
public class RotatedShipImage extends ImageView {public RotatedShipImage next;public RotatedShipImage prev;
}
導彈
Missile類繼承自Atom類。 導彈是標記物,用于區分小球和導彈。 制造導彈后,它們將以更大的速度包含與船相同的方向(船鼻指向的方向)。
下面顯示的是Missile類的源代碼。
package carlfx.demos.navigateship;import javafx.scene.paint.Color;/*** A missile projectile without the radial gradient.*/
public class Missile extends Atom {public Missile(Color fill) {super(5, fill, false);}public Missile(int radius, Color fill) {super(radius, fill, true);}
}
結論
輸入對任何游戲都至關重要,因此通常很難正確輸入。 較舊的游戲引擎將在游戲循環中進行輪詢。 使用JavaFX 2.x的事件處理時,可以實現要添加到場景圖或單個Node對象中的事件類型。 希望在將來,我們將看到更多用于游戲的巧妙輸入設備(請參閱Oracle的Java技術推廣員Simon Ritter )。 睜大眼睛看一下第4部分,它涉及碰撞檢測。 因此,請繼續關注并隨時發表評論。
有用的鏈接:
7-11 :http://www.7-eleven.com
玩小行星 :http://www.play.vg/games/4-Asteroids.html
小行星 :http://en.wikipedia.org/wiki/Asteroids_(video_game)
斯科特·薩夫蘭(Scott Safran) :http://en.wikipedia.org/wiki/Scott_Safran
后院拱廊 :http://www.themysteryworld.com/2011/02/guy-builds-video-arcade-in-his-back.html
縮小《星戰》 :http://techland.time.com/2012/04/26/man-builds-16-scale-star-wars-arcade-game/
三角學 :http://en.wikipedia.org/wiki/ 三角學
JavaFX節點API :http://docs.oracle.com/javafx/2/api/javafx/scene/Node.html
JavaFX Scene API :http://docs.oracle.com/javafx/2/api/javafx/scene/Scene.html
JavaFX SVGPath API :http: //docs.oracle.com/javafx/2/api/javafx/scene/shape/SVGPath.html
多點觸控和手勢支持 :http://www.oracle.com/technetwork/java/javafx/overview/roadmap-1446331.html
Pro JavaFX 2 Apress發布– pg。 62第2章第2節“處理輸入事件”。 http://www.apress.com/9781430268727
Java 7 Recipe Apress發行-第pg。 602第16章食譜16-3“沿路徑動畫制作形狀”。 http://www.apress.com/9781430240563
電子游戲廳柜 :http://en.wikipedia.org/wiki/Video_game_arcade_cabinet
柵格圖形 :http://en.wikipedia.org/wiki/Raster_graphics
GitHub上的第3部分源代碼 :https://github.com/carldea/JFXGen/tree/master/demos/navigateship
JavaFX Canvas節點 :http://mail.openjdk.java.net/pipermail/openjfx-dev/2012-April/001210.html
JavaFX-為JavaFX應用程序優化性能 :http://www.parleys.com/#st=5&id=2738&sl=0
甲骨文的Java技術推廣員Simon
Ritter :https://blogs.oracle.com/javaone/entry/interface_with_the_interface_javafx
視頻游戲高中第1集 :http://www.rocketjump.com/?video = vghs-episode-1
視頻游戲高中第2集 :http://www.rocketjump.com/?video = vghs-episode-2-5
參考:來自我們的JCG合作伙伴 Carl Dea的JavaFX 2 GameTutorial第3部分 ,位于Carl's FX Blog博客上。
翻譯自: https://www.javacodegeeks.com/2012/05/javafx-2-gametutorial-part-3.html