Try   HackMD

三個Java小程式做出簡易小遊戲

最終結果

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

組成要素

  • 視窗 (JFrame)
  • 小球 (JPanel)
  • 球拍 (JPanel)

要先知道

什麼是類別?想像成 蛋糕模具
什麼是物件?想像成 蛋糕
→ 物件(蛋糕)是透過類別(蛋糕模具)而生成出來的

  • JFrame 類別 → 用來做主頁面框架
  • JPanel 類別 → 作為一個容器使用,但必須放在像JFrame這樣的框架上才能輸出
  • 若想把元件放在該界面中,必須把元件放在JPanel中,然後再把JPanel放在JFrame中
  1. JFrame是最底層,JPanel是置於其上
  2. 同一個界面只能有一個 JFrame,一個 JFrame可以放多個 JPanel

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

paint()方法 → 用來處理圖形,繪製圖形。在繼承JFrame或者JPanel時,系統會自動呼叫,不需要使用物件來呼叫paint()方法,當出現以下幾種情況時會自動呼叫paint()
1. 當程式啟動時
2. 更改視窗大小時
3. 使用repaint()方法時

repaint()方法 → 重新繪製paint()方法
調用update()方法下的paint()方法

視窗

  1. 建立一個Window的類別並繼承 JFrame,其中涵蓋了

    1. Window() 建構子:主要是用來設定初始的視窗資料
    2. paint() 方法:畫出分數
  2. 在主程式main()方法裡面,用剛剛建立好的Window類別,建立名為window的物件,就完成了

    ​​​​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物件 ​​​​ } ​​​​}

結果如下

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

小球

  1. 建立一個Ball的類別並繼承 JPanel,其中涵蓋了

    • Ball() 建構子:在建立物件時,讓Ball類可以獲取Window類別的成員資訊
    • paint() 方法:畫出小球
    • moveBall() 方法:小球移動,碰壁後反彈
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軸, 球的寬度, 球的高度) } }
  1. 在 Window 類別內建立小球物件

    ​​​​Ball ball = new Ball(this); // 這裡建立一個ball物件,this作為引數讓Ball類可以獲取Window的成員資訊
  2. 在Window類別的paint方法中,利用剛新建出的ball物件,呼叫Ball類別中的paint方法,把球畫在window視窗上

    ​​​​ball.paint(g2d); // 呼叫ball類中的paint方法
  3. 在Window類別新增一個move()方法

    ​​​​private void move() // 這裡是move方法用來呼叫Ball類中的moveBall ​​​​{ ​​​​ ball.moveBall(); ​​​​}
  4. 最後在main主程式當中,製作一個無限迴圈,讓它不斷地重新繪製小球,達到移動效果。

    ​​​​public static void main(String[] args) throws InterruptedException // 這個是丟擲異常用來防止執行緒異常的 ​​​​ ​​​​while (true) { ​​​​ window.move(); ​​​​ window.repaint(); // 重新繪製paint方法 ​​​​ Thread.sleep(2); // 延遲2毫秒再繪製,不然小球會移動很快一閃而過 ​​​​}

結果如下

球拍

  1. 建立一個Racquet的類別並繼承 JPanel,其中涵蓋了

    • Racquet() 建構子:在建立物件時,讓Racquet類別可以獲取Window的成員資訊
    • paint() 方法:畫出球拍
    ​​​​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 類別內建立球拍物件

    ​​​​Racquet racquet = new Racquet(this); // 這裡建立一個racquet物件,this作為引數讓Racquet類可以獲取Window的成員資訊
  3. 在Window類別的paint方法中,利用剛新建出的racquet物件,呼叫Racquet類別中的paint方法,把球拍畫在window視窗上

    ​​​​racquet.paint(g2d);
  4. 接下來,如何用鍵盤左右鍵移動球拍呢?

    1. 首先,為了接收到鍵盤操作的動作,在Window類別實作鍵盤監聽器 (implements KeyListener),

      ​​​​​​​​import java.awt.event.*; ​​​​​​​​ ​​​​​​​​public class Window extends JFrame implements KeyListener
    2. 在Window() 建構子新增addKeyListener()方法,有點像是當物件生成時,自動註冊鍵盤監聽器,去接收鍵盤發生的事件

      ​​​​​​​​this.addKeyListener(this); // 增加鍵盤監聽器
    3. 並且一定要定義下列三種方法 (一樣在Window類別)

      1. keyPressed()KeyReleased()keyTyped()
      2. 且在需要用到的方法底下,呼叫剛剛建立好的racquet球拍,並執行按下和放開鍵盤的方法
      ​​​​​​​​// KeyEvent 當以下三種方法的任何一種方法發生時,KeyListener鍵盤監聽器就會啟動KeyEvent ​​​​​​​​public void keyPressed(KeyEvent e) { // 按下鍵盤的動作 ​​​​​​​​ racquet.KeyPressed(e); ​​​​​​​​} ​​​​​​​​public void keyReleased(KeyEvent e) { // 放開鍵盤的動作 ​​​​​​​​ racquet.KeyReleased(e); ​​​​​​​​} ​​​​​​​​public void keyTyped(KeyEvent e) { // 按下鍵盤與放開鍵盤之間的動作 ​​​​​​​​ // 就算不會使用到這個方法,還是要定義 ​​​​​​​​}
  5. 再來,幫球拍寫出左右移動的方法 KeyPressed()KeyReleases()moveRacquet ()

    ​​​​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; ​​​​}
  6. 最後,在move()方法裡,讓racquet物件呼叫Racquet類別中的moveRacquet()方法,就大功告成了!

    ​​​​private void move() ​​​​{ ​​​​ ball.moveBall(); ​​​​ racquet.moveRacquet(); ​​​​}

結果如下

如何建立碰撞機制,並讓分數增加呢?

Java函式庫裡有一個Rectangle類別,把小球和球拍變成Rectangle型態的變數,去使用Rectangle類別內有的intersects()方法,查看他們是否有相交,相交之後小球再彈回~

  1. 分別在Ball類別、Racquet類別,建立一個傳回值為Rectangle型態的方法

    ​​​​import java.awt.Rectangle;
    ​​​​
    ​​​​public Rectangle getBounds() // 返回Rectangle型態的小球
    ​​​​{
    ​​​​    return new Rectangle(x, y, ballsize, ballsize);
    ​​​​}
    
    ​​​​import java.awt.Rectangle; ​​​​ ​​​​public Rectangle getBounds() // 返回Rectangle型態的球拍 ​​​​{ ​​​​ return new Rectangle(x, y, WIDTH, HEIGHT); ​​​​}
  2. 再來是碰撞檢測,使用intersects()方法檢測Rectangle型態的球拍和小球是否有碰撞

    ​​​​private boolean collision() ​​​​{ ​​​​ return window.racquet.getBounds().intersects(getBounds()); // 用intersects方法判斷小球是否和球拍相交 ​​​​}
  3. 何時該使用這個方法呢?

    1. 當每次小球移動時,就檢測是否和球拍碰撞,因此新增在moveBall()方法中

    2. 一到碰撞的當下,必須馬上矯正小球的y座標,避免一直和舊球拍重疊,分數瘋狂增加

      1. 此處透過已矯正後的小球y座標(球拍的y座標 - 小球高度),開始進行反彈
    3. 每碰撞一次,分數+1

      ​​​​​​​​if (collision()) ​​​​​​​​{ ​​​​​​​​ incy = -1; // 改變y軸的移動方向 ​​​​​​​​ y = window.racquet.getTopY() - ballsize; // 這個是矯正球的位置,為了防止碰撞導致的舊球拍和小球重疊,若重疊的話,計分會瘋狂增加! ​​​​​​​​ window.score++; ​​​​​​​​}

      ii. 為了取得球拍y座標而在Racquet類別新增的方法

      ​​​​​​​​public int getTopY() // 返回球拍所在的水平線 ​​​​​​​​{ ​​​​​​​​ return y; ​​​​​​​​}

結果如下

如何讓小球沒碰到球拍就結束比賽?

這邊給大家自己腦力激盪,到底該怎麼做才會變成以下結果勒?? (是需要用到什麼套件嗎?)

雖然這些程式碼在網路上都可以找到,但我希望大家可以透過思考並查詢該使用什麼樣的套件,因而找到答案,並把它內化成自己的養分兒~

總結:

此次帶大家做的小遊戲,不知道大家對Java有沒有更進一步的興趣?不管你是否曾學過Java,我想讓大家可以透過比較有趣的方法去認識並學習Java。這次主要想帶給大家的是

在學習過程中如何透過Java API去找到該類別方法應該如何使用,我想這是學習一個程式語言很重要的過程。

點此連結:Java Documentation


Java JDK 下載、安裝與環境變數設定教學

Eclipse IDE 下載、設定與使用教學-Java 篇

https://www.pcsetting.com/devtools/80?page=0%2C0

資料來源:https://www.itread01.com/content/1547711836.html