fixed # 修补缺失资源,修正已知BUG

This commit is contained in:
2026-09-30 00:33:30 +08:00
parent 98a55dd3d6
commit 3fdaf6d218
17 changed files with 415 additions and 45 deletions
+143
View File
@@ -0,0 +1,143 @@
<#
Batch-crop Godot AtlasTexture .tres files into PNGs from their referenced atlas images.
Requires a FULL RECOVERY output where atlas sources are PNG/WebP. This script does not
unpack PCK files or decode .ctex; it only reads .tres/.tpsheet metadata and crops images.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$ProjectRoot,
[Parameter(Mandatory = $true)]
[string]$OutputDirectory,
[string]$SpriteDirectory = 'images/atlases/compressed.sprites/card_template',
[switch]$Overwrite
)
$ErrorActionPreference = 'Stop'
Add-Type -AssemblyName System.Drawing
$root = [System.IO.Path]::GetFullPath($ProjectRoot).TrimEnd('\')
$outRoot = [System.IO.Path]::GetFullPath($OutputDirectory).TrimEnd('\')
$relativeSpriteDir = $SpriteDirectory.TrimStart('\', '/').Replace('/', '\')
$sourceDir = [System.IO.Path]::GetFullPath((Join-Path $root $relativeSpriteDir))
if (-not (Test-Path -LiteralPath $root -PathType Container)) {
throw "ProjectRoot not found: $root"
}
if (-not (Test-Path -LiteralPath $sourceDir -PathType Container)) {
throw "SpriteDirectory not found: $sourceDir"
}
if ($outRoot.Equals($root, [System.StringComparison]::OrdinalIgnoreCase) -or
$outRoot.StartsWith($root + '\', [System.StringComparison]::OrdinalIgnoreCase)) {
throw 'OutputDirectory must be outside ProjectRoot so recovered source files stay untouched.'
}
$invariant = [System.Globalization.CultureInfo]::InvariantCulture
$files = @(Get-ChildItem -LiteralPath $sourceDir -Filter '*.tres' -File -Recurse)
$written = 0
$skipped = 0
$errors = [System.Collections.Generic.List[string]]::new()
foreach ($tres in $files) {
$sourceImage = $null
$crop = $null
try {
$text = [System.IO.File]::ReadAllText($tres.FullName)
if ($text -notmatch 'type="AtlasTexture"') {
$skipped++
continue
}
$atlasIdMatch = [regex]::Match($text, '(?m)^\s*atlas\s*=\s*ExtResource\("([^"]+)"\)')
$regionMatch = [regex]::Match($text, '(?m)^\s*region\s*=\s*Rect2i?\(\s*(-?[0-9.]+)\s*,\s*(-?[0-9.]+)\s*,\s*(-?[0-9.]+)\s*,\s*(-?[0-9.]+)\s*\)')
if (-not $atlasIdMatch.Success -or -not $regionMatch.Success) {
$skipped++
$errors.Add("Missing atlas/region fields: $($tres.FullName)")
continue
}
$atlasPath = $null
foreach ($blockMatch in [regex]::Matches($text, '(?m)^\s*\[ext_resource[^\]]*\]')) {
$block = $blockMatch.Value
$idMatch = [regex]::Match($block, '\bid="([^"]+)"')
$pathMatch = [regex]::Match($block, '\bpath="([^"]+)"')
if ($idMatch.Success -and $pathMatch.Success -and
$idMatch.Groups[1].Value -eq $atlasIdMatch.Groups[1].Value) {
$atlasPath = $pathMatch.Groups[1].Value
break
}
}
if (-not $atlasPath) {
$skipped++
$errors.Add("Could not resolve atlas ExtResource: $($tres.FullName)")
continue
}
if ($atlasPath.StartsWith('res://', [System.StringComparison]::OrdinalIgnoreCase)) {
$atlasRelative = $atlasPath.Substring(6).Replace('/', '\')
$atlasFile = Join-Path $root $atlasRelative
} else {
$atlasFile = Join-Path $tres.DirectoryName $atlasPath.Replace('/', '\')
}
if (-not (Test-Path -LiteralPath $atlasFile -PathType Leaf)) {
$skipped++
$errors.Add("Atlas image missing (Full Recovery should restore it as PNG): $atlasFile ; referenced by $($tres.FullName)")
continue
}
$values = @(
[double]::Parse($regionMatch.Groups[1].Value, $invariant),
[double]::Parse($regionMatch.Groups[2].Value, $invariant),
[double]::Parse($regionMatch.Groups[3].Value, $invariant),
[double]::Parse($regionMatch.Groups[4].Value, $invariant)
)
$rounded = @($values | ForEach-Object { [int][Math]::Round($_) })
for ($i = 0; $i -lt 4; $i++) {
if ([Math]::Abs($values[$i] - $rounded[$i]) -gt 0.001) {
throw "Non-integer pixel region in $($tres.FullName)"
}
}
$x = $rounded[0]; $y = $rounded[1]; $width = $rounded[2]; $height = $rounded[3]
if ($x -lt 0 -or $y -lt 0 -or $width -le 0 -or $height -le 0) {
throw "Invalid crop region Rect2($x, $y, $width, $height) in $($tres.FullName)"
}
$sourceImage = [System.Drawing.Bitmap]::FromFile($atlasFile)
if (($x + $width) -gt $sourceImage.Width -or ($y + $height) -gt $sourceImage.Height) {
throw "Crop exceeds atlas bounds ($($sourceImage.Width)x$($sourceImage.Height)): $($tres.FullName)"
}
$rect = [System.Drawing.Rectangle]::new($x, $y, $width, $height)
$crop = $sourceImage.Clone($rect, $sourceImage.PixelFormat)
$relativeTres = $tres.FullName.Substring($root.Length).TrimStart('\')
$relativeOutput = [System.IO.Path]::ChangeExtension($relativeTres, '.png')
$target = Join-Path $outRoot $relativeOutput
$targetDir = Split-Path -Parent $target
New-Item -ItemType Directory -Path $targetDir -Force | Out-Null
if ((Test-Path -LiteralPath $target) -and -not $Overwrite) {
$skipped++
continue
}
if (Test-Path -LiteralPath $target) {
Remove-Item -LiteralPath $target -Force
}
$crop.Save($target, [System.Drawing.Imaging.ImageFormat]::Png)
$written++
}
catch {
$skipped++
$errors.Add("$($tres.FullName): $($_.Exception.Message)")
}
finally {
if ($null -ne $crop) { $crop.Dispose() }
if ($null -ne $sourceImage) { $sourceImage.Dispose() }
}
}
Write-Output "TRES scanned: $($files.Count)"
Write-Output "PNG written: $written"
Write-Output "Skipped: $skipped"
foreach ($message in $errors) { Write-Warning $message }