JavaFX实战:从零到一实现一个功能丰富的“高级反应速度测试”游戏

大家好!今天我们不搞简单的“红变绿就点”了,来点硬核的!我们要用 JavaFX 从头开始,构建一个更复杂、更有趣也更考验能力的“高级反应速度测试”游戏。这个版本将引入选择反应时 (Choice Reaction Time) 的概念——你需要在多个干扰项中快速找到并点击指定的目标,这更贴近我们现实生活中的反应场景。

这篇文章将带你深入了解这个游戏的开发全过程,涉及 JavaFX 布局、事件处理、状态管理、动画与计时器、动态 UI 生成等多个方面。无论你是 JavaFX 新手想找个练手项目,还是有经验的开发者想看看具体应用,相信都能有所收获。

我们将实现的核心功能:

  • 多目标呈现与选择: 屏幕随机位置出现多个图形(圆形/方形),每次你需要点击指定的那一种。
  • 干扰项: 除了目标,还有形状或颜色不同的干扰项。
  • 计时与计分: 精确测量反应时间,并根据表现(命中、误击、错过)进行计分。
  • 多轮测试与统计: 游戏包含固定次数的测试(试次),结束后给出总分和平均反应时间。

设计先行:游戏流程与界面布局

在敲代码之前,先理清思路很重要。

游戏流程大概是这样:

  1. 初始状态: 显示标题和“开始游戏”按钮。
  2. 开始游戏: 重置分数和统计,禁用开始按钮,进入第一个试次。
  3. 准备阶段 (GET_READY): 屏幕清空,提示本次要点击的目标(如“准备… 点击 圆形!”),并随机等待 1-2.5 秒。
  4. 刺激呈现 (SHOWING_STIMULUS): 在屏幕随机位置同时显示目标和干扰项图形,并开始计时。提示变为“点击!”。
  5. 玩家交互:
    • 正确点击目标: 记录反应时间,加分,进入下一试次的“准备阶段”。
    • 点击干扰项: 扣分,进入下一试次的“准备阶段”。
    • 点击背景或超时(1.5秒内未点击): 算作“错过”,扣少量分,进入下一试次的“准备阶段”。
  6. 单次试次结束 (TRIAL_OVER): 短暂显示本次结果(命中/点错/错过),然后自动进入下一试次。
  7. 整轮结束 (ROUND_OVER): 完成所有试次(如10次)后,显示总得分、平均反应时间、命中/错误/错过统计,并重新启用“开始游戏”按钮(文本变为“再玩一轮”)。

界面布局:

我们选用 BorderPane 作为根布局,逻辑清晰:

  • 顶部 (Top): 使用 VBox 垂直排列显示游戏状态/指示 (instructionLabel)、试次进度 (trialLabel)、当前得分 (scoreLabel) 和平均反应时间 (avgTimeLabel)。
  • 中部 (Center): 使用 Pane 作为游戏核心区域 (gamePane)。选择 Pane 是因为它允许我们通过 setLayoutX/Y 精确控制子节点(图形)的绝对位置,这对于随机放置目标至关重要。
  • 底部 (Bottom): 使用 HBox 水平放置控制按钮(目前只有一个“开始游戏”按钮 startButton)。

在这里插入图片描述

核心技术点深度解析

1. 状态管理:GameState 枚举与状态机

对于一个交互流程稍复杂的应用,清晰的状态管理是关键。我们定义了一个 GameState 枚举:

private enum GameState {INITIAL, GET_READY, SHOWING_STIMULUS, TRIAL_OVER, ROUND_OVER
}
private GameState currentState = GameState.INITIAL;

程序在任何时候都处于其中一个状态。所有的事件处理(如鼠标点击)和流程控制(如计时器结束)都会首先检查 currentState,并根据当前状态执行相应的逻辑,然后可能转换到下一个状态。这形成了一个简单的状态机,大大降低了逻辑混乱的风险。例如,只有在 SHOWING_STIMULUS 状态下,点击图形才有效;在其他状态下点击会被忽略或有不同处理(如 RESULT 状态点击是重新开始)。

2. 动态 UI 生成与布局:Pane 的妙用

游戏的核心在于动态生成和放置图形。

  • 图形生成 (generateShapes): 每次 showStimulus 时,此方法会:

    • 随机决定本轮目标是圆是方 (targetShapeDefinition)。
    • 创建一个对应的正确目标节点 (correctTargetNode),设置目标颜色 (TARGET_COLOR)。
    • 循环创建指定数量的干扰项。干扰项与目标的区别是随机的(要么形状不同,要么颜色不同,使用预定义的干扰色)。
    • 为每个创建的图形节点(目标和干扰项)添加点击事件监听器 (addClickHandler)。
  • 随机放置 (placeShapesRandomly):

    • 获取 gamePane 的实际宽高。
    • 对每个图形,在 gamePane 内随机生成 x, y 坐标(注意留出边缘空间)。
    • 关键:使用 Pane 布局,可以直接设置 shape.setLayoutX(x)shape.setLayoutY(y) 来定位。
    • 避重叠(简化版): 为了防止图形完全叠在一起,我们做了一个简单的检查:新生成的坐标 (x, y) 是否与 gamePane 上已存在的其他图形的中心点过于接近(距离小于 TARGET_SIZE * 1.5)。如果太近,则重新生成坐标,并限制尝试次数防止死循环。这个方法可以进一步优化(例如使用更精确的边界盒碰撞检测或更智能的布局算法),但对于这个游戏基本够用。
3. 精确计时与动画控制:PauseTransitionTimeline

时间控制是反应测试的核心。JavaFX 提供了方便的动画 API:

  • PauseTransition (waitTimer): 用于实现“准备”阶段(GET_READY) 的随机等待。它非常适合执行一次性的延迟任务。我们设置一个随机的持续时间,当 onFinished 事件触发时,调用 showStimulus() 方法。

    // 在 startNextTrial() 中
    double waitSeconds = MIN_WAIT_SECONDS + random.nextDouble() * (MAX_WAIT_SECONDS - MIN_WAIT_SECONDS);
    waitTimer = new PauseTransition(Duration.seconds(waitSeconds));
    waitTimer.setOnFinished(event -> showStimulus()); // 延迟结束时显示图形
    waitTimer.play();
    
  • Timeline (stimulusTimer): 用于限制刺激物显示的最长时间。如果玩家在 STIMULUS_DURATION_SECONDS 内没有点击任何东西,Timeline 会触发它的 KeyFrame 事件。我们在该事件处理器中检查当前状态是否仍然是 SHOWING_STIMULUS,如果是,则调用 handleMiss() 处理超时。

    // 在 showStimulus() 中
    stimulusTimer = new Timeline(new KeyFrame(Duration.seconds(STIMULUS_DURATION_SECONDS), event -> {if (currentState == GameState.SHOWING_STIMULUS) {handleMiss(); // 超时算作错过}
    }));
    stimulusTimer.play();
    
  • 反应时间测量: 我们使用 System.nanoTime() 来获取纳秒级精度的时间戳。在 showStimulus() 时记录图形出现的 stimulusAppearTimeNanos,在玩家点击时 (handleCorrectHit) 再次获取当前时间,两者之差除以 1_000_000 就得到毫秒级的反应时间。nanoTime()currentTimeMillis() 更适合测量这种短时间间隔。

4. 事件处理:区分目标、干扰项与背景

用户交互的核心是鼠标点击。

  • 图形点击 (addClickHandler): 我们为每个生成的图形(包括目标和干扰项)都添加了 setOnMouseClicked 监听器。这个监听器内部:

    • 首先检查 currentState == GameState.SHOWING_STIMULUS,确保只在该状态下响应。
    • 调用 stopTimers() 停止所有计时器。
    • 计算反应时间。
    • 根据传入的 isCorrectTarget 布尔值,调用 handleCorrectHithandleIncorrectHit
    • 调用 event.consume() 非常重要,它阻止鼠标点击事件继续向上传播给父容器 gamePane。否则,点击图形也会触发 gamePane 的背景点击事件。
  • 背景点击 (gamePane.setOnMouseClicked): 这个监听器用于捕捉玩家在刺激物显示期间点击了空白区域的情况。如果发生,则调用 handleMiss()

5. 游戏流程控制与反馈
  • 试次循环 (startNextTrial, scheduleNextTrial): startNextTrial 负责准备一次测试的所有工作。当一次测试结束时(handleCorrectHit, handleIncorrectHit, handleMiss),它们都会调用 scheduleNextTrialscheduleNextTrial 内部使用一个短暂的 PauseTransition (例如1秒) 来延迟执行 startNextTrial,目的是让玩家有时间看到本次试次的结果反馈,然后再进入下一次准备。
  • 结束与重玩 (endRound):currentTrial 达到 NUM_TRIALS 时触发,负责清场、计算并显示最终统计数据,并重置开始按钮状态允许重玩。
  • UI 更新 (updateUI): 这个辅助方法被频繁调用,用于将内部状态(分数、试次、平均时间)实时反映到界面标签上。注意在 ROUND_OVER 状态下要避免覆盖最终的统计显示。

代码之外:潜在的优化与扩展方向

这个游戏虽然功能已经比较丰富,但仍有提升空间:

  1. 视觉效果与样式: 使用 CSS 美化界面,给按钮、标签、背景添加样式;可以给图形的出现/消失、点击反馈添加简单的动画(如缩放、淡入淡出)。
  2. 声音反馈: 为点击正确、错误、错过、游戏结束等事件添加音效,提升沉浸感。
  3. 更优的避重叠算法: 现在的算法比较基础,可能会在图形较多时效率降低或效果不佳。可以研究更复杂的布局算法或碰撞检测。
  4. 动态难度调整: 根据玩家表现(如平均反应时间、正确率)动态调整下一轮的参数(如刺激显示时间缩短、干扰项增多、目标变小等)。
  5. 配置选项: 允许用户自定义测试轮数、图形数量、颜色主题等。
  6. 数据持久化: 保存最高分或玩家的测试记录。

结语

通过构建这个“高级反应速度测试”游戏,我们不仅复习了 JavaFX 的基础知识,更深入地实践了状态管理、事件处理、计时动画、动态 UI 构建等进阶技巧。它证明了即便是看似简单的概念(反应速度测试),也可以通过增加复杂度(选择反应时、多目标、干扰项)来变成一个既有趣又有挑战性的编程项目。

希望这篇文章的详细解析能对你学习 JavaFX 或进行类似项目开发有所帮助。最重要的是,动手去实现它,你会在解决问题的过程中学到最多!


附注: 上述代码片段是说明性的,完整的可运行代码请参考资源文件中 AdvancedReactionTestFX.java 完整示例。确保你的开发环境已正确配置 JavaFX。

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

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

相关文章

CSS 选择器介绍

CSS 选择器介绍 1. 基本概念 CSS(层叠样式表)是一种用于描述 HTML 或 XML 文档外观的语言。通过 CSS,可以控制网页中元素的布局、颜色、字体等视觉效果。而 CSS 选择器则是用来指定哪些 HTML 元素应该应用这些样式的工具。 2. 基本选择器 …

Vue3父子组件数据同步方法

在 Vue 3 中&#xff0c;当子组件需要修改父组件传递的数据副本并同步更新时&#xff0c;可以通过以下步骤实现&#xff1a; 方法 1&#xff1a;使用 v-model 和计算属性&#xff08;实时同步&#xff09; 父组件&#xff1a; vue <template><ChildComponent v-mo…

el-table中el-input的autofocus无法自动聚焦的解决方案

需求 有一个表格展示了一些进度信息&#xff0c;进度信息可以修改&#xff0c;需要点击进度信息旁边的编辑按钮时&#xff0c;把进度变为输入框且自动聚焦&#xff0c;当鼠标失去焦点时自动请求更新接口。 注&#xff1a;本例以vue2 element UI为例 分析 这个需求看着挺简单…

用高斯溅射技术跨越机器人模拟与现实的鸿沟:SplatSim 框架解析

在机器人领域&#xff0c;让机器人在现实世界中精准执行任务是大家一直追求的目标。可模拟环境和现实世界之间存在着不小的差距&#xff0c;特别是基于 RGB 图像的操作策略&#xff0c;从模拟转移到现实时总是状况百出。 今天咱们就来聊聊 SplatSim 框架&#xff0c;看看它是怎…

【自然语言处理与大模型】如何知道自己部署的模型的最大并行访问数呢?

当你自己在服务器上部署好一个模型后&#xff0c;使用场景会有两种。第一种就是你自己去玩&#xff0c;结合自有的数据做RAG等等&#xff0c;这种情况下一般是不会考虑并发的问题。第二种是将部署好的服务给到别人来使用&#xff0c;这时候就必须知道我的服务到底支持多大的访问…

[FPGA基础] UART篇

Xilinx FPGA UART 硬件接口使用指南 1. 引言 UART (通用异步收发器) 是一种广泛使用的串行通信接口&#xff0c;因其简单、可靠和易于实现而成为 Xilinx FPGA 设计中的常见硬件接口。UART 用于在 FPGA 与外部设备&#xff08;如 PC、微控制器、传感器等&#xff09;之间进行数…

【Netty4核心原理】【全系列文章目录】

文章目录 一、前言二、目录 一、前言 本系列虽说本意是作为 《Netty4 核心原理》一书的读书笔记&#xff0c;但在实际阅读记录过程中加入了大量个人阅读的理解和内容&#xff0c;因此对书中内容存在大量删改。 本系列内容基于 Netty 4.1.73.Final 版本&#xff0c;如下&#xf…

用 PyTorch 和numpy分别实现简单的 CNN 二分类器

作业用到的知识&#xff1a; 1.Pytorch: 1. nn.Conv2d&#xff08;二维卷积层&#xff09; 作用&#xff1a; 对输入的多通道二位数据&#xff08;如图像&#xff09;进行特征提取&#xff0c;通过滑动卷积核计算局部区域的加权和&#xff0c;生成新的特征图。 关键参数&a…

使用n8n构建自动化工作流:从数据库查询到邮件通知的使用指南

n8n是一款强大的开源工作流自动化工具&#xff0c;可以帮助你将各种服务和应用程序连接起来&#xff0c;创建复杂的自动化流程。下面我将详细介绍一个实用的n8n用例&#xff1a;从MySQL数据库查询数据并发送邮件通知&#xff0c;包括使用场景、搭建步骤和节点部署方法。 使用场…

Vscode已经打开的python项目,如何使用已经建立的虚拟环境

在 VS Code 中使用已创建的 Conda/Mamba 虚拟环境 pe100&#xff0c;只需以下几步&#xff1a; 步骤 1&#xff1a;确保虚拟环境已存在 在终端运行以下命令&#xff0c;检查 pe100 环境是否已正确创建&#xff1a; conda activate pe100 python --version # 应显示 Python 3…

Volatility工具学习

背景 VMware虚拟机系统hang死&#xff0c;手动重启无法触发系统panic&#xff0c;从而不能触发kdump产生vmcore文件进行原因分析&#xff1b;此种情况下需要手动生成虚拟机内存快照&#xff0c;进而利用Volatility工具分析系统hang死的具体原因。 配置 使用VMware创建虚拟机…

学习笔记(C++篇)--- Day 4

目录 1.赋值运算符重载 1.1 运算符重载 1.2 赋值运算符重载 1.3 日期类实现 1.赋值运算符重载 1.1 运算符重载 ①当运算符被用于类类型的对象时&#xff0c;C语言允许我们通过通过运算符重载的形式指定新的含义。C规定类类型对象使用运算符时&#xff0c;必须转换成调用对…

Docker 快速入门教程

1. Docker 基本概念 镜像(Image): 只读模板&#xff0c;包含创建容器的指令 容器(Container): 镜像的运行实例 Dockerfile: 用于构建镜像的文本文件 仓库(Repository): 存放镜像的地方&#xff08;如Docker Hub&#xff09; 2. 安装Docker 根据你的操作系统选择安装方式:…

vue项目中使用tinymce富文本编辑器

vue使用tinymce 文章目录 vue使用tinymcetinymce富文本编辑器在这里插入图片描述 一、本文要实现二、使用步骤1.安装tinymce2.tinymce组件新建3. 在store添加商品详情的状态管理4. tinymce组件的引入 tinymce富文本编辑器 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下…

简单适配torch_npu不支持的ATen算子

简单适配torch_npu不支持的ATen算子 一、背景说明1.1 PyTorch扩展机制1.2 核心概念二、实现步骤详解2.1 实现前向、反向传播算子2.2 编译生成动态库2.3 测试验证程序三、关键点解析3.1 设计注意事项3.2 性能优化方向四、验证结果一、背景说明 1.1 PyTorch扩展机制 PrivateUse1…

同样的html标记,不同语言的文本,显示的字体和粗细会不一样吗

同样的 HTML 标记&#xff0c;在不同语言的文本下&#xff0c;显示出来的字体和粗细确实可能会不一样&#xff0c;原因如下&#xff1a; &#x1f30d; 不同语言默认字体不同 浏览器字体回退机制 CSS 里写的字体如果当前系统不支持&#xff0c;就会回退到下一个&#xff0c;比如…

基于 Spring Boot 瑞吉外卖系统开发(六)

基于 Spring Boot 瑞吉外卖系统开发&#xff08;六&#xff09; 菜品列表 在系统管理端首页&#xff0c;单击左侧菜单栏中的“菜品管理”&#xff0c;会在右侧打开菜品管理页面。 请求URL/dish/page&#xff0c;请求方法GET,请求参数page&#xff0c;pageSize。 该菜品列表…

计算机视觉与深度学习 | TensorFlow基本概念与应用场景:MNIST 手写数字识别(附代码)

TensorFlow 基本概念 TensorFlow 是一个开源的机器学习框架,由 Google 开发,核心概念包括: 张量(Tensor):多维数组,是数据的基本单位。计算图(Graph):早期版本中用于描述数据流和计算过程,2.x 默认启用即时执行(Eager Execution),兼顾灵活性和性能。层(Layers)…

vue+django+LSTM微博舆情分析系统 | 深度学习 | 食品安全分析

文章结尾部分有CSDN官方提供的学长 联系方式名片 文章结尾部分有CSDN官方提供的学长 联系方式名片 关注B站&#xff0c;有好处&#xff01; 编号&#xff1a; D031 LSTM 架构&#xff1a;vuedjangoLSTMMySQL 功能&#xff1a; 微博信息爬取、情感分析、基于负面消极内容舆情分析…

RHCE第三次作业 搭建dns的正向解析服务器

server为服务器 client为客户端 设置主配置文件 在server下&#xff1a; [rootServer ~]#vim /etc/named.conf #进入到配置页面&#xff0c;并修改 设置区域文件 [rootServer ~]# vim /etc/named.rfc1912.zones 设置域名解析文件 [rootServer named]# cd /var/named…