自己用 love2d 做游戏,开了好几个头,最后都因为种种原因不了了之。。其中最最重要的原因之一,就是没有现成的图。
网上找到的图,经常是大章的,整合过的。自己在用起来的时候,就得自己拿着 photoshop 去量 每个小土块的 uv坐标,量好了手写到 lua 文件里。
为了快速出原形的时候,这不失为一种便捷的做法,但是每次自己去量的时候也很头疼。这时候就希望能有个工具,能自动给我量好了,然而这并不可能。。最多也就是可视化丈量了。于是想,干脆自己开发这么个工具得了(话说N多年前自己就有过这样的想法)。但是这样搞成本太高了,自己也没这个心思。于是怀念起来刚刚入职 gl 时,用的 AuroraGT.里面的 Sprite 编辑器其实就是一个可视化编辑的工具,非常符合我的需求。
3,4年前上班的时候,自己搞过用 c++解析 AuroraGT 导出的 .sprite 文件,并且用 dx 渲染出来的小东西。那个时候我的做法,竟然是直接在主程序里,读取并解析 .sprite 文件。。而且写得效率并不高,分析一个大点的 .sprite 文件速度慢的要死。现在用 love2d, 打算直接把 .sprite 文件直接导成 .lua 代码。这个思路的主要来源有两个:1年前看cocosstudio ,记得有个功能是能直接根据 排版的 menu 布局,导出成 lua 代码;现在上班的公司,也是直接把 配置文件 csv 搞成 lua 代码的。这样 在 love2d 的游戏主程序里用起来,就省略了解析这一步,用起来 效率肯定会高出不少。
AuroraGT 这个软件,能够把图片,根据 uv 坐标,拆成许许多多的小块,称为 module。 一般我想象中的拆图工具,功能到此也就结束了;然而 AuroraGT 牛的地方在于,还可以自行拼装 这些 module ,组成 几个 module 合成的一个图块,成为 frame;到这里当然还没结束,还能把这些 frame 按照顺序,组成 animation
也就是说,AuroraGT 不是一个简单的拆图片软件,还能做帧动画编辑器,图片如果拆的够细致,图片的利用率就非常高,能在很省图片的情况下,完整保存帧动画数据。当然你也可以把 module 和 frame 视为 1对1 的,简单的把这个东西当成一个普通的帧动画编辑器也可以。
为了简单,我做的这个导出,只支持 一个.sprite 文件,对应一张图片。 我认为这样足够了, 对应多张实在有点乱。
下面3段分别是 AuroraGT 导出的 .sprite 文件, python 导出代码, lua 导出结果。
为了速度实现功能,代码没往美观的写,留在这里做个备忘
AuroraGT .sprite 文件
// E:\love2d\gamerepo\project\casualgame\assets\TestAnim.sprite
// saved by AuroraGT v0.7.1 (SpriteEditor v0.8.1)
/*SPRITE*/ {
VERSION 0001
// Images: 1
// Modules: 7
// Frames: 6
// Anims: 2
// Images...
// <Image> := IMAGE [id] "file" [TRANSP transp_color]
IMAGE 0x0000 "E:\love2d\gamerepo\project\casualgame\assets\Anime\img_phenix.png" TRANSP 0xFFFFFFFF // 0 size: 1024 x 1024 palettes: 1
// Modules...
// <Modules> := MODULES { <MD1> <MD2> ... }
// <MDi> := MD id Type [params] ["desc"]
// Type := MD_IMAGE | MD_RECT | ...
// [params] := if (Type == MD_IMAGE) -> image x y width height
// if (Type == MD_RECT) -> color width height
// if (Type == MD_FILL_RECT) -> color width height
// if (Type == MD_ARC) -> color width height
// if (Type == MD_FILL_ARC) -> color width height
MODULES
{
MD 0x1000 MD_IMAGE 0 0 0 128 198
MD 0x1001 MD_IMAGE 0 1 198 137 190
MD 0x1002 MD_IMAGE 0 127 0 172 185
MD 0x1003 MD_IMAGE 0 298 0 96 162
MD 0x1004 MD_IMAGE 0 254 186 153 165
MD 0x1005 MD_IMAGE 0 408 163 166 156
MD 0x1006 MD_IMAGE 0 0 0 16 16
}
// Frames...
// <Frame> := FRAME ["desc"] { id <RC1> [<RC2> ...] <FM1> [<FM2> ...] }
// <RCi> := RC x1 y1 x2 y2
// <FMi> := FM module_or_frame_id ox oy [FLAGS hex_flags] [+Flags]
// Flags := HYPER_FM | FLIP_X | FLIP_Y | ROT_90
FRAME "" // Index = 0, FModules = 3
{
0x2000
FM 0x1000 -3 -164
FM 0x1002 -172 -57
FM 0x1001 0 0
}
FRAME "" // Index = 1, FModules = 1
{
0x2001
FM 0x1001 0 0
}
FRAME "" // Index = 2, FModules = 1
{
0x2002
FM 0x1002 0 0
}
FRAME "" // Index = 3, FModules = 1
{
0x2003
FM 0x1003 0 0
}
FRAME "" // Index = 4, FModules = 1
{
0x2004
FM 0x1004 0 0
}
FRAME "" // Index = 5, FModules = 1
{
0x2005
FM 0x1005 0 0
}
// Animations...
// <Anim> := ANIM ["desc"] { id [<AF1> <AF2> ...] }
// <AFi> := AF frame_id time ox oy [FLAGS hex_flags] [+Flags]
// Flags := FLIP_X | FLIP_Y | ROT_90
ANIM "" // Index = 0, AFrames = 6
{
0x3000
AF 0x2000 1 -70 -98
AF 0x2001 8 -70 -89
AF 0x2002 1 -76 -86
AF 0x2003 1 -54 -73
AF 0x2004 1 -72 -73
AF 0x2005 1 -78 -64
}
ANIM "" // Index = 1, AFrames = 3
{
0x3001
AF 0x2000 1 0 0
AF 0x2001 1 0 0
AF 0x2002 1 0 0
}
// Tilesets...
SPRITE_END
} // SPRITE
python 脚本:
imageFile = None
allModules = {}
allFrames = {}
allAnimates = {}
LINE_FLAG_NONE = 0
LINE_FLAG_COMMENT = 1
LINE_FLAG_MODULE_START = 2
LINE_FLAG_FRAME_START = 3
LINE_FLAG_ANIM_START = 4
LINE_FLAG_IMAGE = 5
LINE_FLAG_L_BRACKETS = 6
LINE_FLAG_R_BRACKETS = 7
STATE_NONE = 0
STATE_MODULE = 1
STATE_FRAME = 2
STATE_ANIM = 3
curState = STATE_NONE
curKey = None
def dealImage(splitArray):
global imageFile
imageFile = splitArray[2]
strLen = len(imageFile)
imageFile = imageFile[1:strLen - 1]
def dealModule(splitArray):
global allModules
if len(splitArray) == 1:
return
moduleTag = splitArray[1]
allModules[moduleTag] = {}
allModules[moduleTag]['x'] = int(splitArray[4])
allModules[moduleTag]['y'] = int(splitArray[5])
allModules[moduleTag]['w'] = int(splitArray[6])
allModules[moduleTag]['h'] = int(splitArray[7])
def dealFrame(splitArray):
global allFrames
global curKey
if splitArray[0] == 'FRAME':
return
if len(splitArray) == 1:
curKey = splitArray[0]
allFrames[curKey] = []
return
if splitArray[0] == 'FM':
frameModule = {}
frameModule['moduleTag'] = splitArray[1]
frameModule['xOffset'] = int(splitArray[2])
frameModule['yOffset'] = int(splitArray[3])
allFrames[curKey].append(frameModule)
def dealAnim(splitArray):
global allAnimates
global curKey
if splitArray[0] == 'ANIM':
return
if len(splitArray) == 1:
curKey = splitArray[0]
allAnimates[curKey] = []
return
if splitArray[0] == 'AF':
animFrame = {}
animFrame['frameTag'] = splitArray[1]
animFrame['duration'] = splitArray[2]
animFrame['xOffset'] = splitArray[3]
animFrame['yOffset'] = splitArray[4]
allAnimates[curKey].append(animFrame)
def dealLine(line):
global curState
if line.find('Modules...') >= 0:
curState = STATE_MODULE
curKey = None
return LINE_FLAG_MODULE_START
if line.find('Frames...') >= 0:
curState = STATE_FRAME
curKey = None
return LINE_FLAG_FRAME_START
if line.find('Animations...') >= 0:
curState = STATE_ANIM
curKey = None
return LINE_FLAG_ANIM_START
if len(line) == 0:
return LINE_FLAG_NONE
if line.find('') != -1:
curState = STATE_NONE
return LINE_FLAG_COMMENT
if line.find('//') == 0:
return LINE_FLAG_COMMENT
if line.find('/*') != -1:
return LINE_FLAG_COMMENT
splitBySpaceArray = line.split(' ')
if len(splitBySpaceArray) <= 0:
return LINE_FLAG_NONE
if splitBySpaceArray[0] == 'IMAGE':
dealImage(splitBySpaceArray)
return LINE_FLAG_IMAGE
if splitBySpaceArray[0] == '{':
curKey = None
return LINE_FLAG_L_BRACKETS
if splitBySpaceArray[0] == '}':
curKey = None
return LINE_FLAG_R_BRACKETS
if curState == STATE_MODULE:
splitBySpaceArray = splitBySpaceArray[0].split('\t')
dealModule(splitBySpaceArray)
if curState == STATE_FRAME:
splitBySpaceArray = splitBySpaceArray[0].split('\t')
dealFrame(splitBySpaceArray)
if curState == STATE_ANIM:
splitBySpaceArray = splitBySpaceArray[0].split('\t')
dealAnim(splitBySpaceArray)
return LINE_FLAG_NONE
def fileToMem(filename):
fileObject = open(filename,'r')
allLines = fileObject.readlines()
curLine = 0
for line in allLines:
curLine = curLine + 1
line = line.strip()
dealLine(line)
def dumpModules():
global allModules
print('all modules:')
print(allModules)
def dumpFrames():
global allFrames
print('all frames:')
print(allFrames)
def dumpAnims():
global allAnimates
print('all animates:')
print(allAnimates)
def dump():
dumpModules()
dumpFrames()
dumpAnims()
def exportAsJson(filename):
'''
'''
def generateLua(filename,wholeName):
strLua = ''
strLua = strLua + filename + ' = '
strLua = strLua + '{}\n'
global imageFile
imageFilePath = imageFile.replace('\\','\\\\')
strLua = strLua + filename + '.image' + '=\'' + imageFilePath + '\'\n'
strLua = strLua + filename + '.modules = {}\n'
strLua = strLua + filename + '.frames = {}\n'
strLua = strLua + filename + '.anims = {}\n'
# construct modules
strLua = strLua + '-- modules\n'
modulePrefix = filename + '.modules'
for (key,moduleItem) in allModules.items():
strLua = strLua + modulePrefix + '[\'' + str(key) + '\']' + '={}'
strLua = strLua + '\n'
mItemPrefix = modulePrefix + '[\'' + str(key) + '\']'
for fieldKey,fieldItem in moduleItem.items():
strLua = strLua + mItemPrefix + '.' + fieldKey + '=' + str(fieldItem) + '\n'
# construct frames
strLua = strLua + '-- frames\n'
framePrefix = filename + '.frames'
for (key,frameItem) in allFrames.items():
strLua = strLua + framePrefix + '[\'' + str(key) + '\']' + '={}'
strLua = strLua + '\n'
fItemPrefix = framePrefix + '[\'' + str(key) + '\']'
for i in range(0,len(frameItem)):
strLua = strLua + fItemPrefix + '[' + str(i + 1) +'] = {}'
strLua = strLua + '\n'
frameModulePrefix = fItemPrefix + '[' + str(i + 1) +']'
for (fmPropertyKey,fmPropertyVal) in frameItem[i].items():
strLua = strLua + frameModulePrefix + '.' + fmPropertyKey
if fmPropertyKey == 'moduleTag':
strLua = strLua + '=\'' + str(fmPropertyVal) + '\''
else:
strLua = strLua + '=' + str(fmPropertyVal)
strLua = strLua + '\n'
# construct anims
strLua = strLua + '-- anims\n'
animPrefix = filename + '.anims'
for (key,animItem) in allAnimates.items():
strLua = strLua + animPrefix + '[\'' + str(key) + '\']' + '={}'
strLua = strLua + '\n'
aItemPrefix = animPrefix + '[\'' + str(key) + '\']'
for i in range(0,len(animItem)):
strLua = strLua + aItemPrefix + '[' + str(i + 1) +'] = {}'
strLua = strLua + '\n'
animFramePrefix = aItemPrefix + '[' + str(i + 1) + ']'
for (afPropertyKey,afPropertyVal) in animItem[i].items():
strLua = strLua + animFramePrefix + '.' + afPropertyKey
if afPropertyKey == 'frameTag':
strLua = strLua + '=\'' + str(afPropertyVal) + '\''
else:
strLua = strLua + '=' + str(afPropertyVal)
strLua = strLua + '\n'
return strLua
def getFileName(filePath):
filePath = filePath.split('/')
filePath = filePath[len(filePath) - 1]
filePath = filePath.split('.')
return filePath[0]
if __name__ == '__main__':
filePath = 'project/casualgame/assets/TestAnim.sprite'
fileToMem(filePath)
#dump()
fileName = getFileName(filePath)
exportAsJson(fileName + '.json')
strLua = generateLua(fileName,fileName + '.lua')
luaFile = open('export/' + fileName + '.lua','w')
luaFile.write(strLua)
luaFile.close()
导出的结果 lua 文件:
TestAnim = {}
TestAnim.image='E:\\love2d\\gamerepo\\project\\casualgame\\assets\\Anime\\img_phenix.png'
TestAnim.modules = {}
TestAnim.frames = {}
TestAnim.anims = {}
-- modules
TestAnim.modules['0x1002']={}
TestAnim.modules['0x1002'].w=172
TestAnim.modules['0x1002'].h=185
TestAnim.modules['0x1002'].y=0
TestAnim.modules['0x1002'].x=127
TestAnim.modules['0x1005']={}
TestAnim.modules['0x1005'].w=166
TestAnim.modules['0x1005'].h=156
TestAnim.modules['0x1005'].y=163
TestAnim.modules['0x1005'].x=408
TestAnim.modules['0x1000']={}
TestAnim.modules['0x1000'].w=128
TestAnim.modules['0x1000'].h=198
TestAnim.modules['0x1000'].y=0
TestAnim.modules['0x1000'].x=0
TestAnim.modules['0x1004']={}
TestAnim.modules['0x1004'].w=153
TestAnim.modules['0x1004'].h=165
TestAnim.modules['0x1004'].y=186
TestAnim.modules['0x1004'].x=254
TestAnim.modules['0x1003']={}
TestAnim.modules['0x1003'].w=96
TestAnim.modules['0x1003'].h=162
TestAnim.modules['0x1003'].y=0
TestAnim.modules['0x1003'].x=298
TestAnim.modules['0x1006']={}
TestAnim.modules['0x1006'].w=16
TestAnim.modules['0x1006'].h=16
TestAnim.modules['0x1006'].y=0
TestAnim.modules['0x1006'].x=0
TestAnim.modules['0x1001']={}
TestAnim.modules['0x1001'].w=137
TestAnim.modules['0x1001'].h=190
TestAnim.modules['0x1001'].y=198
TestAnim.modules['0x1001'].x=1
-- frames
TestAnim.frames['0x2000']={}
TestAnim.frames['0x2000'][1] = {}
TestAnim.frames['0x2000'][1].moduleTag='0x1000'
TestAnim.frames['0x2000'][1].yOffset=-164
TestAnim.frames['0x2000'][1].xOffset=-3
TestAnim.frames['0x2000'][2] = {}
TestAnim.frames['0x2000'][2].moduleTag='0x1002'
TestAnim.frames['0x2000'][2].yOffset=-57
TestAnim.frames['0x2000'][2].xOffset=-172
TestAnim.frames['0x2000'][3] = {}
TestAnim.frames['0x2000'][3].moduleTag='0x1001'
TestAnim.frames['0x2000'][3].yOffset=0
TestAnim.frames['0x2000'][3].xOffset=0
TestAnim.frames['0x2001']={}
TestAnim.frames['0x2001'][1] = {}
TestAnim.frames['0x2001'][1].moduleTag='0x1001'
TestAnim.frames['0x2001'][1].yOffset=0
TestAnim.frames['0x2001'][1].xOffset=0
TestAnim.frames['0x2002']={}
TestAnim.frames['0x2002'][1] = {}
TestAnim.frames['0x2002'][1].moduleTag='0x1002'
TestAnim.frames['0x2002'][1].yOffset=0
TestAnim.frames['0x2002'][1].xOffset=0
TestAnim.frames['0x2003']={}
TestAnim.frames['0x2003'][1] = {}
TestAnim.frames['0x2003'][1].moduleTag='0x1003'
TestAnim.frames['0x2003'][1].yOffset=0
TestAnim.frames['0x2003'][1].xOffset=0
TestAnim.frames['0x2004']={}
TestAnim.frames['0x2004'][1] = {}
TestAnim.frames['0x2004'][1].moduleTag='0x1004'
TestAnim.frames['0x2004'][1].yOffset=0
TestAnim.frames['0x2004'][1].xOffset=0
TestAnim.frames['0x2005']={}
TestAnim.frames['0x2005'][1] = {}
TestAnim.frames['0x2005'][1].moduleTag='0x1005'
TestAnim.frames['0x2005'][1].yOffset=0
TestAnim.frames['0x2005'][1].xOffset=0
-- anims
TestAnim.anims['0x3001']={}
TestAnim.anims['0x3001'][1] = {}
TestAnim.anims['0x3001'][1].frameTag='0x2000'
TestAnim.anims['0x3001'][1].yOffset=0
TestAnim.anims['0x3001'][1].duration=1
TestAnim.anims['0x3001'][1].xOffset=0
TestAnim.anims['0x3001'][2] = {}
TestAnim.anims['0x3001'][2].frameTag='0x2001'
TestAnim.anims['0x3001'][2].yOffset=0
TestAnim.anims['0x3001'][2].duration=1
TestAnim.anims['0x3001'][2].xOffset=0
TestAnim.anims['0x3001'][3] = {}
TestAnim.anims['0x3001'][3].frameTag='0x2002'
TestAnim.anims['0x3001'][3].yOffset=0
TestAnim.anims['0x3001'][3].duration=1
TestAnim.anims['0x3001'][3].xOffset=0
TestAnim.anims['0x3000']={}
TestAnim.anims['0x3000'][1] = {}
TestAnim.anims['0x3000'][1].frameTag='0x2000'
TestAnim.anims['0x3000'][1].yOffset=-98
TestAnim.anims['0x3000'][1].duration=1
TestAnim.anims['0x3000'][1].xOffset=-70
TestAnim.anims['0x3000'][2] = {}
TestAnim.anims['0x3000'][2].frameTag='0x2001'
TestAnim.anims['0x3000'][2].yOffset=-89
TestAnim.anims['0x3000'][2].duration=8
TestAnim.anims['0x3000'][2].xOffset=-70
TestAnim.anims['0x3000'][3] = {}
TestAnim.anims['0x3000'][3].frameTag='0x2002'
TestAnim.anims['0x3000'][3].yOffset=-86
TestAnim.anims['0x3000'][3].duration=1
TestAnim.anims['0x3000'][3].xOffset=-76
TestAnim.anims['0x3000'][4] = {}
TestAnim.anims['0x3000'][4].frameTag='0x2003'
TestAnim.anims['0x3000'][4].yOffset=-73
TestAnim.anims['0x3000'][4].duration=1
TestAnim.anims['0x3000'][4].xOffset=-54
TestAnim.anims['0x3000'][5] = {}
TestAnim.anims['0x3000'][5].frameTag='0x2004'
TestAnim.anims['0x3000'][5].yOffset=-73
TestAnim.anims['0x3000'][5].duration=1
TestAnim.anims['0x3000'][5].xOffset=-72
TestAnim.anims['0x3000'][6] = {}
TestAnim.anims['0x3000'][6].frameTag='0x2005'
TestAnim.anims['0x3000'][6].yOffset=-64
TestAnim.anims['0x3000'][6].duration=1
TestAnim.anims['0x3000'][6].xOffset=-78
AuroraGT 除了 sprite 编辑器外,还有个 Game编辑器,和 Map 编辑器的功能。
这两个编辑器我还不太明白具体的用法。将来了解一下,如果好用的话,我会再搞相应的 python脚本 去导出这两种工具的 结果文件。
记录结束,赶紧睡了。