# 三個Java小程式做出簡易小遊戲 ## 最終結果 ![](https://i.imgur.com/grjdm2T.gif =60%x) ## 組成要素 - 視窗 (JFrame) - 小球 (JPanel) - 球拍 (JPanel) ## 要先知道 > **什麼是類別?想像成 蛋糕模具 什麼是物件?想像成 蛋糕 → 物件(蛋糕)是透過類別(蛋糕模具)而生成出來的** > - **JFrame 類別** → 用來做主頁面框架 - **JPanel 類別** → 作為一個容器使用,但必須放在像JFrame這樣的框架上才能輸出 - 若想把元件放在該界面中,必須把元件放在JPanel中,然後再把JPanel放在JFrame中 :::info 1. JFrame是最底層,JPanel是置於其上 2. 同一個界面只能有一個 JFrame,一個 JFrame可以放多個 JPanel ![](https://i.imgur.com/trTd1qc.gif =60%x) ::: :::warning **paint()方法** → 用來處理圖形,繪製圖形。在繼承JFrame或者JPanel時,系統會自動呼叫,不需要使用物件來呼叫paint()方法,當出現以下幾種情況時會自動呼叫paint() 1. 當程式啟動時 2. 更改視窗大小時 3. 使用repaint()方法時 ::: :::warning **repaint()方法** → 重新繪製paint()方法 調用update()方法下的paint()方法 ::: ## 視窗 1. 建立一個Window的類別並繼承 JFrame,其中涵蓋了 1. Window() 建構子:主要是用來設定初始的視窗資料 2. paint() 方法:畫出分數 2. 在主程式main()方法裡面,用剛剛建立好的Window類別,建立名為window的物件,就完成了 ```java= import javax.swing.JFrame; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; public class Window extends JFrame // 繼承父類Jframe { static int score; public Window() { // 建構子 this.setTitle("不讓球掉下來!!"); // 視窗的標題 this.setSize(600, 600); // 視窗的寬和高 this.setVisible(true); // 顯示視窗 this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 視窗的關閉按鈕、使用System exit方法退出應用程式 this.setLocationRelativeTo(null); //讓視窗置中 } public void paint(Graphics g) { // 覆蓋從JFrame或者JPanel類別繼承的方法,這個方法會被系統自動呼叫。 super.paint(g); //呼叫從父類JFrame繼承的paint方法,這樣才不會留存之前的螢幕內容 Graphics2D g2d = (Graphics2D) g; // 進行物件轉型,因為Graphic2D的方法比較好也比較豐富,它也繼承Graphics這個類別 // 在視窗上設計分數 g2d.setColor(Color.GRAY); //畫筆顏色 g2d.setFont(new Font("Verdana", Font.BOLD, 50)); //字型 g2d.drawString(String.valueOf(score), 20, 120); } public static void main(String[] args) { Window window = new Window(); // 建立window物件 } } ``` ### 結果如下 ![](https://i.imgur.com/W5Te27m.png =60%x) ## 小球 1. 建立一個Ball的類別並繼承 JPanel,其中涵蓋了 - `Ball()` 建構子:在建立物件時,讓Ball類可以獲取Window類別的成員資訊 - `paint()` 方法:畫出小球 - `moveBall()` 方法:小球移動,碰壁後反彈 ```java= import javax.swing.JPanel; import java.awt.Graphics2D; import java.awt.Color; public class Ball extends JPanel { private static final int ballsize = 60; int x = 0; // 小球的預設位置 int y = 0; int incx = 1; // 這是小球要移動的單位 int incy = 1; private Window window; public Ball(Window w) { // 建構子 this.window = w; } void moveBall() // 這個方法就是不斷更新小球的位置 { if (x + incx > window.getWidth() - ballsize) // 如果小球移動後的位置超出視窗範圍的話,移動方向就是-1; 再扣掉球的大小 incx = -1; if (x + incx < 0) // 同理 incx = 1; if (y + incy > window.getHeight() - ballsize) incy = -1; if (y + incy < 0) incy = 1; x += incx; y += incy; } public void paint(Graphics2D g) { g.setColor(Color.darkGray); //設定顏色 g.fillOval(x, y, ballsize, ballsize); //(x軸, y軸, 球的寬度, 球的高度) } } ``` 2. 在 Window 類別內建立小球物件 ```java= Ball ball = new Ball(this); // 這裡建立一個ball物件,this作為引數讓Ball類可以獲取Window的成員資訊 ``` 3. 在Window類別的paint方法中,利用剛新建出的ball物件,呼叫Ball類別中的paint方法,把球畫在window視窗上 ```java= ball.paint(g2d); // 呼叫ball類中的paint方法 ``` 4. 在Window類別新增一個move()方法 ```java= private void move() // 這裡是move方法用來呼叫Ball類中的moveBall { ball.moveBall(); } ``` 5. 最後在main主程式當中,製作一個無限迴圈,讓它不斷地重新繪製小球,達到移動效果。 ```java= public static void main(String[] args) throws InterruptedException // 這個是丟擲異常用來防止執行緒異常的 while (true) { window.move(); window.repaint(); // 重新繪製paint方法 Thread.sleep(2); // 延遲2毫秒再繪製,不然小球會移動很快一閃而過 } ``` ### 結果如下 ![](https://i.imgur.com/cFYxzAm.gif =60%x) ## 球拍 1. 建立一個Racquet的類別並繼承 JPanel,其中涵蓋了 - Racquet() 建構子:在建立物件時,讓Racquet類別可以獲取Window的成員資訊 - paint() 方法:畫出球拍 ```java= import javax.swing.JPanel; import java.awt.Graphics2D; public class Racquet extends JPanel { int x = 0; private static final int y = 570; // 新增三個球拍屬性的final變數,因為已經確定下來了不會再改 private static final int WIDTH = 120; private static final int HEIGHT = 30; private Window window; public Racquet(Window w) { // 建構子 this.window = w; } public void paint(Graphics2D g) { g.fillRect(x, y, WIDTH, HEIGHT); } } ``` 2. 在 Window 類別內建立球拍物件 ```java= Racquet racquet = new Racquet(this); // 這裡建立一個racquet物件,this作為引數讓Racquet類可以獲取Window的成員資訊 ``` 3. 在Window類別的paint方法中,利用剛新建出的racquet物件,呼叫Racquet類別中的paint方法,把球拍畫在window視窗上 ```java= racquet.paint(g2d); ``` 4. 接下來,如何用鍵盤左右鍵移動球拍呢? 1. 首先,為了接收到鍵盤操作的動作,在Window類別實作鍵盤監聽器 (implements KeyListener), ```java= import java.awt.event.*; public class Window extends JFrame implements KeyListener ``` 2. 在Window() 建構子新增addKeyListener()方法,有點像是當物件生成時,自動註冊鍵盤監聽器,去接收鍵盤發生的事件 ```java= this.addKeyListener(this); // 增加鍵盤監聽器 ``` 3. 並且一定要定義下列三種方法 (一樣在Window類別) 1. `keyPressed()`、`KeyReleased()`、`keyTyped()` 2. 且在需要用到的方法底下,呼叫剛剛建立好的racquet球拍,並執行按下和放開鍵盤的方法 ```java= // KeyEvent 當以下三種方法的任何一種方法發生時,KeyListener鍵盤監聽器就會啟動KeyEvent public void keyPressed(KeyEvent e) { // 按下鍵盤的動作 racquet.KeyPressed(e); } public void keyReleased(KeyEvent e) { // 放開鍵盤的動作 racquet.KeyReleased(e); } public void keyTyped(KeyEvent e) { // 按下鍵盤與放開鍵盤之間的動作 // 就算不會使用到這個方法,還是要定義 } ``` 4. 再來,幫球拍寫出左右移動的方法 `KeyPressed()`、`KeyReleases()`、`moveRacquet ()` ```java= import java.awt.event.KeyEvent; int xa = 0; // 移動單位 public void KeyPressed(KeyEvent e) { // 鍵盤按下時,移動 if (e.getKeyCode() == KeyEvent.VK_LEFT) // 往左移動 xa = -2; if (e.getKeyCode() == KeyEvent.VK_RIGHT) // 往右移動 xa = 2; } public void KeyReleased(KeyEvent e) { // 鍵盤放開時,不移動 xa = 0; } public void moveRacquet() { if (x + xa < window.getWidth() - 120 && x + xa > 0) // 移動座標 小於右邊邊界 且 大於左邊邊界 x += xa; } ``` 5. 最後,在move()方法裡,讓racquet物件呼叫Racquet類別中的moveRacquet()方法,就大功告成了! ```java= private void move() { ball.moveBall(); racquet.moveRacquet(); } ``` ### 結果如下 ![](https://i.imgur.com/BIdFUlp.gif =60%x) ## 如何建立碰撞機制,並讓分數增加呢? > Java函式庫裡有一個Rectangle類別,把小球和球拍變成Rectangle型態的變數,去使用Rectangle類別內有的intersects()方法,查看他們是否有相交,相交之後小球再彈回~ > ![](https://i.imgur.com/S56cnL5.gif =80%x) 1. 分別在Ball類別、Racquet類別,建立一個傳回值為Rectangle型態的方法 ```java import java.awt.Rectangle; public Rectangle getBounds() // 返回Rectangle型態的小球 { return new Rectangle(x, y, ballsize, ballsize); } ``` ```java= import java.awt.Rectangle; public Rectangle getBounds() // 返回Rectangle型態的球拍 { return new Rectangle(x, y, WIDTH, HEIGHT); } ``` 2. 再來是碰撞檢測,使用intersects()方法檢測Rectangle型態的球拍和小球是否有碰撞 ```java= private boolean collision() { return window.racquet.getBounds().intersects(getBounds()); // 用intersects方法判斷小球是否和球拍相交 } ``` 3. 何時該使用這個方法呢? 1. 當每次小球移動時,就檢測是否和球拍碰撞,因此新增在moveBall()方法中 2. 一到碰撞的當下,必須馬上矯正小球的y座標,避免一直和舊球拍重疊,分數瘋狂增加 1. 此處透過已矯正後的小球y座標(球拍的y座標 - 小球高度),開始進行反彈 3. 每碰撞一次,分數+1 ```java= if (collision()) { incy = -1; // 改變y軸的移動方向 y = window.racquet.getTopY() - ballsize; // 這個是矯正球的位置,為了防止碰撞導致的舊球拍和小球重疊,若重疊的話,計分會瘋狂增加! window.score++; } ``` ii. 為了取得球拍y座標而在Racquet類別新增的方法 ```java= public int getTopY() // 返回球拍所在的水平線 { return y; } ``` ### 結果如下 ![](https://i.imgur.com/0BaUUA5.gif =60%x) ## 如何讓小球沒碰到球拍就結束比賽? 這邊給大家自己腦力激盪,到底該怎麼做才會變成以下結果勒?? (是需要用到什麼套件嗎?) 雖然這些程式碼在網路上都可以找到,但我希望大家可以透過思考並查詢該使用什麼樣的套件,因而找到答案,並把它內化成自己的養分兒~ ![](https://i.imgur.com/943bd6r.gif =60%x) **總結:** 此次帶大家做的小遊戲,不知道大家對Java有沒有更進一步的興趣?不管你是否曾學過Java,我想讓大家可以透過比較有趣的方法去認識並學習Java。這次主要想帶給大家的是 在學習過程中如何透過Java API去找到該類別方法應該如何使用,我想這是學習一個程式語言很重要的過程。 點此連結:[Java Documentation](https://docs.oracle.com/en/java/javase/index.html) --- ### Java JDK 下載、安裝與環境變數設定教學 - For Windows:[https://www.pcsetting.com/devtools/35?page=0%2C0](https://www.pcsetting.com/devtools/35?page=0%2C0) - For Mac:[https://iyoumealice20.pixnet.net/blog/post/327171772](https://iyoumealice20.pixnet.net/blog/post/327171772) ### Eclipse IDE 下載、設定與使用教學-Java 篇 [https://www.pcsetting.com/devtools/80?page=0%2C0](https://www.pcsetting.com/devtools/80?page=0%2C0) **資料來源:**[https://www.itread01.com/content/1547711836.html](https://www.itread01.com/content/1547711836.html)