资讯动态

用 Godot 和 PowerShell 实现 C 盘清理工具:Windows 桌面工具实战

发布时间:2026/9/4 21:47:08 来源:尧图企业网站定制
使用游戏引擎写系统工具听起来像“混搭”但实际折腾下来非常有意思。最近做 Windows 测试机时C 盘空间反复告警又不想为这点小事上个重客户端于是基于 Godot 做了一个轻量 C 盘清理工具项目代号叫做CleanScope。本文会把从环境搭建、UI 设计、目录扫描到调用 PowerShell 清理的完整思路整理出来代码可以直接在 Godot 4.x 中运行适合正在学习 Godot、又对桌面工具开发感兴趣的开发者。1. 为什么选择 Godot PowerShell 做 C 盘清理1.1 C 盘爆满的常见原因日常开发中C 盘占用上涨通常是几个固定“凶手”在叠加系统临时目录C:\Windows\Temp当前用户临时目录%TEMP%Windows Update 下载缓存浏览器产生的缓存文件回收站中残留的旧文件部分软件生成的日志和崩溃转储文件单独看每一个目录占用的空间可能并不大但经年累月之后里面会堆积大量小文件和旧的压缩包最终把系统盘塞满。手工清理也不是不行但需要记住一堆路径删除时还要担心会不会把正在运行的程序文件干掉。正因如此做一个可视化、可勾选、可预览大小的清理工具是一个很有价值的练手项目。1.2 为什么不用 WinForms而用 Godot做 Windows 桌面工具传统方案通常是 WinForms / WPF / Electron。WinForms 足够熟悉WPF 写界面也成熟Electron 则依赖 Node.js 运行环境。使用 Godot 做这类工具的出发点有两个Godot 的 UI 节点树自带布局能力可以快速做出一个带进度条、列表、按钮的桌面界面如果后续愿意扩展成游戏中的“系统控制台”或者做成一个集成开发小工具Godot 里可以直接复用那套场景和脚本逻辑。但 Godot 毕竟不是为系统管理设计的它没有内置的 Windows 磁盘信息查询接口也没有现成的“清空临时目录”按钮。所以 CleanScope 采用了一个相对务实的架构Godot 负责界面展示、用户交互、结果列表PowerShell 负责执行 Windows 系统级操作GDScript 通过OS.execute()调用 PowerShell并把返回结果解析展示。这种方案分工明确既利用了 Godot 的跨平台 UI 能力又弥补了游戏引擎在系统操作上的短板。1.3 CleanScope 的能力边界这里需要先讲清楚“能做什么”和“不应该做什么”否则很容易写出一个危险工具。CleanScope 主要提供以下功能读取指定磁盘已经使用和剩余的空间对一组经过筛选的“安全可清理目录”进行占用扫描在扫描结果中展示目录名称、路径、占用大小通过复选框决定是否清理调用 PowerShell 删除目录下的临时内容在界面中输出清理前后磁盘剩余空间的对比。CleanScope 默认不提供对C:\Windows\System32、C:\Windows\WinSxS这类系统关键目录的删除操作。它的定位是安全的“临时文件顺手清理工具”不是系统文件分析器。2. 环境准备与项目初始化2.1 运行环境下面的示例基于以下环境整理Windows 10 或 Windows 11Godot 4.x 官方标准版PowerShell 5.1Windows 自带不需要额外第三方依赖你的电脑上如果安装的是 Godot 3.x部分 API 名称会有变化比如DirAccess.open()的用法不同建议直接使用 Godot 4.x。2.2 创建 Godot 工程打开 Godot 项目管理器点击“新建项目”项目名称CleanScope项目路径按自己的目录习惯选择例如D:\Projects\CleanScope渲染器选择Forward、Mobile或Compatibility都可以因为本项目不涉及 3D 渲染推荐Compatibility以降低导出体积创建完成后在项目目录下建立以下文件夹CleanScope/ ├── scenes/ # 存放场景文件 ├── scripts/ # 存放 GDScript └── project.godot # Godot 自动生成2.3 UI 节点结构建议在scenes目录下新建一个主场景main.tscn根节点类型选择Control。CleanScope 的 UI 不需要多复杂核心节点结构如下MainScene(Control) ├── VBoxContainer │ ├── Label # 顶部标题 │ ├── HBoxContainer # 工具栏 │ │ ├── Button # 扫描按钮 │ │ ├── Button # 清理按钮 │ │ └── ProgressBar # 扫描进度 │ ├── HBoxContainer # 主体内容 │ │ ├── VBoxContainer │ │ │ ├── ProgressBar # 磁盘容量进度条 │ │ │ └── Label # 磁盘空间文本 │ │ └── ItemList # 可清理项目列表 │ └── RichTextLabel # 日志区域在需要引用的节点上可以开启“唯一名称”选项这样脚本中可以通过%DriveBar、%CleanList的方式快速访问。组件不必手动摆放只要把每个控件拖到对应容器中Godot 会自动完成横向或纵向排列。3. 核心概念GDScript 目录遍历与 PowerShell 调用3.1 GDScript 遍历目录的基本写法如果只是遍历某个非系统目录GDScript 自带的DirAccess足够用。下面是一个最简单的目录遍历示例它会递归统计某个目录下所有文件的总大小func calculate_dir_size(path: String) - int: var total: int 0 var dir : DirAccess.open(path) if dir null: return 0 dir.list_dir_begin() var file_name : dir.get_next() while file_name ! : if file_name . or file_name ..: file_name dir.get_next() continue if dir.current_is_dir(): total calculate_dir_size(path.path_join(file_name)) else: var full_path : path.path_join(file_name) var file : FileAccess.open(full_path, FileAccess.READ) if file: total file.get_length() file.close() file_name dir.get_next() dir.list_dir_end() return total这段代码的思路是打开指定路径逐项读取目录内容如果是子目录递归继续统计如果是文件通过FileAccess.open()打开后读取文件长度累加所有文件大小后返回。不过这种递归写法在扫描大型目录时会比较慢而且遇到没有权限访问的目录时返回的错误信息不够直观。因此 CleanScope 中真正做大目录扫描时更推荐交给 PowerShell 完成。3.2 为什么把系统操作交给 PowerShellWindows 对中文路径、长路径、系统权限的处理在 PowerShell 中已经很成熟。PowerShell 可以使用下面的写法让无法访问的文件自动跳过Get-ChildItem -Path $dir -Recurse -Force -ErrorAction SilentlyContinue-ErrorAction SilentlyContinue会让脚本在遇到权限不足或文件被占用时继续向下执行这是系统清理工具非常关键的能力。GDScript 中调用外部命令的接口是OS.execute()基础用法如下var output: Array [] var args : PackedStringArray([-NoProfile, -Command, Get-Date]) var exit_code : OS.execute(powershell.exe, args, output, true) print(output)注意OS.execute()的参数是PackedStringArray返回值是进程退出码。如果 PowerShell 命令执行成功通常返回0。3.3 可清理目录清单CleanScope 默认清理的目录不是随意枚举的它只面向确定安全的临时和缓存区域。目录作用默认推荐C:\Windows\Temp系统临时目录是%USERPROFILE%\AppData\Local\Temp当前用户临时目录是C:\Windows\SoftwareDistribution\DownloadWindows Update 下载缓存可选%USERPROFILE%\AppData\Local\Microsoft\Windows\INetCache系统 Web 缓存可选回收站已删除文件暂存区可选在真正的工程中不建议把“浏览器缓存目录”写死到代码里因为每个人的浏览器安装路径不同浏览器版本升级后也可能改变缓存位置。4. CleanScope 关键代码实现4.1 获取磁盘剩余空间获取 C 盘剩余空间的核心命令是Get-PSDrive -Name C它返回的对象中包含Used和Free两个属性。为了在 GDScript 中方便解析可以让 PowerShell 将其转换为 JSONGet-PSDrive -Name C | Select-Object {nUsed;e{$_.Used}}, {nFree;e{$_.Free}} | ConvertTo-Json -Compress对应的 GDScript 代码如下func get_drive_space(drive_letter: String) - Dictionary: if drive_letter.length() ! 1 or not drive_letter[0] in ABCDEFGHIJKLMNOPQRSTUVWXYZ: return {} var ps_command : (Get-PSDrive -Name drive_letter \ | Select-Object {nUsed;e{$_.Used}}, {nFree;e{$_.Free}}) | ConvertTo-Json -Compress var output: Array [] var args : PackedStringArray([ -NoProfile, -NonInteractive, -ExecutionPolicy, Bypass, -Command, ps_command ]) var exit_code : OS.execute(powershell.exe, args, output, true) if exit_code ! 0 or output.is_empty(): push_warning(无法获取磁盘信息) return {} var json_text: String output[0].strip_edges() var result JSON.parse_string(json_text) if typeof(result) ! TYPE_DICTIONARY: return {} return result这里对drive_letter做了一个简单校验确保只允许单个大写字母。这是防止外部传入恶意 PowerShell 命令的第一道防线即便工具只在本机使用也应该保留这种校验。4.2 让 PowerShell 脚本文件在运行时生成很多人会直接把res://路径传给 PowerShell这是不推荐的。Godot 在导出后res://对应的是打包资源不一定能直接被 PowerShell 以文件路径方式读取。CleanScope 的做法是把需要用到的 PowerShell 脚本内容写到user://数据目录下的一个文件中再由 GDScript 调用。GDScript 中需要把user://转换成系统绝对路径使用ProjectSettings.globalize_path()var user_dir : user://cleanscope DirAccess.make_dir_recursive_absolute(user_dir) var script_path : ProjectSettings.globalize_path(user_dir /clean_scope.ps1)这样做的好处是不修改res://pack内部文件程序退出后日志和临时脚本保留用户可以打开对应目录检查脚本内容做到行为透明。4.3 生成扫描与清理脚本将下面的 PowerShell 脚本内容写入clean_scope.ps1。脚本接收一个 JSON 数组作为目标列表通过Mode区分扫描与清理。param( [string]$Mode, [string]$TargetsJson, [string]$OutFile ) $targets $TargetsJson | ConvertFrom-Json $results () foreach ($item in $targets) { $name $item.name $path $item.path if (-not (Test-Path -LiteralPath $path)) { $results [PSCustomObject]{ name $name path $path size 0 exists $false cleaned $false } continue } if ($Mode -eq scan) { $size (Get-ChildItem -LiteralPath $path -Recurse -Force -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum if ($null -eq $size) { $size 0 } $results [PSCustomObject]{ name $name path $path size [int64]$size exists $true cleaned $false } } if ($Mode -eq clean) { $cleaned $false if (Test-Path -LiteralPath $path) { $items Get-ChildItem -LiteralPath $path -Force -ErrorAction SilentlyContinue if ($items) { $items | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue $cleaned $true } } $results [PSCustomObject]{ name $name path $path size 0 exists $true cleaned $cleaned } } } $results | ConvertTo-Json -Compress | Set-Content -LiteralPath $OutFile -Encoding UTF8这里清理的是“目录下的内容”而不是目录本身这样不会破坏Temp目录的系统引用。4.4 构造目标列表CleanScope 的清理项目定义为一个常量数组。为了让用户名路径在不同电脑上都能生效使用USERPROFILE环境变量动态拼接func build_clean_targets() - Array: var user_profile : OS.get_environment(USERPROFILE) if user_profile.is_empty(): user_profile C:/Users/Default var targets : [ { name: 系统临时目录, path: C:/Windows/Temp, checked: true }, { name: 当前用户临时目录, path: user_profile /AppData/Local/Temp, checked: true }, { name: Windows 更新缓存, path: C:/Windows/SoftwareDistribution/Download, checked: false }, { name: 系统 Web 缓存, path: user_profile /AppData/Local/Microsoft/Windows/INetCache, checked: false } ] return targets实际 UI 中可以把这个数组渲染到ItemList或Tree上并允许用户通过复选框决定是否勾选。4.5 扫描按钮逻辑点击扫描按钮后CleanScope 把勾选的目标转成 JSON传给 PowerShell 脚本并将输出文件读取回来。func _on_scan_pressed() - void: var targets: Array build_clean_targets() var json : JSON.stringify(targets) var target_file : user_dir /targets.json var result_file : user_dir /scan_result.json var target_file_abs : ProjectSettings.globalize_path(target_file) var result_file_abs : ProjectSettings.globalize_path(result_file) var f : FileAccess.open(target_file_abs, FileAccess.WRITE) if f: f.store_string(json) f.close() var script_abs : ProjectSettings.globalize_path(ps_script_path) var args : PackedStringArray([ -NoProfile, -NonInteractive, -ExecutionPolicy, Bypass, -File, script_abs, -Mode, scan, -TargetsJson, target_file_abs, -OutFile, result_file_abs ]) var output: Array [] OS.execute(powershell.exe, args, output, true) var rf : FileAccess.open(result_file_abs, FileAccess.READ) if rf null: push_error(扫描结果文件不存在) return var text : rf.get_as_text() rf.close() var parsed JSON.parse_string(text) if typeof(parsed) ! TYPE_ARRAY: return clean_list.clear() for item in parsed: var path_text: String item.get(path, ) var size_value: int int(item.get(size, 0)) clean_list.add_item( %s | %s | %s % [item.get(name, ), path_text, format_size(size_value)] )这样设计有一个好处扫描过程中即使有文件夹无法打开PowerShell 的SilentlyContinue也会保证脚本继续运行不会因为个别权限错误导致整个扫描中断。4.6 执行清理清理逻辑与扫描类似只是在调用脚本时把Mode换成clean。为了避免用户误点清理前必须再次确认。确认框中要明确列出会影响的范围。func _on_clean_pressed() - void: var confirm : ConfirmationDialog.new() confirm.dialog_text 确认清理勾选的临时文件吗该操作不会删除临时目录本身。 confirm.ok_button_text 确认清理 add_child(confirm) confirm.popup_centered() confirm.confirmed.connect(_do_clean)真正执行前记录一下当前剩余空间清理后再读取一次剩余空间把差值显示到日志区域。func _do_clean() - void: var before: Dictionary get_drive_space(C) var free_before: int int(before.get(Free, 0)) # 构造目标、生成脚本并执行 PowerShell run_powershell_mode(clean) var after: Dictionary get_drive_space(C) var free_after: int int(after.get(Free, 0)) var released : free_after - free_before log_view.append_text([colorgreen]清理完成预计释放空间%s[/color]\n % format_size(released))4.7 字节大小格式化函数为了把扫描结果中的原始字节转换成更易读的文本可以添加一个通用格式化函数func format_size(size: int) - String: if size 1024: return %d B % size elif size 1024 * 1024: return %.2f KB % (size / 1024.0) elif size 1024 * 1024 * 1024: return %.2f MB % (size / (1024.0 * 1024.0)) else: return %.2f GB % (size / (1024.0 * 1024.0 * 1024.0))在 UI 中传入字节数即可显示成3.56 GB对普通用户更友好。5. 完整项目脚本示例下面给出一份精简但完整的clean_scope.gd脚本结构方便直接对照实现。你需要先建立主场景并添加两个节点%CleanList类型为ItemList%LogView类型为RichTextLabel脚本内容如下extends Control var user_dir : user://cleanscope var ps_script_path : user_dir /clean_scope.ps1 const POWER_SHELL_SCRIPT : { param( [string]$Mode, [string]$TargetsJson, [string]$OutFile ) $targets $TargetsJson | ConvertFrom-Json $results () foreach ($item in $targets) { $name $item.name $path $item.path if (-not (Test-Path -LiteralPath $path)) { $results [PSCustomObject]{ name $name path $path size 0 exists $false cleaned $false } continue } if ($Mode -eq scan) { $size (Get-ChildItem -LiteralPath $path -Recurse -Force -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum if ($null -eq $size) { $size 0 } $results [PSCustomObject]{ name $name path $path size [int64]$size exists $true cleaned $false } } if ($Mode -eq clean) { $cleaned $false if (Test-Path -LiteralPath $path) { $items Get-ChildItem -LiteralPath $path -Force -ErrorAction SilentlyContinue if ($items) { $items | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue $cleaned $true } } $results [PSCustomObject]{ name $name path $path size 0 exists $true cleaned $cleaned } } } $results | ConvertTo-Json -Compress | Set-Content -LiteralPath $OutFile -Encoding UTF8 } onready var clean_list: ItemList %CleanList onready var log_view: RichTextLabel %LogView func _ready() - void: DirAccess.make_dir_recursive_absolute(user_dir) ensure_ps_script() func ensure_ps_script() - void: var abs_path : ProjectSettings.globalize_path(ps_script_path) if FileAccess.file_exists(abs_path): return var f : FileAccess.open(abs_path, FileAccess.WRITE) if f: f.store_string(POWER_SHELL_SCRIPT) f.close() func build_clean_targets() - Array: var user_profile : OS.get_environment(USERPROFILE) if user_profile.is_empty(): user_profile C:/Users/Default var targets : [ { name: 系统临时目录, path: C:/Windows/Temp, checked: true }, { name: 当前用户临时目录, path: user_profile /AppData/Local/Temp, checked: true }, { name: Windows 更新缓存, path: C:/Windows/SoftwareDistribution/Download, checked: false }, { name: 系统 Web 缓存, path: user_profile /AppData/Local/Microsoft/Windows/INetCache, checked: false } ] return targets然后在按钮信号中调用扫描与清理方法。核心思路并不复杂本质上就是“用户确认勾选路径 → 调用 PowerShell 统计或删除 → 刷新界面”。6. 常见问题与排查思路在实际运行 CleanScope 时你可能会遇到一些问题下面整理成可快速对照的表格。问题现象常见原因解决思路查询磁盘信息失败PowerShell 执行策略或参数错误检查是否使用-NoProfile -ExecutionPolicy Bypass确认磁盘盘符是单个大写字母扫描结果始终为空目标目录不存在或 JSON 解析失败先手动确认C:\Windows\Temp等路径是否存在再用 Godot 的调试输出查看返回文本清理时提示“拒绝访问”当前进程没有管理员权限右键以管理员身份运行程序或修改项目导出设置部分文件无法删除文件被系统进程或软件锁定这类文件无法删除属正常现象应跳过并继续清理其他文件执行 PowerShell 后弹出黑窗口外部进程执行时的控制台窗口闪烁在导出配置中为 Windows 平台设置当前进程不显示控制台窗口或在调试阶段接受窗口闪现清理后剩余空间变化不明显其他软件会在清理后重新写入缓存清理前关闭浏览器、

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价