贪吃蛇小游戏—手把手教你会你写贪吃蛇(java)

贪吃蛇小游戏—手把手教你会你写贪吃蛇(java)

    首先说一下,经典游戏贪吃蛇–原理很简单不罗嗦,我们是来看东西的。

    游戏需要一个容器来承载,其次是我们需要为游戏准备一些必要的东西,贪吃蛇首先的要有蛇–准备蛇的图片素材,为了便于开发建议蛇的每个部位的图片大小一样;

    1,准备好上述素材之后开始创建容器–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) {

        //创建窗体

        JFrame jFrame= new JFrame();

        //设置窗口标题

        jFrame.setTitle("贪吃蛇游戏  designed by Gavin");

        //设置窗体大小,由于要考虑到不同的屏幕尺寸,所以首先要获得运行时电脑的屏幕参数

        int  height =Toolkit.getDefaultToolkit().getScreenSize().height;

        int width =Toolkit.getDefaultToolkit().getScreenSize().width;

        jFrame.setBounds((width-800)/2,(height-800)/2,800,800);

        //设置窗体大小不可调节

        jFrame.setResizable(false);

        //默认窗体不可见,设置成可见的

        jFrame.setVisible(true);

    }

}

但是关闭窗体后程序并没有结束


————————————————

我们想要的是–程序关闭后游戏即退出–不然占内存呀!!

完善之后的代码—

package com.snake;

import javax.swing.*;

import java.awt.*;

public class StartGame {

    public static void main(String[] args) {

        //创建窗体

        JFrame jFrame = new JFrame();

        //设置窗口标题

        jFrame.setTitle("贪吃蛇游戏  designed by Gavin");

        //设置窗体大小,由于要考虑到不同的屏幕尺寸,所以首先要获得运行时电脑的屏幕参数

        int height = Toolkit.getDefaultToolkit().getScreenSize().height;

        int width = Toolkit.getDefaultToolkit().getScreenSize().width;

        jFrame.setBounds((width - 800) / 2, (height - 800) / 2, 800, 800);

        //设置窗体大小不可调节

        jFrame.setResizable(false);

        //设置窗体关闭游戏退出

        jFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

        //默认窗体不可见,设置成可见的

        jFrame.setVisible(true);

    }

}

   

    ,2,接下来—封装小蛇—这是一种思想,如果要把图片、文件等加载到程序中就要将他封装成具体的对象,封装是先通过封装地址,在通过地址封装为具体对象的;封装之前先导入素材–可以单独创建一个包用来存放素材;


————————————————

这里是用一个包专门来放素材的—方便查找;

封装之前我们要明白代码是经过编译之后运行的,所以我们存放素材的地址并不是编译之后的地址–做一个测试类–

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);

    }

}

执行结果如下—

Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8

file:/C:/Users/Administrator/IdeaProjects/SnakeEatGame/out/production/SnakeEatGame/

 

所以在封装之前我们要预编译一下看图片被加载到哪里了;



预编译之后我们会发存放class文件的out文件夹下多了一个文件夹—

这个snakeImages就是存放图片的位置;


————————————————

图片编译前存放位置(绝对路径)

C:\Users\Administrator\IdeaProjects\SnakeEatGame\src\snakeImage\header.png

图片编译后存放位置(绝对路径)

C:/Users/Administrator/IdeaProjects/SnakeEatGame/out/production/SnakeEatGame/

对比后我们发现的确不一样,由于运行时看的是编译后的地址,所以要先找地址而不能直接封装图片。

    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;

    //定义分数

    int score;

    //定义初始化方法---在构造方法中初始化并不太好,所以拿到外面来方便直接调用;

    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,接下来定义面板构造方法–承接上面代码,构造方法中有监听器,同时有判断的逻辑

    public GamePanel() {

        reset();

//程序运行之后要将焦点挪到游戏界面

        this.setFocusable(true);

//加入按键监听--

          /*public interface KeyListener extends EventListener {

            KeyListener 是一个接口,实现接口需要全部实现*/

      /*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实现了KeyListener接口,同时又是一个抽象类,可以根据自己需要进行覆写*/

        this.addKeyListener(new KeyAdapter() {

            @Override

            public void keyPressed(KeyEvent e) {

                super.keyPressed(e);

                //通过监听按键来控制游戏--每一个按键对应一个ASCII码,通过此来判断用户按了哪个键

                int input = e.getKeyCode();

                //监听空格以开始游戏

                if (input == KeyEvent.VK_SPACE) {

//                    同时要判断小蛇是否死亡

                    if (isDie) {//如果死了,那么恢复初始状态

                        reset();

                        //重新赋予小蛇生命

                        isDie = false;

                    } else {//没死的话,开始--暂停进行切换

                        isStart = !isStart;

                        repaint();//重绘

                    }

                }

                //这个监听完毕之后开始监听上下左右箭头---当然可以监听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";

                }

            }

//按键释放--没有相应操作--所以不用动他

            @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) {//游戏开始状态

                    //蛇动----蛇尾开始动,然后开始蛇头--俗称guyong'

                    for (int i = length - 1; i > 0; i--) {

                        //后一节依次往前挪一位

                        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的倍数,不然会哦出现吃不到食物的尴尬

                    if (snakeX[0] == foodX && snakeY[0] == foodY) {

                        //吃到食物后蛇产古增加,积分增加

                        length++;

                        //吃到食物后,食物会重新产生--随机

                        foodX = ((int) (Math.random() * 30) + 1) * 25;

                        foodY = ((new Random().nextInt(26) + 4) * 25);

                        score += 10;

          /**蛇长度增加后要马上修改该节身子的位置--不然会在游戏左上角,当蛇每吃到一颗食物就会闪一下,可以把以下代码注释掉试一下,出现的原因是每次增加的时候没有立马给定增加的身体部分位置,所以默认为位置为 (0,0),加上一下两行代码就解决了;*/

                        snakeX[length - 1] = snakeX[length - 2];

                        snakeY[length - 1] = snakeY[length - 2];

                    }

                    //判定死亡的方法--蛇头跟任意一节身子碰撞就是死亡

                    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);//这是一个画笔

        //设置游戏区面板颜色

        this.setBackground(new Color(195, 253, 4));

        // 加载图片--header--m默认位置,不需要动

        SnakeImages.headerImg.paintIcon(this, g, 0, 5);

        //设置画笔颜色

        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) {

            //设置画笔颜色

            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);

        //死亡状态:

        if (isDie) {

            g.setColor(new Color(255, 82, 68));

            g.setFont(new Font("微软雅黑", Font.BOLD, 20));

            g.drawString("小蛇死亡,游戏停止,按下空格重新开始游戏", 200, 330);

            score = 0;

        }

    }

}

 

以上代码写完,运行后发现少了点什么,-----在游戏启动界面没有添加还面板,接下来就是补充和完善代码了-

package com.snake;

import javax.swing.*;

import java.awt.*;

public class StartGame {

    public static void main(String[] args) {

        //创建窗体

        JFrame jFrame = new JFrame();

        //设置窗口标题

        jFrame.setTitle("贪吃蛇游戏  designed by Gavin");

        //设置窗体大小,由于要考虑到不同的屏幕尺寸,所以首先要获得运行时电脑的屏幕参数

        int height = Toolkit.getDefaultToolkit().getScreenSize().height;

        int width = Toolkit.getDefaultToolkit().getScreenSize().width;

        jFrame.setBounds((width - 800) / 2, (height - 800) / 2, 800, 800);

        //设置窗体大小不可调节

        jFrame.setResizable(false);

        //设置窗体关闭游戏退出

        jFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

        GamePanel gamePanel= new GamePanel();

        jFrame.add(gamePanel);

        //默认窗体不可见,设置成可见的

        jFrame.setVisible(true);

    }

}

 

运行结果的一些截图–

启动界面–


运行界面


死亡界面


如果你有大块时间完全可以在完善一下此代码,添加一些模式–

1,急速模式–调节启动器的时间间隔即可

2,在窗口中再来一条蛇–与多线程结合

3,在丰富一些设置内容–比如选择模式等

重新游戏时记得积分清零哦。。。

    总结—虽然现在swing编程很low了,但是该学习的地方还是有的—

    1,要学会将外部对象导入到程序中并封装成相应的类

    2,练习监听器的思想

    3,检测自己学过的基础知识–数组,循环以及逻辑思维;

    4,重要的是游戏的开发思维—动效来自于不断的刷新;

    5,代码中多次用到了匿名对象—如果该对象只用一次,可以这样用,如果多次那么就要去实例化他了

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,547评论 4 374
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,787评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,175评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,752评论 0 223
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,169评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,056评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,195评论 2 321
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,980评论 0 214
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,724评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,872评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,344评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,667评论 3 264
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,379评论 3 245
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,202评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,992评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,189评论 2 286
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,987评论 2 279