??它來了它來了,最后一期終于來了。理論上該講的全都講完了,只剩下那個拖了好幾期的自定義控件和一個比較沒有存在感的設置功能沒有講。所以這次就重點介紹它們倆吧。
??首先我們快速瀏覽下設置的實現(xiàn),上圖:
??然后是控制器代碼:
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 // 文本框組, 自定義游戲數(shù)據(jù)
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();
}
});
}
}
??和上期排行版難度按鈕類似,都是單選按鈕分組然后設置對應點擊事件。不同的是不像排行版切換那樣直觀,這里需要一個提示來讓玩家清楚保存是生效了的,所以我設置了保存按鈕和對應的動畫提示。下面介紹自定義控件的實現(xiàn)(設置真的沒有存在感,哈哈哈)。
??LedNumber,這個東西可是費了我老半天勁。我的思路是既然想在界面上顯示,他要么是布局要么是控件,經過嘗試后發(fā)現(xiàn)還是控件合理。所以這個自定義類要繼承 Control 類,然后按照要求實現(xiàn) createDefaultSkin 方法。到這里我就不會了,它要求的返回值類型為 Skin<?>,這是啥,沒見過啊。求助萬能的GPT后大概明白了它的要求(我理解的不一定準確)——控件顯示是需要有Skin的,沒有的話就類似無內容的Label,不設置背景色在界面上看起來就跟沒有一樣。所以我按GPT的提示創(chuàng)建了對應的skin類 LedNumberSkin,并在里面進行外觀設計。
??設計思路受這篇文章啟發(fā):https://blog.csdn.net/hx0_0_8/article/details/8012448
??其思想就是將數(shù)字看作由以下七個線段組合而成,不同數(shù)字使用不同的線段:
??只是這樣看起來還不夠美觀,所以可以對這些線段的拼接處進行處理,比如下面這種形式(繪制的有些簡陋,代碼中是可以控制連接處貼合的,適當留白更立體):
??那么代碼中是如何實現(xiàn)的呢?對于每一條邊,可以使用多邊形Polygon類實現(xiàn),只需要依次寫入它的坐標即可(必須是順時針或者逆時針,起始點位置不做要求),如下:
/**
* 計算自定義多邊形各頂點坐標
*
* @param toward 多邊形朝向 [01234: 右下左上中]
* @param x 起始點橫坐標
* @param y 起始點縱坐標
* @return 坐標數(shù)組
*/
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;
}
??有了繪制方法,接下來就是具體數(shù)字需要的初始化方法:
/**
* 構建點陣數(shù)字需要的邊
*/
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));
// 根據(jù)edges數(shù)組判斷每條邊待設置的顏色
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數(shù)組,控制每條邊的顏色顯示 [true:亮紅色, false:暗紅色]
??這樣就有了每個數(shù)字對應的外觀,由于我使用的jdk版本較早,所以不支持 LedNumber 類運行中修改 Skin,所以采用以下方式實現(xiàn)數(shù)字顯示切換:
/**
* 切換數(shù)字顯示
* @param index 要轉化成的數(shù)字
*/
public void switchSkin(int index) {
// 清空當前控件的子節(jié)點, 重新添加
getChildren().clear();
LedNumber newLedNumber = new LedNumber(index);
getChildren().add(newLedNumber);
}
??這樣就完成了LED數(shù)字的顯示,感覺這個想法還是很不錯的。而且在這個基礎上你甚至還可以自己實現(xiàn)電子萬年歷之類的程序,有興趣的伙伴可以嘗試下。到此本次掃雷項目已經介紹差不多了,如果您還有疑問歡迎在評論區(qū)留言,有緣明年再見(開鴿!)
——————————————我———是———分———割———線—————————————文章來源:http://www.zghlxwxcb.cn/news/detail-549310.html
??稀里糊涂地講完啦,不知道大家能理解多少??。過一陣子應該會去上海吧,找朋友聚聚,散散心開啟新生活,如正文結尾所說,我們有緣的話明年博客園再會啦,886??!文章來源地址http://www.zghlxwxcb.cn/news/detail-549310.html
到了這里,關于基于JavaFX的掃雷游戲實現(xiàn)(五)——設置和自定義控件的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!