自己用 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脚本 去导出这两种工具的 结果文件。


记录结束,赶紧睡了。