前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Unity基础(14)-事件系统

Unity基础(14)-事件系统

作者头像
孙寅
发布2020-06-02 14:51:25
1.5K0
发布2020-06-02 14:51:25
举报
文章被收录于专栏:宜达数字宜达数字

一、生命周期事件

1、生命周期函数

废话不说直接代码演示

代码语言:javascript
复制
 // 1
    private void Awake()
    {
        // 组件,对象初始化工作
        Debug.Log("Awake");
    }
    // 2   // 判断当前的脚本是否可用?
    private void OnEnable()
    {
        // 前期控制
        Debug.Log("OnEnable");
    }
    //  3
    void Start()
    {
        // 变量进行赋值
        Debug.Log("Start");
    }
    // 4
    private void FixedUpdate()
    {
        // 固定的更新
        Debug.Log("FixedUpdate");
    }
    // 5
    void Update()
    {
        // 游戏角色控制逻辑
        Debug.Log("Update");
    }
    // 6
    private void LateUpdate()
    {
        // 跟游戏角色相关的
        Debug.Log("LateUpdate");
    }
    // 7
    private void OnGUI()
    {
        // UI,特效
        Debug.Log("OnGUI");
    }
    // 8
    private void OnDisable()
    {
        // 结束的收尾工作,销毁对象,销毁携程,关闭通道Socket
        Debug.Log("OnDisable");
    }
    // 9
    private void OnDestroy()
    {
        // 游戏已经退出,本地数据存储,游戏步骤
        Debug.Log("OnDestroy");
    }

运行结果

2、各个生命周期函数的作用

1.Awake:用于在游戏开始之前初始化变量或游戏状态。在脚本整个生命周期内它仅被调用一次.Awake在所有对象被初始化之后调用,所以你可以安全的与其他对象对话或用诸如GameObject.FindWithTag()这样的函数搜索它们。每个游戏物体上的Awake以随机的顺序被调用。因此,你应该用Awake来设置脚本间的引用,并用Start来传递信息Awake总是在Start之前被调用。它不能用来执行协同程序。

2.Start:仅在Update函数第一次被调用前调用。Start在behaviour的生命周期中只被调用一次。它和Awake的不同是Start只在脚本实例被启用时调用。你可以按需调整延迟初始化代码。Awake总是在Start之前执行。这允许你协调初始化顺序。在所有脚本实例中,Start函数总是在Awake函数之后调用。

3.FixedUpdate:固定帧更新,在Unity导航菜单栏中,点击“Edit”–>“Project Setting”–>“Time”菜单项后,右侧的Inspector视图将弹出时间管理器,其中“Fixed Timestep”选项用于设置FixedUpdate()的更新频率,更新频率默认为0.02s。

4.Update:正常帧更新,用于更新逻辑。每一帧都执行,处理Rigidbody时,需要用FixedUpdate代替Update。例如:给刚体加一个作用力时,你必须应用作用力在FixedUpdate里的固定帧,而不是Update中的帧。(两者帧长不同)FixedUpdate,每固定帧绘制时执行一次,和update不同的是FixedUpdate是渲染帧执行,如果你的渲染效率低下的时候FixedUpdate调用次数就会跟着下降。FixedUpdate比较适用于物理引擎的计算,因为是跟每帧渲染有关。Update就比较适合做控制。

5.LateUpdate:在所有Update函数调用后被调用,和fixedupdate一样都是每一帧都被调用执行,这可用于调整脚本执行顺序。例如:当物体在Update里移动时,跟随物体的相机可以在LateUpdate里实现。LateUpdate,在每帧Update执行完毕调用,他是在所有update结束后才调用,比较适合用于命令脚本的执行。官网上例子是摄像机的跟随,都是在所有update操作完才跟进摄像机,不然就有可能出现摄像机已经推进了,但是视角里还未有角色的空帧出现。

6.OnGUI:在渲染和处理GUI事件时调用。比如:你画一个button或label时常常用到它。这意味着OnGUI也是每帧执行一次。

7.Reset:在用户点击检视面板的Reset按钮或者首次添加该组件时被调用。此函数只在编辑模式下被调用。Reset最常用于在检视面板中给定一个默认值。

8.OnDisable:当物体被销毁时 OnDisable将被调用,并且可用于任意清理代码。脚本被卸载时,OnDisable将被调用,OnEnable在脚本被载入后调用。注意: OnDisable不能用于协同程序。

9.OnDestroy:当MonoBehaviour将被销毁时,这个函数被调用。OnDestroy只会在预先已经被激活的游戏物体上被调用。注意:OnDestroy也不能用于协同程序。

百度搜索找到的图:

3.png

二、碰撞器与触发器

Paste_Image.png

1、什么是碰撞器:Collider?

描述了物体可被碰撞的边界,以及碰撞过程中相互影响效果。 碰撞效果: 两个游戏对象碰在一起,通过物理引擎产生类似现实的效果

2、碰撞器方法

Paste_Image.png

总结:

  • 碰撞器(Collider)不需要刚体(Rigidbody)
  • 刚体(Rigidbody)要发生碰撞,一定需要碰撞器(Collider)
  • 碰撞器决定了碰撞发生时的边界条件
  • 刚体决定了碰撞发生后的物体的运动效果
  • 没有碰撞器的刚体,会在物理模拟中相互穿透。
3、触发器方法

Paste_Image.png

结论:

1、想要打印触发器方法,必须有一方是触发器,必须有一方带有刚体。二者缺一不可。 2、双方都是触发器,或者其中一方是触发器,另一方是碰撞器,都不会打印。 3、只要一方是触发器,并且有刚体组件,不管另一方是碰撞体还是触发器都会打印各自的触发器方法。 4、一般我们将触发器方法写在角色碰到的物体上,角色一般不参与触发方法。只写碰撞方法。

4、碰撞器和触发器的区别?
  • 4-1、碰撞器是触发器的载体,而触发器只是碰撞器身上的一个属性。
  • 4-2、当IsTrigger=false时,碰撞器根据物理引擎引发碰撞,产生碰撞的效果,可以调用OnCollisionEnter/Stay/Exit函数;
  • 4-3、当IsTrigger=true时,碰撞器被物理引擎所忽略,没有碰撞效果,可以调用OnTriggerEnter/Stay/Exit函数。
  • 4-4、如果既要检测到物体的接触又不想让碰撞检测影响物体移动或要检测一个物件是否经过空间中的某个区域这时就可以用到触发器。

三、鼠标事件

代码语言:javascript
复制
 // 当鼠标进入碰撞器的时候
    private void OnMouseEnter()
    {
        Debug.Log("enter--鼠标进入的时候");
    }
    private void OnMouseOver()
    {
        Debug.Log("over——鼠标在上面的时候");
    }
    // 此方法只调用一次
    private void OnMouseDown()
    {
        Debug.Log("Down——鼠标按下的时候");
    }
 // 此方法在鼠标按下的时候每帧调用
    private void OnMouseDrag()
    {
    Debug.Log("Drag——鼠标按下拖拽的时候");
    }
  // 当鼠标抬起时候
    private void OnMouseUpAsButton()
    {
        Debug.Log("OnMouseUpAsButton");
    }
    private void OnMouseUp()
    {
        Debug.Log("up——鼠标抬起的时候");
    }
    private void OnMouseExit()
    {
        Debug.Log("Exit——鼠标离开的时候");
    }

使用鼠标移动3D物体

代码语言:javascript
复制
 private void OnMouseDown()
 {
        Debug.Log("Down——鼠标按下的时候");
        // 坐标转换
        // 物体的屏幕坐标
        cScreenPos = Camera.main.WorldToScreenPoint(transform.position);
        Debug.Log(cScreenPos);
         //物体一开始的屏幕坐标
        Vector3 mScrernPos = new Vector3(Input.mousePosition.x, Input.mousePosition.y, cScreenPos.z);
        // 转换后的偏移量
        offset = transform.position - Camera.main.ScreenToWorldPoint(mScrernPos);
        Debug.Log(offset);
        // 物体的坐标 = 偏移量 + 物体开始的屏幕坐标转换后的世界坐标
        transform.position = offset + Camera.main.ScreenToWorldPoint(mScrernPos);
   }
    private void OnMouseDrag()
    {
        // 得到每一帧的屏幕坐标
        mSPos = new Vector3(Input.mousePosition.x, Input.mousePosition.y, cScreenPos.z);
        // 物体的位置 = 偏移量+每一帧的屏幕坐标转换后的世界坐标
        transform.position = offset +  Camera.main.ScreenToWorldPoint(mSPos);    
    }

四、协程

一个线程可以拥有多个协程

代码语言:javascript
复制
   Func<bool> f;
   void Start () {
        // 启动协程
        StartCoroutine(IEtest4());
        //f = isTrue;
        //f();
    }

    public bool isTrue()
    {
        return true;
    }
    IEnumerator IEtest1()
    {
        s = "你好,欢迎你到家";
        Test();
        yield return new WaitForSeconds(5f);  // update 和 LateUpdate 之间
        s = "你可以去客厅等我";
        Test1();
    }

    IEnumerator IEtest2()
    {     
        Debug.Log("永远会");
        yield return new WaitForFixedUpdate(); //第一次的FixedUpdate和 下一次FixedUpdate之间     //先执行一次,然后判断 设置了TimeScale = 0,那么永远不会执行
        Debug.Log("永远不会" +index);     
    }

    IEnumerator IEtest3()
    {
        Debug.Log("永远会");
        yield return null;  // 啥都没有   // update 和 LateUpdate 之间
        Debug.Log("永远不会");
    }

    IEnumerator IEtest4()
    {
        Debug.Log("WaitForEndOfFrame + 888");
        yield return new  WaitForEndOfFrame(); //截屏需要,LateUpdate的时序后
        Debug.Log("WaitForEndOfFrame + 999");
    }

    IEnumerator IEtest5()
    {
        Debug.Log("888");
        yield return new WaitForSecondsRealtime(5f); // 根据实际时间进行等待   update 和 LateUpdate 之间
        Debug.Log(" 999");
    }

    IEnumerator IEtest6()
    {
        Debug.Log(" 888");
        yield return new WaitUntil(f); // 执行一个委托,等待委托执行完毕,再执行下面的代码  它挂起语句,直到指定的条件返回true
        Debug.Log(" 999");
    }

    IEnumerator IEtest7()
    {
        Debug.Log("888");
        yield return new WaitWhile(f); // 执行一个委托,等待委托执行完毕,再执行下面的代码 当条件为真,不执行后面的代码,
        Debug.Log("999");
    }

五、相机事件

代码语言:javascript
复制
  // 当挂在此脚本的物体在相机视野中不可见,调用此方法
    private void OnBecameInvisible()
    {
        Debug.Log("相机看不见了 +888");
        Destroy(this.gameObject,5f);// 删除自己,5秒后
    }
    // 当挂在此脚本的物体在相机视野中可见,调用此方法
    private void OnBecameVisible()
    {
        Debug.Log("相机看见了 + 999");
    }

六、射线

游戏中进行碰撞检测。例如在射击游戏中子弹是否击中敌人,在RPG游戏中是否捡到装备等等。在进行碰撞检测时,我们最常用的工具就是射线。射线是在三维世界中从一个点沿一个方向发射的一条无限长的线。在射线的轨迹上,一旦与添加了碰撞器的模型发生碰撞,将停止发射。我们可以利用射线实现子弹击中目标的检测,鼠标点击拾取物体等功能

射线类
  • Ray
代码语言:javascript
复制
创建一条射线Ray需要指明射线的起点(origin)和射线的方向(direction)。
这两个参数也是Ray的成员变量。
注意,射线的方向在设置时如果未单位化,Unity 3D会自动进行单位归一化处理。射线Ray的构造函数为 :
public Ray(Vector3 origin, Vector3 direction);
  • Raycast
代码语言:javascript
复制
public static bool Raycast(Vector3 origin, Vector3 direction, float distance=Mathf.Infinity, intlayerMask=DefaultRaycastLayers);
     **参数说明:**
     origin            射线起点世界坐标
     direction          射线方向矢量
     distance            射线长度(起点到终点的距离),默认设置为无限长
     layerMask        显示层掩码(只选择层次为layerMask指定层次的碰撞器进行碰撞,其他层次的碰撞器忽略)
     **返回值说明:**
     当射线与碰撞器发生碰撞时返回值为true,未穿过任何碰撞器时返回为false。

public static boolRaycast(Vector3 origin, Vector3 direction, RaycastHit hitInfo, float distance =Mathf.Infinity, int layerMask = DefaultRaycastLayers);
     这个重载函数定义了一个碰撞信息类**RaycastHit**,在使用时通过out关键字传入一个空的碰撞信息对象。当射线与碰撞器发生碰撞时,该对象将被赋值,可以获得碰撞信息包括transform、rigidbody、point 等。如果未发生碰撞,该对象为空。

public static boolRaycast(Ray ray, float distance = Mathf.Infinity, int layerMask =DefaultRaycastLayers);
     这个重载函数使用已有的一条射线Ray来作为参数。

public static boolRaycast(Ray ray, RaycastHit hitInfo, float distance = Mathf.Infinity, intlayerMask = DefaultRaycastLayers);
     这个重载函数使用已有的射线Ray来作为参数并获取碰撞信息RaycastHit。
     在调试时如果想显示一条射线,可以使用Debug.DrawLine来实现。
     **public static void DrawLine(Vector3start, Vector3 end, Color color);**
     只有当发生碰撞时,在Scene视图中才能看到画出的射线。
射线使用方法

当我们要使用鼠标拾取物体或判断子弹是否击中物体时,我们往往是沿着特定的方向发射射线,这个方向可能是朝向屏幕上的一个点,或者是世界坐标系中的一个矢量方向。 针对向屏幕上的某一点发射射线,Unity 3D为我们提供了两个API函数以供使用,分别是ScreenPointToRay和ViewportPointToRay。

  • public Ray ScreenPointToRay(Vector3 position); 参数说明:position是屏幕上的一个参考点坐标。 返回值说明:返回射向position参考点的射线。当发射的射线未碰撞到物体时,碰撞点hit.point的值为(0,0,0)。

ScreenPointToRay方法从摄像机的近视口nearClip向屏幕上的一点position发射射线。Position用实际像素值表示射线到屏幕上的位置。当参考点position的x分量或y分量从0增长到最大值时,射线将从屏幕的一边移动到另一边。由于position在屏幕上,因此z分量始终为0。

  • 普通射线检测(一般用于检测某一个物体)
代码语言:javascript
复制
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        Debug.DrawRay(ray.origin ,ray.direction , Color.red);
        RaycastHit hit;
        if(Physics .Raycast (ray,out hit,int.MaxValue,1<<LayerMask .NameToLayer ("layername")))
        {
            Debug.Log("检测到物体");
        }
  • 直线射线检测多个物体
代码语言:javascript
复制
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        Debug.DrawRay(ray.origin ,ray.direction , Color.red);
        RaycastHit[] hit = Physics.RaycastAll(ray, Mathf.Infinity, 1 << LayerMask.NameToLayer("layername"));
        if(hit .Length >0)
        {
            for (int i = 0; i < hit.Length ; i++)
            {
                Debug.Log("检测到物体"+hit[i].collider.name );
            }
        }

-public Ray ViewportPointToRay(Vector3 position); 参数说明:position为屏幕上的一个参考点坐标(坐标已单位化处理)。 返回值说明:返回射向position参考点的射线。当发射的射线未碰撞到物体时,碰撞点hit.point的值为(0,0,0)。

ViewportPointToRay方法从摄像机的近视口nearClip向屏幕上的一点position发射射线。Position用单位化比例值的方式表示射线到屏幕上的位置。当参考点position的x分量或y分量从0增长到1时,射线将从屏幕的一边移动到另一边。由于position在屏幕上,因此z分量始终为0。

代码语言:javascript
复制
Ray ray;  
    RaycastHit hit;  
    // 创建射线到屏幕上的参考点,单位化坐标  
    Vector3 position = new Vector3(0.5f, 0.5f, 0.0f);  
    void Update () {  
        // 射线沿着屏幕x轴从左向右循环扫描  
        position.x = position.x >= 1.0f ? 0.0f : position.x + 0.002f;  
        // 生成射线  
        ray = Camera.main.ViewportPointToRay(position);  
        if(Physics.Raycast(ray, out hit, 100.0f))  
        {  
            // 如果与物体发生碰撞,在Scene视图中绘制射线  
            Debug.DrawLine(ray.origin, hit.point, Color.green);  
            // 打印射线检测到的物体的名称  
            Debug.Log("射线检测到的物体名称: " + hit.transform.name);  
        }  
    }
  • 球形射线检测(一般用于检测周边物体)
代码语言:javascript
复制
int radius = 3;
        Collider[] cols = Physics.OverlapSphere(this.transform.position, radius, LayerMask.NameToLayer("layername"));
        if(cols.Length >0)
        {
            for (int i = 0; i < cols.Length; i++)
            {
                Debug.Log("检测到物体" + cols[i].name);
            }
        }
画出球形检测范围方法,可用
private void OnDrawGizmos()
    {
        Gizmos.DrawWireSphere(this.transform.position, 3);
    }
  • 检测内部物体
代码语言:javascript
复制
我们要检测的物体在其他物体的内部,并且这两个物体都具有碰撞器,用射线检测返回的是第一个物体的信息,使用二次射线发射,利用第一次射线碰撞的外层物体的碰撞点作为第二次射线发射的起点,沿原来方向发射射线,判断是否与内部物体发生碰撞。
GameObject wrapper; // 外层物体  
        GameObject target; // 内层物体  
        string info = ""; // 碰撞检测信息  
      
        void Update () {  
        
        if(Input.GetMouseButton (0))  
        {  
            // 当鼠标左键按下时,向鼠标所在的屏幕位置发射一条射线  
      Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);  
            RaycastHit hitInfo;  
            if(Physics.Raycast(ray, out hitInfo))  
            {  
                // 当射线与物体发生碰撞时,在场景视图中绘制射线  
                Debug.DrawLine(ray.origin, hitInfo.point, Color.red);  
                // 获得第一次碰撞的外层物体对象  
                wrapper = hitInfo.collider.gameObject;  
               // 以第一次的碰撞点为起点,沿原来的方向二次发射射线  
               Ray ray2= new Ray(hitInfo.point, ray.direction);  
               RaycastHit hitInfo2;  
               if(Physics.Raycast(ray2, out hitInfo2))  
               {  
               // 当射线与内层物体碰撞时,在场景中绘制射线  
               Debug.DrawLine(ray2.origin, ray2.direction, Color.green);  
               // 获得内层物体对象  
               target = hitInfo2.collider.gameObject;  
               // 将外层物体的网格隐藏  
               wrapper.GetComponent<MeshRenderer().enabled = false;  
               // 设置碰撞信息  
               info = "检测到物体: " + target.name + "坐标: " + target.transform.position;  
                }  
                else  
                {  
               // 如果二次发射的射线没有与内层物体碰撞  
               // 显示外层物体的网格  
               wrapper.GetComponent<MeshRenderer>().enabled = true;  
               // 设置碰撞信息  
               info = "检测到物体: " + wrapper.name + "坐标: " + wrapper.transform.position;  
               }  
            }  
        }  
    }

在上面这段代码中我们使用左移位操作符<<来设置碰撞层的掩码layerMask。Unity 3D中共有32个层,对应使用一个32位整数的各个位来表示每个层级,当这个位为1时表示使用这个层,为0时表示不使用这个层。

LayerMask.NameToLayer这个API是返回我们使用自定义命名所定义的层的层索引,注意从0开始。当我们使用左移位操作设置层次掩码时,对应的自定义层级是n我们就将1左移n位,这样射线就只在layerMask指定的层次上进行碰撞检测。可供使用的自定义的层级从第8层开始,我们将8~10层分别命名为Capsule、Sphere和Cube,并将Capsule、Shpere和Cube三个物体的layer分别设置为对应的层次。一开始我们将所有物体设置为透明不可见。当按下鼠标左键发射射线时,返回射线方向上所有碰撞的物体信息,将获取到的物体对象,全部设置为半透明可见。点击按钮可以切换检测碰撞的层次。

7、Application事件

代码语言:javascript
复制
 // 程序暂停的时候  // 程序没有暂停就是False,暂停了就是True" 
    private void OnApplicationPause(bool pause)
    {
        Debug.Log(pause);
    }
    // 失去焦点,失去了就是True,没有失去就时false
    private void OnApplicationFocus(bool focus)
    {
        Debug.Log(focus);
    }
    private void OnApplicationQuit()
    {
        Debug.Log("当程序退出");
    }
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、生命周期事件
    • 1、生命周期函数
      • 2、各个生命周期函数的作用
      • 二、碰撞器与触发器
        • 1、什么是碰撞器:Collider?
          • 2、碰撞器方法
            • 3、触发器方法
              • 4、碰撞器和触发器的区别?
              • 三、鼠标事件
              • 四、协程
              • 五、相机事件
              • 六、射线
                • 射线类
                  • 射线使用方法
                  • 7、Application事件
                  相关产品与服务
                  数据保险箱
                  数据保险箱(Cloud Data Coffer Service,CDCS)为您提供更高安全系数的企业核心数据存储服务。您可以通过自定义过期天数的方法删除数据,避免误删带来的损害,还可以将数据跨地域存储,防止一些不可抗因素导致的数据丢失。数据保险箱支持通过控制台、API 等多样化方式快速简单接入,实现海量数据的存储管理。您可以使用数据保险箱对文件数据进行上传、下载,最终实现数据的安全存储和提取。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档