文章目錄
- 前言
- 第二版概述
- 右鍵選單功能實作
- 喚醒右鍵選單類
- 右鍵選單控制器類
- 物品展示選單欄
- 物品展示選單欄UI設計
- 物品展示選單欄控制器類
- 物品大家族
- 物品列舉類(全域變數的更好的選擇)
- 物品物體類
- 物品的倉庫(全域唯一,單例模式)
- 狀態家族
- 心情值狀態類
- 體力值狀態類
- 總狀態(全域唯一,單例模式)
- 一點點優化
- 后話
前言
我是一個喜歡打開設定界面一個個功能嘗試的人,以前試著設定了CSDN的訊息通知是每天一次的,心想“反正也沒什么人關注,設定了和沒設定一樣,”
23日我收到郵件說有人給我留言啦,我挺開心地,點開頁面一看,猛地發現我居然有600+的未讀訊息,可把我給嚇壞了

真的特別感謝大家的關注(*^▽^*)!這也給了我很大的動力,所以最近幾天也沒閑著,加班加點把功能一點點完善,
很多人想用demo嘗嘗鮮,我怕大家會失望所以沒有打包成安裝包,因為第一版真的特別特別簡陋

如果真的想運行,我當然毫不吝嗇,只是沒有打包成可執行檔案,還需要自己配置Java環境運行哦,專案的所有檔案我都放在GitHub上了,點擊訪問 => Jiang-TaiBai/IXiaoHei <=
第二版概述
大致上做了以下內容:
- 右鍵選單
- 設定了狀態類(心情、體力、清潔度等)
- 用具倉庫
- 用具的使用效果
- 優化了之前的代碼,并且設定了更多的單例模式(不知道這樣做好不好,容易造成記憶體泄漏,不過代碼寫起來更爽哈哈)
下圖是預覽圖,不知道為什么CSDN上播放著卡卡的,可惜視頻插不了(得要已上傳到其他平臺的視頻鏈接)

界面設計地不夠好,本人想著前期先把功能完善吧,等功能確定了,再沉下心來把界面整理一遍(找素材真的特別難!!!)

右鍵選單功能實作

基本思想就是當對羅小黑所在的ImageView這個Node右鍵的時候,打開一個FXML渲染的界面,該FXML界面又得用一層nominalStage(找不到好的命名的,直譯是名義上的舞臺),這樣就避免了在任務欄上多一個小logo,
該FXML設定很多按鈕,每一個按鈕對應著Controller的方法,
喚醒右鍵選單類
package org.taibai.hellohei.menu;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventType;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Rectangle2D;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import javafx.stage.Popup;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import org.taibai.hellohei.controller.ContextMenuController;
import java.io.IOException;
/**
* <p>Creation Time: 2021-09-25 04:36:35</p>
* <p>Description: 點擊本體觸發的右鍵選單</p>
*
* @author 太白
*/
public class ContextMenu {
private static ContextMenu contextMenu;
private ContextMenu() {
}
public static ContextMenu getInstance() {
if (contextMenu == null) contextMenu = new ContextMenu();
return contextMenu;
}
public void show(Node node, double screenX, double screenY) {
// ====== 設定名義上的stage,避免在任務欄生成一個小視窗 ======
final Stage nominalStage = new Stage();
nominalStage.initStyle(StageStyle.UTILITY);
nominalStage.setOpacity(0);
final Stage stage = new Stage();
stage.initOwner(nominalStage);
stage.initStyle(StageStyle.TRANSPARENT); // 設定視窗透明且無邊框
stage.setAlwaysOnTop(true); // 設定視窗總顯示在最前
// ====== 設定選單出現的位置,默認出現在游標的右下方,但是有兩種超出螢屏邊緣的情況 ======
Rectangle2D screenRectangle = Screen.getPrimary().getBounds();
double screenWidth = screenRectangle.getWidth();
double screenHeight = screenRectangle.getHeight();
double stageWidth = 138.0;
double stageHeight = 280.0;
// 如果彈出的右鍵選單超出螢屏下邊緣,就向上展開
if(screenY + stageHeight > screenHeight) screenY -= stageHeight;
// 如果彈出的右鍵選單超出螢屏右邊緣,就向左展開
if(screenX + stageWidth > screenWidth) screenX -= stageWidth;
stage.setX(screenX);
stage.setY(screenY);
// ====== 獲得fxml檔案 ======
FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/taibai/hellohei/fxml/ContextMenu.fxml"));
Parent root = null;
try {
root = loader.load();
} catch (IOException e) {
e.printStackTrace();
}
// ====== 獲得控制器實體 ======
ContextMenuController controller = loader.getController(); //獲取Controller的實體物件
controller.Init(stage, screenX, screenY);
// ====== 在stage中裝入scene,并為scene設定css樣式 ======
Scene scene = new Scene(root);
scene.setFill(Color.TRANSPARENT);
stage.setScene(scene);
scene.getStylesheets().addAll(this.getClass().getResource("/org/taibai/hellohei/fxml/ContextMenu.css").toExternalForm());
// ====== 當失去焦點的時候設定隱藏stage ======
stage.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (!stage.isFocused()) {
stage.close();
}
});
// ====== 展示選單 ======
nominalStage.show();
stage.show();
}
}
右鍵選單的位置
這個類叫做【喚醒右鍵選單類】,是通過點擊事件觸發的,因此可以獲得滑鼠點擊時候游標在螢屏上的坐標,所以只要設定城右鍵選單左上角坐標就行,
當然啦,就像我們在桌面上右擊一樣,如果右鍵選單超出了螢屏就應當在另一側出現,這里用如下代碼解決了這個需求:
// ====== 設定選單出現的位置,默認出現在游標的右下方,但是有兩種超出螢屏邊緣的情況 ======
Rectangle2D screenRectangle = Screen.getPrimary().getBounds();
double screenWidth = screenRectangle.getWidth();
double screenHeight = screenRectangle.getHeight();
double stageWidth = 138.0;
double stageHeight = 280.0;
// 如果彈出的右鍵選單超出螢屏下邊緣,就向上展開
if(screenY + stageHeight > screenHeight) screenY -= stageHeight;
// 如果彈出的右鍵選單超出螢屏右邊緣,就向左展開
if(screenX + stageWidth > screenWidth) screenX -= stageWidth;
stage.setX(screenX);
stage.setY(screenY);
關閉右鍵選單
這個功能可獻祭了我十幾根頭發

搜遍了全網找了無數種解決方法,終于給解決了,網上JavaFx的內容沒有Spring多,可謂是寸步難行吶~
解決方法就是對stage的focused屬性進行監聽,一旦發現!stage.isFocused(),就把stage關閉
stage.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (!stage.isFocused()) {
stage.close();
}
});
右鍵選單控制器類
右鍵選單控制器類主要控制的就是按鈕的觸發事件,目前只實作了喂食、洗澡,沒有看病的原因是因為我沒找到素材

沒事,模板搭好了,看病模塊只需要等素材到手就可以很快弄好啦,其他模塊還是需要時間沉淀的,
package org.taibai.hellohei.controller;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Rectangle2D;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import java.io.IOException;
/**
* <p>Creation Time: 2021-09-25 10:38:00</p>
* <p>Description: 右鍵選單的控制器</p>
*
* @author 太白
*/
public class ContextMenuController {
/**
* 點擊按鈕后應當隱藏一級選單
*/
private Stage preStage;
/**
* 打開的選單左上角X坐標,為了打開二級選單
*/
private double screenX;
/**
* 打開的選單左上角Y坐標,為了打開二級選單
*/
private double screenY;
public void Init(Stage stage, double screenX, double screenY) {
this.preStage = stage;
this.screenX = screenX;
this.screenY = screenY;
}
@FXML
public void eat() {
preStage.close();
showItemsWindow(ItemsWindowController.FoodTitle);
}
@FXML
public void bath() {
preStage.close();
showItemsWindow(ItemsWindowController.BathTitle);
}
private void showItemsWindow(String title) {
// ====== 設定名義上的stage,避免在任務欄生成一個小視窗 ======
final Stage nominalStage = new Stage();
nominalStage.initStyle(StageStyle.UTILITY);
nominalStage.setOpacity(0);
final Stage stage = new Stage();
stage.initOwner(nominalStage);
stage.initStyle(StageStyle.TRANSPARENT); // 設定視窗透明且無邊框
stage.setAlwaysOnTop(true); // 設定視窗總顯示在最前
// ====== 設定選單出現的位置,默認出現在游標的右下方,但是有兩種超出螢屏邊緣的情況 ======
stage.setX(screenX);
stage.setY(screenY);
// ====== 獲得fxml檔案 ======
FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/taibai/hellohei/fxml/ItemsWindow.fxml"));
Parent root = null;
try {
root = loader.load();
} catch (IOException e) {
e.printStackTrace();
}
// ====== 獲得控制器實體 ======
ItemsWindowController controller = loader.getController(); //獲取Controller的實體物件
controller.Init(title, stage);
// ====== 在stage中裝入scene,并為scene設定css樣式 ======
Scene scene = new Scene(root);
scene.setFill(Color.TRANSPARENT);
stage.setScene(scene);
scene.getStylesheets().addAll(this.getClass().getResource("/org/taibai/hellohei/fxml/ItemsWindow.css").toExternalForm());
// ====== 當失去焦點的時候設定隱藏stage ======
stage.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (!stage.isFocused()) {
stage.close();
}
});
// ====== 展示選單 ======
nominalStage.show();
stage.show();
}
}
這個和【喚醒右鍵選單類】幾乎一模一樣,畢竟實作的功能都是創建一個選單到關閉的程序,
該類引出了ItemsWindowController類,該類就是控制該選單的控制器
物品展示選單欄
物品展示選單欄UI設計

用戶需要選擇物品才能觸發使用物品(牛奶和雞蛋在第一集中就出現啦~等以后有時間扣下來就可以換成相對應的影片了,),那么里面的內容肯定不能是死的,所以我設計了如此頁面(經過無數次試驗 T_T,一直搞不懂怎么給vbox加一個滾動條):

從上到下依次是:
- 最外面的AnchorPane是整個物品清單的面板
- Label里的文字只需要更換,就能復用為食物、沐浴用品、藥物清單
- Pane……現在想想好像確實沒有設定的必要,等第三版優化吧
- ScrollPane是滾動窗格,這樣如果里面有很多物品可以滾動瀏覽了
- VBox用來放置物品特別方便,不用自己計算xy坐標了
物品展示選單欄控制器類
目前第二版打開二級選單會很卡,大致上分析應該是頻繁地增加洗掉節點導致的,希望第三版能將節點組織結構快取下來,畢竟里面的內容除了文字其他沒啥大的更改的,
package org.taibai.hellohei.controller;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import org.taibai.hellohei.items.bath.BathItem;
import org.taibai.hellohei.items.food.FoodItem;
import org.taibai.hellohei.items.ItemWarehouse;
import org.taibai.hellohei.state.TotalState;
import java.util.Map;
/**
* <p>Creation Time: 2021-09-27 00:32:16</p>
* <p>Description: 貨物串列控制器,該視窗用于展示食物、洗澡用品、打工串列</p>
*
* @author 太白
*/
public class ItemsWindowController {
public static final String FoodTitle = "食物倉庫";
public static final String BathTitle = "沐浴倉庫";
public static final String DrugTitle = "藥品倉庫";
@FXML
public Label title;
private String titleText;
@FXML
public Pane itemPane;
@FXML
public ScrollPane scrollPane;
@FXML
public VBox vbox;
private Stage stage;
public void Init(String title, Stage stage) {
this.stage = stage;
this.titleText = title;
this.title.setText(title);
vbox.setAlignment(Pos.TOP_CENTER);
vbox.setSpacing(10);
// 禁用左右滾軸
scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
loadItems();
}
private void loadItems() {
switch (titleText) {
case FoodTitle:
loadFoodItems();
break;
case BathTitle:
loadBathItems();
break;
case DrugTitle:
loadDrugItems();
}
}
private void loadFoodItems() {
Map<String, FoodItem> foodItemList = ItemWarehouse.getInstance().getFoodItemMap();
for (Map.Entry<String, FoodItem> entry : foodItemList.entrySet()) {
if (entry.getValue().getItemNum() != 0) {
vbox.getChildren().add(entry.getValue().toItemAnchorPane());
}
}
ObservableList<Node> children = vbox.getChildren();
children.forEach(c -> c.setOnMouseReleased(e -> {
if (TotalState.getInstance().getStaminaState().canIncrease()) {
stage.close();
}
}));
}
private void loadBathItems() {
Map<String, BathItem> bathItemList = ItemWarehouse.getInstance().getBathItemMap();
for (Map.Entry<String, BathItem> entry : bathItemList.entrySet()) {
if (entry.getValue().getItemNum() != 0) {
vbox.getChildren().add(entry.getValue().toItemAnchorPane());
}
}
ObservableList<Node> children = vbox.getChildren();
children.forEach(c -> c.setOnMouseReleased(e -> {
if (TotalState.getInstance().getCleanlinessState().canIncrease()) {
stage.close();
}
}));
}
private void loadDrugItems() {
// 小黑是不會生病的!(不是因為我找不到素材 QAQ)
}
}
裝載物品 loadItems ——拿食物舉例
用戶擁有總倉庫ItemWarehouse,可以查看自己所擁有的所有物品,因此在這里只需要遍歷倉庫里的所有東西(這里就算是0個也算倉庫里有這個物品,只是不會顯示出來而已),如果物品數量大于等于1,就加入到vbox中待顯示,
這里我將vbox每一個元素也就是anchorPane的生成封裝在了FoodItem類中,這樣也精簡了代碼量,提高了anchorPane的復用度(同一個物件,只更改文字就能再次使用)
Map<String, FoodItem> foodItemList = ItemWarehouse.getInstance().getFoodItemMap();
for (Map.Entry<String, FoodItem> entry : foodItemList.entrySet()) {
if (entry.getValue().getItemNum() != 0) {
vbox.getChildren().add(entry.getValue().toItemAnchorPane());
}
}
同時為每一個物品增加一個點擊事件,當點擊物品就代表要使用該商品(當然如果能用的話),要使用就默認關閉該視窗:
ObservableList<Node> children = vbox.getChildren();
children.forEach(c -> c.setOnMouseReleased(e -> {
if (TotalState.getInstance().getStaminaState().canIncrease()) {
stage.close();
}
}));
物品大家族
物品列舉類(全域變數的更好的選擇)
這里拿食物舉例,每一個食物應當包括如下資訊:
- 食物的名稱
- 食物的ID
- 食物的圖片資源路徑
- 吃某個食物能增加多少體力
- 吃這個食物對應的動作圖片資源路徑(未來想做成動作佇列,這樣三個或三個以上連續的動作就能實作了,無需自定義GIF了)
package org.taibai.hellohei.items.food;
/**
* <p>Creation Time: 2021-09-27 17:22:01</p>
* <p>Description: Item串列,列出所有食物、洗澡用品、藥物</p>
*
* @author 太白
*/
public enum FoodEnum {
EGG("雞蛋", "FOOD_001", "foods/egg.png", 10, "eat drumstick.gif"),
MILK("牛奶", "FOOD_002", "foods/milk.png", 5, "eat drumstick.gif");
/**
* 食物名稱
*/
private final String name;
/**
* 食物的唯一性ID
*/
private final String id;
/**
* 食物的圖片資源路徑
*/
private final String path;
/**
* 吃一個這個可以增加多少飽腹度(一般叫饑餓值,感覺不太對)
*/
private final int buff;
/**
* 吃東西的圖片資源路徑
*/
private final String actionPath;
public static final String pathPrefix = "/org/taibai/hellohei/img/";
FoodEnum(String name, String id, String path, int buff, String actionPath) {
this.name = name;
this.id = id;
this.path = path;
this.buff = buff;
this.actionPath = actionPath;
}
public String getName() {
return name;
}
public String getId() {
return id;
}
public String getPath() {
return pathPrefix + path;
}
public int getBuff() {
return buff;
}
public String getActionPath() {
return pathPrefix + actionPath;
}
}
物品物體類
一個物品應當有如下兩個資訊:
- 該物品是什么: foodEnum
- 該物品有多少: itemNum
另外我將物品在物品展示選單的窗格復用了,這樣只需要修改label就能在不重建AnchorPane的情況下復用原來的窗格了,
package org.taibai.hellohei.items.food;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import org.taibai.hellohei.constant.Constant;
import org.taibai.hellohei.img.ResourceGetter;
import org.taibai.hellohei.state.TotalState;
import org.taibai.hellohei.ui.Action;
import org.taibai.hellohei.ui.ActionExecutor;
import org.taibai.hellohei.ui.InterfaceFunction;
/**
* <p>Creation Time: 2021-09-27 17:09:50</p>
* <p>Description: 供以吃東西、洗澡、看病的物品</p>
*
* @author 太白
*/
public class FoodItem {
private FoodEnum foodEnum;
private int itemNum;
private AnchorPane anchorPane;
private Label label;
public FoodItem(FoodEnum foodEnum, int itemNum) {
this.foodEnum = foodEnum;
this.itemNum = itemNum;
init();
}
/**
* 創建一個AnchorPane供以在ItemsWindow顯示,同時要添加點擊事件
* 點擊后將產生用該物品的動作、并且減去一個物品
*
* @return 創建出來的AnchorPane
*/
public AnchorPane toItemAnchorPane() {
label.setText(foodEnum.getName() + "*" + itemNum);
return anchorPane;
}
/**
* 每次只是個數的變化,所以不需要再次創建新的物件,否則會特別耗時
*/
private void init() {
anchorPane = new AnchorPane();
ImageView imageView = new ImageView(ResourceGetter.getInstance().get(foodEnum.getPath()));
imageView.setFitWidth(86);
imageView.setFitHeight(86);
label = new Label(foodEnum.getName() + "*" + itemNum);
label.setLayoutX(0);
label.setLayoutY(66);
label.setMinWidth(86);
label.setMinHeight(20);
label.setAlignment(Pos.CENTER); //垂直水平居中
label.setStyle("-fx-background-color: rgba(0, 0, 0, 0.6); -fx-text-fill: white");
anchorPane.getChildren().addAll(imageView, label);
anchorPane.setOnMousePressed(e -> {
useItem();
});
}
private void useItem() {
if (itemNum <= 0) return;
if (!TotalState.getInstance().getStaminaState().canIncrease()) {
InterfaceFunction.getInstance().say("不能再吃啦~", Constant.UserInterface.SayingRunTime);
return;
}
decrease(1);
Action action = Action.creatTemporaryInterruptableAction(
foodEnum.getActionPath(),
Constant.UserInterface.ActionRunTime * 2,
Constant.ImageShow.mainImage
);
ActionExecutor.getInstance().execute(action);
InterfaceFunction.getInstance().say("真好吃", Constant.UserInterface.SayingRunTime);
// 增加體力值
TotalState.getInstance().getStaminaState().increase(foodEnum.getBuff());
}
/**
* 將該item數量增加num個
*
* @param num 增加的數量
*/
public void increase(int num) {
this.itemNum += num;
}
/**
* 將該item數量減少num個
*
* @param num 減少的數量
*/
public void decrease(int num) {
this.itemNum -= num;
itemNum = Math.max(0, itemNum);
}
/**
* 得到該item還有多少個
*
* @return item的數量
*/
public int getItemNum() {
return itemNum;
}
}
物品的倉庫(全域唯一,單例模式)
單機的寫死的倉庫當然簡單啦,這里默認每個物品都有10個,
未來這里可以接入后端,登錄后同步資料到本地,這也是為什么要設定物品ID,為了能唯一標識物品,
package org.taibai.hellohei.items;
import org.taibai.hellohei.items.bath.BathEnum;
import org.taibai.hellohei.items.bath.BathItem;
import org.taibai.hellohei.items.food.FoodEnum;
import org.taibai.hellohei.items.food.FoodItem;
import java.util.HashMap;
import java.util.Map;
/**
* <p>Creation Time: 2021-09-27 17:35:14</p>
* <p>Description: Item存量</p>
*
* @author 太白
*/
public class ItemWarehouse {
private static ItemWarehouse itemWarehouse;
private final Map<String, FoodItem> foodItemMap = new HashMap<>();
private final Map<String, BathItem> bathItemMap = new HashMap<>();
private ItemWarehouse() {
// 這里默認有10個,等后端系統寫好后就可以持久化了
for (FoodEnum foodEnum : FoodEnum.values()) {
foodItemMap.put(foodEnum.getId(), new FoodItem(foodEnum, 10));
}
// 這里也默認有10個洗澡用品
for (BathEnum bathEnum : BathEnum.values()) {
bathItemMap.put(bathEnum.getId(), new BathItem(bathEnum, 10));
}
}
public static ItemWarehouse getInstance() {
if (itemWarehouse == null) itemWarehouse = new ItemWarehouse();
return itemWarehouse;
}
public Map<String, FoodItem> getFoodItemMap() {
return foodItemMap;
}
public Map<String, BathItem> getBathItemMap() {
return bathItemMap;
}
}
狀態家族
心情值狀態類
心情值就是點擊小黑就能讓小黑開心,開心就得有個表示對吧,不僅是內部資料的更改,還有升起的文字表示(這里用云代替,暫時找不到好看的素材)

package org.taibai.hellohei.state;
import javafx.animation.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.util.Duration;
import org.taibai.hellohei.constant.Constant;
import org.taibai.hellohei.img.ResourceGetter;
/**
* <p>Creation Time: 2021-09-25 02:59:11</p>
* <p>Description: 小黑的心情值</p>
*
* @author 太白
*/
public class EmotionState {
/**
* 心情值,取值為[0, 100]
*/
private int emotion = 60;
public static final int Reduce_Step = 5;
public static final int Increase_Step = 10;
public static final int Max_Value = 100;
public static final int Min_Value = 0;
private ImageView imageView;
private final ResourceGetter resourceGetter = ResourceGetter.getInstance();
public EmotionState() {
imageView = new ImageView();
}
/**
* 心情值降低
*/
public void reduce() {
emotion = Math.max(Min_Value, emotion - Reduce_Step);
}
/**
* 心情值增加
*/
public void increase() {
if (emotion < Max_Value) showIncreasedAnimation();
emotion = Math.min(Max_Value, emotion + Increase_Step);
System.out.printf("[EmotionState::increase]-當前心情=%d\n", emotion);
}
/**
* 展示心情增加的影片
*/
private void showIncreasedAnimation() {
Image increasingImg = resourceGetter.get(Constant.ImageShow.emotionIncreasingImage);
imageView.setImage(increasingImg);
imageView.setStyle("-fx-background:transparent;");
// 設定相對于父容器的位置
imageView.setX(0);
imageView.setY(0);
imageView.setLayoutX(60);
imageView.setLayoutY(0);
imageView.setFitHeight(80); // 設定圖片顯示的大小
imageView.setFitHeight(80);
imageView.setPreserveRatio(true); // 保留width:height比例
imageView.setVisible(true);
double millis = Constant.UserInterface.ActionRunTime * 1000;
// 位移影片
TranslateTransition translateTransition = new TranslateTransition(Duration.millis(millis), imageView);
translateTransition.setInterpolator(Interpolator.EASE_BOTH);
translateTransition.setFromY(40);
translateTransition.setToY(0);
// translateTransition.play();
// 淡入淡出影片
FadeTransition fadeTransition = new FadeTransition(Duration.millis(millis), imageView);
fadeTransition.setFromValue(1.0);
fadeTransition.setToValue(0);
// 并行執行影片
ParallelTransition parallelTransition = new ParallelTransition();
parallelTransition.getChildren().addAll(
fadeTransition,
translateTransition
);
parallelTransition.play();
}
public ImageView getImageView() {
return imageView;
}
}
大致上就是讓一張圖片顯示,并且添加兩個影片:向上移動、淡入淡出
讓這兩個影片并行顯示,就得到圖中的效果啦,
體力值狀態類
俺其實不想叫做體力值的,以前一直叫“饑餓度”,但最近一細想,吃東西會導致“饑餓度”下降,雖然符合邏輯但是還是有點不符合邏輯?但是叫“飽腹度”、“飽脹度”好像更不順口?
package org.taibai.hellohei.state;
/**
* <p>Creation Time: 2021-09-28 00:51:27</p>
* <p>Description: 體力值</p>
*
* @author 太白
*/
public class StaminaState {
private int stamina = 60;
public static final int Reduce_Step = 2;
public static final int Max_Value = 100;
public static final int Min_Value = 0;
/**
* 體力值降低
*/
public void reduce() {
stamina = Math.max(Min_Value, stamina - Reduce_Step);
}
/**
* 體力值增加
*
* @param num 增加的量
*/
public void increase(int num) {
stamina = Math.min(Max_Value, stamina + num);
System.out.printf("[StaminaState::increase(%d)]-當前體力值=%d\n", num, stamina);
}
/**
* 是否還能增加
*
* @return 能否增加
*/
public boolean canIncrease() {
return stamina < Max_Value;
}
}
總狀態(全域唯一,單例模式)
之后取得狀態都是從總狀態取來的,所以設定為單例模式也能確保子狀態是唯一的,
package org.taibai.hellohei.state;
import javax.swing.text.html.ImageView;
/**
* <p>Creation Time: 2021-09-25 02:58:15</p>
* <p>Description: 小黑的所有狀態</p>
*
* @author 太白
*/
public class TotalState {
private static TotalState totalState;
private final EmotionState emotionState;
private final StaminaState staminaState;
private final CleanlinessState cleanlinessState;
private TotalState() {
emotionState = new EmotionState();
staminaState = new StaminaState();
cleanlinessState = new CleanlinessState();
}
public static TotalState getInstance() {
if (totalState == null) totalState = new TotalState();
return totalState;
}
public EmotionState getEmotionState() {
return emotionState;
}
public StaminaState getStaminaState() {
return staminaState;
}
public CleanlinessState getCleanlinessState() {
return cleanlinessState;
}
}
一點點優化
之前寫代碼考慮不周全,作為全域共享的物件設定成單例模式確實會省不少事情,就不用在構造物件的時候把物件傳來傳去的了,
這里將顯示GIF的ImageView與顯示ImageView的舞臺Stage都設定成了單例模式,我稱之為MainNode,意思是主要的節點
package org.taibai.hellohei.ui;
import javafx.scene.image.ImageView;
import javafx.stage.Stage;
/**
* <p>Creation Time: 2021-09-27 22:37:00</p>
* <p>Description: 動作的展示視窗,包括主界面的ImageView(展示GIF),Stage(控制整個程式的顯隱、關閉等)
* 畢竟是全域唯一的物件,所以設定為單例模式,全域拿到的就是唯一的物件</p>
*
* @author 太白
*/
public class MainNode {
private static MainNode mainNode;
private final ImageView imageView;
private final Stage stage;
private MainNode() {
imageView = new ImageView();
stage = new Stage();
}
public static MainNode getInstance() {
if (mainNode == null) mainNode = new MainNode();
return mainNode;
}
public ImageView getImageView() {
return imageView;
}
public Stage getStage() {
return stage;
}
}
并且將之前所有用到該ImageView/Stage的物件都重新修改了一遍,保證全域拿到的是同一個物件,
后話
十月后事情開始變多了,比賽也開始多起來了,報名的軟考也要如約而至了,希望閑暇時能繼續推進專案的開發
專案GitHub倉庫地址 => Jiang-TaiBai/IXiaoHei <=
國慶節快到了,預祝大家國慶節快樂呀~

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/303919.html
標籤:其他
上一篇:字串函式實作和講解
