分类
开发日志

ProjectNull开发日志#1——Aseprite导入工具

在Unity里做像素游戏,导入精灵素材的时候,每个都需要修改他的像素单位系数,图像缩放模式要改成Point,图集锚点啊balababala的。量多起来的话,无聊的要死,需要找个工具来代替我做这种工作。还好搜到一篇知乎专栏发现了MetaSprite,而且有特别有意思的meta功能,也就是用图层来标注一些元数据,可以标注精灵锚点或者是组件参数并在动画剪辑中表现出来。

我目前需求是一个又多个分块组成的像素小人,希望有工具可以帮我把导入预制体的工作也完成掉,显然需要魔改这个工具。

MetaSpriteEx

MetaSpriteEx是一个可以将Aseprite工具导入到Unity中的工具,这个工具派生自开源的WeAthFoLD/MetaSprite。与原来的工具相比,修改了生成图集的逻辑,修改了元数据语法用法,增加了生成预制体的功能来满足我目前的需求。这里想记下各个自己修改的想法,工具的使用都已经写在了wiki里了。

生成图集的逻辑

原本的MetaSprite只能生成一张主图集,只能通过元数据图层的Sub(“layerName”)来新建多个子图层,无论ase中的图层如何安排,除开子图层,其余的都会被整合到同一张图集里。

个人认为辑时多份块可以方便制作动画
通过标记子图像来分图像,并生成一张新的图集

我目前的需求是至少把一个角色的手部拆分,并且允许自由旋转。以对美术提供最懒的空间的想法,就是分块,用多个层包围着身体。

在美术编辑的情况下分块可以让动画编辑过程更加简单,所以必须保留整合多图层的能力。而游戏编辑时,以可操控对象分块可以进一步减少点美术需求。除了手部,我想把这个下半身腿部动作也分开控制,整体看上去可能和骨骼的思路有点类似。

综上,我决定图层的逻辑这样安排:

  • 图层组作为一个集合,组内的直属图层将会被合并成一个图集,并且会在Unity场景中生成一个节点(后面会讲),图集名字会以图层组的名字命名
  • 去掉繁琐的元数据参数,不需要指定目标名字,放在图层组内就会对这个集合对象生效。但似乎牺牲了些表示能力,比如标注的transform应该不被别的图层给挡住视野,不利于编辑,我应该把目标参数功能加回去
  • 类比注释图层以忽略的功能,我也添加了注释图层组的功能,注释掉的图层组的直属子图层会被继承到他的上一级图层组上去,所以在没有图层组的根部,它在代码里的数据结构也被视为存在一个根图层组
  • 图层的读取顺序是从下到上,图层层级从父到子,类似DFS。我需要有一个跨层级的,但是想在一个gameObject来控制。比如左右手握着武器,左右手的图像在最底下和最上面,我想在引擎中直接控制它的旋转。 这时候不能把他们都拉进同一个图层组,因为会破环顺序,不符合预期。 于是添加了Name=>Target这样的语法,读取到这个图层时候会把图层组作为Target的子图层组,而Target会取代原来图层组的关系位置。

生成预制体

生成预制体主要是懒得组块块,逐个重命名也很无聊der,又不知道有什么快捷键,次次都要用鼠标点,这样子。

除了生成GameObject,还有往有图像的节点都添加Sprite Render组件,并且根据设置文件指定精灵所在的排序组和精灵排序。

如果直接修改工具生成的预制体,当美术有修改后重新导入的话可能会把添加的修改给去掉。这里需要用到2019的新功能,把修改过的根节点直接拖到Assets里:

Prefab Variant

创建一个像是分支一样的预制体,它继承于原来的预制体,根据源的变化而变。在这个预制体上挂载更多的组件,这样在不更改已有的节点名称的情况下,新增或者更新参数就不会丢弃掉一些工作进度。

记录下一些自己以前没有用过的关键代码

  • 储存预制体的API函数 ,根据节点名字自带替换覆盖能力,不需要管uid的问题PrefabUtility.SaveAsPrefabAssetAndConnect
  • 调出Layer&Tag面板
    Selection.objects = AssetDatabase.LoadAllAssetsAtPath("ProjectSettings/TagManager.asset");
  • 获取Layer时候,最好使用Name提取给用户,然后用NameToID来得到ID保存。
string[] opetions = SortingLayer.layers.Select(it => it.name).ToArray();
index = EGL.Popup("Sort In Layer", index, opetions);
settings.spritesSortInLayer = SortingLayer.NameToID(opetions[index]);
var path = layer.group.parent.Path;
EditorCurveBinding xb = AnimationUtility.GetCurveBindings(clip).Where(it => it.path == path && it.propertyName == "m_LocalPosition.x").ToArray()[0];
EditorCurveBinding yb = AnimationUtility.GetCurveBindings(clip).Where(it => it.path == path && it.propertyName == "m_LocalPosition.y").ToArray()[0];

var x = AnimationUtility.GetEditorCurve(clip, xb).keys.Where(it => Mathf.Approximately(it.time, t)).ToArray()[0].value;
var y = AnimationUtility.GetEditorCurve(clip, yb).keys.Where(it => Mathf.Approximately(it.time, t)).ToArray()[0].value;

pos -= new Vector2(x, y);

杂项问题

Per Pixel Units 与 snap settings

上网搜索像素游戏项目中关于这个设置的相关建议,给出的建议值是32的PPU,这里前三项建议是1/32,即0.34375。然后我看着Unity上自带的网格,WDNMD不对齐!!!难受。

我这里建议使用PPU为100,三轴的snap为0.01,这样可以Reset Transform然后按着ctrl移动到精准的位置。

旋转手臂角度计算

算错就会这样
白色两角大小相等

强制完美像素旋转手臂的效果

用手绘45°补充旋转手臂

这是在像素爱好群里茶茶大佬提的意见,挺有意思的,保留。

Undo功能

在自定义的编辑器里,如果没有撤销功能的话,会严重影响体验

我在这个编辑器发现它是使用EditorUtility.SetDirty来做Undo,但是这个一页的API说明页说与建议使用Undo.RecordObject,我把它替换了

类集合

System.Reflection.Assembly.GetExecutingAssembly().GetTypes();

编辑器通过这个来获得程序里类的列表,作者用于查找所有类型的元数据解析器。

需要探索Lua Unity

呜呜呜