java-贪吃蛇小游戏

java GUI编程实现贪吃蛇小游戏

一 准备

首先准备三张图片,分别是:蛇头,蛇身体,食物。(尺寸 25 * 25 像素)
我创建的是一个springboot项目,所以我把这些静态资源放在了resources目录下的static文件夹下,创建个文件夹叫snake,再把这三张图片放进去。

image

二 新建一个Data类,作为图片资源


import cn.hutool.core.io.resource.ClassPathResource;

import javax.swing.*;
import java.net.URL;

/**
 * create by fzg
 * 2022/11/15 13:07
 * 获取数据,图片
 */

// 数据:图片资源
public class Data {

    // 图片资源放哪里都可以,只要能找到,并且将类型转化为URL
    // 我用的是胡图工具的 ClassPathResource

    // 蛇头
    public static URL headerURL = new ClassPathResource("/static/snake/header.png").getUrl();
    // 蛇身体
    public static URL bodyURL = new ClassPathResource("/static/snake/body.png").getUrl();
    // 食物
    public static URL foodURL  = new ClassPathResource("/static/snake/food.png").getUrl();


    public static ImageIcon header = new ImageIcon(headerURL);
    public static ImageIcon body = new ImageIcon(bodyURL);
    public static ImageIcon food = new ImageIcon(foodURL);

    

}

三 Snake类,定义蛇的数据结构和初始化方法


/**
 * create by fzg
 * 2022/11/15 13:33
 */

// 贪吃蛇基本类
public class Snake {

    // 定义蛇的数据结构
    int length; // 蛇的长度
    int[] snakex = new int[600]; // 蛇的x坐标
    int[] snakey = new int[500]; // 蛇的y坐标

    // 初始化方向
    String fx;

    // 初始化方法
    public void init(){
        // 初始化长度3
        length = 3;
        // 脑袋的坐标
        snakex[0] = 100;
        snakey[0] = 100;
        // 第一个身体的坐标
        snakex[1] = 75;
        snakey[1] = 100;
        // 第二个身体的坐标
        snakex[2] = 50;
        snakey[2] = 100;
        // 初始化方向往右
        fx = "D";

    }

    public Snake(){
        init();
    }

}

四 游戏面板类 GamePanel(核心代码)

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;

/**
 * create by fzg
 * 2022/11/15 13:39
 */

/**
 * 游戏面板
 * 键盘监听器
 * 定时器
 */
public class GamePanel extends JPanel implements KeyListener, ActionListener {

    // 游戏当前的状态
    boolean isStart = false; // 默认不开始
    // 游戏失败,默认FALSE
    boolean isFail = false;
    // 初始化成绩
    int score = 0;
    Snake snake = new Snake();

    // 定时器(小蛇快慢)
    Timer timer = new Timer(500,this);

    // 食物坐标
    int foodx;
    int foody;

    // 前一个按键
    String prePressKey = "";

    Random random = new Random();

    public GamePanel(){
        // 获取焦点
        this.setFocusable(true);
        // 获取键盘的监听事件
        this.addKeyListener(this);
        timer.start();
        createFood();
    }

    /**
     * 绘制面板,
     * 绘制游戏的画面
     */
    @Override
    protected void paintComponent(Graphics g){
        // 清屏,没有这段代码就会不停闪屏
        super.paintComponent(g);
        // 绘制静态面板,背景颜色:白色
        this.setBackground(Color.WHITE);
        // 默认游戏界面
        g.fillRect(25,75,850,600);

        // 食物
        Data.food.paintIcon(this,g,foodx,foody);

        // 画积分
        g.setColor(Color.blue);
        g.setFont(new Font("微软雅黑",Font.BOLD,18));
        g.drawString("长度: ",750,35);
        g.drawString("分数: ",750,53);
        g.setColor(Color.RED);
        g.drawString(snake.length + "",800,35);
        g.drawString(score + "",800,53);

        // 画小蛇
        for (int i = 0; i < snake.length; i++) {
            if (i == 0){
                // 画脑袋
                Data.header.paintIcon(this,g,snake.snakex[i],snake.snakey[i]);
            }else {
                Data.body.paintIcon(this,g,snake.snakex[i],snake.snakey[i]);
            }
        }

        // 游戏未开始时
        if (!isStart){
            g.setColor(Color.WHITE);
            g.setFont(new Font("微软雅黑",Font.BOLD,40));
            g.drawString("按下空格开始游戏", 200,300);
            // 说明一下:请确保键盘的WSAD键处于英文状态下
            g.setColor(Color.GREEN);
            g.setFont(new Font("微软雅黑",Font.PLAIN,25));
            g.drawString("请确保键盘上的WSAD键处于英文状态",200,350);
            g.drawString("除了键盘上的WSAD键,还可以按up,down,left,right键",200,400);
        }

        // 游戏失败
        if (isFail){
            g.setColor(Color.RED);
            g.setFont(new Font("微软雅黑",Font.BOLD,40));
            g.drawString("游戏结束",300,200);
            g.setFont(new Font("微软雅黑",Font.PLAIN,30));
            g.drawString("按下空格重新开始游戏", 300,300);
            timer.setDelay(500);
        }

    }

    /**
     * Invoked when an action occurs.
     *
     * @param e
     *
     * 事件监听
     * 通过固定事件监听
     */
    @Override
    public void actionPerformed(ActionEvent e) {

        if (isStart && !isFail){
            // 如果游戏状态是开始,就让小蛇动起来
            for (int i = snake.length - 1; i > 0; i--) {
                snake.snakex[i] = snake.snakex[i - 1];
                snake.snakey[i] = snake.snakey[i - 1];
            }
            // 走向
            switch (snake.fx){
                case "D":
                    snake.snakex[0] = snake.snakex[0] + 25;
                    // 边界判断(撞墙就死)
                    if (snake.snakex[0] > 850){
                        // 这是小蛇撞墙不会死,从另一边出来
                        // snake.snakex[0] = 25;
                        // 让小蛇撞墙就game over
                        isFail = true;
                    }
                    break;
                case "A":
                    snake.snakex[0] = snake.snakex[0] - 25;
                    // 边界判断(撞墙就死)
                    if (snake.snakex[0] < 25){
                        // snake.snakex[0] = 850;
                        isFail = true;
                    }
                    break;
                case "W":
                    snake.snakey[0] = snake.snakey[0] - 25;
                    // 边界判断(撞墙就死)
                    if (snake.snakey[0] < 75){
                        // snake.snakey[0] = 650;
                        isFail = true;
                    }
                    break;
                case "S":
                    snake.snakey[0] = snake.snakey[0] + 25;
                    // 边界判断(撞墙就死)
                    if (snake.snakey[0] > 650){
                        // snake.snakey[0] = 75;
                        isFail = true;
                    }
                    break;
            }

            // 把下面的代码注释,让小蛇可以掉头,并且吃到自己的身体也不会死
            // 判断失败,撞到自己就算失败
//            for (int i = 1; i < snake.length; i++) {
//                if (snake.fx.equals("W") && prePressKey.equals("S")){
//                    isFail = false;
//                }else if (snake.fx.equals("S") && prePressKey.equals("W")){
//                    isFail = false;
//                }else if (snake.fx.equals("A") && prePressKey.equals("D")){
//                    isFail = false;
//                }else if(snake.fx.equals("D") && prePressKey.equals("A")){
//                    isFail = false;
//                }else {
//                    if (snake.snakex[0] == snake.snakex[i] && snake.snakey[0] == snake.snakey[i]) {
//                        isFail = true;
//                    }
//                }
//            }

            // 吃到食物
            if (snake.snakex[0] == foodx && snake.snakey[0] == foody){
                // 长度加1
                snake.length++;
                // 加分
                score = addCarry(score,10);

                /**
                 * 分数等级:
                 * 0 - 50:原速度      0.5s
                 * 50 - 100:-100毫秒  0.4s
                 * 100 - 150:-100毫秒  0.3s
                 * 150 - 200:-100毫秒  0.2s
                 * 200 - 250: -100毫秒  0.1s
                 * 250 - 以上: -50毫秒   0.05s 最快速度了
                 */
                if (score >= 50 && score < 100){
                    timer.setDelay(400);
                }else if (score >= 100 && score < 150){
                    timer.setDelay(300);
                }else if (score >= 150 && score < 200){
                    timer.setDelay(200);
                }else if (score >= 200 && score < 250){
                    timer.setDelay(100);
                }else if (score >= 250){
                    timer.setDelay(50);
                }

                // 再次生成食物
                createFood();
            }
            // 重画页面
            repaint();
        }
        // 开启定时
        timer.start();
    }

    /**
     * 生成食物
     *  g.fillRect(25,75,850,600); 界面的位置和宽高,
     *  游戏界面坐标:25,75 ;距离左边25,距离上边75
     *  宽度:850 ; 高度: 600
     *  食物必须随机生成在游戏界面内
     *  所以:食物的x坐标 必须在25 到 850 + 25之间,
     *  如果刚好等于850 + 25那么食物就生成在游戏界面的右边所以不能等于850 + 25
     *  y坐标也是
     *  所以食物的生成坐标区间
     *  x: [25,850] 
     *  y: [75,675-25]
     */
    public void createFood(){
         /**
         * 把食物随机分配到界面上
         * random.nextInt生成随机整数
         * 850 / 25 = 34 最大值
         * 650 / 25 = 26 最大值
         */
        foodx = 25 + 25 * random.nextInt(34);
        foody = 75 + 25 * random.nextInt(26);
    }

    /**
     * 位运算实现相加
     */
    int addCarry(int a,int b){
        int result;
        int sum,carry;
        sum = a ^ b;
        carry = (a & b) << 1;
        if (carry == 0){
            result = sum;
        }else {
            result = addCarry(sum,carry);
        }
        return result;
    }


    /**
     * Invoked when a key has been typed.
     * See the class description for {@link KeyEvent} for a definition of
     * a key typed event.
     *
     * @param e
     */
    @Override
    public void keyTyped(KeyEvent e) {

    }

    /**
     * Invoked when a key has been pressed.
     * See the class description for {@link KeyEvent} for a definition of
     * a key pressed event.
     * 键盘按下
     * @param e
     */
    @Override
    public void keyPressed(KeyEvent e) {
        // 获取键盘按键是哪个
        int keyCode = e.getKeyCode();
        /**
         * 上:38
         * 下:40
         * 左:37
         * 右:39
         */
        if (keyCode == KeyEvent.VK_SPACE){
            // 按下的是空格
            if (isFail){
                // 重新开始
                isFail = false;
                snake.init();
                createFood();
                score = 0;
            }else {
                isStart = !isStart;
            }
            repaint();
        }

        // 小蛇移动
        switch (keyCode){
            case KeyEvent.VK_W:
                snake.fx = "W";
                break;
            case KeyEvent.VK_S:
                snake.fx = "S";
                break;
            case KeyEvent.VK_A:
                snake.fx = "A";
                break;
            case KeyEvent.VK_D:
                snake.fx = "D";
                break;
            case 37:
                snake.fx = "A";
                break;
            case 38:
                snake.fx = "W";
                break;
            case 39:
                snake.fx = "D";
                break;
            case 40:
                snake.fx = "S";
                break;
        }
        prePressKey = snake.fx;
    }

    /**
     * Invoked when a key has been released.
     * See the class description for {@link KeyEvent} for a definition of
     * a key released event.
     *
     * @param e
     */
    @Override
    public void keyReleased(KeyEvent e) {

    }
}

1. 游戏规则:蛇头撞墙结束游戏

2. 根据积分等级,蛇的移动速度加快,积分越高,移动速度越快

3. 键盘:WSAD 和 上下左右键 都可以控制蛇的移动方向

4. 蛇可以撞到自己的身体,可以直接反方向移动,都不会结束游戏

五 最后一个类,启动类 StartGame


import javax.swing.*;

/**
 * create by fzg
 * 2022/11/15 14:23
 */

// 游戏启动类
public class StartGame {

    public static void main(String[] args) {
        JFrame jFrame = new JFrame();
        jFrame.setBounds(500, 280,950,800);
        jFrame.setResizable(false); // 窗口大小不准拉伸
        // 正常的游戏都在面板上
        GamePanel gamePanel = new GamePanel();
        jFrame.add(gamePanel);
        jFrame.setVisible(true);
        jFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

    }

}

六. 这样一个贪吃蛇小游戏就做成了



posted @ 2022-11-16 13:34  合起来的彳亍  阅读(305)  评论(0编辑  收藏  举报