貪吃蛇小游戲—手把手教你會你寫貪吃蛇(java)
? ? 首先說一下,經(jīng)典游戲貪吃蛇–原理很簡單不羅嗦,我們是來看東西的。
? ? 游戲需要一個容器來承載,其次是我們需要為游戲準(zhǔn)備一些必要的東西,貪吃蛇首先的要有蛇–準(zhǔn)備蛇的圖片素材,為了便于開發(fā)建議蛇的每個部位的圖片大小一樣;
? ? 1,準(zhǔn)備好上述素材之后開始創(chuàng)建容器–Jframe
看代碼–
package com.snake;
import javax.swing.*;
import java.awt.*;
/**
* @Auther: GavinLim
* @Date: 2021/7/5 - 07 - 05 - 8:27
* @Description: com.snake
* @version: 1.0
*/
public class StartGame {
? ? public static void main(String[] args) {
? ? ? ? //創(chuàng)建窗體
? ? ? ? JFrame jFrame= new JFrame();
? ? ? ? //設(shè)置窗口標(biāo)題
? ? ? ? jFrame.setTitle("貪吃蛇游戲? designed by Gavin");
? ? ? ? //設(shè)置窗體大小,由于要考慮到不同的屏幕尺寸,所以首先要獲得運行時電腦的屏幕參數(shù)
? ? ? ? int? height =Toolkit.getDefaultToolkit().getScreenSize().height;
? ? ? ? int width =Toolkit.getDefaultToolkit().getScreenSize().width;
? ? ? ? jFrame.setBounds((width-800)/2,(height-800)/2,800,800);
? ? ? ? //設(shè)置窗體大小不可調(diào)節(jié)
? ? ? ? jFrame.setResizable(false);
? ? ? ? //默認(rèn)窗體不可見,設(shè)置成可見的
? ? ? ? jFrame.setVisible(true);
? ? }
}
但是關(guān)閉窗體后程序并沒有結(jié)束

————————————————
我們想要的是–程序關(guān)閉后游戲即退出–不然占內(nèi)存呀?。?/p>
完善之后的代碼—
package com.snake;
import javax.swing.*;
import java.awt.*;
public class StartGame {
? ? public static void main(String[] args) {
? ? ? ? //創(chuàng)建窗體
? ? ? ? JFrame jFrame = new JFrame();
? ? ? ? //設(shè)置窗口標(biāo)題
? ? ? ? jFrame.setTitle("貪吃蛇游戲? designed by Gavin");
? ? ? ? //設(shè)置窗體大小,由于要考慮到不同的屏幕尺寸,所以首先要獲得運行時電腦的屏幕參數(shù)
? ? ? ? int height = Toolkit.getDefaultToolkit().getScreenSize().height;
? ? ? ? int width = Toolkit.getDefaultToolkit().getScreenSize().width;
? ? ? ? jFrame.setBounds((width - 800) / 2, (height - 800) / 2, 800, 800);
? ? ? ? //設(shè)置窗體大小不可調(diào)節(jié)
? ? ? ? jFrame.setResizable(false);
? ? ? ? //設(shè)置窗體關(guān)閉游戲退出
? ? ? ? jFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
? ? ? ? //默認(rèn)窗體不可見,設(shè)置成可見的
? ? ? ? jFrame.setVisible(true);
? ? }
}
? ?
? ? ,2,接下來—封裝小蛇—這是一種思想,如果要把圖片、文件等加載到程序中就要將他封裝成具體的對象,封裝是先通過封裝地址,在通過地址封裝為具體對象的;封裝之前先導(dǎo)入素材–可以單獨創(chuàng)建一個包用來存放素材;

————————————————
這里是用一個包專門來放素材的—方便查找;
封裝之前我們要明白代碼是經(jīng)過編譯之后運行的,所以我們存放素材的地址并不是編譯之后的地址–做一個測試類–
import java.net.URL;
/**
* @Auther: GavinLim
* @Date: 2021/7/5 - 07 - 05 - 8:45
* @Description: com.snake
* @version: 1.0
*/
public class UrlTest {
? ? public static void main(String[] args) {
? ? ? ? URL headerUrl=SnakeImages.class.getResource("/");
? ? ? ? System.out.println(headerUrl);
? ? }
}
執(zhí)行結(jié)果如下—
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8
file:/C:/Users/Administrator/IdeaProjects/SnakeEatGame/out/production/SnakeEatGame/
?
所以在封裝之前我們要預(yù)編譯一下看圖片被加載到哪里了;

預(yù)編譯之后我們會發(fā)存放class文件的out文件夾下多了一個文件夾—
這個snakeImages就是存放圖片的位置;

————————————————
圖片編譯前存放位置(絕對路徑)
C:\Users\Administrator\IdeaProjects\SnakeEatGame\src\snakeImage\header.png
圖片編譯后存放位置(絕對路徑)
C:/Users/Administrator/IdeaProjects/SnakeEatGame/out/production/SnakeEatGame/
對比后我們發(fā)現(xiàn)的確不一樣,由于運行時看的是編譯后的地址,所以要先找地址而不能直接封裝圖片。
? ? 3,接下來開始封裝—
package com.snake;
import javax.swing.*;
import java.awt.*;
import java.net.URL;
import java.sql.SQLOutput;
/**
* @Auther: GavinLim
* @Date: 2021/7/5 - 07 - 05 - 8:44
* @Description: com.snake
* @version: 1.0
*/
public class SnakeImages {
? ? public static URL headerUrl = SnakeImages.class.getResource("/snakeImage/header.png");
? ? public static ImageIcon headerImg = new ImageIcon(headerUrl);
? ? public static URL upUrl = SnakeImages.class.getResource("/snakeImage/up.png");
? ? public static ImageIcon upImg = new ImageIcon(upUrl);
? ? public static URL downUrl = SnakeImages.class.getResource("/snakeImage/down.png");
? ? public static ImageIcon downImg = new ImageIcon(downUrl);
? ? public static URL rightUrl = SnakeImages.class.getResource("/snakeImage/right.png");
? ? public static ImageIcon rightImg = new ImageIcon(rightUrl);
? ? public static URL leftUrl = SnakeImages.class.getResource("/snakeImage/left.png");
? ? public static ImageIcon leftImg = new ImageIcon(leftUrl);
? ? public static URL bodyUrl = SnakeImages.class.getResource("/snakeImage/body.png");
? ? public static ImageIcon bodyImg = new ImageIcon(bodyUrl);
? ? public static URL foodUrl = SnakeImages.class.getResource("/snakeImage/food.png");
? ? public static ImageIcon foodImg = new ImageIcon(foodUrl);
}
? ? 4, 封裝完畢之后,要畫一個面板,可以把窗體理解為容器,面板才是游戲運行是的界面–
新建一個面板類–并不是隨便一個類就可以成為面板,需要繼承Jpanel類
面板中定義蛇的廚師位置等信息
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.Random;
/**
* @Auther: GavinLim
* @Date: 2021/7/5 - 07 - 05 - 9:07
* @Description: com.snake
* @version: 1.0
*/
public class GamePanel extends JPanel {
? ? int length;//蛇的長度
? ? //蛇的坐標(biāo)
? ? int snakeX[] = new int[10000];
? ? int snakeY[] = new int[10000];
? ? //蛇頭的方向
? ? String direction;
? ? //游戲是否開始--默認(rèn)沒開始
? ? boolean isStart = false;
? ? //蛇是否死亡---開始時候默認(rèn)活著
? ? boolean isDie = false;
? ? //定時器,用于啟動小蛇
? ? Timer timer;
? ? //定義食物位置
? ? int foodX;
? ? int foodY;
? ? //定義分?jǐn)?shù)
? ? int score;
? ? //定義初始化方法---在構(gòu)造方法中初始化并不太好,所以拿到外面來方便直接調(diào)用;
? ? public void reset() {
? ? ? ? //初始化蛇的長度
? ? ? ? length = 4;
? ? ? ? //初始化蛇的位置
? ? ? ? snakeX[0] = 200;
? ? ? ? snakeY[0] = 275;
? ? ? ? snakeX[1] = 175;
? ? ? ? snakeY[1] = 275;
? ? ? ? snakeX[2] = 150;
? ? ? ? snakeY[2] = 275;
? ? ? ? snakeX[3] = 125;
? ? ? ? snakeY[3] = 275;
? ? ? ? //初始化食物位置
? ? ? ? foodX = 400;
? ? ? ? foodY = 500;
? ? ? ? //初始化蛇頭位置
? ? ? ? direction = "R";
? ? }
?
? ? 5,接下來定義面板構(gòu)造方法–承接上面代碼,構(gòu)造方法中有監(jiān)聽器,同時有判斷的邏輯
? ? public GamePanel() {
? ? ? ? reset();
//程序運行之后要將焦點挪到游戲界面
? ? ? ? this.setFocusable(true);
//加入按鍵監(jiān)聽--
? ? ? ? ? /*public interface KeyListener extends EventListener {
? ? ? ? ? ? KeyListener 是一個接口,實現(xiàn)接口需要全部實現(xiàn)*/
? ? ? /*this.addKeyListener(new KeyListener() {
? ? ? ? ? ? @Override
? ? ? ? ? ? public void keyTyped(KeyEvent e) {
? ? ? ? ? ? }
? ? ? ? ? ? @Override
? ? ? ? ? ? public void keyPressed(KeyEvent e) {
? ? ? ? ? ? }
? ? ? ? ? ? @Override
? ? ? ? ? ? public void keyReleased(KeyEvent e) {
? ? ? ? ? ? }
? ? ? ? });*/
? ? ? ? /*----public abstract class KeyAdapter implements KeyListener {
? ? ? ? KeyAdapter實現(xiàn)了KeyListener接口,同時又是一個抽象類,可以根據(jù)自己需要進(jìn)行覆寫*/
? ? ? ? this.addKeyListener(new KeyAdapter() {
? ? ? ? ? ? @Override
? ? ? ? ? ? public void keyPressed(KeyEvent e) {
? ? ? ? ? ? ? ? super.keyPressed(e);
? ? ? ? ? ? ? ? //通過監(jiān)聽按鍵來控制游戲--每一個按鍵對應(yīng)一個ASCII碼,通過此來判斷用戶按了哪個鍵
? ? ? ? ? ? ? ? int input = e.getKeyCode();
? ? ? ? ? ? ? ? //監(jiān)聽空格以開始游戲
? ? ? ? ? ? ? ? if (input == KeyEvent.VK_SPACE) {
//? ? ? ? ? ? ? ? ? ? 同時要判斷小蛇是否死亡
? ? ? ? ? ? ? ? ? ? if (isDie) {//如果死了,那么恢復(fù)初始狀態(tài)
? ? ? ? ? ? ? ? ? ? ? ? reset();
? ? ? ? ? ? ? ? ? ? ? ? //重新賦予小蛇生命
? ? ? ? ? ? ? ? ? ? ? ? isDie = false;
? ? ? ? ? ? ? ? ? ? } else {//沒死的話,開始--暫停進(jìn)行切換
? ? ? ? ? ? ? ? ? ? ? ? isStart = !isStart;
? ? ? ? ? ? ? ? ? ? ? ? repaint();//重繪
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? //這個監(jiān)聽完畢之后開始監(jiān)聽上下左右箭頭---當(dāng)然可以監(jiān)聽wasd,
? ? ? ? ? ? ? ? if (input == KeyEvent.VK_UP) {
? ? ? ? ? ? ? ? ? ? direction = "U";
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? if (input == KeyEvent.VK_W) {
? ? ? ? ? ? ? ? ? ? direction = "U";
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? if (input == KeyEvent.VK_DOWN) {
? ? ? ? ? ? ? ? ? ? direction = "D";
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? if (input == KeyEvent.VK_S) {
? ? ? ? ? ? ? ? ? ? direction = "D";
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? if (input == KeyEvent.VK_RIGHT) {
? ? ? ? ? ? ? ? ? ? direction = "R";
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? if (input == KeyEvent.VK_D) {
? ? ? ? ? ? ? ? ? ? direction = "R";
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? if (input == KeyEvent.VK_LEFT) {
? ? ? ? ? ? ? ? ? ? direction = "L";
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? if (input == KeyEvent.VK_A) {
? ? ? ? ? ? ? ? ? ? direction = "L";
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
//按鍵釋放--沒有相應(yīng)操作--所以不用動他
? ? ? ? ? ? @Override
? ? ? ? ? ? public void keyReleased(KeyEvent e) {
? ? ? ? ? ? ? ? super.keyReleased(e);
? ? ? ? ? ? }
? ? ? ? });
? ? 6,寫完上述代碼,小蛇還沒有動,這時候需要啟動定時器
? ? ? ? timer = new Timer(200, new ActionListener() {
? ? ? ? ? ? @Override
? ? ? ? ? ? public void actionPerformed(ActionEvent e) {
? ? ? ? ? ? ? ? if (isStart == true && isDie == false) {//游戲開始狀態(tài)
? ? ? ? ? ? ? ? ? ? //蛇動----蛇尾開始動,然后開始蛇頭--俗稱guyong'
? ? ? ? ? ? ? ? ? ? for (int i = length - 1; i > 0; i--) {
? ? ? ? ? ? ? ? ? ? ? ? //后一節(jié)依次往前挪一位
? ? ? ? ? ? ? ? ? ? ? ? snakeX[i] = snakeX[i - 1];
? ? ? ? ? ? ? ? ? ? ? ? snakeY[i] = snakeY[i - 1];
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? //頭部比較復(fù)雜一下,因為要上下左右動
? ? ? ? ? ? ? ? ? ? if ("R".equals(direction)) {
? ? ? ? ? ? ? ? ? ? ? ? snakeX[0] += 25;
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? if ("L".equals(direction)) {
? ? ? ? ? ? ? ? ? ? ? ? snakeX[0] -= 25;
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? if ("U".equals(direction)) {
? ? ? ? ? ? ? ? ? ? ? ? snakeY[0] -= 25;
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? if ("D".equals(direction)) {
? ? ? ? ? ? ? ? ? ? ? ? snakeY[0] += 25;
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? //防止越界后? ? 回不來了
? ? ? ? ? ? ? ? ? ? if (snakeX[0] > 750) {
? ? ? ? ? ? ? ? ? ? ? ? snakeX[0] = 25;
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? if (snakeX[0] < 25) {
? ? ? ? ? ? ? ? ? ? ? ? snakeX[0] = 750;
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? if (snakeY[0] < 100) {
? ? ? ? ? ? ? ? ? ? ? ? snakeY[0] = 725;
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? if (snakeY[0] > 725) {
? ? ? ? ? ? ? ? ? ? ? ? snakeY[0] = 100;
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? //檢測碰撞動作--吃食物
? ? ? ? ? ? ? ? ? ? //這里一定要注意是25的倍數(shù),不然會哦出現(xiàn)吃不到食物的尷尬
? ? ? ? ? ? ? ? ? ? if (snakeX[0] == foodX && snakeY[0] == foodY) {
? ? ? ? ? ? ? ? ? ? ? ? //吃到食物后蛇產(chǎn)古增加,積分增加
? ? ? ? ? ? ? ? ? ? ? ? length++;
? ? ? ? ? ? ? ? ? ? ? ? //吃到食物后,食物會重新產(chǎn)生--隨機
? ? ? ? ? ? ? ? ? ? ? ? foodX = ((int) (Math.random() * 30) + 1) * 25;
? ? ? ? ? ? ? ? ? ? ? ? foodY = ((new Random().nextInt(26) + 4) * 25);
? ? ? ? ? ? ? ? ? ? ? ? score += 10;
? ? ? ? ? /**蛇長度增加后要馬上修改該節(jié)身子的位置--不然會在游戲左上角,當(dāng)蛇每吃到一顆食物就會閃一下,可以把以下代碼注釋掉試一下,出現(xiàn)的原因是每次增加的時候沒有立馬給定增加的身體部分位置,所以默認(rèn)為位置為 (0,0),加上一下兩行代碼就解決了;*/
? ? ? ? ? ? ? ? ? ? ? ? snakeX[length - 1] = snakeX[length - 2];
? ? ? ? ? ? ? ? ? ? ? ? snakeY[length - 1] = snakeY[length - 2];
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? //判定死亡的方法--蛇頭跟任意一節(jié)身子碰撞就是死亡
? ? ? ? ? ? ? ? ? ? for (int i = 1; i < length; i++) {
? ? ? ? ? ? ? ? ? ? ? ? if (snakeX[0] == snakeX[i] && snakeY[0] == snakeY[i]) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? isDie = true;
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? repaint();//重繪制
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? });
//? ? ? ? 以上寫完小蛇還沒動--因為沒有開啟定時器
? ? ? ? timer.start();
? ? }
?
? ? 7,接下來要不斷繪制圖像界面才能使得人眼看到動的小蛇
? ? @Override//覆寫該方法
? ? public void paintComponent(Graphics g) {
? ? ? ? super.paintComponent(g);//這是一個畫筆
? ? ? ? //設(shè)置游戲區(qū)面板顏色
? ? ? ? this.setBackground(new Color(195, 253, 4));
? ? ? ? // 加載圖片--header--m默認(rèn)位置,不需要動
? ? ? ? SnakeImages.headerImg.paintIcon(this, g, 0, 5);
? ? ? ? //設(shè)置畫筆顏色
? ? ? ? g.setColor(new Color(193, 255, 183));
? ? ? ? //填充游戲界面,可以找個好看的圖片
? ? ? ? //SnakeImages.backImg.paintIcon(this,g,8,70);//由于太丑,所以不加載背景圖了
? ? ? ? g.fillRect(10, 70, 770, 685);
? ? ? ? //下面加載蛇頭和身子
? ? ? ? if ("R".equals(direction)) {
? ? ? ? ? ? SnakeImages.rightImg.paintIcon(this, g, snakeX[0], snakeY[0]);
? ? ? ? }
? ? ? ? if ("L".equals(direction)) {
? ? ? ? ? ? SnakeImages.leftImg.paintIcon(this, g, snakeX[0], snakeY[0]);
? ? ? ? }
? ? ? ? if ("U".equals(direction)) {
? ? ? ? ? ? SnakeImages.upImg.paintIcon(this, g, snakeX[0], snakeY[0]);
? ? ? ? }
? ? ? ? if ("D".equals(direction)) {
? ? ? ? ? ? SnakeImages.downImg.paintIcon(this, g, snakeX[0], snakeY[0]);
? ? ? ? }
//? ? ? ? 畫完蛇頭畫蛇身
? ? ? ? for (int i = 1; i < length; i++) {
? ? ? ? ? ? SnakeImages.bodyImg.paintIcon(this, g, snakeX[i], snakeY[i]);
? ? ? ? }
? ? ? ? if (isStart == false) {
? ? ? ? ? ? //設(shè)置畫筆顏色
? ? ? ? ? ? g.setColor(new Color(255, 173, 0));
? ? ? ? ? ? //畫一個文字
? ? ? ? ? ? g.setFont(new Font("微軟雅黑", Font.BOLD, 40));
? ? ? ? ? ? g.drawString("點擊空格開始游戲", 250, 300);
? ? ? ? }
? ? ? ? //畫食物
? ? ? ? SnakeImages.foodImg.paintIcon(this, g, foodX, foodY);
//畫積分
? ? ? ? g.setColor(new Color(255, 0, 19));
? ? ? ? g.setFont(new Font("微軟雅黑", Font.BOLD, 20));
? ? ? ? g.drawString("積分:" + score, 620, 40);
? ? ? ? //死亡狀態(tài):
? ? ? ? if (isDie) {
? ? ? ? ? ? g.setColor(new Color(255, 82, 68));
? ? ? ? ? ? g.setFont(new Font("微軟雅黑", Font.BOLD, 20));
? ? ? ? ? ? g.drawString("小蛇死亡,游戲停止,按下空格重新開始游戲", 200, 330);
? ? ? ? ? ? score = 0;
? ? ? ? }
? ? }
}
?
以上代碼寫完,運行后發(fā)現(xiàn)少了點什么,-----在游戲啟動界面沒有添加還面板,接下來就是補充和完善代碼了-
package com.snake;
import javax.swing.*;
import java.awt.*;
public class StartGame {
? ? public static void main(String[] args) {
? ? ? ? //創(chuàng)建窗體
? ? ? ? JFrame jFrame = new JFrame();
? ? ? ? //設(shè)置窗口標(biāo)題
? ? ? ? jFrame.setTitle("貪吃蛇游戲? designed by Gavin");
? ? ? ? //設(shè)置窗體大小,由于要考慮到不同的屏幕尺寸,所以首先要獲得運行時電腦的屏幕參數(shù)
? ? ? ? int height = Toolkit.getDefaultToolkit().getScreenSize().height;
? ? ? ? int width = Toolkit.getDefaultToolkit().getScreenSize().width;
? ? ? ? jFrame.setBounds((width - 800) / 2, (height - 800) / 2, 800, 800);
? ? ? ? //設(shè)置窗體大小不可調(diào)節(jié)
? ? ? ? jFrame.setResizable(false);
? ? ? ? //設(shè)置窗體關(guān)閉游戲退出
? ? ? ? jFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
? ? ? ? GamePanel gamePanel= new GamePanel();
? ? ? ? jFrame.add(gamePanel);
? ? ? ? //默認(rèn)窗體不可見,設(shè)置成可見的
? ? ? ? jFrame.setVisible(true);
? ? }
}
?
運行結(jié)果的一些截圖–
啟動界面–

運行界面

死亡界面

如果你有大塊時間完全可以在完善一下此代碼,添加一些模式–
1,急速模式–調(diào)節(jié)啟動器的時間間隔即可
2,在窗口中再來一條蛇–與多線程結(jié)合
3,在豐富一些設(shè)置內(nèi)容–比如選擇模式等
重新游戲時記得積分清零哦。。。
? ? 總結(jié)—雖然現(xiàn)在swing編程很low了,但是該學(xué)習(xí)的地方還是有的—
? ? 1,要學(xué)會將外部對象導(dǎo)入到程序中并封裝成相應(yīng)的類
? ? 2,練習(xí)監(jiān)聽器的思想
? ? 3,檢測自己學(xué)過的基礎(chǔ)知識–數(shù)組,循環(huán)以及邏輯思維;
? ? 4,重要的是游戲的開發(fā)思維—動效來自于不斷的刷新;
? ? 5,代碼中多次用到了匿名對象—如果該對象只用一次,可以這樣用,如果多次那么就要去實例化他了