《Unity 3D游戏开发(第2版)》学习笔记

游戏脚本

自定义定时器

  • CustomYieldInstruction类并重写keepWaiting属性。
  • 保持协程暂停返回true。为了让协程进行执行返回false。
  • 调用重写的函数,在MonoBehaviour.Update之后和MonoBehaviour.LateUpdate之后的每个帧中- 此类需要Unity 5.3或更高版本

样例

using System;
using System.Collections;
using UnityEngine;
using UnityEngine.Events;

public class Test: MonoBehaviour
{
    IEnumerator Start()
    {
        // 10秒后结束
        yield return new CustomWait(10f, 1f, delegate () {
            Debug.LogFormat("每过一秒回调一次 : {0}", Time.time);
        });
        Debug.Log("十秒结束");
    }
}

public class CustomWait : CustomYieldInstruction
{
    public override bool keepWaiting
    {
        get
        {
            //此方法返回false表示协程结束
            if (Time.time - m_StartTime >= m_Time)
            {
                return false;
            }
            else if(Time.time - m_LastTime >= m_Interval)
            {
                //更新上一次间隔时间
                m_LastTime = Time.time;
                m_CallBack();
            }
            return true;
        }
    }

    private float m_StartTime;
    private float m_LastTime;
    private float m_Interval;
    private float m_Time;
    private UnityAction m_CallBack;

    public CustomWait(float time, float interval, UnityAction callback)
    {
        //记录开始时间
        m_StartTime = Time.time;
        //记录上一次回调的时间
        m_LastTime = Time.time;
        //记录间隔的时间
        m_Interval = interval;
        //记录总时间
        m_Time = time;
        //回调
        m_CallBack = callback;
    }
}

多线程

  • 2018之后开放了工作线程(多线程)
  • 主线程和工作线程的数据需要使用Native来传递
  • 工作线程只需要读数据,所以在现在代码中标注是只读

样例:

TransformAccessArray transformAccessArray = new TransformAccessArray(cubes);
//启动工作线程
MyJob job = new MyJob() { position = position };
JobHandle jobHandle = job.Schedule(transformAccessArray);
//等待工作线程结束
jobHandle.Complete();
//结束
transformAccessArray.Dispose();
position.Dispose();

struct MyJob : IJobParallelForTransform
{
    //只读
    [ReadOnly] public NativeArray<Vector3> position;

    public void Execute(int index, TransformAccess transform)
    {
        //工作线程中设置坐标
        transform.position = position[index];
    }

}

代码编译

编译顺序如下

  1. Plugins根目录 -> Assembly-CSharp-firstpass.dll
  2. Plugins/Editor -> Assembly-CSharp-Editor-firstpass.dll
  3. 根目录 -> Assembly-CSharp.dll
  4. Editor -> Assembly-CSharp-Editor.dll

生命周期

UGUI

渲染顺序

三层,顺序如下

Camera,先绘制depth低的相机下的物体,depth高的相机会覆盖depth低的  
    1. RenderQueue 2500以下(含2500)  
        sorting layer + Order in Layer,越小越优先,后者为前者子项  
            RenderQueue, 越小越优先
                RenderQueue 相等,【由近到远】排序优先
                    z轴,远到近
    2. RenderQueue 2500以下
        sorting layer + Order in Layer,越小越优先,后者为前者子项  
            RenderQueue, 越小越优先
                RenderQueue 相等,【由远到近】排序优先
                    z轴,远到近
    3. 当一个RenderQueue在0~2500 ,一个在2501之后,那么此时SortingOrder失效,谁的RenderQueue更大,谁在前面

事件系统

Graphic Raycaaster,绑定在canvas上,表示下面所有UI元素支持的事件。可以挂在子控件上,只对某个子控件生效

通过继承IPointerEnterHandler来重写各个监听的方法

UI事件管理

可以通过UGUIEventListener.Get(gameObject).OnClick = func的方式,进行统一管理,这个样例是对点击事件进行管理。

图片和文本的点击事件

继承EventSystems.EventTrigger来实现OnPointerClick()点击方法。

实现方式:重写UGUIEventListener方法

public class UGUIEventListener : UnityEngine.EventSystems.EventTrigger 
{
    public override void OnPointerClick(UnityEngine.EventSystems.PointerEventData eventData)
    {
        
    }

    static public UGUIEventListener Get(GameObject go)
    {

    }
}

事件传递

UnityAction: 类似c#中的委托,可以实现方法的传递

UnityEvent: 类似c++中的信号,负责管理UnityAction。提供接口:

  • AddListener
  • RemoveListener
  • RemoveAllListerners

*RaycastTarget优化(屏幕射线功能)

重写OnDrawGizmos()方法,找到所有RaycastTarget勾选的UI,然后画出线框,然后取消UI中勾选就可以了

UI穿透

EventSystem.current.RaycastAll(PointerEventData, func) 可以找到所有能传递点击事件的对象
EventEvents.Execute(gameObject, PointerEventData, func) 可以将事件传递给需要的对象

自适应

Canvas Scaler组件

Canvas优化问题

如果一个Canvas中的元素过多,每次更新UI,都需要重新合并Mesh,会很卡
如果Canvas数量过多,DrawCall会卡,因为每个Canvas会单独占用一个DrawCall

和NGUI的对比

NGUI与UGUI的区别

  1. uGUI的Canvas 有世界坐标和屏幕坐标
  2. uGUI的Image可以使用material
  3. UGUI通过Mask来裁剪,而NGUI通过Panel的Clip
  4. NGUI的渲染前后顺序是通过Widget的Depth,而UGUI渲染顺序根据Hierarchy的顺序,越下面渲染在顶层.
  5. UGUI 不需要绑定Colliders,UI可以自动拦截事件
  6. UGUI的Anchor是相对父对象,没有提供高级选项,个人感觉uGUI的Anchor操作起来比NGUI更方便
  7. UGUI没有Atlas一说,使用Sprite Packer
  8. UGUI的Navigation在Scene中能可视化
  9. UGUI的事件需要实现事件系统的接口,但写起来也算简单

各自的优缺点

  1. NGUI还保留着图集,需要进行图集的维护。而UGUI没有图集的概念,可以充分利用资源,避免重复资源。
  2. UGUI出现了锚点的概念,更方便屏幕自适应。
  3. NGUI支持图文混排,UGUI暂未发现支持此功能。
  4. UGUI没有 UIWrap 来循环 scrollview 内容。
  5. UGUI暂时没有Tween组件。

2D游戏

Tile更新

tilemap.GetTile()
tilemap.SetTile()

2D物理

物理引擎基于PhysX

碰撞效果发生条件:主动碰撞的物体,需要有Collider2D和Rigidbody2D,被碰撞的物体只需要有Collider2D

刚体

  • Dynamic: 动态刚体,完全模拟物理效果,碰到Collider2D会被挡住。效率最低,仅适合主角使用
  • Kinematic: 运动学,只能和选中Dynamic复选框的刚体发生碰撞效果。效率比上面那个好
  • Static: 静态,只能和Dynamic发生碰撞效果,和Kinematic只能发生碰撞事件(需要勾选Use Full Kinematic Contact复选框,效率最高)

碰撞监听

  • OnCollisionEnter2D
  • OnCollisionStay2D
  • OnCollisionExit2D

只触发事件,不触发效果

Is Trigger

动画系统

2017新加Playables,也支持脚本控制动画
2018新概念:Constraint,可以让平级的游戏对象互相依赖

状态机相关监听

  • OnStateEnter,进入状态
  • OnStateUpdate,状态更新,每帧调用
  • OnStateExit,离开状态
  • OnStateMove,处理动画根节点的位移
  • OnStateIK,处理IK(反向动力学)动画

剧情动画

TimeLine编辑器

持久化数据

json支持字典

Unity的JSON是不支持字典的,不过可以继承ISerializationCallbackReceiver借口,间接实现字典序列化。
实现原理:分别序列化两个List元素来保存key-value。在OnBeforeSerialize()和OnAfterDeserialize()进行序列化和反序列化赋值操作。

游戏存档

PlayerPrefs

Unity自带的存档方法,会在应用程序将切入后台时统一保存文件,也可以强制调用PlayerPrefs.Save()保存。支持存储接口:

  • SetInt(String, Int)
  • SetFloat(String, Float)
  • SetString(String, String)

EditorPrefs

编辑器模式下,Unity提供的一组存档功能。接口和上面一样,且是及时保存的。

TextAssert

unity提供的一个文本对象,用来读取Resources目录下的MyText文本

本地文件读写

c#的File类,需要注意下路径问题,编辑模式和打包的路径有区别

存储类型对比

  • json: 方便,但是数据多了之后,可读性差
  • XML: 比json可读性好一点
  • YAML: 数据格式要求没那么严格(比如少些括号或者逗号),预览性和编辑性都非常好。可以用#来注释部分内容

静态对象

Lightmap

两类物体:一类是可发生位移变化的,使用实时光照计算;另一类是不可发生位移变化的,采用预先烘焙Lightmap

光源设置

windows - Lighting - Setting,可以打开烘焙面板
windows - Lighting - Light Explore, 管理光源
windows - Occlusion Culling, 设置最小遮挡距离、最小遮挡空隙和最小阈值(遮挡的对象,需要选中Occluder Static和Occludee Static选项)

遮挡事件

  • OnBecameInvisible
  • OnBecameVisible
    手动遮挡:Renderer.isVisible

自动寻路

unity使用的是A*寻路
windows - Navigation 打开寻路面板,可以控制寻路的各个参数

public void Update()
{
    if(Input.GetMouseButton(0)) {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        RaycastHit[] hits = Phytsics.RaycastAll(ray);
        foreach(var hit in hits) {
            string name = hit.collider.gameObject.name;
            if (name == "Plane") {
                // 移动方块
                navMeshAgent.SetDestination(hit.point);
            }
        }
    }
}

连接不相通的两点: OffMeshLink组件
检查路径是否合法: NavMesh.CalcuiatePath()
支持动态阻挡:Nav Mesh Obstacle组件

音频

Audio Source组件

  • Spatial Blend: 0表示2D音频,1表示3D,0-1表示之间的插值音频
  • Priority: 优先级,同时播放的音频有上限,超过后悔关闭最高的音频,一般bgm设置0
  • Volume: 音量
  • Stereo pan: 左右声道占比
  • Pitch: 播放速度

资源加载与优化

Prefab实例化

SetParent会继承世界坐标,所以新建的实例不一定在原点

  • GameObject.Instantiate: 只创建新对象,会丢失Prefab的引用
  • PrefabUtility.InstantiatePrefab: 只能在编辑模式使用

资源卸载

  • GameObject.Destroy()
  • GameObject.DestroyImmediate(): 编辑模式用

上面两个方法只会卸载掉对象,它身上引用的贴图和Mesh还在内存中,如果需要卸载这些无用的资源,需要调用EditorUtility.UnloadUnusedAssetsImmediate()方法
ps: Unity这么做的原因是为了防止频繁的加载和卸载资源导致的IO阻塞

版本管理

需要管理的只有Assets、ProjectSetting中的所有文件(包括.meta文件)

删除资源

Resources.UnLoadUnusedAssets() + isDone

m_Operation = Resources.UnLoadUnusedAssets();
// 强制卸载对象引用的资源
// Resources.UnLoadAssets(g1);

if(m_Operation.isDone) {
    m_Operation = null;
    Debug.Log("资源卸载完成");
}

AB包压缩格式

  1. LZMA压缩:默认压缩方式,优点是非常小,缺点是每次使用都要解压,可能会有卡顿,不建议在项目中使用
  2. BuildAssetBundleOptions.UncompressedAssetBundle 不压缩:缺点是构建的AB包很大,优点是加载速度很快,可以通过第三方算法压缩,然后在解压
  3. BuildAssetBundleOptions.ChunkBasedCompression LZ4压缩:上面两种的折中方案,综合了优缺点,建议在项目中使用

加载流程

磁盘中加载AssetBundle对象 -> 从AB对象中加载资源对象 -> 从资源读取对象并且实例化到Hierarchy视图,变成游戏对象

卸载流程就是上面这个流程相反: Destroy删除游戏对象 -> 卸载资源 -> 卸载AB包

游戏资源管理

特殊的文件夹:

  • Assets:游戏资源的顶层文件夹。AssetDatabase方法可以访问到里面的任意资源
  • Editor:编辑模式下的代码需要放在这里。打包之后,会自动剥离它
  • Editor Default Resources:编辑模式的资源
  • Gizmos:放一些工具类图标,使用Gizmos.DrawIcon()方法来显示他们
  • Plugins:优先编译成DLL文件
  • Resources:资源目录,自动构建到包内,可以使用Resources.Load()加载资源
  • Standard Assets:导入Unity标准资源的package文件夹,这里的脚本执行顺序会设置得靠前
  • StreamingAssets:AB包适合放在这里,这里的资源不会被压缩或改变
posted @ 2022-03-02 20:57  二律背反GG  阅读(374)  评论(0编辑  收藏  举报