从零开始的Java桌宠(一)
文章目录
0.介绍
制作一个Win桌面宠物, 功能:
编写IDE: VSCode ; 语言: Java;
1. 准备工作
首先,准备好你绘制的图片,保存为支持的格式(如PNG、JPG等), 最好是透明底, 这里我用的是ps导出png格式.
2. 配置Java环境
2.1 选择Project Type
-
使用轻量级的
VScode
IDE, 并在扩展中安装插件: Java Extension Pack:包含了对 Java 开发的基本支持,如代码补全、调试、运行等功能。
然后你就可以新建一个Java Project了, 新建时它会提示你选择一个
Project Type
:项目类型怎么选
-
在 VSCode 中选择 Java 项目类型时,提示的这些选项实际上代表了不同的构建工具和框架。它们用于帮助你管理和构建项目,但对于初学者来说,理解它们的作用是有帮助的。以下是每个选项的简要介绍:
- No build tools(推荐)
- 适用场景:如果你只是想快速编写一个简单的 Java 程序,没有复杂的依赖管理需求,可以选择这个选项。
- 特点:不使用任何构建工具(比如 Maven 或 Gradle),你手动管理项目文件。适合初学者和小型项目。
- Maven
- 适用场景:如果你的项目需要管理多个依赖项,或者你打算将项目发布到某个仓库,Maven 是一个非常常用的构建工具。
- 特点:
- 自动管理依赖项。
- 使用
pom.xml
文件来描述项目和依赖。 - 支持构建、打包、发布等操作。
- 对大型企业级项目非常有帮助。
- Gradle
- 适用场景:与 Maven 类似,Gradle 也是一个构建工具,但它的语法更加灵活,支持多种语言的构建,通常在性能和配置灵活性上表现更好。
- 特点:
- 比 Maven 更加现代化,支持 Groovy 或 Kotlin DSL(领域特定语言)来配置。
- 支持增量构建和并行构建,适合更复杂的项目。
- JavaFX
-
适用场景:如果你打算开发桌面应用程序(如 Java 桌宠),可以选择 JavaFX。
-
特点:
-
专为桌面应用设计,支持图形界面、动画和用户交互。
-
提供丰富的 UI 组件,可以用于构建现代化的桌面应用程序。
你该选择哪个?
-
-
如果你只是创建一个简单的 Java 程序或桌宠应用,并且不需要使用构建工具(例如 Maven 或 Gradle),选择 “No build tools” 就可以了。你可以直接开始编写代码,VSCode 会自动处理 Java 的编译和运行。如果你不确定,可以先选择它,这不会影响你后续的学习和开发,也可以在后期添加构建工具来优化项目管理。
-
如果你打算使用图形界面,做桌面应用,可以选择 JavaFX,它是专门为桌面应用提供的框架,可以帮助你更容易地创建图形界面和动画。
-
如果你以后打算扩展项目,使用构建工具(如 Maven 或 Gradle)来管理依赖,可以选择 Maven 或 Gradle。
所以这里我们先选 No build tools 就好, 选它也可以import JavaFX库.
2.2 *找不到JAVA_HOME?
-
问题出在 Java 的安装路径和环境变量没有正确设置。即使你已经安装了 Java,但如果系统找不到 Java 的路径,通常是因为
JAVA_HOME
环境变量没有正确配置,或者Path
环境变量中没有包括 Java 的bin
目录。解决步骤:
-
确保 Java 安装路径正确
确认 Java 安装目录在C:\Program Files (x86)\Java
中,例如C:\Program Files (x86)\Java\jdk-17
,检查该路径下是否有bin
目录,其中包含java.exe
和javac.exe
文件。 -
设置 JAVA_HOME 环境变量
需要设置JAVA_HOME
环境变量,让系统知道 JDK 的安装路径。步骤:
- 右键点击 此电脑 或 计算机,选择 属性。
- 点击 高级系统设置。(直接在Win搜这步就可以了)
- 在 系统属性 窗口,点击 环境变量。
- 在 系统变量 区域,点击 新建,然后添加:
- 变量名:
JAVA_HOME
- 变量值:JDK 的安装路径,例如:
C:\Program Files (x86)\Java\jdk-17
。
- 变量名:
-
修改 Path 环境变量
还需要将JAVA_HOME
路径中的bin
目录添加到Path
环境变量中,这样你就可以在任何地方运行java
和javac
命令。步骤
- 在 系统变量 区域,找到并选中
Path
,点击 编辑。 - 点击 新建,然后输入
%JAVA_HOME%\bin
(这会自动引用你设置的JAVA_HOME
环境变量的bin
子目录)。 - 确定保存所有修改。
- 在 系统变量 区域,找到并选中
-
验证环境变量设置
完成设置后,打开一个新的 命令提示符 窗口(非常重要,因为已经打开的命令提示符不会加载新的环境变量)。(使用Win+R输入cmd打开, 而不是使用IDE中的终端)输入以下命令来验证:
echo %JAVA_HOME%
这应该输出你设置的 JDK 安装路径,例如:
C:\Program Files (x86)\Java\jdk-17
然后再运行:
java -version javac -version
如果一切配置正确,你应该能够看到 Java 和 Javac 的版本信息。
额外注意:(反正我试了加不加引号都可以)
-
路径中有空格的问题:你提到 Java 安装在
Program Files (x86)
下,路径中包含空格,这通常不会成为问题,只要正确设置了环境变量。如果你在设置Path
或JAVA_HOME
时遇到问题,可以用双引号括起来,如:"C:\Program Files (x86)\Java\jdk-17"
通过以上步骤,应该能够解决 “java -version” 找不到 Java 的问题。如果仍然有问题,可以确认 JDK 是否正确安装,或者重新安装 JDK。
-
2.3 *设置VScode终端同步cmd
我们需要确保 VSCode 的终端配置文件(settings.json
)能正确加载 Java 的环境变量。
步骤:
-
打开 VSCode 的
settings.json
文件:- 在 VSCode 中,点击左下角的齿轮图标(设置),然后选择 Settings。
- 在右上角的搜索框中,输入 “JSON”,然后选择 Edit in settings.json,这将打开 VSCode 的配置文件。
-
手动配置终端环境变量: 在
settings.json
中添加以下配置来确保JAVA_HOME
和Path
环境变量在 VSCode 终端中生效:{ "terminal.integrated.env.windows": { "JAVA_HOME": "此处是你的jdk路径", "Path": "此处是你的jdk路径\\bin;${env:Path}" } }
这会直接在 VSCode 的终端中设置
JAVA_HOME
和Path
,使其指向正确的 Java 安装路径。 -
重启 VSCode: 配置文件更新后,重启 VSCode 并打开一个新的终端,确保新的设置生效。
** 检查 Java 环境变量**:
-
打开 VSCode 中的终端,然后输入:
echo $env:JAVA_HOME echo $env:Path
检查是否能正确显示
JAVA_HOME
和包含 Javabin
目录的Path
环境变量。
2.4 *Java下载/更新?
我之前安装的 Java6版本太老了, VScode的 Maven尝试连接的远程服务器(如 Maven 中央仓库)不再支持较旧的 SSL/TLS 协议版本. 故需要下载或者更新 Java. 这边推荐java8.
-
方法一:
找到
Java控制面板
它一般在和jdk文件夹同级的另一个文件夹jre
的子文件夹bin
里, 程序名是javacpl.exe
, 双击打开即可. 然后选择更新. -
方法二:
卸了重装. 就正常win卸载程序的步骤就行, 注意Oracle官网版本8往上是要收费的. 所以Java8就好.
问题
不使用Java8自带的javafx, 外配更高版本的javafx-sdk是不可以的哦. (我手动下载配置了javafx17结果报错了, 因为fx版本过高, java是不兼容的 ).
3. 先验知识
- 基本Java语法
实现一个 “Hello, world!” 吧. 注意vscode中按Ctrl+F5
实现调试.
public class Pet {
public static void main(String[] args) {
//Start
System.out.println("Hello,Pet!");
}
}
- 基本GUI知识
显示一张图片
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Pet extends Application {
public static void main(String[] args) {
launch(args); // 启动 JavaFX 应用程序
}
@Override
public void start(Stage primaryStage) {
// 创建一个图片视图对象
Image image = new Image("file:/D:/WORK/Unity/Pet/Pet.jpg"); // 你的图片路径
ImageView imageView = new ImageView(image);
// 将图片放到一个容器中
StackPane root = new StackPane();
root.getChildren().add(imageView);
// 创建场景并设置到舞台
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("My Java Pet");
primaryStage.setScene(scene);
// 显示窗口
primaryStage.show();
}
}
调整图片大小
// 调整图片大小
imageView.setFitWidth(300); // 设置图片的宽度
imageView.setFitHeight(250); // 设置图片的高度
imageView.setPreserveRatio(true); // 保持图片的纵横比
调整原始图片比例:
// 调整图片大小为原尺寸的八分之一
imageView.setFitWidth(image.getWidth() /8 );
imageView.setFitHeight(image.getHeight() / 8);
imageView.setPreserveRatio(true); // 保持图片的纵横比
设置窗口大小适应屏幕: (下面是一处定义变量, 两处修改)
import javafx.stage.Screen;
// 获取屏幕尺寸
double screenWidth = Screen.getPrimary().getVisualBounds().getWidth();
double screenHeight = Screen.getPrimary().getVisualBounds().getHeight();
// 设置初始位置
double initialY = screenHeight / 2 - imageView.getFitHeight() / 2;
imageView.setY(initialY);
// 创建场景并设置到舞台
Scene scene = new Scene(root, screenWidth, screenHeight);
图片上下移动:
使用 JavaFX 的 Timeline 和 KeyFrame 来实现简单的动画效果。例如,可以让你的桌宠图片上下移动,或者实现其他的动画效果。
// 定义动画的方向和移动速度
boolean[] movingDown = {true};
double moveAmount = 10; // 每次移动的像素值
// 创建动画:每隔10毫秒更新一次位置
Timeline timeline = new Timeline(
new KeyFrame(Duration.millis(10), event -> {
if (movingDown[0]) {
System.out.println("Moving Down");
imageView.setY(imageView.getY() + moveAmount);
// 当到达底部边界时改变方向
if (imageView.getY() >= 250 - imageView.getFitHeight()) {
movingDown[0] = false;
}
} else {
System.out.println("Moving Up");
imageView.setY(imageView.getY() - moveAmount);
// 当到达顶部边界时改变方向
if (imageView.getY() <= 0) {
movingDown[0] = true;
}
}
})
);
timeline.setCycleCount(Timeline.INDEFINITE); // 无限循环
timeline.setRate(0.1); // 动画速率
timeline.play(); // 播放动画
// 将图片放到一个容器中
Pane root = new Pane();
root.getChildren().add(imageView);
*图片不移动?
解决方案: 容器的格式不要选择StackPane, 而选择Pane.
在 JavaFX 中,
StackPane
和Pane
是不同类型的布局容器,它们的布局策略不同,这就是使用StackPane
导致图片看似没有移动的原因:
StackPane
的布局策略
StackPane
会将所有子节点堆叠在一起,并将它们对齐到容器的中心。当你通过代码改变子节点(如ImageView
)的y
坐标时,StackPane
会尝试重新将子节点对齐到中心位置,从而覆盖了你手动设置的y
坐标变化。也就是说,你在代码里让图片上下移动,但StackPane
会不断地把图片拉回到中心位置,这样图片就好像没有移动一样。
Pane
的布局策略
Pane
是一个非常简单的布局容器,它不会对子节点进行任何布局调整。当你将子节点添加到Pane
中时,子节点会保持你设置的位置和大小。所以,当你通过代码改变ImageView
的y
坐标时,Pane
不会干扰这个变化,图片就能按照你的代码逻辑正常上下移动。
去掉窗口:
// 设置舞台样式为无装饰,去掉窗口边框, 且设置背景为透明色
scene.setFill(Color.TRANSPARENT);
primaryStage.initStyle(StageStyle.TRANSPARENT);
右击图片显示 exit
按键, 退出桌宠:
使用 JavaFX 的 ContextMenu
组件。ContextMenu
是一个弹出式菜单,通常在用户右键单击时显示。
// 创建右键菜单
ContextMenu contextMenu = new ContextMenu();
MenuItem exitMenuItem = new MenuItem("Exit");
exitMenuItem.setOnAction(e -> {
primaryStage.close(); // 关闭舞台,退出程序
});
contextMenu.getItems().add(exitMenuItem);
// 为图片视图添加右键菜单事件处理
imageView.setOnContextMenuRequested(e -> {
contextMenu.show(imageView, e.getScreenX(), e.getScreenY());
});
动画消失一会儿, 播放一会儿, 周期循环: (注意这里的movementTimeline代替了上文的Timeline)
//注意这里的movementTimeline代替了上文的Timeline, 所以定义处要做修改, 这里没写, 如下所示
//movementTimeline = new Timeline(...
movementTimeline.setCycleCount(Timeline.INDEFINITE); // 无限循环
movementTimeline.play(); // 初始时暂停移动动画
// 创建显示和隐藏的时间线
appearanceTimeline = new Timeline(
new KeyFrame(Duration.seconds(2), event -> {
System.out.println("Showing image and starting movement");
imageView.setVisible(false); // 隐藏图片
movementTimeline.pause(); // 暂停移动动画
}),
new KeyFrame(Duration.seconds(3), event -> {
System.out.println("Hiding image and pausing movement");
imageView.setVisible(true); // 显示图片
movementTimeline.play(); // 开始移动动画
})
);
appearanceTimeline.setCycleCount(Timeline.INDEFINITE);
appearanceTimeline.play();
实现宠物具有 “重力” 效果
即悬空时停止上下移动动画并往下掉落,直到接触屏幕底部边缘:
实现思路
- 添加重力逻辑:在动画循环中,当宠物处于悬空状态时,使其
Y
坐标以一定速度增加,模拟下落效果。- 检测底部边缘:在下落过程中,检查宠物是否接触到屏幕底部边缘,如果接触到则停止下落。
- 暂停上下移动动画:当宠物开始下落时,暂停之前的上下移动动画。
private Timeline gravityTimeline;
double gravitySpeed = 3.5; // 重力下落速度
// 检查是否悬空,若悬空则切换到重力动画, 这段添加在总移动动画的else{}后面
if (imageView.getY() < screenHeight - imageView.getFitHeight()-15) {
movementTimeline.pause();
gravityTimeline.play();
}
// 创建重力动画
gravityTimeline = new Timeline(
new KeyFrame(Duration.millis(20), event -> {
double newY = imageView.getY() + gravitySpeed;
if (newY <= screenHeight - imageView.getFitHeight()) {
imageView.setY(newY);
} else {
gravityTimeline.pause(); // 到达底部,停止下落
movementTimeline.play(); // 回到底部后恢复移动动画
}
})
);
/*最后记得在时间线那里
gravityTimeline.play(); // 开始重力动画 和
gravityTimeline.pause(); // 暂停重力动画
*/
设置桌宠始终置顶, 不被其他程序窗口遮挡:
primaryStage.setAlwaysOnTop(true); // 设置舞台始终置顶
鼠标拖拽Ta:
import javafx.scene.input.MouseEvent;
private double mouseXOffset;
private double mouseYOffset;
// 添加鼠标拖动功能
imageView.setOnMousePressed((MouseEvent event) -> {
mouseXOffset = event.getSceneX() - imageView.getX();
mouseYOffset = event.getSceneY() - imageView.getY();
movementTimeline.pause();
gravityTimeline.pause();
});
imageView.setOnMouseDragged((MouseEvent event) -> {
double newX = event.getSceneX() - mouseXOffset;
double newY = event.getSceneY() - mouseYOffset;
// 限制宠物在屏幕范围内移动
newX = Math.max(0, Math.min(newX, screenWidth - imageView.getFitWidth()));
newY = Math.max(0, Math.min(newY, screenHeight - imageView.getFitHeight()));
imageView.setX(newX);
imageView.setY(newY);
});
imageView.setOnMouseReleased((MouseEvent event) -> {
// 释放鼠标后检查是否悬空,若悬空则启动重力动画
if (imageView.getY() < screenHeight - imageView.getFitHeight()) {
movementTimeline.pause();
gravityTimeline.play();
} else {
movementTimeline.play();
}
});
by xc