- 当变量重命名后,已序列化保存的值会丢失,如果希望继续保留其数值,可使用FormerlySerializedAs,如下代码所示:
[UnityEngine.Serialization.FormerlySerializedAs("hp")] public int newHp = 20;
-
Unity有个隐藏的彩蛋:void Main(){},它的调用在Awake() OnEnable()之后Start()之前。
- unity对文件名以点开头的文件视而不见
- 当用enum枚举值作为Dictionary的key,在访问字典时则会产生GC。枚举是值类型的它被当作为整形使用,而Dictionary每次判断key时都会调用
Object.getHashCode(Object),
枚举会被调用到基类引用类型Object的该方法从而导致装箱产生GC,其他如int等值类型因自己的getHashCode
不会调用到Object上的方法则作为key不会有GC,建议在需要用到enum作为key时先强转为int再将int作为key,或者在Dictionary的构造函数内传入自定义的IEqualityComparer:
1 public class MyEnumComparer : IEqualityComparer<MyEnum> { 2 public bool Equals(MyEnum x, MyEnum y) { 3 return x == y; 4 } 5 public int GetHashCode(MyEnum x) { 6 return (int)x; 7 } 8 }
-
C#属性(Property)想要显示在Inspector上且set get能被正常执行:https://github.com/LMNRY/SetProperty
[SerializeField, SetProperty ("Number")] private float number; public float Number { get { return number; } private set { number = Mathf.Clamp01(value); } }
- 当你在Unity中编辑场景,突然死机时,可以在项目文件目录中找到Temp文件夹,双击文件夹,找到_Backupscenes文件夹,把后缀为.backup的文件后缀改为.unity,然后拖进Unity的Project界面里面,这样就可以还原死机前场景最后情况。
- 通过Debug.Log获取执行此语句物件:在脚本的Debug.Log语句中加入gameObject,即Debug.Log("Test", gameObject); 脚本运行时点击Console界面中的输出语句,就能在Hierarchy界面中看到哪个物件执行了这个脚本。
- Debug.Break()可以在任何地方暂停游戏,调试小技巧。
- 性能调试时将你想观察的那部分代码放入
Profiler.BeginSample ("aaa");
Profiler.EndSample ();
代码之间就可指定查看该部分代码的开销。 - 当修改了Prefab并想将该改动应用到所有的物体上:
若是在Project视图中直接修改的该prefab则一定要记得执行File->Save Project,unity并不会将改动直接保存到磁盘。
若是在Hierarchy视图中修改的该prefab,必须点击Apply,若同时还需要保存场景时,则尽量记住先执行Save Project操作再执行Save Scene,不然你场景中的prefab很可能跟它本身的prefab永久丢失关联。 - 屏幕坐标与鼠标位置:
屏幕坐标系以左上角为原点(0, 0),右下角为(Screen.Width, Screen.Height)。
Input.mousePosition鼠标位置以屏幕左下角为原点(0, 0),屏幕右上角为(Screen.Width, Screen.Height)。 - Awake会在物体初始化之后被调用,无论脚本本身是否被启用(是否被禁用,在Inspector视图上脚本前面的复选框是否被勾上,实际上该复选框只有脚本在存在Start(), Update(), FixedUpdate(), and OnGUI()至少一个时才会显示);OnEnable、Start则是在脚本被启动的情况下被调用,其中OnEnable会在脚本或者物体每次被重新启用时都会被再次调用。
- 父物体与子物体Awake、OnEnable、Start函数执行顺序:
首先同一脚本中该三个函数的执行顺序是:Awake->OnEnable->Start,然后:
* 若父子物体都是在场景中已经存在的:子物体的Awake、OnEnable全执行完毕后才会执行到父物体的Awake、OnEnable;当所有父与子物体的Awake与OnEnable全都执行完毕后才开始执行子物体的Start再执行父物体的Start。
* 若父子物体先不存在于场景中,而是通过Instantiate()动态产生的:则父与子的调用顺序与上面相反,父物体的Awake、OnEnable全执行完毕后才会执行到子物体的Awake、OnEnable;当所有父与子物体的Awake与OnEnable全都执行完毕后才开始执行父物体的Start再执行子物体的Start。
该结论是实测多次观察出来的,但unity官方没未明确说明Awake OnEnable Start存在固定顺序,故不排除有时不表现为以上所说顺序而是随机顺序,不应该依赖这些执行顺序而应尽量避免初始化的先后的要求。 - 调用Instantiate()方法动态添加GameObject时,新GameObject的Awake、OnEnable都调用结束后Instantiate()才会返回。
- 一般在新建类时会产生空的Update函数。如果代码不需要用到该函数,应该该函数进行删除。另外,尽量不要在Update函数内执行Find、FindObjectOfType、FindGameObjectsWithTag这些寻找物体的函数,面应该尽量在Start或Awake函数中执行。
- 每个脚本中实现Update()回调函数,这是一般做法,更优的做法是只有一个Manger脚本含有Update函数,遍历(Array性能优于List)调用其他脚本的OnUpdate(或其他命名)函数。Unity要维护调用的Update函数越多开销越大,详细测试见:https://blogs.unity3d.com/cn/2015/12/23/1k-update-calls/ 从评论中了解到,unity正在开发全新的一套消息机制,喜闻乐见。
另一方面,有人想到用Coroutines协程代替Update,测试证明Update比协程要快5倍,协程内部至少要处理move next和current两个调用 - 引用一个游戏对象的逻辑,可以在最开始的地方定义它。例如:
1 private Transform myTransform; 2 private Rigidbody myRigidbody; 3 void Start() 4 { 5 myTransform = transform; 6 myRigidbody = rigidbody; 7 }
- 尽量减少使用临时变量,特别是在Update等实时调用的函数中。
- 捕捉Android返回与Home键:
1 //返回键 2 if(Application.platform == Runtimeplatform.Android 3 && Input.GetKeyDown(KeyCode.Escape)) 4 { 5 //... 6 } 7 8 //Home键 9 if(Application.platform == Runtimeplatform.Android 10 && Input.GetKeyDown(KeyCode.Home)) 11 { 12 //... 13 }
- 直接打开app store,同理根据链接不同也可打开Mail或浏览器等:
void OnRateButtonClick() { #if UNITY_ANDROID Application.OpenURL("market://details?id=YOUR_APP_ID"); #elif UNITY_IPHONE Application.OpenURL("itms-apps://itunes.apple.com/app/idYOUR_APP_ID"); #endif }
- 获取Android手机分辨率:
1 private float androidDensity = 1.0f; 2 void GetDensity() 3 { 4 //#if UNITY_ANDROID 5 if (Application.platform == RuntimePlatform.Android) 6 { 7 AndroidJavaClass player = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); 8 AndroidJavaObject activity = player.GetStatic<AndroidJavaObject>("currentActivity"); 9 AndroidJavaObject wm = activity.Call<AndroidJavaObject>("getWindowManager"); 10 if (wm != null) 11 { 12 AndroidJavaObject display = wm.Call<AndroidJavaObject>("getDefaultDisplay"); 13 if (display != null) 14 { 15 AndroidJavaObject displayMetrics = new AndroidJavaObject("android.util.DisplayMetrics"); 16 display.Call("getMetrics", displayMetrics); 17 androidDensity = displayMetrics.Get<float>("density"); 18 } 19 } 20 } 21 //#endif 22 }
- Time.timeScale设置为0时Update()仍然会继续执行,FixedUpdate()会停止调用,注意:此时协程WaitForSeconds和Invoke调用都会停止!
Time.timeScale会影响Time.deltaTime和Time.time,不会影响Time.realtimeSinceStartup, Time.unscaledDeltaTime, Time.unscaledTime;
Time.time/timeScale = unscaledTime,
Time.deltaTime / timeScale = unscaledDeltaTime;
在Update中使用Time.deltaTime,就可以使用timeScale改变运动速率
在Update中使用Time.unscaledDeltaTime,timeScale就不会影响运动速率
在后台运行(暂停)和卡顿(如加载大场景时进入场景那一刻)期间,Time.deltaTime和Time.time不会继续计时,Time.realtimeSinceStartup, Time.unscaledDeltaTime, Time.unscaledTime会继续计时,此时Time.realtimeSinceStartup更精准会在游戏恢复时立马得到正常时间,Time.unscaledDeltaTime, Time.unscaledTime则可能要等好几帧之后才会得到正常的时间(即暂停期间经过的时间过几帧才加上来)。
当手机调整了设备时间后Time.realtimeSinceStartup也同样会受影响。 - 获取当前animator播放的state时长:anim.GetCurrentAnimatorStateInfo(0).length。然而该函数需要等动画播放后才能正确获取,即使在协程中等待一帧再调用该函数得到的时间也可能是错误的,应该调用anim.Update(0)后再去获取:
m_animator.Play(animName, -1, 0); m_animator.Update(0); int length = m_animator.GetCurrentAnimatorStateInfo(0).length;
若是在5.x版本中则可以通过指定的状态名字获取其时长:anim.runtimeAnimatorController.animationClips.First (x => x.name == "AnimationName").length;
- Animator.Play(string stateName, int layer = -1, float normalizedTime = float.NegativeInfinity):
layer:经测试0和-1是一样的。
normalizedTime:
0-1为从开始到结束之间某时间点开始播放;
负无穷时如果play的是当前状态则继续播放当前状态不做任何改变,否则跳到从头开始播目标状态;
为负数时,时间会保持增长,从负数开始至0期间为暂停的,从0之后开始正常播放;
为超过1的数时则会以小数部分的时间(等同于0-1的参数)点开始播放,忽略整数部分(实际上整数部分代表重复播放了多少遍)。 - 简单实现animator反方向播放动画:animator.speed=-1;
- 发ios包的注意点:
-
在用unity4.6发iOS包的时候,发现导出的xcode工程出错;其实原因是xcode太新了,为7.2, 而unity还只支持7.1,而两者兼容性不好,导致了很多error的出现。所以,发ios包时要注意xcode的版本和unity是否相符,不然会花太多的时间在上面的,切记!
-
图形化调试:
Unity中图形化调试主要4种
Debug.Draw
Gizmos.Draw
Graphic.DrawMesh
GL
只需在Scene窗口显示的调试图像
一直显示的 OnDrawGizmos + Gizmos.Draw
选中显示的 OnDrawGizmosSelected + Gizmos.Draw
脚本控制的 Update + Debug.Draw
需要在实际设备屏幕显示的调试图像
Update+Graphic.DrawMesh
OnRenderObject+GL
Graphic.DrawMesh和Debug.Draw 调用一致,都是在Update系里
Graphic.DrawMesh和GL 显示类似,都在各个窗口显示,并且可以设置材质。
详见:http://blog.sina.com.cn/s/blog_471132920101gxzf.html -
Unity提供了命令行的接口,可以通过Shell调用。进而可以一键打包,一键打AssetBundle,接着上传SVN等操作。文档:http://docs.unity3d.com/Manual/CommandLineArguments.html
- 调试执行时间:
1 Stopwatch sp = new Stopwatch (); 2 sp.Start (); 3 DoSomething (); 4 sp.Stop (); 5 Debug.Log (string.Format ("Elapsed:{0} ms", (float)sp.ElapsedTicks/ Stopwatch.Frequency * 1000f));
- 写Unity编辑器控件时如何获得Unity内置的图标:Texture tex = (Texture)EditorGUIUtility.Load("PlayButton On");或
EditorGUIUtility.IconContent("PlayButton On")这样就可以获得一个蓝色的播放图标。所有的图标名字集合请转至http://www.xuanyusong.com/archives/3777
- 您可以通过使用[MenuItem(“CONTEXT / ...”)添加自定义项目到上下文菜单,即使是内置的类
-
写一个继承自AssetPostporocessor的脚本可在Unity导入或更新各种资源之前或之后增加自定义处理,如更改图片或音频的格式、转换xlsx配置档为bytes文件等等。
- 在使用缓冲池等需要修改对象的父节点时,正确的顺序应该是首先禁用对象,然后将其父对象重置为对象池,而不是先修改父对象再禁用对象,这样会造成不必要的污染。
- ??和?.两个操作符表达式对于继承自UnityEngine.Object的类通常是得不到正确结果的,它是纯C#的null检查,它会绕过Unity内部自定义的==null检查,请谨慎使用或避免使用在内置组件中。
unity为了开发者方便使用调试获得更多错误细节信息,在对C++封装C#时增加了很多额外处理,如果不做这些操作,则用户看到的就是简单的空引用异常的错误,对错误原因和修改方式知之较少。
如GameObject等在C#仅仅是对引擎内部原生C++ Object的封装,C#的内存管理是GC自动处理,C++则是是仅当切换场景或手动调用UnityEngine.Object.Destroy()时清理,故存在C#引用还存在而底层原生代码已被销毁的情况。故unity重载了==操作符可以判断原生Object是否被destoryed,也因此==null的操作比你想像中更费,它要判断处理的事比较复杂:private Transform m_CachedTransform public Transform transform { get { if (m_CachedTransform == null) m_CachedTransform = InternalGetTransform(); return m_CachedTransform; } }
以上代码即会看不出对transform进行缓存有多少性能提升,因==null的操作本身就比较费。
对object进行判空有两种意图,是确保已进行赋值还是检查引用的底层引擎对象生命周期,以下代码意义不明:
var go = gameObject ?? CreateNewGameObject();
当你是想判断实际引用的物体是否被destory需要显示调用==操作符:
var go = gameObject != null ? gameObject : CreateNewGameObject(); // Or use the implicit bool conversion operators for the same check go = gameObject ? gameObject : CreateNewGameObject();
当你想确保变量已被初始化赋了正确的值需要显示调用object.ReferenceEquals()(对null的检查该调用已被编译器优化,且速度快于自定义的==操作符):
return !object.ReferenceEquals(gameObject, null) ? gameObject : CreateNewGameObject();
详情:https://blogs.unity3d.com/2014/05/16/custom-operator-should-we-keep-it/
实际代码实践中发现仅如BoxCollider等内置组件不可使用??和?.表达式,自定义类及UGUI相关组件是没问题的,可能unity5或之后某版本unity已优化。 - LinkedList、List当自定义结构体struct做链表节点,必须实现IEquatable<T>、IComparable<T>接口,否则Remove、Cotains、Find、FindLast每次都有GC产生.