使用Vim修复Sprite Sheet Editor 0.5.6版生成的错误XML文件

Sprite Sheet Editor 0.5.6有一个很重大的Bug,在保存metadata的时候,我将frame的ow/oh属性保存成了与w/h属性相同的值。

根据Sprite Sheet Editor修剪每帧中的空白区域的原理说明可以知道,对于剪切过空白的帧来说,ow和oh是还原原始帧大小的关键。如果这两个值出错,会导致无法取得动画的正确尺寸。

好在这个是可以回溯的。可以通过比较某个Label包含的所有帧的尺寸,通过ox/oy和w/h计算出每帧的实际尺寸,最大的那一个,就是该Label中的所有帧的统一ow/oh属性值。

使用这个方法,得到的实际值,甚至比原来通过Sprite Sheet Editor进行手工设定的值更小,也就是更加节省内存。

选择什么工具处理?AS3有强悍的E4X,JAVA和BASH也不错。但我正好想学习下Vim脚本,就用它了!

处理思路:

  • metadata中的 <labels>标签中保存所有Label的帧索引;
  • 取得每个Label的帧索引,获取每帧的行号,保存;
  • 针对每个Label进行处理,通过Label中每帧的ox/oy/w/h计算出ow/oh;
  • 记录每个Label中最大的ow/oh;
  • 用最大的ow/oh替换该Label中所有帧的ow/oh;
  • 循环处理所有Label。

错误的XML文件

  1" 转换Sprite Sheet Editor 0.5.6版生成的错误XML文件
  2" zrong (zrongzrong@gmail.com) 2011-09-02
  3
  4" 获取当前目录下的所有xml文件
  5let xmls = split(glob("*.xml"), "\")
  6" 执行批量修复工作
  7for b:xml in xmls
  8    echom 'progress '.b:xml
  9    call RepareWH(b:xml)
 10endfor
 11
 12" 修复file参数提供的xml文件
 13function! RepareWH(file)
 14    exec 'sp '.a:file
 15    normal gg
 16    let labelStart = search('')+1
 17    " 找不到Label,处理所有帧
 18    if labelStart <= 1
 19        echom 'label not found'
 20        let framesLine = GetFrames()
 21        call WriteFrames(range(len(framesLine)), framesLine)
 22        update
 23        close
 24        return
 25    endif
 26    let labelEnd = search('<\/labels>')-1
 27    let labels = []
 28    " 将所有的label名称与对应的帧索引写入一个List
 29    " List中的每一项是一个字典,name是label名称,index帧索引的数组(字符串形式保存)
 30    for i in range(labelStart, labelEnd)
 31        let labelMatch = matchlist(getline(i), '<\(\w\+\)>\([0-9,]\+\)')
 32        call add(labels, {'name':labelMatch[1], 'index':split(labelMatch[2], ',')})
 33    endfor
 34    normal gg
 35    let framesLine = GetFrames()
 36    echom 'find '.len(framesLine).' frames'
 37    " 修复所有的label
 38    for label in labels
 39        echo 'progress label '.label.name
 40        call WriteFrames(label.index, framesLine)
 41    endfor
 42    update
 43    close
 44endfunction
 45
 46" 将所有的frame块转换成字典,并存入list
 47function! GetFrames()
 48    let framesLine = []
 49    while search('','',line('$'))
 50        let frameStart = line(".")+1
 51        let frameEnd = search('<\/frame>')-1
 52        call add(framesLine, ParseFrame(frameStart, frameEnd))
 53    endwhile
 54    return framesLine
 55endfunction
 56
 57" 将一帧的信息转换成字典,并返回
 58function! ParseFrame(start, end)
 59    let aFrame = {}
 60    let reg = '<\(\w\+\)>\([-0-9]\+\)'
 61    for lineIndex in range(a:start, a:end)
 62        let matchFrame = matchlist(getline(lineIndex), reg)
 63        let aFrame[matchFrame[1]] = str2nr(matchFrame[2])
 64        " 写入ow和oh的行号方便后面替换
 65        if matchFrame[1] =~ 'ow'
 66            let aFrame.owl = lineIndex
 67        endif
 68        if matchFrame[1] =~ 'oh'
 69            let aFrame.ohl = lineIndex
 70        endif
 71    endfor
 72    return aFrame
 73endfunction
 74
 75" 计算真实尺寸的最大值,将最大值写入这一组帧
 76function! WriteFrames(frameIndexs, framesLine)
 77    let owMax = 0
 78    let ohMax = 0
 79    for index in a:frameIndexs
 80        "echo '== progress frame '.index
 81        let l:frame = a:framesLine[str2nr(index)]
 82        " 如果修剪尺寸小于原始尺寸,则说明该帧没有问题
 83        if l:frame.w < l:frame.ow && l:frame.h < l:frame.oh
 84            "echom 'this frame '.index.' is not problem'
 85            continue
 86        endif
 87        " 真实的帧大小,是用修剪大小减去偏移值
 88        let trueW = l:frame.w - l:frame.ox
 89        let trueH = l:frame.h - l:frame.oy
 90        " 更新真实帧大小的最大值
 91        if trueW > owMax
 92            let owMax = trueW
 93        endif
 94        if trueH > ohMax
 95            let ohMax = trueH
 96        endif
 97    endfor
 98    if owMax > 0 && ohMax > 0
 99        " 写入帧的真实大小
100        for index in a:frameIndexs
101            let l:frame = a:framesLine[str2nr(index)]
102            let ows = substitute(getline(l:frame.owl), '\d\+', owMax, '')
103            let ohs = substitute(getline(l:frame.ohl), '\d\+', ohMax, '')
104            echo '== write frame '.index.' line:'.l:frame.owl.','.l:frame.ohl.' w:'.owMax.' h:'.ohMax
105            call setline(l:frame.owl, ows)
106            call setline(l:frame.ohl, ohs)
107        endfor
108    endif
109endfunction