Как стать автором
Обновить

Передача файлов от дизайнера к программисту. Скрипты

Время на прочтение 7 мин
Количество просмотров 16K
image
Допустим у нас есть команда разработчиков приложений для мобильных устройств. В такой команде точно есть дизайнер и точно есть программист. Сначала они занимаються каждый своей работой — программист разрабатывает прототип, механику, дизайнер делает наброски клавиш, бэкграундов. Но приходит момент когда дизайнер должен передать свою работу для того, что бы увидеть её уже в приложении. И вот тут могут возникнуть трудности.

Рассмотрим это на примере одного экрана меню. В нём может быть 30-40 элементов графики. Это и клавиши (статичные, нажатые), анимированый логотип из 10-ти объектов, всплывающее меню, анимация на бэкграунде. Преимущественно дизайнер выдаёт каждый файл отдельно, а затем программист по новой складывает его. Таким образом выходит конструктор, который складываеться и раскладываеться по несколько раз. Затем дизайн может поменяться и всё приходиться делать по-новой.

Как максимально автоматизировать этот процесс

Вот несколько шагов, которые я использую для того, что бы от дизайнера к программисту пришел максимально удобный набор файлов без потери качества.

Исходный файл в AI в максимальном разрешении (2048*1536px в моём случае) с черновым вариантом дизайна.
— разложить и сгрупировать все объекты по слоям
— назвать их правильно (кнопки — Btn, иконки — Icon, подложки — Underlay...)
— сделать все элементы кратными 2px по ширине и высоте
— расположить все элементы по XY кратными 2px
— экспортировать XY-координаты каждого слоя, его название и положение на листе. Поскольку Illustrator не умеет экспортироваль текстовый файл — данные будут помещены в самый верхний слой в объект *Текст*
XY layer coordinates script
if (app.documents.length > 0) 

{ 
var doc = app.activeDocument;
var x, y, t;
app.coordinateSystem = CoordinateSystem.ARTBOARDCOORDINATESYSTEM; 
var count = doc.layers.length; 
var out_txt=""; 

for ( var i = 0; i < count; ++i)
{ 
doc.activeLayer = doc.layers[i]; 
doc.layers[i].hasSelectedArtwork = true; 
}


for ( var i = 0; i < count; ++i)
{
x = doc.selection[i].position[0]; 
y = doc.selection[i].position[1]*(-1); 
// Layer name, X, Y, layer number
out_txt += doc.layers[i].name + ";;;;x=" + x.toFixed(0) + ";;;;y=" + y.toFixed(0) + ";;;;" + i +"\n"; 
} 

//Create text frame in first layer on position 0,0
t = doc.layers[0].textFrames.add();
        t.contents = out_txt;
 
} 


— экспортировать каждый слой в отдельный PNG24 файл в двухкратном размере. Arcticmill
Export Layers as PNG files
// *** Export Layers as PNG files (in multiple resolutions) ***
// This script will export all layers that have a name starting with "#", or "%" into a subfolder of the current .ai document.
// These options can be configured below:
// *** Config options  ***
var subFolderName = "Export";
var saveInMultipleResolutions = true;
// ...
// Note: only use one character!
var exportLayersStartingWith = "#";
var exportLayersWithArtboardClippingStartingWith = "%";
// ...
var lowResolutionFileAppend = "@Low";
var normalResolutionFileAppend = "-ipad";
var highResolutionFileAppend = "-ipadhd";
// ...
var lowResolutionScale = 50;
var normalResolutionScale = 100;
var highResolutionScale = 200;

// *** Start of script ***
var doc = app.activeDocument;

// Make sure we have saved the document
if (doc.path != "") {

    // Check if we need to create the export directory or we will get errors up ahead
    var exportDirectoryPath = doc.path + "/" + subFolderName;

    var exportDirectory = new Folder(exportDirectoryPath);

    if (!exportDirectory.exists) {
        // We must create the export directory it seems
        var newFolder = new Folder(exportDirectoryPath);
        newFolder.create();
    }

    var layerData = new Array();

    // Finds all layers that should be saved and saves these to the export layers array
    collectLayerData(doc, null);

    var layersToExportCount = 0;

    for (var i = 0; i < layerData.length; i++) {
        if ((layerData[i].tag == "include") || (layerData[i].tag == "include_and_clip")) {

            // Hide all layers first
            hideAllLayers();

            var clipToArtboard = false;

            if (layerData[i].tag == "include_and_clip") {
                clipToArtboard = true;
            }

            // Now show all layers needed to actually display the current layer on screen
            layerData[i].showIncludingParentAndChildLayers(); //showIncludingParents();

            // Now we can export the layer as one or multiple PNG files! 
            var savePath = doc.path; // Save to same folder as document but in a sub directory

            if (saveInMultipleResolutions) {
                // iPhone 3GS (50%)
                savePath.changePath(subFolderName + "/" + layerData[i].layer.name.substring(1, layerData[i].layer.name.length) + fixFileAppend(lowResolutionFileAppend));
                savePNG(savePath, lowResolutionScale, clipToArtboard);

                savePath = doc.path;

                // iPhone 4 (100%)
                savePath.changePath(subFolderName + "/" + layerData[i].layer.name.substring(1, layerData[i].layer.name.length) + fixFileAppend(normalResolutionFileAppend));
                savePNG(savePath, normalResolutionScale, clipToArtboard);

                savePath = doc.path;

                // iPad Retina (200%)
                savePath.changePath(subFolderName + "/" + layerData[i].layer.name.substring(1, layerData[i].layer.name.length) + fixFileAppend(highResolutionFileAppend));
                savePNG(savePath, highResolutionScale, clipToArtboard);

            } else {
                // Save normally (100%)
                savePath.changePath(subFolderName + "/" + layerData[i].layer.name.substring(1, layerData[i].layer.name.length));
                savePNG(savePath, normalResolutionScale, clipToArtboard);
            }

            layersToExportCount++;
        }
    }

    // Restore everything like it was before!
    restoreAllLayers();

    // Was there anything exported? If not make a warning!
    if (layersToExportCount == 0) {
        alert("Ooops, Found no layers to export!\n\nRemember that you must add a \"" + exportLayersStartingWith + "\" (when exporting the layer cropped to it's bound) or \"" + exportLayersWithArtboardClippingStartingWith + "\" (when layer should be clipped to artboard) to the beginning of the layer name. Also make sure that they layers you want to export are not locked or hidden.");
    } else {
        // Show a completed message
        alert(layersToExportCount + " layer(s) was successfully exported to: \n" + exportDirectoryPath);
    }

} else {
    // Document not saved yet!
    alert("Sorry, but you must save your document before you can use the export layers script! This is because exported images are saved in a subfolder to your original file.");
}

function fixFileAppend(fileAppend) {

    if (fileAppend == "") {
        return "";
    } else {
        return fileAppend + ".png";
    }

}

function hideAllLayers() {

    for (var i = 0; i < layerData.length; i++) {
        layerData[i].hide();
    }
}

function restoreAllLayers() {

    for (var i = 0; i < layerData.length; i++) {
        layerData[i].restoreVisibility();
    }

}

// Collects information about the various layers
function collectLayerData(rootLayer, extendedRootLayer) {
    for (var i = 0; i < rootLayer.layers.length; i++) {

        // We never even process locked or hidden layers
        if ((!rootLayer.layers[i].locked) && (rootLayer.layers[i].visible)) {

            var extendedLayer = new ExtendedLayer(rootLayer.layers[i]);

            // Set up parent
            extendedLayer.parentLayer = extendedRootLayer;

            // Also add this layer to the parents child collection
            if (extendedRootLayer != null) {
                extendedRootLayer.childLayers.push(extendedLayer);
            }

            layerData.push(extendedLayer);

            // Tag these layers so that we later can find out if we should export these layers or not
            if (rootLayer.layers[i].name.substring(0, 1) == exportLayersStartingWith) {
                extendedLayer.tag = "include";
            } else if (rootLayer.layers[i].name.substring(0, 1) == exportLayersWithArtboardClippingStartingWith) {
                extendedLayer.tag = "include_and_clip";
            } else {
                extendedLayer.tag = "skip";
            }

            // We should not export this layer but we continue looking for sub layers that might need to be exported
            collectLayerData(rootLayer.layers[i], extendedLayer);
        }
    }
}

// Holds info and additional methods for layers
function ExtendedLayer(layer) {
    this.originalVisibility = layer.visible;
    this.layer = layer;
    this.tag = "";
    this.hide = hide;
    this.show = show;
    this.showIncludingParentAndChildLayers = showIncludingParentAndChildLayers;
    this.restoreVisibility = restoreVisibility;
    this.restoreVisibilityIncludingChildLayers = restoreVisibilityIncludingChildLayers;
    this.layerName = layer.name;

    // Set after creating
    this.childLayers = new Array();
    this.parentLayer = null;

    function hide() {
        layer.visible = false;
    }

    function show() {
        layer.visible = true;
    }

    // Shows this layer including it's parent layers (up to the root) and it's child layers
    function showIncludingParentAndChildLayers() {

        var parentlayerName = "";

        if (this.parentLayer != null) {
            parentlayerName = this.parentLayer.layerName;
        }

        // Show all parents first
        var aParentLayer = this.parentLayer;

        while (aParentLayer != null) {
            aParentLayer.restoreVisibility();

            // Keep looking
            aParentLayer = aParentLayer.parentLayer;
        }

        // Show our own layer finally
        this.restoreVisibilityIncludingChildLayers();
    }

    function restoreVisibility() {
        layer.visible = this.originalVisibility;
    }

    function restoreVisibilityIncludingChildLayers() {
        this.restoreVisibility();

        // Call recursively for each child layer
        for (var i = 0; i < this.childLayers.length; i++) {
            this.childLayers[i].restoreVisibilityIncludingChildLayers();
        }
    }
}

// Save PNG file
function savePNG(file, scale, artBoardClipping) {

    var exp = new ExportOptionsPNG24();
    exp.transparency = true;
    exp.horizontalScale = scale
    exp.verticalScale = scale;
    exp.artBoardClipping = artBoardClipping;

    doc.exportFile(file, ExportType.PNG24, exp);
}


— сделать из этих файлов атлас размером 4096*4096 (или меньше, но обьязательно квадратный) с помощью Zwoptex или Texture Packer. Обьязательно дать отступ между картинками 8px
— избежать проблем с прозрачностью, которые случаються, если принебрегти кратностью 2px в векторном файле. К счастью есть экшн для Photoshop AlphaUnity
image
— уменьшить атлас в 2 раза
— перегнать файл в PNG8

Таким образом программист получит от меня текстовый файл с названиями картинок, их координаты, положение на листе, PLIST или XML с координатами и поворотами, атлас картинок в оптимальном качестве и превьюшку. Так сложилось что ни дизайнер ни программист не хотят заниматься этой рутинной, но очень нужной процедурой.

Такой алгоритм действий будет трудным и долгим только в первый раз, но потом очень упростит жизнь всем участникам проэкта.

Линк на скрипты и экшн. GitHub
Теги:
Хабы:
+5
Комментарии 11
Комментарии Комментарии 11

Публикации

Истории

Работа

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн