Unity Animator Controller复制产生的冗余依赖

Unity 的 Animator Controller 在游戏中一般用来做动画状态机,存放一个游戏中人物或物体的各种动画,便于程序调用。

由于很多人物的动画状态机结构基本一致,我们在使用时常常直接复制 Animator Controller 或者复制其中的节点到另外一个 Animator Controller 中。

但实际操作中,这样会产生冗余数据和不必要的引用关系。

问题

image-20200730150643361

如图,在导出名为12100的Animator Controller 时,选择包含引用时,会同时导出名为17001的Animator Controller。

然而实际上打开12100,视图里面并没有17001的animation节点,即使把所有节点都删除再保存,重新导出依然有17001的引用。

查找guid

打开17001的meta文件,找到它的guid,然后在12100的Controller文件中搜索,可以发现一个AnimatorStateTransition关联了这个guid。

image-20200730162337228

关联的内容是:

1
2
m_DstState: {fileID: 1102131212858548580, guid: df538bb3c58384d4b83ab1cdbb708854,
type: 2}

改成:

1
m_DstState: {fileID: 0}

这样再保存,引用关系就解除了。

再次导出就没有多余的引用了。

image-20200730163042936

原因

这个问题产生的原因是复制17001,在此基础上制作了12001,因此12001包含了17001的引用。

上面是大概的原因,下面讲一下冗余依赖产生的具体原因。

假如有两个Animator Controller 文件,分别名为a.controllerb.controller

a.controller 中的状态如下:

技术分享

可以看到Attack01指向Attack02。

当拷贝了包含Transitions并且该Transitions的Dst State不存在的Animator State到另一个Animator Controller时,就会出现游离依赖数据。以a.controller为例,查看该文件能够发现Attack01节点包含的Transitions数据:

技术分享

该Transition的Dst State为Attack02。如果我们拷贝Attack01但没有拷贝Attack02b.controller,那么b.controller中就出现了游离依赖数据m_DstState。

并且这个数据我们在视图中看不到,也删不掉,随着拷贝次数增加,冗余数据会越来越多,甚至会造成循环依赖。如果这两个Controller都是AssetBundle的话,就会产生无限依赖加载。

解决办法

手工

不使用直接复制来制作新的Animator Controller ,只通过工具生成空的Animator Controller 。

在遇到冗余引用时,暂时只能找到荣誉动画的meta,找到guid然后再有冗余的Controller中查找删除。

如果是引用了多余的Animator Controller:

1
m_DstState: {fileID: 0}

如果是引用了多余的Animation:

1
m_Motion: {fileID: 0}

脚本

通过之前的游离依赖数据分析可知他们的共性为m_DstState项包含了所依赖的.controller文件的guid,因此我们可以通过读取.controller文件将这些游离依赖数据删除。

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
using System.IO;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditor.Animations;

public class AnimatorChecker
{
[MenuItem("美术工具/Animator Controller/清除冗余引用数据")]
private static void CorrectData()
{
string block = null;
bool isDependOtherAnimatorController = false;
AnimatorController animatorController = Selection.activeObject as AnimatorController;
string filePathName = Path.GetFullPath(AssetDatabase.GetAssetPath(animatorController));
string tempFilePathName = Application.dataPath + "/" + System.DateTime.Now.Ticks.ToString() + ".controller";
using (StreamWriter writer = File.CreateText(tempFilePathName))
{
using (StreamReader reader = File.OpenText(filePathName))
{
string content;
while (null != (content = reader.ReadLine()))
{
if (content.StartsWith("--- !u"))
{
if (!string.IsNullOrEmpty(block))
writer.Write(block);

block = content + System.Environment.NewLine;
isDependOtherAnimatorController = false;
}
else
{
if (isDependOtherAnimatorController)
continue;

if (string.IsNullOrEmpty(block))
writer.WriteLine(content);
else
{
block += (content + System.Environment.NewLine);

// 检测是否依赖其他的Animator Controller
if (content.Contains("m_DstState:") && content.Contains("guid"))
{
block = null;
isDependOtherAnimatorController = true;
}
}
}
}

// 写入最后的数据
if (!string.IsNullOrEmpty(block))
writer.Write(block);
}
}

FileUtil.ReplaceFile(tempFilePathName, filePathName);
AssetDatabase.Refresh();
}

[MenuItem("美术工具/Animator Controller/查找Animator Controller引用")]
private static void CollectAnimatorControllerDependencies()
{
AnimatorController animatorController = Selection.activeObject as AnimatorController;
string[] dependencyArray = AssetDatabase.GetDependencies(AssetDatabase.GetAssetPath(animatorController));

Debug.Log("************************* Animator Controller Dependencies (" + animatorController.name + ") *************************");
foreach (string dependency in dependencyArray)
{
if (dependency.EndsWith(".controller"))
Debug.Log(dependency);
}
Debug.Log("************************************************* End *************************************************");
}

[MenuItem("美术工具/Animator Controller/查找所有Animator Controller引用")]
private static void CheckAnimatorControllerDependencies()
{
List<string> dependencyCheckNameList = new List<string>();
string[] filePathNameArray = Directory.GetFiles(Application.dataPath + "/Content/Animator", "*.controller", SearchOption.TopDirectoryOnly);
foreach (string filePathName in filePathNameArray)
{
string[] dependencyArray = AssetDatabase.GetDependencies(filePathName.Substring(filePathName.IndexOf("/Assets/") + 1));
foreach (string dependency in dependencyArray)
{
if (dependency.EndsWith(".controller"))
{
string assetName = Path.GetFileNameWithoutExtension(filePathName);
string dependencyName = Path.GetFileNameWithoutExtension(dependency);

// A依赖于B,如果"B_A"存在,表示B也依赖于A,则是循环依赖
string checkName = dependencyName + "_" + assetName;
if (dependencyCheckNameList.Contains(checkName))
Debug.Log(Path.GetFileName(filePathName) + " and " + Path.GetFileName(dependency) + " depend each other");

dependencyCheckNameList.Add(assetName + "_" + dependencyName);
}
}
}
}

[MenuItem("美术工具/Animator Controller/清除冗余引用数据", true)]
[MenuItem("美术工具/Animator Controller/查找Animator Controller引用", true)]
private static bool ValidateCorrectData()
{
return Selection.activeObject is AnimatorController;
}
}

参考

https://www.cnblogs.com/twjcnblog/p/7663048.html