貪吃蛇小游戲—手把手教你會你寫貪吃蛇(java)
? ? 首先說一下,經(jīng)典游戲貪吃蛇–原理很簡單不羅嗦,我們是來看東西的居砖。
? ? 游戲需要一個容器來承載,其次是我們需要為游戲準備一些必要的東西驴娃,貪吃蛇首先的要有蛇–準備蛇的圖片素材奏候,為了便于開發(fā)建議蛇的每個部位的圖片大小一樣;
? ? 1唇敞,準備好上述素材之后開始創(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è)置窗口標題
? ? ? ? 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è)置成可見的
? ? ? ? jFrame.setVisible(true);
? ? }
}
但是關(guān)閉窗體后程序并沒有結(jié)束
————————————————
我們想要的是–程序關(guān)閉后游戲即退出–不然占內(nèi)存呀V渚镶柱!
完善之后的代碼—
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è)置窗口標題
? ? ? ? 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);
? ? ? ? //默認窗體不可見歇拆,設(shè)置成可見的
? ? ? ? jFrame.setVisible(true);
? ? }
}
? ?
? ? ,2,接下來—封裝小蛇—這是一種思想范咨,如果要把圖片故觅、文件等加載到程序中就要將他封裝成具體的對象,封裝是先通過封裝地址渠啊,在通過地址封裝為具體對象的逻卖;封裝之前先導入素材–可以單獨創(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/
?
所以在封裝之前我們要預編譯一下看圖片被加載到哪里了炼杖;
預編譯之后我們會發(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;//蛇的長度
? ? //蛇的坐標
? ? int snakeX[] = new int[10000];
? ? int snakeY[] = new int[10000];
? ? //蛇頭的方向
? ? String direction;
? ? //游戲是否開始--默認沒開始
? ? boolean isStart = false;
? ? //蛇是否死亡---開始時候默認活著
? ? boolean isDie = false;
? ? //定時器,用于啟動小蛇
? ? Timer timer;
? ? //定義食物位置
? ? int foodX;
? ? int foodY;
? ? //定義分數(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ù)自己需要進行覆寫*/
? ? ? ? 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) {//如果死了猴娩,那么恢復初始狀態(tài)
? ? ? ? ? ? ? ? ? ? ? ? reset();
? ? ? ? ? ? ? ? ? ? ? ? //重新賦予小蛇生命
? ? ? ? ? ? ? ? ? ? ? ? isDie = false;
? ? ? ? ? ? ? ? ? ? } else {//沒死的話阴幌,開始--暫停進行切換
? ? ? ? ? ? ? ? ? ? ? ? isStart = !isStart;
? ? ? ? ? ? ? ? ? ? ? ? repaint();//重繪
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? //這個監(jiān)聽完畢之后開始監(jiān)聽上下左右箭頭---當然可以監(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];
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? //頭部比較復雜一下,因為要上下左右動
? ? ? ? ? ? ? ? ? ? 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é)身子的位置--不然會在游戲左上角徙瓶,當蛇每吃到一顆食物就會閃一下毛雇,可以把以下代碼注釋掉試一下,出現(xià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默認位置震捣,不需要動
? ? ? ? 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è)置窗口標題
? ? ? ? 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);
? ? ? ? //默認窗體不可見,設(shè)置成可見的
? ? ? ? jFrame.setVisible(true);
? ? }
}
?
運行結(jié)果的一些截圖–
啟動界面–
運行界面
死亡界面
如果你有大塊時間完全可以在完善一下此代碼养篓,添加一些模式–
1秃流,急速模式–調(diào)節(jié)啟動器的時間間隔即可
2,在窗口中再來一條蛇–與多線程結(jié)合
3柳弄,在豐富一些設(shè)置內(nèi)容–比如選擇模式等
重新游戲時記得積分清零哦剔应。。语御。
? ? 總結(jié)—雖然現(xiàn)在swing編程很low了峻贮,但是該學習的地方還是有的—
? ? 1,要學會將外部對象導入到程序中并封裝成相應(yīng)的類
? ? 2应闯,練習監(jiān)聽器的思想
? ? 3纤控,檢測自己學過的基礎(chǔ)知識–數(shù)組,循環(huán)以及邏輯思維碉纺;
? ? 4船万,重要的是游戲的開發(fā)思維—動效來自于不斷的刷新刻撒;
? ? 5,代碼中多次用到了匿名對象—如果該對象只用一次耿导,可以這樣用声怔,如果多次那么就要去實例化他了