【Java GUI 编程】实现贪吃蛇游戏

文章目录

    • 一、前言
    • 二、游戏效果
    • 三、开发
      • 1、界面绘制
      • 2、绘制静态的小蛇
      • 3、让小蛇动起来
      • 4、键盘控制小蛇移动
      • 5、小蛇吃食物长大
      • 6、失败判定,积分系统
      • 7、项目源码
    • 五、拓展

一、前言

之前学习 GUI 编程,主要总结了 AWT 中(组件,容器,面板,事件监听,文本框监听,画图类,鼠标监听,键盘监听,窗口监听) 还有 Swing (容器,面板,label ,按钮,列表,文本框等)所以综合以上知识,写一个练手的小项目

二、游戏效果

【Java GUI 编程】实现贪吃蛇游戏_第1张图片

三、开发

1、界面绘制

1、加载游戏主启动类

public class StartGame {
     
    public static void main(String[] args) {
     
        JFrame frame = new JFrame();

        frame.setResizable(false); // 窗口大小不可变
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setBounds(10,10,900,750);
        // 把面板添加到 frame 中
        frame.add(new GamePanel());
        frame.setVisible(true);
    }
}

2、绘制静态的小蛇

加载资源类

public class Data {
     
    // logo
    public static URL headerURL = Data.class.getResource("static/header.png");
    public static ImageIcon header = new ImageIcon(headerURL);

    // 头的四个方向
    public static URL upURL = Data.class.getResource("static/up.png");
    public static URL downURL = Data.class.getResource("static/down.png");
    public static URL leftURL = Data.class.getResource("static/left.png");
    public static URL rightURL = Data.class.getResource("static/right.png");
    public static ImageIcon up = new ImageIcon(upURL);
    public static ImageIcon down = new ImageIcon(downURL);
    public static ImageIcon left = new ImageIcon(leftURL);
    public static ImageIcon right = new ImageIcon(rightURL);

    //身体
    public static URL bodyURL = Data.class.getResource("static/body.png");
    public static ImageIcon body = new ImageIcon(bodyURL);
    // 食物
    public static URL foodURL = Data.class.getResource("static/food.png");
    public static ImageIcon food = new ImageIcon(foodURL);
}

游戏界面类

public class GamePanel extends JPanel implements KeyListener, ActionListener {
     
    // 定义蛇的数据结构
    int length; //蛇长度
    int[] snakeX = new int[600]; // 蛇的 x 坐标
    int[] snakeY = new int[500]; // 蛇的 y 坐标
    String fx; // 方向
    //游戏当前状态
    boolean isStart = false; //默认是暂停状态
        init();
        // 获得焦点和键盘事件
        this.setFocusable(true); //获得焦点
    }
    //初始化
    public void init(){
     
        length = 3;
        snakeX[0] = 100;snakeY[0] = 100;
        snakeX[1] = 75;snakeY[1] = 100;
        snakeX[2] = 50;snakeY[2] = 100;
        fx = "R";
    }
    // 绘制面板,游戏中的所有东西都是用这个画笔
    @Override
    protected void paintComponent(Graphics g) {
     
        super.paintComponent(g);
        // 绘制静态面板
        this.setBackground(Color.WHITE);
        Data.header.paintIcon(this,g,25,11);
        g.fillRect(25,75,850,600); //默认游戏界面

        // 把小蛇画上去,初始化向右
        if(fx.equals("R")){
     
            Data.right.paintIcon(this,g,snakeX[0],snakeY[0]);
        }
        else if(fx.equals("L")){
     
            Data.left.paintIcon(this,g,snakeX[0],snakeY[0]);
        }else if(fx.equals("U")){
     
            Data.up.paintIcon(this,g,snakeX[0],snakeY[0]);
        } else if(fx.equals("D")){
     
            Data.down.paintIcon(this,g,snakeX[0],snakeY[0]);
        }
        // 动态增加身体
        for(int i= 1;i < length;++i){
     
            Data.body.paintIcon(this,g,snakeX[i],snakeY[i]);
        }

        //游戏状态
        if(isStart == false){
     
            g.setColor(Color.white);
            g.setFont(new Font("微软雅黑",Font.BOLD,40));
            g.drawString("按下空格开始游戏",300,300);
        }
    }


    //键盘监听事件
    @Override
    public void keyPressed(KeyEvent e) {
     
        //获得键盘按键
     
        // 小蛇移动
    }

    // 事件监听----定时器--通过固定的时间刷新
    @Override
    public void actionPerformed(ActionEvent e) {
     
    }
}

3、让小蛇动起来

这里需要定时器来实现,让每一张图片动起来,然后没 100ms 刷新一次,我们就可以看到动态的小蛇

	// 定时器 --- 100 ms 执行一次
	Timer timer =  new Timer(100,this);
	timer.start();
    // 事件监听----定时器--通过固定的时间刷新
    @Override
    public void actionPerformed(ActionEvent e) {
     
        if(isStart && isFail == false){
      // 游戏开始,小蛇动起来
            //  吃食物
            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 = length-1;i>0;i--){
     
                snakeX[i] = snakeX[i-1];
                snakeY[i] = snakeY[i-1];
            }
            // 走向
            if(fx.equals("R")){
     
                snakeX[0] = snakeX[0]+25;
                if(snakeX[0] > 850){
     snakeX[0] = 25;}
            }else if(fx.equals("L")){
     
                snakeX[0] = snakeX[0]-25;
                if(snakeX[0] < 25){
     snakeX[0] = 850;}
            }
            else if(fx.equals("U")){
     
                snakeY[0] = snakeY[0]-25;
                if(snakeY[0] < 75){
     snakeY[0] = 650;}
            }
            else if(fx.equals("D")){
     
                snakeY[0] = snakeY[0]+25;
                if(snakeY[0] > 650){
     snakeY[0] = 75;}
            }
            repaint(); //重新画一次
        }
        timer.start(); //开始定时器
    }

4、键盘控制小蛇移动

这里需要用到键盘监听

 	//键盘监听事件
    @Override
    public void keyPressed(KeyEvent e) {
     
        //获得键盘按键
        int keyCode = e.getKeyCode();
        if(keyCode == KeyEvent.VK_SPACE){
     
            if(isFail){
      //游戏失败
                //重新开始
                isFail = false;
                init(); //初始化
            }
            else{
     
                isStart = !isStart;
            }
            repaint();
        }
        // 小蛇移动
        if(keyCode == KeyEvent.VK_LEFT){
     
            fx = "L";
        }else if(keyCode == KeyEvent.VK_RIGHT){
     
            fx = "R";
        }else if(keyCode == KeyEvent.VK_UP){
     
            fx = "U";
        }else if(keyCode == KeyEvent.VK_DOWN){
     
            fx = "D";
        }
    }

5、小蛇吃食物长大

定义变量 int length
如果食物的坐标和小蛇头部坐标重合,则认为吃到了食物,然后让 length+1

6、失败判定,积分系统

如果小蛇头部碰到了自己的身体,则认为游戏失败,用一个循环判断,当头部等于身体的任何一个位置,游戏判定变量设置为 true,等到下一次遍历的时候就会退出

积分系统:定义一个 score 变量,每吃到一个食物分数+10,然后通过画板把它画到界面的指定位置

7、项目源码

加载资源类

package JavaGUI.snake;
import javax.swing.*;
import java.net.URL;

/**
 * @Title:
 * @Package
 * @Description:
 * @author: maze
 * @date 2020/10/21上午 9:12
 */
public class Data {
     
    // 加载当前目录下的 static 文件夹功能的 header.png 图片
    public static URL headerURL = Data.class.getResource("static/header.png");
    public static ImageIcon header = new ImageIcon(headerURL);

    // 头的四个方向
    public static URL upURL = Data.class.getResource("static/up.png");
    public static URL downURL = Data.class.getResource("static/down.png");
    public static URL leftURL = Data.class.getResource("static/left.png");
    public static URL rightURL = Data.class.getResource("static/right.png");
    public static ImageIcon up = new ImageIcon(upURL);
    public static ImageIcon down = new ImageIcon(downURL);
    public static ImageIcon left = new ImageIcon(leftURL);
    public static ImageIcon right = new ImageIcon(rightURL);

    //身体
    public static URL bodyURL = Data.class.getResource("static/body.png");
    public static ImageIcon body = new ImageIcon(bodyURL);
    // 食物
    public static URL foodURL = Data.class.getResource("static/food.png");
    public static ImageIcon food = new ImageIcon(foodURL);
}

游戏界面类

package JavaGUI.snake;

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;
import java.util.Random;

/**
 * @Title: GamePane
 * @Package
 * @Description:
 * @author: maze
 * @date 2020/10/21上午 8:57
 */
public class GamePanel extends JPanel implements KeyListener, ActionListener {
     
    // 定义蛇的数据结构
    int length; //蛇长度
    int[] snakeX = new int[600]; // 蛇的 x 坐标
    int[] snakeY = new int[500]; // 蛇的 y 坐标
    String fx; // 方向
    // 食物坐标
    int foodx;
    int foody;
    Random random = new Random();
    int score;

    //游戏当前状态
    boolean isStart = false; //默认是暂停状态
    boolean isFail = false; //游戏失败

    // 定时器 --- 100 ms 执行一次
    Timer timer =  new Timer(100,this);


    public GamePanel(){
     
        init();
        // 获得焦点和键盘事件
        this.setFocusable(true); //获得焦点
        this.addKeyListener(this); //获得键盘监听事件
        timer.start(); //定时器启动
        score = 10;
    }
    //初始化
    public void init(){
     
        length = 3;
        snakeX[0] = 100;snakeY[0] = 100;
        snakeX[1] = 75;snakeY[1] = 100;
        snakeX[2] = 50;snakeY[2] = 100;
        fx = "R";
        foodx = 25 + 25*random.nextInt(34);
        foody = 75 + 25*random.nextInt(24);
    }
    // 绘制面板,游戏中的所有东西都是用这个画笔
    @Override
    protected void paintComponent(Graphics g) {
     
        super.paintComponent(g);
        // 绘制静态面板
        this.setBackground(Color.WHITE);
        Data.header.paintIcon(this,g,25,11);
        g.fillRect(25,75,850,600); //默认游戏界面
        // 画小蛇的食物
        Data.food.paintIcon(this,g,foodx,foody);

        // 画积分
        g.setColor(Color.white);
        g.setFont(new Font("微软雅黑",Font.BOLD,18));
        g.drawString("长度"+length,750,35);
        g.drawString("分数"+score,750,54);
        // 把小蛇画上去,初始化向右
        if(fx.equals("R")){
     
            Data.right.paintIcon(this,g,snakeX[0],snakeY[0]);
        }
        else if(fx.equals("L")){
     
            Data.left.paintIcon(this,g,snakeX[0],snakeY[0]);
        }else if(fx.equals("U")){
     
            Data.up.paintIcon(this,g,snakeX[0],snakeY[0]);
        } else if(fx.equals("D")){
     
            Data.down.paintIcon(this,g,snakeX[0],snakeY[0]);
        }
        // 动态增加身体
        for(int i= 1;i < length;++i){
     
            Data.body.paintIcon(this,g,snakeX[i],snakeY[i]);
        }

        //游戏状态
        if(isStart == false){
     
            g.setColor(Color.white);
            g.setFont(new Font("微软雅黑",Font.BOLD,40));
            g.drawString("按下空格开始游戏",300,300);
        }
        if(isFail){
     
            g.setColor(Color.red);
            g.setFont(new Font("微软雅黑",Font.BOLD,40));
            g.drawString("游戏失败,按下空格重新开始!",200,300);
        }
    }


    //键盘监听事件
    @Override
    public void keyPressed(KeyEvent e) {
     
        //获得键盘按键
        int keyCode = e.getKeyCode();
        if(keyCode == KeyEvent.VK_SPACE){
     
            if(isFail){
      //游戏失败
                //重新开始
                isFail = false;
                init(); //初始化
            }
            else{
     
                isStart = !isStart;
            }
            repaint();
        }
        // 小蛇移动
        if(keyCode == KeyEvent.VK_LEFT){
     
            fx = "L";
        }else if(keyCode == KeyEvent.VK_RIGHT){
     
            fx = "R";
        }else if(keyCode == KeyEvent.VK_UP){
     
            fx = "U";
        }else if(keyCode == KeyEvent.VK_DOWN){
     
            fx = "D";
        }
    }

    // 事件监听----定时器--通过固定的时间刷新
    @Override
    public void actionPerformed(ActionEvent e) {
     
        if(isStart && isFail == false){
      // 游戏开始,小蛇动起来
            //  吃食物
            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 = length-1;i>0;i--){
     
                snakeX[i] = snakeX[i-1];
                snakeY[i] = snakeY[i-1];
            }
            // 走向
            if(fx.equals("R")){
     
                snakeX[0] = snakeX[0]+25;
                if(snakeX[0] > 850){
     
                    snakeX[0] = 25;
                }
            }else if(fx.equals("L")){
     
                snakeX[0] = snakeX[0]-25;
                if(snakeX[0] < 25){
     
                    snakeX[0] = 850;
                }
            }
            else if(fx.equals("U")){
     
                snakeY[0] = snakeY[0]-25;
                if(snakeY[0] < 75){
     
                   snakeY[0] = 650;
                }
            }
            else if(fx.equals("D")){
     
                snakeY[0] = snakeY[0]+25;
                if(snakeY[0] > 650){
     
                    snakeY[0] = 75;
                }
            }
            // 失败判定,撞到自己
            for(int i = 1;i<length;++i){
     
                if(snakeX[0]== snakeX[i] && snakeY[0] == snakeY[i]){
     
                    isFail = true;
                }
            }

            repaint(); //重新画一次
        }
        timer.start(); //开始定时器
    }


    @Override
    public void keyTyped(KeyEvent e) {
      }
    @Override
    public void keyReleased(KeyEvent e) {
      }

}

游戏的主启动类

package JavaGUI.snake;
import javax.swing.*;

/**
 * @Title: StartGame
 * @Package JavaGUI.snake
 * @Description:
 * @author: maze
 * @date 2020/10/21上午 8:47
 */
// 游戏的主启动类
public class StartGame {
     

    public static void main(String[] args) {
     
        JFrame frame = new JFrame();

        frame.setResizable(false); // 窗口大小不可变
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setBounds(10,10,900,750);
        // 把面板添加到 frame 中
        frame.add(new GamePanel());
        frame.setVisible(true);
    }
}

由于静态文件的图片资源在本地目录,直接运行无法加载

五、拓展

其实这个游戏只是提供一个思路,可以有很多优化的地方

  • 把用户信息保存到数据库中,包括上次玩的记录,每次加载的时候先登录用户,验证账号和密码
  • 联机登录,比如在界面上有两条蛇一起玩,涉及网络系统
  • 界面可以再变的美观一点
  • 怎么把这个程序打包成一个 jar 包,让用户点击就可以玩,不用在程序编译器中运行

你可能感兴趣的:(学习篇,&,应用篇,---,服务器,Java,实战篇,---,项目,GUI,编程实现贪吃蛇,贪吃蛇游戏,swing,java)