zoukankan      html  css  js  c++  java
  • Java贪吃蛇小游戏

    贪吃蛇

    思路

    1. 首先构思游戏布局,计算合理的坐标系。

    2. 绘制静态数据(广告、初始小蛇、提示信息、棋盘)

      image-20200515134905430

    3. 添加键盘监听事件,改变游戏状态以及小蛇运动方向

    4. 添加定时器,让小蛇在一段时间内移动一定的距离

    5. 随机产生食物,并监听食物状态是否被吃

    6. 处理游戏结束事件

    7. 扩展相关游戏机制(积分、等级)

      • 定义数据
      • 绘制图像
      • 事件监听

    注意事项

    • 导入文件资源时,通过类的相对路径获取

      • URL headURL = Data.class.getResource("header.png"); // 这是放在源代码同一个包下的文件

      • URL headURL = Data.class.getResource("/header.png"); // 这是放在项目根目录下的文件

    • 键盘监听时,需要自动获取焦点。

      • this.setFocusable(true); // 获取焦点
        this.addKeyListener(new MyKeyListener());
    • 在修改数据之后,需要repaint重绘图形

    • 小蛇运动时,需要注意边界问题

    • 食物随机产生的坐标也要限制在游戏区域内

    • 添加游戏的可玩性

    具体实现

    Data.java 存放所有图像数据

    package snake;
    
    import javax.swing.*;
    import java.net.URL;
    
    /**
     * 数据中心
     */
    public class Data {
    
        // 相对路径
        // 绝对路径  / 当前项目-->"GUI编程目录"
    
        public static URL headURL = Data.class.getResource("header.png");
        public static URL upURL = Data.class.getResource("up.png");
        public static URL downURL = Data.class.getResource("down.png");
        public static URL leftURL = Data.class.getResource("left.png");
        public static URL rightURL = Data.class.getResource("right.png");
        public static URL bodyURL = Data.class.getResource("body.png");
        public static URL foodURL = Data.class.getResource("food.png");
        public static URL foodURL2 = Data.class.getResource("food2.png");
    
        public static ImageIcon header = new ImageIcon(headURL);
        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 ImageIcon body = new ImageIcon(bodyURL);
        public static ImageIcon food = new ImageIcon(foodURL);
        public static ImageIcon food2 = new ImageIcon(foodURL2);
    }
    
    

    StartGame.java 游戏启动类

    package snake;
    
    import javax.swing.*;
    import java.awt.*;
    
    public class StartGame {
        public static void main(String[] args) {
            Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
            int screenWidth = (int) screenSize.getWidth();
            int screenHeight = (int) screenSize.getHeight();
            System.out.println("屏幕宽度:" + screenWidth + ",屏幕高度:" + screenHeight);
    
            // 本游戏固定窗体大小(900,720)不可变
            int x = (screenWidth - 900) / 2;
            int y = (screenHeight - 720) / 2;
            System.out.println("相对坐标x:" + x + ",y:" + y);
    
            JFrame jFrame = new JFrame("贪吃蛇");
            jFrame.setBounds(x, y, 900, 720);
            jFrame.setResizable(false);
            jFrame.add(new GamePanel());
            jFrame.setVisible(true);
            jFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    
        }
    }
    

    GamePanel.java 处理游戏逻辑

    package snake;
    
    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;
    
    public class GamePanel extends JPanel {
        // 蛇的数据结构
        int length;
        int[] snakeX = new int[600];
        int[] snakeY = new int[500];
        char direction;
    
        // 食物的坐标
        int foodx;
        int foody;
        Random random = new Random();
    
        // 计数器
        int count;
        int score;
        int worth;
    
        boolean isStart;
        boolean isFail;
    
        // 定时器,每隔100毫秒执行一次参数的事件。
        int interval = 100;
        Timer timer = new Timer(interval, new MyActionLinstener());
    
    
        public void init() {
            // 默认游戏未开始
            this.isStart = false;
            this.isFail = false;
    
            // 初始化速度
            interval = 200;
            timer.setDelay(interval);
    
            // 初始化小蛇数据
            this.length = 3;
            this.score = 0;
            this.snakeX[0] = 100;
            this.snakeY[0] = 100;
            this.snakeX[1] = 75;
            this.snakeY[1] = 100;
            this.snakeX[2] = 50;
            this.snakeY[2] = 100;
            this.direction = 'R';
    
            // 随机产生事物的坐标
            this.foodx = 25 + 25 * random.nextInt(34);
            this.foody = 75 + 25 * random.nextInt(24);
            this.count = 1; // 统计食物数量
    
            // 获得焦点和键盘监听事件
            this.setFocusable(true);
            // 添加键盘监听事件
            this.addKeyListener(new MyKeyListener());
            // 开启定时器
            timer.start();
        }
    
        public GamePanel() {
            init();
        }
    
        // 自动绘制面板
        // paintComponent()是swing的一个方法,相当于图形版的main(),是会自执行的。
        @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);
    
            // 画积分
            g.setColor(Color.white);
            g.setFont(new Font("微软雅黑", Font.BOLD, 18));
            g.drawString("长度:" + length, 750, 30);
            g.drawString("分数:" + score, 750, 56);
    
            // 绘制食物
            if (count % 5 != 0) {
                Data.food.paintIcon(this, g, foodx, foody);
                worth = 1;
            } else {
                Data.food2.paintIcon(this, g, foodx, foody);
                worth = 3;
            }
    
            // 游戏状态 默认为暂停 需要提示信息
            if (!isStart) {
                g.setColor(Color.white);
                g.setFont(new Font("微软雅黑", Font.BOLD, 40));
                g.drawString("按下空格开始游戏", 300, 300);
            }
    
            // 画小蛇 初始化向右 长度为3
            // 选择头部的方向
            ImageIcon head = Data.right;
            switch (this.direction) {
                case 'U':
                    head = Data.up;
                    break;
                case 'D':
                    head = Data.down;
                    break;
                case 'L':
                    head = Data.left;
                    break;
                case 'R':
                    head = Data.right;
                    break;
                default:
                    break;
            }
    
            head.paintIcon(this, g, snakeX[0], snakeY[0]);
            for (int i = 1; i < this.length; i++) {
                Data.body.paintIcon(this, g, snakeX[i], snakeY[i]);
            }
    
            // 结束状态
            if (isFail) {
                g.setColor(Color.red);
                g.setFont(new Font("微软雅黑", Font.BOLD, 100));
                g.drawString("Game Over", 150, 200);
                g.drawString("按空格重新开始", 100, 400);
            }
    
    
        }
    
    
        /**
         * 内部类监听键盘事件
         */
        class MyKeyListener extends KeyAdapter {
            // 只需要监听键盘按下
            @Override
            public void keyPressed(KeyEvent e) {
                int keyCode = e.getKeyCode();
                // System.out.println(keyCode);
                if (keyCode == KeyEvent.VK_SPACE) {
                    if (isFail) {
                        // 重新开始
                        init();
                        isStart = true;
                    } else {
                        isStart = !isStart;
                    }
    
                    repaint();
                }
    
                // 内层增加一个判断,180度转向 不生效
                switch (keyCode) {
                    case KeyEvent.VK_UP:
                        if (direction != 'D') {
                            direction = 'U';
                        }
                        break;
                    case KeyEvent.VK_DOWN:
                        if (direction != 'U') {
                            direction = 'D';
                        }
                        break;
                    case KeyEvent.VK_LEFT:
                        if (direction != 'R') {
                            direction = 'L';
                        }
                        break;
                    case KeyEvent.VK_RIGHT:
    
                        if (direction != 'L') {
                            direction = 'R';
                        }
                        break;
                    default:
                        break;
                }
    
    
                // 调整速度  小键盘的 + -
                if (keyCode == 107) {
                    interval = interval > 10 ? interval - 10 : 10;
                    timer.setDelay(interval);
                    System.out.println("加速" + interval);
                } else if (keyCode == 109) {
                    interval += 20;
                    timer.setDelay(interval);
                    System.out.println("减速" + interval);
                }
            }
        }
    
        class MyActionLinstener implements ActionListener {
            // 事件监听类
            @Override
            public void actionPerformed(ActionEvent e) {
                // 通过事件刷新界面
                // 如果游戏开始且未失败,则刷新界面
                if (isStart && !isFail) {
    
                    // 吃食物  蛇头与食物重合
                    if (snakeX[0] == foodx && snakeY[0] == foody) {
                        length++; // 吃了边长
                        foodx = 25 + 25 * random.nextInt(34);
                        foody = 75 + 25 * random.nextInt(24);
                        count++; // 统计食物数量
                        score += worth;
    
                        // 这里可以设置积分到一定值,增加移动速度
                        if (score < 5) {
                            interval = 200;
                            timer.setDelay(interval);
                            System.out.println("一级速度");
                        } else if (score < 15) {
                            interval = 150;
                            timer.setDelay(interval);
                            System.out.println("二级速度");
                        } else if (score < 25) {
                            interval = 100;
                            timer.setDelay(interval);
                            System.out.println("三级速度");
                        } else if (score < 50) {
                            interval = 50;
                            timer.setDelay(interval);
                            System.out.println("四级速度");
                        }
    
                    }
    
    
                    for (int i = length - 1; i > 0; i--) {
                        // 从最后一节开始继承前一节的位置
                        snakeX[i] = snakeX[i - 1];
                        snakeY[i] = snakeY[i - 1];
                    }
                    // 头部找新的路,利用三元运算符判断边界重置
                    switch (direction) {
                        case 'U':
                            snakeY[0] = snakeY[0] <= 75 ? 650 : snakeY[0] - 25;
                            break;
                        case 'D':
                            snakeY[0] = snakeY[0] >= 650 ? 75 : snakeY[0] + 25;
                            break;
                        case 'L':
                            snakeX[0] = snakeX[0] <= 25 ? 850 : snakeX[0] - 25;
                            break;
                        case 'R':
                            snakeX[0] = snakeX[0] % 850 + 25;
                            break;
                        default:
                            break;
                    }
    
                    // 失败判定
                    for (int i = 1; i < length; i++) {
                        if (snakeX[0] == snakeX[i] && snakeY[0] == snakeY[i]) {
                            isFail = true;
                        }
                    }
                    repaint();
                }
            }
        }
    
    }
    
    

    游戏效果

    snake

    该版本已知bug

    小蛇不能反向运动,玩家操作过快导致的意外死亡。

    比如小蛇向↑运动过程中,玩家按 ↓ 是无效的,但如果按 ← 的瞬间按 ↓

    由于定时器的刷新速度没有跟上玩家的操作,游戏帧尚未刷新就已经有了下一次操作。

    误判为小蛇回头撞了自己,游戏结束。

    第二个问题是

    空格本应该具有暂停/继续/重玩的功能。

    游戏局数为偶数时,空格键暂停功能失效。

    打包游戏

    首先通过IDEA生成Jar包,再通过exe4j将jar打包为exe

    image-20200516113408392

    具体操作跳转至Java桌面应用程序打包。

  • 相关阅读:
    部署应用映射外部目录、迁移与备份、容器保存镜像、镜像打压成压缩包、压缩包恢复为镜像、dockerfiles、私有仓库、docker-compose、部署多应用、容器间通信
    docker的介绍、架构图、乌班图、安装、远程仓库、镜像操作、容器操作、应用部署、
    flask-script(制定命令)、sqlschemy、orm的使用、线程安全、增删查改、多表操作、flask-sqlalchemy
    g对象、flask-session、数据库连接池、wtforms(forms组件)、信号
    中间件、猴子补丁、蓝图、请求上下文执行流程
    flask配置文件、模板、request对象的属性和方法、响应对象方法、闪现、session的使用、请求扩展、全局标签、全局过滤器、
    flask入门、配置文件、路由系统、路由的本质、CBV
    基本权限chmod、软件管理-二进制安装、源码安装、进程管理、系统服务、
    对爬取京东商品按照标题为其进行自动分类---基于逻辑回归的文本分类
    学习进度3.16
  • 原文地址:https://www.cnblogs.com/1101-/p/12897167.html
Copyright © 2011-2022 走看看