├── 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 | --------------------------------------------------------------------------------