基于JavaFX的扫雷游戏实现(二)——游戏界面

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

  废话环节:看过上期文章的小伙伴现在可能还是一头雾水,怎么就完成了核心内容,界面呢?哎我说别急让我先急,博主这不夜以继日地肝出了界面部分嘛。还是老规矩,不会把所有地方都照顾到,只挑一些有代表性的内容介绍,您各位多担待🙏。另外博主的JavaFX是跟着B站视频速成的,指路👉:https://www.bilibili.com/video/BV1Qf4y1F7Zv  有哪些地方讲的不对欢迎在评论区友好交流🤝。

  上期内容已经介绍了游戏初始数据,即地雷和数字分布情况的二维数组,那么如何把它与图形界面对应到一起呢?如果您熟悉JavaFX的各种布局和控件的话,很容易会联想到GridPane布局。至于可以点击的格子,用label或button也好,用rectangle绘制矩形也罢,只要看起来像那回事,能设置对应点击事件就OK。选完角儿后就是代码环节了,考虑到纯java代码实现界面不够直观,所以推荐使用fxml文件,因为有对应的可视化设计工具。这里我采用的是Scene Builder,建议大家也了解下。下面给出游戏界面设计图:

基于JavaFX的扫雷游戏实现(二)——游戏界面

  图中各部分内容所要承担的功能如下:

  • 上方左右两侧的黑色格子是用于显示剩余标记计数和游戏用时的;
  • 按钮是游戏重置按钮,不论游戏是否结束,点击就可以重新开局;
  • 下方大片区域是要存放格子的GridPane布局;

  设计完毕后生成的fxml文件如下(对于 controller 或 fx:id 等内容需要手动设置):

game.fxml
 <?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.text.Font?>

<AnchorPane fx:id="anchorPane"
            prefWidth="400" prefHeight="500"
            maxHeight="-Infinity" maxWidth="-Infinity"
            minHeight="-Infinity" minWidth="-Infinity"
            xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1"
            fx:controller="controllers.GameController">
    <children>
        <Label fx:id="labelTop" style="-fx-background-color: #8e7f7f; ">
            <font>
                <Font size="1.0"/>
            </font>
        </Label>
        <Label fx:id="labelBottom" style="-fx-background-color: #8e7f7f;">
            <font>
                <Font size="1.0"/>
            </font>
        </Label>
        <Label fx:id="labelLeft" style="-fx-background-color: #8e7f7f;">
            <font>
                <Font size="1.0"/>
            </font>
        </Label>
        <Label fx:id="labelRight" style="-fx-background-color: #8e7f7f;">
            <font>
                <Font size="1.0"/>
            </font>
        </Label>
        <Label fx:id="labelCenter" style="-fx-background-color: #8e7f7f;">
            <font>
                <Font size="1.0"/>
            </font>
        </Label>
        <GridPane fx:id="grid"/>
        <GridPane fx:id="mark" prefWidth="80.0" prefHeight="45.0" AnchorPane.topAnchor="35.0"
                  AnchorPane.leftAnchor="20.0" style="-fx-background-color: #000000; -fx-hgap: 5.0"/>
        <GridPane fx:id="time" prefWidth="80.0" prefHeight="45.0" AnchorPane.topAnchor="35.0"
                  AnchorPane.rightAnchor="20.0" style="-fx-background-color: #000000; -fx-hgap: 5.0"/>
        <Button fx:id="reset" prefHeight="50.0" prefWidth="50.0" AnchorPane.topAnchor="35.0" onAction="#onResetClick"/>
    </children>
</AnchorPane>

  你可能会有疑问,为什么图中没有格子按钮呢?原因很简单,以扫雷简单模式为例,9*9大小,一共要81个格子。这部分内容如果手动添加可太费时费力了,因为它们初始状态完全一致,所以建议在代码中通过循环来实现,如下:

for (int i = 0; i < GAME.height; ++i) {
    for (int j = 0; j < GAME.width; ++j) {
        Button button = new Button();
        // 设置边界线的外观效果, 使按钮看起来更突出
        button.setBorder(new Border(new BorderStroke(Color.web("#737373"), BorderStrokeStyle.SOLID, new CornerRadii(4), new BorderWidths(1))));
        button.setPadding(new Insets(0));
        // 设置按钮大小和点击事件
        button.setPrefSize(GAME.buttonSize, GAME.buttonSize);
        button.setOnMouseClicked(event -> {
            handleEvent(event);
        });
        // 添加按钮到指定位置
        grid.add(button, j, i);
    }
}

  而对于错位的重置按钮和暂时不可见的五个label边框,考虑到后续设置不同游戏难度的情况,这部分内容在代码中设置比较合适,我的做法如下:

/**
  * 调整边框以及其他组件的位置和大小
  */
private void adjustControls() {
    HashMap<String, Double> params = GAME.genParamsMap();
    double thickness = params.get("thickness");
    double offset = params.get("offset");
    double lenVertical = params.get("lenVertical");
    double lenHorizontal = params.get("lenHorizontal");

    // 计算实际窗口宽高
    WIDTH_OFFSET += lenHorizontal + thickness * 2;
    HEIGHT_OFFSET += lenVertical;

    // 设置窗口大小
    anchorPane.setPrefSize(WIDTH_OFFSET, lenVertical);

    // 设置网格布局位置
    AnchorPane.setTopAnchor(grid, offset + thickness);
    AnchorPane.setLeftAnchor(grid, thickness);

    // 设置重置按钮的位置
    reset.setStyle("-fx-background-size: contain; -fx-background-image: url(" + SMILE_IMG + ")");
    AnchorPane.setLeftAnchor(reset, thickness + (lenHorizontal - 50) / 2);

    // 设置边框标签的大小和位置
    labelTop.setPrefSize(lenHorizontal, thickness);
    AnchorPane.setLeftAnchor(labelTop, thickness);
    AnchorPane.setTopAnchor(labelTop, 0.0);

    labelCenter.setPrefSize(lenHorizontal, thickness);
    AnchorPane.setLeftAnchor(labelCenter, thickness);
    AnchorPane.setTopAnchor(labelCenter, offset);

    labelBottom.setPrefSize(lenHorizontal, thickness);
    AnchorPane.setLeftAnchor(labelBottom, thickness);
    AnchorPane.setTopAnchor(labelBottom, lenVertical - thickness);

    labelLeft.setPrefSize(thickness, lenVertical);
    AnchorPane.setLeftAnchor(labelLeft, 0.0);
    AnchorPane.setTopAnchor(labelLeft, 0.0);

    labelRight.setPrefSize(thickness, lenVertical);
    AnchorPane.setLeftAnchor(labelRight, lenHorizontal + thickness);
    AnchorPane.setTopAnchor(labelRight, 0.0);
}

  注:GAME为游戏难度枚举类实例,genParamsMap是用于生成计算所需数据的静态方法

  完整的枚举类代码如下:

GameEnum
 package components;

import java.util.HashMap;

/**
 * @description: 游戏难度枚举
 * @author: 郭小柒w
 * @time: 2023/6/11
 */
public enum GameEnum {
    EASY(9, 9, 10, 40.0, 30.0),
    MEDIUM(16, 16, 40, 35.0, 25.0),
    HARD(30, 16, 99, 30.0, 20.0),
    CUSTOM();

    // 游戏难度规格[宽 x 高], 相应地雷个数
    public int width, height, bomb;
    // 网格按钮尺寸, 数字字体大小
    public double buttonSize, numSize;

    GameEnum(int width, int height, int bomb, double buttonSize, double numSize) {
        this.width = width;
        this.height = height;
        this.bomb = bomb;
        this.buttonSize = buttonSize;
        this.numSize = numSize;
    }

    GameEnum() {
        this.buttonSize = 35.0;
        this.numSize = 25.0;
    }

    // 宽和高限制在简单和困难之间
    public void setWidth(int width) {
        if (width < EASY.width) {
            this.width = EASY.width;
        } else if (width > HARD.width) {
            this.width = HARD.width;
        } else {
            this.width = width;
        }
    }

    public void setHeight(int height) {
        if (height < EASY.height) {
            this.height = EASY.height;
        } else if (height > HARD.height) {
            this.height = HARD.height;
        } else {
            this.height = height;
        }
    }

    // 地雷数介于格子数之间
    public void setBomb(int bomb) {
        if (bomb < 0) {
            this.bomb = 0;
        } else if (bomb > width * height) {
            this.bomb = width * height;
        } else {
            this.bomb = bomb;
        }
    }

    /**
     * 生成游戏窗口和边框大小计算需要用到的参数
     * @return 参数集合
     */
    public HashMap<String, Double> genParamsMap() {
        HashMap<String, Double> params = new HashMap();
        // 标签宽度, 固定值10
        double thickness = 10.0;
        params.put("thickness", thickness);
        // 中间位置的标签框相对于布局顶部的偏移量, 固定值110
        double offset = 110.0;
        params.put("offset", offset);
        // 边框标签边的水平和竖直长度, 宽度为固定值10
        double lenVertical = height * buttonSize + thickness * 2 + offset;
        double lenHorizontal = width * buttonSize;
        params.put("lenVertical", lenVertical);
        params.put("lenHorizontal", lenHorizontal);
        return params;
    }
}

  为什么要使用枚举类对游戏难度进行区分呢?如果您完整地阅读过我的代码,就会发现MineSweeper类仅负责对接游戏进行中的各种逻辑,对于游戏难度、计时判断、排行计算等功能可以说完全不参与。这是因为和win7自带的扫雷不同,我打算新增一个菜单页,而不是运行程序直接开始游戏。这就需要我合理划分每个类负责的功能,不然就要全部塞进MineSweeper类里,显得过于臃肿(事实上大二时期我用awt和swing干过这种蠢事,那一版扫雷几百行的代码全在一个类里,没有注释还bug百出🤡)。你也可以把难度作为MineSweeper类的一个属性来处理,不过这会导致和难度有关的逻辑修改起来比较麻烦,比如下面的代码是我进行游戏初始化的部分:

public void initialize() {
    // 重置剩余可用标记数
    REST_FLAG = GAME.bomb;
    // 重置点击状态
    CLICKED = NO;
    // 重置游戏状态
    STATE = UNSURE;
    // 重置计时器
    if (TIMELINE != null) {
        TIMELINE.stop();
        TIMELINE = null;
    }
    // 生成新游戏的用到的数据
    mineSweeper = new MineSweeper(GAME.width, GAME.height, GAME.bomb, new int[GAME.height][GAME.width]);
    // 设置监听
    addListener();
    // 绘制界面
    adjustControls();
    // 填充网格布局
    addToGrid();
}

  很显然,如果没有使用枚举类,创建minesweeper对象的语句将会更繁琐。因为那需要你根据一个难度全局变量,使用if-else或者switch语句对其进行判断,然后才能设置对应长宽地雷数,另外想要增加一个新的难度时也不可避免地要修改多处代码。而现在仅需要这个全局变量是枚举类实例。

  至于图中计数和计时两个黑框框为什么不显示内容,这是因为我想实现液晶数字显示的效果,就像计算器(时代眼泪)的显示风格那样。这种情况没有官方类库可以使用,只能魔改大神轮子做一个自定义控件来满足我的需求,内容较多放在下期再说。

  有了fxml文件和初始化代码(下期展开讲),通过这段代码来生成界面:

/**
 * 打开新窗口
 *
 * @param filePath fxml文件相对路径
 * @param method   方法名
 */
public void openNewWindow(String filePath, String method) {
    try {
        parent = (Stage) anchorPane.getScene().getWindow();
        // 加载设置界面布局文件
        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(getClass().getResource(filePath));
        Parent root = loader.load();
        Scene scene = new Scene(root);
        // 设置Stage
        Stage stage = new Stage();
        stage.setResizable(false);
        if ("onPlayClick".equals(method)) {
            // 根据实际效果重置窗口大小
            stage.setOnShown(event -> {
                stage.setWidth(WIDTH_OFFSET);
                stage.setHeight(HEIGHT_OFFSET);
            });
        }
        // 设置左上角图标
        stage.getIcons().add(new Image(ICON_IMG));
        stage.setScene(scene);
        // 设置父窗体
        stage.initOwner(anchorPane.getScene().getWindow());
        // 设置除当前窗体外其他窗体均不可编辑
        stage.initModality(Modality.WINDOW_MODAL);
        // 隐藏父窗口
        parent.hide();
        stage.setOnCloseRequest(event -> {
            if(TIMELINE != null) {
                TIMELINE.stop();
                TIMELINE = null;
            }
            // 显示父窗口
            parent.show();
            // 还原更改的值
            WIDTH_OFFSET = 6.0;
            HEIGHT_OFFSET = 35.0;
        });
        stage.showAndWait();
    } catch (IOException e) {
        System.out.println("Error on [Class:MenuController, Method:" + method + "]=>");
        e.printStackTrace();
    }
}

  打开游戏界面:

/**
 * 点击开始新游戏
 */
public void onPlayClick() { openNewWindow("/fxmls/game.fxml", "onPlayClick"); }

  最终效果图如下(以简单模式为例):

基于JavaFX的扫雷游戏实现(二)——游戏界面

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

  不知道本期的介绍有没有让您对项目更加了解呢?是否对没有讲的部分更加期待呢?如果看完所有代码后仍有不清楚地方,请在评论区中指出。我会抽时间回复或者出一期答疑😀。下期的话打算讲讲交互的实现,网格按钮点击事件第一期已经介绍过了所以下期不会着重说明。感谢各位阅读,我们下期不见不散👋文章来源地址https://www.toymoban.com/news/detail-521388.html

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

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

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

相关文章

  • C#开发的OpenRA游戏加载界面的实现

    C#开发的OpenRA游戏加载界面的实现 游戏的UI是一个游戏必备, 但是游戏的UI都是自己处理的,不能使用像Windows自带的UI。 这样游戏的UI,其实也是使用游戏的方式来显示的, 只不过使用了低帧率的方式来显示。 比如OpenRA游戏界面,就会显示如下: 游戏的界面有很多,先从一

    2024年02月01日
    浏览(41)
  • 在unity中设置游戏死亡界面并实现跳转

    首先是整体的游戏场景,本次场景是一个跑酷游戏 附上UI场景结构 ,GM为节点。 GM代码为 using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; public class GM : MonoBehaviour {     public GameObject panel;            //此处panel是UI列表中的panel,需要挂上

    2023年04月16日
    浏览(29)
  • 简单的Unity中设置游戏死亡界面并实现跳转

    在Unity中设置游戏死亡界面可以通过以下步骤完成: 创建新的场景:在Unity的菜单栏中选择“File”,然后选择“New Scene”。这将创建一个新的、空的场景。 创建游戏死亡UI:在新的场景中创建一个游戏死亡界面。可以使用Unity的UI工具来创建UI元素,例如文本、按钮、背景等。

    2024年02月06日
    浏览(45)
  • Unity 关卡跳转——开始界面切换到游戏场景的实现

    1 .在已有关卡的基础上,另外新建一个关卡,将其命名为start并进行保存。 2 .在新建立的start关卡中创建TitleScreen.cs脚本,代码如下。 (值得注意的是,这里不要忘记使用unity引擎提供的SceneManagement类,即在脚本顶部添加 using UnityEngine.SceneManagement;) 3 .将TitleScreen.cs脚本挂载到

    2024年02月11日
    浏览(31)
  • 飞机打方块(二)游戏界面制作

    1.新建plane节点  2.新建脚本GameController.ts,并绑定Canvas  GameControll.ts   Game.ts Bullet.ts: 1.新建state_lb_parent节点:  2.新建Label节点  绑定Canvas  GameController.ts    GameController.ts 1.新建所有障碍父节点 2.新建预制体barrier ,新建Barrier脚本  3.新建子节点  GameController.ts Barrier.ts 4.绑定

    2024年02月12日
    浏览(30)
  • C#开发的OpenRA游戏的游戏界面内鼠标处理窗口

    C#开发的OpenRA游戏的游戏界面内鼠标处理窗口 OpenRA游戏里,游戏上面所有物品显示,都是基于窗口容器。 前面也讨论过,其实就是基于ingame.yaml文件来布局和创建的, 在ingame.yaml文件里,根窗口Container@INGAME_ROOT,共有六个子节点: Children:     LogicKeyListener@GLOBAL_KEYHANDLER:   

    2024年02月11日
    浏览(32)
  • 使用Pygame创建一个简单游戏界面

    首先需要安装Pygame 模块,在Python代码中添加引用。 1. 引用代码如下: 2. 定义初始化窗口函数: 在初始化窗口函数中,定义窗口大小和窗口标题。 3. 创建一个循环,不断更新界面和检测事件 加载背景图片,将背景图片对象放置在窗口上,位置(0,0) 最左角,图片有实际的

    2024年02月13日
    浏览(38)
  • Unity游戏开始界面制作教学

    第一步: 新建一个Scene 第二步: 在Scene里添加一个Canvas对象(在Hierarchy右键–UI–Canavas) 如何给添加开始按钮: 在Canvas右键–UI–Button 如何给按钮添加文字: Button的子对象Text的Text组件可以修改按钮上的文字 如何给按钮添加事件: Button对象的Button组件最下面有一个“Oncl

    2024年02月02日
    浏览(30)
  • 【C#项目实战】控制台游戏勇士斗恶龙(1)——游戏初始设置以及开始界面

    君兮_的个人主页 即使走的再远,也勿忘启程时的初心 C/C++ 游戏开发 Hello,米娜桑们,这里是君兮_,最近开始正式的步入学习游戏开发的正轨,想要通过写博客的方式来分享自己学到的知识和经验,这就是开设本专栏的目的。希望这些独立的C#小项目能对做游戏的你有所帮助,

    2024年02月09日
    浏览(40)
  • 游戏界面设计:打造吸引人的视觉体验

    如何进行游戏界面设计?游戏UI界面设计的前景如何?我相信这是许多UI设计师和想要转向UI设计的设计师非常关心的问题。今天,小将和你谈谈。 首先,游戏界面设计师的前景仍然很好。游戏用户界面是一门研究人机交互的学科,是一个新兴的热门方向和活跃的领域。此外,

    2024年02月06日
    浏览(35)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包