基于JavaFX的贪吃蛇小游戏

游戏背景介绍

贪吃蛇游戏是一款经典的小游戏,它的玩法很简单,就是控制蛇吃食物,每吃一个食物蛇的长度就会加一,直到蛇撞到墙壁或者撞到自己时游戏结束,最终的得分是蛇的长度减一。

JavaFX

用Java开发桌面端首选就是JavaFX,它的推出用来取代Swing(一个古老的Java桌面端框架)。

虽然都说Java开发桌面端性能不行,但是我们的Java开发工具IntelliJ IDEA的界面是由JavaFX构建的。最开始的我的世界(Minecraft)这款游戏是Java开发的,虽然没有使用Java标准GUI库(它自己的游戏引擎和自定义的用户界面),但也足以证明Java的魅力。

游戏规则

  • 初始时,蛇的长度为一,位于游戏界面的中心位置。
  • 每次随机生成一块食物,食物不能出现在蛇的身体上。
  • 蛇可以通过四个方向键上下左右移动,不能撞到墙壁或自己的身体。
  • 每吃一块食物,蛇的长度加一。
  • 穿过左边的墙壁,出现在右边;穿过上边的墙壁,出现在下面;反之亦然。
  • 游戏结束时,弹出得分对话框,点击重新开始新游戏。

代码结构

本教程主要涉及的代码文件是SnakeGame.java,整个代码文件的框架如下:

import java.util.ArrayDeque;
import java.util.Deque;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;public class SnakeGame extends Application {// 游戏界面的宽度private static final int WIDTH = 20;// 游戏界面的高度private static final int HEIGHT = 20;// 每个格子的大小private static final int SIZE = 20;// 蛇的速度private static final int SPEED = 5;// 蛇的身体private Deque<Point> snake = new ArrayDeque<>();// 蛇的初始方向private Direction direction = Direction.RIGHT;// 食物的位置private Point food;// 游戏是否结束private boolean gameOver = false;// 游戏是否暂停private boolean gamePaused = false;@Overridepublic void start(Stage primaryStage) throws Exception {// 界面初始化// ...// 初始化游戏// ...// 动画循环// ...}// 界面初始化方法private void initGUI() {// ...}// 初始化游戏方法private void initGame() {// ...}// 蛇的移动方法private void move() {// ...}// 检测碰撞方法private void checkCollision() {// ...}// 生成食物方法private void generateFood() {// ...}// 绘制游戏画面方法private void paint(GraphicsContext gc) {// ...}// 显示游戏结束对话框方法private void showGameOverDialog() {// ...}// 方向枚举类private enum Direction {UP, DOWN, LEFT, RIGHT}// 坐标点类private static class Point {private int x;private int y;public Point(int x, int y) {this.x = x;this.y = y;}public int getX() {return x;}public int getY() {return y;}@Overridepublic boolean equals(Object o) {// ...}@Overridepublic int hashCode() {// ...}}public static void main(String[] args) {launch(args);}}

逻辑分析

在实现贪吃蛇游戏之前,我们需要先了解一下游戏的逻辑。

  • 在游戏界面内,不断地移动蛇的位置。
  • 蛇的移动方向可以通过键盘上的上下左右四个方向键来控制。
  • 当蛇头碰到边界或碰到自己的身体时,游戏结束。
  • 当蛇头碰到食物时,就会吃掉食物,长度加1,随后继续向前移动。
  • 吃掉食物后,会重新生成一个新的食物,判断新食物的位置是否和已有的蛇的位置冲突。

实现步骤

下面分步骤进行实现,每一个步骤都结合代码,逻辑清晰。

步骤1:界面初始化

start方法中进行界面的初始化,包括创建CanvasGraphicsContext等,并将Canvas添加到StackPane作为根节点,最后显示舞台。代码如下:

@Override
public void start(Stage primaryStage) throws Exception {// 创建CanvasCanvas canvas = new Canvas(WIDTH * SIZE, HEIGHT * SIZE);GraphicsContext gc = canvas.getGraphicsContext2D();// 创建根节点StackPane root = new StackPane(canvas);root.setAlignment(Pos.CENTER);// 创建场景Scene scene = new Scene(root);scene.setOnKeyPressed(event -> {KeyCode keyCode = event.getCode();switch (keyCode) {// ...}});// 显示舞台primaryStage.setScene(scene);primaryStage.setTitle("贪吃蛇游戏");primaryStage.setResizable(false);primaryStage.show();
}

步骤2:初始化游戏

在游戏开始前,需要初始化一些参数,包括蛇的位置、食物位置、游戏状态等。具体实现代码如下:

// 初始化游戏方法
private void initGame() {// 清空蛇的身体snake.clear();// 在游戏界面的中心生成蛇头int x = WIDTH / 2;int y = HEIGHT / 2;snake.add(new Point(x, y));// 生成食物generateFood();// 初始化游戏状态gameOver = false;gamePaused = false;
}

步骤3:蛇的移动

在游戏中,蛇可以通过键盘上的上下左右四个方向键来控制移动方向。我们可以在Scene的按键监听事件中实现,根据按下的方向键修改蛇的移动方向。具体代码实现如下:

// Scene的按键监听事件
scene.setOnKeyPressed(event -> {KeyCode keyCode = event.getCode();switch (keyCode) {case UP:if (direction != Direction.DOWN) {direction = Direction.UP;}break;case DOWN:if (direction != Direction.UP) {direction = Direction.DOWN;}break;case LEFT:if (direction != Direction.RIGHT) {direction = Direction.LEFT;}break;case RIGHT:if (direction != Direction.LEFT) {direction = Direction.RIGHT;}break;case P:gamePaused = !gamePaused;break;case R:initGame();break;default:break;}
});

在每次动画循环中,根据蛇的移动方向来计算移动后的新位置。如果新位置在蛇的身体上或者超出了边界,就说明游戏结束了。判断蛇是否吃到了食物,如果吃到了就让蛇的身体变长,并在新位置生成一个新的食物。

// 蛇的移动方法
private void move() {Point head = snake.getFirst();Point newHead = null;switch (direction) {case UP:newHead = new Point(head.getX(), head.getY() - 1);break;case DOWN:newHead = new Point(head.getX(), head.getY() + 1);break;case LEFT:newHead = new Point(head.getX() - 1, head.getY());break;case RIGHT:newHead = new Point(head.getX() + 1, head.getY());break;default:break;}// 判断是否撞到自己的身体if (snake.contains(newHead)) {gameOver = true;showGameOverDialog();return;}// 判断是否撞到墙壁if (newHead.getX() < 0 || newHead.getX() >= WIDTH ||newHead.getY() < 0 || newHead.getY() >= HEIGHT) {gameOver = true;showGameOverDialog();return;}// 更新蛇的位置snake.addFirst(newHead);// 判断是否吃到了食物if (newHead.equals(food)) {// 如果吃到了食物,就让蛇的身体变长generateFood();} else {// 如果没有吃到食物,就让蛇的尾巴消失snake.removeLast();}
}

步骤4:检测碰撞

在每次蛇的移动后,需要检测蛇是否撞到了自己的身体。如果撞到了,说明游戏结束了。具体代码实现如下:

// 检测碰撞方法
private void checkCollision() {Point head = snake.getFirst();for (Point point : snake) {if (point != head && point.equals(head)) {gameOver = true;showGameOverDialog();break;}}
}

步骤5:生成食物

每个食物都是在游戏界面上随机出现的,食物不能出现在蛇的身体上。生成食物时,可以使用do-while循环来判断是否有重合的情况。具体代码实现如下:

// 生成食物方法
private void generateFood() {boolean validPosition;int x, y;do {validPosition = true;x = (int) (Math.random() * WIDTH);y = (int) (Math.random() * HEIGHT);for (Point point : snake) {if (point.getX() == x && point.getY() == y) {validPosition = false;break;}}} while (!validPosition);food = new Point(x, y);
}

步骤6:绘制游戏画面

Canvas上通过GraphicsContext绘制蛇、食物等游戏元素,实现游戏的画面。具体代码实现如下:

// 绘制游戏画面方法
private void paint(GraphicsContext gc) {// 清空画布gc.clearRect(0, 0, WIDTH * SIZE, HEIGHT * SIZE);// 绘制蛇身gc.setFill(javafx.scene.paint.Color.GREEN);for (Point point : snake) {gc.fillRect(point.getX() * SIZE, point.getY() * SIZE, SIZE, SIZE);}// 绘制头部gc.setFill(javafx.scene.paint.Color.DARKGREEN);Point head = snake.getFirst();gc.fillRect(head.getX() * SIZE, head.getY() * SIZE, SIZE, SIZE);// 绘制食物gc.setFill(javafx.scene.paint.Color.RED);gc.fillRect(food.getX() * SIZE, food.getY() * SIZE, SIZE, SIZE);
}

步骤7:显示游戏结束对话框

当游戏结束时,弹出得分对话框,点击重新开始新游戏。具体代码实现如下:

// 显示游戏结束对话框方法
private void showGameOverDialog() {Alert alert = new Alert(AlertType.INFORMATION);alert.setTitle("游戏结束");alert.setHeaderText(null);alert.setContentText("游戏结束,您的得分是:" + (snake.size() - 1));alert.show();alert.setOnHidden(event -> {initGame();});
}

至此,贪吃蛇游戏的实现已经完成了。

完整代码如下:

package org.example;import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;import java.util.ArrayDeque;
import java.util.Deque;public class SnakeGame extends Application {private static final int WIDTH = 20; // 游戏界面的宽度private static final int HEIGHT = 20; // 游戏界面的高度private static final int SIZE = 20; // 每个格子的大小private static final int SPEED = 5; // 蛇的速度private Deque<Point> snake = new ArrayDeque<>(); // 蛇的身体private Direction direction = Direction.RIGHT; // 蛇的初始方向private Point food; // 食物的位置private boolean gameOver = false; // 游戏是否结束private boolean gamePaused = false; // 游戏是否暂停@Overridepublic void start(Stage primaryStage) throws Exception {Canvas canvas = new Canvas(WIDTH * SIZE, HEIGHT * SIZE);GraphicsContext gc = canvas.getGraphicsContext2D();StackPane root = new StackPane(canvas);root.setAlignment(Pos.CENTER);Scene scene = new Scene(root);scene.setOnKeyPressed(event -> {KeyCode keyCode = event.getCode();switch (keyCode) {case UP:if (direction != Direction.DOWN) {direction = Direction.UP;}break;case DOWN:if (direction != Direction.UP) {direction = Direction.DOWN;}break;case LEFT:if (direction != Direction.RIGHT) {direction = Direction.LEFT;}break;case RIGHT:if (direction != Direction.LEFT) {direction = Direction.RIGHT;}break;case P:gamePaused = !gamePaused;break;case R:initGame();break;default:break;}});primaryStage.setScene(scene);primaryStage.setTitle("贪吃蛇游戏");primaryStage.setResizable(false);primaryStage.show();initGame();new AnimationTimer() {private long lastUpdateTime;@Overridepublic void handle(long now) {if (now - lastUpdateTime >= 1_000_000_000 / SPEED) { // 调整蛇的速度lastUpdateTime = now;if (!gameOver && !gamePaused) {move();checkCollision();paint(gc);}}}}.start();}// 初始化游戏private void initGame() {snake.clear();snake.add(new Point(WIDTH / 2, HEIGHT / 2));generateFood();gameOver = false;gamePaused = false;}// 蛇的移动private void move() {Point head = snake.getFirst();Point newHead = null;switch (direction) {case UP:newHead = new Point(head.getX(), head.getY() - 1);break;case DOWN:newHead = new Point(head.getX(), head.getY() + 1);break;case LEFT:newHead = new Point(head.getX() - 1, head.getY());break;case RIGHT:newHead = new Point(head.getX() + 1, head.getY());break;default:break;}// 判断是否撞到自己的身体if (snake.contains(newHead)) {gameOver = true;showGameOverDialog();return;}// 判断是否撞到墙壁if (newHead.getX() < 0 || newHead.getX() >= WIDTH ||newHead.getY() < 0 || newHead.getY() >= HEIGHT) {gameOver = true;showGameOverDialog();return;}snake.addFirst(newHead);if (newHead.equals(food)) {generateFood();} else {snake.removeLast();}}// 检测碰撞private void checkCollision() {Point head = snake.getFirst();for (Point point : snake) {if (point != head && point.equals(head)) {gameOver = true;showGameOverDialog();break;}}}// 生成食物private void generateFood() {boolean validPosition;int x, y;do {validPosition = true;x = (int) (Math.random() * WIDTH);y = (int) (Math.random() * HEIGHT);for (Point point : snake) {if (point.getX() == x && point.getY() == y) {validPosition = false;break;}}} while (!validPosition);food = new Point(x, y);}// 绘制游戏画面private void paint(GraphicsContext gc) {// 清空画布gc.clearRect(0, 0, WIDTH * SIZE, HEIGHT * SIZE);// 绘制蛇身gc.setFill(javafx.scene.paint.Color.GREEN);for (Point point : snake) {gc.fillRect(point.getX() * SIZE, point.getY() * SIZE, SIZE, SIZE);}// 绘制头部gc.setFill(javafx.scene.paint.Color.DARKGREEN);Point head = snake.getFirst();gc.fillRect(head.getX() * SIZE, head.getY() * SIZE, SIZE, SIZE);// 绘制食物gc.setFill(javafx.scene.paint.Color.RED);gc.fillRect(food.getX() * SIZE, food.getY() * SIZE, SIZE, SIZE);}// 显示游戏结束对话框private void showGameOverDialog() {Alert alert = new Alert(AlertType.INFORMATION);alert.setTitle("游戏结束");alert.setHeaderText(null);alert.setContentText("游戏结束,您的得分是:" + (snake.size() - 1));alert.show();alert.setOnHidden(event -> {initGame(); // 游戏结束后重新开始游戏});}// 方向枚举类private enum Direction {UP, DOWN, LEFT, RIGHT}// 坐标点类private static class Point {private int x;private int y;public Point(int x, int y) {this.x = x;this.y = y;}public int getX() {return x;}public int getY() {return y;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Point point = (Point) o;return x == point.x && y == point.y;}@Overridepublic int hashCode() {return x * 31 + y;}}public static void main(String[] args) {launch(args);}}

关注微信公众号:“小虎哥的技术博客”。我们会定期发布关于Java技术的详尽文章,让您能够深入了解该领域的各种技巧和方法,让我们一起成为更优秀的程序员👩‍💻👨‍💻!

相关文章源码放在:gitee仓库、github仓库上。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

shell脚本——循环语句、sed、函数、数组、免交互expect

目录 循环语句 for while 与 until sed 基本用法 sed脚本格式 函数 注意事项 定义函数和调用函数 脚本中函数的位置 查看函数 删除函数 函数返回值 函数的传参操作 使用函数文件 递归函数 数组 声明数组 数组切片 免交互expect 定义 基本命令 循环语句 …

python3/pip3 SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed

环境&#xff1a; mac os 背景&#xff1a; 电脑之前安装的是python3.9 &#xff0c; 现在升级到python3.10。 从python官网下载macos版本的python3.10 pkg。 双击安装。 程序使用aiohttp访问ebay 。 出错&#xff1a; aiohttp.client_exceptions.ClientConnectorCertifi…

MySql015——使用子查询

一、创建customers表 ######################## # Create customers table ######################## use study;CREATE TABLE customers (cust_id int NOT NULL AUTO_INCREMENT,cust_name char(50) NOT NULL ,cust_address char(50) NULL ,cust_city char…

如何让qt tableView每个item中个别字用不同颜色显示?

如何让qt tableView每个item中个别字用不同颜色显示&#xff1f; 从上面图片可以看到&#xff0c;Item为红色&#xff0c;数字5为黑色。 要实现在一个控件实现不同颜色&#xff0c;目前想到的只有QTextEdit 、QLabel。有两种方法&#xff0c;第一种是代理&#xff0c;第二种是…

yolov5添加SimAM注意力机制(yolov7同理)

SimAM注意力机制简介 关于SIMAM注意力机制的原理这里不再详细解释,这篇发在Proceeddings of the 38th Internation Conference on Machine Learning.论文参考如下论文链接here   yolov5中添加SimAM注意力机制 注意力机制分为接收通道数和不接受通道数两种。这次属于不接受通…

数据库相关知识2

数据库知识2 关系完整性 数据完整性 指的是数据库中的数据的准确性和可靠性 实体完整性约束&#xff1a; 目的&#xff1a; 在表中至少有一个唯一的 标识&#xff0c;主属性字段中&#xff0c;不为空&#xff0c;不重复 主键约束&#xff1a;唯一 不重复 不为空 primary k…

c语言实现堆

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、树1、树的概念2、树的相关概念3、树的表示 二、二叉树1、二叉树概念2、特殊的二叉树3、二叉树的性质4、二叉树的顺序结构5、二叉树的链式结构 三、堆(二叉树…

C# textBox1.Text=““与textBox1.Clear()的区别

一、区别 textbox.Text "" 和 textbox.Clear() 都可以用于清空文本框的内容&#xff0c;但它们之间有一些细微的区别。 textbox.Text "": 这种方式会将文本框的 Text 属性直接设置为空字符串。这样会立即清除文本框的内容&#xff0c;并将文本框显示为空…

【leetcode 力扣刷题】双指针///原地扩充线性表

双指针///原地扩充线性表 剑指 Offer 05. 替换空格定义一个新字符串扩充字符串&#xff0c;原地替换思考 剑指 Offer 05. 替换空格 题目链接&#xff1a;剑指 Offer 05. 替换空格 题目内容&#xff1a; 这是一道简单题&#xff0c;理解题意&#xff0c;就是将字符串s中的空格…

拼多多开放平台的API接口可以获取拼多多电商数据。以下是API接口流程

使用拼多多开放平台的API接口可以获取拼多多电商数据。以下是一般的API接口流程&#xff1a; 1. 注册开发者账号&#xff1a;首先&#xff0c;您需要在拼多多开放平台注册一个开发者账号。通过开发者账号&#xff0c;您可以获得API密钥和其他必要的信息。 2. 鉴权与认证&…

数据结构1

数据结构是计算机科学中存储和组织数据的一种方式&#xff0c;它定义了数据的表示方式和对数据进行操作的方法&#xff0c;常见的数据结构包括数组、栈、链表、队列、树、图等。 目录 一、常见的数据结构 1.数组 2.栈 3.队列 4.链表 5.树 6.图 一、常见的数据结构 1.数…

自动设置服务器全教程

亲爱的爬虫探险家&#xff01;在网络爬虫的世界里&#xff0c;自动设置代理服务器是一个非常有用的技巧。今天&#xff0c;作为一家代理服务器供应商&#xff0c;我将为你呈上一份轻松实用的教程&#xff0c;帮助你轻松搞定爬虫自动设置代理服务器。 一、为什么需要自动设置代…

前端如何走通后端接口

0 写在前面 现在基本都是前后端分离的项目了&#xff0c;那么前端小伙伴如何获取后端小伙伴接口呢&#xff1f; 1 条件 同一WiFi下&#xff0c;让后端小伙伴分享出自己的ip地址&#xff1a; 步骤1:winr调出运行界面 步骤2&#xff1a;cmd调出命令行窗口 步骤3&#xff1a;…

JavaScript用indexOf()在字符串数组中查找子串时需要注意的一个地方

一、遇到问题 在 继续更新完善&#xff1a;C 结构体代码转MASM32代码 中&#xff0c;由于结构体成员中可能为数组类型的情况&#xff0c;因此我们在提取结构体成员信息的过程中&#xff0c;需要检测结构体成员名称字符串中是否包括 []&#xff0c;如果包括那么我们要截取[前面…

Python爬虫分布式架构 - Redis/RabbitMQ工作流程介绍

在大规模数据采集和处理任务中&#xff0c;使用分布式架构可以提高效率和可扩展性。本文将介绍Python爬虫分布式架构中常用的消息队列工具Redis和RabbitMQ的工作流程&#xff0c;帮助你理解分布式爬虫的原理和应用。 为什么需要分布式架构&#xff1f; 在数据采集任务中&#…

android2022配置opencv4android480

1&#xff0c;安装android studio2022。 2&#xff0c;下载OPENCV4ANDROID&#xff0c;解压到任意盘中。 3&#xff0c;File->New->New Project&#xff0c;选择Empty Views Activity。再选择语言&#xff0c;本文选择JAVA。 4&#xff0c;File->New->Import Modu…

麒麟系统开启root账户及自动登陆

1.首先我们通过“开始菜单t”快捷键打开命令行页面&#xff0c;然后我们通过 cd /usr/share/lightdm/lightdm.conf.d/进入对应系统目录。之后我们通过ls命令查看目录中的文件&#xff0c;找到95-ukui-greeter.conf这个文件。 2.之后我们通过命令 sudo vim 95-ukui-greeter.c…

【C++进阶(二)】STL大法--vector的深度剖析以及模拟实现

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:C从入门到精通⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习C   &#x1f51d;&#x1f51d; vector 1. 前言2. 熟悉vector的接口函数2.1 vec…

leetcode 1022.从根到叶的二进制数之和

⭐️ 题目描述 &#x1f31f; leetcode链接&#xff1a;https://leetcode.cn/problems/sum-of-root-to-leaf-binary-numbers/description/ 代码&#xff1a; class Solution { public:int sum (TreeNode* root , int num 0) {if (root nullptr) {return 0;}int cur num r…

Docker学习笔记

Docker学习笔记 docker的作用docker的基本组成安装docker阿里云镜像加速run的流程和docker原理 docker的思想来自于集装箱。 核心思想&#xff1a; 隔离 docker可以通过隔离机制将服务器利用到极致。 虚拟机&#xff1a;在windows中装一个Vmware&#xff0c;通过这个软件可以虚…