30.碰撞检测函数

30.核心系统-物理系统-碰撞检测函数


30.1 知识点

知识点回顾

  • 如何让两个游戏物体之间产生碰撞:两个对象至少有一个刚体和两个碰撞器
  • 如何让两个物体之间碰撞时表现出不同效果:使用不同的物理材质
  • 触发器的作用是什么:让两个物体碰撞没有物理效果,只进行碰撞触发处理
  • 注意:碰撞和触发响应函数属于特殊的生命周期函数也是通过反射调用
  • 每次物理帧更新就会检测碰撞和触发响应函数

物理碰撞检测响应函数

代码

// OnCollisionEnter方法 碰撞触发接触时会自动执行这个函数
private void OnCollisionEnter(Collision collision)
{
    // 获取碰撞到的对象的相关信息
    Collider collider = collision.collider; // 碰撞到的对象的碰撞器信息
    GameObject gameObject = collision.gameObject; // 碰撞到的对象
    Transform transform = collision.transform; // 碰撞到的对象的位置信息

    // 获取接触点数
    int contactCount = collision.contactCount;
    ContactPoint[] pos = collision.contacts; // 返回接触点类数组,可以查看每个接触点的具体的坐标

    print(this.name + "被" + collision.gameObject.name + "撞到了");
}

// OnCollisionExit方法 碰撞结束分离时会自动执行的函数
private void OnCollisionExit(Collision collision)
{
    print(this.name + "被" + collision.gameObject.name + "结束碰撞了");
}

// OnCollisionStay方法 两个物体相互接触摩擦时会不停的调用该函数 接触但静止时不会调用
private void OnCollisionStay(Collision collision)
{
    print(this.name + "一直在和" + collision.gameObject.name + "接触");
}

图示

触发器检测响应函数

代码

// OnTriggerEnter方法 触发开始的函数 当第一次接触时会自动调用
protected virtual void OnTriggerEnter(Collider other)
{
    print(this.name + "被" + other.gameObject.name + "触发了");
}

// OnTriggerExit方法 触发结束的函数 当水乳相融的状态结束时会调用一次
private void OnTriggerExit(Collider other)
{
    print(this.name + "被" + other.gameObject.name + "结束水乳相融的状态了");
}

// OnTriggerStay方法 当两个对象水乳相融的时候会不停调用
private void OnTriggerStay(Collider other)
{
    print(this.name + "和" + other.gameObject.name + "正在水乳相融");
}

图示

要明确什么时候会响应函数

  • 只要挂载的对象能和别的物体产生碰撞或者触发,对应的这6个函数就能够被响应。前提是物理碰撞触发的是物理碰撞检测的函数,触发器触发的是触发检测的函数。
  • 6个函数不是说都得写,我们一般是根据需求来进行选择书写。
  • 如果是一个异形物体,刚体在父对象上,如果你想通过子对象上挂脚本检测碰撞是不行的,必须挂载到这个刚体父对象上才行。
  • 要明确物理碰撞和触发器响应的区别。

碰撞和触发器函数都可以写成虚函数,在子类去重写逻辑

  • 一般会把想要重写的碰撞和触发检测函数写成protected保护类型的,加上virtual变成虚函数。
  • 没有必要写成public,因为不会自己手动调用碰撞和触发检测函数,都是Unity通过反射帮助我们自动调用的。

30.2 知识点代码

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

public class Lesson30_物理系统之碰撞检测_碰撞检测函数 : MonoBehaviour
{
    #region 知识点回顾

    //1.如何让两个游戏物体之间产生碰撞(至少1个刚体 和 两个碰撞器)
    //2.如何让两个物体之间碰撞时表现出不同效果(物理材质)
    //3.触发器的作用是什么(让两个物体碰撞没有物理效果,只进行碰撞处理)

    #endregion

    #region 注意:碰撞和触发响应函数 属于 特殊的生命周期函数 也是通过反射调用

    #endregion

    #region 知识点一 物理碰撞检测响应函数

    //OnCollisionEnter方法 碰撞触发接触时会 自动执行这个函数
    //当该碰撞体/刚体已开始接触另一个刚体/碰撞体时,调用 OnCollisionEnter。
    private void OnCollisionEnter(Collision collision)
    {
        //Collision类型的 参数 包含了 碰到自己的对象的相关信息

        //Collision类关键参数

        //collider变量 碰撞到的对象碰撞器的信息
        //我们撞击的 Collider(只读)。
        Collider collider = collision.collider;

        //gameObject变量 碰撞对象的依附对象(GameObject)
        //您正在碰撞其碰撞体的 GameObject。(只读)。
        GameObject gameObject = collision.gameObject;

        //transform变量 碰撞对象的依附对象的位置信息
        //我们撞击的对象的 Transform(只读)。
        Transform transform = collision.transform;

        //触碰点数相关

        //contactCount变量 
        //获取此碰撞的接触点数。
        int contactCount = collision.contactCount;

        //contacts变量 返回接触点类数组 可以查看每个接触点的具体的坐标
        //物理引擎生成的接触点。应避免使用它,因为它会产生内存垃圾。改用 GetContact 或 GetContacts。
        ContactPoint[] pos = collision.contacts;

        //只要得到了 碰撞到的对象的 任意一个信息 就可以得到它的所有信息
        print(this.name + "被" + collision.gameObject.name + "撞到了");
    }

    //OnCollisionExit方法 碰撞结束分离时  会自动执行的函数
    //当该碰撞体/刚体已停止接触另一个刚体/碰撞体时,调用 OnCollisionExit。
    private void OnCollisionExit(Collision collision)
    {
        print(this.name + "被" + collision.gameObject.name + "结束碰撞了");
    }

    //OnCollisionStay方法 两个物体相互接触摩擦时 会不停的调用该函数 接触但静止时不会调用
    //对应正在接触刚体/碰撞体的每一个碰撞体/刚体,每帧调用一次 :ref::OnCollisionStay。
    private void OnCollisionStay(Collision collision)
    {
        print(this.name + "一直在和" + collision.gameObject.name + "接触");
    }

    #endregion

    #region 知识点二 触发器检测响应函数

    //OnTriggerEnter方法 触发开始的函数 当第一次接触时 会自动调用
    //GameObject 与另一个 GameObject 碰撞时,Unity 会调用 OnTriggerEnter。
    protected virtual void OnTriggerEnter(Collider other)
    {
        print(this.name + "被" + other.gameObject.name + "触发了");
    }

    //OnTriggerExit方法 触发结束的函数 当水乳相融的状态结束时 会调用一次
    //当 Collider other 已停止接触该触发器时调用 OnTriggerExit。
    private void OnTriggerExit(Collider other)
    {
        print(this.name + "被" + other.gameObject.name + "结束水乳相融的状态了");
    }

    //OnTriggerStay方法 当两个对象 水乳相融的时候 会不停调用
    //对于接触触发器的每一个 Collider /other/,每次物理更新调用一次 OnTriggerStay。
    private void OnTriggerStay(Collider other)
    {
        print(this.name + "和" + other.gameObject.name + "正在水乳相融");
    }

    #endregion

    #region 知识点三 要明确什么时候会响应函数

    //1.只要挂载的对象 能和别的物体产生碰撞或者触发 那么对应的这6个函数 就能够被响应 前提是物理碰撞触发的是物理碰撞检测的函数 触发器触发的是触发检测的函数
    //2.6个函数不是说 我都得写 我们一般是根据需求来进行选择书写
    //3.如果是一个异形物体,刚体在父对象上,如果你想通过子对象上挂脚本检测碰撞是不行的 必须挂载到这个刚体父对象上才行
    //4.要明确 物理碰撞和触发器响应的区别

    #endregion

    #region 知识点四 碰撞和触发器函数都可以写成虚函数 在子类去重写逻辑
    //一般会把想要重写的 碰撞和触发检测函数 写成protected保护类型的 加上virtual变成虚函数 没有必要写成public
    //因为不会自己手动调用碰撞和触发检测函数 都是Unity通过反射帮助我们自动调用的
    #endregion
}

30.3 练习题

在之前Input和Screen中的练习题基础上,加入一个点击鼠标左键可以发射一颗子弹飞出的功能

给坦克添加一个炮口空物体当做子弹飞出来的位置

添加点击飞出子弹代码,创建子弹预设体,拖拽赋值炮口空物体和子弹预设体

//Update内
if (Input.GetMouseButtonDown(0))
{
    //实例化一个子弹对象
    GameObject obj = Instantiate(bulletObj);
    //设置对象的位置
    obj.transform.position = bulletPos.position;
    //设置对象的角度
    obj.transform.eulerAngles = bulletPos.eulerAngles;
}

添加子弹飞出去的脚本,附加给子弹预设体

//Update内
//子弹一直往前飞
this.transform.Translate(Vector3.forward * moveSpeed * Time.deltaTime);

在上一题的基础上,加入子弹触碰到地面会自动消失的功能

给子弹预设体添加刚体,让它受到重力的作用,添加平面

发射子弹时如果和坦克自身的碰撞和重合了,可能一开始就会被移除,所以给平面添加一个Ground标签,方便碰撞检测判断是什么对象,判断自己碰撞到的对象的标签是什么,一定是特定对象才移除自己依附的子弹对象。

假如在OnCollisionEnter写碰撞逻辑,坦克本身就带有碰撞盒 ,当子弹和坦克自身的碰撞盒碰撞可能会产生力的作用,出现一些意想不到的效果,所以改在OnTriggerEnter写逻辑

private void OnTriggerEnter(Collider other)
{
    //问题一:如果发射子弹时  和坦克自身的碰撞和重合了 可能一开始 就会被移除
    //解决方案:判断自己碰撞到的对象 是什么 一定是特定对象 才移除自己
    if (other.gameObject.CompareTag("Ground") ||
        other.gameObject.CompareTag("Monster"))
    {
        //碰撞到别的东西 就让子弹小时
        //一定是移除自己依附的GameObject对象 而不是脚本自己
        Destroy(this.gameObject);
    }

    //问题二:坦克本身就带有碰撞盒  当子弹和坦克自身的碰撞盒碰撞可能会产生力的作用 出现一些意想不到的效果
    //解决方案:把子弹做成触发器 这样就没有了力的作用
}

在上一题的基础上,在场景加入一些立方体,每个立方体被子弹打3下就会消失

给立方体添加OnTriggerEnter碰撞检测函数逻辑,血量小于等于0就销毁自身,把立方体的标签修改为Monster,让他子弹判断触发到Monster标签的对象也销毁自身

public int HP = 3;

//当子弹碰到我时  就减血 血量为0了 就移除
private void OnTriggerEnter(Collider other)
{
    //由于场景上 只有子弹时触发器 所以我们可以不用进行任何判断 就可以完成这个功能
    //减血
    HP -= 1;
    //为0就移除自己
    if (HP <= 0)
    {
        Destroy(this.gameObject);
    }
}

30.4 练习题代码

Lesson30_物理系统之碰撞检测_练习题

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

public class Lesson30_物理系统之碰撞检测_练习题 : MonoBehaviour
{
    #region Lesson21 练习题

    public float moveSpeed = 50;
    public float rotateSpeed = 50;

    public Transform head;
    public float headRotateSpeed = 50;

    #endregion

    #region Lesson22 练习题

    //声明炮管尾部Transform和炮管移动速度 炮管尾部Transform在Inspector拖拽赋值
    //class内
    public Transform gunTail;
    public float gunTailRotateSpeed = 50;

    //声明摄像机看向的对象Transform和摄像机旋转的速度 摄像机看向的对象Transform在Inspector拖拽赋值

    //class内

    public GameObject mainCamera;
    //摄像机看向的对象
    public Transform target;
    //摄像机旋转的速度
    public float roundSpeed = 50;

    #endregion

    #region Lesson30 练习题一

    //子弹预设体
    public GameObject bulletObj;
    //炮管 子弹将要出现的位置
    public Transform barrelHead;

    #endregion
    
    void Update()
    {
        #region Lesson21 练习题
        //1.使用之前的坦克预设体,用WASD键控制坦克的前景后退,左右转向

        //会用到
        //Transform当中的 位移 自转 相关知识点
        //键盘输入 相关知识点

        //可以利用默认轴向
        //Input.GetAxis("Horizontal"); 水平方向 -1到1之间的值 0就是没有按下
        //Input.GetAxis("Vertical");  竖直方向 -1到1之间的值 0就是没有按下

        //ws键 控制位移
        // 这公式 是 : 前进方向 * 速度 * 时间 * 当前是否移动(-1~1 相当于 正向还是反向的感觉 不按就不动 0)
        this.transform.Translate(Vector3.forward * moveSpeed * Time.deltaTime * Input.GetAxis("Vertical"));

        //ad键 控制 左右转向
        // 这公式 是 : 转动的轴 * 速度 * 时间 * 当前是否移动(-1~1 相当于 正向还是反向的感觉 不按就不动 0)
        this.transform.Rotate(Vector3.up * rotateSpeed * Time.deltaTime * Input.GetAxis("Horizontal"));

        //2.在上一题的基础上,鼠标左右移动控制炮台的转向

        //会用到
        //鼠标输入相关 
        //Input.GetAxis("Mouse X");
        // 这公式 是 : 转动的轴 * 速度 * 时间 * 当前是否移动(-1~1 相当于 正向还是反向的感觉 不按就不动 0)
        head.Rotate(Vector3.up * headRotateSpeed * Time.deltaTime * Input.GetAxis("Mouse X"));

        #endregion

        #region Lesson22 练习题
        //1.在输入习题的基础上,鼠标滚轮控制控制炮管的抬起放下

        //使用鼠标滚轮滚动Api Input.mouseScrollDelta.y 调整炮管
        //Update内
        gunTail.Rotate(Vector3.right * gunTailRotateSpeed * Time.deltaTime * Input.mouseScrollDelta.y);

        //2.在上一题的基础上,加入长按鼠标右键移动鼠标
        // 可以让摄像机围着坦克旋转,改变观察坦克的视角

        //脚本挂载到摄像机上
        //让摄像机一直看向目标 检测鼠标长按后检测鼠标水平移动旋转摄像机

        //Update内

        //每一帧实时让摄像机看向目标对象
        mainCamera.transform.LookAt(target);

        //使用 绕着某一个点 的某一个轴 旋转的知识点 进行处理
        //鼠标 右键长按 知识点
        if (Input.GetMouseButton(1))
        {
            mainCamera.transform.RotateAround(target.position, //围绕的点
                Vector3.up, //哪个轴
                roundSpeed * Time.deltaTime * Input.GetAxis("Mouse X"));//速度 Mouse X -1到1 
        }

        #endregion

        #region Lesson30 练习题一
        //在之前Input和Screen中的练习题基础上,加入一个点击鼠标左键可以发射一颗子弹飞出的功能

        //左键按下
        if (Input.GetMouseButtonDown(0))
        {
            //实例化一个子弹对象
            GameObject obj = Instantiate(bulletObj);
            //设置对象的位置
            obj.transform.position = barrelHead.position;
            //角度
            //sphere.transform.rotation = barrelHead.rotation;
            //角度
            obj.transform.eulerAngles = barrelHead.eulerAngles;
        }

        #endregion


        #region Lesson30 练习题二
        //在上一题的基础上,加入子弹触碰到地面会自动消失的功能

        #endregion

        #region Lesson30 练习题三
        //在上一题的基础上,在场景加入一些立方体,每个立方体被子弹打3下就会消失

        #endregion
    }
}

Lesson30_物理系统之碰撞检测_练习题子弹

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

public class Lesson30_物理系统之碰撞检测_练习题子弹 : MonoBehaviour
{
    #region Lesson30 练习题一
    public float moveSpeed = 20;
    #endregion

    void Update()
    {
        #region Lesson30 练习题一

        //Update内
        //子弹一直往前飞
        this.transform.Translate(Vector3.forward * moveSpeed * Time.deltaTime);

        #endregion
    }

    #region Lesson30 练习题二
    //在上一题的基础上,加入子弹触碰到地面会自动消失的功能


    //private void OnCollisionEnter(Collision collision)
    //{
    //    //问题一:如果发射子弹时  和坦克自身的碰撞和重合了 可能一开始 就会被移除
    //    //解决方案:判断自己碰撞到的对象 是什么 一定是特定对象 才移除自己
    //    if (collision.gameObject.CompareTag("Ground"))
    //    {
    //        //碰撞到别的东西 就让子弹小时
    //        //一定是移除自己依附的GameObject对象 而不是脚本自己
    //        Destroy(this.gameObject);
    //    }

    //    //问题二:坦克本身就带有碰撞盒  当子弹和坦克自身的碰撞盒碰撞可能会产生力的作用 出现一些意想不到的效果
    //    //解决方案:把子弹做成触发器 这样就没有了力的作用
    //}

    private void OnTriggerEnter(Collider other)
    {
        //问题一:如果发射子弹时  和坦克自身的碰撞和重合了 可能一开始 就会被移除
        //解决方案:判断自己碰撞到的对象 是什么 一定是特定对象 才移除自己
        if (other.gameObject.CompareTag("Ground") ||
            other.gameObject.CompareTag("Monster"))
        {
            //碰撞到别的东西 就让子弹小时
            //一定是移除自己依附的GameObject对象 而不是脚本自己
            Destroy(this.gameObject);
        }

        //问题二:坦克本身就带有碰撞盒  当子弹和坦克自身的碰撞盒碰撞可能会产生力的作用 出现一些意想不到的效果
        //解决方案:把子弹做成触发器 这样就没有了力的作用
    }

    #endregion
}

Lesson30_物理系统之碰撞检测_练习题立方体

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

public class Lesson30_物理系统之碰撞检测_练习题立方体 : MonoBehaviour
{
    #region Lesson30 练习题三
    //在上一题的基础上,在场景加入一些立方体,每个立方体被子弹打3下就会消失


    public int HP = 3;

    //当子弹碰到我时  就减血 血量为0了 就移除
    private void OnTriggerEnter(Collider other)
    {
        //由于场景上 只有子弹时触发器 所以我们可以不用进行任何判断 就可以完成这个功能
        //减血
        HP -= 1;
        //为0就移除自己
        if (HP <= 0)
        {
            Destroy(this.gameObject);
        }
    }

    #endregion
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏