# 三個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)