Java實現貪吃蛇遊戲的示例代碼

項目演示

項目演示地址

項目實戰

1. 遊戲的主啟動類

作為貪吃蛇遊戲的主啟動類,構建瞭頂級窗口,可以容納各種面板,

package Snake;

import javax.swing.*;

/**
 * 遊戲的主啟動類
 */
public class StartGame {
    public static void main(String[] args) {
        JFrame frame = new JFrame();

        frame.setBounds(10,10,900,720);
        frame.setResizable(false);  //窗口大小不可變
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

        //正常遊戲界面都應該在面板上

        frame.setVisible(true);
    }
}

2. 遊戲的面板

若是沒有super.paintComponent(g);,則會出現閃屏,

在主啟動類StartGame中添加frame.add(new GamePanel());

package Snake;

import javax.swing.*;
import java.awt.*;

/**
 * 遊戲的面板
 */
public class GamePanel extends JPanel {
    //繪制面板,遊戲中所有東西都用這個畫筆來畫
    @Override
    protected void paintComponent(Graphics g){
        super.paintComponent(g);    //清屏
        this.setBackground(Color.BLACK);
    }
}

3. 數據中心

創建一個Data類作為數據中心,用於調用statics包裡的資源,

package Snake;

import javax.swing.*;
import java.net.URL;

/**
 * 數據中心,用於調用資源
 */
public class Data {
    public static URL headerURL = Data.class.getResource("/statics/header.png");
    public static ImageIcon header = new ImageIcon(headerURL);

    public static URL downURL = Data.class.getResource("/statics/down.png");
    public static URL leftURL = Data.class.getResource("/statics/left.png");
    public static URL rightURL = Data.class.getResource("/statics/right.png");
    public static URL upURL = Data.class.getResource("/statics/up.png");
    public static ImageIcon up = new ImageIcon(upURL);
    public static ImageIcon down = new ImageIcon(downURL);
    public static ImageIcon left = new ImageIcon(leftURL);
    public static ImageIcon right = new ImageIcon(rightURL);

    public static URL bodyURL = Data.class.getResource("/statics/body.png");
    public static ImageIcon body = new ImageIcon(bodyURL);

    public static URL foodURL = Data.class.getResource("/statics/food.png");
    public static ImageIcon food = new ImageIcon(foodURL);
}

4. 繪制靜態面板

GamePanel類中,構建一個初始的靜態面板,添加如下代碼,

/**
 * 繪制靜態面板
 */
this.setBackground(Color.WHITE);
Data.header.paintIcon(this,g,25,11);    //頭部廣告欄
g.fillRect(25,75,850,600);    //默認遊戲界面

5. 繪制靜態小蛇

依然是在類GamePanel中,先是繪制好小蛇的初始形態,

//定義蛇的數據結構
int length;     //蛇的長度
int[] snakeX = new int[600];    //蛇的x坐標 25*25
int[] snakeY = new int[500];    //蛇的y坐標 25*25

//構造器
public GamePanel(){
    init();
}

//初始化方法
public void init(){
    length = 3;
    snakeX[0] = 100; snakeY[0] = 100;    //蛇腦袋的坐標
    snakeX[1] = 75; snakeY[1] = 100;    //蛇第一節身體的坐標
    snakeX[2] = 50; snakeY[2] = 100;    //蛇第二節身體的坐標
}

然後再把繪制好的小蛇畫到面板上去,即在paintComponent(Graphics g)方法中添加如下代碼,

/**
 * 繪制靜態小蛇
 */
Data.right.paintIcon(this,g,snakeX[0],snakeY[0]);    //蛇腦袋的坐標
Data.body.paintIcon(this,g,snakeX[1],snakeY[1]);    //蛇第一節身體的坐標
Data.body.paintIcon(this,g,snakeX[2],snakeY[2]);    //蛇第二節身體的坐標

6. 繪制動態小蛇

小蛇在動起來之後,蛇頭會進行上下左右的移動,身體也會變長,因此不能局限於固定的坐標,需要對靜態小蛇的代碼做如下改動,

添加一個名為fxString對象,存儲小蛇的方向,使用if語句進行判斷,

對於小蛇身體節數的增長使用for循環語句進行控制,

String fx;

//初始化方法
public void init(){
    length = 3;
    snakeX[0] = 100; snakeY[0] = 100;    //蛇腦袋的坐標
    snakeX[1] = 75; snakeY[1] = 100;    //蛇第一節身體的坐標
    snakeX[2] = 50; snakeY[2] = 100;    //蛇第二節身體的坐標
    fx = "L";
}

/**
 * 繪制小蛇
 */
if(fx.equals("R")){
    Data.right.paintIcon(this,g,snakeX[0],snakeY[0]);    //蛇頭初始化向右,需要通過方向來判斷
}else if(fx.equals("L")){
    Data.left.paintIcon(this,g,snakeX[0],snakeY[0]);    //蛇頭初始化向左,需要通過方向來判斷
}else if(fx.equals("U")){
    Data.up.paintIcon(this,g,snakeX[0],snakeY[0]);    //蛇頭初始化向上,需要通過方向來判斷
}else if(fx.equals("D")){
    Data.down.paintIcon(this,g,snakeX[0],snakeY[0]);    //蛇頭初始化向下,需要通過方向來判斷
}

for (int i = 1; i < length; i++) {
    Data.body.paintIcon(this,g,snakeX[i],snakeY[i]);    //蛇第一節身體的坐標
}

7. 設置遊戲狀態

遊戲狀態主要分為開始停止兩種,我們默認遊戲狀態為停止,

依舊是在類GamePanel中進行設置,

添加一個boolean對象,

//遊戲狀態:開始,停止
boolean isStart = false;    //默認遊戲不開始

paintComponent(Graphics g)方法中添加如下代碼,

/**
 * 遊戲狀態
 */
if(isStart == false){
    g.setColor(Color.CYAN);
    g.setFont(new Font("微軟雅黑",Font.BOLD,40));   //設置字體
    g.drawString("按下空格開始遊戲",300,350);
}

8. 讓蛇動起來

讓蛇能夠動起來就是為程序添加監聽事件,內部類或者外部類都可,  

空格鍵獲得響應

設置鍵盤的監聽事件,先設置空格的監聽事件,

接上接口KeyListener,重寫它的三個方法,

//鍵盤監聽事件
@Override
public void keyPressed(KeyEvent e){
    int keyCode = e.getKeyCode();   //獲得鍵盤按鍵是哪一個
    if (keyCode == KeyEvent.VK_SPACE){      //如果按下空格鍵
        isStart = !isStart;     //取反
        repaint();
    }
}

@Override
public void keyTyped(KeyEvent e) {

}

@Override
public void keyReleased(KeyEvent e) {

}

在構造器中獲取焦點和鍵盤事件,鼠標在范圍內獲取焦點,離開范圍則失去焦點,

//構造器
public GamePanel(){
    init();
    //獲得焦點和鍵盤事件
    this.setFocusable(true);    //獲得焦點事件
    this.addKeyListener(this);  //獲得鍵盤監聽事件
}

初始化狀態

點擊空格後

設置定時器

通過對固定事件的高頻率刷新,實現動畫效果,即創建定時器Timer

    //定時器,以ms為單位,1s = 1000ms
    Timer timer = new Timer(100, this);     //100毫秒執行一次

接下來設置事件監聽,先以右移為例,

//事件監聽——需要通過固定事件來刷新,比如10次/s
@Override
public void actionPerformed(ActionEvent e) {
    if(isStart){    //如果遊戲是開始狀態,就讓小蛇動起來
        //右移
        for (int i = length-1; i > 0; i--) {    //後一節移動到前一節的位置 snakeX[1] = snakeX[0];
            snakeX[i] = snakeX[i-1];
            snakeY[i] = snakeY[i-1];
        }
        snakeX[0] += 25;

        //邊界判斷
        if (snakeX[0] > 850){
            snakeX[0] = 25;
        }

        repaint();  //重畫頁面
    }
    timer.start();  //定時器開啟
}

同時不要忘記在構造器中添加啟動語句timer.start();,啟動程序,

方向鍵獲得響應

在鍵盤監聽事件keyPressed(KeyEvent e)中,添加上下左右鍵盤監聽,類似於空格鍵獲得的響應,

/**
 * 小蛇移動
 */
if(keyCode == KeyEvent.VK_UP){
    fx = "U";
}else if (keyCode == KeyEvent.VK_DOWN){
    fx = "D";
}else if (keyCode == KeyEvent.VK_LEFT){
    fx = "L";
}else if (keyCode == KeyEvent.VK_RIGHT){
    fx = "R";
}

然後再對定時器進行修改,

//事件監聽——需要通過固定事件來刷新,比如10次/s
@Override
public void actionPerformed(ActionEvent e) {
    if(isStart){    //如果遊戲是開始狀態,就讓小蛇動起來
        //移動
        for (int i = length-1; i > 0; i--) {    //後一節移動到前一節的位置 snakeX[1] = snakeX[0];
            snakeX[i] = snakeX[i-1];
            snakeY[i] = snakeY[i-1];
        }

        //走向
        if (fx.equals("R")){
            snakeX[0] = snakeX[0] + 25;
            if(snakeX[0] > 850){    //邊界判斷
                snakeX[0] = 25;
            }
        }else if (fx.equals("L")){
            snakeX[0] = snakeX[0] - 25;
            if(snakeX[0] < 25){    //邊界判斷
                snakeX[0] = 850;
            }
        }else if (fx.equals("U")){
            snakeY[0] = snakeY[0] - 25;
            if(snakeY[0] < 75){    //邊界判斷
                snakeY[0] = 650;
            }
        }else if (fx.equals("D")){
            snakeY[0] = snakeY[0] + 25;
            if(snakeY[0] > 650){    //邊界判斷
                snakeY[0] = 75;
            }
        }

        repaint();  //重畫頁面
    }
    timer.start();  //定時器開啟
}

9. 繪制食物佈局

先是創建食物的坐標,

//食物的坐標
int foodX;
int foodY;

在初始化方法中添加如下語句,隨機產生食物的位置,

//把食物隨機分佈在界面上
foodX = 25 + 25*random.nextInt(34);
foodY = 75 + 25*random.nextInt(24);

在繪制面板方法paintComponent(Graphics g)中,將食物畫上去,

Data.food.paintIcon(this,g,foodX,foodY);

再在事件監聽actionPerformed(ActionEvent e)中,將小蛇吃瞭食物會使身體變長的語句寫上去,

//吃食物
if (snakeX[0] == foodX && snakeY[0] == foodY){
    length++;   //小蛇身體長度增加一節
    //再次隨機分配食物
    foodX = 25 + 25*random.nextInt(34);
    foodY = 75 + 25*random.nextInt(24);
}

10. 遊戲失敗判定

先設置一個失敗標志,

//遊戲失敗判定
boolean isFail = false;     //遊戲失敗狀態

然後在繪制畫板paintComponent(Graphics g)中設置一個失敗回顯,

 if (isFail){
    g.setColor(Color.RED);
    g.setFont(new Font("微軟雅黑",Font.BOLD,40));   //設置字體
    g.drawString("遊戲結束,按下空格重新開始",300,350);
}

再在鍵盤監聽事件keyPressed(KeyEvent e)裡重寫空格鍵的監聽事件,

 if (keyCode == KeyEvent.VK_SPACE){      //如果按下空格鍵
    if(isFail){
        //重新開始
        isFail = false;
        init();
    }else {
        isStart = !isStart;     //取反
    }
    repaint();
}

然後再在事件監聽actionPerformed(ActionEvent e)中再寫對失敗的判斷,

//失敗判定,撞到自己遊戲結束
for (int i = 1; i < length; i++) {
    if (snakeX[0] == snakeX[i] && snakeY[0] == snakeY[i]){
        isFail = true;
    }
}

11. 積分獲取系統

先定義一個用於存儲積分的對象score,然後在繪制面板paintComponent(Graphics g)中顯示出積分來,

 /**
 * 顯示積分
 */
g.setColor(Color.white);
g.setFont(new Font("微軟雅黑",Font.BOLD,18));   //設置字體
g.drawString("長度: "+length,750,30);
g.drawString("分數: "+score,750,55);

然後重寫事件監聽actionPerformed(ActionEvent e)裡的吃食物代碼塊,

//吃食物
if (snakeX[0] == foodX && snakeY[0] == foodY){
    //小蛇身體長度增加一節
    length++;
    //一個食物加十點積分
    score += 10;
    //再次隨機分配食物
    foodX = 25 + 25*random.nextInt(34);
    foodY = 75 + 25*random.nextInt(24);
}

12. 遊戲優化

移動優化

對蛇頭的移動進行瞭優化,避免瞭蛇頭與第一節蛇身的碰撞,即如果蛇頭向右前進,這時候按向左是無效的,

/**
 * 小蛇移動
 */
if(keyCode == KeyEvent.VK_UP && !fx.equals("D")){
    fx = "U";
}else if (keyCode == KeyEvent.VK_DOWN && !fx.equals("U")){
    fx = "D";
}else if (keyCode == KeyEvent.VK_LEFT && !fx.equals("R")){
    fx = "L";
}else if (keyCode == KeyEvent.VK_RIGHT && !fx.equals("L")){
    fx = "R";
}

速度優化

隨著蛇身越來越長,小蛇移動速度會越來越快,這裡蛇身每增加5節,速度提升一個等級,

//判斷是否吃到食物
boolean foodEat = false;

//蛇身越長,蛇的移動速度越快
if (foodEat == true && length % 5 ==0 && foodColor.equals("Blue")){
    grade++;
}
timer.setDelay(150 -  grade*10);

食物優化

避免食物的位置與蛇身的位置重疊,而造成食物被蛇身所覆蓋,

因此修改原先的食物分配佈局,加入判定代碼塊,

//判斷食物是否與蛇身重疊
boolean flag = false;       //默認為重疊狀態

//把食物隨機分佈在界面上
while (flag == false){
    flag = true;
    foodX = 25 + 25*random.nextInt(34);
    foodY = 75 + 25*random.nextInt(24);
    for (int i = 1; i < length; i++) {
        if(foodX == snakeX[i] && foodY == snakeY[i]){
            flag = false;
        }
    }
}

對食物的種類進行多樣化,每種顏色代表不同的功能,其中,

藍色:增加一節蛇的身體,分數+10 綠色:減少一節蛇的身體,分數+10 紫色:加快蛇的移動速度,分數+10 橘色:減慢蛇的移動速度,分數+10

通過隨機數對食物種類進行分配,其中,

藍色:[0.1,0.85) 綠色:[0.85,0.95) 且蛇的長度length>=2 紫色:[0,0.1) 且timerDelay值>=80 橘色:[0.95,1) 且timerDelay值<=100

 //食物的種類
String foodColor;
boolean foodFlag = false;
public static URL foodURL;
public static ImageIcon food;

foodFlag = false;
while (foodFlag == false){
    double num = random.nextDouble();
    if(0.1 <= num && num < 0.85){
        foodURL = GamePanel.class.getResource("statics/foodB.png");
        foodColor = "Blue";
        foodFlag = true;
        break;
    }else if (0.85 <= num && num < 0.95 && length >= 2){
        foodURL = GamePanel.class.getResource("statics/foodG.png");
        foodColor = "Green";
        foodFlag = true;
        break;
    }else if (0.0 <= num && num < 0.1 && timer.getDelay() >= 90){
        foodURL = GamePanel.class.getResource("statics/foodP.png");
        foodColor = "Purple";
        foodFlag = true;
        break;
    }else if (0.95 <= num && num < 1.0 && timer.getDelay() <= 130){
        foodURL = GamePanel.class.getResource("statics/foodO.png");
        foodColor = "Orange";
        foodFlag = true;
        break;
    }
}
food = new ImageIcon(foodURL);

if (foodColor.equals("Blue")){
    //小蛇身體長度增加一節
    length++;
}else if (foodColor.equals("Green")){
    //如果蛇身長度正好是5的倍數會進行降速處理
    if (length % 5 ==0){
        grade--;
    }
    //小蛇身體長度減少一節
    length--;
}else if (foodColor.equals("Purple")){
    //小蛇移動速度加快
    grade++;
}else if (foodColor.equals("Orange")){
    //小蛇移動速度加快
    grade--;
}

以上就是Java實現貪吃蛇遊戲的示例代碼的詳細內容,更多關於Java貪吃蛇的資料請關註WalkonNet其它相關文章!

推薦閱讀: