[转载]一个高效简洁的Aseprite to Unity导入工具

原文链接 https://zhuanlan.zhihu.com/p/28644268 

期待原作者上传至AssetStore.

今天,我的第一个 Unity 插件 MetaSprite 正式发布了它的 0.1 版本,所以想趁这个机会写一篇文章做下记录。

MetaSprite 是一个高效、灵活的 Aseprite to Unity 导入插件。它可以把像素动画软件 Aseprite 生成的 .ase 文件导入 Unity,作为 Mecanim 动画系统的 Animation Clip 和 Animator Controller 使用。

MetaSprite 不是第一个完成这件事的插件,但相较它的先来者(talecrafter/AnimationImporter),有以下的改进点和新功能:

  • 不依赖外部的 Aseprite 可执行程序以导入
  • 内建 ase 文件的 parser,因此导入速度非常的快,可以达到前者的10倍以上
  • 在创建图集(atlas)的时候会略去空白区域,大大提高了空间利用率
  • 更简单的工作流,通过右键菜单来实现导入
  • 导入设置单独存储,不需要在一个全局配置上改来改去
  • 完善的metadata(元数据)支持

项目的源代码在这里 WeAthFoLD/MetaSprite。接下来,我想简单说一说在开发这个插件过程中的一些想法和心得。

 

1. 动机

我在过去的一段时间一直和同学做一个像素风格的横版动作游戏。在多次试验试错之后,我们最终确定了以像素帧动画为核心表现形式,基于 Aseprite 绘制的帧动画进行导入的工作流。很长一段时间,我们用的是手动导入,通过 Aseprite 导出图集,然后在 Unity 里手动切图、设置锚点,再手动创建关键帧。这样的重复工作占用了大量的时间,导致动画编辑的效率一直上不去。现在想想,当时可谓是我们动画编辑的石器时代(雾)。

暑假,我在上海的一家独立游戏工作室实习。非常凑巧的是,这里同样使用了基于 Aseprite 的一套像素动画导入的工作流。但是这里的引擎大大写了一个非常棒的 ase 导入器(跑在基于lua的自研引擎上),把 ase 文件放在项目中就可以直接播放使用。

这样顺畅的工作流让我恍然大悟:如果在 Unity 里使用自动化工具进行 ase 文件的导入,那么会大大降低重复工作量,从而提高工作速度。我就此开始了 ase 导入工作流的探索。

初步的搜寻以后,我找到了 talecrafter/AnimationImporter 这一导入工具。初步使用起来感觉效果还行,但是有一些问题很大的影响了体验:

  1. 需要外部的 Aseprite 可执行文件来运行。这个工具通过调用 Aseprite 的 CLI-mode 来获取 ase 文件的 metadata 以及生成 atlas。这个过程相当消耗时间 (一个30帧左右的 ase 文件,在我的电脑上导入要5秒以上);
  2. 导入操作不符合直觉。我需要打开一个专门的 Animation Importer 窗口,然后把文件拖进去实现导入。并且导入设置是全局的,每次导入都要费力气把设置改来改去,可以预想到会消耗很多的时间;
  3. Aseprite 自己的 atlas 生成不是很给力,有大量留白。理想的状况是没有内容的区域全部忽略掉,让里面的 sprite 紧紧的贴在一起。

除此之外,我们还有一个最大的痛点没有被满足:通过 ase 文件来操纵一些非图像的数据。一个最典型的例子就是攻击判定框:因为我们是对反馈要求比较严格的动作游戏,碰撞框需要和动画比较好的同步,才能带来比较好的体验。对此,最好的方式就是美术直接在帧动画中绘制碰撞框。因此,我们需要某种方式来区别 ase 文件中的内容层和这种功能层,并对应的去解析这些层里的数据。

综上,现有的工具完全无法满足我对一个完善的 Aseprite 导入器的要ye求xin。自己造一个轮子的想法应运而生。

 

2. 开发小记

作为 proof-of-concept,我首先做的事是沿用 AnimationImporter 基本的工作流,并在上面加入自己的一些想法。也就是说核心还是调用 Aseprite 可执行文件,读取数据和生成,但会慢慢换掉其中的各个流程。

首先从最想做的 metadata 支持入手,设计了一些最基本的 metadata 规则,包括:

  1. 可以用 // 注释掉层或者动画片段(这里把frameTag译作动画片段,代表动画中的一段时间区间)
  2. 可以在动画片段名字后面用 #loop 让生成的 clip 循环播放
  3. '@' 字符开头的层是 meta层。meta层不会参与到 atlas 的生成,会被对应的 MetaLayerProcessor 读取并进行对应处理,比如说生成操纵 BoxCollider2D 的动画轨。

实现上,基本就是通过读取 Aseprite CLI 生成的 json 文件到一个 ASEMetadata 的数据结构中,然后再根据 metadata 进行接下来的 atlas生成 → clip生成 → controller生成 → 处理 meta layer。

工作流方面,实现了一个单独的导入配置文件作为 ScriptableObject 存在项目中。多个 ase 文件可以复用同一个导入配置,解决了之前在全局配置上改来改去的问题。导入的方式也改成了选择要导入的文件 -> 右键导入,便利了许多。

 

至此,这个项目的工作方式已经基本成型。我对新插件的工作流相当满意,除了一个严重的问题:慢。调用 Aseprite CLI 会带来不可避免的性能瓶颈。单生成一个 30 帧的 Atlas 就花了3秒左右的时间,Unity 导入这个 atlas 又要花去一秒多的时间。如果一个文件里有多个 meta 层的话,每个 meta 层在处理过程中都要单独生成一个 atlas,最后的时间消耗是非常恐怖的。因此,我最终还是下定决心,从头写一个自己的 ase 文件解析器,一劳永逸的解决性能上的问题。

非常幸运的是,aseprite 的官方 repo 里就有文件格式的描述文档(aseprite/aseprite),文件格式也很简单,因此在写 parser 的过程中阻力很小,连带 parser 和一个简单的 atlas generator 在一个周末的时间就写完了。最后的效果也很振奋人心,导入的时间从 5 秒以上起步变到了基本可以忽略不计(1秒以内)。

最后的大致效果可以看下面的演示:

 

 

 

 

3. 公开发布和未来计划

在这个工具做到一半的时候,我们的美术同学就开始怂恿我把这个东西公开出来。我自己也非常的看好这个工具,因为它真的切切实实解决了我们项目工作流的核心痛点。因此,在插件基本成型之后,我选择把它尽快公开,一方面希望这个工具可以让更多有和我们同样问题的开发者的需求得到解决,一方面希望能得到更多改善这个工具的建议。最终目标:让 MetaSprite 成为最好的 Aseprite to Unity 导入工具!

这个工具在 alpha 阶段会一直免费并且 MIT 开源,持续的对这个插件进行改进。毕竟作为一个刚成型的插件,还有很多重要的feature没有完工。

如果你和我一样也是一个 Aseprite 使用者和 Unity 开发者,也希望你能受益于这个工具,并且对这个工具提出各方面的批评建议。You will be my biggest motivation :-)

 

转载于:https://www.cnblogs.com/ironbear/p/8176959.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 可以这样做:1. 使用Unity的AssetDatabase类,使用AssetDatabase.LoadAssetAtPath方法来加载本地图片。2. 将图片赋值给Texture2D对象。3. 使用Texture2D.EncodeToPNG()方法来编码图片,将编码结果赋值给byte[]数组。4. 使用File.WriteAllBytes()方法来将byte[]数组保存到本地。 ### 回答2: 在Unity导入本地图片并保存,可以通过以下脚本实现: ```csharp using UnityEngine; using UnityEngine.UI; using UnityEditor; public class ImportAndSaveImage : MonoBehaviour { public Image image; // 用于显示导入的图片 public string imagePath; // 本地图片的路径 public void ImportImage() { // 打开文件选择器 imagePath = EditorUtility.OpenFilePanel("选择图片", "", "png,jpg,jpeg"); // 检查是否选择了图片 if(!string.IsNullOrEmpty(imagePath)) { // 读取图片数据 byte[] imageData = System.IO.File.ReadAllBytes(imagePath); Texture2D texture = new Texture2D(2, 2); texture.LoadImage(imageData); // 将图片数据设置给Image组件 Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f)); image.sprite = sprite; } } public void SaveImage() { // 检查是否已导入图片 if (image.sprite != null) { // 将图片数据保存到本地 byte[] imageData = image.sprite.texture.EncodeToPNG(); // 选择保存路径 string savePath = EditorUtility.SaveFilePanel("保存图片", "", "image", "png"); // 检查是否选择了保存路径 if (!string.IsNullOrEmpty(savePath)) { // 将图片数据写入到指定路径 System.IO.File.WriteAllBytes(savePath, imageData); Debug.Log("图片保存成功"); } } else { Debug.Log("请先导入图片"); } } } ``` 上述脚本包含两个方法,`ImportImage`用于导入本地图片并显示在Image组件上,`SaveImage`用于将导入的图片保存到本地。需要注意的是,上述脚本使用了Unity的`EditorUtility`类,所以只能在Unity编辑器中使用,无法在运行时使用。 ### 回答3: 在Unity中,要导入本地图片并保存,可以通过以下方式实现: 1. 首先,你需要创建一个脚本,可以命名为"ImportAndSaveImage"或者其他你喜欢的名字。在Unity编辑器中,右键点击Assets面板,选择Create -> C# Script,然后将其命名为"ImportAndSaveImage"。 2. 双击打开"ImportAndSaveImage"脚本,并在脚本中输入以下代码: ```csharp using UnityEngine; public class ImportAndSaveImage : MonoBehaviour { public string imagePath; // 存储图片路径 void Start() { ImportImage(); SaveImage(); } // 导入本地图片 void ImportImage() { Texture2D texture = new Texture2D(2, 2); // 创建一个空的Texture2D byte[] imgBytes = System.IO.File.ReadAllBytes(imagePath); // 读取图片字节数据 texture.LoadImage(imgBytes); // 加载图片 GetComponent<Renderer>().material.mainTexture = texture; // 设置图片为材质贴图 } // 保存图片 void SaveImage() { Texture2D texture = (Texture2D)GetComponent<Renderer>().material.mainTexture; // 获取图片贴图 byte[] imgBytes = texture.EncodeToPNG(); // 将图片编码为PNG格式的字节数据 System.IO.File.WriteAllBytes(Application.persistentDataPath + "/savedImage.png", imgBytes); // 保存图片到本地 } } ``` 3. 接下来,在Unity编辑器中的Hierarchy面板中创建一个空的GameObject,并将"ImportAndSaveImage"脚本添加到该GameObject上。 4. 在Project面板中,将你想导入的本地图片拖拽到Unity中,并将图片路径赋值给"imagePath"变量。你可以在"ImportAndSaveImage"脚本的Inspector面板中找到这个变量进行赋值,或者在脚本中直接修改。 5. 点击运行按钮,在Unity场景中,你将看到导入的本地图片被应用于指定的GameObject上,并且该图片将被保存在Unity项目的持久化数据路径中,名为"savedImage.png"。 记得在使用该脚本时,需要确保已经在Unity编辑器或者Unity应用运行时授予相应的文件读写权限。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值