贪吃蛇:从零开始搭建一个完整的小游戏

目录

导语:

一、游戏框架

二、蛇的实现

三、绘制游戏界面

四、食物

五、移动蛇

六.得分系统,是否吃到食物

七、检查碰撞

八、处理按键事件

九、得分系统

十、游戏状态管理


导语:

    贪吃蛇这个经典的小游戏,我上学的时候就曾经写过一个,参加校内竞赛还拿了个参与奖。想找以前代码,结果发现没有保存。今天有空就再次整理编写一个。理一理思路。

一、游戏框架

首先,我们需要一个游戏框架,用来处理游戏的逻辑、绘制游戏界面等。

游戏框架是贪吃蛇游戏的基础结构,负责处理游戏的逻辑、绘制游戏界面以及处理用户输入等任务。在Java中,我们可以使用Swing库或JavaFX库来创建游戏窗口和图形界面。

Swing库:Swing是Java提供的一套用于创建图形用户界面(GUI)的工具包。通过Swing,你可以创建窗口、按钮、标签等GUI组件,并通过监听器来响应用户的输入事件。Swing适合于简单的2D游戏,对于贪吃蛇游戏来说,它是一个不错的选择。

下面是创建游戏框架的基本步骤:

使用Swing库创建游戏框架

  • 导入Swing库:在Java项目中,确保你已经导入了Swing库。

  • 创建游戏窗口:使用JFrame类创建游戏窗口,并设置窗口标题、大小等属性。

import javax.swing.*;

/**
 * 微澜贪吃蛇游戏窗口
 */
public class SnakeGame extends JFrame {

    public SnakeGame() {
      add(new GamePanel(this));
        setTitle("微澜贪吃蛇");
        setSize(800, 600); // 设置窗口大小
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 设置关闭行为
        setLocationRelativeTo(null); // 将窗口置于屏幕中央
        setVisible(true); // 显示窗口
    }

    public static void main(String[] args) {
        // 创建游戏实例
        SwingUtilities.invokeLater(() -> new SnakeGame());
    }
}

代码运行如下图所示:

图片

二、蛇的实现

蛇是游戏中的核心角色。我们需要实现以下功能:

蛇的移动:蛇可以朝四个方向之一移动,每次移动一个单位长度。

蛇身的增长:当蛇吃到食物时,蛇身长度增加一个单位。

碰撞检测:检测蛇头是否与墙壁、自己的身体或食物发生碰撞,如果碰撞则游戏结束。

创建一个游戏面板类:GamePanel.java

编写完之后需要将类添加到窗口中。

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 GamePanel extends JPanel implements ActionListener, KeyListener {

    private static final int WIDTH = 600;
    private static final int HEIGHT = 600;
    private static final int UNIT_SIZE = 20;
    private static final int GAME_UNITS = (WIDTH * HEIGHT) / UNIT_SIZE;
    private static final int DELAY = 100;

    private final int[] x = new int[GAME_UNITS];
    private final int[] y = new int[GAME_UNITS];
    private int bodyParts = 6;
    private int foodX;
    private int foodY;
    private char direction = 'R';
    private boolean running = false;
    private Timer timer;
    
    private SnakeGame game; // 添加 SnakeGame 实例变量

    public GamePanel() {
        this.game = game; // 初始化 game 对象
        setPreferredSize(new Dimension(WIDTH, HEIGHT));
        setBackground(Color.BLACK);
        setFocusable(true);
        addKeyListener(this);
        startGame();
    }

    public void startGame() {
        newFood();
        running = true;
        timer = new Timer(DELAY, this);
        timer.start();
    }

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        draw(g);
    }

    public void draw(Graphics g) {
        // 绘制游戏界面
    }

    public void newFood() {
        // 生成新的食物的位置
    }

    public void move() {
        // 移动蛇
    }

    public void checkFood() {
        // 检查是否吃到食物
    }

    public void checkCollision() {
        // 检查碰撞
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        if (running) {
            move();
            checkFood();
            checkCollision();
            repaint();
        }
    }

    @Override
    public void keyPressed(KeyEvent e) {
        // 处理按键事件
    }

    @Override
    public void keyTyped(KeyEvent e) {}

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

(具体代码过程,不完全展示)

    这个类继承自JPanel,用于绘制游戏界面和处理游戏逻辑。在这个类中,我们定义了蛇的移动、食物生成、碰撞检测等方法,并实现了ActionListenerKeyListener接口来处理游戏逻辑和键盘输入。

    可以根据自己的一些设计需要填充drawnewFoodmovecheckFoodcheckCollision等方法的具体实现逻辑。

三、绘制游戏界面

用于绘制蛇、食物和游戏边界等元素。

public void draw(Graphics g) {
    if (running) {
        // 绘制食物
        g.setColor(Color.RED);
        g.fillRect(foodX, foodY, UNIT_SIZE, UNIT_SIZE);

        // 绘制蛇头和蛇身
        for (int i = 0; i < bodyParts; i++) {
            if (i == 0) {
                g.setColor(Color.GREEN); // 蛇头颜色
                g.fillRect(x[i], y[i], UNIT_SIZE, UNIT_SIZE);
            } else {
                g.setColor(new Color(45, 180, 0)); // 蛇身颜色
                g.fillRect(x[i], y[i], UNIT_SIZE, UNIT_SIZE);
            }
        }

        // 绘制游戏边界
        g.setColor(Color.YELLOW);
        g.drawLine(0, 0, WIDTH, 0);
        g.drawLine(0, 0, 0, HEIGHT);
        g.drawLine(WIDTH - 1, 0, WIDTH - 1, HEIGHT);
        g.drawLine(0, HEIGHT - 1, WIDTH, HEIGHT - 1);
    }
}

    这段代码将会在游戏面板上绘制食物、蛇头、蛇身和游戏边界。食物使用红色填充,蛇头使用绿色填充,蛇身使用深绿色填充,游戏边界使用黄色线条绘制。

四、食物

生成新食物的位置

public void newFood() {
    foodX = (int) (Math.random() * (WIDTH / UNIT_SIZE)) * UNIT_SIZE;
    foodY = (int) (Math.random() * (HEIGHT / UNIT_SIZE)) * UNIT_SIZE;
}

    这段代码将随机生成食物的位置。食物的横坐标foodX和纵坐标foodY被限制在游戏窗口的网格上,每个网格的大小为UNIT_SIZE。因此,食物的位置会在游戏窗口的格子中随机选择一个位置。

五、移动蛇

游戏的重中之重就是移动了。

public void move() {
    // 将蛇的身体部分向前移动
    for (int i = bodyParts; i > 0; i--) {
        x[i] = x[i - 1];
        y[i] = y[i - 1];
    }
    
    // 根据当前方向移动蛇头
    switch (direction) {
        case 'U':
            y[0] -= UNIT_SIZE;
            break;
        case 'D':
            y[0] += UNIT_SIZE;
            break;
        case 'L':
            x[0] -= UNIT_SIZE;
            break;
        case 'R':
            x[0] += UNIT_SIZE;
            break;
    }
}

        这段代码将会让蛇的身体部分向前移动一步,然后根据当前方向移动蛇头。如果蛇向上移动,蛇头的纵坐标会减少一个单位大小;如果蛇向下移动,蛇头的纵坐标会增加一个单位大小;如果蛇向左移动,蛇头的横坐标会减少一个单位大小;如果蛇向右移动,蛇头的横坐标会增加一个单位大小。

六.得分系统,是否吃到食物

public void checkFood() {
    if (x[0] == foodX && y[0] == foodY) {
        // 如果蛇头的位置与食物的位置重合,则吃到了食物
        bodyParts++; // 增加蛇身长度
        newFood(); // 生成新的食物
    }
}

    这段代码会检查蛇头的位置是否与食物的位置重合。如果重合,则表示蛇吃到了食物,蛇的身体长度会增加,并且生成新的食物位置。

七、检查碰撞

用于检查蛇是否与边界或自身发生碰撞:碰撞之后也就是结束

public void checkCollision() {
    // 检查是否与边界发生碰撞
    if (x[0] < 0 || x[0] >= WIDTH || y[0] < 0 || y[0] >= HEIGHT) {
        running = false; // 游戏结束
        return;
    }

    // 检查是否与自身发生碰撞
    for (int i = bodyParts; i > 0; i--) {
        if (x[0] == x[i] && y[0] == y[i]) {
            running = false; // 游戏结束
            return;
        }
    }
}

    首先检查蛇头是否撞到了游戏边界,如果是,则游戏结束。然后,它检查蛇头是否与自身的任何部分重合,如果是,则游戏也会结束。

八、处理按键事件

补充keyPressed(KeyEvent e)方法的代码,用于处理按键事件,改变蛇的移动方向。

@Override
public void keyPressed(KeyEvent e) {
    switch (e.getKeyCode()) {
        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;
    }
}

       根据用户按下的方向键来改变蛇的移动方向。如果用户按下上箭头键,则蛇向上移动;如果按下下箭头键,则蛇向下移动;如果按下左箭头键,则蛇向左移动;如果按下右箭头键,则蛇向右移动。同时,还加入了一些限制条件,例如禁止蛇直接反向移动(比如,当蛇向上移动时,不允许按下下箭头键使其立即向下移动)。

九、得分系统

玩游戏还有很重要就是自己有多厉害。那么得分展示那就是必不可少的了。

首先,在SnakeGame类中添加得分变量和相应的方法:

public class SnakeGame extends JFrame {
    // 其他代码...
    
    private int score = 0;

    public int getScore() {
        return score;
    }

    public void increaseScore() {
        score += 10; // 每次吃到食物增加10分
    }
}

GamePanel类的draw(Graphics g)方法中添加绘制得分的逻辑:

public void draw(Graphics g) {
    // 绘制游戏界面的其他内容...
    
    // 绘制得分
    g.setColor(Color.WHITE);
    g.setFont(new Font("Arial", Font.BOLD, 20));
    g.drawString("Score: " + game.getScore(), 20, 30);
}

    这样,每次游戏界面重新绘制时,都会显示当前的得分。接下来,在checkFood()方法中调用increaseScore()方法,以便在蛇吃到食物时增加得分:

public void checkFood() {
    if (x[0] == foodX && y[0] == foodY) {
        game.increaseScore(); // 增加得分
        bodyParts++; // 增加蛇身长度
        newFood(); // 生成新的食物
    }
}

现在,游戏界面会显示当前的得分,并且每次蛇吃到食物时,得分都会增加。

十、游戏状态管理

    要添加游戏状态管理,我们需要在 SnakeGame 类中添加游戏状态的枚举,并在 GamePanel 类中根据不同的游戏状态来控制游戏逻辑的执行。让我们一步步完成这个任务。

首先,在 SnakeGame 类中添加游戏状态的枚举:

public enum GameState {
    RUNNING,
    PAUSED,
    GAME_OVER
}

public class SnakeGame extends JFrame {
    // 其他代码...

    private GameState gameState = GameState.RUNNING;

    public GameState getGameState() {
        return gameState;
    }

    public void setGameState(GameState gameState) {
        this.gameState = gameState;
    }

    // 其他代码...
}

    在这里,我们定义了一个 GameState 枚举,表示游戏的不同状态。我们还添加了一个 gameState 实例变量来保存当前的游戏状态,并提供了相应的访问方法。

    接下来,在 GamePanel 类中根据不同的游戏状态来控制游戏逻辑的执行。在 actionPerformed 方法中,我们只在游戏运行中(RUNNING 状态)执行游戏逻辑:

public void actionPerformed(ActionEvent e) {
    if (game.getGameState() == GameState.RUNNING) {
        move();
        checkFood();
        checkCollision();
        repaint();
    }
}

    然后,在 paintComponent 方法中,我们根据游戏状态来绘制相应的内容,比如游戏进行中绘制游戏界面,游戏暂停时绘制暂停提示,游戏结束时绘制游戏结束提示:

public void paintComponent(Graphics g) {
        super.paintComponent(g);
        switch (game.getGameState()) {
            case RUNNING:
                draw(g);
                break;
            case PAUSED:
                // 绘制暂停提示
                g.setColor(Color.WHITE);
                g.setFont(new Font("Arial", Font.BOLD, 30));
                g.drawString("PAUSE!!!", WIDTH / 2 - 100, HEIGHT / 2);
                break;
            case GAME_OVER:
                // 绘制游戏结束提示
                g.setColor(Color.WHITE);
                g.setFont(new Font("Arial", Font.BOLD, 30));
                g.drawString("Game Over!!!", WIDTH / 2 - 100, HEIGHT / 2);
                break;
        }
    }

按键操作部分需要补充逻辑:

@Override
public void keyPressed(KeyEvent e) {
    switch (e.getKeyCode()) {
        //其他代码
        case KeyEvent.VK_SPACE:
            if (game.getGameState() == GameState.RUNNING) {
                game.setGameState(GameState.PAUSED); // 暂停游戏
            } else if (game.getGameState() == GameState.PAUSED) {
                game.setGameState(GameState.RUNNING); // 恢复游戏
            }
            break;
    }
}

    在这个更新后的 keyPressed 方法中,我们添加了对空格键的监听。如果游戏正在运行,按下空格键会将游戏状态设为暂停;如果游戏已经暂停,再次按下空格键会将游戏状态设为运行。这样就实现了按空格键暂停和恢复游戏的功能。

到这一步一个简单的贪吃蛇小游戏就搭建成功了。

图片

代码地址:https://gitee.com/zhangyang214/snake.git

欢迎讨论!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/506441.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

设计模式-概述篇

1. 掌握设计模式的层次 第1层&#xff1a;刚开始学编程不久&#xff0c;听说过什么是设计模式第2层&#xff1a;有很长时间的编程经验&#xff0c;自己写了很多代码&#xff0c;其中用到了设计模式&#xff0c;但是自己却不知道第3层&#xff1a;学习过了设计模式&#xff0c;…

【机器学习】无监督学习与聚类技术:解锁数据的隐藏结构

无监督学习介绍 无监督学习&#xff0c;作为机器学习的一大分支&#xff0c;专注于探索未经标记的数据集中的潜在结构。不同于有监督学习&#xff0c;无监督学习不依赖于外部提供的标签或输出结果&#xff0c;而是通过数据本身的特征来寻找模式、聚类或降维。这种学习方法在多…

03-MySQl数据库的-用户管理

一、创建新用户 mysql> create user xjzw10.0.0.% identified by 1; Query OK, 0 rows affected (0.01 sec) 二、查看当前数据库正在登录的用户 mysql> select user(); ---------------- | user() | ---------------- | rootlocalhost | ---------------- 1 row …

PI案例分享--2000A核心电源网络的设计、仿真与验证

目录 摘要 0 引言 1 为什么需要 2000A 的数字电子产品? 2 2000A 的供电电源设计 2.1 "MPM3698 2*MPM3699"的 MPS扩展电源架构 2.2 使用恒定导通时间(COT)模式输出核心电压的原因 2.3 模块化 VRM 的优势 2.4 用步进负载验证2000A的设计难点 2.4.1 电源网络 …

初始Java篇(JavaSE基础语法)(5)(类和对象(上))

个人主页&#xff08;找往期文章包括但不限于本期文章中不懂的知识点&#xff09;&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 目录 面向对象的初步认知 面向对象与面向过程的区别 类的定义和使用 类的定义格式 类的实例化 this引用 什么是this引用&#xff1f; this引用…

Python爬虫-懂车帝城市销量榜单

前言 本文是该专栏的第23篇,后面会持续分享python爬虫干货知识,记得关注。 最近粉丝留言咨询某汽车平台的汽车销量榜单数据,本文笔者以懂车帝平台为例,采集对应的城市汽车销量榜单数据。 具体的详细思路以及代码实现逻辑,跟着笔者直接往下看正文详细内容。(附带完整代码…

Gradle 使用详解

目录 一. 前言 二. 下载与安装 2.1. 下载 2.2. 配置环境变量 2.3. 配置镜像 2.3.1. 全局设置 2.3.2. 项目级设置 三. Gradle 配置文件 3.1. build.gradle 3.2. settings.gradle 3.3. gradle.properties 3.4. init.d 目录 3.5. buildSrc 目录 四. Java Library 插…

计算机网络——28自治系统内部的路由选择

自治系统内部的路由选择 RIP 在1982年发布的BSD-UNIX中实现Distance vector算法 距离矢量&#xff1a;每条链路cost 1&#xff0c;# of hops(max 15 hops)跳数DV每隔30秒和邻居交换DV&#xff0c;通告每个通告包括&#xff1a;最多25个目标子网 RIP通告 DV&#xff1a;在…

Qt笔记-解决Qt程序连不上数据库MySQL数据库(重编libqsqlmysql.so)

使用QSqlDatabase连接MySQL数据库时。在自己程序配置没有错误的情况下报这类错误&#xff1a; QSqlDatabase: QMYSQL driver not loaded QSqlDatabase::exec: database not open 造成这样的问题大多数是libqsqlmysql.so有问题。 Qt的QSqlDatabase使用的是libqsqlmysql.so&a…

文章解读与仿真程序复现思路——电网技术EI\CSCD\北大核心《偏远地区能源自洽系统源储容量协同配置方法》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

HTTP 常见面试题(计算机网络)

HTTP 基本概念 一、HTTP 是什么&#xff1f; HTTP(HyperText Transfer Protocol) &#xff1a;超文本传输协议。 HTTP 是一个在计算机世界里专门在「两点」之间「传输」文字、图片、音频、视频等「超文本」数据的「约定和规范」。 「HTTP 是用于从互联网服务器传输超文本到本…

《FFmpeg开发实战:从零基础到短视频上线》出版后记

在软件开发行业当中&#xff0c;音视频是比较小众的方向&#xff0c;以至于很多程序员都没听说过YUV&#xff0c;就更别说了FFmpeg。实际上FFmpeg很早就有了&#xff0c;只是它采用C语言编写&#xff0c;所以在WEB当道的国内软件业中&#xff0c;FFmpeg就像独孤求败那样&#x…

PVE设置显卡直通(二:Linux显卡直通,以及Linux系统下安装cuda库)

PVE设置显卡直通(一:硬件设置) 本文仅记录PVE关于Linux下的显卡直通步骤 例程不过多阐述 ps: 无直通经验的同学,先参阅 PVE设置显卡直通(一:硬件设置),再参阅本博文 参阅完成 PVE设置显卡直通(一:硬件设置)后,直接在PVE面板中添加显卡硬件到自己的主机即可,此文中…

JavaScript(二)---【js数组、js对象、this指针】

零.前言 JavaScript(一)---【js的两种导入方式、全局作用域、函数作用域、块作用域】-CSDN博客 一.js数组 在js中也有数组的概念&#xff0c;数组使用“[]”定义&#xff0c;其中数组中还可以嵌套数组从而达到多层数组的作用。 访问数组中的某个元素&#xff0c;我们可以直…

什么是HTTP? HTTP 和 HTTPS 的区别?

文章目录 一、HTTP二、HTTPS三、区别参考文献 一、HTTP HTTP (HyperText Transfer Protocol)&#xff0c;即超文本运输协议&#xff0c;是实现网络通信的一种规范 在计算机和网络世界有&#xff0c;存在不同的协议&#xff0c;如广播协议、寻址协议、路由协议等等… 而HTTP是…

【Vue】搭建第一个vue3+vite前段项目

不要奇怪我为啥突然开始写前端的文章&#xff0c;唉&#xff0c;一切都是公司的任务罢了。 其实这周学习了前端和coverity&#xff0c;但是后者就算学了我也不能写在我博客里&#xff0c;所以还是写一写前端吧。 node.js 和 npm npm是一个类似于 maven 的包管理器。 去以下…

55 npm run serve 和 npm run build 的分包策略

前言 这里我们来看一下 vue 这边 打包的时候的一些 拆分包的一些策略 我们经常会使用到 npm run build 进行服务的打包 然后 打包出来的情况, 可能如下, 可以看到 chunk-vendors 是进行了包的拆分, 我们这里就是 来看一下 这里 npm run build 的时候的, 一个分包的策略 测试…

【Leetcode每日一题】模拟 - 提莫攻击(难度⭐)(45)

1. 题目解析 题目链接&#xff1a;495. 提莫攻击 2.算法原理 一、分情况讨论 要计算中毒的总时长&#xff0c;我们需要考虑时间点之间的差值&#xff0c;并根据这些差值来确定中毒的实际持续时间。 情况一&#xff1a;差值大于等于中毒时间 假设你的角色在时间点A中毒&#…

C语言 | Leetcode C语言题解之两数相加

题目&#xff1a; 题解&#xff1a; struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2) {struct ListNode *head NULL, *tail NULL;int carry 0;while (l1 || l2) {int n1 l1 ? l1->val : 0;int n2 l2 ? l2->val : 0;int sum n1 n2 …

C#(winform) 调用MATLAB函数

测试环境 VisualStudio2022 / .NET Framework 4.7.2 Matlab2021b 参考&#xff1a;C# Matlab 相互调用 Matlab 1、编写Matlab函数 可以没有任何参数单纯定义matlab处理的函数&#xff0c;输出的数据都存在TXT中用以后期读取数据 function [result,m,n] TEST(list) % 计算…