java实现贪吃蛇小游戏(简洁)

贪吃蛇

学习了半年的算法,多少有点凌乱了,所以最近痴迷于java做一些小的项目,做一些小的项目,这种节奏还是很舒服的

本次文章是我看麦叔编程学习的贪吃蛇,代码很简洁,本文也是基础篇,可能会有点啰嗦,如果基础稍微好一点的可以直接去文章最后拿源码看,也有详细的注释,一共也就二百来行代码,我会一步一步往下走,每一步的运行截图也会放上,在文章的中间也会放一次当前的源码给予对照,文章最后也会把完整的代码放上
在这里插入图片描述做好以后就是这样,然后开始吧

首先在IDEA上创建一个java项目,我是用的IDEA,eclipse也一样,都是工具,按照自己的工具来就行,还有就是我的用的JDK16版本,因为我胆子比较大(其实是我懵懂无知),大家最好还是用JDK1.8
起名Msnake,因为我们要用的图形界面,这里选择的是JFrame,然后导入import javax.swing.*;这个包,然后创建一些我们窗口最基本的信息,都写在代码注释里了

import javax.swing.*;

public class Msnake {
    public static void main(String[] args) {
        JFrame jFrame = new JFrame();
        jFrame.setBounds(10,10,900,720);
        //设置窗口左上角x,y的坐标,和长、宽
        jFrame.setResizable(false);
        //设置窗口大小不可拖动改变
        jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        //设置窗口的关闭,如果括号里什么都不写,那么点关闭只会隐藏窗口
        jFrame.setTitle("贪吃蛇");
        //设置我们窗口的名称,也可以说是标题
        jFrame.setVisible(true);
        //设置窗口显,让我们的窗口展现出来
    }
}

运行一下应该是这种情况,当然,自己改数据也是可以的,最好先按照我的弄一边,再自己拓展
在这里插入图片描述窗口创建完了,我们给窗口加一下东西,新建一个类Mpanel,用来存放我们给窗口加的东西

import javax.swing.*;//现在需要用的包,后面还会慢慢加
import java.awt.*;

public class Mpanel extends JPanel {//继承于Jpanel,Jpanel是比较常用的swing中的一个容器
    public void paintComponent(Graphics g){//创建一个画布类,Graphics可以相当于一个画笔对象
        super.paintComponent(g);
        //可以认为是一个重绘,后面我们会用到repaint()方法来调用paintComponent来绘图,每次先用画笔重新绘一次窗口,我是这么理解的
        this.setBackground(Color.WHITE);//设置背景为WHITE(白色)
        g.fillRect(25, 75, 850, 600);//用画笔填满一个方格,左上角x、y坐标和长、宽
    }
}

为了让我们的这些东西展示到窗口上,我们需要在主类里面加上

jFrame.add(new Mpanel());

然后运行就是这样
在这里插入图片描述接下来我们来放一些到我们的项目目录下去,后面游戏的图片都是我一个个放上去的
百度网盘: https://pan.baidu.com/s/1EFrLU0MWUwoUdxSCEEl1dQ
提取码:0309
这里面有需要用到的所有图片
下面我都用截图放代码,也更直观一点
在这里插入图片描述大家按照自己图片下载放的地址先在我们的类中加载进来,这里用的是ImageIcon来加载图片
首先我们先用到我们的标题和蛇放到画布上
这里用到paintIcon方法,把下面这些写入paintComponent类中
参数可以理解成用我们的画笔,在我们的画布上画这个对象,后面两个参数图片左上角x、y的坐标
在这里插入图片描述这样我们一个静态的界面和蛇就做好了

在这里插入图片描述
接下来我们要放一串字符串在画布上告诉别人按下空格启动,用我们的画笔画

g.drawString("Press Space to Start", 250, 400);

这个方法也很好理解,不过我们直接加进去运行的话,应该是看不到字符串的,并不是我们写错了,而是我们的画笔画出来的是黑色的,我们的画布也是黑色的,理解了吧

g.setColor(Color.WHITE);//设置画笔颜色为白色

在这里插入图片描述这也太小了,我们再设置一下字体,直接看图吧,上面都有注释
在这里插入图片描述

既然说按下空格要开始,那按下空格要有反应,我们先设置一下按下空格,让这一串字符串消失,再次按下,再出现
先构造一个无参的Mpanel方法
在这里插入图片描述写完以后应该就报错了,因为我们要用的这两个方法,在另一个接口处,我们没指出来,他不乐意了
我们只需要implement一个KeyListener,然后带入一下包

import java.awt.event.KeyEvent;//要导的包
import java.awt.event.KeyListener;

在这里插入图片描述这个时候他又不乐意了,因为我们时候我们要响应键盘,但是我们却没有响应键盘的方法,将鼠标放上去看一下提示,我用的IDEA可以直接创造子类,eclipse也是有的
在这里插入图片描述
直接创建就可以了
在这里插入图片描述系统生成三个方法,注释我已经写上去了,我们随便选一个进行编写就行,我们选按中键盘时的吧
先定义一个Boolean变量isStarted==false表示我们还没下去键盘
在这里插入图片描述字符串这里自然而然就是这样的
在这里插入图片描述

然后填一下KeyPressed,注释都是代码里了
在这里插入图片描述运行一下应该就是按下空格字符串消失,再按,就出来,再按就消失,自己演示一下,如果有问题再看一下是不是哪一步出错了
接下来我们要让蛇动起来了,蛇肯定是会不断变长的,我们如果一个一个body给写出来,那肯定不行
我们换成循环来写
先定义一个变量lenSnake为蛇的初始长度,和两个数组来记录每一节身体的x、y
在这里插入图片描述写一个方法初始化一下我们的数据

在这里插入图片描述实现一下
在这里插入图片描述调用我们的数组来初始化蛇
在这里插入图片描述做到这里其实运行一下和刚刚是没有区别的,但是我们代码从死板的一个一个打印蛇,换成的循环来打印,对我们后面的进展有很大帮助
为了实现我们动态的蛇,定义一个Timer变量,用来当时钟
在这里插入图片描述
同时他也需要一个接口和包
在这里插入图片描述这里加一下,其实还要加一个包import java.awt.event.ActionEvent;图片上没有显示
这里又报错了,原因应该很清楚了,既然说要运行本项目指定的方法,那么方法呢?所以我们创建一个
在这里插入图片描述
在这里插入图片描述得到一个actionPerformed方法,所以我们时钟过100毫秒后就会执行它,那我们让蛇动起来
根据规律我们知道,蛇的每一节身体每次会移动到下一节身体上,最开始的身体,移动到头的位置,头向前一个位置
首先启动时钟
在这里插入图片描述然后在我们的actionPerformed方法这样实现在这里插入图片描述因为在我们的蛇头或者蛇的身体都是25 * 25的图片,所以每次加25
现在蛇应该动起来了吧,我下面放一下写到这里该有的代码,如果有问题,对照一下我的代码

//Msnake类
import javax.swing.*;

public class Msnake {
    public static void main(String[] args) {
        JFrame jFrame = new JFrame();
        jFrame.setBounds(10,10,900,720);
        jFrame.setResizable(false);
        jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jFrame.setTitle("贪吃蛇");
        jFrame.add(new Mpanel());
        jFrame.setVisible(true);
    }
}

//Mpanel类
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

public class Mpanel extends JPanel implements KeyListener, ActionListener {
	//先加载图片
    ImageIcon title = new ImageIcon("E:/JAVA01/idea_project/Msnakex/src/resources/picture/title.jpg");
    ImageIcon body = new ImageIcon("E:/JAVA01/idea_project/Msnakex/src/resources/picture/body.png");
    ImageIcon up = new ImageIcon("E:/JAVA01/idea_project/Msnakex/src/resources/picture/up.png");
    ImageIcon down = new ImageIcon("E:/JAVA01/idea_project/Msnakex/src/resources/picture/down.png");
    ImageIcon left = new ImageIcon("E:/JAVA01/idea_project/Msnakex/src/resources/picture/left.png");
    ImageIcon right = new ImageIcon("E:/JAVA01/idea_project/Msnakex/src/resources/picture/right.png");
    ImageIcon food = new ImageIcon("E:/JAVA01/idea_project/Msnakex/src/resources/picture/food.png");

    boolean isStarted = false;//按下空格的标志
    int lenSnake = 3;//蛇的长度
    int[] snakex = new int[800];//x坐标
    int[] snakey = new int[800];//y坐标
    Timer timer = new Timer(100, this);//100毫秒后运行本项目指定的方法

    public Mpanel() {
        this.setFocusable(true);//允许响应
        this.addKeyListener(this);//添加一个KeyListener用来响应我们的键盘
        timer.start();//启动时钟
        initSnake();//初始化
    }

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        //可以认为是一个重绘,后面我们会用到pain()方法来调用paintComponent来绘图,每次先用画笔重新绘一次窗口,我是这么理解的
        this.setBackground(Color.WHITE);
        g.fillRect(25, 75, 850, 600);
        title.paintIcon(this, g, 25, 11);//可以理解成用我们的画笔,在我们的画布上画这个对象,后面两个参数图片左上角x、y的坐标
//        body.paintIcon(this, g, 75, 100);
//        body.paintIcon(this, g, 50, 100);
        right.paintIcon(this, g, snakex[0], snakey[0]);//蛇头
        for (int i = lenSnake - 1; i >= 1; i--) {//蛇身
            body.paintIcon(this, g, snakex[i], snakey[i]);
        }


        if (!isStarted) {
            g.setColor(Color.WHITE);//画笔为白色
            g.setFont(new Font("arial", Font.BOLD, 40));//设置字体类型、加粗、字体大小
            g.drawString("Press Space to Start", 250, 400);//画一串字符串出来
        }

    }

    public void initSnake() {//初始化
        lenSnake = 3;
        snakex[0] = 100;
        snakey[0] = 100;
        snakex[1] = 75;
        snakey[1] = 100;
        snakex[2] = 50;
        snakey[2] = 100;
    }

    @Override
    public void keyTyped(KeyEvent e) {//按下键盘时

    }

    @Override
    public void keyPressed(KeyEvent e) {//按中键盘时
        int keyCode = e.getKeyCode();//读取按下的键位
        if (keyCode == KeyEvent.VK_SPACE) {
            //如果键位等于KeyEvent.VK_SPACE(空格的意思),我就翻转isStarted
            // 并且repaint()重新画图,也就是说重新调用paintComponent
            isStarted = !isStarted;
            repaint();
        }
    }

    @Override
    public void keyReleased(KeyEvent e) {//抬起键盘时

    }

    @Override
    public void actionPerformed(ActionEvent e) {
        for (int i = lenSnake - 1; i >= 1; i--) {//每个身体位置等于前一个的位置
            snakex[i] = snakex[i - 1];
            snakey[i] = snakey[i - 1];
        }
        snakex[0] += 25;//蛇头前进一格
        repaint();//重新画图
        timer.start();//再次启动
    }
}

写到这里蛇应该已经动起来了,并且按下空格可以控制字符串的出现和消失,我也困了,先睡觉了,晚上下课继续补,(如果有人看的话)

继续继续
我们发现蛇竟然可以直接跑出界面,那我们要想办法让他不出界,或者一旦出界,立马初始化,相当于一个穿墙的过程,我们只需要加上这么一句话就可以了
在这里插入图片描述现在我们的蛇还只会向右跑,定义一下方向吧
在这里插入图片描述
所以接下来我们画蛇头的时候就要根据fx来画了,我们用switch语句替换掉我们原先一直向右的头,现在我们会根据fx去画头,而我们的fx初始化为"R"
在这里插入图片描述加一句初始化就好了
在这里插入图片描述
我们已经写过了按下空格会有响应,那么我们对应的蛇头是不是也该响应我们的上下左右键位
在这里插入图片描述类比上文的空格响应,这四句不难看懂吧
可是我们发现,那一句字符串提示语怎么一直在啊,而且我们的蛇,一上来就开始动
我们设置一下当我们按下空格后蛇再开始动,字符串提示消失,并且再次按下可以暂停,字符串再次出现
在这里插入图片描述这样的话就是当我们isStarted为true时,才会动,false时就会暂停了
刚刚说到蛇头的方向,当我们按下下键其实蛇头已经改变了但是我们的蛇并没有朝着蛇头方向走,这时,我们需要在actionPerformed中加上,当fx == R时snakex[0] += 25;其他方向类比一下就可以了

在这里插入图片描述 在这里插入图片描述
把原来的这两句注释掉就可以了
现在关于蛇的四个方向跑,暂停就已经做好了,当然蛇可以直接掉头的,大家可以试一下,因为我们还没有完善我们的代码,当然,你也可以把这个叫做游戏特色
游戏已经做了一多半了,还差一个事物的生成
好吧,写到这里头有点懵了,晚上再来补一下

最后放一下源码和资源
提取码:0309

可以运行! (以下代码只是其中的一个类) package chy.snake.entities; import java.awt.Color; import java.awt.Graphics; import java.awt.Point; import java.util.HashSet; import java.util.LinkedList; import java.util.Set; import chy.snake.listener.SnakeListener; import chy.snake.util.Global; public class Snake { public static final int up = 1; public static final int down = -1; public static final int left = -2; public static final int right = 2; private int oldDirection,newDirection; //newDirection:一次时间 间隔内输入的最后方向 private Point oldTail; private boolean life; //life 为 true或者false,初始为true, 用于118行 private LinkedList<Point> body = new LinkedList<Point> (); //需要经常访问蛇的第一个和最后一个节点,使用链表LinkedList存放蛇的身体节点,因为它有getFirst(),getLast(),removeLast(),方法 private Set<SnakeListener> listeners = new HashSet<SnakeListener>(); public Snake(){ init(); } public void init(){ //初始化 int x = Global.WIDTH/2; int y = Global.HEIGHT/2; for(int i=0;i<3;i++){ //初始长度3 body.addLast(new Point(x-i,y)); //是addLast } oldDirection = newDirection = right; //初始方向 右 life = true; } public void die(){ life = false; } public void move(){ System.out.println("Snake's move"); if (!(oldDirection + newDirection == 0)){ oldDirection = newDirection; } //1.去尾 oldTail = body.removeLast(); int x = body.getFirst().x; int y = body.getFirst().y; //蛇头的x,y坐标 switch(oldDirection){ case up: y--; break; case down: y++; break; case left: x--; break; case right: x++; break; } Point newHead = new Point(x,y); //2.加头 body.addFirst(newHead); } public void changeDirection(int direction){ /*无效方向:在蛇的这一次移动之后和下一次移动之前的 这个时间间隔内输入了多个方向,只有最后一个方向 是 有效方向,其余的都为无效方向*/ System.out.println("Snake's changeDirection"); newDirection = direction; //将一个时间间隔内按得最后方向,赋给 newDirection } public void eatFood(){ System.out.println("Snake's eatFood"); body.addLast(oldTail); //后面的节点不去掉 } public boolean isEatFood(){ System.out.println("Snake's isEatFood"); return false; } public boolean isEatBody(Snake snake){ //比较蛇是否吃到身体 System.out.println("snake's isEatBody"); for(int i= 1;i<body.size();i++){ //i 从蛇头结点的下一个节点开始,排除蛇头结点 if(body.get(i).equals(this.getHead())){ //如果i 的节点 和 头结点 相同 return true; } } return false; } public void drawMe(Graphics g){ System.out.println("Snake's drawMe"); g.setColor(Color.GREEN); //设置蛇的颜色 for(Point p : body){ g.fill3DRect(p.x * Global.CELL_SIZE, p.y * Global.CELL_SIZE, Global.CELL_SIZE, Global.CELL_SIZE, true); } } public Point getHead(){ //得到蛇头节点,判断吃食物 return body.getFirst(); } private class SnakeDriver implements Runnable{ //线程,不停的调用move方法 @Override public void run() { // TODO 自动生成的方法存根 while(life){ // 42和46行,life为true 或者false move(); for(SnakeListener l : listeners){ l.snakeMoved(Snake.this); //循环,依次调用SnakeMoved方法 } try { Thread.sleep(300); } catch (InterruptedException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } } } } public void start(){ new Thread(new SnakeDriver()).start(); //启动线程的方法 } public void addSnakeListener(SnakeListener l){ if(l != null){ this.listeners.add(l); } } }
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Miss .

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值