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 Launcher](logo.png) + +**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');