├── Assets
└── AnimCompression
│ ├── CompressedAnimationDirver.cs
│ ├── CompressedAnimationDirver.cs.meta
│ ├── CompressedClipData.cs
│ ├── CompressedClipData.cs.meta
│ ├── CompressedCurveData.cs
│ ├── CompressedCurveData.cs.meta
│ ├── CompressedShareData.cs
│ ├── CompressedShareData.cs.meta
│ ├── CompressionAnimatorLayerProxy.cs
│ ├── CompressionAnimatorLayerProxy.cs.meta
│ ├── CompressionAnimatorStateFlag.cs
│ ├── CompressionAnimatorStateFlag.cs.meta
│ ├── CompressionAnimatorStateProxy.cs
│ ├── CompressionAnimatorStateProxy.cs.meta
│ ├── CurveDataBasicValues.cs
│ ├── CurveDataBasicValues.cs.meta
│ ├── CurveDataCalcTan.cs
│ ├── CurveDataCalcTan.cs.meta
│ ├── CurveDataStoreTan.cs
│ ├── CurveDataStoreTan.cs.meta
│ ├── Editor.meta
│ ├── Editor
│ ├── AnimatorCompressUtils.cs
│ ├── AnimatorCompressUtils.cs.meta
│ ├── ClipCompress.cs
│ └── ClipCompress.cs.meta
│ ├── NumberCompressUtils.cs
│ └── NumberCompressUtils.cs.meta
└── README.md
/Assets/AnimCompression/CompressedAnimationDirver.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using UnityEngine;
5 | namespace AnimCompression
6 | {
7 |
8 | ///
9 | /// Animator对象挂载Animation组件冲突后,需要添加AnimationDirver来驱动。
10 | ///
11 | public class CompressedAnimationDirver : MonoBehaviour
12 | {
13 | Animation anim;
14 | Animator _animator;
15 | public AnimationClip playingClip;
16 | //Stub属性。@see AnimatorCompressUtils.ConvertAnimatorState
17 | public float stubAnimParam;
18 | public Animator animator
19 | {
20 | get
21 | {
22 | if (!_animator)
23 | {
24 | _animator = GetComponent();
25 | }
26 | return _animator;
27 | }
28 | }
29 | public Animation GetOrCreateAnimation()
30 | {
31 | if (anim)
32 | {
33 | return anim;
34 | }
35 | anim = GetComponent();
36 | if (!anim)
37 | {
38 | anim = gameObject.AddComponent();
39 | }
40 | return anim;
41 | }
42 |
43 |
44 |
45 | // Use this for initialization
46 | //void Awake()
47 | //{
48 |
49 | // //anim.animatePhysics = true;
50 | // //anim.cullingType = AnimationCullingType.BasedOnRenderers;
51 | // ////丢到一个看不见的地方
52 | // //anim.localBounds = new Bounds(new Vector3(9527 * 2, 9527 * 2 ,- 9527*100), new Vector3());
53 | // // anim.enabled = false;
54 | //}
55 |
56 | // Update is called once per frame
57 | void LateUpdate()
58 | {
59 | if (!anim)
60 | {
61 | return;
62 | }
63 | //anim.Sample();
64 | //模拟Animator行为,停留在最后一个动画帧上
65 | if (playingClip && !anim.isPlaying)
66 | {
67 | playingClip.SampleAnimation(gameObject, playingClip.length);
68 | }
69 | }
70 | internal void DoStateLayerEnter(Animator animator, ref AnimatorStateInfo stateInfo)
71 | {
72 | //throw new NotImplementedException();
73 |
74 | }
75 | internal void DoStateLayerExit(Animator animator, ref AnimatorStateInfo stateInfo)
76 | {
77 | // throw new NotImplementedException();
78 | // playingClip = null;
79 | }
80 |
81 | private void OnDestroy()
82 | {
83 | playingClip = null;
84 | anim = null;
85 | }
86 | internal void DoStateEnter(Animator animator,ref AnimatorStateInfo stateInfo, CompressedClipData data)
87 | {
88 | if (!data)
89 | {
90 | playingClip = null;
91 | return;
92 | }
93 | var clip = data.getOrCreateClip();
94 | var anim = GetOrCreateAnimation();
95 | var clipState = anim[clip.name];
96 | if (clipState == null)
97 | {
98 | anim.AddClip(clip, clip.name);
99 | }
100 | if (!anim.isPlaying)
101 | {
102 | anim.Play(clip.name);
103 | }
104 | else
105 | {
106 | anim.CrossFade(clip.name);
107 | }
108 | animator.updateMode = AnimatorUpdateMode.AnimatePhysics;
109 | playingClip = clip;
110 | //anim.localBounds = new Bounds(new Vector3(9527 * 2, 9527 * 2, -9527 * 100), new Vector3());
111 | }
112 |
113 | internal void DoStateExit(Animator animator, ref AnimatorStateInfo stateInfo, CompressedClipData data)
114 | {
115 | if (!data)
116 | {
117 | return;
118 | }
119 | var clip = data.getOrCreateClip();
120 | anim.Stop(clip.name);
121 | if(clip == playingClip)
122 | {
123 | playingClip = null;
124 | }
125 | }
126 | }
127 | }
128 |
129 |
--------------------------------------------------------------------------------
/Assets/AnimCompression/CompressedAnimationDirver.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 0af79b0e499680b4f9f201ed59a34289
3 | timeCreated: 1527750393
4 | licenseType: Pro
5 | MonoImporter:
6 | externalObjects: {}
7 | serializedVersion: 2
8 | defaultReferences: []
9 | executionOrder: 0
10 | icon: {instanceID: 0}
11 | userData:
12 | assetBundleName:
13 | assetBundleVariant:
14 |
--------------------------------------------------------------------------------
/Assets/AnimCompression/CompressedClipData.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 | using System;
5 | namespace AnimCompression
6 | {
7 | ///
8 | /// 动画(AnimationClip)压缩数据集合
9 | /// 主要通过压缩曲线数据来进行动画压缩。
10 | /// *AnimCompression动画压缩是有损的,主要损失的是关键帧值精度和丢弃切线信息,其中关键帧时间是无损的
11 | ///
12 | public class CompressedClipData:ScriptableObject {
13 | ///
14 | /// 曲线数据
15 | ///
16 | public CompressedCurveData[] curves;
17 |
18 | public List curvesCalcTan = new List();
19 | public List curvesStoreTan = new List();
20 |
21 |
22 | public byte frameRate;
23 | public float length;
24 | public byte wrapMode;
25 | //共享数据(属性路径)
26 | public CompressedShareData shareData;
27 | //运行时解码后的Clip
28 | AnimationClip clip;
29 | ///
30 | /// 解码或者获取一个已经解码的AnimationClip
31 | ///
32 | ///
33 | public AnimationClip getOrCreateClip()
34 | {
35 | if (this.clip)
36 | {
37 | return this.clip;
38 | }
39 | AnimationClip clip = new AnimationClip();
40 | clip.legacy = true;
41 | clip.name = this.name;
42 | clip.wrapMode = (WrapMode)this.wrapMode;
43 | clip.frameRate = frameRate;
44 | //解析每个曲线
45 | DecodeCuves(clip,this.curvesCalcTan);
46 | DecodeCuves(clip, this.curvesStoreTan);
47 | this.clip = clip;
48 | return clip;
49 | }
50 |
51 | public void DecodeCuves(AnimationClip clip,List curves) where T: CurveDataBasicValues
52 | {
53 | //解析曲线集合
54 | for (int i = 0,count = curves.Count; i < count; i++)
55 | {
56 | var curveData = curves[i];
57 | var curve = curveData.decodeData(this);
58 | if (shareData.fields.Count <= curveData.property)
59 | {
60 | Debug.LogError("属性未找到:" + curveData.property + " clip:" + this.name + " :" + i);
61 | continue;
62 | }
63 | CompressedShareData.DataField field = shareData.fields[curveData.property];
64 | clip.SetCurve(field.propertyPath, typeof(GameObject).Assembly.GetType(field.propertyType), field.propertyName, curve);
65 | }
66 | }
67 |
68 | }
69 |
70 | }
--------------------------------------------------------------------------------
/Assets/AnimCompression/CompressedClipData.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: d35f1a23814726949980d68f4f37173f
3 | timeCreated: 1527731889
4 | licenseType: Pro
5 | MonoImporter:
6 | externalObjects: {}
7 | serializedVersion: 2
8 | defaultReferences: []
9 | executionOrder: 0
10 | icon: {instanceID: 0}
11 | userData:
12 | assetBundleName:
13 | assetBundleVariant:
14 |
--------------------------------------------------------------------------------
/Assets/AnimCompression/CompressedCurveData.cs:
--------------------------------------------------------------------------------
1 | //#define ANIM_CMP_DEBUG_DATA
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using UnityEngine;
5 | using System;
6 |
7 |
8 | namespace AnimCompression
9 | {
10 |
11 | ///
12 | /// 曲线压缩数据集
13 | /// CompressedCurve主要存储
14 | /// 关键帧时间:无损压缩,将时间转换成帧序列位,有关键帧为【1】否则为【0】,最大压缩率为1/32。
15 | /// 帧不填满的时候可能没有压缩空间甚至更大,帧无法对齐时会有额外损失。
16 | /// 关键帧的值:有损压缩:关键帧的值,算法是在最大值和最小值之间线性采样成byte
17 | /// 关键帧切线:丢弃
18 | ///
19 | ///
20 | [Serializable]
21 | public class CompressedCurveData
22 | {
23 | const int ValueMax = Byte.MaxValue;
24 |
25 | const float TanMin = -180;
26 | const float TanMax = 180;
27 |
28 | public enum ValueMode{
29 | Byte1,Byte2,ValueTan
30 | }
31 | #if ANIM_CMP_DEBUG_DATA
32 | #region 调试数据
33 | //DebugDatas
34 | public string propertyPath;
35 | public string propertyType;
36 | public string propertyName;
37 | public float[] keysTansIn;
38 | public float[] keysTansOut;
39 | public float[] keysValues;
40 | public float[] keysTimes;
41 | //public string propertyName;
42 |
43 | public void encodeDebugData(List keys)
44 | {
45 | var length = keys.Count;
46 | keysValues = new float[length];
47 | keysTimes = new float[length];
48 | keysTansIn = new float[length];
49 | keysTansOut = new float[length];
50 | for (int i = 0; i < length; i++)
51 | {
52 | var key = keys[i];
53 | keysTansIn[i] = key.inTangent;
54 | keysTansOut[i] = key.outTangent;
55 | keysValues[i] = key.value;
56 | keysTimes[i] = key.time;
57 | }
58 | }
59 | public void decodeDebugData(Keyframe[] keys)
60 | {
61 | var length = keys.Length;
62 | for (int i = 0; i < length; i++)
63 | {
64 | Keyframe key = keys[i];
65 | key.value = keysValues[i];
66 | key.time = keysTimes[i];
67 | key.inTangent = keysTansIn[i];
68 | key.outTangent = keysTansOut[i];
69 | keys[i] = key;
70 | }
71 | }
72 | public void initProperty(ushort propertyId, string propertyPath, string propertyType, string propertyName)
73 | {
74 | this.property = propertyId;
75 | this.propertyPath = propertyPath;
76 | this.propertyType = propertyType;
77 | this.propertyName = propertyName;
78 | }
79 | #endregion
80 | #else
81 | public void encodeDebugData(List keys){}
82 | public void decodeDebugData(Keyframe[] keys){}
83 | public void initProperty(ushort propertyId,string propertyPath, string propertyType, string propertyName)
84 | {
85 | this.property = propertyId;
86 | }
87 | #endif
88 | ///
89 | /// property 12bit
90 | /// valueMode 4 bit
91 | /// mode 1:
92 | /// valueBase 5 bit
93 | /// valueAvg 13 bit
94 | /// mode 2:
95 | /// valueBase 5 bit
96 | ///
97 | ///
98 | public int header;
99 | //属性ID
100 | public ushort property;
101 |
102 | public byte valueMode;
103 | //256
104 | //byte
105 | //
106 | ///
107 | /// 时间数据,无损压缩,每个byte对应时间轴上的一帧(非关键帧),关键帧则为1,将时间转换成帧序列位,有关键帧为【1】否则为【0】,最大压缩率为1/32。
108 | ///
109 | public byte[] keysTime;
110 |
111 | //public byte mainData;
112 |
113 | ///
114 | /// 关键帧值数据,每个byte对应关键帧上一个值,在当前曲线最大值和最小值之间线性采样成byte
115 | ///
116 | public byte[] keysValueByte;
117 | public float[] keysTans;
118 | /////
119 | ///// 关键帧值数据,每个byte对应关键帧上一个值,在当前曲线最大值和最小值之间线性采样成byte
120 | /////
121 | //public ushort[] keysValueShort;
122 |
123 | ///
124 | /// 当前曲线的最小值
125 | ///
126 | public float valueMin;
127 | ///
128 | /// 当前曲线的最大值
129 | ///
130 | public float valueMax;
131 |
132 | void deleteOverframe(float frameRate,List keys)
133 | {
134 | //剔除重叠帧 *有些情况下,同一时刻会有重复帧,这将导致时间压缩算法出问题
135 | float frameTime = 1 / frameRate;
136 | for (int i = 1; i < keys.Count; i++)
137 | {
138 | float frameDiff = Mathf.Abs(keys[i].time - keys[i - 1].time);
139 | //小于帧时间间隔,则表示重叠帧
140 | if (frameDiff < frameTime / 2)
141 | {
142 | keys.RemoveAt(i--);
143 | }
144 | }
145 | }
146 | void encodBytes(byte[] bytes,ValueMode valueMode,int i, ref Keyframe key)
147 | {
148 | var value = key.value;
149 | var valueNormal = Mathf.InverseLerp(valueMin, valueMax, value);
150 |
151 | if (valueMode == ValueMode.Byte1)
152 | {
153 | bytes[i] = (byte)(valueNormal * byte.MaxValue);
154 | }
155 | else if (valueMode == ValueMode.Byte2)
156 | {
157 | ushort bytesValue = (ushort)(valueNormal * ushort.MaxValue);
158 | bytes[i * 2] = (byte)(bytesValue & 0XFF);
159 | bytes[i * 2 + 1] = (byte)((bytesValue >> 8) & 0XFF);
160 | }
161 | else if(valueMode == ValueMode.ValueTan)
162 | {
163 | ushort bytesValue = (ushort)(valueNormal * ushort.MaxValue);
164 | bytes[i * 2] = (byte)(valueNormal * byte.MaxValue);
165 | bytes[i * 2 + 1] = (byte)(Mathf.InverseLerp(TanMin, TanMax, key.inTangent) * byte.MaxValue);
166 | }
167 | else
168 | {
169 | throw new ArgumentException("dataMode:" + valueMode + " error!");
170 | }
171 | }
172 | void decodeBytes(byte[] bytes, ValueMode valueMode, int i,ref Keyframe key)
173 | {
174 | float valueNormal;
175 | if (valueMode == ValueMode.Byte1)
176 | {
177 | valueNormal = bytes[i] * 1.0f / byte.MaxValue;
178 | }
179 | else if (valueMode == ValueMode.Byte2)
180 | {
181 | int value = bytes[i * 2];
182 | value |= bytes[i * 2 + 1] << 8;
183 | valueNormal = value * 1.0f / ushort.MaxValue;
184 | }
185 | else if (valueMode == ValueMode.ValueTan)
186 | {
187 | valueNormal = bytes[i * 2] * 1.0f / byte.MaxValue;
188 | float tanNormal = bytes[i * 2 + 1] * 1.0f / byte.MaxValue;
189 | key.inTangent = key.outTangent = Mathf.Lerp(TanMin, TanMax, tanNormal);
190 | }
191 | else
192 | {
193 | throw new ArgumentException();
194 | }
195 | key.value = Mathf.Lerp(valueMin, valueMax, valueNormal);
196 | }
197 |
198 |
199 | ///
200 | /// 编码成AnimationCurve
201 | ///
202 | ///
203 | ///
204 | public void encodeData(CompressedClipData clip, AnimationCurve curve, ValueMode valueMode,bool tan)
205 | {
206 | float maxTime = clip.length;
207 | float time = clip.length;
208 | float frameRate = clip.frameRate;
209 | List keys = new List(curve.keys);
210 | deleteOverframe(frameRate,keys);
211 | var length = keys.Count;
212 | //keysData = new uint[length];
213 | if(valueMode == ValueMode.Byte1)
214 | {
215 | keysValueByte = new byte[length];
216 | }
217 | else
218 | {
219 | keysValueByte = new byte[length * 2];
220 | }
221 | if (tan)
222 | {
223 | keysTans = new float[length];
224 | }
225 | //计算最大值和最小值
226 | valueMin = float.MaxValue;
227 | valueMax = float.MinValue;
228 | for (int i = 0; i < length; i++)
229 | {
230 | var key = keys[i];
231 | valueMin = Mathf.Min(valueMin, key.value);
232 | valueMax = Mathf.Max(valueMax, key.value);
233 | }
234 | int timeBytes = Mathf.CeilToInt((time * frameRate + 1) / 8);
235 | keysTime = new byte[timeBytes];
236 |
237 | float valueSize = valueMax - valueMin;
238 | //编码时间和关键帧值
239 | for (int i = 0; i < length; i++)
240 | {
241 | var key = keys[i];
242 | //时间
243 | int keyIndex = Mathf.RoundToInt(key.time * frameRate);
244 | int byteIndex = keyIndex / 8;
245 | int byteBit = keyIndex % 8;
246 | byte keysTimeByte = keysTime[byteIndex];
247 | keysTime[byteIndex] |= (byte)(1 << byteBit);
248 |
249 | encodBytes(keysValueByte, valueMode, i,ref key);
250 | if (tan)
251 | {
252 | keysTans[i] = key.inTangent;
253 | }
254 | }
255 | this.valueMode = (byte)valueMode;
256 | encodeDebugData(keys);
257 | }
258 | //解码时间信息
259 | void decodeTime(CompressedClipData clip,Keyframe[] keys)
260 | {
261 | float frameRate = clip.frameRate;
262 | for (int i = 0, idx = 0; i < keysTime.Length; i++)
263 | {
264 | byte timeBits = keysTime[i];
265 | for (int j = 0; j < 8; j++)
266 | {
267 | if (((timeBits >> j) & 1) != 0)
268 | {
269 | if (keys.Length <= idx)
270 | {
271 | Debug.LogError("解码动画数据异常:帧时间索引:" + idx + " 当前帧:" + (i * 8 + j) + " 动画名称:" + clip.name + " 曲线:" + property);
272 | }
273 | else
274 | {
275 | keys[idx++].time = (i * 8 + j) / frameRate;
276 | }
277 | }
278 | }
279 | }
280 | }
281 | //解码动画曲线
282 | public AnimationCurve decodeData(CompressedClipData clip)
283 | {
284 | float maxTime = clip.length;
285 | ValueMode valueMode = (ValueMode)this.valueMode;
286 | AnimationCurve curve = new AnimationCurve();
287 | var length = keysValueByte.Length;
288 | if(valueMode== ValueMode.Byte2 || valueMode == ValueMode.ValueTan)
289 | {
290 | length = length / 2;
291 | }
292 | // float ValueMax = valueMode == ValueMode.Byte1 ? byte.MaxValue : ushort.MaxValue;
293 | var keys = new Keyframe[length];
294 | bool tansData = keysTans != null && keysTans.Length > 0;
295 | for (int i = 0; i < length; i++)
296 | {
297 | Keyframe key = new Keyframe();
298 | decodeBytes(keysValueByte,valueMode,i,ref key);
299 | if (tansData)
300 | {
301 | key.inTangent = keysTans[i];
302 | key.outTangent = keysTans[i];
303 | }
304 | keys[i] = key;
305 | }
306 | decodeTime(clip, keys);
307 | decodeDebugData(keys);
308 | curve.keys = keys;
309 | if(valueMode != ValueMode.ValueTan && !tansData)
310 | {
311 | for (int i = 0; i < length; i++)
312 | {
313 | curve.SmoothTangents(i, 0f);
314 | }
315 | }
316 |
317 | return curve;
318 | }
319 | }
320 | }
--------------------------------------------------------------------------------
/Assets/AnimCompression/CompressedCurveData.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 8eeae9788723bf14faaa71a4b901f961
3 | timeCreated: 1528113548
4 | licenseType: Pro
5 | MonoImporter:
6 | externalObjects: {}
7 | serializedVersion: 2
8 | defaultReferences: []
9 | executionOrder: 0
10 | icon: {instanceID: 0}
11 | userData:
12 | assetBundleName:
13 | assetBundleVariant:
14 |
--------------------------------------------------------------------------------
/Assets/AnimCompression/CompressedShareData.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 | using System;
5 |
6 | namespace AnimCompression
7 | {
8 | ///
9 | /// 共享数据集,存储属性,并将其ID化
10 | ///
11 | public class CompressedShareData : ScriptableObject
12 | {
13 | [Serializable]
14 | public class DataField
15 | {
16 | //[NonSerialized]
17 | public string propertyName;
18 | //[NonSerialized]
19 | public string propertyPath;
20 |
21 | public string propertyType;
22 |
23 | }
24 | public List fields = new List();
25 | #if UNITY_EDITOR
26 | public string rawAnimatorGUID;
27 | #endif
28 | public ushort getFieldIndex(string propertyPath, string propertyType, string propertyName)
29 | {
30 | for (ushort i = 0, length = (ushort)fields.Count; i < length; i++)
31 | {
32 | var field = fields[i];
33 | if (field.propertyPath == propertyPath && field.propertyType == propertyType && field.propertyName == propertyName)
34 | {
35 | return i;
36 | }
37 | }
38 | fields.Add(new DataField()
39 | {
40 | propertyPath = propertyPath,
41 | propertyType = propertyType,
42 | propertyName = propertyName,
43 | });
44 | return (ushort)(fields.Count -1);
45 | }
46 | }
47 |
48 | }
--------------------------------------------------------------------------------
/Assets/AnimCompression/CompressedShareData.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 1ff07d5b191e4f44cbfa8d71b8cac798
3 | timeCreated: 1528161781
4 | licenseType: Pro
5 | MonoImporter:
6 | externalObjects: {}
7 | serializedVersion: 2
8 | defaultReferences: []
9 | executionOrder: 0
10 | icon: {instanceID: 0}
11 | userData:
12 | assetBundleName:
13 | assetBundleVariant:
14 |
--------------------------------------------------------------------------------
/Assets/AnimCompression/CompressionAnimatorLayerProxy.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 | namespace AnimCompression
5 | {
6 | ///
7 | ///
8 | ///
9 | public class CompressionAnimatorLayerProxy : StateMachineBehaviour
10 | {
11 |
12 | CompressedAnimationDirver getDirver(Animator animator)
13 | {
14 | CompressedAnimationDirver dirver = animator.GetComponent();
15 | if (!dirver)
16 | {
17 | dirver = animator.gameObject.AddComponent();
18 | }
19 | return dirver;
20 | }
21 |
22 | //OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
23 | override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
24 | {
25 | CompressedAnimationDirver dirver = getDirver(animator);
26 | dirver.DoStateLayerEnter(animator,ref stateInfo);
27 | }
28 |
29 | // OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks
30 | //override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
31 | //{
32 |
33 | // if (clip)
34 | // {
35 | // clip.SampleAnimation(animator.gameObject, stateInfo.normalizedTime);
36 | // }
37 | //}
38 |
39 | //OnStateExit is called when a transition ends and the state machine finishes evaluating this state
40 | override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
41 | {
42 | CompressedAnimationDirver dirver = getDirver(animator);
43 | dirver.DoStateLayerExit(animator, ref stateInfo);
44 | //dirver.DoStateEnter(stateInfo, getClip());
45 | //var runner = animator.GetComponent();
46 | //if (runner)
47 | //{
48 | // runner.Stop(clip.name);
49 | // //最后采样一次,确保最终状态正确
50 | // clip.SampleAnimation(animator.gameObject, clip.length);
51 |
52 | //}
53 | }
54 |
55 | // OnStateMove is called right after Animator.OnAnimatorMove(). Code that processes and affects root motion should be implemented here
56 | //override public void OnStateMove(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
57 | //{
58 | // //var runner = animator.GetComponent();
59 | // //if (runner)
60 | // //{
61 | // // runner.Sample();
62 | // //}
63 | // // clip.SampleAnimation(animator.gameObject, stateInfo.normalizedTime * clip.length);
64 | //}
65 |
66 | //// OnStateIK is called right after Animator.OnAnimatorIK(). Code that sets up animation IK (inverse kinematics) should be implemented here.
67 | //override public void OnStateIK(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
68 | // clip.SampleAnimation(animator.gameObject, stateInfo.normalizedTime * clip.length);
69 |
70 | //}
71 | }
72 |
73 | }
--------------------------------------------------------------------------------
/Assets/AnimCompression/CompressionAnimatorLayerProxy.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: fd4d017f2b6fbab45a081ea33e41b69a
3 | timeCreated: 1528769212
4 | licenseType: Pro
5 | MonoImporter:
6 | serializedVersion: 2
7 | defaultReferences: []
8 | executionOrder: 0
9 | icon: {instanceID: 0}
10 | userData:
11 | assetBundleName:
12 | assetBundleVariant:
13 |
--------------------------------------------------------------------------------
/Assets/AnimCompression/CompressionAnimatorStateFlag.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 | namespace AnimCompression
5 | {
6 | public class CompressionAnimatorStateFlag : StateMachineBehaviour
7 | {
8 | //当前状态的动画是否跳过压缩
9 | public bool skipCompressed = false;
10 | //当前状态的动画是否存储旋转的tan信息
11 | public bool rotationTan = false;
12 | }
13 |
14 | }
--------------------------------------------------------------------------------
/Assets/AnimCompression/CompressionAnimatorStateFlag.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: ea4a1ca574e9de342a87dac210171c0e
3 | timeCreated: 1528275289
4 | licenseType: Pro
5 | MonoImporter:
6 | serializedVersion: 2
7 | defaultReferences: []
8 | executionOrder: 0
9 | icon: {instanceID: 0}
10 | userData:
11 | assetBundleName:
12 | assetBundleVariant:
13 |
--------------------------------------------------------------------------------
/Assets/AnimCompression/CompressionAnimatorStateProxy.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 | namespace AnimCompression
5 | {
6 | ///
7 | ///
8 | ///
9 | public class CompressionAnimatorStateProxy : StateMachineBehaviour
10 | {
11 | public CompressedClipData data;
12 | //AnimationClip clip;
13 |
14 | //AnimationClip getClip()
15 | //{
16 | // if (!data)
17 | // {
18 | // return null;
19 | // }
20 | // if (!clip)
21 | // {
22 | // clip = data.getOrCreateClip();
23 | // }
24 | // return clip;
25 | //}
26 | CompressedAnimationDirver getDirver(Animator animator)
27 | {
28 | CompressedAnimationDirver dirver = animator.GetComponent();
29 | if (!dirver)
30 | {
31 | dirver = animator.gameObject.AddComponent();
32 | }
33 | return dirver;
34 | }
35 |
36 | //OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
37 | override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
38 | {
39 | CompressedAnimationDirver dirver = getDirver(animator);
40 | dirver.DoStateEnter(animator,ref stateInfo, data);
41 | }
42 |
43 | // OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks
44 | //override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
45 | //{
46 |
47 | // if (clip)
48 | // {
49 | // clip.SampleAnimation(animator.gameObject, stateInfo.normalizedTime);
50 | // }
51 | //}
52 |
53 | //OnStateExit is called when a transition ends and the state machine finishes evaluating this state
54 | override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
55 | {
56 | CompressedAnimationDirver dirver = getDirver(animator);
57 | dirver.DoStateExit(animator, ref stateInfo,data);
58 | //dirver.DoStateEnter(stateInfo, getClip());
59 | //var runner = animator.GetComponent();
60 | //if (runner)
61 | //{
62 | // runner.Stop(clip.name);
63 | // //最后采样一次,确保最终状态正确
64 | // clip.SampleAnimation(animator.gameObject, clip.length);
65 |
66 | //}
67 | }
68 |
69 | // OnStateMove is called right after Animator.OnAnimatorMove(). Code that processes and affects root motion should be implemented here
70 | //override public void OnStateMove(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
71 | //{
72 | // //var runner = animator.GetComponent();
73 | // //if (runner)
74 | // //{
75 | // // runner.Sample();
76 | // //}
77 | // // clip.SampleAnimation(animator.gameObject, stateInfo.normalizedTime * clip.length);
78 | //}
79 |
80 | //// OnStateIK is called right after Animator.OnAnimatorIK(). Code that sets up animation IK (inverse kinematics) should be implemented here.
81 | //override public void OnStateIK(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
82 | // clip.SampleAnimation(animator.gameObject, stateInfo.normalizedTime * clip.length);
83 |
84 | //}
85 | }
86 |
87 | }
--------------------------------------------------------------------------------
/Assets/AnimCompression/CompressionAnimatorStateProxy.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 622d7c5c8c2cb00438a65ae22a943ad0
3 | timeCreated: 1527748623
4 | licenseType: Pro
5 | MonoImporter:
6 | externalObjects: {}
7 | serializedVersion: 2
8 | defaultReferences: []
9 | executionOrder: 0
10 | icon: {instanceID: 0}
11 | userData:
12 | assetBundleName:
13 | assetBundleVariant:
14 |
--------------------------------------------------------------------------------
/Assets/AnimCompression/CurveDataBasicValues.cs:
--------------------------------------------------------------------------------
1 | //#define ANIM_CMP_DEBUG_DATA
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using UnityEngine;
5 | using System;
6 |
7 |
8 | namespace AnimCompression
9 | {
10 |
11 | [Serializable]
12 | public class CurveDataBasicValues
13 | {
14 | #if ANIM_CMP_DEBUG_DATA
15 | #region 调试数据
16 | //DebugDatas
17 | public string propertyPath;
18 | public string propertyType;
19 | public string propertyName;
20 | public float[] keysTansIn;
21 | public float[] keysTansOut;
22 | public float[] keysValues;
23 | public float[] keysTimes;
24 | //public string propertyName;
25 |
26 | public void encodeDebugData(List keys)
27 | {
28 | var length = keys.Count;
29 | keysValues = new float[length];
30 | keysTimes = new float[length];
31 | keysTansIn = new float[length];
32 | keysTansOut = new float[length];
33 | for (int i = 0; i < length; i++)
34 | {
35 | var key = keys[i];
36 | keysTansIn[i] = key.inTangent;
37 | keysTansOut[i] = key.outTangent;
38 | keysValues[i] = key.value;
39 | keysTimes[i] = key.time;
40 | }
41 | }
42 | public void decodeDebugData(Keyframe[] keys)
43 | {
44 | var length = keys.Length;
45 | for (int i = 0; i < length; i++)
46 | {
47 | Keyframe key = keys[i];
48 | key.value = keysValues[i];
49 | key.time = keysTimes[i];
50 | key.inTangent = keysTansIn[i];
51 | key.outTangent = keysTansOut[i];
52 | keys[i] = key;
53 | }
54 | }
55 | public void initProperty(ushort propertyId, string propertyPath, string propertyType, string propertyName)
56 | {
57 | this.property = propertyId;
58 | this.propertyPath = propertyPath;
59 | this.propertyType = propertyType;
60 | this.propertyName = propertyName;
61 | }
62 | #endregion
63 | #else
64 | public void encodeDebugData(List keys) { }
65 | public void decodeDebugData(Keyframe[] keys) { }
66 | public void initProperty(ushort propertyId, string propertyPath, string propertyType, string propertyName)
67 | {
68 | this.property = propertyId;
69 | }
70 | #endif
71 | //属性ID
72 | public ushort property;
73 |
74 | //256
75 | //byte
76 | //
77 | ///
78 | /// 时间数据,无损压缩,每个byte对应时间轴上的一帧(非关键帧),关键帧则为1,将时间转换成帧序列位,有关键帧为【1】否则为【0】,最大压缩率为1/32。
79 | ///
80 | public byte[] keysTime;
81 |
82 | //public byte mainData;
83 |
84 | ///
85 | /// 关键帧值数据,每个byte对应关键帧上一个值,在当前曲线最大值和最小值之间线性采样成byte
86 | ///
87 | public byte[] keysValueByte;
88 |
89 | /////
90 | ///// 关键帧值数据,每个byte对应关键帧上一个值,在当前曲线最大值和最小值之间线性采样成byte
91 | /////
92 | //public ushort[] keysValueShort;
93 |
94 | ///
95 | /// 当前曲线的最小值
96 | ///
97 | public float valueMin;
98 | ///
99 | /// 当前曲线的最大值
100 | ///
101 | public float valueMax;
102 |
103 | void deleteOverframe(float frameRate, List keys)
104 | {
105 | //剔除重叠帧 *有些情况下,同一时刻会有重复帧,这将导致时间压缩算法出问题
106 | float frameTime = 1 / frameRate;
107 | for (int i = 1; i < keys.Count; i++)
108 | {
109 | float frameDiff = Mathf.Abs(keys[i].time - keys[i - 1].time);
110 | //小于帧时间间隔,则表示重叠帧
111 | if (frameDiff < frameTime / 2)
112 | {
113 | keys.RemoveAt(i--);
114 | }
115 | }
116 | }
117 | protected virtual void EncodeFrameValue(int keyIndex, ref Keyframe keyframe)
118 | {
119 | var value = keyframe.value;
120 | var valueNormal = Mathf.InverseLerp(valueMin, valueMax, value);
121 | this.keysValueByte[keyIndex] = (byte)(valueNormal * byte.MaxValue);
122 | }
123 | protected virtual void DecodeFrame(int keyIndex, ref Keyframe keyframe)
124 | {
125 | float valueNormal = this.keysValueByte[keyIndex] * 1.0f / byte.MaxValue;
126 | keyframe.value = Mathf.Lerp(valueMin, valueMax, valueNormal);
127 | }
128 |
129 |
130 | ///
131 | /// 编码成AnimationCurve
132 | ///
133 | ///
134 | ///
135 | public void encodeData(CompressedClipData clip, AnimationCurve curve)
136 | {
137 | List keys = new List(curve.keys);
138 | deleteOverframe(clip.frameRate, keys);
139 | EncodeFrames(clip,curve,keys);
140 | }
141 | protected virtual void InitEncode(CompressedClipData clip, AnimationCurve curve, List keys)
142 | {
143 | //计算最大值和最小值
144 | valueMin = float.MaxValue;
145 | valueMax = float.MinValue;
146 | var keysCount = keys.Count;
147 | for (int i = 0; i < keysCount; i++)
148 | {
149 | var key = keys[i];
150 | valueMin = Mathf.Min(valueMin, key.value);
151 | valueMax = Mathf.Max(valueMax, key.value);
152 | }
153 | this.keysValueByte = new byte[keysCount];
154 | }
155 | private void initFramesTime(float clipLength, float frameRate)
156 | {
157 | int timeBytes = Mathf.CeilToInt((clipLength * frameRate + 1) / 8);
158 | this.keysTime = new byte[timeBytes];
159 | }
160 | protected virtual void EncodeFrames(CompressedClipData clip, AnimationCurve curve,List keyframes)
161 | {
162 | float maxTime = clip.length;
163 | float clipLength = clip.length;
164 | float frameRate = clip.frameRate;
165 | var keysCount = keyframes.Count;
166 |
167 | InitEncode(clip, curve, keyframes);
168 | initFramesTime(clipLength, frameRate);
169 |
170 | //编码时间和关键帧值
171 | for (int keyIndex = 0; keyIndex < keysCount; keyIndex++)
172 | {
173 | var keyframe = keyframes[keyIndex];
174 | EncodeFrameTime(keysTime, keyIndex, frameRate, keyframe.time);
175 | EncodeFrameValue(keyIndex, ref keyframe);
176 | }
177 | encodeDebugData(keyframes);
178 | }
179 |
180 |
181 |
182 | private void EncodeFrameTime(byte[] keysTime, int i, float frameRate, float keyTime)
183 | {
184 | //时间
185 | int keyIndex = Mathf.RoundToInt(keyTime * frameRate);
186 | int byteIndex = keyIndex / 8;
187 | int byteBit = keyIndex % 8;
188 | byte keysTimeByte = keysTime[byteIndex];
189 | keysTime[byteIndex] |= (byte)(1 << byteBit);
190 | }
191 |
192 | //解码时间信息
193 | void decodeTime(CompressedClipData clip, Keyframe[] keys)
194 | {
195 | float frameRate = clip.frameRate;
196 | for (int i = 0, idx = 0; i < keysTime.Length; i++)
197 | {
198 | byte timeBits = keysTime[i];
199 | for (int j = 0; j < 8; j++)
200 | {
201 | if (((timeBits >> j) & 1) != 0)
202 | {
203 | if (keys.Length <= idx)
204 | {
205 | Debug.LogError("解码动画数据异常:帧时间索引:" + idx + " 当前帧:" + (i * 8 + j) + " 动画名称:" + clip.name + " 曲线:" + property);
206 | }
207 | else
208 | {
209 | keys[idx++].time = (i * 8 + j) / frameRate;
210 | }
211 | }
212 | }
213 | }
214 | }
215 |
216 | protected virtual void SmoothTangents(AnimationCurve curve,int keys)
217 | {
218 |
219 | }
220 | //解码动画曲线
221 | public virtual AnimationCurve decodeData(CompressedClipData clip)
222 | {
223 | float maxTime = clip.length;
224 | AnimationCurve curve = new AnimationCurve();
225 | var length = keysValueByte.Length;
226 |
227 | // float ValueMax = valueMode == ValueMode.Byte1 ? byte.MaxValue : ushort.MaxValue;
228 | var keys = new Keyframe[length];
229 | for (int i = 0; i < length; i++)
230 | {
231 | Keyframe key = new Keyframe();
232 | DecodeFrame(i, ref key);
233 | keys[i] = key;
234 | }
235 | decodeTime(clip, keys);
236 | decodeDebugData(keys);
237 | curve.keys = keys;
238 | SmoothTangents(curve,length);
239 | return curve;
240 | }
241 | }
242 | }
--------------------------------------------------------------------------------
/Assets/AnimCompression/CurveDataBasicValues.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: ccb7331e5c18c4c4fbb767fcda7711fa
3 | timeCreated: 1528789591
4 | licenseType: Pro
5 | MonoImporter:
6 | serializedVersion: 2
7 | defaultReferences: []
8 | executionOrder: 0
9 | icon: {instanceID: 0}
10 | userData:
11 | assetBundleName:
12 | assetBundleVariant:
13 |
--------------------------------------------------------------------------------
/Assets/AnimCompression/CurveDataCalcTan.cs:
--------------------------------------------------------------------------------
1 | //#define ANIM_CMP_DEBUG_DATA
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using UnityEngine;
5 | using System;
6 |
7 |
8 | namespace AnimCompression
9 | {
10 | ///
11 | /// 不存储切线的曲线数据(运行时计算)
12 | ///
13 | [Serializable]
14 | public class CurveDataCalcTan : CurveDataBasicValues
15 | {
16 | protected override void SmoothTangents(AnimationCurve curve, int keys)
17 | {
18 | for (int i = 0; i < keys; i++)
19 | {
20 | curve.SmoothTangents(i, 0f);
21 | }
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/Assets/AnimCompression/CurveDataCalcTan.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: c6b24c3b40d9d444b88a29a5e6c2a1ec
3 | timeCreated: 1528789591
4 | licenseType: Pro
5 | MonoImporter:
6 | serializedVersion: 2
7 | defaultReferences: []
8 | executionOrder: 0
9 | icon: {instanceID: 0}
10 | userData:
11 | assetBundleName:
12 | assetBundleVariant:
13 |
--------------------------------------------------------------------------------
/Assets/AnimCompression/CurveDataStoreTan.cs:
--------------------------------------------------------------------------------
1 | //#define ANIM_CMP_DEBUG_DATA
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using UnityEngine;
5 | using System;
6 |
7 |
8 | namespace AnimCompression
9 | {
10 | ///
11 | /// 存储切线的曲线数据
12 | ///
13 | [Serializable]
14 | public class CurveDataStoreTan : CurveDataBasicValues
15 | {
16 | public float[] keysTans;
17 | protected override void InitEncode(CompressedClipData clip, AnimationCurve curve, List keys)
18 | {
19 | base.InitEncode(clip, curve, keys);
20 | keysTans = new float[keys.Count];
21 | }
22 | protected override void EncodeFrameValue(int keyIndex, ref Keyframe keyframe)
23 | {
24 | base.EncodeFrameValue(keyIndex, ref keyframe);
25 | keysTans[keyIndex] = keyframe.inTangent;
26 | }
27 | protected override void DecodeFrame(int keyIndex, ref Keyframe keyframe)
28 | {
29 | base.DecodeFrame(keyIndex, ref keyframe);
30 | keyframe.inTangent = keysTans[keyIndex];
31 | keyframe.outTangent = keysTans[keyIndex];
32 | }
33 | protected override void SmoothTangents(AnimationCurve curve, int keys)
34 | {
35 | //自己存储了Tan数据,不再需要SmoothTangents
36 | //base.SmoothTangents(curve, keys);
37 | }
38 |
39 | }
40 | }
--------------------------------------------------------------------------------
/Assets/AnimCompression/CurveDataStoreTan.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e5020900f79cb474ab34631636a0d7e0
3 | timeCreated: 1528789591
4 | licenseType: Pro
5 | MonoImporter:
6 | serializedVersion: 2
7 | defaultReferences: []
8 | executionOrder: 0
9 | icon: {instanceID: 0}
10 | userData:
11 | assetBundleName:
12 | assetBundleVariant:
13 |
--------------------------------------------------------------------------------
/Assets/AnimCompression/Editor.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 9a7573b5a468e9942aa9f4d560f3124c
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/AnimCompression/Editor/AnimatorCompressUtils.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 | using UnityEditor;
5 | using UnityEditor.Animations;
6 | using System.IO;
7 | using System;
8 |
9 | namespace AnimCompression
10 | {
11 | ///
12 | /// 动画压缩编辑时工具
13 | ///
14 | public class AnimatorCompressionUtils
15 | {
16 | static HashSet UncompressFields = new HashSet() {
17 | "m_IsActive"
18 | };
19 | const string CompressedAnimatorSuffix = ".anim_cpr";
20 | const string StoreDefaultStateName = "anim_cpr_gen_dft_store";
21 | [MenuItem("Tools/Build Bundle Test")]
22 | static void buildBundles()
23 | {
24 | BuildPipeline.BuildAssetBundles("bundles/", BuildAssetBundleOptions.ChunkBasedCompression, BuildTarget.StandaloneWindows);
25 | }
26 |
27 |
28 | static string ConvertAnimatorState(AnimatorState state, HashSet stateMachineAllFields)
29 | {
30 | return new ClipCompress(state, stateMachineAllFields).Process();
31 | }
32 | static string ToCompressedAnimatorPath(string rawPath)
33 | {
34 | var fileDir = Path.GetDirectoryName(rawPath);
35 | var fileName = Path.GetFileName(rawPath);
36 | if (fileName.Contains(CompressedAnimatorSuffix))
37 | {
38 | //已经是压缩格式
39 | return null;
40 | }
41 |
42 | var assetName = Path.GetFileNameWithoutExtension(rawPath);
43 | var fileSuffix = Path.GetExtension(rawPath);
44 | return fileDir + "/" + assetName + CompressedAnimatorSuffix + fileSuffix;
45 | }
46 | static string ToRawAnimatorPath(string compPath)
47 | {
48 | var fileDir = Path.GetDirectoryName(compPath);
49 | var fileName = Path.GetFileName(compPath);
50 | if (!fileName.Contains(CompressedAnimatorSuffix))
51 | {
52 | return null;
53 | }
54 | fileName = fileName.Replace(CompressedAnimatorSuffix, "");
55 | return fileDir + "/" + fileName;
56 | }
57 | static void ConvertAnimator(AnimatorController rawAnimator)
58 | {
59 | //AssetDatabase.StartAssetEditing();
60 | string rawPath = AssetDatabase.GetAssetPath(rawAnimator);
61 | //var fileDir = Path.GetDirectoryName(rawPath);
62 | //var fileName = Path.GetFileNameWithoutExtension(rawPath);
63 | //var fileSuffix = Path.GetExtension(rawPath);
64 |
65 | string newAnimatorPath = ToCompressedAnimatorPath(rawPath);// fileDir +"/" + fileName+"_compressed"+ fileSuffix;
66 | if (newAnimatorPath == null)
67 | {
68 | rawPath = ToRawAnimatorPath(rawPath);
69 | rawAnimator = AssetDatabase.LoadAssetAtPath(rawPath);
70 | newAnimatorPath = ToCompressedAnimatorPath(rawPath);
71 | }
72 | AssetDatabase.DeleteAsset(newAnimatorPath);
73 | File.Copy(rawPath, newAnimatorPath,true);
74 | AssetDatabase.ImportAsset(newAnimatorPath);
75 | //CompressedShareData shareData = ScriptableObject.CreateInstance();
76 | AnimatorController animator = AssetDatabase.LoadAssetAtPath(newAnimatorPath);
77 | //shareData.rawAnimatorGUID = AssetDatabase.AssetPathToGUID(rawPath);
78 | //string sharedDataPath = dataDir + "shared_data.asset";
79 | //AssetDatabase.CreateAsset(shareData, sharedDataPath);
80 | //shareData = AssetDatabase.LoadAssetAtPath(sharedDataPath);
81 | var stateMachine = animator.layers[0].stateMachine;
82 | var defaultState = stateMachine.defaultState;
83 | var childStates = stateMachine.states;
84 | //当前状态机的所有字段,用于还原Write defaults效果
85 | //动画被替换后,所有字段都会被移除,这将破坏Write defaults效果
86 | HashSet stateMachineAllBinding = new HashSet();
87 | stateMachine.AddStateMachineBehaviour();
88 | List datasPathList = new List();
89 | foreach (var cstate in childStates)
90 | {
91 | //跳过入口
92 | if (defaultState == cstate.state)
93 | {
94 | //添加一个不包含数据的Proxy
95 | cstate.state.AddStateMachineBehaviour();
96 | continue;
97 | }
98 | var dataPath = ConvertAnimatorState(cstate.state,stateMachineAllBinding);
99 | if (!string.IsNullOrEmpty(dataPath))
100 | {
101 | datasPathList.Add(dataPath);
102 | }
103 | }
104 | MakeStoreDefaultState(stateMachine, stateMachineAllBinding, rawPath);
105 |
106 | AssetDatabase.SaveAssets();
107 | //EditorApplication.delayCall += () => {
108 | // ApplyShareData(sharedDataPath, datasPathList);
109 | //};
110 | //AssetDatabase.StopAssetEditing();
111 | // ApplyShareData(sharedDataPath,datasPathList);
112 | }
113 | ///
114 | /// 创建一个用于还原Write defaults的 state
115 | ///
116 | ///
117 | ///
118 | private static void MakeStoreDefaultState(AnimatorStateMachine stateMachine, HashSet stateMachineAllBinding,string animatorPath)
119 | {
120 | string animatorDir = Path.GetDirectoryName(animatorPath);
121 | string animatorName = Path.GetFileNameWithoutExtension(animatorPath);
122 |
123 | string defaultsClipPath = animatorDir + "/" + animatorName + ".defaults.anim";
124 |
125 | //查找是否已经存在
126 | AnimatorState storeDefaultsState = null;
127 | foreach(var state in stateMachine.states)
128 | {
129 | if(state.state.name == StoreDefaultStateName)
130 | {
131 | storeDefaultsState = state.state;
132 | }
133 | }
134 | if (!storeDefaultsState)
135 | {
136 | storeDefaultsState = stateMachine.AddState(StoreDefaultStateName);
137 | }
138 |
139 | //创建一个用于还原Write defaults的clip
140 | AnimationClip defaultsClip = new AnimationClip();
141 | defaultsClip.name = "StoreDefaultStateName";
142 | foreach (var binding in stateMachineAllBinding)
143 | {
144 | AnimationCurve defaultsCurve = new AnimationCurve();
145 | defaultsCurve.AddKey(new Keyframe(0,0));
146 | if(binding.propertyName == "m_LocalRotation.w")
147 | {
148 | defaultsCurve.AddKey(new Keyframe(1, 0));
149 | }
150 | AnimationUtility.SetEditorCurve(defaultsClip, binding, defaultsCurve);
151 | }
152 | AssetDatabase.CreateAsset(defaultsClip,defaultsClipPath);
153 | defaultsClip = AssetDatabase.LoadAssetAtPath(defaultsClipPath);
154 | storeDefaultsState.motion = defaultsClip;
155 | }
156 |
157 |
158 | //private static void ApplyShareData(string sharedDataPath, List datasPathList)
159 | //{
160 | // CompressedShareData shareData = AssetDatabase.LoadAssetAtPath(sharedDataPath);
161 | // foreach(var dataPath in datasPathList)
162 | // {
163 | // CompressedClipData clipData = AssetDatabase.LoadAssetAtPath(dataPath);
164 | // clipData.shareData = shareData;
165 | // }
166 | // AssetDatabase.SaveAssets();
167 | //}
168 |
169 | ///将动画还原
170 | [MenuItem("Tools/Revert Compressed Animator")]
171 | static void RevertCompressedAnimator()
172 | {
173 | List gameObjects = new List();
174 | foreach (var obj in Selection.GetFiltered(typeof(GameObject), SelectionMode.Assets))
175 | {
176 | GameObject gameObject = (GameObject)obj;
177 | gameObjects.Add(gameObject);
178 | var animators = gameObject.GetComponentsInChildren();
179 | foreach (var animator in animators)
180 | {
181 | AnimatorController controller = (AnimatorController)animator.runtimeAnimatorController;
182 | var path = AssetDatabase.GetAssetPath(controller);
183 | var rawPath = ToRawAnimatorPath(path);
184 | if (rawPath != null)
185 | {
186 | AnimatorController rawController = AssetDatabase.LoadAssetAtPath(rawPath);
187 | if (rawController)
188 | {
189 | AnimatorController.SetAnimatorController(animator,rawController);
190 | }
191 | }
192 | }
193 | }
194 | }
195 | ///
196 | /// 将选择的Animator 或者prefab转换成压缩动画
197 | /// 转换后prefab内部的Animator组件将指向压缩后的动画
198 | ///
199 | [MenuItem("Tools/Compress Selected Animator")]
200 | static void CompressSelectedAnimator()
201 | {
202 | HashSet allController = new HashSet();
203 | EditorUtility.DisplayProgressBar("压缩动画", "准备", 0);
204 | try
205 | {
206 | foreach (var animator in Selection.GetFiltered(typeof(AnimatorController), SelectionMode.Assets))
207 | {
208 | allController.Add((AnimatorController)animator);
209 | }
210 | List gameObjects = new List();
211 | foreach (var obj in Selection.GetFiltered(typeof(GameObject), SelectionMode.Assets))
212 | {
213 | GameObject gameObject = (GameObject)obj;
214 | gameObjects.Add(gameObject);
215 | var animators = gameObject.GetComponentsInChildren();
216 | foreach (var animator in animators)
217 | {
218 | AnimatorController controller = (AnimatorController)animator.runtimeAnimatorController;
219 | allController.Add(controller);
220 | }
221 | }
222 | float i = 0;
223 | foreach (var animator in allController)
224 | {
225 | EditorUtility.DisplayProgressBar("压缩动画", "处理"+ animator, i/ allController.Count);
226 | ConvertAnimator(animator);
227 | i++;
228 | }
229 |
230 | ApplyAnimator(gameObjects, true);
231 | }
232 | finally
233 | {
234 | EditorUtility.ClearProgressBar();
235 | }
236 |
237 | }
238 |
239 | static void ApplyAnimator(List gameObjects,bool toCompression)
240 | {
241 | foreach (var gameObject in gameObjects)
242 | {
243 | var animators = gameObject.GetComponentsInChildren();
244 | foreach (var animator in animators)
245 | {
246 | AnimatorController controller = (AnimatorController)animator.runtimeAnimatorController;
247 | string path = AssetDatabase.GetAssetPath(controller);
248 | string convertPath = toCompression ?ToCompressedAnimatorPath(path) : ToRawAnimatorPath(path);
249 | if (convertPath != null)
250 | {
251 | var compController = AssetDatabase.LoadAssetAtPath(convertPath);
252 | if (compController)
253 | {
254 | Debug.LogWarning("替换Animator "+ animator);
255 | AnimatorController.SetAnimatorController(animator, compController);
256 | if (!animator.GetComponent())
257 | {
258 | animator.gameObject.AddComponent();
259 | }
260 |
261 | }
262 | }
263 | }
264 | }
265 | AssetDatabase.SaveAssets();
266 | }
267 |
268 |
269 |
270 | static void CompressSelectedAnimatorPrefabs()
271 | {
272 |
273 | }
274 | }
275 | }
276 |
--------------------------------------------------------------------------------
/Assets/AnimCompression/Editor/AnimatorCompressUtils.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 5355a31f1f6829041848cd9c48b1a120
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/AnimCompression/Editor/ClipCompress.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 | using UnityEditor;
5 | using UnityEditor.Animations;
6 | using System.IO;
7 | using System;
8 |
9 | namespace AnimCompression
10 | {
11 | ///
12 | /// 动画压缩编辑时工具
13 | ///
14 | public class ClipCompress
15 | {
16 | static HashSet UncompressFields = new HashSet() {
17 | "m_IsActive"
18 | };
19 |
20 | CompressionAnimatorStateFlag stateFlag;
21 | AnimatorState animatorState;
22 | HashSet stateMachineFields;
23 |
24 | CompressedShareData shareData;
25 | AnimationClip rawClip;
26 | AnimationClip stubClip;
27 | CompressedClipData clipData;
28 |
29 | public ClipCompress(AnimatorState state, HashSet stateMachineFields)
30 | {
31 | this.animatorState = state;
32 | this.stateMachineFields = stateMachineFields;
33 | }
34 | private static CompressionAnimatorStateFlag findStateFlag(AnimatorState animatorState)
35 | {
36 | //查找标记,检查是否可以跳过
37 | foreach (var behaviour in animatorState.behaviours)
38 | {
39 | var flag = behaviour as CompressionAnimatorStateFlag;
40 | if (flag)
41 | {
42 | return flag;
43 | }
44 | }
45 | return null;
46 | }
47 | public string Process()
48 | {
49 | this.rawClip = animatorState.motion as AnimationClip;
50 | if (!rawClip)
51 | {
52 | return null;
53 | }
54 | this.stateFlag = findStateFlag(animatorState);
55 | //查找标记,检查是否可以跳过
56 | if (stateFlag && stateFlag.skipCompressed)
57 | {
58 | return null;
59 | }
60 | var size = UnityEngine.Profiler.GetRuntimeMemorySize(rawClip);
61 | //小于16K 不压缩
62 | if (size < 1024 * 16)
63 | {
64 | return null;
65 | }
66 |
67 | string rawClipPath = AssetDatabase.GetAssetPath(rawClip);
68 | if (string.IsNullOrEmpty(rawClipPath))
69 | {
70 | return null;
71 | }
72 |
73 | string anim_gen_dir = Path.GetDirectoryName(rawClipPath) + "/_anim_gen_";
74 | string dataDir = anim_gen_dir + "/datas";
75 | string stubDir = anim_gen_dir + "/stubs";
76 |
77 | Directory.CreateDirectory(anim_gen_dir);
78 | Directory.CreateDirectory(dataDir);
79 | Directory.CreateDirectory(stubDir);
80 |
81 | this.shareData = getShareData(anim_gen_dir);
82 | this.stubClip = new AnimationClip();
83 | AnimationClipSettings settings = AnimationUtility.GetAnimationClipSettings(rawClip);
84 | stubClip.SetCurve("", typeof(CompressedAnimationDirver), "stubAnimParam", AnimationCurve.Linear(0, 0, rawClip.length, 1));
85 | stubClip.name = rawClip.name + "_stub";
86 | this.clipData = CompressClipData();
87 | AnimationUtility.SetAnimationClipSettings(stubClip, settings);
88 |
89 | //*注意:这里是为了兼容通过AnimationClipSettings设置Loop而Wrap没设置
90 | if (settings.loopTime)
91 | {
92 | clipData.wrapMode = (byte)WrapMode.Loop;
93 | stubClip.wrapMode = WrapMode.Loop;
94 | }
95 | else
96 | {
97 | stubClip.wrapMode = rawClip.wrapMode;
98 | }
99 | animatorState.motion = stubClip;
100 | AssetDatabase.CreateAsset(stubClip, stubDir + "/" + stubClip.name + ".anim");
101 | string dataPath = dataDir + "/" + clipData.name + ".asset";
102 | AssetDatabase.CreateAsset(clipData, dataPath);
103 | var stateProxy = animatorState.AddStateMachineBehaviour();
104 | stateProxy.data = clipData;
105 | return dataPath;
106 | }
107 | //压缩Clip数据
108 | public CompressedClipData CompressClipData()
109 | {
110 | CompressedClipData compressingData = ScriptableObject.CreateInstance();
111 | compressingData.name = rawClip.name + "_data";
112 | compressingData.length = rawClip.length;
113 | compressingData.wrapMode = (byte)rawClip.wrapMode;
114 | compressingData.frameRate = (byte)rawClip.frameRate;
115 | //data.shareData = shareData;
116 | EditorCurveBinding[] bindings = AnimationUtility.GetCurveBindings(rawClip);
117 |
118 | for (int i = 0; i < bindings.Length; i++)
119 | {
120 | CompressCurveData(compressingData, bindings[i]);
121 | }
122 | compressingData.shareData = shareData;
123 | EditorUtility.SetDirty(shareData);
124 | return compressingData;
125 | }
126 | //压缩曲线数据
127 | void CompressCurveData(CompressedClipData compressingData,EditorCurveBinding binding)
128 | {
129 | if (binding.isPPtrCurve)
130 | {
131 | //属性帧不压缩,还原到stubClip里
132 | var keyframes = AnimationUtility.GetObjectReferenceCurve(rawClip, binding);
133 | AnimationUtility.SetObjectReferenceCurve(stubClip, binding, keyframes);
134 | }
135 | else if (UncompressFields.Contains(binding.propertyName))
136 | {
137 | //白名单内的属性也不压缩
138 | var curve = AnimationUtility.GetEditorCurve(rawClip, binding);
139 | AnimationUtility.SetEditorCurve(stubClip, binding, curve);
140 | }
141 | else
142 | {
143 | //从共享数据里获取字段ID
144 | var propertyId = shareData.getFieldIndex(binding.path, binding.type.FullName, binding.propertyName);
145 | //
146 | var curve = AnimationUtility.GetEditorCurve(rawClip, binding);// curves[i];
147 | //检测是否需要存储切线数据
148 | bool needStoreTanData = false;
149 | if (stateFlag && stateFlag.rotationTan && binding.propertyName.StartsWith("m_LocalRotation."))
150 | {
151 | needStoreTanData = true;
152 | }
153 |
154 | //选择不同的存储方式
155 | CurveDataBasicValues curveData;
156 | if (needStoreTanData)
157 | {
158 | var storeTanData = new CurveDataStoreTan();
159 | compressingData.curvesStoreTan.Add(storeTanData);
160 | curveData = storeTanData;
161 | }
162 | else
163 | {
164 | var calcTanData = new CurveDataCalcTan();
165 | compressingData.curvesCalcTan.Add(calcTanData);
166 | curveData = calcTanData;
167 | }
168 | curveData.initProperty(propertyId, binding.path, binding.type.FullName, binding.propertyName);
169 | curveData.encodeData(compressingData, curve);
170 | stateMachineFields.Add(binding);
171 | }
172 | }
173 | static CompressedShareData getShareData(string dataDir)
174 | {
175 | string sharedDataPath = dataDir + "/" + "shared_data.asset";
176 | CompressedShareData shareData = AssetDatabase.LoadAssetAtPath(sharedDataPath);
177 | if (!shareData)
178 | {
179 | shareData = ScriptableObject.CreateInstance();
180 | AssetDatabase.CreateAsset(shareData, sharedDataPath);
181 | shareData = AssetDatabase.LoadAssetAtPath(sharedDataPath);
182 | }
183 | return shareData;
184 | }
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/Assets/AnimCompression/Editor/ClipCompress.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 32a8778414acbfe4e84d60b59f14961f
3 | timeCreated: 1528789590
4 | licenseType: Pro
5 | MonoImporter:
6 | serializedVersion: 2
7 | defaultReferences: []
8 | executionOrder: 0
9 | icon: {instanceID: 0}
10 | userData:
11 | assetBundleName:
12 | assetBundleVariant:
13 |
--------------------------------------------------------------------------------
/Assets/AnimCompression/NumberCompressUtils.cs:
--------------------------------------------------------------------------------
1 | //#define ANIM_CMP_DEBUG_DATA
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using UnityEngine;
5 | using System;
6 |
7 |
8 | namespace AnimCompression
9 | {
10 |
11 | public static class NumberCompressUtils
12 | {
13 | public static short EncodeFloat(float value)
14 | {
15 | int cnt = 0;
16 |
17 | while (value != Mathf.Floor(value))
18 | {
19 | value *= 10.0f;
20 | cnt++;
21 | if (cnt > 16)
22 | {
23 | throw new Exception();
24 | }
25 | }
26 | return (short)((cnt << 12) + (int)value);
27 | }
28 |
29 | public static float DecodeFloat(short value)
30 | {
31 | int cnt = value >> 12;
32 | float result = value & 0xfff;
33 | while (cnt > 0)
34 | {
35 | result /= 10.0f;
36 | cnt--;
37 | }
38 | return result;
39 | }
40 |
41 | }
42 | }
--------------------------------------------------------------------------------
/Assets/AnimCompression/NumberCompressUtils.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: d0108ccb1b5b9734d85b1a0b11f12458
3 | timeCreated: 1528854273
4 | licenseType: Pro
5 | MonoImporter:
6 | serializedVersion: 2
7 | defaultReferences: []
8 | executionOrder: 0
9 | icon: {instanceID: 0}
10 | userData:
11 | assetBundleName:
12 | assetBundleVariant:
13 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AnimCompression
2 | 重新定义Unity动画存储格式,剔除不必要的曲线数据,更利于存储的数据结构,极限压缩动画1
3 |
--------------------------------------------------------------------------------