Unity3D 学习笔记

2015-09-09 来源: Jeason1997 发布在  http://www.cnblogs.com/jeason1997/p/4795438.html

不是什么技术文章,纯粹是我个人学习是遇到一些觉得需要注意的要点,当成笔记。

1.关于调试,在Android下无法断点,Debug也无法查看,
查看日志方法可以启动adb的log功能,或者自己写个GUI控件直接在屏幕上显示Info

2.所有自定义的编辑器扩展插件脚本必须放在Editor文件夹里,不然会导致编译程序时出错,
放到Editor文件下,编译成游戏时才会忽略这些脚本

3.打包资源时,假设是在移动设备上使用,打包方式务必选择成:BuildTarget.Android
BuildPipeline.BuildAssetBundle(obj, null, targetPath, BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets | BuildAssetBundleOptions.DeterministicAssetBundle, BuildTarget.Android)
否则到安卓上会读不出资源。PC则无此问题。

4.重新打包资源后,加入在LoadAssetBunldes时没给指定读取新版本,例如原来为:LoadFromCacheOrDownload(path, 1);
现在仍然为LoadFromCacheOrDownload(path, 1);那么是无法读取到新包的。因为系统会先从缓存检查有没有版本为1的同名bundle,如果有,则直接使用缓存的,
如果没有,则读取这个新的包。因此,重新打包文件后,应该给文件包升1级,读取的同时也多读一级。这样才会读出来。

5.如果在测试的时候为了避免频繁打包频繁换级造成麻烦,则可以在每次加载时或者打包时选择手动清空缓存:Caching.CleanCache();这样就会强制读取最新的包。
但千万不要在正式版使用,因为这样是清空全部数据,频繁的读取会造成性能消耗。PC就无所谓了。

6.想要控制磁盘缓存不超标. 只要设置Caching.maximumAvailableDiskSpace 的值为你预期的容量大小就可以(byte为单位)例如, 想要限定200M的缓存空间可以这样:Caching.maximumAvailableDiskSpace = 200*1024*1024;

7.画线可以用LineRenderer,或者直接GL画,或者更方便的可以Debug.DrawLine,甚至可以将物理射线画出Debug.DrawRay,但是Debug画出的线只能在调试模

式看得到,编译成游戏后将不再出现。参见:http://www.cnblogs.com/jeason1997/p/4805825.html

8.判断游戏是否联网

Application.internetReachability == NetworkReachability.NotReachable
NotReachable  网络不可达
ReachableViaCarrierDataNetwork  网络通过运营商数据网络是可达的。
ReachableViaLocalAreaNetwork   网络通过WiFi或有线网络是可达的。

9.UGUI做在人物头上血条的HUD的制作方法

public Transform follow;
Vector2 position = Camera.main.WorldToScreenPoint(follow.position);
img.rectTransform.position = position;//位置
img.rectTransform.localScale = new Vector2(2,2);//大小

10.动态改变相机的Culling Mask

http://answers.unity3d.com/questions/348974/edit-camera-culling-mask.html

11.安卓调式时,连接上手机,然后打开epclise就可以看到locat输出Debug内容了。

12.AssetBundle依赖关系

如果一个公共对象被多个对象依赖,我们打包的时候,可以有两种选取。一种是比较省事的,就是将这个公共对象打包到每个对象中。这样会有很多弊端:内存被浪费了;加入公共对象改变了,每个依赖对象都得重新打包。AssetBundle提供了依赖关系打包。

//启用交叉引用,用于所有跟随的资源包文件,直到我们调用PopAssetDependencies
    BuildPipeline.PushAssetDependencies();

    var options =
        BuildAssetBundleOptions.CollectDependencies |
        BuildAssetBundleOptions.CompleteAssets;

    //所有后续资源将共享这一资源包中的内容,由你来确保共享的资源包是否在其他资源载入之前载入
    BuildPipeline.BuildAssetBundle(
        AssetDatabase.LoadMainAssetAtPath("assets/artwork/lerpzuv.tif"),
        null, "Shared.unity3d", options);

        //这个文件将共享这些资源,但是后续的资源包将无法继续共享它
    BuildPipeline.PushAssetDependencies();
    BuildPipeline.BuildAssetBundle(
        AssetDatabase.LoadMainAssetAtPath("Assets/Artwork/Lerpz.fbx"),
        null, "Lerpz.unity3d", options);
    BuildPipeline.PopAssetDependencies();

    这个文件将共享这些资源,但是后续的资源包将无法继续共享它
    BuildPipeline.PushAssetDependencies();
    BuildPipeline.BuildAssetBundle(
        AssetDatabase.LoadMainAssetAtPath("Assets/Artwork/explosive guitex.prefab"),
        null, "explosive.unity3d", options);
    BuildPipeline.PopAssetDependencies();

    BuildPipeline.PopAssetDependencies();

13.非MonooBehavior脚本实现协程:

一般要实现多线程功能(即协程)时,一般都是在MonoBehavior脚本里StartCoroutine一个返回值为IEnumerator的函数,

但有些时候,需要在非继承自MonoBehaviro的脚本(例如单例等)里也实现多线程效果,就无法通过自身实现了,我采取这样的方法:

public class CoroutineProvider : MonoBehaviour
{
    private static CoroutineProvider instance = null;

    public static CoroutineProvider GetInstance()
    {
        if (instance == null)
        {
            GameObject go = new GameObject();
            go.name = "CoroutineProvider";
            instance = go.AddComponent<CoroutineProvider>();
        }
        return instance;
    }
}

创建一个继承自MonoBehavior的协程提供者,该提供者初始时并不存在,但有地方需要使用到协程时,就通过:

CoroutineProvider.GetInstance().StartCoroutine(Fuction());

这个时候就会在Scene里实例化一个GameObject,以它作为协程提供者的身份出现。

14.创建HTTP服务器并添加自定义格式文件下载支持:

为了让游戏能够自动更新,需每次登陆游戏时都向服务器下载版本文件验证游戏版本,若发现版本不同,

则下载文件更新列表,根据列表下载最新的资源,但默认情况下HTTP只能下载一些默认资源,例如:rar,txt,xml等,

像“.assetbundle”这类的的自定义格式默认情况下HTTP服务器是无法下载的,如果提交请求,例如:http://127.0.0.1/Aseet/Resource.assetbundle,

则结果是404。为了能够下载自定义格式文件,需要配置服务器,比如IIS的做法就是:

打开管理器->点击根节点->在右边找到IIS列表->找到MIME类型->打开并添加自定义格式扩展名

15.寻找游戏中的某个对象

为了降低耦合,尽量避免对象脚本间通过“public value”拖动对象进行互相引用。

但有时候有必须某个对象引用另一个对象,可以通过在脚本中查找对象。

常用的查找方法有:GameObject.Find / FindWithTag 等一系列,这种方法寻找对象虽然最简单,

但是效率却比较低,尽量避免在Update中使用,最好就是在Start和Awake中使用。

还有一种高效的做法,就是将特定对象归类,并集中放到一个List中,使用时查找就快速多了。

16.获取拥有某种类型组件的子gameobject,后面的参数的意思是运行获取不活跃的对象,即active = false

gameObject.GetComponentsInChildren<Component> (true);

17.只删除gameobject 脚本组件会残留

彻底删除方法如下:

GameObject go = new GameObject();

Component c = go.GetComponent<Component >();

MonoBehaviour.DestroyImmediate(c);
MonoBehaviour.DestroyImmediate(go);

18.单例模式慎用,如果一个GameObject对象为单例对象,请小心在其他的GameObject的OnDestory里引用该单例

因为在程序关闭前会执行所有GameObject的OnDestory,但是执行的先后顺序不定。

如果GameObject_1在OnDestory时引用GameObject_2(单例对象),假设GO2的OnDestory先运行,这时候它的

Instance为null,被GO1引用时它会再创建一次。

而如果在OnDestory里创建任何对象,该对象都将不会被UnityEngine释放,一直保留下来。

19.Unity3D 优化技术(3D图形方面)

① http://blog.csdn.net/candycat1992/article/details/42127811

② http://blog.csdn.net/leonwei/article/details/18042603

③ 尽量不要动态的instantiate和destroy object,使用object pool代替,前者的开销很大。

具体参见:利用缓存池解决Instantiate慢的问题用

20.Unity中的.meta文件

每个资源文件(外部导入和内部创建的)对应一个.meta文件。这个.meta文件中的guid就唯一标识这个资源。

内部创建的资源会使用外部导入的资源,比如 内部资源材质Material使用贴图Textures(预制体、场景中使用了更多的资源)。

材质怎么知道自己使用了那些资源呢? 就在自己的文件中记录着其它资源的GUID。

而且每个meta里的标识都是随机产生的,而且同一文件多次生成的meta文件里的标识一不同。

在多人合作项目中,如果你上传资源时没有同步上传.meta文件,那么别人的机器就会为这个文件生成一个,但标识可能和你不同,

导致的结果就是Prefab引用的各种组件丢失。

21.Editor运行时从Scene视图观察对象

在Hierarchy选择要观察的GameObject,鼠标联系点击4下,

这样的话,Scene视图就会将焦点集中在该GameObject身上,

就跟绑定一个摄影机一样,调式的时候非常方便。

22.Profiler分析器

分析器平时只检测关键代码,如果要检测所有代码,就必须深度检测,但深度检测又非常消耗性能。所以有时候只想检测某一小段代码的时候,可以设置检测段:
Profiler.BeginSample ("标签");
// 要检测性能的代码
Profiler.EndSample ();
然后就可以在分析器中找到相应的标签了:

23.Unity 5.3 C# 部分源码

https://bitbucket.org/xuanyusong/unity-decompiled/src/779abf10e556?at=master

备份地址

24.调节脚本执行顺序

Unity的不同脚本间的执行顺序一般是没有规矩的,不同脚本的同一函数,例如Start,启动的顺序也不同。

因此很少在U3D的脚本里的同一函数做先后顺序依赖。例如在脚本A的Start里调用脚本B的某个属性,而该

属性又在脚本B的Start函数里初始化,由于不知道两者的Start的先后顺序,因此一般改为,在脚本B的Awake里

初始化该属性,而不是Start。

但要强制改变脚本的执行顺序也可以:

在这里调节脚本执行顺序,数值越小,越先执行

 edit->projectseeting->script execution order 

25.通过命令行在控制台编译U3D项目

官方Manual教程

示例:

(Unity.exe Path) -batchmode -projectPath (Project Path) -executeMethod MyEditorScript.MyBuildMethod -quit -logFile (Log Path)

UnityPath : Windows: C:\program files\Unity\Editor\Unity.exe

UnityPath : Mac OS: /Applications/Unity/Unity.app/Contents/MacOS/Unity

26.C# 以及 Uniyt3D 添加全局预编译指令

#define DEBUG

#if DEBUG

  dosome;

#endif

呐,这就是预编译指令拉,要注意,这不叫宏,跟C/C++的宏还是有一定差距的。

至于如何定义,在代码里定义的话,当疼的C#只能在每个文件的文件头定义才生效,也就是说,你在test1.cs里定义#define DEBUG的话,

那么你也必须在test2.cs里的头部定义一个才能在test2里生效。如果有一百个文件要使用这个预编译调节,那么你就要手动定义100次。

还好微软留了下后路,其实全局预编译指令可以在项目里的*.CSharp.csproj里设置,

右键项目,在属性里设置即可,或者干脆直接用文本文件打开项目路径下的该文件,直接编辑。

Unity也给我们留了解决方案,不过藏得比较深:

Editor->ProjectSetting->Player->Other->如图红圈处

需要注意的是,Unity里设置的全局预编译,是按平台分开的,

也就是说,你在安卓平台下设置的指令,在PC平台是没法使用的,除非复制过去。

这里有个插件可以方便地设置预编译指令:

源代码:

下载后把中文部分去掉,扔到“Editor”文件夹下,然后打开使用便是

27.Unity3D自定义Debug

Unity的Debug功能比较有限,有时候要自定义一些特殊的Debug,比如封装一个Logger,在Editor平台时打印日志

到控制台,而在其他平台,则打印信息到屏幕或输出到Log文件。功能倒挺容易实现,问题就是,这个封装的Logger底层

部分也是通过Debug实现,在调试的时候要实现Unity控制台那种双击日志就跳到日志输出的地方的功能,就比较麻烦了。

因为就是Logger封装了Debug,双击的时候也不会跳到Logger.Log的地方,而是跳到内部用Debug实现的地方,导致调式

不方便。

解决方法就是,将Logger编译成dll,然后再在Unity内部引用,这时候Debug输出控制台的时候,双击日志就会直接跳到Logger.Log。

28.Unity3D里的特殊文件夹以及脚本编译顺序

第一阶段: Standard Assets, Pro Standard Assets 和Plugins文件夹之内的脚本,编译好后为:Assembly-CSharp-firstpass.dll(C#)

第二阶段: 在第一阶段那些文件夹里任何名字为'Editor'的文件夹里的脚本,例如'Plugins/Editor'

第三阶段: 所有其他不在'Editor'文件夹里的脚本,编译好后为:Assembly-CSharp.dll(C#)

第四阶段: 其他剩余脚本,例如'Editor'文件夹里的脚本(注,不是第二阶段那些在特殊文件夹里的Editor),编译好后为:Assembly-CSharp-Editor.dll(C#)

在U3D里,注意脚本的编译顺序有时候极其重要,例如:

要在C#脚本里引用UnityScprite脚本时,或者反过来,那么被引用的那些脚本,就必须在前一阶段先编译,不然会出现‘找不到脚本’的编译错误。

参考官方链接

29.Unity AssetBundle打包资源的一个坑:

就是Shader不会被打包进去,如果在对象身上挂有Shader,那么加载AB后,会出现Shader丢失的情况。

解决方法就是对通过AB动态加载的对象在Awake时做个判定,看看自身的Shader列表是否为空,如果为空,

则手动从本地加载。

30.通过Application.RegisterLogCallback来捕获Debug输出:

该方法可以监听到unity中所有的log消息,然后你可以选择保存到文件中或者显示到gui上做调试来用。

通过LogType来判断处理哪些类型的消息需要被保存或显示。

LogType如下:

Error

LogType used for Errors.

Assert

LogType used for Asserts. (These indicate an error inside Unity itself.)

Warning

LogType used for Warnings.

Log

LogType used for regular log messages.

Exception

LogType used for Exceptions.

using UnityEngine;
using System.Collections;

public class example : MonoBehaviour {
    public string output = "";
    public string stack = "";
    void OnEnable() {
        Application.RegisterLogCallback(HandleLog);
    }
    void OnDisable() {
        Application.RegisterLogCallback(null);
    }
    void HandleLog(string logString, string stackTrace, LogType type) {
        output = logString;
        stack = stackTrace;
    }
}

31.物理射线检测之BoxCast

在Physics下的RayCast已经很熟悉了,今天发现有一个BoxCast,起初以为它的意思就是检测给定立方体内的物体,

如果物体在该Box内,则返回true:

如图,但实际使用起来总是检测失败,仔细看下BoxCast的参数:

第一二个参数很好理解,立方体的中心,立方体的大小,第三个参数就让我有点疑惑了,竟然都已经决定好Box的大小了,为何还有方向这个概念,

直接检测是否在Box里不就好,知道去Google了下网上的用法才知道:

原来这个BoxCast的意思是,在给定的center处,建立一个给定大小的Box,然后往给定的方向一直往前拖/射

出这个Box,一直拉长,然后检测这条路上的所有障碍物,大概效果如图(中间的空隙是我加上便于理解的,实际中检测是无缝的)

32.U3D中的Time

一般Time中有两个比较重要的参数:

Time.realTime: 表示现实中的时间

Time.time: 表示游戏中的时间(受TimeScale影响)

而我们游戏中的Time.fixedTime,Time.deltaTime都是与游戏时间对应,而不是与现实时间对应。

比如我们的fixedTime设置为0.2,那就表明游戏中每0.2“秒”会调用一次FixedUpdate,而这个0.2秒并不是现实中的0.2秒,

也就是说,FixedUpdate不能保证现实时间中每0.2秒调用一次,他的实际调用还会受到TimeScale等的影响。

如果我们在一个FixedUpdate里做太多的内容,那么,现实中每一个FixedUpdate的时长就会拉长,但是在游戏中,两个FixedUpdate

的间隔仍然是0.2秒(可以理解为TimeScale变化了,但实际上不是),造成的结果就是,我们看到的游戏比较卡,帧数下降。

结论:

FixedUpdate与Update都不能保证在现实中多久调用一次,只能保证在游戏时间中FixedUpdate固定调用,而Update会受渲染影响

同样的游戏,当FixedUpdate里处理的内容很少时

FixedUpdate处理的内容较多,可以明星地看到卡了

但是这两个例子,Time.fixedTime都是0.2s

33.物理中的Collider性能对比

首先,这是一个有700多颗树的场景,每棵树都挂有一个Rigibody组件

没有任何Collider时:

Box: 可见大概涨了1ms

Shpere: 涨幅巨大,大概是Box的6-7倍

Capsule:虽然也涨幅比较大,但没Sphere严重

由此可见,性能上Box >> Capsule > Shpere,

分别耗时:1ms 7.4ms 5ms,

网上有篇试验教程,是09年的,结果跟我完全反过来,不知道是不是这几年来U3D物理进行了大优化

http://forum.unity3d.com/threads/capsule-vs-box-colliders.34254/#post-222443

另外,以上是基于动态Collider(所谓动态Collider,即该Collider挂载在一个非静止的物体的上,即有Rigibody且不为IsKinematic)

如果是在IsKinematic条件下,则性能消耗会进一步下降,比如以上情况,Box的情况为:

如果把Rigibody去掉,那么情况也差不多,主要消耗性能的部分是动态Collider

34. Unity里的Transform在SetPos或者SetRot时会造成很大的性能开销的原因:

这种情况一般是因为该Transform的GameObject上挂有太多的Collider,Collider分为静态与动态部分,

静态的碰撞体,物理系统会批优化处理,他们基本不占多少资源。但如果这个Collider是动态运行的,比如一直在

改变位置或者旋转,那么物理系统会不断重新计算他,如果你的GameObject上挂有太多的Collider,那么在

改变Transform时就会造成额外的巨大开销。

解决方法就是尽量减少运动的Collider,如果一个物体身上的Collider太多,可以尝试使用MeshCollider的方式来优化,

在运动的情况下,一个MeshCollider的开销比许多个小BoxCollider要小得多。

相关文章