How to modify a sprite asset's animation2d bone and weights through editor scripts
Greetings unity communitiy!
I am using the official animation2d package. I need to rig a sprite's bone structure through an editor script. So I need to persist the asset modifications to the project files.
I have managed to read the sprite's mesh, bones, and weight data perfectly fine through the SpriteDataAccessExtensions extension methods. I have also managed to compose the desired bone list, and corresponding weight VertexAttribute lists.
However, writing those attributes to the sprite asset through sprite.SetBones() and sprite.SetVertexAttribute() doesn't seem to be persisted to the asset. After executing the editor script, the original sprite is unaltered.
I assume the problem may be I am trying to write an in-memory instance of the asset (instead of the asset itself), as I'm accessing the sprite through a serialized property in a monobehaviour with an editor script.
Pressing the button calls the following extension method on the sprite serialized on the monobehaviour.
public static class SpriteRiggingExtensions
{ //Extension method modifying the Sprite class
public static void BonesFromVertexList (this Sprite _this, string boneBaseName)
{
//iterate through the vertex list creating corresponding bones and weights
NativeSlice<Vector3> vertexList = _this.GetVertexAttribute<Vector3>(VertexAttribute.Position);
//create lists to hold created bones and weights
SpriteBone[] boneList = new SpriteBone[vertexList.Length];
BoneWeight[] weightList = new BoneWeight[vertexList.Length];
//loop over vertex list
for (int i = 0, iLimit = vertexList.Length; i < iLimit; i++)
{
boneList[i] = CreateBoneForVertex(i, boneBaseName, vertexList[i]);
weightList[i] = CreateSimpleVertexWeight(i);
}
//apply the lists of bones and weights to the sprite
_this.SetBones(boneList);
_this.SetVertexAttribute<BoneWeight>(VertexAttribute.BlendWeight, new NativeArray<BoneWeight>(weightList, Allocator.Temp));
AssetDatabase.CreateAsset(_this, "Assets/Tmp/GeneratedAss.asset");
}
private static SpriteBone CreateBoneForVertex (/**/) {/* Create a bone */}
private static BoneWeight CreateSimpleVertexWeight (/**/) {/* Create weight for bone & vertex */}
}
So I need to persist the changes to the asset. Using AssetDatabase.CreateAsset() won't let me write a new asset arguing the same asset already exists (even if writing to a different path).
I found no other apparent way to use AssetDatabase to alter an existing asset. So, How can I persist the sprite asset changes? Please, and thank you very much.
Answer by mephistoII666 · Dec 09, 2020 at 02:52 AM
Update: In the end I did not find how to do this within unity.
Since unity's .meta files are serialized in plain YAML (if serialization configured as plain text), I could bypass this by processing the .meta files through an external python script.
There I could read all the properties related to the sprite's asset serialization, such as the skinning rig, and re-write it as necessary. Then simply loading unity with the externally-altered assets.
At first it sounds daunting and dangerous messing with unity's files, but since YAML is human readable it's easy to get a hold of it. Now I've even gotten used to make small tweaks to my assets by simply altering the .meta files with sublime text. It's easy as long as you are careful to respect tabulation, as YAML formatting is tabulation-based.
I could try that, would you be able to explain me or give me an example ? I would need to modify the same things. Bones [] and weight [] to be able to have persistent changes. I made a small tool to override parts of a generic white psb, and when I make a new skin with, let's say a new shirt, the moment I set the bones with the bones and weight it works, but not when I press play. So yeah if you could give me an example of what you did, it would be really appreciated =)
The specifics of meta files vary depending on asset type. In this case (Sprites with animation2d skinning properties) you need bone data and mesh vertex weight data.
The yaml format for the corresponding meta files is the following: [comments in square brackets]
TextureImporter:
[...]
spriteSheet:
bones:
- length: [float > bone length in units]
position: { x: [float], y: [float], z: [float] }
rotation: { x: [float], y: [float], z: [float], w: [float] }
parentId: [int > index of the parent bone. The root bone's parentId is -1]
[A bone's index is its position within this list]
- [...bone...]
- [...bone...]
[...]
vertices:
- {x: [float], y: [float]}
- [...vertex...]
- [...vertex...]
[...]
indices: [this defines the mesh's triangles. it should be a ushort array as in Sprite.triangles containing a multiple of 3 entries. each group corresponds to the indexes of each vertex of a single triangle]
[...]
weights: [a list containing as many entries as the vertices list. each entry defines weight for the vertex with the same index pointing to up to 4 bones and no more]
- weight[0]: [float, defining the weight value for the first bone]
weight[1]: [float, weight for 2nd bone]
weight[2]: [float, weight for 3rd bone]
weight[3]: [float, weight for 4th bone]
boneIndex[0]: [int, index of the first bone]
boneIndex[1]: [int, 2nd bone index]
boneIndex[2]: [int, 3nd bone index]
boneIndex[3]: [int, 4nd bone index]
- [...weights...]
- [...weights...]
You could alter this data anyway you want, even by hand. I just chose python as it offers native yaml support with full compliance. - Guide - PyYA$$anonymous$$L wiki
Please upvote if it helps you, and do let me know if you figure something else within unity!
Example of my solution: I had to create a bone for each vertex and correspondingly generate vertex weights
#processes an individual file
def processFile (filePath):
try:
print ("* Processing \"" + filePath + "\"");
#check if file exists
if (not os.path.isfile(filePath)):
print ("! Nonexistent file")
raise FileNotFoundError
#move original file to backup location
originalFilePath = moveToBackup (filePath)
#read the file
with open(originalFilePath) as file:
fileContents = yaml.full_load(file)
print (" File read")
#print (fileContents['TextureImporter']['spriteSheet']['vertices'])
#process the contents
fileContents['TextureImporter']['spriteSheet']['bones'] = boneListFromVertexList(fileContents['TextureImporter']['spriteSheet']['vertices'])
fileContents['TextureImporter']['spriteSheet']['weights'] = weightListFromVertexList(fileContents['TextureImporter']['spriteSheet']['vertices'])
#re-write the yaml to the original file path
with open(filePath, 'w') as file:
yaml.dump(fileContents, file)
print (" File written")
#return true if success, false if failed
except:
print ("! Failure")
print (" ", sys.exc_info()[0])
print (" ", sys.exc_info()[1])
return False
else:
print ("> Success")
return True
Nice ! I'll try that today ! Do you call the python script from you c# script ? What did you use to call the python script ? Do you need a plugin or something ?
I don't know python that much...like where does your boneListFromVertexList comes from ? Here is my email ... danstradh@gmail.com
Answer by Dan_Stradh · Dec 09, 2020 at 12:57 AM
Have you ever found out how to make it persist ?? Running into the same problem...
I wrote an accepted answer. Feel free to remove this answer to prevent clutter :)
Edit: if you are in need of further detail contact me or something and I can develop, but the specifics of editing a .meta file depend too much on what are you trying to do to write a full guide here
Your answer
Follow this Question
Related Questions
Adding sprites to same layer as bone in 2D animation rig 1 Answer
Copy Bind Poses from Sprite in Runitme 0 Answers
Animation is overriding sprite 0 Answers
Precise animation issue with 2D sprites 0 Answers