主頁 > 軟體設計 > 桌面寵物開發——羅小黑(二)

桌面寵物開發——羅小黑(二)

2021-09-29 08:21:27 軟體設計

文章目錄

  • 前言
  • 第二版概述
  • 右鍵選單功能實作
    • 喚醒右鍵選單類
    • 右鍵選單控制器類
  • 物品展示選單欄
    • 物品展示選單欄UI設計
    • 物品展示選單欄控制器類
  • 物品大家族
    • 物品列舉類(全域變數的更好的選擇)
    • 物品物體類
    • 物品的倉庫(全域唯一,單例模式)
  • 狀態家族
    • 心情值狀態類
    • 體力值狀態類
    • 總狀態(全域唯一,單例模式)
  • 一點點優化
  • 后話

前言

我是一個喜歡打開設定界面一個個功能嘗試的人,以前試著設定了CSDN的訊息通知是每天一次的,心想“反正也沒什么人關注,設定了和沒設定一樣,”
23日我收到郵件說有人給我留言啦,我挺開心地,點開頁面一看,猛地發現我居然有600+的未讀訊息,可把我給嚇壞了
請添加圖片描述
真的特別感謝大家的關注(*^▽^*)!這也給了我很大的動力,所以最近幾天也沒閑著,加班加點把功能一點點完善,
很多人想用demo嘗嘗鮮,我怕大家會失望所以沒有打包成安裝包,因為第一版真的特別特別簡陋
請添加圖片描述
如果真的想運行,我當然毫不吝嗇,只是沒有打包成可執行檔案,還需要自己配置Java環境運行哦,專案的所有檔案我都放在GitHub上了,點擊訪問 => Jiang-TaiBai/IXiaoHei <=

第二版概述

大致上做了以下內容:

  1. 右鍵選單
  2. 設定了狀態類(心情、體力、清潔度等)
  3. 用具倉庫
  4. 用具的使用效果
  5. 優化了之前的代碼,并且設定了更多的單例模式(不知道這樣做好不好,容易造成記憶體泄漏,不過代碼寫起來更爽哈哈)

下圖是預覽圖,不知道為什么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;
    }
}

物品物體類

一個物品應當有如下兩個資訊:

  1. 該物品是什么: foodEnum
  2. 該物品有多少: 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

標籤:其他

上一篇:字串函式實作和講解

下一篇:技術分享 | 嵌入式常用濾波演算法的matlab實作

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more