iT邦幫忙

2025 iThome 鐵人賽

DAY 23
0
Software Development

30天從基礎學起Java,直到做出我的第一個遊戲系列 第 23

Day 23:Java Snake Game 增加遊戲功能(二)

  • 分享至 

  • xImage
  •  

今日內容:public to private, 音效, 音效路徑問題, 升等加速

public to private

開始今天的功能新增前,我決定先把大部分的method(除Override或可能會被被外部呼叫的method)都由public改為private,增加整個class的封裝性,也間接提高安全性

音效

再來是設定遊戲的音效,畢竟一款遊戲如果沒有聲音那玩起來會非常枯燥
遊戲聲音的資料我是從 FreesoundPixabay 上找的免費公開資源

下載完檔案後先轉格式為.wav檔 使用 CloudConverter 進行轉檔

接著我們要透過AduioInputStream和Clip進行載入與播放音效的操作
於是我在constructor內再寫一個loadSound(),用來讀取和載入音效

private void loadSound(){
    try{
        AudioInputStream audioInputStream = 
           AudioSystem.getAudioInputStream(new File("getPoint.wav").getAbsoluteFile());
        eatSound = AudioSystem.getClip();
        eatSound.open(audioInputStream);
        
        AudioInputStream audioInputStream2 = 
           AudioSystem.getAudioInputStream(new File("gameOver.wav").getAbsoluteFile());
        gameOverSound = AudioSystem.getClip();
        gameOverSound.open(audioInputStream2);
    }
    catch(Exception e){
        System.out.println("Error: " + e);
    }
}

載入完音效後,我們設定播放吃到食物的音效跟結束遊戲的音效,然後分別將他們加入得分時與遊戲結束時的位置

private void playEatSound(){
    eatSound.start();
}
private void playGameOverSound(){
    gameOverSound.start();
}

不過我發現音效播一次就不會再播了,所以我們需要在播完音效之前將音效檔案回到開頭,然後再播放就可以了

eatSound.setFramePosition(0);
eatSound.start();

但現在又遇到一個問題是,短時間吃到兩個食物就沒辦法重新播放(只會播放一次音效),所以我們要檢查當前是否在播放,如果有就先暫停

if(eatSound.isRunning()) eatSound.stop(); // 使用內建method isRunning()

這樣就弄完音效的設定了

音效路徑問題(並未理解)

不過接下來有一個嚴重的問題是,我將程式打包成jar後,系統會抓不到IDE中設定的音效檔案位置
因此Gemini建議我:
image image

於是我照著建議修改了,此時IntelliJ卻建議我:
image

由於getClass().getResourceAsStream("/getPoint.wav") 的回傳值有可能會是null,如果AudioSystem.getAudioInputStream接收到null就會出錯(不接受輸入null)
因此我決定直接套用IntelliJ建議我的寫法

然而,儘管我的資料全部放在正確的位置,打包過程的設定也都正確,打包成jar後仍然抓不到音效檔案。
於是我又向Gemini求助
image
他建議我使用InputStream和BufferedInputStream的方式,將音效讀取到記憶體後再傳給Clip物件
image

import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import java.io.BufferedInputStream;
import java.io.InputStream;
import java.io.ByteArrayInputStream;

// ...

private void loadSound(){
    try {
        InputStream is1 = 
            Objects.requireNonNull(getClass().getResourceAsStream("/getPoint.wav"));
        AudioInputStream audioInputStream1 = 
            AudioSystem.getAudioInputStream(new BufferedInputStream(is1));
        
        eatSound = AudioSystem.getClip();
        eatSound.open(audioInputStream1);

        InputStream is2 = 
            Objects.requireNonNull(getClass().getResourceAsStream("/gameOver.wav"));
        AudioInputStream audioInputStream2 = 
            AudioSystem.getAudioInputStream(new BufferedInputStream(is2));
        
        gameOverSound = AudioSystem.getClip();
        gameOverSound.open(audioInputStream2);

    } catch(Exception e) {
        System.out.println("Error: " + e);
    }
}

重新打包jar後,音效就可以正確播放了
至此我們也徹底完成音效的設置了

升等加速

為了提高挑戰性,我打算每吃3個食物可以升一等,加快速度以此增加挑戰性
要做到這點,我們需要在遊戲中改變Timer的延遲時間,並播放一個升等音效
我在 Pixabay 下載免費公開的音效資源,然後透過修改Timer的delay控制延遲時間

// 我首先設置兩個常數和變數控制遊戲時間
static final int INITIAL_SPEED = 150;
static final int SPEED_MULTIPLIER = 0.9

int delay = INITIAL_SPEED

// 然後在得分處
private void move(){
    // ...
    if(score % 3 == 0){
        // ...
        delay = (int)(delay * SPEED_MULTIPLIER);
    }
    // ...
}

// 最後在遊戲重新開始時將時間設回預設值
private void startGame(){
    // ...
    delay = INITIAL_SPEED;
    timer.setDelay(delay);
}

升等音效的部分,一樣在loadSound()中用Clip和BufferInputStream、AudioStream這些東西載入,然後再寫一個playLevelUpSound()

private void playLevelUpSound(){
    if(levelUp.inRunning) levelUp.stop();
    levelUp.setFramePosition(0);
    levelUp.start();
}

接著,我不打算讓這個升等無止盡下去,所以我打算限制最多升到5等(提高0.9^5速度)

int level;

// ...

private void move(){
    if(score % 3 == 0 && level < 5){
        // ...
        delay = (int)(delay * SPEED_MULTIPLIER);
        ++level;
        playLevelUpSound();
    }  
}

升等的邏輯弄完,接下來就是在畫面上顯示等級
我打算將等級與分數放在同一個label中呈現,並且滿級時也會有max的字樣

private void drawScore(Graphics2D g2d){
    g2d.setColor(scoreC);
    g2d.setFont(new Font("MV Boli", Font.PLAIN, 25));
    String levelText = "Level: " + level;
    if(level == 5) {
        levelText += " (max)";
    }
    g2d.drawString("Score: " + score + " " + levelText, 280, 50);
}

至此升等的功能也製作完成了
image


上一篇
Day 22:Java Snake Game 增加遊戲功能(一)
系列文
30天從基礎學起Java,直到做出我的第一個遊戲23
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言