查看: 9442|回复: 18
[spine] PS导出到Spine的方法教程

152

薇星辰

-4

薇积分

2万

精灵币

名誉版主

积分
2958

CGwell游戏特效04期学员

发表于 2014-11-19 17:58
欢迎入群    Spine2D骨骼动画   7708065
大家好,我是笨笨,笨笨的笨,笨笨的笨,谢谢!
PS图层 —> Spine

20140316 发现官方文档更新。其中多了一段 Photoshop 相关的介绍。官方提供了一个 LayersToPNG.jsx 脚本。用它可以:
1.将 PS 中的图层直接导出为 PNG,并生成 JSON 文件。
2.Spine 中 Import Data 导入此 JSON。所有图片将出现在 Spine 中。
3.名称不 PS 中的图层命名一至。
4.显示顺序不 PS 中的图层顺序一至。
这样原画只要在 PS 中画好角色,按需求命名、分层、将角色各部分调整好位置摆放。而动画师只要导入 JSON,架设骨骼。免去了原画师的输出和动画拼图对位的繁琐工作。
遗憾的是目前为止没发现皮肤功能。如果你需要皮肤那还是得手动添加,丌过起码导出
PNG 时可以方便些了。希望官方尽快加上皮肤的支持。
虽然很简单下面还是说下使用方法,把脚本拷到 PS 安装目录中的以下位置:\Adobe
Photoshop CS6 (64 Bit)\Presets\Scripts\LayersToPNG.jsx
cgwellPS导出到Spine的方法教程32 作者:圣树酱 帖子ID:8421


如果 PS 是开着的,重启一下才能加载脚本。
PS 中启动脚本的方法:“文件”=》“脚本”=》“LayersToPNG”。 脚本窗口参数:
Save PNGs: 保存 PNGSave JSON: 保存 JSON
Ignore hiddenlayers: 忽略隐藏层
Image Scale: 图片缩放
cgwellPS导出到Spine的方法教程66 作者:圣树酱 帖子ID:8421



最后要补充一点就是,脚本在当前 PS 文件的路径,创建一个 images 存放导出的文件。如果你的PS 文件是刚新建的并且目前还没保存,那脚本会说找丌到路径。找个地方先保存 一下,再执行脚本吧。
1 / 1



上一篇:Spine官方小游戏 spine-superspineboy
下一篇:Spine 角色间动画互导的方法

95

薇星辰

0

薇积分

172

精灵币

二星会员

发表于 2017-5-9 17:53
刚刚在网上找的, 能导出。但是一个问题,Spine在导入的时候会出现材质丢失,能详细说一下 Spine导入json 跟image 的过程顺序么?
// This script exports photoshop layers as individual PNGs. It also
// writes a JSON file that can be imported into Spine where the images
// will be displayed in the same positions and draw order.

// Setting defaults.
var writePngs = true;
var writeTemplate = false;
var writeJson = true;
var ignoreHiddenLayers = true;
var pngScale = 1;
var groupsAsSkins = false;
var useRulerOrigin = false;
var imagesDir = "./images/";
var projectDir = "";
var padding = 1;

// IDs for saving settings.
const settingsID = stringIDToTypeID("settings");
const writePngsID = stringIDToTypeID("writePngs");
const writeTemplateID = stringIDToTypeID("writeTemplate");
const writeJsonID = stringIDToTypeID("writeJson");
const ignoreHiddenLayersID = stringIDToTypeID("ignoreHiddenLayers");
const groupsAsSkinsID = stringIDToTypeID("groupsAsSkins");
const useRulerOriginID = stringIDToTypeID("useRulerOrigin");
const pngScaleID = stringIDToTypeID("pngScale");
const imagesDirID = stringIDToTypeID("imagesDir");
const projectDirID = stringIDToTypeID("projectDir");
const paddingID = stringIDToTypeID("padding");

var originalDoc;
try {
        originalDoc = app.activeDocument;
} catch (ignored) {}
var settings, progress;
loadSettings();
showDialog();

function run () {
        // Output dirs.
        var absProjectDir = absolutePath(projectDir);
        new Folder(absProjectDir).create();
        var absImagesDir = absolutePath(imagesDir);
        var imagesFolder = new Folder(absImagesDir);
        imagesFolder.create();
        var relImagesDir = imagesFolder.getRelativeURI(absProjectDir);
        relImagesDir = relImagesDir == "." ? "" : (relImagesDir + "/");

        // Get ruler origin.
        var xOffSet = 0, yOffSet = 0;
        if (useRulerOrigin) {
                var ref = new ActionReference();
                ref.putEnumerated(charIDToTypeID("Dcmn"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
                var desc = executeActionGet(ref);
                xOffSet = desc.getInteger(stringIDToTypeID("rulerOriginH")) >> 16;
                yOffSet = desc.getInteger(stringIDToTypeID("rulerOriginV")) >> 16;
        }

        activeDocument.duplicate();

        // Output template image.
        if (writeTemplate) {
                if (pngScale != 1) {
                        scaleImage();
                        storeHistory();
                }

                var file = new File(absImagesDir + "template");
                if (file.exists) file.remove();

                activeDocument.saveAs(file, new PNGSaveOptions(), true, Extension.LOWERCASE);

                if (pngScale != 1) restoreHistory();
        }

        if (!writeJson && !writePngs) {
                activeDocument.close(SaveOptions.DONOTSAVECHANGES);
                return;
        }

        // Rasterize all layers.
        try {
                executeAction(stringIDToTypeID( "rasterizeAll" ), undefined, DialogModes.NO);
        } catch (ignored) {}

        // Collect and hide layers.
        var layers = [];
        collectLayers(activeDocument, layers);
        var layersCount = layers.length;

        storeHistory();

        // Store the slot names and layers for each skin.
        var slots = {}, skins = { "default": [] };
        for (var i = layersCount - 1; i >= 0; i--) {
                var layer = layers[i];

                // Use groups as skin names.
                var potentialSkinName = trim(layer.parent.name);
                var layerGroupSkin = potentialSkinName.indexOf("-NOSKIN") == -1;
                var skinName = (groupsAsSkins && layer.parent.typename == "LayerSet" && layerGroupSkin) ? potentialSkinName : "default";

                var skinLayers = skins[skinName];
                if (!skinLayers) skins[skinName] = skinLayers = [];
                skinLayers[skinLayers.length] = layer;

                slots[layerName(layer)] = true;
        }

        // Output skeleton and bones.
        var json = '{"skeleton":{"images":"' + relImagesDir + '"},\n"bones":[{"name":"root"}],\n"slots":[\n';

        // Output slots.
        var slotsCount = countAssocArray(slots);
        var slotIndex = 0;
        for (var slotName in slots) {
                if (!slots.hasOwnProperty(slotName)) continue;

                // Use image prefix if slot's attachment is in the default skin.
                var attachmentName = slotName;
                var defaultSkinLayers = skins["default"];
                for (var i = defaultSkinLayers.length - 1; i >= 0; i--) {
                        if (layerName(defaultSkinLayers[i]) == slotName) {
                                attachmentName = slotName;
                                break;
                        }
                }

                json += '\t{"name":"' + slotName + '","bone":"root","attachment":"' + attachmentName + '"}';
                slotIndex++;
                json += slotIndex < slotsCount ? ",\n" : "\n";
        }
        json += '],\n"skins":{\n';

        // Output skins.
        var skinsCount = countAssocArray(skins);
        var skinIndex = 0;
        for (var skinName in skins) {
                if (!skins.hasOwnProperty(skinName)) continue;
                json += '\t"' + skinName + '":{\n';

                var skinLayers = skins[skinName];
                var skinLayersCount = skinLayers.length;
                var skinLayerIndex = 0;
                for (var i = skinLayersCount - 1; i >= 0; i--) {
                        var layer = skinLayers[i];
                        var slotName = layerName(layer);
                        var placeholderName, attachmentName;
                        if (skinName == "default") {
                                placeholderName = slotName;
                                attachmentName = placeholderName;
                        } else {
                                placeholderName = slotName;
                                attachmentName = skinName + "/" + slotName;
                        }

                        var x = activeDocument.width.as("px") * pngScale;
                        var y = activeDocument.height.as("px") * pngScale;

                        layer.visible = true;
                        if (!layer.isBackgroundLayer) activeDocument.trim(TrimType.TRANSPARENT, false, true, true, false);
                        x -= activeDocument.width.as("px") * pngScale;
                        y -= activeDocument.height.as("px") * pngScale;
                        if (!layer.isBackgroundLayer) activeDocument.trim(TrimType.TRANSPARENT, true, false, false, true);
                        var width = activeDocument.width.as("px") * pngScale + padding * 2;
                        var height = activeDocument.height.as("px") * pngScale + padding * 2;

                        // Save image.
                        if (writePngs) {
                                if (pngScale != 1) scaleImage();
                                if (padding > 0) activeDocument.resizeCanvas(width, height, AnchorPosition.MIDDLECENTER);

                                if (skinName != "default") new Folder(absImagesDir + skinName).create();
                                activeDocument.saveAs(new File(absImagesDir + attachmentName), new PNGSaveOptions(), true, Extension.LOWERCASE);
                        }

                        restoreHistory();
                        layer.visible = false;

                        x += Math.round(width) / 2;
                        y += Math.round(height) / 2;

                        // Make relative to the Photoshop document ruler origin.
                        if (useRulerOrigin) {
                                x -= xOffSet * pngScale;
                                y -= activeDocument.height.as("px") * pngScale - yOffSet * pngScale; // Invert y.
                        }

                        if (attachmentName == placeholderName) {
                                json += '\t\t"' + slotName + '":{"' + placeholderName + '":{'
                                        + '"x":' + x + ',"y":' + y + ',"width":' + Math.round(width) + ',"height":' + Math.round(height) + '}}';
                        } else {
                                json += '\t\t"' + slotName + '":{"' + placeholderName + '":{"name":"' + attachmentName + '", '
                                        + '"x":' + x + ',"y":' + y + ',"width":' + Math.round(width) + ',"height":' + Math.round(height) + '}}';
                        }

                        skinLayerIndex++;
                        json += skinLayerIndex < skinLayersCount ? ",\n" : "\n";
                }
                json += "\t\}";

                skinIndex++;
                json += skinIndex < skinsCount ? ",\n" : "\n";
        }
        json += '},\n"animations":{"animation":{}}\n}';

        activeDocument.close(SaveOptions.DONOTSAVECHANGES);

        // Output JSON file.
        if (writeJson) {
                var name = decodeURI(originalDoc.name);
                name = name.substring(0, name.indexOf("."));
                var file = new File(absProjectDir + name + ".json");
                file.remove();
                file.open("w", "TEXT");
                file.lineFeed = "\n";
                file.write(json);
                file.close();
        }
}

// Dialog and settings:

function showDialog () {
        if (!originalDoc) {
                alert("Please open a document before running the LayersToPNG script.");
                return;
        }
        if (!hasFilePath()) {
                alert("Please save the document before running the LayersToPNG script.");
                return;
        }

        var dialog = new Window("dialog", "Spine LayersToPNG");
        dialog.alignChildren = "fill";

        var checkboxGroup = dialog.add("group");
                var group = checkboxGroup.add("group");
                        group.orientation = "column";
                        group.alignChildren = "left";
                        var writePngsCheckbox = group.add("checkbox", undefined, " Write layers as PNGs");
                        writePngsCheckbox.value = writePngs;
                        var writeTemplateCheckbox = group.add("checkbox", undefined, " Write a template PNG");
                        writeTemplateCheckbox.value = writeTemplate;
                        var writeJsonCheckbox = group.add("checkbox", undefined, " Write Spine JSON");
                        writeJsonCheckbox.value = writeJson;
                group = checkboxGroup.add("group");
                        group.orientation = "column";
                        group.alignChildren = "left";
                        var ignoreHiddenLayersCheckbox = group.add("checkbox", undefined, " Ignore hidden layers");
                        ignoreHiddenLayersCheckbox.value = ignoreHiddenLayers;
                        var groupsAsSkinsCheckbox = group.add("checkbox", undefined, " Use groups as skins");
                        groupsAsSkinsCheckbox.value = groupsAsSkins;
                        var useRulerOriginCheckbox = group.add("checkbox", undefined, " Use ruler origin as 0,0");
                        useRulerOriginCheckbox.value = useRulerOrigin;

        var slidersGroup = dialog.add("group");
                group = slidersGroup.add("group");
                        group.orientation = "column";
                        group.alignChildren = "right";
                        group.add("statictext", undefined, "PNG scale:");
                        group.add("statictext", undefined, "Padding:");
                group = slidersGroup.add("group");
                        group.orientation = "column";
                        var scaleText = group.add("edittext", undefined, pngScale * 100);
                        scaleText.characters = 4;
                        var paddingText = group.add("edittext", undefined, padding);
                        paddingText.characters = 4;
                group = slidersGroup.add("group");
                        group.orientation = "column";
                        group.add("statictext", undefined, "%");
                        group.add("statictext", undefined, "px");
                group = slidersGroup.add("group");
                        group.alignment = ["fill", ""];
                        group.orientation = "column";
                        group.alignChildren = ["fill", ""];
                        var scaleSlider = group.add("slider", undefined, pngScale * 100, 1, 100);
                        var paddingSlider = group.add("slider", undefined, padding, 0, 4);
        scaleText.onChanging = function () { scaleSlider.value = scaleText.text; };
        scaleSlider.onChanging = function () { scaleText.text = Math.round(scaleSlider.value); };
        paddingText.onChanging = function () { paddingSlider.value = paddingText.text; };
        paddingSlider.onChanging = function () { paddingText.text = Math.round(paddingSlider.value); };

        var outputGroup = dialog.add("panel", undefined, "Output directories");
                outputGroup.alignChildren = "fill";
                outputGroup.margins = [10,15,10,10];
                var textGroup = outputGroup.add("group");
                        group = textGroup.add("group");
                                group.orientation = "column";
                                group.alignChildren = "right";
                                group.add("statictext", undefined, "Images:");
                                group.add("statictext", undefined, "JSON:");
                        group = textGroup.add("group");
                                group.orientation = "column";
                                group.alignChildren = "fill";
                                group.alignment = ["fill", ""];
                                var imagesDirText = group.add("edittext", undefined, imagesDir);
                                var projectDirText = group.add("edittext", undefined, projectDir);
                outputGroup.add("statictext", undefined, "Begin paths with \"./\" to be relative to the PSD file.").alignment = "center";

        var group = dialog.add("group");
                group.alignment = "center";
                var runButton = group.add("button", undefined, "OK");
                var cancelButton = group.add("button", undefined, "Cancel");
                cancelButton.onClick = function () {
                        dialog.close(0);
                        return;
                };

        function updateSettings () {
                writePngs = writePngsCheckbox.value;
                writeTemplate = writeTemplateCheckbox.value;
                writeJson = writeJsonCheckbox.value;
                ignoreHiddenLayers = ignoreHiddenLayersCheckbox.value;
                var scaleValue = parseFloat(scaleText.text);
                if (scaleValue > 0 && scaleValue <= 100) pngScale = scaleValue / 100;
                groupsAsSkins = groupsAsSkinsCheckbox.value;
                useRulerOrigin = useRulerOriginCheckbox.value;
                imagesDir = imagesDirText.text;
                projectDir = projectDirText.text;
                var paddingValue = parseInt(paddingText.text);
                if (paddingValue >= 0) padding = paddingValue;
        }

        dialog.onClose = function() {
                updateSettings();
                saveSettings();
        };
       
        runButton.onClick = function () {
                if (scaleText.text <= 0 || scaleText.text > 100) {
                        alert("PNG scale must be between > 0 and <= 100.");
                        return;
                }
                if (paddingText.text < 0) {
                        alert("Padding must be >= 0.");
                        return;
                }
                dialog.close(0);

                var rulerUnits = app.preferences.rulerUnits;
                app.preferences.rulerUnits = Units.PIXELS;
                try {
                        run();
                } catch (e) {
                        alert("An unexpected error has occurred.\n\nTo debug, run the LayersToPNG script using Adobe ExtendScript "
                                + "with \"Debug > Do not break on guarded exceptions\" unchecked.");
                        debugger;
                } finally {
                        if (activeDocument != originalDoc) activeDocument.close(SaveOptions.DONOTSAVECHANGES);
                        app.preferences.rulerUnits = rulerUnits;
                }
        };

        dialog.center();
        dialog.show();
}

function loadSettings () {
        try {
                settings = app.getCustomOptions(settingsID);
        } catch (e) {
                saveSettings();
        }
        if (typeof settings == "undefined") saveSettings();
        settings = app.getCustomOptions(settingsID);
        if (settings.hasKey(writePngsID)) writePngs = settings.getBoolean(writePngsID);
        if (settings.hasKey(writeTemplateID)) writeTemplate = settings.getBoolean(writeTemplateID);
        if (settings.hasKey(writeJsonID)) writeJson = settings.getBoolean(writeJsonID);
        if (settings.hasKey(ignoreHiddenLayersID)) ignoreHiddenLayers = settings.getBoolean(ignoreHiddenLayersID);
        if (settings.hasKey(pngScaleID)) pngScale = settings.getDouble(pngScaleID);
        if (settings.hasKey(groupsAsSkinsID)) groupsAsSkins = settings.getBoolean(groupsAsSkinsID);
        if (settings.hasKey(useRulerOriginID)) useRulerOrigin = settings.getBoolean(useRulerOriginID);
        if (settings.hasKey(imagesDirID)) imagesDir = settings.getString(imagesDirID);
        if (settings.hasKey(projectDirID)) projectDir = settings.getString(projectDirID);
        if (settings.hasKey(paddingID)) padding = settings.getDouble(paddingID);
}

function saveSettings () {
        var settings = new ActionDescriptor();
        settings.putBoolean(writePngsID, writePngs);
        settings.putBoolean(writeTemplateID, writeTemplate);
        settings.putBoolean(writeJsonID, writeJson);
        settings.putBoolean(ignoreHiddenLayersID, ignoreHiddenLayers);
        settings.putDouble(pngScaleID, pngScale);
        settings.putBoolean(groupsAsSkinsID, groupsAsSkins);
        settings.putBoolean(useRulerOriginID, useRulerOrigin);
        settings.putString(imagesDirID, imagesDir);
        settings.putString(projectDirID, projectDir);
        settings.putDouble(paddingID, padding);
        app.putCustomOptions(settingsID, settings, true);
}

// Photoshop utility:

function scaleImage () {
        var imageSize = activeDocument.width.as("px");
        activeDocument.resizeImage(UnitValue(imageSize * pngScale, "px"), null, null, ResampleMethod.BICUBICSHARPER);
}

var historyIndex;
function storeHistory () {
        historyIndex = activeDocument.historyStates.length - 1;
}
function restoreHistory () {
        activeDocument.activeHistoryState = activeDocument.historyStates[historyIndex];
}

function collectLayers (layer, collect) {
        for (var i = 0, n = layer.layers.length; i < n; i++) {
                var child = layer.layers[i];
                if (ignoreHiddenLayers && !child.visible) continue;
                if (child.bounds[2] == 0 && child.bounds[3] == 0) continue;
                if (child.layers && child.layers.length > 0)
                        collectLayers(child, collect);
                else if (child.kind == LayerKind.NORMAL) {
                        collect.push(child);
                        child.visible = false;
                }
        }
}

function hasFilePath () {
        var ref = new ActionReference();
        ref.putEnumerated(charIDToTypeID("Dcmn"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
        return executeActionGet(ref).hasKey(stringIDToTypeID("fileReference"));
}

function absolutePath (path) {
        path = trim(path);
        if (path.length == 0)
                path = activeDocument.path.toString();
        else if (imagesDir.indexOf("./") == 0)
                path = activeDocument.path + path.substring(1);
        path = path.replace(/\\/g, "/");
        if (path.substring(path.length - 1) != "/") path += "/";
        return path;
}

// JavaScript utility:

function countAssocArray (obj) {
        var count = 0;
        for (var key in obj)
                if (obj.hasOwnProperty(key)) count++;
        return count;
}

function trim (value) {
        return value.replace(/^\s+|\s+$/g, "");
}

function endsWith (str, suffix) {
        return str.indexOf(suffix, str.length - suffix.length) !== -1;
}

function stripSuffix (str, suffix) {
        if (endsWith(str.toLowerCase(), suffix.toLowerCase())) str = str.substring(0, str.length - suffix.length);
        return str;
}

function layerName (layer) {
        return stripSuffix(trim(layer.name), ".png").replace(/[:\/\\*\?\"\<\>\|]/g, "");
}

楼层回复(0) 收起

2

薇星辰

0

薇积分

6

精灵币

见习会员

发表于 2016-12-14 17:09
不好意思,打擾你
謝謝你的PS导出到Spine的方法教程  教學

可是 妳最後的一句(((最后要补充一点就是,脚本在当前 PS 文件的路径,创建一个 images 存放导出的文件。如果你的PS 文件是刚新建的并且目前还没保存,那脚本会说找丌到路径。找个地方先保存 一下,再执行脚本吧。))))

我不是很懂耶,謝謝   還有我選了JSX檔
PS就出現錯誤22沒有建構涵式

實在不知道哪邊出問題,麻煩你了一下問那麼多,非常感謝


楼层回复(0) 收起

57

薇星辰

0

薇积分

103

精灵币

一星会员

发表于 2017-8-30 10:35
我这个PS CC版本好像不支持 SPINE,只能PS CS6吗
楼层回复(0) 收起

1

薇星辰

0

薇积分

3

精灵币

见习会员

发表于 2018-2-7 16:20
谢谢楼主分享~~学到了~
楼层回复(0) 收起

3

薇星辰

0

薇积分

13

精灵币

见习会员

发表于 2018-3-19 20:40
谢谢分享,学习了
楼层回复(0) 收起

2

薇星辰

2

薇积分

13

精灵币

见习会员

发表于 2017-12-24 12:59
不错的脚本。
楼层回复(0) 收起

2

薇星辰

0

薇积分

3

精灵币

见习会员

发表于 2018-1-10 21:42



谢谢分享.
楼层回复(0) 收起

24

薇星辰

0

薇积分

49

精灵币

一星会员

发表于 2016-12-2 11:00
感谢分享
楼层回复(0) 收起

73

薇星辰

2

薇积分

32

精灵币

一星会员

CGwell游戏特效01期学员CGwell游戏特效03期学员

发表于 2016-8-26 17:40
看看
楼层回复(0) 收起

34

薇星辰

0

薇积分

43

精灵币

一星会员

发表于 2017-3-5 08:02
cgwellPS导出到Spine的方法教程91 作者:黑武士 帖子ID:8421
楼层回复(0) 收起

1

薇星辰

0

薇积分

85

精灵币

一星会员

发表于 2015-5-13 14:02
谢谢分享,很实用
楼层回复(0) 收起

0

薇星辰

0

薇积分

9

精灵币

一星会员

发表于 2015-5-27 16:27
谢谢分享,很实用
楼层回复(0) 收起

1

薇星辰

0

薇积分

17

精灵币

见习会员

发表于 2015-8-18 17:07
谢谢分享,楼主可以分享一下这个 LayersToPNG.jsx脚本吗?一直没找到呢。
楼层回复(0) 收起

0

薇星辰

0

薇积分

311

精灵币

一星会员

发表于 2015-9-17 10:53
不错的脚本。
楼层回复(0) 收起

667

薇星辰

0

薇积分

1526

精灵币

三星会员

发表于 2015-11-20 14:38
{cgwell:lol:}
楼层回复(0) 收起

1

薇星辰

0

薇积分

4

精灵币

见习会员

发表于 2016-3-14 17:20




楼层回复(0) 收起

5

薇星辰

0

薇积分

19

精灵币

见习会员

发表于 2016-4-19 00:11
楼主好人,感谢楼主分享。

楼层回复(0) 收起

4

薇星辰

0

薇积分

23

精灵币

见习会员

发表于 2016-7-9 11:03
谢谢楼主分享~~学到了~
楼层回复(0) 收起
您需要登录后才可以回帖 登录 | 普通注册

本版积分规则 回复 诸如“sadasdasf”“撒旦撒旦撒范围为” 将被直接禁言 快捷回复内容选择:

CG薇儿热门讨论X

近日有些会员无法登陆网站

近日有些会员无法登陆网站,系网站登陆模块有bug,请暂时切换为IE内核的浏览器登陆,这个兼容问题会努力修复...

参加讨论查看详情
快速回复 返回列表 客服中心
快速回复 返回顶部 返回列表