Unity基础

59 阅读31分钟

一、Mathf

一个unity中用于计算的结构体。

1、区别及介绍

image.png

image.png

2、常用方法

①、π、绝对值、向上向下取整:

![image.png](https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/07fb84b3853d4cfbabf88521ad9740e9~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5Y2K5aSc5b6u56yR54uX:q75.awebp?rk3s=f64ab15b&x-expires=1770956683&x-signature=XkWYnALrXCOEST8QR%2FzSsfyE2QI%3D)
向上和向下取整是直接取比当前这个数大1或小1的整数,不是四舍五入。

②、钳制函数:

image.png

钳制函数第一个参数是需要判定的值,第二三个是范围。判定参数1是否在范围内,是则取本身,否则取距离近的最大最小值。

③、取最大最小值:

image.png

④、n次幂:

image.png

参数1:底数,参数2:指数

⑤、四舍五入和平方根:

image.png

⑥、判断是否是2的n次方、判断正负数:

image.png

注意:0判断时是返回1。

⑦、插值运算:Lerp

image.png

一般用于在一个物体跟随另一个物体时。

3、练习题:

image.png

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;

public class Test1 : MonoBehaviour
{
    // Start is called before the first frame update
    public Transform pointB;
    private float time = 0;
    private Vector3 startPos;
    private Vector3 EndPos;
    private Vector3 pos;
    void Start()
    {
        startPos = this.transform.position;
        EndPos = pointB.position;
    }

    // Update is called once per frame
    void Update()
    {

        time += Time.deltaTime;
        pos.x = Mathf.Lerp(startPos.x, EndPos.x, time);
        pos.y = Mathf.Lerp(startPos.y, EndPos.y, time);
        pos.z = Mathf.Lerp(startPos.z, EndPos.z, time);
        this.transform.position = pos;
    }
}

二、三角函数

1、角度和弧度转换

①、转换关系:

image.png

②、代码转换

image.png

2、正、余弦函数:

正弦函数:对边/斜边

余弦函数:临边/斜边

①、特殊角度对应正、余弦值:

image.png

②、三角函数:

image.png

③、反三角函数

使用正、余弦函数反推弧度

image.png

3、练习题

image.png

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SinAndCos : MonoBehaviour
{
    //前进和上下波动速度
    public int moveSpeed;
    public float fluctuateSpeed;//变化角度值
    public int height;
    public float fluctuateRad;
    // Update is called once per frame
    void Update()
    {
        this.transform.Translate(Vector3.forward * moveSpeed * Time.deltaTime, Space.Self);
        this.transform.Translate(Vector3.up * height * Mathf.Sin(fluctuateRad * Mathf.Deg2Rad) * Time.deltaTime, Space.Self);
        fluctuateRad += fluctuateSpeed;
    }
}

三、坐标系

1、坐标系

①、屏幕坐标系

image.png

②、视口坐标系

image.png

③、世界坐标系代码计算

image.png

④、相对于父类的物体坐标系代码

image.png

⑤、屏幕坐标系代码

image.png

⑥、视口坐标系

调整unity编辑器中摄像机的参数变化:

image.png

2、坐标系转换

image.png

image.png

四、向量

1、向量模长和单位向量

①、向量概念

image.png

向量既可以代表一个点的位置,也能代表一个方向。

②、向量计算

终点 - 起点 = 向量

unity中可以两个向量直接做减法。

③、零向量和负向量

image.png

④、向量的模长

英语生词:magnitude:数值。

image.png

⑤、单位向量

image.png

⑥、unity代码求单位向量

英语生词:normalized:标准化的。

image.png

⑦、练习题:

image.png

image.png

手写计算即为:根号下(x的平方+y的平方+z的平方)

2、向量的加减乘除

①、向量加减的几何意义-加法:

①位置 + 位置一般没有什么意义。

②向量 + 向量:得到一个新的向量。

image.png

③位置 + 向量 或 向量 + 位置:平移一个点。

image.png

②、向量加减的几何意义-减法:

①位置 - 位置:

image.png

②向量 - 向量:得到新向量

image.png

③位置 - 向量:相反方向位移。而向量-位置一般没有什么意义。

image.png

③、向量乘除:

image.png

④、Unity中计算:

image.png

⑤、练习题

image.png

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Test2 : MonoBehaviour
{
    public GameObject cube1;
    // Update is called once per frame
    void LateUpdate()//注意摄像机移动需要在LateUpdate,因为在其中还要处理画面。
    {
        //位置 + 向量 = 位置
        this.transform.position = cube1.transform.position + -cube1.froward * 4 + cube1.up * 7;
    }
}

3、点乘

①、计算公式:

image.png

②、点乘几何意义:判断前后位置和夹角。

image.png

③、辅助线:

image.png

④、如何在Unity中判断:

image.png

⑤、推导向量夹角:

image.png

image.png

另外一种计算方法:

image.png

⑥、练习题:

image.png

注意:使用Vector时第一个是单位向量。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class L1 : MonoBehaviour
{
    public Transform B;
    float distanceAB;
    float angleAB;
    // Update is called once per frame
    void Update()
    {
        //先计算两点距离和夹角
        distanceAB = (B.position - this.transform.position).magnitude;
        angleAB = Vector3.Angle(this.transform.forward, B.position - this.transform.position);
        //print("距离是:" + distanceAB + ", 夹角是:" + angleAB);
        if (distanceAB <= 5 && angleAB <= 22.5)
        {
            print("发现入侵者");
        }
    }
}

4、叉乘

①、叉乘计算公式:

image.png

注意:叉乘计算公式目标向量的对应的Y计算过程是:Y = ZaXb - XaZb 其中Y是不参与计算的。然后先计算Y下面的Z轴,再循环回来重头计算X轴。

②、叉乘几何意义:

image.png

image.png

得到法向量和得到两个向量的左右位置关系。

③、练习题:

image.png

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class L1 : MonoBehaviour
{
    public Transform B;
    float distanceAB;
    float angleAB;
    Vector3 crossAB;
    // Update is called once per frame
    void Update()
    {
        //先计算两点距离和夹角
        distanceAB = (B.position - this.transform.position).magnitude;
        angleAB = Vector3.Angle(this.transform.forward, B.position - this.transform.position);
        crossAB = Vector3.Cross(this.transform.forward, B.position - this.transform.position);
        if (angleAB >= 0 && angleAB <= 90 && crossAB.y > 0)
        {
            print("球在方块的右前方");
        }
        else if (angleAB >= 0 && angleAB <= 90 && crossAB.y < 0)
        {
            print("球在方块的左前方");
        }
        else if (angleAB >= 90 && angleAB <= 180 && crossAB.y > 0)
        {
            print("球在方块的右后方");
        }
        else if (angleAB >= 90 && angleAB <= 180 && crossAB.y < 0)
        {
            print("球在方块的左后方");
        }
        if ((distanceAB <= 5 && angleAB <= 20 && crossAB.y < 0) || (distanceAB <= 5 && angleAB <= 30 && crossAB.y > 0))
        {
            print("发现入侵者");
        }
    }
}

5、向量插值运算

①、Vector3的插值运算:

image.png

②、作用:让一个位置靠近另一个位置,有两种方法

image.png

③、线性插值:

image.png

④、球形插值:

image.png

⑤、作用:

image.png

⑥、练习题:

image.png

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;

public class Test3 : MonoBehaviour
{
    //跟随题变量
    public Transform cube1;
    Vector3 target;
    Vector3 nowTarget;
    float time;
    //日落题变量
    public Transform sun;
    Vector3 startSun;
    Vector3 targetSun;
    float timeSun;
    private void Start()
    {
        startSun = sun.position;
        targetSun = sun.position - sun.right * 20;
    }
    // Update is called once per frame
    void Update()
    {
        target = cube1.position - cube1.forward * 4 + cube1.up * 7;
        if (nowTarget != target)
        {
            nowTarget = target;
            time = 0;
        }
        time += Time.deltaTime * 0.1f;
        this.transform.position = Vector3.Lerp(this.transform.position, nowTarget, time);
        this.transform.LookAt(cube1);
        timeSun += Time.deltaTime * 0.2f;
        /*
         * 首先,计算起始位置(startSun)和目标位置(targetSun)的中心点(center),然后将该中心点向下移动一定距离(这里是5个单位)。这个移动后的中心点将作为弧线的圆心。

然后,将起始位置和目标位置转换为相对于这个圆心的坐标(riseRelCenter和setRelCenter)。这样,我们就可以在以圆心为原点的坐标系中处理这两个点。

接着,使用Vector3.Slerp对这两个相对坐标进行球面线性插值。Slerp会在球面上插值,从而产生一个弧线运动。插值的参数timeSun在0到1之间变化,0表示起始点,1表示目标点。

最后,将插值结果(仍在相对坐标中)转换回世界坐标,通过加上圆心的坐标(center)来实现。

这样,太阳就会沿着一个圆形弧线从起始位置运动到目标位置,弧线的高度由中心点向下移动的距离控制(移动得越多,弧线越高)。
         */
        Vector3 center = (startSun + targetSun) * 0.5f;
        center -= Vector3.up * 5.0f; // 调整弧线高度

        Vector3 riseRelCenter = startSun - center;
        Vector3 setRelCenter = targetSun - center;

        sun.position = Vector3.Slerp(riseRelCenter, setRelCenter, timeSun);
        sun.position += center;
    }
}

五、四元数

1、为何使用四元数

①、欧拉角

欧拉角旋转约定:

image.png

欧拉角优缺点:

image.png

同一旋转不唯一是:同一表现形式下,旋转数值可能不同。

②、万向节死锁

image.png

③、总结

image.png

2、四元数

①、四元数的构成

四元数概念:

image.png

四元数构成:

image.png

轴-角对概念:

image.png

轴-角对和四元数结合:

image.png

②、Unity中的四元数

Unity中的四元数是一个结构体。名为:Quaternion

两种初始化方式:

image.png

Unity中使用对公式化示例:

image.png

书写顺序:Quaternion()中先写x,y,z(即你要绕的轴)再写Mathf.cos或Mathf.sin,之后填写角度并乘上角度转弧度的对应值。

Unity中常用对方法示例:

image.png

注意这里是绕自身x轴的顺时针旋转60°

欧拉角和四元数转换:

image.png

③、四元数弥补欧拉角的缺点

解决欧拉角不唯一:四元数顺时针0-》180度,逆时针左半圆是0-》-180度。

解决万向节死锁:能在不影响其他轴的情况下旋转。但需要注意的是Vector3.up指的是物体本地坐标系,而不是世界坐标系。

this.transform.rotation *= Quaternion.AngleAxis(1, Vector3.up);

3、四元数常用方法

image.png

①、单位四元数

单位四元数概念:

image.png

其中Quaternion.identity;得到(0.0, 0.0, 0.0, 1.0)中的1代表的是:

sin(0/2) = sin(0) = 0 → 向量部分为 (0, 0, 0)

cos(0/2) = cos(0) = 1 → 标量部分为 1

单位四元数常用于初始化物体旋转量。

②、插值运算

四元数插值介绍:但其中更推荐Slerp

image.png

代码展示:

image.png

③、向量方向转换为对应四元数角度

image.png

image.png

④、练习题:

image.png

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Qua1 : MonoBehaviour
{
    public Transform A;
    public Transform B;
    Quaternion start;
    Quaternion target;
    Quaternion nowTarget;
    float time;
    private void Start()
    {
        start = A.localRotation;
        target = Quaternion.LookRotation(B.position - A.position);
    }
    // Update is called once per frame
    void Update()
    {
        nowTarget = Quaternion.LookRotation(B.position - A.position);
        if (nowTarget != target)
        {
            target = nowTarget;
            time = 0;
            start = A.localRotation;
        }
        time += Time.deltaTime * 0.1f;
        A.localRotation = Quaternion.Slerp(start, target, time);
        Debug.DrawLine(A.position, B.position, Color.red);
    }
}

4、四元数计算

四元数相乘得到的是新四元数,代表旋转量的叠加。(旋转是以自己为坐标轴)

①、四元数相乘

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Qua2 : MonoBehaviour
{
    Quaternion q1 = Quaternion.AngleAxis(1, Vector3.up);
    // Update is called once per frame
    void Update()
    {
        this.transform.rotation *= q1;
    }
}

②、四元数乘向量

四元数 * 向量 = 新向量

相当于旋转一个向量。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Qua2 : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        Quaternion q1 = Quaternion.AngleAxis(45, Vector3.forward);
        Vector3 v1 = q1 * Vector3.forward;//必须四元数写前面
        print(v1);
    }
    // Update is called once per frame
    void Update()
    {
        
    }
}

注意:四元数与向量相乘时,四元数必须写前面。

③、练习题

image.png

第一题:

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;

public class Qua : MonoBehaviour
{
    public GameObject bullet;
    public int bulletForce = 20;
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        //单发
        if (Input.GetMouseButtonDown(0))
        {
            GameObject pre = Instantiate(bullet, this.transform);
            pre.transform.position = this.transform.position + this.transform.forward * 2;
            Rigidbody rb = pre.GetComponent<Rigidbody>();
            if(rb == null)
            {
                rb = pre.AddComponent<Rigidbody>();
            }
            rb.AddForce(pre.transform.forward * bulletForce);
            // 设置物理属性
            rb.mass = 0.1f;//质量
            rb.drag = 0f;//阻力

            // 使用Impulse模式立即施加力,v = Ft/m 瞬时力模式会忽略时间,默认为1
            rb.AddForce(this.transform.forward * bulletForce, ForceMode.Impulse);

            // 可选:自动销毁子弹
            Destroy(pre, 5f); // 5秒后销毁
        }
    }
}
//发射类挂载在发射的飞机上
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;

public class Qua : MonoBehaviour
{
    public GameObject bullet;
    public int bulletForce = 20;
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
        {
        //双发:类似王者的干将莫邪弯曲弹道
        if (Input.GetMouseButtonDown(0))
        {
            GameObject pre1 = Instantiate(bullet);
            GameObject pre2 = Instantiate(bullet);
            pre1.transform.position = this.transform.position + this.transform.forward * 1 - this.transform.right * 0.6f;
            pre2.transform.position = this.transform.position + this.transform.forward * 1 + this.transform.right * 0.6f;
            Rigidbody rb1 = pre1.GetComponent<Rigidbody>();
            Rigidbody rb2 = pre2.GetComponent<Rigidbody>();
            if (rb1 == null)
            {
                rb1 = pre1.AddComponent<Rigidbody>();
            }
            if (rb2 == null)
            {
                rb2 = pre2.AddComponent<Rigidbody>();
            }
            rb1.AddForce(pre1.transform.forward * bulletForce);
            rb2.AddForce(pre2.transform.forward * bulletForce);
            // 设置物理属性
            rb1.mass = 0.05f;//质量
            rb2.mass = 0.05f;//质量

            //改变初始方向
            Vector3 direction1 = Quaternion.AngleAxis(-45, Vector3.up) * pre1.transform.forward;
            Vector3 direction2 = Quaternion.AngleAxis(45, Vector3.up) * pre2.transform.forward;
            print(direction1);
            print(direction2);

            // 使用持续力
            rb1.AddForce(direction1 * bulletForce, ForceMode.Force);
            rb2.AddForce(direction2 * bulletForce, ForceMode.Force);

            // 标记子弹方向
            Bullet bulletScript1 = pre1.GetComponent<Bullet>();
            Bullet bulletScript2 = pre2.GetComponent<Bullet>();

            if (bulletScript1 != null) bulletScript1.SetBulletDirection(true);  // 左子弹
            if (bulletScript2 != null) bulletScript2.SetBulletDirection(false); // 右子弹

            // 可选:自动销毁子弹
            Destroy(pre1, 5f); // 5秒后销毁
            Destroy(pre2, 5f); // 5秒后销毁
        }
    }
}
//挂载在子弹预制体上
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Bullet : MonoBehaviour
{
    Rigidbody rb;
    private bool isLeftBullet; // true: 左子弹, false: 右子弹
    private float rotationSpeed = 0.1f;
    private void Start()
    {
        rb = GetComponent<Rigidbody>();
    }
    // Update is called once per frame
    void Update()
    {
        rb.velocity = Quaternion.AngleAxis(rotationSpeed, Vector3.up) * rb.velocity.normalized * rb.velocity.magnitude;
        //*= Quaternion.AngleAxis(1, Vector3.up);
    }
    public void SetBulletDirection(bool isLeft)
    {
        isLeftBullet = isLeft;

        // 根据左右设置不同的旋转速度(左子弹顺时针,右子弹逆时针)
        rotationSpeed = isLeft ? 0.1f : -0.1f;

        // 可选:根据左右设置不同的颜色
         GetComponent<Renderer>().material.color = isLeft ? Color.blue : Color.red;
    }
}

总展示:

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;

public class Qua : MonoBehaviour
{
    public GameObject bullet;
    public int bulletForce = 20;
    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        //单发
        //if (Input.GetMouseButtonDown(0))
        //{
        //    GameObject pre = Instantiate(bullet, this.transform);
        //    pre.transform.position = this.transform.position + this.transform.forward * 2;
        //    Rigidbody rb = pre.GetComponent<Rigidbody>();
        //    if(rb == null)
        //    {
        //        rb = pre.AddComponent<Rigidbody>();
        //    }
        //    rb.AddForce(pre.transform.forward * bulletForce);
        //    // 设置物理属性
        //    rb.mass = 0.1f;//质量
        //    rb.drag = 0f;//阻力

        //    // 使用Impulse模式立即施加力,v = Ft/m 瞬时力模式会忽略时间,默认为1
        //    rb.AddForce(this.transform.forward * bulletForce, ForceMode.Impulse);

        //    // 可选:自动销毁子弹
        //    Destroy(pre, 5f); // 5秒后销毁
        //}
        //双发:类似王者的干将莫邪弯曲弹道
        if (Input.GetMouseButtonDown(0))
        {
            GameObject pre1 = Instantiate(bullet);
            GameObject pre2 = Instantiate(bullet);
            pre1.transform.position = this.transform.position + this.transform.forward * 1 - this.transform.right * 0.6f;
            pre2.transform.position = this.transform.position + this.transform.forward * 1 + this.transform.right * 0.6f;
            Rigidbody rb1 = pre1.GetComponent<Rigidbody>();
            Rigidbody rb2 = pre2.GetComponent<Rigidbody>();
            if (rb1 == null)
            {
                rb1 = pre1.AddComponent<Rigidbody>();
            }
            if (rb2 == null)
            {
                rb2 = pre2.AddComponent<Rigidbody>();
            }
            rb1.AddForce(pre1.transform.forward * bulletForce);
            rb2.AddForce(pre2.transform.forward * bulletForce);
            // 设置物理属性
            rb1.mass = 0.05f;//质量
            rb2.mass = 0.05f;//质量

            //改变初始方向
            Vector3 direction1 = Quaternion.AngleAxis(-45, Vector3.up) * pre1.transform.forward;
            Vector3 direction2 = Quaternion.AngleAxis(45, Vector3.up) * pre2.transform.forward;
            print(direction1);
            print(direction2);

            // 使用持续力
            rb1.AddForce(direction1 * bulletForce, ForceMode.Force);
            rb2.AddForce(direction2 * bulletForce, ForceMode.Force);

            // 标记子弹方向
            Bullet bulletScript1 = pre1.GetComponent<Bullet>();
            Bullet bulletScript2 = pre2.GetComponent<Bullet>();

            if (bulletScript1 != null) bulletScript1.SetBulletDirection(true);  // 左子弹
            if (bulletScript2 != null) bulletScript2.SetBulletDirection(false); // 右子弹

            // 可选:自动销毁子弹
            Destroy(pre1, 5f); // 5秒后销毁
            Destroy(pre2, 5f); // 5秒后销毁
        }
        //扇形子弹
        if (Input.GetMouseButtonDown(1))
        {
            for (int i = 0; i < 3; i++)
            {
                Quaternion qua = Quaternion.AngleAxis((i - 1) * 45, Vector3.up);
                Fire(qua);
            }
        }
        //环形子弹
        if (Input.GetMouseButtonDown(2))
        {
            for (int i = 0; i < 8; i++)
            {
                Quaternion qua = Quaternion.AngleAxis((i - 4) * 45, Vector3.up);
                print((i - 4) * 45);
                print(qua.eulerAngles);
                Fire(qua);
            }
        }

        //扇形子弹和环形子弹发射模版
        void Fire(Quaternion qua)
        {
            GameObject prefabBullet = Instantiate(bullet);
            prefabBullet.transform.position = this.transform.position + this.transform.forward;//设置位置到前面
            Rigidbody rb = prefabBullet.GetComponent<Rigidbody>();
            if (rb == null)
            {
                rb = prefabBullet.AddComponent<Rigidbody>();
            }
            //rb.mass = 0.05f;
            // 设置为触发器
            rb.GetComponent<Collider>().isTrigger = true;
            Vector3 direction = qua * rb.transform.forward;
            rb.AddForce(direction * bulletForce, ForceMode.Impulse);
        }
    }
}

上面是我写的第一题 唐老师所写的逻辑也有子弹和飞机两个脚本,子弹脚本:使用Translate朝自己正方向移动。飞机脚本:有一个枚举变量,在Update中检测数字键来切换发射模式(空格键发射)。空格键按下后触发Fire方法,Fire方法中判断发射类型(switch),使用Instantiate(预制体, 位置, 角度)创建,这是环形的发射:

image.png

可以根据全局公共变量roundNum来改变子弹数量。

第二题:

image.png

先分析1:通过角度控制倾斜率是设置一个β角,它是看向点与摄像机向量和看向点后方的夹角。

2:鼠标滑轮控制摄像机距离看向点的距离

3:控制看向点距离物体中心点的距离,距离是正上方 * n,控制n的大小即可。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CameraControl : MonoBehaviour
{
    public Transform cube1;
    public Vector3 targetPos;//看向点
    public float multiple;//看向点上方倍数
    public float angle;//看向点正后方 与 摄像机B、看向点A之间AB向量 的夹角
    public float distance;//两点距离
    public float maxDistance;
    public float minDistance;
    public float mouseScrollSpeed;//鼠标滑轮滚动灵敏度
    float time;



    // Update is called once per frame
    void Update()
    {
        distance += -Input.GetAxis("Mouse ScrollWheel") * mouseScrollSpeed;//获取负的鼠标滑轮滚动度(0~1) * 灵敏度  注:负的滚动度可以模拟向前滑靠近的效果。
        distance = Mathf.Clamp(distance, minDistance, maxDistance);//夹紧函数:超过取极值,中间取本身。
        targetPos = cube1.position + cube1.up * multiple;//看向点位置
        Vector3 dir = Quaternion.AngleAxis(angle, cube1.right) * (-cube1.forward);//获取方向向量
        Vector3 cameraPos = targetPos + dir * distance;
        this.transform.position = Vector3.Lerp(this.transform.position, cameraPos, Time.deltaTime);
        Debug.DrawLine(this.transform.position, targetPos, Color.green);
        Quaternion que = Quaternion.LookRotation(-dir);//方向向量转换为四元数
        //四元数的球形插值
        this.transform.rotation = Quaternion.Slerp(this.transform.rotation, que, Time.deltaTime);
    }
}

六、延迟函数

概念:延迟调用函数。

1、延迟函数使用

①、延迟函数

英语生词:Invoke 调用

image.png

②、延迟重复执行

英语生词:Repeating 重复的

image.png

③、取消延迟函数

全部取消和指定名取消

image.png

④、判断存在延迟函数

image.png

⑤、失活对延迟函数影响

image.png

通常与OnEnable和OnDisable联用。

⑥、练习题

image.png

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class L2 : MonoBehaviour
{
    int time = 0;
    // Start is called before the first frame update
    void Start()
    {
        InvokeRepeating("Print", 2, 2);
        //CancelInvoke("Print");
        //CancelInvoke();
    }
    void Print()
    {
        time += 2;
        print($"{time}");
    }
}

练习题2

image.png

七、协同程序

1、多线程示例

image.png

线程休眠是:Thread.Sleep(1000);

void OnDestroy(){
    t.Abort();
    t = null;
}

新线程无法访问Unity中的内容是无法再新的线程中控制物体(如去移动或打印其信息)

由于副线程是无法访问Unity相关对象的,所以一般处理网络相关和复杂算法。

2、协同程序概念

//协同程序简称协程//它是“假”的多线程,它不是多线程
//它的主要作用//将代码分时执行,不卡主线程//简单理解,是把可能会让主线程卡顿的耗时的逻辑分时分步执行
//主要使用场景
//异步加载文件//异步下载文件//场景异步加载
//批量创建时防止卡顿

3、协程和线程区别

image.png

协程是在主线程执行到协程函数时,在主线程上开启的,可以分步执行,在Unity循环中不断判断是否执行下一段协程代码。

4、协程使用

①、声明协程格式说明

//继承MonoBehavior的类 都可以开启 协程函数
//第一步:申明协程函数
// 协程函数2个关键点
//1-1返回值为IEnumerator类型及其子类
//1-2函数中通过 yield return 返回值;进行返回

②、协程声明示例

//关键点一: 协同程序(协程)函数 返回值 必须是 IEnumerator或者继承它的类型
IEnumerator MyCoroutine(int i, string str)
{
    print(i);
    //关键点二: 协程函数当中 必须使用 yield return 进行返回
    yield return new WaitForSeconds(5f);
    print(str);
}

yield return有将协程分段的作用

③、开启协程

StartCoroutine(协程函数调用);//这是最常用的,传入一个IEnumerator来开启

image.png

④、关闭协程

image.png

Coroutine c1=startcoroutine(Mycoroutine(1,“123"));
Coroutine c2=startcoroutine(MyCoroutine(1,“123"));
Coroutine c3=startcoroutine(Mycoroutine(1,"123"));
//第三步:关闭协程
//关闭所有协程
//stopAllCoroutines();
//关闭指定协程
Stopcoroutine(c1);

⑤、不同返回值(控制协程执行)

image.png

⑥、协程受对象和组件失活销毁的影响

//协程开启后
//组件和物体销毁,协程不执行
//物体失活协程不执行,组件失活协程执行

即只有脚本组件失活会继续执行协程

⑦、练习题

image.png

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Coroutine : MonoBehaviour
{
    int time = 0;
    private void Start()
    {
        StartCoroutine(Timer());
    }
    IEnumerator Timer()
    {
        while (true)
        {
            print(time++);
            yield return new WaitForSeconds(1f);
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Coroutine : MonoBehaviour
{
    public GameObject target;
    private void Start()
    {
        StartCoroutine(Timer());
    }
    IEnumerator Timer()
    {
        for (int i = 0; i < 100; i++)
        {
            for (int j = 0; j < 1000; j++)
            {
                Vector3 pos = new Vector3(Random.Range(0, 100), Random.Range(0, 100), Random.Range(0, 100));
                Instantiate(target, pos, Quaternion.identity);
            }
            yield return new WaitForSeconds(2f);
        }
    }
}

5、协程原理

①、协程的本质

image.png

迭代器遍历:

image.png

②、协程调度器

image.png

③、练习题

image.png

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MakeReturnTime
{
    public IEnumerator ie;
    public float time;
}

public class MyStartCoroutine : MonoBehaviour
{
    private static MyStartCoroutine instance;
    public static MyStartCoroutine Instance => instance;
    private void Awake()
    {
        instance = this;
    }

    private List<MakeReturnTime> makeReturnTimes = new List<MakeReturnTime>();
    
    public void AddIEnumerator(IEnumerator ie)
    {
        if (ie.MoveNext())
        {
            if(ie.Current is int)
            {
                MakeReturnTime makeReturnTime = new MakeReturnTime();
                makeReturnTime.ie = ie;
                makeReturnTime.time = Time.time + (int)ie.Current;
                makeReturnTimes.Add(makeReturnTime);
            }
        }
    }

    // Update is called once per frame
    void Update()
    {
        //从后往前判断(防止移出导致索引错误)达到时间就移动到下一段去,MoveNext()返回false则移除。
        for (int i = makeReturnTimes.Count - 1; i >= 0; i--)
        {
            if(makeReturnTimes[i].time <= Time.time)
            {
                if (makeReturnTimes[i].ie.MoveNext())
                {
                    if (makeReturnTimes[i].ie.Current is int)
                    {
                        makeReturnTimes[i].time = Time.time + (int)makeReturnTimes[i].ie.Current;
                    }
                    else
                    {
                        makeReturnTimes.RemoveAt(i);
                    }
                }
                else
                {
                    makeReturnTimes.RemoveAt(i);
                }
            } 
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Coroutine : MonoBehaviour
{
    public GameObject target;
    private void Start()
    {
        //StartCoroutine(Timer());
        MyStartCoroutine.Instance.AddIEnumerator(Timer());
    }
    IEnumerator Timer()
    {
        print(1);
        yield return 1;
        print(2);
        yield return 2;
        print(3);
        yield return 3;
        print(2);
        yield return 2;
    }
}

八、Resources动态加载

1、Unity特殊文件夹

①、工程路径获取

//注意 该方式 获取到的路径 一般情况下 只在 编辑模式下使用
//我们不会在实际发布游戏后 还使用该路径
//游戏发布过后 该路径就不存在了
print(Application.dataPath);

②、Resources文件夹

//路径获取:
//一般不获取
//只能使用Resources相关API进行加载
//如果硬要获取 可以用工程路径拼接

print(Application.dataPath +"/Resources");

//注意:
//需要我们自己将创建
//作用:
//资源文件夹
//1-1.需要通过Resources相关API动态加载的资源需要放在其中
//1-2.该文件夹下所有文件都会被打包出去
//1-3.打包时unity会对其压缩加密
//1-4.该文件夹打包后只读 只能通过Resources相关API加载

③、StreamingAssets 流动资源文件夹

//路径获取:
print(Application.streamingAssetsPath);
//注意:
//需要我们自己将创建
//作用:
//流文件夹
//2-1.打包出去不会被压缩加密,可以任由我们摆布
//2-2.移动平台只读,PC平台可读可写
//2-3.可以放入一些需要自定义动态加载的初始资源

④、persistentDataPath 持久化数据文件夹

英语生词:persistent:持续的。

//路径获取:
print(Application.persistentDataPath);
//注意:
//不需要我们自己将创建
//作用:
//固定数据文件夹
//3-1.所有平台都可读可写
//3-2.一般用于放置动态下载或者动态创建的文件,游戏中创建或者获取的文件都放在其中

⑤、Plugins 文件夹

//路径获取:
//一般不获取
//注意:
//需要我们自己将创建
//作用:
//插件文件夹
//不同平台的插件相关文件放在其中
//比如Ios和Android平台

⑥、Editor 编辑器文件夹

//路径获取:
//一般不获取

//如果硬要获取 可以用工程路径拼接print(Application.dataPath +"/Editor");

//注意:
//需要我们自己将创建
//作用:
//编辑器文件夹
//5-1.开发unity编辑器时,编辑器相关脚本放在该文件夹中
//5-2.该文件夹中内容不会被打包出去

⑦、默认资源文件夹 standard Assets

//路径:
//一般不获取
//注意:
//需要我们自己将创建
//作用:
//默认资源文件夹
//一般Unity自带资源都放在这个文件夹下
//代码和资源优先被编译

2、Resources 资源同步加载

①、Resources资源动态加载的作用

#region 知识点一 Resources资源动态加载的作用
//1.通过代码动态加载Resources文件夹下指定路径资源
//2.避免繁琐的拖曳操作
#endregion

②、常见资源类型

//1.预设体对象-Gameobject
//2.音效文件-Audioclip
//3.文本文件-TextAsset
//4.图片文件-Texture
//5.其它类型一需要什么用什么类型
//注意:
//预设体对象加载需要实例化
//其它资源加载一般直接用

③、资源加载-普通方法

//使用Load加载同名资源时会优先找Assets下Resources里的。
//1.预设体对象 想要创建在场景上 记住实例化
// 第一步:要去加载预设体的资源文件(本质上 就是加载 配置数据 在内存中)
Object oj1 = Resources.Load("Bullet");
//第二步:如果想要在场景上 创建预设体 一定是加载配置文件过后 然后实例化
Instantiate(oj1);
//2.音效资源
//第一步:就是加载数据
Object obj3 = Resources.Load("Music/BKMusic");
//第二步:使用数据 我们不需要实例化 音效切片 我们只需要把数据 赋值到正确的脚本上即可
audios.clip =obj3 as Audioclip;//这里的audios是Audio Source对象
audios.Play();
//3.文本资源
//文本资源支持的格式
//.txt
//.xml
//.bytes
//.json
//.html
//.csv//.....
TextAsset ta= Resources.Load("Txt/Test")as TextAsset;
//文本内容
print(ta.text);
//字节数据组
print(ta.bytes);
//4.图片
tex= Resources.Load("Tex/TestJPG") as Texture;
//5.其它类型 需要什么类型 就用什么类型就行
//可以使用另外的API
//6-1加载指定类型的资源
tex = Resources.Load("Tex/TestJPG",typeof(Texture)) as Texture;
ta= Resources.Load("Tex/TestIPG",typeof(TextAsset)) as TextAsset;print(ta.text);
//6-2加载指定名字的所有资源
object[]objs = Resources.LoadAll("Tex/TestJPG");
foreach(object item in objs){
    if(item is Texture)
    else if(item is TextAsset)
}

④、资源同步加载 泛型方法

TextAsset ta2 = Resources.Load<TextAsset>("Tex/TestJPG");
print(ta2.text);
tex = Resources.Load<Texture>("Tex/TestJPG");

⑤、练习题

请把之前四元数练习题中,发射散弹等相关逻辑改为动态加载资源并创建

void start()
//bullet = Resources.Load<Gameobject>("Bullet");

3、Resources异步加载

Resources异步加载 就是内部新开一个线程进行资源加载 不会造成主线程卡顿

异步加载

①、方法一:事件监听

//注意:
//异步加载 不能马上得到加载的资源 至少要等一帧
//1.通过异步加载中的完成事件监听 使用加载的资源
//这句代码 你可以理解 unity 在内部 就会去开一个线程进行资源下载
ResourceRequest rg=Resources.LoadAsync<Texture>("Tex/TestJPG");
//马上进行一个 资源下载结束 的一个事件函数监听
rq.completed += Loadover;
print(Time.framecount);
//这个 刚刚执行了异步加载的 执行代码 资源还没有加载完毕 这样用 是不对的
//一定要等加载结束过后 才能使用
//rg.asset xxxxxxxxxxxX

private void Loadover( Asyncoperation rq)
{
    print("加载结束");
    //asset 是资源对象 加载完毕过后 就能够得到它
    tex =(rg as ResourceRequest).asset as Texture;
    print(Time.framecount);
}

②、方法二:协程

image.png

还需要startcoroutine(Load());

③、练习题:

image.png

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

public class ResourcesMar
{
    private static ResourcesMar instance = new ResourcesMar();
    public static ResourcesMar Instance => instance;
    public void Load<T>(string pathName, UnityAction<T> unityAction) where T : Object
    {
        ResourceRequest rr = Resources.LoadAsync<T>(pathName);
        rr.completed += (a) =>
        {
            unityAction((a as ResourceRequest).asset as T);
        };
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class L18 : MonoBehaviour
{
    Texture tex;
    // Start is called before the first frame update
    void Start()
    {
        ResourcesMar.Instance.Load<Texture>("2", (rq) =>
        {
            tex = rq;
        });
    }

    private void OnGUI()
    {
        if(tex != null)
            GUI.DrawTexture(new Rect(0, 0, 100, 100), tex);
    }
}

4、卸载指定资源

//1.卸载指定资源
//Resources.UnloadAsset 方法
//注意:
//该方法 不能释放 Gameobject对象 因为它会用于实例化对象
//它只能用于一些 不需要实例化的内容 比如 图片 和 音效 文本等等
void Update()
{
    if(Input.GetKeyDown(Keycode.Alpha1))
    {
        print(“加载资源”);
        tex= Resources.Load<Texture>("Tex/TestJPG");
    } 
    if(Input .GetKeyDown(KeyCode .Alpha2))
    {
        print("卸载资源");
        Resources.UnloadAsset(tex);
        tex= null;
    }  
}
//2.卸载未使用的资源
//注意:
//一般在过场景时和Gc一起使用
Resources.UnloadUnusedAssets();//卸载未使用资源
GC.Collect();//主动回收不被引用的对象

九、场景异步加载

1、同步加载回顾

#region 知识点一 回顾场景同步切换
SceneManager.Loadscene("Lesson20Test")
#region 场景同步切换的缺点
//在切换场景时
//unity会删除当前场景上所有对象
//并且去加载下一个场景的相关信息
//如果当前场景 对象过多或者下一个场景对象过多
//这个过程会非常的耗时 会让玩家感受到卡顿
//所以异步切换就是来解决该问题的
#endregion
#endregion

2、场景异步切换

//场景异步加载和资源异步加载 几乎一致 有两种方式
//1.通过事件回调函数 异步加载
Asyncoperation ao=sceneManager.LoadsceneAsync("Lesson20Test");
//当场景异步加载结束后 就会自动调用该事件函数 我们如果希望在加载结束后 做一些事情 那么久可以在该函数
//写处理逻辑
ao.completed +=(a)=>
{
    print("加载结束");
};
IEnumerator Loadscene(string name)
//第一步
//异步加载场景
AsyncOperation ao=SceneManager.LoadsceneAsync(name);
//unity内部的 协程协调器 发现是异步加载类型的返回对象 那么就会等待
//等待异步加载结束后 才会继续执行 迭代器函数中后面的步骤
print("异步加载过程中 打印的信息");
//协程的好处 是异步加载场景时 我可以在加载的同时 做一些别的逻辑
//yield return ao;
//第二步
print("异步加载结束后 打印的信息");
//比如 我们可以在异步加载过程中 去更新进度条
//第一种 就是利用 场景异步加载 的进度 去更新 但是 不是特别准确 一般也不会直接用
while(!ao.isDone)
{
    print(ao.progress);
    yield return null;
}
//第二种 就是根据你游戏的规则 自己定义 进度条变化的条件
yield return ao,
//场景加载结束 更新20%进度
//接着去加载场景中 的其它信息
//比如红
//动态加载怪物
//这时 进度条 再更新20%
//动态加载 场景模型
//这时 就认为 加载结束了 进度条顶满
//隐藏进度条

3、异步加载练习题:

image.png

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;

public class SceneAsync
{
    private SceneAsync instance = new SceneAsync();
    public SceneAsync Instance => instance;

    private SceneAsync()
    {

    }

    public void Load(string name, UnityAction action)
    {
        AsyncOperation asyncOperation = SceneManager.LoadSceneAsync(name);
        asyncOperation.completed += (a) => {
            action();
        };
    }
}

十、LineRenderer画线组件

1、参数介绍

Loop:是否终点起始自动相连

Positions:线段的点,可以增加点来扩展线段(通过size/大小来控制点的数量)

线段宽度曲线调整:可以右键添加调整线的粗细

Color:颜色变化

Corner Vertices:(角顶点,圆角)此属性指示在一条线中绘制角时使用了多少额外的顶点增加此值,使线角看起来更圆

End Cap Vertices:(终端顶点,圆角)终点圆角

image.png

注意:如果你要使用受到光影响的材质,先勾选Generate Lighting Data(生成照明数据)

image.png

image.png

2、代码控制

//动态添加一个线段
Gameobject line = new Gameobject();line.name = "Line";
LineRenderer lineRenderer= line.Addcomponent<LineRenderer>();
//首尾相连
lineRenderer.loop = true;
//开始结束宽lineRenderer.startWidth=0.02f;lineRenderer.endwidth=0.02f;
//开始结束颜色lineRenderer.startColor=Color.white;lineRenderer.endcolor= Color.red;
//设置材质
m= Resources.Load<Material>("M");lineRenderer.material =m;
//设置点
//一定注意 设置点 要 先设置点的个数lineRenderer.positioncount=4;
//接着就设置 对应每个点的位置
lineRenderer.setPositions(new Vector3[]{ new Vector3(0,,0)
new Vector3(0,0,5)
new Vector3(5,0,5)});
lineRenderer.SetPosition(3,new Vector3(5,0,0));
//是否使用世界坐标系//决定了 是否随对象移动而移动lineRenderer.useWorldSpace=false;
//让线段受光影响 会接受光数据 进行着色器计算lineRenderer.generateLightingData=true;

3、练习题

image.png

1、画个圆

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LineDraw : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    private void Update()
    {
        DrawLineRenderer(new Vector3(0, 0, 0), 4);
    }

    public void DrawLineRenderer(Vector3 o1, float r1)
    {
        LineRenderer lr = this.GetComponent<LineRenderer>();
        lr.positionCount = 360;
        Vector3 tempPos = new Vector3(o1.x + r1, o1.y, o1.z);
        lr.SetPosition(0, tempPos);
        lr.loop = true;
        for (int i = 1; i < lr.positionCount; i++)
        {
            tempPos = Quaternion.AngleAxis(1, Vector3.up) * tempPos;
            lr.SetPosition(i, tempPos);
        }
    }
}

2、长按鼠标画鼠标轨迹

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LineMouseDown : MonoBehaviour
{
    LineRenderer lr;
    Vector3 pos;
    private void Start()
    {
        lr = this.gameObject.GetComponent<LineRenderer>();
        lr.startWidth = 0.5f;
        lr.endWidth = 0.5f;
        lr.positionCount = 0;
    }
    // Update is called once per frame
    void Update()
    {
        if (Input.GetMouseButton(0))
        {
            lr.positionCount += 1;
            pos = Input.mousePosition;
            pos.z = 10;
            lr.SetPosition(lr.positionCount - 1, Camera.main.ScreenToWorldPoint(pos));
        }
    }
}

十一、范围检测

1、碰撞检测回顾

image.png

2、范围检测API

①、盒状范围检测

//必备条件:想要被范围检测到的对象 必须具备碰撞器
//注意点:
//1.范围检测相关API只有当执行该句代码时 进行一次范围检测 它是瞬时的//2.范围检测相关API 并不会真正产生一个碰撞器 只是碰撞判断计算而已

//范围检测API

//1.盒状范围检测
//参数一:立方体中心点
//参数二:立方体三边大小
//参数三:立方体角度
//参数四:检测指定层级(不填检测所有层)
//参数五:是否忽略触发器 UseGloba1-使用全局设置 co1lide-检测触发器 Ignore-忽略触发器 不填使用useGloba1//返回值:在该范围内的触发器(得到了对象触发器就可以得到对象的所有信息)

print(LayerMask.NameToLayer("UI"));
Collider[] colliders = physics .OverlapBox( Vector3.zero, Vector3.one, Quaternion.AngleAxis(45, Vector3.up),
1<< LayerMask.NameToLayer("UI")
1<<LayerMask.NameToLayer("Default"),QueryTriggerInteraction.UseGlobal);
//0000 0001
//0010 0000

//重要知识点:
//关于层级
//通过名字得到层级编号 LayerMask.NameToLayer//我们需要通过编号左移构建二进制数//这样每一个编号的层级 都是 对应位为1的2进制数//我们通过 位运算 可以选择想要检测层级
//好处 一个int 就可以表示所有想要检测的层级信息
//层级编号是 8~31 刚好32位
//是一个int数
//每一个编号 代表的 都是二进制的一位
//0-1<<0-0000 0000 0000 0000 0000 0000 0000 0001= 1
//1-1<<1-0000 0000 0000 0000 0000 0000 0000 0010 = 2
//2-1<<2-0000 0000 0000 0000 0000 0000 0000 0100 = 4
//3-1<<3-0000 0000 0000 0000 0000 0000 0000 1000=8
//4-1<<4-0000 0000 0000 0000 0000 0000 0001 0000 = 16
//5-1<<5-0000 0000 0000 0000 0000 0000 0010 0000 = 32

//另一个API
//返回值:碰撞到的碰撞器数量
//参数:传入一个数组进行存储
//Physics.overlapBoxNonA1loc()
if(Physics.0verlapBoxNonAlloc(Vector3.zero, Vector3.one, colliders)!= 0)

②.球形范围检测

//2.球形范围检测
//参数一:中心点//参数二:球半径
//参数三检测指定层级(不填检测所有层)
是否忽略触发器 useGloba1-使用全局设置 co1lide-检测触发器 Ignore-忽略触发器 不填使用useGlobal//参数四:在该范围内的触发器(得到了对象触发器就可以得到对象的所有信息)//返回值:
colliders= Physics.0verlapsphere(Vector3.zero, 5,1<<LayerMask.NameToLayer("Default"), this.transform.rotation);

//另一个API
//返回值:碰撞到的碰撞器数量
//参数:传入一个数组进行存储
//Physics.overlapsphereNonAlloc
if( Physics.overlapSphereNonAlloc(Vector3.zero,5,colliders)!= 0)

③、胶囊范围检测

//3.胶囊范围检测
//参数一:半圆一中心点
//参数二:半圆二中心点
//参数三:半圆半径
//参数四:检测指定层级(不填检测所有层)
//参数五:是否忽略触发器 UseGlobal-使用全局设置 Collide-检测触发器 Ignore-忽略触发器 不填使用useGlobal
//返回值:在该范围内的触发器(得到了对象触发器就可以得到对象的所有信息)
colliders = physics.0verlapcapsule(Vector3.zero, Vector3.up, 1,1 <<LayerMask,NameToLayer("UI"),QueryTriggerInteraction.UseGlobal);

//另一个API
//返回值:碰撞到的碰撞器数量
//参数:传入一个数组进行存储
//Physics.0verlapcapsuleNonA110c
if( Physics.0verlapcapsuleNonAlloc(Vector3.zero.
Vector3.up,1,colliders )!=0

3、练习题

世界坐标原点有一个立方体,键盘WASD键可以控制其前后移动和旋转请结合所学知识实现

1.按」键在立方体面朝向前方1米处进行立方体范围检测 2.按K键在立方体前面5米范围内进行胶囊范围检测 3.按L键以立方体脚下为原点,半径10米内进行球形范围检测

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PhysicsTest : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKey(KeyCode.W))
        {
            this.transform.Translate(this.transform.forward * 3 * Time.deltaTime, Space.Self);
        }
        if (Input.GetKey(KeyCode.S))
        {
            this.transform.Translate(-this.transform.forward * 3 * Time.deltaTime, Space.Self);
        }
        if (Input.GetKey(KeyCode.A))
        {
            this.transform.Rotate(-this.transform.up * 10 * Time.deltaTime, Space.Self);
        }
        if (Input.GetKey(KeyCode.D))
        {
            this.transform.Rotate(this.transform.up * 10 * Time.deltaTime, Space.Self);
        }
        if (Input.GetKey(KeyCode.J))
        {
            Collider[] colliders = Physics.OverlapBox(this.gameObject.transform.position + this.transform.forward, Vector3.one, this.transform.rotation);
            print("this.transform.forward" + (this.gameObject.transform.position + this.transform.forward));
            print("Vector3.forward" + (this.gameObject.transform.position + Vector3.forward));
            // 过滤掉自身的碰撞体
            List<Collider> otherColliders = new List<Collider>();
            foreach (Collider col in colliders)
            {
                if (col.gameObject != this.gameObject)
                {
                    otherColliders.Add(col);
                }
            }
            print(otherColliders.Count);
        }
        if (Input.GetKey(KeyCode.K))
        {
            Collider[] colliders = Physics.OverlapCapsule(this.transform.position + this.transform.forward, this.transform.position + Vector3.forward * 4f, 1);
            print(colliders.Length);
        }
        if (Input.GetKey(KeyCode.L))
        {
            Collider[] colliders = Physics.OverlapSphere(this.transform.position + this.transform.forward * 5.5f, 5);
            print(colliders.Length);
        }
    }
}

十二、射线检测

射线检测:检测一个点发射指定方向射线,判断该射线与哪些碰撞器相交。

1、射线对象

//注意:
//理解参数含义
//参数一:起点
//参数二:方向(一定记住 不是两点决定射线方向,第二个参数 直接就代表方向向量)
//目前只是申明了一个射线对象 对于我们来说 没有任何的用处Ray r= new Ray(Vector3.right,Vector3.forward);
//Ray中的参数
print(r.origin);//起点
print(r.direction);//方向
//2.摄像机发射出的射线
// 得到一条从屏幕位置作为起点
// 摄像机视口方向为 方向的射线
Ray r2= Camera.main.ScreenPointToRay(Input.mousePosition);
//注意:
//单独的射线对于我们来说没有实际的意义
//我们需要用它结合物理系统进行射线碰撞判断

2、碰撞检测函数

①、仅检测

//射线检测也是瞬时的
//执行代码时进行一次射线检测
//1.最原始的射线检测
//准备一条射线
Ray r3 = new Ray(Vector3.zero, Vector3.forward);
// 进行射线检测 如果碰撞到对象 返回true
//参数一:射线
//参数二:检测的最大距离 超出这个距离不检测
//参数三:检测指定层级(不填检测所有层)//参数四:是否忽略触发器 UseGloba1-使用全局设置 collide-检测触发器 Ignore-忽略触发器 不填使用useGloba1//返回值:boo1 当碰撞到对象时 返回 true 没有 返回false
//可以把第一个射线参数写成起点和方向向量两个参数,这样就不用new一个射线
if (Physics.Raycast(r3, 1000, 1 << LayerMask.NameToLayer("Monster"), queryTriggerInteraction.UseGlobal))
print("碰撞到了对象”);

②、检测单个对象

//物体信息类 RaycastHit
RaycastHit hitInfo;
//参数一:射线
//参数二:RaycastHit是结构体(值类型) unity会通过out 关键在 在函数内部处理后 得到碰撞数据后返回到该参数中
//参数三:距离
//参数四:检测指定层级(不填检测所有层)
/参数五:是否忽略触发器 UseGloba1-使用全局设置 collide-检测触发器 Ignore-忽略触发器 不填使用useGlobal
if( Physics.Raycast(r3,out hitInfo,1000,1<<LayerMask.NameToLayer("Monster"),QueryTriggerInteraction.UseGlobal)
{
    print("碰撞到了物体 得到了信息");
    //碰撞器信息
    print("碰撞到物体的名字"+hitInfo.collider.gameobject.name);
    //碰撞到的点
    print(hitInfo.point);
    //法线信息
    print(hitInfo.normal);
    //得到碰撞到对象的位置
    print(hitInfo.transform.position);
    //得到碰撞到对象 离自己的距离
    print(hitInfo.distance);
}
//RaycastHit 该类 对于我们的意义//它不仅可以得到我们碰撞到的对象信息//还可以得到一些 碰撞的点 距离 法线等等的信息
//还有一种重载 不用传入 射线 直接传入起点 和 方向 也可以用于判断
if (Physics.Raycast(Vector3.zero, vector3.forward, out hitInfo, 1080, 1 << LayerMask,NameToLayer("Monster")

③、检测多个对象

//3.获取相交的多个物体
//可以得到碰撞到的多个对象
//如果没有 就是容量为0的数组
//参数一:射线
//参数二:距离 
//参数三:检测指定层级(不填检测所有层)
//参数四:是否忽略触发器 useGlobal-使用全局设置 co1lide-检测触发器 Ignore-忽略触发器 不填使用useGlobal
RaycastHit[] hits = physics.RaycastAll(r3, 1000, 1 << LayerMask,NameToLayer("Monster"), QueryTriggerInteraction.useGlobal
for(inti=0:i< hits.Length; i++)
{
    print("碰到的所有物体 名字分别是"+ hits[i].collider.gameobject.name);
}
//还有一种重载 不用传入 射线 直接传入起点 和 方向 也可以用于判断
//之前的参数一射线 通过两个点传入
//还有一种函数 返回的碰撞的数量 通过out得到数据
//还有一种函数 返回的碰撞的数量 通过out得到数据
if(Physics.RaycastNonAlloc(r3,hits, 1800, 1<<LayerMask.NameToLayer("Monster"),
QueryTriggerInteraction.UseGlobal))
{
    print();
}