基于JavaFX的扫雷游戏实现(五)——设置和自定义控件

这篇具有很好参考价值的文章主要介绍了基于JavaFX的扫雷游戏实现(五)——设置和自定义控件。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

  它来了它来了,最后一期终于来了。理论上该讲的全都讲完了,只剩下那个拖了好几期的自定义控件和一个比较没有存在感的设置功能没有讲。所以这次就重点介绍它们俩吧。

  首先我们快速浏览下设置的实现,上图:

基于JavaFX的扫雷游戏实现(五)——设置和自定义控件

  然后是控制器代码:

SettingsController.java
package controllers;

import components.GameEnum;
import javafx.animation.FadeTransition;
import javafx.animation.RotateTransition;
import javafx.animation.SequentialTransition;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TextField;
import javafx.scene.control.ToggleGroup;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.util.Duration;

import static components.Constant.*;

/**
 * @description: 设置界面控制逻辑
 * @author: 郭小柒w
 * @time: 2023/6/15
 */
public class SettingsController {
    @FXML  // 单选按钮, 难度
    private RadioButton easy, medium, hard, custom;
    @FXML  // 文本框组, 自定义游戏数据
    private TextField numWidth, numHeight, numBomb;
    @FXML // 保存按钮
    private Button save;
    @FXML // 辅助效果图
    private ImageView loading;
    // 单选按钮组
    private ToggleGroup degree;

    public void initialize() {
        // 文本框默认不可编辑
        numWidth.setEditable(false);
        numHeight.setEditable(false);
        numBomb.setEditable(false);
        // 首先尝试使用已保存设置
        switch (GAME) {
            case MEDIUM:
                medium.setSelected(true);
                break;
            case HARD:
                hard.setSelected(true);
                break;
            case CUSTOM:
                custom.setSelected(true);
                numWidth.setEditable(true);
                numHeight.setEditable(true);
                numBomb.setEditable(true);
                numWidth.setText(GAME.width + "");
                numHeight.setText(GAME.height + "");
                numBomb.setText(GAME.bomb + "");
                break;
            default:
                easy.setSelected(true);
                break;
        }

        // 单选按钮分组
        degree = new ToggleGroup();
        easy.setToggleGroup(degree);
        medium.setToggleGroup(degree);
        hard.setToggleGroup(degree);
        custom.setToggleGroup(degree);
        // 难度按钮选中事件
        degree.selectedToggleProperty().addListener(((observable, oldValue, newValue) -> {
            String id = ((RadioButton) newValue).getId();
            // 只有选中自定义难度情况下可编辑文本框, 默认不可编辑
            if (id.equals("custom")) {
                GAME = GameEnum.CUSTOM;
                numWidth.setEditable(true);
                numHeight.setEditable(true);
                numBomb.setEditable(true);
            } else {
                // 清空文本框并设置为不可编辑
                numWidth.setText(null);
                numHeight.setText(null);
                numBomb.setText(null);
                numWidth.setEditable(false);
                numHeight.setEditable(false);
                numBomb.setEditable(false);
                if (id.equals("easy")) {
                    GAME = GameEnum.EASY;
                } else if (id.equals("medium")) {
                    GAME = GameEnum.MEDIUM;
                } else {
                    GAME = GameEnum.HARD;
                }
            }
        }));

        // 保存按钮点击事件
        save.setOnMouseClicked(event -> {
            try {
                // 如果是自定义难度, 保存输入的值
                if (GAME == GameEnum.CUSTOM) {
                    try {
                        // 保存自定义输入
                        GAME.setWidth(Integer.parseInt(numWidth.getText()));
                        GAME.setHeight(Integer.parseInt(numHeight.getText()));
                        GAME.setBomb(Integer.parseInt(numBomb.getText()));
                    } catch (NumberFormatException e) {
                        // 输入问题导致的转换失败, 按简单设置处理
                        GAME.setWidth(9);
                        GAME.setHeight(9);
                        GAME.setBomb(10);
                    }
                }
                // 设置用于动画效果的图片
                loading.setImage(new Image(LOAD_IMG));
                loading.setVisible(true);
                // 点击保存时的动画效果,分两步, 1:旋转缓冲 2:图片淡出
                RotateTransition transition1 = new RotateTransition(Duration.seconds(1), loading);
                // 旋转角度
                transition1.setByAngle(360);
                transition1.setOnFinished(event1 -> {
                    loading.setImage(new Image(SAVE_IMG));
                });

                FadeTransition transition2 = new FadeTransition(Duration.seconds(1), loading);
                // 不透明度变化
                transition2.setFromValue(1);
                transition2.setToValue(0);

                SequentialTransition sequence = new SequentialTransition(transition1, transition2);
                // 播放动画
                sequence.play();
            } catch (Exception e) {
                System.out.println("Error on [Class:SettingsController, Method:initialize, Event: save]=>");
                e.printStackTrace();
            }
        });
    }
}

  和上期排行版难度按钮类似,都是单选按钮分组然后设置对应点击事件。不同的是不像排行版切换那样直观,这里需要一个提示来让玩家清楚保存是生效了的,所以我设置了保存按钮和对应的动画提示。下面介绍自定义控件的实现(设置真的没有存在感,哈哈哈)。

  LedNumber,这个东西可是费了我老半天劲。我的思路是既然想在界面上显示,他要么是布局要么是控件,经过尝试后发现还是控件合理。所以这个自定义类要继承 Control 类,然后按照要求实现 createDefaultSkin 方法。到这里我就不会了,它要求的返回值类型为 Skin<?>,这是啥,没见过啊。求助万能的GPT后大概明白了它的要求(我理解的不一定准确)——控件显示是需要有Skin的,没有的话就类似无内容的Label,不设置背景色在界面上看起来就跟没有一样。所以我按GPT的提示创建了对应的skin类 LedNumberSkin,并在里面进行外观设计。

  设计思路受这篇文章启发:https://blog.csdn.net/hx0_0_8/article/details/8012448

  其思想就是将数字看作由以下七个线段组合而成,不同数字使用不同的线段:

基于JavaFX的扫雷游戏实现(五)——设置和自定义控件

  只是这样看起来还不够美观,所以可以对这些线段的拼接处进行处理,比如下面这种形式(绘制的有些简陋,代码中是可以控制连接处贴合的,适当留白更立体):

基于JavaFX的扫雷游戏实现(五)——设置和自定义控件

  那么代码中是如何实现的呢?对于每一条边,可以使用多边形Polygon类实现,只需要依次写入它的坐标即可(必须是顺时针或者逆时针,起始点位置不做要求),如下:

/**
 * 计算自定义多边形各顶点坐标
 *
 * @param toward 多边形朝向 [01234: 右下左上中]
 * @param x      起始点横坐标
 * @param y      起始点纵坐标
 * @return 坐标数组
 */
public ArrayList<Double> getPoints(int toward, double x, double y) {
    ArrayList<Double> points = new ArrayList();
    // 添加起始点坐标
    points.add(x);
    points.add(y);
    // 按顺时针方向依次添加其余坐标
    switch (toward) {
        case 0:
            points.add(x + height);
            points.add(y + height);
            points.add(x + height);
            points.add(y + height + lenShort);
            points.add(x);
            points.add(y + lenLong);
            break;
        case 1:
            points.add(x + lenLong);
            points.add(y);
            points.add(x + height + lenShort);
            points.add(y + height);
            points.add(x + height);
            points.add(y + height);
            break;
        case 2:
            points.add(x + height);
            points.add(y - height);
            points.add(x + height);
            points.add(y + height + lenShort);
            points.add(x);
            points.add(y + lenShort);
            break;
        case 3:
            points.add(x + lenShort);
            points.add(y);
            points.add(x + height + lenShort);
            points.add(y + height);
            points.add(x - height);
            points.add(y + height);
            break;
        case 4:
            points.add(x + height);
            points.add(y - height + 2);
            points.add(x + height + lenShort);
            points.add(y - height + 2);
            points.add(x + lenLong);
            points.add(y);
            points.add(x + height + lenShort);
            points.add(y + height - 2);
            points.add(x + height);
            points.add(y + height - 2);
            break;
    }
    return points;
}

  有了绘制方法,接下来就是具体数字需要的初始化方法:

/**
 * 构建点阵数字需要的边
 */
public void init() {
    // 初始化
    for (int i = 0; i < 7; ++i) {
        polygons[i] = new Polygon();
    }
    // 计算出各多边形的顶点坐标
    polygons[0].getPoints().addAll(getPoints(0, 1, 1));
    polygons[1].getPoints().addAll(getPoints(1, 2, 0));
    polygons[2].getPoints().addAll(getPoints(2, height + lenShort + 3, height + 1));
    polygons[3].getPoints().addAll(getPoints(2, height + lenShort + 3, height + lenLong + 3));
    polygons[4].getPoints().addAll(getPoints(3, height + 2, lenLong * 2 - 1));
    polygons[5].getPoints().addAll(getPoints(0, 1, lenLong + 3));
    polygons[6].getPoints().addAll(getPoints(4, 2, lenLong + 2));

    // 根据edges数组判断每条边待设置的颜色
    for (int i = 0; i < 7; ++i) {
        if(edges[index][i]) {
            polygons[i].setFill(Color.web("#FF0000"));
        } else {
            polygons[i].setFill(Color.web("#680404"));
        }
        pane.getChildren().add(polygons[i]);
    }

    getChildren().add(pane);
}

  注:edges为二维boolean数组,控制每条边的颜色显示 [true:亮红色, false:暗红色]

  这样就有了每个数字对应的外观,由于我使用的jdk版本较早,所以不支持 LedNumber 类运行中修改 Skin,所以采用以下方式实现数字显示切换:

/**
 * 切换数字显示
 * @param index 要转化成的数字
 */
public void switchSkin(int index) {
    // 清空当前控件的子节点, 重新添加
    getChildren().clear();
    LedNumber newLedNumber = new LedNumber(index);
    getChildren().add(newLedNumber);
}

  这样就完成了LED数字的显示,感觉这个想法还是很不错的。而且在这个基础上你甚至还可以自己实现电子万年历之类的程序,有兴趣的伙伴可以尝试下。到此本次扫雷项目已经介绍差不多了,如果您还有疑问欢迎在评论区留言,有缘明年再见(开鸽!

——————————————我———是———分———割———线—————————————

  稀里糊涂地讲完啦,不知道大家能理解多少😟。过一阵子应该会去上海吧,找朋友聚聚,散散心开启新生活,如正文结尾所说,我们有缘的话明年博客园再会啦,886✈️!文章来源地址https://www.toymoban.com/news/detail-549310.html

到了这里,关于基于JavaFX的扫雷游戏实现(五)——设置和自定义控件的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Java调用ChatGPT(基于SpringBoot和Vue)实现连续对话、流式输出和自定义baseUrl

    源码及更详细的介绍说明参见Git上的 README.md 文档 https://github.com/asleepyfish/chatgpt 本文Demo(SpringBoot和Main方法Demo均包括)的Git地址:https://github.com/asleepyfish/chatgpt-demo 流式输出结合Vue前端的Demo的Git地址:https://github.com/asleepyfish/chatgpt-vue 后续使用方法和api版本更新均在Github的READM

    2024年02月16日
    浏览(36)
  • QT自定义优雅的表单控件,简单实现设置界面布局

    FormView.h FormView.cpp 核心函数 函数 变量 功能 addEditableItem title: 输入框前面的提示文字,同时作为该控件的标识符 place_holder: 输入框中的提示文字 在表单中插入一个可填写项 addCheckableItem title: 不显示在UI中,仅作为该控件的标识符 content: 勾选框后面的内容 init_status: 勾选框的初

    2024年02月11日
    浏览(44)
  • VScode函数跳转,再返回,快捷键设置和自定义

    Win10: ctrl + 鼠标左键 Ubuntu:同上操作 win10: alt + leftarrow ← leftarrow ← ubuntu下: ctrl + alt + - 注意ubuntu下的, - 使用数字小键盘好像不咋好使,使用主键盘区的 - 才可以! 🤷‍♂️因此,在ubuntu下进行自定义设置返回原处按键,设置成和win10下面的操作一致,即 alt + leftarr

    2024年02月11日
    浏览(34)
  • 安卓开发——控件AlertDialog实现方式,设置下部三个按钮,自定义布局设置.setView(dialogView)样式,控件PopupWindow1常用方法,showAsDropDown构造方法

     AlertDialog . Builder builder = new AlertDialog . Builder ( context ); 构建 Dialog 的各种参数  Builder . setlcon ( int iconld ); 添加 ICON   Builder . setTitle ( CharSequence title ); 添加标题  Builder . setMessage ( CharSequence message ); 添加消息  Builder . setView ( View view ); 设置自定义布

    2024年02月03日
    浏览(44)
  • Ubuntu 22.04网络配置指南:如何设置静态IP和自定义DNS服务器

    找到并打开Netplan配置文件 : 在Ubuntu终端中,输入以下命令来编辑Netplan的配置文件: 请确保文件名与您系统中实际的文件名匹配。 更新配置文件 : 使用以下内容替换文件中的内容(根据实际情况调整接口名称 enp0s3 、IP地址、网关和DNS服务器): 在这个配置中: enp0s3 是网

    2024年04月11日
    浏览(58)
  • 【C语言】实现扫雷游戏

    详细介绍扫雷游戏的思路和实现过程,并用随机数实现埋雷。 • 使用控制台实现经典的扫雷游戏 • 游戏可以通过菜单实现继续玩或者退出游戏 • 扫雷的棋盘是9*9的格⼦ • 默认随机布置10个雷 • 可以排查雷 ◦ 如果位置不是雷,就显示周围有几个雷 ◦ 如果位置是雷,就炸

    2024年02月04日
    浏览(44)
  • 扫雷游戏的实现(C语言)

    对于扫雷游戏,大家应该都很熟悉吧,下面让我们来看看它是如何实现的。 目录 一、游戏规则及设计思想 二、各功能的代码实现 1.创建菜单 2、主函数的实现  3、创建棋盘并初始化 4、打印棋盘  5、布置雷的位置 (埋雷) 6、排查雷   三、代码汇总 1、game.h文件 2、game.c文

    2024年02月03日
    浏览(37)
  • 小游戏扫雷实现教学(详解)

    目录  【前言】 一、模块化程序设计(多文件编程)介绍 1.概述 2.传统编程的方式 3.模块化程序设计的方法 二、扫雷代码设计思路 三、扫雷代码设计 1.创建菜单函数  2.实现9x9扫雷 3.初始化棋盘  4.打印棋盘  5.随机布置雷的位置 6.排查雷的信息  7.回到步骤1,重新选择进入

    2024年02月12日
    浏览(32)
  • “纯C”实现——扫雷游戏(递归实现展开功能)

    📺游戏动画演示 🚀扫雷实现思路 🚀棋盘实现 🚀布置雷实现 🚀玩家扫雷实现 🚀带展开功能的扫雷 🚀小结语 🚗text.c文件 🚗game.h文件 🚗game.c文件 游戏实现完成的模样: 实现扫雷游戏的前提是要知道: 扫雷游戏的玩法 (会玩的跳过这步) 扫雷游戏也就是排雷,让玩家点

    2024年02月03日
    浏览(43)
  • C语言之扫雷游戏实现篇

    目录 主函数test.c 菜单函数 选择循环 扫雷游戏实现分析 整体思路  问题1 问题2  问题3 问题4  游戏函数(函数调用)  创建游戏盘数组mine 创建游戏盘数组show 初始化游戏盘数组InitBoard 展示游戏盘DisplayBoard 游戏盘置雷SetMine 游戏盘排雷FindMine test.c总代码 头文件函数声明game

    2024年02月11日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包