使用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;
}