文章目录
- 前言
- 第二版概述
- 右键菜单功能实现
- 唤醒右键菜单类
- 右键菜单控制器类
- 物品展示菜单栏
- 物品展示菜单栏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 <=
国庆节快到了,预祝大家国庆节快乐呀~