Java實現貪吃蛇小遊戲(附完整源碼)

今天我就從零開始來完成這個小遊戲,完成的方式也是一步一步的添加功能這樣的方式來實現。

第一步完成的功能:寫一個界面
大家見到的貪吃蛇小遊戲,界面肯定是少不了的。因此,第一步就是寫一個小界面。

實現代碼如下:

public class SnakeFrame extends Frame{
    //方格的寬度和長度
    public static final int BLOCK_WIDTH = 15 ;
    public static final int BLOCK_HEIGHT = 15 ;
    //界面的方格的行數和列數
    public static final int ROW = 40;
    public static final int COL = 40;
    public static void main(String[] args) {
        new SnakeFrame().launch();
    }

    public void launch(){

        this.setTitle("Snake");
        this.setSize(ROW*BLOCK_HEIGHT, COL*BLOCK_WIDTH);
        this.setLocation(300, 400);
        this.addWindowListener(new WindowAdapter() {

            @Override
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }

        });
        this.setResizable(false);
        this.setVisible(true);
    }

}

第二步完成的功能:在界面上畫成一格一格的
我們見過的貪吃蛇遊戲,是有一個格子一個格子構成,然後蛇在這個裏面運動。

重寫paint方法,單元格就是橫着畫幾條線豎着畫幾條線即可。

代碼如下:

@Override
public void paint(Graphics g) {
    Color c = g.getColor();
    g.setColor(Color.GRAY);
    /*
     * 將界面畫成由ROW*COL的方格構成,兩個for循環即可解決
     * */
    for(int i = 0;i<ROW;i++){
        g.drawLine(0, i*BLOCK_HEIGHT, COL*BLOCK_WIDTH,i*BLOCK_HEIGHT );
    }
    for(int i=0;i<COL;i++){
        g.drawLine(i*BLOCK_WIDTH, 0 , i*BLOCK_WIDTH ,ROW*BLOCK_HEIGHT);
    }

    g.setColor(c);
}

效果如下:
在這裏插入圖片描述
第三步完成的功能:建立另外的線程來控制重畫
由於,蛇的運動就是改變蛇所在的位置,然後進行重畫,就是我們所看到的運動。因此,在這裏,我們單獨用一個線程來控制重畫。

private class MyPaintThread implements Runnable{

    @Override
    public void run() {
        //每隔50ms重畫一次
        while(true){
            repaint();//會自動調用paint方法
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

}

2、在SnakeFrame的launchFrame方法中添加代碼:new Thread(new MyPaintThread()).start();即可。

完成功能:利用雙緩衝來解決閃爍的問題

private Image offScreenImage = null;
/*
 * 重寫update方法
 * */
@Override
public void update(Graphics g) {
    if(offScreenImage==null){
        offScreenImage = this.createImage(ROW*BLOCK_HEIGHT, COL*BLOCK_WIDTH);
    }
    Graphics offg = offScreenImage.getGraphics();
    //先將內容畫在虛擬畫布上
    paint(offg);
    //然後將虛擬畫布上的內容一起畫在畫布上
    g.drawImage(offScreenImage, 0, 0, null);
}

第四步完成的功能:在界面上畫一個蛇出來
貪吃蛇遊戲中的蛇就是用一系列的點來表示,這裏我們來模擬一個鏈表。鏈表上的每個元素代表一個節點。
首先,我們先新建一個Node類來表示構成蛇的節點,用面向對象的思想,發現,這個類應該有如下的屬性和方法:
1、位置
2、大小,即長度、寬度
3、方向
4、構造方法
5、draw方法
Node類的代碼如下:

public class Node {

    private static final int BLOCK_WIDTH = SnakeFrame.BLOCK_WIDTH;
    private static final int BLOCK_HEIGHT = SnakeFrame.BLOCK_HEIGHT;
    /*
     * 每個節點的位置
     * */
    private int row;
    private int col;
    //方向
    private Direction dir ;

    private Node pre;
    private Node next;

    public Node(int row, int col, Direction dir) {
        this.row = row;
        this.col = col;
        this.dir = dir;
    }

    public void draw(Graphics g){
        Color c = g.getColor();
        g.setColor(Color.BLACK);
        g.fillRect(col*BLOCK_WIDTH, row*BLOCK_HEIGHT, BLOCK_WIDTH, BLOCK_HEIGHT);
        g.setColor(c);      
    }
}

Direction是一個enum,具體如下:

public enum Direction {
    L,U,R,D
}

而在Snake類中,用面向對象的思維,可以發現,Snake類中應該有如下的屬性和方法

1、頭結點

2、尾結點

3、構造函數

3、draw方法

具體代碼如下:

public class Snake {

    private Node head = null;
    private Node tail = null;   

    private SnakeFrame sf;
    //初始化是蛇的位置
    private Node node = new Node(3,4,Direction.D);

    private int size = 0;
    public Snake(SnakeFrame sf) {
        head = node;
        tail = node;
        size ++;
        this.sf = sf ;      
    }

    public void draw(Graphics g){
        if(head==null){
            return ;
        }
        for(Node node = head;node!=null;node = node.next){
            node.draw(g);
        }   
    }


}

在SnakeFrame類中new一個Snake對象,然後調用Snake對象的draw方法即可。

效果如下:
在這裏插入圖片描述
第五步完成的功能:通過鍵盤控制蛇的上下左右移動
首先想到的是這樣:在Snake類中添加一個keyPressed方法,然後在SnakeFrame的鍵盤事件中調用Snake對象的keyPressed方法。
注意:蛇的移動是通過在頭部添加一個單元格,在尾部刪除一個單元格這樣的思想來實現。
具體如下:
Snake類中添加一個keyPressed方法,主要是根據鍵盤的上下左右鍵來確定蛇的頭結點的方向,然後move方法再根據頭結點的方向來在頭部添加一個單元格。

public void keyPressed(KeyEvent e) {
    int key = e.getKeyCode();
    switch(key){
    case KeyEvent.VK_LEFT :
        if(head.dir!=Direction.R){
            head.dir = Direction.L;
        }
        break;
    case KeyEvent.VK_UP :
        if(head.dir!=Direction.D){
            head.dir = Direction.U;
        }
        break;
    case KeyEvent.VK_RIGHT :
        if(head.dir!=Direction.L){
            head.dir = Direction.R;
        }
        break;
    case KeyEvent.VK_DOWN :
        if(head.dir!=Direction.U){
            head.dir = Direction.D;
        }
        break;
    }
}

public void move() {
    addNodeInHead();
    deleteNodeInTail();
}

private void deleteNodeInTail() {
    Node node = tail.pre;
    tail = null;
    node.next = null;
    tail = node;
}

private void addNodeInHead() {
    Node node = null;
    switch(head.dir){
    case L:
        node = new Node(head.row,head.col-1,head.dir);
        break;
    case U:
        node = new Node(head.row-1,head.col,head.dir);
        break;
    case R:
        node = new Node(head.row,head.col+1,head.dir);
        break;
    case D:
        node = new Node(head.row+1,head.col,head.dir);
        break;
    }

    node.next = head;
    head.pre = node;
    head = node;

}
//最後,在draw中調用move方法即可
public void draw(Graphics g){
    if(head==null){
        return ;
    }
    move();
    for(Node node = head;node!=null;node = node.next){
        node.draw(g);
    }   
}

這樣就實現了通過鍵盤來實現蛇的移動。

完成的功能:蛇吃蛋

首先我們新建一個蛋Egg的類。

類的屬性和方法有:

1、位置、大小

2、構造方法

3、draw方法

4、getRect方法:用於碰撞檢測

5、reAppear方法:用於重新產生蛋的方法

代碼如下:

public class Egg {
    //所在的位置
    private int row;
    private int col;
    //大小
    private static final int BLOCK_WIDTH = SnakeFrame.BLOCK_WIDTH;
    private static final int BLOCK_HEIGHT = SnakeFrame.BLOCK_HEIGHT;

    private static final Random r = new Random();

    private Color color = Color.RED;

    public Egg(int row, int col) {
        this.row = row;
        this.col = col;
    }

    public Egg() {
        this((r.nextInt(SnakeFrame.ROW-2))+2,(r.nextInt(SnakeFrame.COL-2))+2);
    }
    /*
     * 改變當前對象的位置,即完成蛋的重現
     * */
    public void reAppear(){
        this.row = (r.nextInt(SnakeFrame.ROW-2))+2;
        this.col = (r.nextInt(SnakeFrame.COL-2))+2;
    } 

    public void draw(Graphics g){
        Color c= g.getColor();
        g.setColor(color);
        g.fillOval(col*BLOCK_WIDTH, row*BLOCK_HEIGHT, BLOCK_WIDTH, BLOCK_HEIGHT);
        g.setColor(c);
        //改變下一次的顏色
        if(color==Color.RED){
            color = Color.BLUE;
        }
        else{
            color = Color.RED;
        }

    }
    //用於碰撞檢測
    public Rectangle getRect(){
        return new Rectangle(col*BLOCK_WIDTH, row*BLOCK_HEIGHT, BLOCK_WIDTH, BLOCK_HEIGHT);
    }

}

蛇吃蛋,怎麼樣才能判斷蛇吃到蛋了呢,這就需要用到碰撞檢測了。

這裏我們在Snake類中添加一個eatEgg方法。當蛇吃到蛋之後,就需要將蛇的長度+1,這裏處理的是在蛇的頭部添加一個節點,當蛋被吃掉之後,就需要再重新隨機產生一個蛋。

代碼如下:

public Rectangle getRect(){
    return new Rectangle(head.col*BLOCK_WIDTH, head.row*BLOCK_HEIGHT, BLOCK_WIDTH, BLOCK_HEIGHT);
}

public boolean eatEgg(Egg egg){

    if(this.getRect().intersects(egg.getRect())){
        addNodeInHead();
        egg.reAppear();
        return true;
    }
    else{
        return false;
    }
}

以上就完成了蛇吃蛋的功能。

完成的功能:添加邊界處理

在我們熟悉的貪吃蛇遊戲中,我們一般都知道,當蛇撞到牆或者是撞到自己身體的某一部分,則遊戲就結束。下面我們就來實現這一功能。

在Snake類中,添加checkDead方法

private void checkDead() {
    //頭結點的邊界檢查
    if(head.row<2||head.row>SnakeFrame.ROW||head.col<0||head.col>SnakeFrame.COL){
        this.sf.gameOver();
    }

    //頭結點與其它結點相撞也是死忙
    for(Node node =head.next;node!=null;node = node.next){
        if(head.row==node.row&&head.col == node.col){
            this.sf.gameOver();
        }
    }
}

如果蛇撞牆或是撞到自己本身的某一個部分。則調用SnakeFrame類中的gameOver()方法來進行一定的處理。

本遊戲的處理方法爲:通過設置一個boolean 變量,來停止遊戲並提示相關信息。

具體代碼如下:

private boolean b_gameOver = false;

public void gameOver(){
    b_gameOver = true;
}

@Override
public void update(Graphics g) {
    //其它代碼省略
    if(b_gameOver){
        g.drawString("遊戲結束!!!", ROW/2*BLOCK_HEIGHT, COL/2*BLOCK_WIDTH);
    }

}

以上就完成了蛇是否撞牆或是撞到自身一部分的功能。
小結
以上基本上實現了貪吃蛇的基本功能。剩下的一些功能不再介紹,例如:添加得分記錄、通過鍵盤某按鍵來控制遊戲的停止、重新開始、再來一局等。
以上的功能雖然沒有介紹,但是在代碼中,我有實現這些相應的功能。
完整代碼可以在這裏獲取:https://github.com/wojiushimogui/Snake

我有一個微信公衆號,經常會分享一些Java技術相關的乾貨;如果你喜歡我的分享,可以用微信搜索“java新手入門”關注。
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章