unity2d导出apk

Unity has been used to develop numerous high quality 2D games over the years. This article, which is based on a talk I gave at three of our regional Unite developer conferences in Korea, Japan and China, gives in-depth instruction for a solid, real-life 2D production workflow. I hope this post can be helpful for any of our readers that are creating 2D games and interactive content with Unity. Due to the length of the tutorial, I’ve split it into two blog posts. Today you can read about authoring and exporting and tomorrow I’ll post the section on importing. You can find a link to download all the files at the end of the post.

多年来,Unity已用于开发众多高质量的2D游戏。 本文基于我在韩国,日本和中国的三个地区Unite开发者大会上的演讲而给出的内容,它为扎实,真实的2D生产工作流程提供了深入的指导。 我希望这篇文章对我们使用Unity创建2D游戏和交互式内容的所有读者有所帮助。 由于教程的篇幅,我将其分为两篇博客文章。 今天您可以阅读有关创作和导出的信息,明天我将发布有关导入的部分。 您可以在文章末尾找到一个链接来下载所有文件。

The benefits of a good 2D content workflow

良好的2D内容工作流程的好处

This tutorial takes you through the implementation of a real production workflow. When we talk about workflow it means the steps and processes used all the way from the initial authoring until the content is actually running in the game. Regardless of the number of applications needed in the chain from beginning to end, at a high-level a workflow consists of 3 major steps: authoring, exporting and importing.

本教程将指导您完成实际生产工作流程的实施。 当我们谈论工作流时,它意味着从最初的创作到内容在游戏中实际使用的所有步骤和过程。 从头到尾,无论链中需要多少应用程序,工作流程总体上都包含3个主要步骤:创作,导出和导入。

For some it may seem odd that we need both an exporter and an importer since tools like Unity are able to import data directly. The reason we need both is because we’re essentially creating our own intermediate file format that acts as the glue between any two applications in your workflow, in this case between Photoshop and Unity. This eliminates the need for an external application to be able to read and parse an application’s native file format, and allows chaining of applications to process the content as it moves from one application to the next.

对于某些人来说,我们既需要导出器又需要导入器似乎很奇怪,因为Unity等工具能够直接导入数据。 我们需要两者的原因是因为我们实际上是在创建自己的中间文件格式,该文件格式充当工作流程中任何两个应用程序(在本例中为Photoshop和Unity之间)之间的粘合剂。 这消除了外部应用程序能够读取和解析应用程序的本机文件格式的需求,并允许应用程序链接来处理内容从一个应用程序移动到另一个应用程序的过程。

Authoring

创作

It all starts with great content, and the first workflow we’ll look at is using the industry standard workhorse Photoshop. One of the great things about Photoshop is it’s simplicity to get started. Creating and editing imagery and graphics is painless and easy, and as your needs grow there’s an arsenal of tools and techniques at your disposal. The problem is that image files by themselves don’t have much ability to express useful meta-data for games in the same way that 3D models are able to do. What’s more, 2D is inherently in need of more meta-data given it’s limited nature of just being pixels.

一切都从丰富的内容开始,我们将要看的第一个工作流程是使用行业标准主力Photoshop。 Photoshop的一大优点是入门非常简单。 创建和编辑图像和图形既轻松又轻松,并且随着您的需求的增长,可以使用大量的工具和技术。 问题在于,图像文件本身不具备像3D模型一样能够表达游戏有用的元数据的能力。 此外,由于2D本质上仅是像素,因此本质上需要更多的元数据。

So what we want to do is imbue the image with useful information so that ultimately we know how to use the image in a game. To do that, we can use groups and layers to organize and tag them with what we need.

因此,我们要做的是向图像注入有用的信息,以便最终我们知道如何在游戏中使用图像。 为此,我们可以使用组和图层来组织和标记所需的内容。

We also need an example of 2D content that is useful and represents a realistic use. One of the last games I worked on before joining Unity was a simple 2D hidden-object game called Goddess Chronicles, and so we’ll use that as an example. In a hidden-object game the general idea is to find certain items that are hidden in plain sight in a scene such as the example shown below.

我们还需要一个有用且代表实际用途的2D内容示例。 加入Unity之前,我参与开发的最后一款游戏是一个名为Goddess Chronicles的简单2D隐藏对象游戏,因此我们以它为例。 在隐藏对象游戏中,通常的想法是找到某些场景中隐藏在普通视线中的项目,例如下面所示的示例。

Depending upon the game you’re making, the meta-data you need to capture will be different. For this game the design called for 2 basic kinds of imagery: “scenery” and “items”.

根据您制作的游戏,您需要捕获的元数据将有所不同。 对于这款游戏,设计需要两种基本的图像:“风景”和“物品”。

The importance of layers

图层的重要性

Scenery is the non-interactive content that’s there to provide the bulk of what’s seen, communicate a theme and support the gameplay by providing an environment where items can be hidden. So scenery will be placed into a group named “scenery” and we don’t need to care about the art layers in that group because they’re non-interactive. The items are the things you’ll actually be searching for within the scene and comprise the core gameplay. These are placed in groups that are named “item” and, unlike scenery, the art layers are important and each item can have up to 4 unique layers associated with them.

风景是一种非交互性内容,通过提供可以隐藏项目的环境来提供大部分可见内容,传达主题并支持游戏玩法。 因此,风景将被放置在一个名为“风景”的组中,我们不需要关心该组中的艺术层,因为它们是非交互的。 这些项目是您实际上将在场景中搜索的内容,并构成核心游戏玩法。 它们被放置在称为“项目”的组中,与风景不同,艺术层很重要,每个项目最多可以有4个与之关联的独特层。

“Whole” layers are required for all items. Usually once an item is found there will be some effect such as zooming the item up, or placing it into the player’s inventory, and we need the whole image so that it looks right.

所有项目都需要“整个”层。 通常,一旦找到某件物品,就会产生一些效果,例如放大该物品或将其放入玩家的库存,并且我们需要整个图像以使其看起来正确。

“Obscured” layers are used when you want to create the illusion that an item is behind something when in fact it’s floating in front of it. By erasing the pixels that should be hidden it tricks the eye into thinking that it’s actually behind something. In theory we could use whole images for everything, but there are many situations when it’s tedious for the artist to draw everything as separate pieces in order to hide an item, plus using the minimal amount of images in the game will increase the runtime performance.

当您想创建一个项目在某物后面,而实际上它漂浮在某物之前时,则使用“模糊”层。 通过擦除应该隐藏的像素,它使眼睛误以为它实际上在某物后面。 从理论上讲,我们可以为所有内容使用整个图像,但是在很多情况下,对于艺术家来说,将所有内容绘制为单独的部分以隐藏项目是很繁琐的,而且在游戏中使用最少数量的图像会提高运行时性能。

“Shadow” layers are used to help visually place the items into the scene and look like they belong there. Shadows are kept separately from the whole or obscured image so that if the whole image is zoomed up or moved it doesn’t have an odd looking shadow following it, and instead we can just hide the shadow in the scene once the item is found.

“阴影”层用于帮助视觉上将项目放置到场景中,并看起来像它们属于那里。 阴影与整个图像或被遮盖的图像分开放置,因此,如果放大或移动整个图像,其后将没有奇怪的阴影,而是可以在找到该项目后将其隐藏在场景中。

“Hotspot” layers are used to increase or decrease the area which the item can be interacted with. For example if you hide a golf club in a scene it could be very difficult to click or tap on. By using a hotspot you could make the interaction area bigger and easier to use.

“热点”层用于增加或减少可以与项目进行交互的区域。 例如,如果您将高尔夫俱乐部隐藏在一个场景中,则可能很难单击或点击。 通过使用热点,您可以使交互区域更大并且更易于使用。

So putting all this together we can use groups to designate whether something in our scene is scenery or an item, and we can use the art layers for items to hold the whole image for the item and optionally include obscured, shadow and hotspot images. The image below shows a set of layers for some scenery and an item. The item group is labelled “item:Beads” and contains 2 art layers called “whole” and “hotspot”. The scenery group is labelled “scenery:Column” and it contains any number of art layers that can be named anything since scenery layers aren’t special and we don’t need to keep track of them.

因此,将所有这些放在一起,我们可以使用组来指定场景中的某物是风景还是一件物品,并且我们可以使用艺术品的艺术品图层来保存该物品的整个图像,并可以选择包括模糊,阴影和热点图像。 下图显示了一些场景和项目的一组图层。 该项目组被标记为“ item:Beads”,并包含2个称为“整个”和“热点”的艺术图层。 风光组被标记为“ scenery:Column”,它包含任意数量的可以命名的艺术层,因为风光层不是特别的,我们不需要对其进行跟踪。

The end result is that we have a well composed scene where the group and layer names are encoded with what we need to give them meaning in our game. You can download the package at the end of the post and have a look for yourself. The next step is to now get all this exported.

最终结果是,我们有一个结构良好的场景,其中组和层的名称已编码为我们需要在游戏中赋予它们的含义。 您可以在文章末尾下载该软件包,并自己看看。 下一步是现在将所有这些导出。

Exporting

出口

Once we have some content, we need some way to get it all out for the next application in our workflow, which in this case is Unity. What we want to export are the images used to construct the scene along with the meta-data describing the position, order and other information. In order to do that we need a way to interact with the application at a low level with something that understands how to do that. Fortunately for us, Photoshop is scriptable using javascript and this will do exactly what we need.

一旦有了一些内容,我们就需要某种方法将其全部用于工作流中的下一个应用程序,在本例中为Unity。 我们要导出的是用于构建场景的图像以及描述位置,顺序和其他信息的元数据。 为了做到这一点,我们需要一种与底层应用程序进行交互的方法,该方法可以理解如何做到这一点。 对我们来说幸运的是,Photoshop可使用javascript编写脚本,这将完全满足我们的需求。

In fact quite a few Adobe apps are scriptable including Fireworks, Illustrator, Flash and others. We can use this capability to write our own exporter that can prepare the images for use in Unity and also capture the meta-data we need to make them meaningful. Adobe provides useful documentation and a script editor and debugger called ExtendScript for free.

实际上,相当多的Adobe应用程序都可以编写脚本,包括Fireworks,Illustrator,Flash等。 我们可以使用此功能来编写我们自己的导出器,该导出器可以准备要在Unity中使用的图像,还可以捕获使它们有意义的元数据。 Adobe免费提供有用的文档以及名为ExtendScript的脚本编辑器和调试器。

If you have the Creative Suite, it’s probably already installed on your system. On Mac it can be found in the Utilities/Adobe Utilities – CS6/ExtendScript Toolkit CS6 folder for the latest Creative Suite. On Windows, you should be able to find it at C:\Documents and Settings\\Application Data\ Adobe\ExtendScript Toolkit\3.8.

如果您拥有Creative Suite,则它可能已经安装在系统上。 在Mac上,可以在实用程序/ Adob​​e Utilities – CS6 / ExtendScript Toolkit CS6文件夹中找到最新的Creative Suite。 在Windows上,您应该能够在C:\ Documents and Settings \\ Application Data \ Adob​​e \ ExtendScript Toolkit \ 3.8中找到它。

Pretty much anything you can do in Photoshop can be scripted, and if there isn’t an API for a particular thing you want to do, you can record actions and convert them to command codes that you can paste into your scripts (alpha channel operations, I’m looking at you!).

您可以在Photoshop中执行的几乎所有操作都可以编写脚本,并且如果没有要执行的特定操作的API,则可以记录操作并将其转换为可粘贴到脚本中的命令代码(alpha通道操作) , 我在看着你!)。

For our purposes, we need a simple script that basically:

为了我们的目的,我们需要一个简单的脚本,该脚本基本上:

The final exporter script is in the tutorial package. To use the script you place it in the Presets/Scripts sub-directory inside your Photoshop application directory. The script contains comments that explain what it does so I won’t go through all of it, but I will cover some of the more important parts.

最终的导出程序脚本位于教程包中。 要使用脚本,请将其放置在Photoshop应用程序目录内的“预设/脚本”子目录中。 该脚本包含一些注释,这些注释解释了该脚本的功能,因此我不会全部介绍,但我将介绍一些更重要的部分。

Firstly, the scripts are written in javascript which makes it easy to learn and use. The javascript engine used by Adobe isn’t very fast, but it works and is debuggable with the ExtendScript editor which makes it very useful.

首先,脚本是用JavaScript编写的,因此易于学习和使用。 Adobe使用JavaScript引擎并不是很快,但是它可以正常工作并且可以通过ExtendScript编辑器进行调试,这使其非常有用。

Since the meta-data is actually the part that gives meaning to the data, we need to spend the most time figuring out exactly what meta-data we need and how it should be formatted. I use XML as the way to specify the meta-data since that makes it really easy to parse later on in Unity. Based on the game design we know we have certain data that will be needed in the game. An extract of that data looks like this:

由于元数据实际上是赋予数据含义的部分,因此我们需要花费大量时间来准确确定所需的元数据以及应如何格式化。 我使用XML作为指定元数据的方式,因为这使得以后在Unity中解析起来真的很容易。 根据游戏设计,我们知道游戏中需要某些数据。 该数据的摘录如下所示:

<?xml version="1.0" encoding="utf-8"?> <HogScene xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <layers> <Layer> <type>Scenery</type> <name>Scenery 01-4 background</name> <images> <Image> <type>Whole</type> <name>Scenery Tree.png</name> <x>25</x> <y>63</y> </Image> </images> </Layer> ... </layers> </HogScene> <?xml version="1.0" encoding="utf-8"?> <HogScene xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <layers> <Layer> <type>Scenery</type> <name>Scenery 01-4 background</name> <images> <Image> <type>Whole</type> <name>Scenery Tree.png</name> <x>25</x> <y>63</y> </Image> </images> </Layer> ... </layers> </HogScene>

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

<?xml version="1.0" encoding="utf-8"?>
<HogScene xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<layers>
<Layer>
<type>Scenery</type>
<name>Scenery 01-4 background</name>
<images>
<Image>
<type>Whole</type>
<name>Scenery Tree.png</name>
<x>25</x>
<y>63</y>
</Image>
</images>
</Layer>
...
</layers>
</HogScene>

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

& lt ; ? xml version = "1.0" encoding = "utf-8" ? & gt ;
& lt ; HogScene xmlns : xsi = "http://www.w3.org/2001/XMLSchema-instance"
xmlns : xsd = "http://www.w3.org/2001/XMLSchema" & gt ;
& lt ; layers & gt ;
& lt ; Layer & gt ;
& lt ; type & gt ; Scenery & lt ; / type & gt ;
& lt ; name & gt ; Scenery 01 - 4 background & lt ; / name & gt ;
& lt ; images & gt ;
& lt ; Image & gt ;
& lt ; type & gt ; Whole & lt ; / type & gt ;
& lt ; name & gt ; Scenery Tree . png & lt ; / name & gt ;
& lt ; x & gt ; 25 & lt ; / x & gt ;
& lt ; y & gt ; 63 & lt ; / y & gt ;
& lt ; / Image & gt ;
& lt ; / images & gt ;
& lt ; / Layer & gt ;
. . .
& lt ; / layers & gt ;
& lt ; / HogScene & gt ;

So our exporter script will capture these simple XML elements during export and when it’s all completed write that out to a file.

因此,我们的导出器脚本将在导出过程中捕获这些简单的XML元素,并在全部完成后将其写到文件中。

In our exporter script, we start off with creating some file scope variables that we need for convenience and then call the “main” function which is defined right after that. The basic way this works is that when you run your script it’s evaluated and executed from top to bottom. You find examples on the internet where some code is contained in functions and some is not, and I find this a bit confusing. So I put everything into functions and call the entry point explicitly as a matter of convention.

在我们的导出器脚本中,我们首先创建一些为方便起见而需要的文件作用域变量,然后调用紧随其后定义的“ main”函数。 这种工作方式的基本方法是,当您运行脚本时,脚本将自上而下进行评估和执行。 在Internet上可以找到一些示例,其中一些代码包含在函数中,而有些则不包含在函数中,我发现这有些令人困惑。 因此,按照惯例,我将所有内容都放入函数中并显式调用入口点。

Inside the main function, there’s a couple of interesting parts. The first one is the line:

在主要功能内,有几个有趣的部分。 第一个是该行:

duppedPsd = app.activeDocument.duplicate(); duppedPsd = app.activeDocument.duplicate();

1

duppedPsd = app.activeDocument.duplicate();

1

duppedPsd = app . activeDocument . duplicate ( ) ;

Duplicating the active document is highly recommended for two reasons. The first is that in Photoshop if you touch anything in a document, even expanding or collapsing a group, it causes the document to be marked as dirty. So if we didn’t duplicate the active document, the process of exporting would always cause it to be marked as dirty. When the artist quits Photoshop or closes the document they will be prompted to save, and they may not remember if they made changes to the document or not and will likely just save it. This will cause the timestamp of the file to be newer than the exported files, and once checked into version control will be confusing because a designer, producer or programmer won’t know if they need to re-export the file or not. This creates an endless cycle because the file will always be newer than the export. By making a duplicate it will leave the original document in it’s original state, which still may cause a problem, but that’s then a production process problem and not something we’re causing.

强烈建议复制活动文档,这有两个原因。 首先是在Photoshop中,如果您触摸文档中的任何内容,甚至展开或折叠一组,都会导致文档被标记为脏污。 因此,如果我们不复制活动文档,则导出过程将始终导致该文档被标记为脏文件。 当艺术家退出Photoshop或关闭文档时,将提示他们进行保存,并且他们可能不记得他们是否对文档进行过更改,因此很可能只是保存它。 这将导致文件的时间戳比导出的文件新,并且一旦签入版本控制,就会使设计人员,生产者或程序员不知道是否需要重新导出文件,这会造成混乱。 这将导致一个无休止的循环,因为文件将始终比导出文件新。 通过复制,它将使原始文档保持其原始状态,这仍然可能会引起问题,但这是生产过程中的问题,而不是我们所引起的。

The second reason for duplicating the document is simply that things will go wrong while creating your own exporters, and when things go wrong it will leave your document in an unknown state. So by duplicating it we always have our untouched master copy to work with.

复制文档的第二个原因是,在创建自己的导出器时会出错,而在出错时会导致文档处于未知状态。 因此,通过复制它,我们始终可以使用我们的原始副本。

The next thing of interest in the main function is:

主要功能中的下一件有趣的事情是:

removeAllTopLevelArtLayers(duppedPsd); removeAllTopLevelArtLayers(duppedPsd);

1

removeAllTopLevelArtLayers(duppedPsd);

1

removeAllTopLevelArtLayers ( duppedPsd ) ;

Since we’re using groups as the containers for the things we’re interested in, we can let the artists or designers add art layers that aren’t in any group (“top level”). They can use this for concept or photo references, screen layout guides, or placing placeholder elements like a game HUD. So we clean up the duplicated file by removing these images before processing.

由于我们使用组作为我们感兴趣的事物的容器,因此我们可以让艺术家或设计师添加不在任何组中的艺术层(“顶层”)。 他们可以将其用于概念或图片参考,屏幕布局指南或放置占位符元素(例如游戏HUD)。 因此,我们通过在处理之前删除这些图像来清理重复的文件。

Other than that, the main function calls the export function, then creates and writes the XML file containing our meta-data.

除此之外,main函数调用export函数,然后创建并写入包含我们的元数据的XML文件。

The export function exportLayerSets is a recursive function. A recursive function means it calls itself if needed to “drill down” into our groups to find the lowest level group that is deepest in the scene.

导出函数exportLayerSets是一个递归函数。 递归函数意味着,如果需要,它会调用自身以“深入”到我们的组中,以找到场景中最深的最低级别的组。

function exportLayerSets(obj) { for(var i = obj.layerSets.length-1; 0 < = i; i--) { exportLayerSets(obj.layerSets[i]); } ... } function exportLayerSets(obj) { for(var i = obj.layerSets.length-1; 0 < = i; i--) { exportLayerSets(obj.layerSets[i]); } ... }

1

2
3
4
5
6
7
8

function exportLayerSets(obj)
{
for(var i = obj.layerSets.length-1; 0 < = i; i--)
{
exportLayerSets(obj.layerSets[i]);
}
...
}

1

2
3
4
5
6
7
8

function exportLayerSets ( obj )
{
for ( var i = obj . layerSets . length - 1 ; 0 & lt ; = i ; i -- )
{
exportLayerSets ( obj . layerSets [ i ] ) ;
}
. . .
}

It does this by simply looping backwards through the list of groups, and if one of the groups has a group inside it, then it recurses and calls itself again with that group and so on. We loop backwards because in Photoshop the bottom-most layer in the list is the first one and groups and layers higher in the list are drawn on top of lower ones. So we process everything back to front.

它只是通过在组列表中向后循环来完成此操作,如果其中一个组内部有一个组,则它会递归并再次使用该组调用自身,依此类推。 我们向后循环,因为在Photoshop中,列表中的最底层是第一个图层,而列表中较高的组和图层则绘制在较低层的上方。 因此,我们将一切都从头到尾进行处理。

Once we have an actual art layer, we then look at the group name and see if it starts with “item:”, “custom:” (which we used for HUD elements) or else it’s assumed to be scenery.

有了实际的艺术图层后,我们将查看组名称,并查看它是否以“ item:”,“ custom:”(我们用于HUD元素)开头,否则被假定为风景。

if(obj.name.search("item:") >= 0) { ... } else if(obj.name.search("custom:") >= 0) { ... } else // must be a scenery group { ... } if(obj.name.search("item:") >= 0) { ... } else if(obj.name.search("custom:") >= 0) { ... } else // must be a scenery group { ... }

1

2
3
4
5
6
7
8
9
10
11
12

if(obj.name.search("item:") >= 0)
{
...
}
else if(obj.name.search("custom:") >= 0)
{
...
}
else // must be a scenery group
{
...
}

1

2
3
4
5
6
7
8
9
10
11
12

if ( obj . name . search ( "item:" ) & gt ; = 0 )
{
. . .
}
else if ( obj . name . search ( "custom:" ) & gt ; = 0 )
{
. . .
}
else // must be a scenery group
{
. . .
}

For items, we then loop through the art layers, switch on the layer and export whichever of the known types we support.

对于项目,我们然后遍历美术图层,打开图层并导出我们支持的任何已知类型。

// process layers for(var layerIndex = 0; layerIndex < obj.artLayers.length; layerIndex++) { sceneData += "<Image>"; obj.artLayers[layerIndex].visible = true; switch(obj.artLayers[layerIndex].name) { case "hotspot": saveScenePng(...); break; case "obscured": saveScenePng(...); break; case "shadow": saveScenePng(...); break; case "whole": saveScenePng(...); break; } obj.artLayers[layerIndex].visible = false; sceneData += "</Image>"; } // process layers for(var layerIndex = 0; layerIndex < obj.artLayers.length; layerIndex++) { sceneData += "<Image>"; obj.artLayers[layerIndex].visible = true; switch(obj.artLayers[layerIndex].name) { case "hotspot": saveScenePng(...); break; case "obscured": saveScenePng(...); break; case "shadow": saveScenePng(...); break; case "whole": saveScenePng(...); break; } obj.artLayers[layerIndex].visible = false; sceneData += "</Image>"; }

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

// process layers
for(var layerIndex = 0; layerIndex < obj.artLayers.length; layerIndex++)
{
sceneData += "<Image>";
obj.artLayers[layerIndex].visible = true;
switch(obj.artLayers[layerIndex].name)
{
case "hotspot":
saveScenePng(...);
break;
case "obscured":
saveScenePng(...);
break;
case "shadow":
saveScenePng(...);
break;
case "whole":
saveScenePng(...);
break;
}
obj.artLayers[layerIndex].visible = false;
sceneData += "</Image>";
}

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

// process layers
for ( var layerIndex = 0 ; layerIndex & lt ; obj . artLayers . length ; layerIndex ++ )
{
sceneData += "<Image>" ;
obj . artLayers [ layerIndex ] . visible = true ;
switch ( obj . artLayers [ layerIndex ] . name )
{
case "hotspot" :
saveScenePng ( . . . ) ;
break ;
case "obscured" :
saveScenePng ( . . . ) ;
break ;
case "shadow" :
saveScenePng ( . . . ) ;
break ;
case "whole" :
saveScenePng ( . . . ) ;
break ;
}
obj . artLayers [ layerIndex ] . visible = false ;
sceneData += "</Image>" ;
}

When we find something we want to save, we call the function to save the image as a PNG file. This function collapses the image and trims the left and top in order to determine the X and Y coordinates of the image. Then we trim off the right and bottom and save it out, and capture the meta-data into the XML string.

当我们找到要保存的内容时,我们调用函数将图像另存为PNG文件。 此功能可折叠图像并修剪左侧和顶部,以便确定图像的X和Y坐标。 然后我们修剪掉右边和底部并将其保存下来,然后将元数据捕获到XML字符串中。

function saveScenePng(psd, imageType, fileName) { // we should now have a single art layer if all went well psd.mergeVisibleLayers(); // figure out where the top-left corner is so it can be exported // into the scene file for placement in game // capture current size var height = psd.height.value; var width = psd.width.value; var top = psd.height.value; var left = psd.width.value; // trim off the top and left psd.trim(TrimType.TRANSPARENT, true, true, false, false); // the difference between original and trimmed is the amount of offset top -= psd.height.value; left -= psd.width.value; // trim the right and bottom psd.trim(TrimType.TRANSPARENT); // find center top += (psd.height.value / 2) left += (psd.width.value / 2) // unity needs center of image, not top left top = -(top - (height/2)); left -= (width/2); // save the image var pngFile = new File(destinationFolder + "/" + fileName + ".png"); var pngSaveOptions = new PNGSaveOptions(); psd.saveAs(pngFile, pngSaveOptions, true, Extension.LOWERCASE); psd.close(SaveOptions.DONOTSAVECHANGES); // save the scene data sceneData += ("<type>" + imageType + "</type> <name>" + fileName + ".png</name> <x>" + left + "</x><y>" + top + "</y>"); } function saveScenePng(psd, imageType, fileName) { // we should now have a single art layer if all went well psd.mergeVisibleLayers(); // figure out where the top-left corner is so it can be exported // into the scene file for placement in game // capture current size var height = psd.height.value; var width = psd.width.value; var top = psd.height.value; var left = psd.width.value; // trim off the top and left psd.trim(TrimType.TRANSPARENT, true, true, false, false); // the difference between original and trimmed is the amount of offset top -= psd.height.value; left -= psd.width.value; // trim the right and bottom psd.trim(TrimType.TRANSPARENT); // find center top += (psd.height.value / 2) left += (psd.width.value / 2) // unity needs center of image, not top left top = -(top - (height/2)); left -= (width/2); // save the image var pngFile = new File(destinationFolder + "/" + fileName + ".png"); var pngSaveOptions = new PNGSaveOptions(); psd.saveAs(pngFile, pngSaveOptions, true, Extension.LOWERCASE); psd.close(SaveOptions.DONOTSAVECHANGES); // save the scene data sceneData += ("<type>" + imageType + "</type> <name>" + fileName + ".png</name> <x>" + left + "</x><y>" + top + "</y>"); }

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

function saveScenePng(psd, imageType, fileName)
{
// we should now have a single art layer if all went well
psd.mergeVisibleLayers();
// figure out where the top-left corner is so it can be exported
// into the scene file for placement in game
// capture current size
var height = psd.height.value;
var width = psd.width.value;
var top = psd.height.value;
var left = psd.width.value;
// trim off the top and left
psd.trim(TrimType.TRANSPARENT, true, true, false, false);
// the difference between original and trimmed is the amount of offset
top -= psd.height.value;
left -= psd.width.value;
// trim the right and bottom
psd.trim(TrimType.TRANSPARENT);
// find center
top += (psd.height.value / 2)
left += (psd.width.value / 2)
// unity needs center of image, not top left
top = -(top - (height/2));
left -= (width/2);
// save the image
var pngFile = new File(destinationFolder + "/" + fileName + ".png");
var pngSaveOptions = new PNGSaveOptions();
psd.saveAs(pngFile, pngSaveOptions, true, Extension.LOWERCASE);
psd.close(SaveOptions.DONOTSAVECHANGES);
// save the scene data
sceneData += ("<type>" + imageType + "</type>
<name>" + fileName + ".png</name>
<x>" + left + "</x><y>" + top + "</y>");
}

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

function saveScenePng ( psd , imageType , fileName )
{
// we should now have a single art layer if all went well
psd . mergeVisibleLayers ( ) ;
// figure out where the top-left corner is so it can be exported
// into the scene file for placement in game
// capture current size
var height = psd . height . value ;
var width = psd . width . value ;
var top = psd . height . value ;
var left = psd . width . value ;
// trim off the top and left
psd . trim ( TrimType . TRANSPARENT , true , true , false , false ) ;
// the difference between original and trimmed is the amount of offset
top -= psd . height . value ;
left -= psd . width . value ;
// trim the right and bottom
psd . trim ( TrimType . TRANSPARENT ) ;
// find center
top += ( psd . height . value / 2 )
left += ( psd . width . value / 2 )
// unity needs center of image, not top left
top = - ( top - ( height / 2 ) ) ;
left -= ( width / 2 ) ;
// save the image
var pngFile = new File ( destinationFolder + "/" + fileName + ".png" ) ;
var pngSaveOptions = new PNGSaveOptions ( ) ;
psd . saveAs ( pngFile , pngSaveOptions , true , Extension . LOWERCASE ) ;
psd . close ( SaveOptions . DONOTSAVECHANGES ) ;
// save the scene data
sceneData += ( "<type>" + imageType + "</type>
<name>" + fileName + ".png</name>
<x>" + left + "</x><y>" + top + "</y>" ) ;
}

When we run the exporter on the file, we should end up with a directly of cropped PNG files plus an XML file with the same base filename as the original document such as that shown below.

当我们在文件上运行导出器时,我们应该直接得到裁剪的PNG文件以及一个与原始文档具有相同基本文件名的XML文件,如下所示。

Now that we successfully exported all our images and meta-data, we move to the next application in our workflow which is Unity and get the files imported. I’ll cover this in my next blog post. Thanks!

现在我们已经成功导出了所有图像和元数据,我们将移至工作流中的下一个应用程序,即Unity,并导入文件。 我将在下一篇博客文章中介绍。 谢谢!

You can download a Unity package with all the files for this tutorial.

您可以下载带有本教程所有文件的Unity软件包。

Update: Part II

更新: 第二部分

翻译自: https://blogs.unity3d.com/2013/05/17/a-good-workflow-to-smoothly-import-2d-content-into-unity-part-i-authoring-and-exporting/

unity2d导出apk

unity2d导出apk_顺利将2D内容导入Unity的良好工作流程,第I部分:创作和导出相关推荐

  1. 顺利将2D内容导入Unity的良好工作流程,第二部分:导入

    Recently I wrote about authoring your 2D content in Photoshop and then setting it up for exporting. ...

  2. 华测P550数据导入睿铂Skyscanner工作流程

    华测P550数据导入睿铂Skyscanner工作流程 本文主要解决P550飞行结束之后,飞手通过P550得到飞机的原始POS数据,并通过睿铂Skyscanner整理出后处理建模软件所需要的影像,和影像 ...

  3. 模型相关:SolidWorks创建Cube模型,在3DMax中给不同面添加不同颜色,导出自带纹理的FBX至Unity

    项目场景: 为了实现将SolidWorks创建的Cube模型,在3DMax中添加不同颜色,导出带有纹理的FBX,导入Unity后不需要再修改其颜色纹理 实现方法: 1.在SolidWorks中创建形如 ...

  4. 3dmax顶点动画导入unity_3DMAX点缓存的动画怎么导入unity呀?

    (引自别处,希望有用)用Max导出Unity3D使用的FBX文件流程注解从max导出FBX到Unity,以下环节需要特别注意.1,单位设置很多人在建模,动画的时候,默认的maxsystemunit是设 ...

  5. 3ds Max 烘焙模型导入 Unity 3D 步骤

    3ds Max 建的宿舍模型导入 Unity 3D 步骤总结. 一. 可以将模型直接导出到unity 3d Assets文件夹中,这样不需要在unity中手动导入模型 二. 烘焙模型后导出步骤如下: ...

  6. 仓库管理|电子公司仓库管理工作内容及工作流程

    ​电子公司仓库管理工作人员,要懂得仓库管理的基本知识点,专业的仓库管理技能及相关的操作技巧,才能更好的展开仓管工作.那么,电子公司仓库管理工作内容是什么?仓库管理工作流程是什么呢? 一.验货 1.电子 ...

  7. oracle导出表中某天数据命令,Oracle数据库使用命令行导入导出数据表及数据内容(本地、远程)...

    一.本机导入导出 1.数据库导出命令,导出用户userA下的表结构和数据: exp 用户名/密码@服务名 owner=用户名 file=导出文件指定存储路径 full=y 2.数据库导入命令,将use ...

  8. Spring MVC 实现Excel的导入导出功能(2:Excel的导入优化和Excel的导出)

    Excel的导入V2优化版 有些时候文件上传这一步骤由前端来处理,只将上传后的 URL 传输给后端(可以参考上一文中的图片上传功能),也就是导入请求中并不会直接处理 MultipartFile 对象, ...

  9. 用java查询excel表格_如何把java查询出的内容导入到excel表格

    展开全部 java查询出的内容导入到excel表格 /**导出数据为XLS格式 * @param fos * @param bo */ public void writeExcelBo(FileOut ...

最新文章

  1. Python requests 多线程抓取 出现HTTPConnectionPool Max retires exceeded异常
  2. 对接接口文档_接口自动化测试框架设计思路
  3. Unix整理笔记-vi简介-里程碑M8
  4. java虚拟机起不来的原因,JVM理解其实并不难!
  5. ways for partner
  6. poj 3920(简单dp)
  7. 删除已有的 HTML 元素
  8. mysql连接参数配置
  9. Vue学习笔记(一)—— 常用特性
  10. mysql的索引缺点_「缺点有哪些」数据库索引是什么 有什么优缺点 - seo实验室
  11. 新概念模拟电路——初识晶体管
  12. 宽带远程服务器无响应,宽带拨号上网服务器无响应是解决方法(图文)
  13. 易游网络验证好不好?如何正确的使用易游验证
  14. vue.js 密码加密_几种常见的密码简介
  15. 不能不用也不可乱用的标准化和归一化处理
  16. 北京大学计算机学院的院徽,院徽、logo和学院口号征集活动
  17. 语音专题第三讲,语言模型
  18. Mysql之查看数据库版本
  19. 深度学习目前主要有哪些研究方向?
  20. 再论EOS的CPU租赁利率,无风险套利?

热门文章

  1. miui怎么用第三方图标包_MIUI上线全新动态图标主题,锁屏功能超级多且好用
  2. 嵌入式开发--RS-485通讯的问题
  3. 使用python进行股票分析
  4. 怪物农场2修改日志1 - FirstBlood
  5. 如何在R中绘制热力地图
  6. adb删除了系统应用能恢复吗_手机删除照片能恢复吗?隐藏小妙招你知道吗?
  7. Python中TKinter的输入框如何绑定回车键事件调用功能函数
  8. 服务计算作业三——CLI 命令行实用程序开发基础
  9. 2022年电动车行业市场分析
  10. 理工科题库创建与Office Word数据交换笔记