PSD导出CocosStudio界面文件

使用photoshop的js脚本支持,生成cocos studio可以打开的.csd格式的布局文件,主要是根据csd中json格式进行生成,保证文字和按钮层级等,生成中间格式的布局文件,便于程序根据此文件进行图片查找和控件位置定位,提高效率,节约时间。

工具用途:
将Photoshop内的图层信息导出为Cocos Studio布局文件

安装方法:
在Photoshop安装目录下的Presets\Scripts文件夹下,新建快捷方式,指向这个目录下的Export To Cocos Studio.js;
重新启动Photoshop即可

使用方法:
在Photoshop内,如果要对当前的 文件 导出,
点击 菜单栏中,点击 文件->脚本->Export To Cocos Studio 执行;
执行完成后,在目标文件夹内会生成:
template.csd <—Cocos Studio布局文件
Resource/ <—图层切图

注意事项:
非文字图层,图层名相同,会认作相同资源,只会导出一次
如果图层名以*开头,导出时将跳过
图层名字不要带中文 空格等非常规字符

// enable double clicking from the Macintosh Finder or the Windows Explorer
#target photoshop

// setup global variables
var sourcePsd;
var duppedPsd;
var destinationFolder;
var objectId = 0;
var debugFile;
var fDebug = false;

var layerMetaList = [];
var exportedCached = {};

var textLabelCounter = 0;
var imageViewCounter = 0;
var tagCounter = 100;

var mainPanelSize = {};

var transformFactor = 1;

// run the exporter
main();

generateCsd();


// main entry point
function main()
{
	// got a valid document?
	if( app.documents.length <= 0 )
	{
		if(app.playbackDisplayDialogs != DialogModes.NO)
		{
			alert("You must have a document open to export!");
		}
		// quit, returning 'cancel' makes the actions palette not record our script
		return 'cancel';
	}

	// ask for where the exported files should go
	destinationFolder = Folder(app.activeDocument.fullName.path).selectDlg("Choose the destination for export.");
	if(!destinationFolder)
	{
		return;
	}

	if(fDebug){
		debugFile = new File(destinationFolder + "/debug.txt");
		debugFile.open('w');
		debugFile.writeln('start');
	}else{
		debugFile = {
			writeln: function(){},
			close: function(){},
		};
	}

	// cache useful variables
	sourcePsdName = app.activeDocument.name; 
	var layerCount = app.documents[sourcePsdName].layers.length;

	var layerSetsCount = app.documents[sourcePsdName].layerSets.length;
	debugFile.writeln('layerCount', layerCount, ',' , layerSetsCount);
	if((layerCount <= 1)&&(layerSetsCount <= 0))
	{
		if(app.playbackDisplayDialogs != DialogModes.NO)
		{
			alert("You need a document with multiple layers to export!");
			// quit, returning 'cancel' makes the actions palette not record our script
			return 'cancel';
		}
	}

	debugFile.writeln('before duplicating psd');
	// duplicate document so we can extract everything we need
	duppedPsd = app.activeDocument.duplicate();
	duppedPsd.activeLayer = duppedPsd.layers[duppedPsd.layers.length-1];
	duppedPsd.crop([new UnitValue(0), new UnitValue(0), duppedPsd.width, duppedPsd.height]);
	
	mainPanelSize.width = duppedPsd.width.as('px');
	mainPanelSize.height = duppedPsd.height.as('px');
	debugFile.writeln('mainPanelSize:', mainPanelSize.width, mainPanelSize.height);
	
	// clean it up
	debugFile.writeln('about to hide all art layers');
	hideAllArtLayers(duppedPsd);
	
	transformFactor = getTransformFactor()
	
	debugFile.writeln('layerCount', app.documents[sourcePsdName].layers.length);
	debugFile.writeln('about to export layer sets');
	exportObjLayers(duppedPsd, 0);
	
	debugFile.writeln('closing the file');
	duppedPsd.close(SaveOptions.DONOTSAVECHANGES);
	debugFile.close();
}

function exportObjLayers(obj, level)
{
	for(var i = 0; i <obj.layers.length; i++){
		var layer = obj.layers[i];
		if(layer.typename == 'LayerSet'){
			layer.visible = true;
			exportObjLayers(layer, level + 1);
			layer.visible = false;
		}else{
			exportLayer(layer);
		}		
	}
}

function exportLayer(layer){
	if(layer.kind != LayerKind.TEXT && layer.kind != LayerKind.NORMAL){
		debugFile.writeln('ingore layer ', layer.kind, layer.name);
		return;
	}
	
	if(layer.name[0] == '*'){
		debugFile.writeln('layer name start with *, ignore ', layer.name);
		return;
	}
	
	debugFile.writeln('exportLayer ', layer.name);
	
	var meta = {};
	meta.name = layer.name;
	meta.kind = layer.kind;
	meta.rect = [layer.bounds[0].as('px'), layer.bounds[1].as('px'), layer.bounds[2].as('px'), layer.bounds[3].as('px')];
	if(((meta.rect[2] - meta.rect[0]) <= 0) || ((meta.rect[3] - meta.rect[1]) <= 0)){
		debugFile.writeln('empty layer');
		return;
	}
	
	debugFile.writeln('layer meta name:', meta.name, ', kind:', meta.kind, ',rect:', meta.rect);
	if(layer.kind == LayerKind.TEXT){
		layer.visible = true;
		var RGBColor = layer.textItem.color.nearestWebColor;
		meta.textItem = {
			size: Math.round(layer.textItem.size.as('pt') * transformFactor),
			colorRed: RGBColor.red,
			colorGreen: RGBColor.green,
			colorBlue: RGBColor.blue,
			font: layer.textItem.font,
			contents: layer.textItem.contents.replace(/\s+$/, ''),
		};
		debugFile.writeln("    ", "TEXT. size:", meta.textItem.size, 
				', colorRed:', meta.textItem.colorRed, 
				', colorGreen:', meta.textItem.colorGreen,
				', colorBlue:', meta.textItem.colorBlue,
				', font:', meta.textItem.font,
				', contents:', meta.textItem.contents
		);
		layer.visible = false;
	}else if(layer.kind == LayerKind.NORMAL){
		if(exportedCached[meta.name]){
			debugFile.writeln('    ', 'use cache ', meta.name);
			meta.imgItem = exportedCached[meta.name].imgItem;
		}else{
			layer.visible = true;
			var imgItem = saveScene(duppedPsd.duplicate(), meta.name);
			meta.imgItem = imgItem;
			layer.visible = false;
			debugFile.writeln("    ", "IMG. ext:", meta.imgItem.ext,
				", fileName:", meta.imgItem.fileName,
				", subFolderName:", meta.imgItem.subFolderName,
				", width:", meta.imgItem.width,
				", height:", meta.imgItem.height
			);
			exportedCached[meta.name] = meta;
		}
	}
	
	layerMetaList.push(meta);
}

function generateCsd(){
	var header = generateCsdHeader(mainPanelSize);
	var footer = generateCsdFooter();
	
	var content = '';
	for(var i = layerMetaList.length - 1; i >= 0; i--){
		var meta = layerMetaList[i];
		if(meta.kind == LayerKind.TEXT){
			content += generateTextLable(mainPanelSize, meta);
		}else if(meta.kind == LayerKind.NORMAL){
			content += generateImageView(mainPanelSize, meta);
		}
	}
	
	var csdFile = new File(destinationFolder + "/template.csd");
	csdFile.encoding = 'UTF-8';
	
	csdFile.open('w');
	csdFile.write(header);
	csdFile.write(content);
	csdFile.write(footer);
	csdFile.close();
	
}

function generateImageView(mainPanelSize, meta){
	var position = toCocosPosition(mainPanelSize, meta.rect, meta.imgItem.width, meta.imgItem.height);
	debugFile.writeln('new textLabel:', meta.name, ' ', position);
	
	var tag = ++imageViewCounter;
	return '<AbstractNodeData Name="Image_' + meta.name + '" ActionTag="' + tag + '" Tag="' + (++tagCounter) + '" IconVisible="False" LeftMargin="' + position.LeftMargin + '" RightMargin="' + position.RightMargin + '" TopMargin="' + position.TopMargin + '" BottomMargin="' + position.BottomMargin + '"  ctype="ImageViewObjectData">\n' + 
'	<Size X="' + position.X + '" Y="' + position.Y + '" />\n' + 
'	<AnchorPoint ScaleX="0.5000" ScaleY="0.5000" />\n' + 
'	<Position X="' + position.PositionX + '" Y="' + position.PositionY + '" />\n' + 
'	<Scale ScaleX="1.0000" ScaleY="1.0000" />\n' + 
'	<CColor A="255" R="255" G="255" B="255" />\n' + 
'	<PrePosition X="0.5000" Y="0.5000" />\n' + 
'	<PreSize X="0.5000" Y="0.4583" />\n' + 
'	<FileData Type="Normal" Path="' + meta.imgItem.subFolderName + '/' + meta.imgItem.fileName + '.' + meta.imgItem.ext + '" Plist="" />\n' + 
'</AbstractNodeData>\n';
}

function generateTextLable(mainPanelSize, meta){
	var position = toCocosPosition(mainPanelSize, meta.rect);
	debugFile.writeln('new textLabel:', meta.textItem.contents, ' ', position);
	
	var tag = ++textLabelCounter;
	return '<AbstractNodeData Name="Label_' + tag + '" ActionTag="' + tag + '" Tag="' + (++tagCounter) + '" IconVisible="False" LeftMargin="' + position.LeftMargin + '" RightMargin="' + position.RightMargin + '" TopMargin="' + position.TopMargin + '" BottomMargin="' + position.BottomMargin + '" FontSize="' + meta.textItem.size + '" LabelText="' + meta.textItem.contents + '" ShadowOffsetX="2.0000" ShadowOffsetY="-2.0000" ctype="TextObjectData">\n' +
'		<Size X="' + position.X + '" Y="' + position.Y + '" />\n' +
'		<AnchorPoint ScaleX="0.5000" ScaleY="0.5000" />\n' +
'		<Position X="' + position.PositionX + '" Y="' + position.PositionY + '" />\n' +
'		<FontResource Type="Normal" Path="font/DFGB_Y7_0.ttf" Plist="" />\n' + 
'		<Scale ScaleX="1.0000" ScaleY="1.0000" />\n' +
'		<CColor A="255" R="' + meta.textItem.colorRed + '" G="' + meta.textItem.colorGreen + '" B="' + meta.textItem.colorBlue + '" />\n' +
'		<PrePosition X="0.4842" Y="0.3054" />\n' +
'		<PreSize X="0.0526" Y="0.0357" />\n' +
'		<OutlineColor A="255" R="255" G="0" B="0" />\n' +
'		<ShadowColor A="255" R="110" G="110" B="110" />\n' +
'	</AbstractNodeData>\n';
}

//rect: [1196,220,1563,280]
function toCocosPosition(mainPanelSize, rect, width, height){
	if(!width) width = rect[2] - rect[0];
	if(!height) height = rect[3] - rect[1];
	
	var rtn = {};
	rtn.X = width;
	rtn.Y = height;
	rtn.LeftMargin = rect[0];
	rtn.RightMargin = mainPanelSize.width - rect[2];
	rtn.TopMargin = rect[1];
	rtn.BottomMargin = mainPanelSize.height - rect[3];
	
	rtn.PositionX = rtn.LeftMargin + width / 2;
	rtn.PositionY = rtn.BottomMargin + height / 2;
	
	return rtn;
}

function generateCsdHeader(opt){
	return "<GameFile>\n" + 
'  <PropertyGroup Name="template" Type="Layer" ID="todo" Version="3.10.0.0" />\n' +
'  <Content ctype="GameProjectContent">\n' + 
'    <Content>\n' +
'      <Animation Duration="0" Speed="1.0000" />\n' +
'      <ObjectData Name="UiEntitytodo" Tag="1" ctype="GameLayerObjectData">\n' +
'        <Size X="' + opt.width + '" Y="' + opt.height + '" />\n' +
'        <Children>\n';
}


function generateCsdFooter(){
return '		</Children>\n'+
'      </ObjectData>\n'+
'    </Content>\n'+
'  </Content>\n'+
'</GameFile>'
}

function saveScene(psd, fileName)
{
	var ext = 'png';

	var levelText = "  ";
	debugFile.writeln(levelText+'use png save options:', fileName);
	
	var subFolderName = 'Resource';
	var folderPath = destinationFolder + '/' + subFolderName;
	var subFolder = Folder(folderPath);
	if(!subFolder.exists) subFolder.create();
	
	// save the image
	var file = new File(destinationFolder + "/" + subFolderName  + "/" + fileName + "." + ext);

	debugFile.writeln(levelText+'mergeVisibleLayers');
	// we should now have a single art layer if all went well
	//psd.flatten();
	//psd.mergeVisibleLayers();

	psd.trim(TrimType.TRANSPARENT);

	var width = new UnitValue(psd.width);
	var height = new UnitValue(psd.height);

	debugFile.writeln(levelText+"about to save the file");
	var sfwOptions = new ExportOptionsSaveForWeb();
	sfwOptions.includeProfile = false;
	sfwOptions.optimized = true;

	sfwOptions.format = SaveDocumentType.PNG;
	sfwOptions.PNG8 = false;
	psd.exportDocument(file, ExportType.SAVEFORWEB, sfwOptions);
	debugFile.writeln(levelText+'done saving scene');

	psd.close(SaveOptions.DONOTSAVECHANGES);
	
	return {ext: ext, fileName: fileName, subFolderName: subFolderName, width: width.as('px'), height: height.as('px')};
}		

function hideAllArtLayersDepr(obj)
{
	for(var i = 0; i < obj.artLayers.length; i++)
	{
		debugFile.writeln("hide artLayer " + obj.artLayers[i].name);
		obj.artLayers[i].allLocked = false;
		obj.artLayers[i].visible = false;
	}

	for( var i = 0; i < obj.layerSets.length; i++)
	{
		var type = obj.layerSets[i].name.split(':')[0];
		// If first character of name is '*', do not export.
		if (obj.layerSets[i].name.charAt(0) == '*') {
			debugFile.writeln("hide layerSet " + obj.layerSets[i].name);
			obj.layerSets[i].visible = false;
		}
		else {
			hideAllArtLayers(obj.layerSets[i]);
		}
	}
}

// Hide layers functions

///
// main - set the visibility of all layers to off (invisible)
///
function hideAllArtLayers(doc) {
	// declare local variables
	var layer = doc.activeLayer;
	// delete Background if it exists
	var background = doc.layers[doc.layers.length -1];
	if (background.isBackgroundLayer) {
		background.remove();
	}
	debugFile.writeln('background deleted');

	// select and hide all layers
	selectAllLayers();
	hideLayers();
	debugFile.writeln('layers hidden');
	
}

///
// selectAllLayers - select all layers (Select > All Layers)
///
function selectAllLayers() {
	var ref = new ActionReference();
	ref.putEnumerated(cTID('Lyr '), cTID('Ordn'), cTID('Trgt'));
	var desc = new ActionDescriptor();
	desc.putReference(cTID('null'), ref);
	executeAction(sTID('selectAllLayers'), desc, DialogModes.NO);
}

///
// hideLayers - hide all selected layers (Layer > Hide Layers)
///
function hideLayers() {
	var ref = new ActionReference();
	ref.putEnumerated(cTID('Lyr '), cTID('Ordn'), cTID('Trgt'));
	var list = new ActionList();
	list.putReference(ref);
	var desc = new ActionDescriptor();
	desc.putList(cTID('null'), list);
	executeAction(cTID('Hd  '), desc, DialogModes.NO);
}

function cTID(s) {return app.charIDToTypeID(s);}
function sTID(s) {return app.stringIDToTypeID(s);}



function getTransformFactor(){
	var ref = new ActionReference();  
	ref.putEnumerated( charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );   
	var desc = executeActionGet(ref).getObjectValue(stringIDToTypeID('textKey'));  
	if (desc.hasKey(stringIDToTypeID('transform'))) {  
		var mFactor = desc.getObjectValue(stringIDToTypeID('transform')).getUnitDoubleValue (stringIDToTypeID("yy") );  
		return  mFactor;
    }  
	return 1;  
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值