From 0c2c9a66963588b48d63bf51763ea2ea559adfef Mon Sep 17 00:00:00 2001
From: zhanglei <383094403@qq.com>
Date: Tue, 1 Sep 2026 16:56:28 +0800
Subject: [PATCH] =?UTF-8?q?Update=20plugin=20Unity=E5=90=AF=E5=8A=A8?=
=?UTF-8?q?=E5=99=A8=20v1.0.1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Initial commit
- update
- feat: 优化项目导入方式
- update
---
plugins/unity-launcher/README.md | 52 +++++++++
plugins/unity-launcher/app.js | 70 ++++++++++--
plugins/unity-launcher/plugin.json | 14 ++-
plugins/unity-launcher/preload.js | 164 ++++++++++++++++++++++++-----
4 files changed, 263 insertions(+), 37 deletions(-)
create mode 100644 plugins/unity-launcher/README.md
diff --git a/plugins/unity-launcher/README.md b/plugins/unity-launcher/README.md
new file mode 100644
index 00000000..b90f26f9
--- /dev/null
+++ b/plugins/unity-launcher/README.md
@@ -0,0 +1,52 @@
+# Unity 启动器 (zTools 插件)
+
+
+
+**Unity 启动器** 是一款专为 Unity 开发者打造的高效项目与编辑器管理插件。无需依赖体积庞大的 Unity Hub,即可轻松实现 Unity 项目的批量扫描识别、多版本编辑器的智能匹配与一键快速启动。
+
+---
+
+## ✨ 核心特性
+
+- 🚀 **脱离 Unity Hub 依赖**
+ 轻量极速,直接关联本机已安装的 `Unity.exe`,一键打开项目,拒绝臃肿。
+
+- 🔍 **深度递归项目扫描**
+ - 支持选择单个项目文件夹、父级目录甚至整个磁盘根目录。
+ - 内置高性能**非阻塞异步扫描引擎**,支持多层级深度递归查找。
+ - 拥有智能白名单与目录黑名单规则(自动跳过 `node_modules`、`Library`、`Windows` 等冗余及系统受保护目录)。
+ - **实时进度反馈**:扫描过程中提供可视化模态框,实时显示已扫描目录数与已识别项目数,界面流畅不卡顿。
+
+- 🛠️ **编辑器版本智能匹配**
+ - 自动读取项目的 `ProjectSettings/ProjectVersion.txt` 识别所需 Unity 版本。
+ - **精确匹配优先**:自动调用版本完全一致的 `Unity.exe`。
+ - **智能版本回退**:若未精准匹配,优先选择最兼容的最新相近大版本编辑器打开。
+ - 支持为特定项目单独绑定或切换指定的 `Unity.exe`。
+
+- ⚡ **自动扫描本机 Unity 编辑器**
+ 自动检索常见安装路径(如 `C:/Program Files/Unity/Hub/Editor` 等),快速导入所有本地 Unity 编辑器,支持版本号解析与版本自然排序。
+
+- 📌 **快捷管理与丰富操作**
+ - 支持项目置顶、快速搜索、备注说明添加。
+ - 支持在文件资源管理器中打开、快捷清理本地项目记录及彻底删除项目文件夹等功能。
+
+- ⌨️ **快捷唤起指令**
+ 在 zTools 搜索框输入以下关键字均可唤起插件:`ul` | `unity` | `unity启动器` | `Unity项目`
+
+---
+
+## 📖 使用指南
+
+### 1. 批量添加 Unity 项目
+1. 点击顶部栏右侧的 `+ 添加项目` 按钮。
+2. 在弹出框中选择任意包含 Unity 项目的文件夹或磁盘盘符(例如 `D:\`)。
+3. 插件将自动开启后台异步扫描,并在界面中实时展示扫描进度。扫描完成后即可自动导入所有新发现的项目。
+
+### 2. 管理 Unity 编辑器
+1. 切换至 **“编辑器管理”** 选项卡。
+2. 点击 `自动扫描`,插件会自动检索常见安装路径下的 `Unity.exe`。
+3. 也可以点击 `添加编辑器` 手动选择指定的 `Unity.exe` 可执行文件。
+
+### 3. 打开与配置项目
+- **默认打开**:直接点击项目卡片上的 `打开项目` 按钮,插件将自动寻找最匹配的 Unity 编辑器启动。
+- **关联特定版本**:点击项目卡片底部的 `版本/编辑器设置`,可手动为该项目指定固定的 `Unity.exe`。
diff --git a/plugins/unity-launcher/app.js b/plugins/unity-launcher/app.js
index 71bea324..9d2bade5 100644
--- a/plugins/unity-launcher/app.js
+++ b/plugins/unity-launcher/app.js
@@ -177,11 +177,63 @@ function renderTabs() {
};
}
-function addItem() {
+async function addItem() {
if (state.tab === 'projects') {
- const list = services.pickFolder();
- if (!list || !list.length) return;
-
+ const selectedDir = services.selectFolder ? services.selectFolder() : null;
+ if (!selectedDir) return;
+
+ const progressHtml = `
+
+
正在深度扫描 Unity 项目,请稍候...
+
${esc(selectedDir)}
+
+
+
+ 已扫描目录: 0
+ 已识别项目: 0 个
+
+
+
+ `;
+
+ openModal({
+ title: '🔍 批量扫描 Unity 项目',
+ content: progressHtml,
+ isHtml: true,
+ showFooter: false
+ });
+
+ const dirTextEl = document.getElementById('scanCurrentDirText');
+ const dirCountEl = document.getElementById('scanProgressDirCount');
+ const foundCountEl = document.getElementById('scanProgressFoundCount');
+
+ let list = [];
+ try {
+ if (services.scanFolderAsync) {
+ list = await services.scanFolderAsync(selectedDir, ({ scannedCount, foundCount, currentDir }) => {
+ if (dirTextEl) dirTextEl.textContent = currentDir;
+ if (dirCountEl) dirCountEl.textContent = `已扫描目录: ${scannedCount}`;
+ if (foundCountEl) foundCountEl.textContent = `已识别项目: ${foundCount} 个`;
+ });
+ } else {
+ list = services.pickFolder() || [];
+ }
+ } catch (err) {
+ console.error('Scan failed:', err);
+ } finally {
+ const overlay = document.getElementById('modalOverlay');
+ if (overlay) overlay.classList.remove('active');
+ }
+
+ if (!list || !list.length) {
+ if (ztools.showNotification) {
+ ztools.showNotification('没有检测到新的 Unity 项目');
+ }
+ return;
+ }
+
let addedCount = 0;
list.forEach(r => {
if (state.projects.some(p => p.path === r.path)) return;
@@ -195,25 +247,25 @@ function addItem() {
});
addedCount++;
});
-
+
if (ztools.showNotification) {
if (addedCount > 0) {
- ztools.showNotification(`成功添加 ${addedCount} 个 Unity 项目`);
+ ztools.showNotification(`成功识别并添加 ${addedCount} 个 Unity 项目`);
} else {
- ztools.showNotification('没有检测到新的 Unity 项目或项目已存在');
+ ztools.showNotification('未扫描到新的 Unity 项目或项目已存在');
}
}
} else {
const list = services.pickExe();
if (!list || !list.length) return;
-
+
let addedCount = 0;
list.forEach(e => {
if (state.editors.some(x => x.path === e.path)) return;
state.editors.push(e);
addedCount++;
});
-
+
if (ztools.showNotification) {
if (addedCount > 0) {
ztools.showNotification(`成功识别并添加 ${addedCount} 个 Unity 编辑器`);
diff --git a/plugins/unity-launcher/plugin.json b/plugins/unity-launcher/plugin.json
index e1cb50c6..3d8155a5 100644
--- a/plugins/unity-launcher/plugin.json
+++ b/plugins/unity-launcher/plugin.json
@@ -3,7 +3,7 @@
"title": "Unity启动器",
"pluginName": "Unity启动器",
"description": "独立管理 Unity 项目与编辑器版本,一键打开项目(不依赖 Unity Hub)",
- "version": "1.0.0",
+ "version": "1.0.1",
"author": "zhanglei",
"homepage": "https://example.com",
"main": "index.html",
@@ -13,7 +13,15 @@
{
"code": "unity-launcher",
"explain": "打开 Unity 项目 / 管理编辑器版本",
- "cmds": ["ul", "unity", "unity启动器", "Unity项目"]
+ "cmds": [
+ "ul",
+ "unity",
+ "unity启动器",
+ "Unity项目"
+ ]
}
+ ],
+ "platform": [
+ "win32"
]
-}
+}
\ No newline at end of file
diff --git a/plugins/unity-launcher/preload.js b/plugins/unity-launcher/preload.js
index 261463b4..8b85513d 100644
--- a/plugins/unity-launcher/preload.js
+++ b/plugins/unity-launcher/preload.js
@@ -20,40 +20,30 @@ window.services = {
dbSet(key, val) { return ztools.dbStorage ? ztools.dbStorage.setItem(key, val) : null; },
// ---------- 文件/目录选择(支持单个/批量扫描) ----------
+ selectFolder() {
+ if (!ztools.showOpenDialog) return null;
+ const r = ztools.showOpenDialog({
+ title: '选择 Unity 项目文件夹(或选择磁盘根目录/父文件夹以批量扫描项目)',
+ properties: ['openDirectory'],
+ filters: [{ name: 'Unity 项目', extensions: ['*'] }]
+ });
+ if (!r || !r.length) return null;
+ return r[0];
+ },
+ scanFolderAsync(selectedPath, onProgress) {
+ return findUnityProjectsAsync(selectedPath, onProgress);
+ },
pickFolder() {
if (!ztools.showOpenDialog) return null;
const r = ztools.showOpenDialog({
- title: '选择 Unity 项目文件夹(或选择其父文件夹以批量扫描项目)',
+ title: '选择 Unity 项目文件夹(或选择磁盘根目录/父文件夹以批量扫描项目)',
properties: ['openDirectory'],
filters: [{ name: 'Unity 项目', extensions: ['*'] }]
});
if (!r || !r.length) return null;
const selectedPath = r[0];
- // 1. 检查选择 of the directory itself is a Unity project
- const selfProj = validateProject(selectedPath);
- if (selfProj.isUnityProject) {
- return [selfProj];
- }
-
- // 2. If it is not a project itself, scan its first-level subdirectories
- const subprojects = [];
- const fs = require('fs');
- const path = require('path');
- try {
- const files = fs.readdirSync(selectedPath, { withFileTypes: true });
- for (const file of files) {
- if (file.isDirectory()) {
- const subPath = path.join(selectedPath, file.name);
- const subProj = validateProject(subPath);
- if (subProj.isUnityProject) {
- subprojects.push(subProj);
- }
- }
- }
- } catch (e) {}
-
- return subprojects;
+ return findUnityProjects(selectedPath);
},
pickExe() {
if (!ztools.showOpenDialog) return null;
@@ -174,6 +164,130 @@ window.services = {
}
};
+async function findUnityProjectsAsync(dir, onProgress, depth = 0, maxDepth = 8, state = { scannedCount: 0, found: [] }) {
+ const fs = require('fs');
+ const path = require('path');
+
+ state.scannedCount++;
+
+ if (state.scannedCount === 1 || state.scannedCount % 10 === 0) {
+ if (onProgress) {
+ onProgress({
+ scannedCount: state.scannedCount,
+ foundCount: state.found.length,
+ currentDir: dir
+ });
+ }
+ if (state.scannedCount % 10 === 0) {
+ await new Promise(resolve => setTimeout(resolve, 0));
+ }
+ }
+
+ // 1. 检查当前选择/搜索的目录自身是否为 Unity 项目
+ const selfProj = validateProject(dir);
+ if (selfProj.isUnityProject) {
+ state.found.push(selfProj);
+ if (onProgress) {
+ onProgress({
+ scannedCount: state.scannedCount,
+ foundCount: state.found.length,
+ currentDir: dir
+ });
+ }
+ return state.found;
+ }
+
+ if (depth >= maxDepth) return state.found;
+
+ try {
+ const files = await fs.promises.readdir(dir, { withFileTypes: true });
+ for (const file of files) {
+ if (file.isSymbolicLink && file.isSymbolicLink()) continue;
+
+ if (file.isDirectory()) {
+ const nameLower = file.name.toLowerCase();
+ if (
+ file.name.startsWith('.') ||
+ file.name.startsWith('$') ||
+ nameLower === 'node_modules' ||
+ nameLower === 'library' ||
+ nameLower === 'assets' ||
+ nameLower === 'projectsettings' ||
+ nameLower === 'temp' ||
+ nameLower === 'logs' ||
+ nameLower === 'obj' ||
+ nameLower === 'build' ||
+ nameLower === 'builds' ||
+ nameLower === 'windows' ||
+ nameLower === 'program files' ||
+ nameLower === 'program files (x86)' ||
+ nameLower === 'programdata' ||
+ nameLower === 'appdata' ||
+ nameLower === 'system volume information'
+ ) {
+ continue;
+ }
+ const subPath = path.join(dir, file.name);
+ await findUnityProjectsAsync(subPath, onProgress, depth + 1, maxDepth, state);
+ }
+ }
+ } catch (e) {}
+
+ return state.found;
+}
+
+function findUnityProjects(dir, depth = 0, maxDepth = 8) {
+ const fs = require('fs');
+ const path = require('path');
+
+ // 1. 检查当前选择/搜索的目录自身是否为 Unity 项目
+ const selfProj = validateProject(dir);
+ if (selfProj.isUnityProject) {
+ return [selfProj];
+ }
+
+ if (depth >= maxDepth) return [];
+
+ let results = [];
+ try {
+ const files = fs.readdirSync(dir, { withFileTypes: true });
+ for (const file of files) {
+ if (file.isSymbolicLink && file.isSymbolicLink()) continue;
+
+ if (file.isDirectory()) {
+ const nameLower = file.name.toLowerCase();
+ if (
+ file.name.startsWith('.') ||
+ file.name.startsWith('$') ||
+ nameLower === 'node_modules' ||
+ nameLower === 'library' ||
+ nameLower === 'assets' ||
+ nameLower === 'projectsettings' ||
+ nameLower === 'temp' ||
+ nameLower === 'logs' ||
+ nameLower === 'obj' ||
+ nameLower === 'build' ||
+ nameLower === 'builds' ||
+ nameLower === 'windows' ||
+ nameLower === 'program files' ||
+ nameLower === 'program files (x86)' ||
+ nameLower === 'programdata' ||
+ nameLower === 'appdata' ||
+ nameLower === 'system volume information'
+ ) {
+ continue;
+ }
+ const subPath = path.join(dir, file.name);
+ const subProjects = findUnityProjects(subPath, depth + 1, maxDepth);
+ if (subProjects.length > 0) {
+ results = results.concat(subProjects);
+ }
+ }
+ }
+ } catch (e) {}
+ return results;
+}
+
function findUnityExes(dir, depth = 0) {
if (depth > 3) return []; // 限制深度为 3 层,避免在大目录下卡死
const fs = require('fs');