贪吃蛇项目的制作

该项目是根据GUI编程学习所写,详细的学习课程可以参考狂神说老师的GUI课程:一小时开发贪吃蛇

以下仅仅为简要说一下逻辑,源码放在最后。

实现该项目总共用到三个类:Data数据类,用于存放静态数据;Snake实现类,用于运行;GamePanel核心类,用于具体实现内部逻辑。

实现结果如图:

image-20220314193116873

Data类

要实现以上的内容,我们需要一些静态图片资源,包括蛇头蛇身,介绍图等,该类就是为了存放这些而设计的,如开头广告栏的存储:

public static URL headURL = Data.class.getResource("/StaticFile/header.png");
public static ImageIcon header = new ImageIcon(headURL);

由于这些均为静态资源,使用static进行存储。

Snake类

该类为运行类,直接调用GamePanel核心类进行运行。它继承了JFrame类,即一个最外层的窗口,只是在窗口中使用frame.add(new GamePanel());添加了面板,而该面板GamePanel即为真正的实现功能的类

GamePanel类

该类为真正的实现类。

实现贪吃蛇功能,我们要明确一点:表面上看见贪吃蛇一格一格动,实际上是整个图片在一次一次变化。每一次贪吃蛇的移动,象征的是面板的一次更新

所以逻辑很清晰了,要实现功能,先实现静态再实现动态

先直接在这里写上在该类中我用到的变量,便于后面大家对照分别是什么意思:

    //定义蛇的数据结构
    int length;//蛇的长度
    String direct;//蛇的方向
    int[] snakeX = new int[600];//蛇的x坐标
    int[] snakeY = new int[500];//蛇的Y坐标
    int score;//游戏成绩
    boolean isStart;//游戏当前的状态,初始为暂停
    boolean isFail;//游戏失败状态

    //食物坐标
    int foodX;
    int foodY;
    Random random = new Random();//随机数
    Timer timer = new Timer(100,this);//定时器,100ms执行一次

这里对部分参数进行解释:

  1. 随机数,用作确定食物的位置,食物要随机分布,下面init()方法有提到。
  2. 定时器,定时刷新界面,前面已经讲到了贪吃蛇的移动象征面板的一次更新,使用定时器就可以定时更新一次面板,也就可以规定贪吃蛇的移动速度了

首先我们需要初始化属性的内容,不建议直接在定义时初始化,我这里采用了设计一个init()方法将这些参数初始化:

public void init(){
        length = 3;
        snakeX[0] = 100;   snakeY[0] = 100;//脑袋坐标
        snakeX[1] = 75;   snakeY[1] = 100;//第一个身体坐标
        snakeX[2] = 50;   snakeY[2] = 100;//第二个身体坐标
        score = 0;//积分初始为0
        direct = "R";//初始方向向右
        isStart = false;//初始为暂停
        isFail = false;//默认为不失败
        //食物随机分布在界面上
        foodX = 25 + 25 * random.nextInt(34);
        foodY = 75 + 25 * random.nextInt(24);
    }

静态图片我们这里继承JPanel类,重写了protected void paintComponent(Graphics g)方法,该方法是用作绘制一次图片的,只要创建该类对象,即可进行一次绘制,自动调用一次这个方法,初始样式如下:

protected void paintComponent(Graphics g){
    super.paintComponent(g);//清屏
}

当我们在运行游戏开始时,即调用一次该方法,使之在游戏未动的时候对面板进行一次静态绘制。所以我们可以把我们想要画的初始界面的样子在这个时候先写好,譬如:

protected void paintComponent(Graphics g){
    super.paintComponent(g);//清屏
	setBackground(Color.white);

	//先绘制静态面板
	Data.header.paintIcon(this,g,25,11);//开头广告画上去
	g.fillRect(25,75,850,600);//默认游戏界面
//画积分
	g.setColor(Color.white);
	g.setFont(new Font("微软雅黑",Font.BOLD,18));
	g.drawString("长度:"+length,750,35);
	g.drawString("分数:"+score,750,55);
    
    g.setColor(Color.white);
    //设置字体
    g.setFont(new Font("微软雅黑",Font.BOLD,40));
    g.drawString("按下空格开始游戏",300,300);
}

这样就可以实现一个没有小蛇没有食物,但是基本框架都写好的页面。小蛇的静态图绘制只需要找好坐标,以上面类似绘制上广告的逻辑写即可:

Data.right.paintIcon(this,g,snakeX[0],snakeY[0]);//蛇头
for (int i = 1; i < length; i++) {
    Data.body.paintIcon(this,g,snakeX[i],snakeY[i]);//蛇身
}

这样我们就可以实现类似于开头这个图一样的页面了。

静态图绘制好了,我们需要实现动态,那么逻辑应该是怎样的呢?不妨把自己带入成一个游戏玩家:

我们按下空格可以开始,暂停游戏,按下上下左右可以变换贪吃蛇的运动方向,所以我们很明显,需要键盘监听器。

当贪吃蛇吃了食物,长度会变长;贪吃蛇也会按照它的方向保持移动;贪吃蛇吃到自己的身体,会游戏失败,很明显需要事件监听器。

所以要引入这些概念,我们很明显,调用两个监听器接口即可:

public class GamePanel extends JPanel implements KeyListener, ActionListener{
    @Override
    public void keyPressed(KeyEvent e){}//实现键盘监听
    
    @Override
    public void actionPerformed(ActionEvent e){}//实现事件监听
    
    //这两个不需要实现,只需要写上,不然会报错
    @Override
    public void keyTyped(KeyEvent e) {}
    @Override
    public void keyReleased(KeyEvent e) {}
}

键盘监听的逻辑很简单:空格就是开始或者暂停,变换isStart变量;上下左右就是小蛇移动,变换direct变量。

事件监听稍微麻烦一点:

  • 如果游戏是开始的情况,小蛇就要移动,要使小蛇移动很简单,就是让小蛇的后一节身体的位置变到前一节身体的位置:

    for (int i = length - 1; i > 0; i--) {
        //后一节身体变成前一节身体的位置
        snakeX[i] = snakeX[i-1];
        snakeY[i] = snakeY[i-1];
    }
    
  • 因为要上下左右动,所以监听方向,当向各种方向时,要变化蛇移动的距离和运动方向。考虑到该游戏的自行设计,设定为超出边界就从另一边出来,所以还要设计监听边界问题:

    if (direct.equals("R")){//右移
        snakeX[0] = snakeX[0] + 25;
        //边界判断
        if (snakeX[0]>850){snakeX[0] = 25;}
    }else if (direct.equals("L")){//左移
        snakeX[0] = snakeX[0] - 25;
        //边界判断
        if (snakeX[0]<0){snakeX[0] = 850;}
    }else if (direct.equals("U")){//上移
        snakeY[0] = snakeY[0] - 25;
        //边界判断
        if (snakeY[0]<75){snakeY[0] = 650;}
    }else if (direct.equals("D")){//下移
        snakeY[0] = snakeY[0] + 25;
        //边界判断
        if (snakeY[0]>650){snakeY[0] = 75;}
    }
    
  • 贪吃蛇要吃东西,所以需要一个蛇头碰到食物的情况:

    if(snakeX[0] == foodX && snakeY[0] == foodY){
        length++;//长度+1
        score+=10;
        //然后再次随机生成食物
        foodX = 25 + 25 * random.nextInt(34);
        foodY = 75 + 25 * random.nextInt(24);
    }
    
  • 失败事件,撞到自己就失败了:

    for (int i = 1 ; i<length;i++){
        if (snakeX[0] == snakeX[i] && snakeY[0] == snakeY[i]){
            isFail = true;
            repaint();
        }
    }
    

监听的话已经实现,但是还是没有阐述如何动态,很简单,在监听器最后调用repaint()方法即可,这个方法可以重新调用paintComponent方法,也就是重新进行一次静态绘制,而监听器通过改变了属性量,再进行一次静态绘制时,就已经不是原来的那张图了。而游戏中每时每刻都发生变化,这样我们每次绘制的面板都不一样,自然就能实现动态了。

当然,如果没有加入这些监听器是没用的,这里我们再重写以下无参构造:

public GamePanel(){
    init();
    //获得焦点和键盘事件
    setFocusable(true);
    addKeyListener(this);
    timer.start();
}

写到这里会发现,那个静态的绘制方法是不是过于简陋了,所以我们需要在静态方法里面加入一系列的判断,因为每一次静态绘制,定义的参数量都可能发生了变化,要想对他们的变化做出反应,就得针对他们不同情况的量做不同的绘制。这里在源码里面都有,不具体说了。

源码地址:

链接:百度网盘链接
提取码:xb58
备注:里面包含了静态资源,在staticFile文件夹内,如果想使用请更改Data数据类里面的路径。且该内容仅为源码,想测试效果请自行搭建项目复制粘贴。

posted @ 2022-03-14 20:38  可乐加品客  阅读(152)  评论(0编辑  收藏  举报