貪吃蛇的java代碼分析(三)


  • 代碼剖析

在上一篇文章中,我們完成了貪吃蛇部分代碼的構造。回頭審視我們寫的代碼與思路,會發現我們遺漏了一個重要的地方,那就是:貪吃蛇的自身移動。想必大家都知道,貪吃蛇自身是會自己移動的,並且會跟隨你的方向來不斷移動。我們需要在代碼中來體現這個功能,那么如何體現呢?查閱API,我們發現了一個TIMER類。API中的描述是:在指定時間間隔觸發一個或多個ActionEvent,一個實例用法就是動畫對象,它將Timer用作繪制其幀 的觸發器。Timer的構造方法是Timer(int delay, ActionListner listener)通俗的說就是創建一個每 delay秒觸發一次動作的計時器,每隔特定的時間就會觸發特定的事件。可以使用start方法啟動計時器。

這個Timer類可以完全滿足我們的需要。我們只要定義一個Timer類,設置好間隔時間與觸發事件就可以了。這里要注意,我們要定義的觸發事件是蛇自身的移動,那么肯定要使用到Move類(在第二篇分析中實現),也就是說們還需要傳遞一個direction,傳遞一個方向。那么這個方向該如何傳遞呢?

貪吃蛇自身的移動有規律可循:一開始,朝固定的某個方向移動;隨着我們的操控,貪吃蛇的移動也隨之發生改變。也就是說,他有一個自有的固定的DIRECTION,之后隨着我們的操控Direction也不斷發生改變,借此來改變它自身不斷移動的方向。用代碼來體現,就是在成員變量處定義一個Direction,我們將其初始化為1,這樣在Timer的事件觸發后,Move()的參數為1,就會不斷的向上移動。在鍵盤的監聽事件中,將direction的值賦值給Direction,那么隨着我們上下左右的控制,Direction的值也不斷發生改變,貪吃蛇的自身移動方向就會發生變化。用代碼體現:

public class mainMap extends JPanel {//在成員變量中定義一個Direction private final int width = 20; private final int length = 30; private final int unit = 20; private ArrayList<snakeNode> snake = new ArrayList<>(); private snakeNode newNode = new snakeNode(0,0,Color.WHITE); private int Length; private int Direction = 1; Timer time = new Timer(1000, new ThingsListener());//定義一個定時器對象,這里我們還要創建一個ThingsListener事件 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
this.addKeyListener(new KeyAdaper() { public void KeyPressed(KeyEvent e) { int direction = 0; switch(e.getKeyCode()) { case KeyEvent.VK_UP: direction = 1; break; case KeyEvent.VK_DOWN: direction = -1; break; case KeyEvent.VK_LEFT: direction = 2; break; case KeyEvent.VK_RIGHT: direction = -2; break; default: break; } if(Direction + direction !=0) {//此處的意義是Direction的方向不能與你的方向相反,你不能掉頭 Direction = direction;//將鍵盤監控的值傳遞給Direction,這樣貪吃蛇定時向玩家操控的方向移動 Move(direction); } } }); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
public class ThingsListener implements ActionListener { public void actionPerformed(ActionEvent e) { Move(Direction); } }//這里是自己新建一個事件處理,每隔Timer的時間間隔,就開始移動Directon的位置,由因為Direction的位置是構造方法中定義好的,所以就會自動地移動方向。而每當玩家使用鍵盤時,Direction的值變化,之后每次自動移動的方向也隨之變化。
  • 1
  • 2
  • 3
  • 4
  • 5

目前為止我們已經完成了絕大多數的代碼編寫,我們還要再完成一個步驟:貪吃蛇吃東西的功能。貪吃蛇要想吃東西,首先它的第一個元素就必須觸碰到隨機點,也就是說當貪吃蛇的第一個點與隨機點的坐標相同時,就啟動吃東西的功能。代碼體現:

public void Move(int direction) {//這是移動蛇身的方法 int firstX = snake.get(0).getX(); int firstY = snake.get(0).getY(); switch(direction) { case 1: firstY--; break; case -1: firstY++; break; case 2: firstX--; break; case -2: firstX++; break; default: break; } if(firstX == newNode.getX()&&firstY == newNode.getY()) {//當第一個元素的坐標與隨機點的坐標相同時,就啟動eat()方法,並且退出Move()方法 eat(); return; } for(int x = 0; x < Length; x++) { if(snake.get(x).getX()==firstX&&snake.get(x).getY()==firstY) { Dead("不好意思,您碰到自己啦~~~~!!!!"); } } if(firstX < 0 || firstX > width - 1 || firstY < 0 || firstY > length -1) { Dead("不好意思,您撞牆啦"); } for(int x = Length - 1; x >0; x--) { snake.get(x).setX(snake.get(x-1).getX()); snake.get(x).setY(snake.get(x-1).getY()); } snake.get(0).setX(firstX); snake.get(0).setY(firstY); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

接下來我們需要實現eat()方法。其實這個方法的思路很簡單,就是往集合中新添加一個元素,然后將蛇身中每一個元素的坐標向前進一位。稍加思索,我們就可以理解:除了集合的第一個元素,集合的剩余元素就是前一個集合的排列,無論順序,坐標還有顏色都相同。我們在把隨機點的坐標賦給集合的第一個元素,那么集合的吞吃功能就完成了,吃掉的點變成了蛇頭。代碼體現:

public void eat() {
    snake.add(new snakeNode());//往集合中新增加一個元素,不用具體賦值 Length++; for(int x = Length-1; x >0; x--) { snake.get(x).setX(snake.get(x-1).getX()); snake.get(x).setY(snake.get(x-1).getY()); snake.get(x).setColor(snake.get(x-1).getColor());//變化坐標時,顏色也要進行變換,這樣順序才能一致 } snake.get(0).setX(newNode.getX()); snake.get(0).setY(newNode.getY()); snake.get(0).setColor(newNode.getColor()); CreateNode();//吞吃完畢后要繼續創造新的隨機點,讓游戲得以繼續 } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

這里有一個細節問題要注意。在Move()方法匹配調用eat()方法后,一定要使用return退出Move()方法。因為如果不退出,那么eat()方法會把隨機點的坐標賦值給蛇頭,然后程序會繼續運行。當運行到if語句查看是否撞到自己的方法時,由於之前定義的firstX與firstY的值與隨機點的值相同,那么蛇頭的第一個元素的值也就與firstX與firstY的值相同,這就會符合if語句的條件,導致出現不必要的錯誤。這是一個很隱蔽的錯誤,我之前在這里卡主了很長時間,希望大家能好好理解這一點。之前我們還定義過一個Dead方法,用於在游戲結束時彈出相關界面。這個方法相對而言比較簡單,我直接貼出代碼:

public void Dead(String str) {//彈出當前的時間,並提示游戲結束 Date date = new Date(); SimpleDateFormat sd = new SimpleDateFormat(); String str2 = sd.format(date); String str3 = str + "\n" + "很遺憾,游戲要結束了~~~"; JOptionPane.showMessageDialog(this, str2 + "\n" + str3 ); System.exit(0); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

目前來說我們已經完成了全部的貪吃蛇代碼,是不是很簡單?一開始做的時候沒有什么思路,但隨着我們一步一步分析,整個項目的流程也就非常清晰。最后我們還要完善兩個方面,第一個是每當我們的集合發生位置移動時,我們需要調用repaint()方法進行重繪,防止出現坐標變化時的殘留現象。在我們的代碼要對集合中元素的坐標產生改變時,就調用repaint()方法進行重繪,防止可能出現的殘留或者閃爍現象。 
第二點就是我們目前只是在坐標軸上進行移動,無法直接在圖案上觀測到。如何畫出貪吃蛇的圖形?這里就要用到java繪圖類——paint()方法。 
java中任何一個圖形界面,都需要paint函數來負責專門顯現。paint()方法一般由父類自動維護,一旦子類重寫,子類就必須自己完成所有的界面顯示工作。paint()有三個受保護的方法,我們因為是要繪制組件,所以調用PaintComponent()方法即可。具體的繪制思路就是以每一個snakeNode為圓心,成員變量中定義的unit為半徑畫園,將貪吃蛇的圖形全部繪制出來。之后再以width,length,兩者乘以unit來做一個矩形。因為集合中元素與隨機產生的元素都在width與length的限制中,所以當繪制的圓碰到繪制的邊框時,就代表着集合中的元素與邊框(width,length)產生了交界,到達了邊界值,在移動就會超出邊界,游戲也就會失敗。

protected void paintComponent(Graphics g) {
        super.paintComponent(g);//調用super是因為文中調用了repaint方法,需要每一次都清空再進行重繪 g.setColor(newNode.getColor()); g.fillOval(newNode.getX()*unit, newNode.getY()*unit, unit, unit); g.setColor(newNode.getColor()); g.drawRect(0, 0, width*unit, length*unit); for(int x = 0; x < Length; x++) { g.setColor(snake.get(x).getColor()); g.fillOval(snake.get(x).getX()*unit, snake.get(x).getY()*unit, unit, unit); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

最后給出完整的實現代碼:

package game; import java.awt.Color; public class SnakeNode {//定義蛇身集合中的各個元素點 private int x; private int y; private Color color; public SnakeNode() { super(); } public SnakeNode(int x, int y, Color color) { super(); this.x = x; this.y = y; this.color = color; } public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } public Color getColor() { return color; } public void setColor(Color color) { this.color = color; } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
package game;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.Random; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.Timer; public class MainGame extends JPanel{ private final int length = 20;//定義活動范圍 private final int width = 30;//定義活動范圍 private final int unit = 20;//定義單位長度 private ArrayList<SnakeNode> snake = new ArrayList<>();//定義蛇身的集合 private int Direction;//定義蛇頭的方向 private int Length ;//定義蛇身的長度 private SnakeNode newNode = new SnakeNode(1,1,Color.BLACK);//定義隨機點 Timer time = new Timer(1000,new ThingsListener()); public MainGame() {//初始化各項數據與方法 snake.add(new SnakeNode(width/2,length/2,Color.GREEN)); snake.add(new SnakeNode(width/2,length/2+1,Color.BLUE)); snake.add(new SnakeNode(width/2,length/2+2,Color.RED)); Direction = 1;//定義初始方向為向上 Length = 3;//蛇身長度為3 CreateNode();//產生隨機點 time.start(); this.addKeyListener(new KeyAdapter() {//捕捉鍵盤的按鍵事件 public void keyPressed(KeyEvent e) { int direction = 0;//定義一個按下按鈕后要去的方向 switch(e.getKeyCode()) { case KeyEvent.VK_UP://按下向上,返回1 direction = 1; break; case KeyEvent.VK_DOWN://按下向下,返回-1 direction = -1; break; case KeyEvent.VK_LEFT://按下相左,返回2 direction = 2; break; case KeyEvent.VK_RIGHT://按下向右,返回-2 direction = -2; break; default: break; } if(direction + Direction !=0) {//不能反向運動 Direction = direction; Move(direction); repaint(); } } }); } public void Move(int direction) {//定義蛇身移動的方法 int FirstX = snake.get(0).getX();//獲取蛇第一個點 int FirstY = snake.get(0).getY();//獲取蛇第二個點 switch(direction) { case 1: FirstY--; break; case -1: FirstY++; break; case 2: FirstX--; break; case -2: FirstX++; break; default: break; } if(FirstX == newNode.getX()&&FirstY == newNode.getY()) {//當碰到隨機點時 getNode(); return; } for(int x = 0; x < Length; x++) {//當碰到蛇身自己時 if((FirstX==snake.get(x).getX())&&(FirstY == snake.get(x).getY())) { Dead("你碰到自己啦~~~"); } } if(FirstX < 0 || FirstX > width-1 || FirstY < 0 || FirstY > length -1) { Dead("菜雞,你撞牆啦~~~~~"); } for(int x = Length - 1; x > 0; x--) { snake.get(x).setX(snake.get(x-1).getX()); snake.get(x).setY(snake.get(x-1).getY()); } snake.get(0).setX(FirstX); snake.get(0).setY(FirstY); repaint(); } public void getNode() { snake.add(new SnakeNode()); Length++; for(int x = Length-1; x >0; x--) { snake.get(x).setX(snake.get(x-1).getX()); snake.get(x).setY(snake.get(x-1).getY()); snake.get(x).setColor(snake.get(x-1).getColor()); } snake.get(0).setX(newNode.getX()); snake.get(0).setY(newNode.getY()); snake.get(0).setColor(newNode.getColor()); CreateNode(); repaint(); } public void Dead(String s) { Date date = new Date(); SimpleDateFormat sd = new SimpleDateFormat(); String str2 = sd.format(date); String str = s +"\n" +"所以說游戲不得已將結束了"; JOptionPane.showMessageDialog(this, str2 + "\n" + str ); System.exit(0); } public void CreateNode() {//創造隨機點的方法 int newX = 0; int newY = 0; Boolean flag = true; while(flag) { newX = new Random().nextInt(width); newY = new Random().nextInt(length); for(int i = 0; i < Length; i++) { if(snake.get(i).getX()==newX && snake.get(i).getY()==newY) { flag = true; break; } flag= false; } } Color color = new Color(new Random().nextInt(255),new Random().nextInt(255),new Random().nextInt(255)); newNode.setX(newX); newNode.setY(newY); newNode.setColor(color); this.setBackground(new Color(new Random().nextInt(255),new Random().nextInt(255),new Random().nextInt(255)));//這里給畫板的背景換隨機色 } class ThingsListener implements ActionListener {//設置一個監聽器事件 public void actionPerformed(ActionEvent e) { Move(Direction); repaint(); } } protected void paintComponent(Graphics g) { super.paintComponent(g); g.setColor(newNode.getColor()); g.fillOval(newNode.getX()*unit, newNode.getY()*unit, unit, unit); g.setColor(newNode.getColor()); g.drawRect(0, 0, width*unit, length*unit); for(int x = 0; x < Length; x++) { g.setColor(snake.get(x).getColor()); g.fillOval(snake.get(x).getX()*unit, snake.get(x).getY()*unit, unit, unit); } } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
package game; import java.awt.Color; import javax.swing.JFrame; public class Test { public static void main(String[] args) { JFrame frame = new JFrame("貪吃蛇————————————made by chenjiaheng"); frame.setBounds(0,0,800,500); MainGame sn = new MainGame(); frame.add(sn); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); sn.requestFocus(); } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

寫到這里,整個貪吃蛇的項目,包括思路,實現,代碼都算是完成了。其實代碼如何實現並不困難,重要的是如何通過方法和技巧將大問題分解成一個一個小問題,最后再加以解決。在這里完成的只是貪吃蛇的基本功能,在以后自己可能會繼續實現更多的功能,包括插入圖片,加入排行榜,記錄個數等功能。 
從0到1,從無到有,希望自己的文章能給各位朋友帶來幫助。如果有什么好的想法和思路,自己也會繼續在博客中和大家分享。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM