FileSize
MemorySize
BlobSize

红色框内即是BlobSize,在我的理解,FileSize是指文件在硬盘中占的大小,BlobSize是从文件反序列化出来的对象的二进制大小。Editor下的MemorySize不仅有序列化后的内存大小,还维护了一份原文件的内存。就像我们在Editor下加载一张Texture内存是双份一样,而真机下就约等于BlobSize。真机下的MemorySize和Inspector里的BlobSize非常接近,BlobSize可以认为是真机上的内存大小,这个大小更有参考意义。
同时,我也对去除Scale曲线的方法进行了实验。下图这个动画文件原来Inspector中Scale的值为4,即有Scale曲线,原始文件BlobSize为10.2KB,去除Scale曲线后,Blob Size变为7.4KB,所以BlobSize减小了27%。


从上面的实验可以看出来,只裁剪动画文件的压缩精度,没有引起Curve减少。BlobSize是不会有任何变化的,因为每个浮点数固定占32bit。而文件大小、AB大小、Editor下的内存大小,压缩精度后不管有没有引起Curve的变化,都会变小。
裁剪动画文件的精度,意味着点的位置发生了变化,所以Constant Curve和Dense Curve的数量也有可能发生变化。由于是裁剪精度所以动画的点更稀疏了,而连续相同的点更多了。所以Dense Curve是减少了,Constant Curve是增多了,总的内存是减小了。
Constant Curve只需要最左边的点就可以描述一个曲线段。

裁剪精度前,大小为2.2kb,ScaleCurve为0, ConstantCurve为4(57.1%),Stream(使用Optimal模式这部分数据将储存为Dense)为3(42.9%)。

裁剪精度后,大小为2.1kb,ConstantCurve为7(100%),Stream为0(0%)。裁剪完精度后导致ConstantCurve增加了3,Stream(Optimal模式下即为Dense)减少了3,BlobSize减小了0.1kb。

因此,可以看出,通过精度优化降低内存的方式,其实质是将曲线上过于接近的数值(例如相差数值出现在小数点4位以后)直接变为一致,从而使部分曲线变为constant曲线来降低内存消耗。
AnimationClip aniClip = AssetDatabase.LoadAssetAtPath<AnimationClip> (path);
var fileInfo = new System.IO.FileInfo(path);
Debug.Log(fileInfo.Length);//FileSize
Debug.Log(Profiler.GetRuntimeMemorySize (aniClip));//MemorySize
Assembly asm = Assembly.GetAssembly(typeof(Editor));
MethodInfo getAnimationClipStats = typeof(AnimationUtility).GetMethod("GetAnimationClipStats", BindingFlags.Static | BindingFlags.NonPublic);
Type aniclipstats = asm.GetType("UnityEditor.AnimationClipStats");
FieldInfo sizeInfo = aniclipstats.GetField ("size", BindingFlags.Public | BindingFlags.Instance);
var stats = getAnimationClipStats.Invoke(null, new object[]{aniClip});
Debug.Log(EditorUtility.FormatBytes((int)sizeInfo.GetValue(stats)));//BlobSize
最后附上工具的代码和简要使用说明,选中想要优化的文件夹或文件,右键Animation->裁剪浮点数去除Scale。
//****************************************************************************
//
// File: OptimizeAnimationClipTool.cs
//
// Copyright (c) SuiJiaBin
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
//****************************************************************************
using System;
using System.Collections.Generic;
using UnityEngine;
using System.Reflection;
using UnityEditor;
using System.IO;
using UnityEngine.Profiling;
namespace EditorTool
{
class AnimationOpt
{
static Dictionary<uint, string> _FLOAT_FORMAT;
static MethodInfo getAnimationClipStats;
static FieldInfo sizeInfo;
static object[] _param = new object[1];
static AnimationOpt()
{
_FLOAT_FORMAT = new Dictionary<uint, string>();
for (uint i = 1; i < 6; i++)
{
_FLOAT_FORMAT.Add(i, "f" + i.ToString());
}
Assembly asm = Assembly.GetAssembly(typeof(Editor));
getAnimationClipStats = typeof(AnimationUtility).GetMethod("GetAnimationClipStats", BindingFlags.Static | BindingFlags.NonPublic);
Type aniclipstats = asm.GetType("UnityEditor.AnimationClipStats");
sizeInfo = aniclipstats.GetField("size", BindingFlags.Public | BindingFlags.Instance);
}
AnimationClip _clip;
string _path;
public string path { get { return _path; } }
public long originFileSize { get; private set; }
public int originMemorySize { get; private set; }
public int originInspectorSize { get; private set; }
public long optFileSize { get; private set; }
public int optMemorySize { get; private set; }
public int optInspectorSize { get; private set; }
public AnimationOpt(string path, AnimationClip clip)
{
_path = path;
_clip = clip;
_GetOriginSize();
}
void _GetOriginSize()
{
originFileSize = _GetFileZie();
originMemorySize = _GetMemSize();
originInspectorSize = _GetInspectorSize();
}
void _GetOptSize()
{
optFileSize = _GetFileZie();
optMemorySize = _GetMemSize();
optInspectorSize = _GetInspectorSize();
}
long _GetFileZie()
{
FileInfo fi = new FileInfo(_path);
return fi.Length;
}
int _GetMemSize()
{
return Profiler.GetRuntimeMemorySize(_clip);
}
int _GetInspectorSize()
{
_param[0] = _clip;
var stats = getAnimationClipStats.Invoke(null, _param);
return (int)sizeInfo.GetValue(stats);
}
void _OptmizeAnimationScaleCurve()
{
if (_clip != null)
{
//去除scale曲线
foreach (EditorCurveBinding theCurveBinding in AnimationUtility.GetCurveBindings(_clip))
{
string name = theCurveBinding.propertyName.ToLower();
if (name.Contains("scale"))
{
AnimationUtility.SetEditorCurve(_clip, theCurveBinding, null);
Debug.LogFormat("关闭{0}的scale curve", _clip.name);
}
}
}
}
void _OptmizeAnimationFloat_X(uint x)
{
if (_clip != null && x > 0)
{
//浮点数精度压缩到f3
AnimationClipCurveData[] curves = null;
curves = AnimationUtility.GetAllCurves(_clip);
Keyframe key;
Keyframe[] keyFrames;
string floatFormat;
if (_FLOAT_FORMAT.TryGetValue(x, out floatFormat))
{
if (curves != null && curves.Length > 0)
{
for (int ii = 0; ii < curves.Length; ++ii)
{
AnimationClipCurveData curveDate = curves[ii];
if (curveDate.curve == null || curveDate.curve.keys == null)
{
//Debug.LogWarning(string.Format("AnimationClipCurveData {0} don't have curve; Animation name {1} ", curveDate, animationPath));
continue;
}
keyFrames = curveDate.curve.keys;
for (int i = 0; i < keyFrames.Length; i++)
{
key = keyFrames[i];
key.value = float.Parse(key.value.ToString(floatFormat));
key.inTangent = float.Parse(key.inTangent.ToString(floatFormat));
key.outTangent = float.Parse(key.outTangent.ToString(floatFormat));
keyFrames[i] = key;
}
curveDate.curve.keys = keyFrames;
_clip.SetCurve(curveDate.path, curveDate.type, curveDate.propertyName, curveDate.curve);
}
}
}
else
{
Debug.LogErrorFormat("目前不支持{0}位浮点", x);
}
}
}
public void Optimize(bool scaleOpt, uint floatSize)
{
if (scaleOpt)
{
_OptmizeAnimationScaleCurve();
}
_OptmizeAnimationFloat_X(floatSize);
_GetOptSize();
}
public void Optimize_Scale_Float3(bool scaleOpt)
{
Optimize(scaleOpt, 3);
}
public void LogOrigin()
{
_logSize(originFileSize, originMemorySize, originInspectorSize);
}
public void LogOpt()
{
_logSize(optFileSize, optMemorySize, optInspectorSize);
}
public void LogDelta()
{
}
void _logSize(long fileSize, int memSize, int inspectorSize)
{
Debug.LogFormat("{0} \nSize=[ {1} ]", _path, string.Format("FSize={0} ; Mem->{1} ; inspector->{2}",
EditorUtility.FormatBytes(fileSize), EditorUtility.FormatBytes(memSize), EditorUtility.FormatBytes(inspectorSize)));
}
}
public class OptimizeAnimationClipTool
{
static List<AnimationOpt> _AnimOptList = new List<AnimationOpt>();
static List<string> _Errors = new List<string>();
static int _Index = 0;
[MenuItem("Assets/Animation/裁剪浮点数去除Scale(会删除Clip Scale动画,慎用)")]
public static void OptimizeScale()
{
_AnimOptList = FindAnims();
if (_AnimOptList.Count > 0)
{
_Index = 0;
_Errors.Clear();
//EditorApplication.update = ScanAnimationClip;
ScanAnimationClip(true);
}
}
[MenuItem("Assets/Animation/裁剪浮点数")]
public static void Optimize()
{
_AnimOptList = FindAnims();
if (_AnimOptList.Count > 0)
{
_Index = 0;
_Errors.Clear();
//EditorApplication.update = ScanAnimationClip;
ScanAnimationClip(true);
}
}
private static void ScanAnimationClip(bool scaleOpt)
{
AnimationOpt _AnimOpt = _AnimOptList[_Index];
bool isCancel = EditorUtility.DisplayCancelableProgressBar("优化AnimationClip", _AnimOpt.path, (float)_Index / (float)_AnimOptList.Count);
_AnimOpt.Optimize_Scale_Float3(scaleOpt);
_Index++;
if (isCancel || _Index >= _AnimOptList.Count)
{
EditorUtility.ClearProgressBar();
Debug.Log(string.Format("--优化完成-- 错误数量: {0} 总数量: {1}/{2} 错误信息↓:\n{3}\n----------输出完毕----------", _Errors.Count, _Index, _AnimOptList.Count, string.Join(string.Empty, _Errors.ToArray())));
Resources.UnloadUnusedAssets();
GC.Collect();
AssetDatabase.SaveAssets();
EditorApplication.update = null;
_AnimOptList.Clear();
_cachedOpts.Clear();
_Index = 0;
}
}
static Dictionary<string, AnimationOpt> _cachedOpts = new Dictionary<string, AnimationOpt>();
static AnimationOpt _GetNewAOpt(string path)
{
AnimationOpt opt = null;
if (!_cachedOpts.ContainsKey(path))
{
AnimationClip clip = AssetDatabase.LoadAssetAtPath<AnimationClip>(path);
if (clip != null)
{
opt = new AnimationOpt(path, clip);
_cachedOpts[path] = opt;
}
}
return opt;
}
static List<AnimationOpt> FindAnims()
{
string[] guids = null;
List<string> path = new List<string>();
List<AnimationOpt> assets = new List<AnimationOpt>();
UnityEngine.Object[] objs = Selection.GetFiltered(typeof(object), SelectionMode.Assets);
if (objs.Length > 0)
{
for (int i = 0; i < objs.Length; i++)
{
if (objs[i].GetType() == typeof(AnimationClip))
{
string p = AssetDatabase.GetAssetPath(objs[i]);
AnimationOpt animopt = _GetNewAOpt(p);
if (animopt != null)
assets.Add(animopt);
}
else
path.Add(AssetDatabase.GetAssetPath(objs[i]));
}
if (path.Count > 0)
guids = AssetDatabase.FindAssets(string.Format("t:{0}", typeof(AnimationClip).ToString().Replace("UnityEngine.", "")), path.ToArray());
else
guids = new string[] { };
}
for (int i = 0; i < guids.Length; i++)
{
string assetPath = AssetDatabase.GUIDToAssetPath(guids[i]);
AnimationOpt animopt = _GetNewAOpt(assetPath);
if (animopt != null)
assets.Add(animopt);
}
return assets;
}
}
}
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
Unity自动旋转动画1.开门需要门把手先动,门再动2.关门需要门先动,门把手再动3.中途播放过程中不可以再次进行操作觉得太复杂?查看我的文章开关门简易进阶版效果:如果这个门可以直接打开的话,就不需要放置"门把手"如果门把手还有钥匙需要旋转,那就可以把钥匙放在门把手的"门把手",理论上是可以无限套娃的可调整参数有:角度,反向,轴向,速度运行时点击Test进行测试自己写的代码比较垃圾,命名与结构比较拉,高手轻点喷,新手有类似的需求可以拿去做参考上代码usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;u
是否有任何可用于Ruby的开源压缩/解压库?有没有人实现过LZW?或者,是否有任何使用压缩组件的开源库可以提取出来独立使用?编辑——感谢您的回答!我应该提到我必须压缩的是只驻留在数据库中的长字符串(我不会压缩文件)。此外,如果可以执行此操作的任何库都具有用于客户端压缩/分解的等效JavaScript实现,那将是理想的,因为这将用于Web应用程序。 最佳答案 您会在rubystdlib下找到所有已交付的ruby库的一个很好的列表.我会使用zlib库,它是开放的,无处不在,您会发现几乎所有语言的库!
我有一个包含100多个zip文件的目录,我需要读取zip文件中的文件以进行一些数据处理,而无需解压缩存档。是否有一个Ruby库可以在不解压缩文件的情况下读取zip存档中的文件内容?使用rubyzip报错:require'zip'Zip::File.open('my_zip.zip')do|zip_file|#Handleentriesonebyonezip_file.eachdo|entry|#Extracttofile/directory/symlinkputs"Extracting#{entry.name}"entry.extract('here')#Readintomemoryc
动画/*INITIALIZEANANIMATION 初始化一个动画*-----------------------*/lv_anim_ta;lv_anim_init(&a);/*MANDATORYSETTINGS 必选设置*------------------*//*Setthe"animator"function 设置“动画”功能*/lv_anim_set_exec_cb(&a,(lv_anim_exec_xcb_t)lv_obj_set_x);/*Setthe"animator"function*/lv_anim_set_var(&a,obj);/*Lengthoftheanim
我有一个正在HerokuCedar堆栈上部署的Rails3.2应用程序。这意味着应用程序本身负责为其静态Assets提供服务。我希望对这些Assets进行gzip压缩,所以我在production.rb的中间件堆栈中插入了Rack::Deflater:middleware.insert_after('Rack::Cache',Rack::Deflater)...curl告诉我这与宣传的一样有效。但是,由于Heroku将全力运行rakeassets:precompile,生成一堆预gzipAssets,我很想使用它们(而不是让Rack::Deflater再次完成所有工作)。我已经看到使用
我想知道与使用native操作系统库执行压缩相比,使用rubyzip压缩数据时的性能差异是什么。我正在从URL获取要压缩的数据,然后使用ZipOutputStream创建zip文件。对于native操作系统实用程序,我正在考虑使用zip工具。很高兴听到这两种方法的优缺点。 最佳答案 事实证明,无论是运算时间还是CPU使用率,都没有太大差异。但是在内存使用方面存在显着差异。与使用ziputil相比,使用rubyzip的过程最终会使用更多的内存。在我们的用例中,内存使用是一个重要问题,因此我们最终使用了zip实用程序。
我知道如何使用rubyzip检索普通zip文件的内容。但是我在解压缩压缩文件夹的内容时遇到了问题,我希望你们中的任何人都能帮助我。这是我用来解压的代码:Zip::ZipFile::open(@file_location)do|zip|zip.eachdo|entry|nextifentry.name=~/__MACOSX/orentry.name=~/\.DS_Store/or!entry.file?logger.debug"#{entry.name}"@data=File.new("#{Rails.root.to_s}/tmp/#{entry.name}")endendentry
我想每周更新一个城市表以反射(reflect)世界各地城市的变化。为此,我正在创建一个Rake任务。如果可能,我希望在不添加其他gem依赖项的情况下执行此操作。压缩文件是在geonames.org/15000cities.zip上公开可用的压缩文件.我的尝试:require'net/http'require'zip'namespace:geocitiesdodesc"RaketasktofetchGeocitiescitylistevery3days"task:fetchdouri=URI('http://download.geonames.org/export/dump/cities
假设我有这个:[{:user_id=>1,:search_id=>a},{:user_id=>1,:search_id=>b},{:user_id=>2,:search_id=>c},{:user_id=>2,:search_id=>d}]我想结束:[{:user_id=>1,:search_id=>[a,b]},{:user_id=>2,:search_id=>[c,d]}]最好的方法是什么? 最佳答案 确实是非常奇怪的要求。无论如何[{:user_id=>1,:search_id=>"a"},{:user_id=>1,:sear