4、Unity【基础】画线功能Linerenderer、物理系统Physics

在这里插入图片描述

画线功能Linerenderer

1、LineRenderer是什么

LineRenderer是Unity提供的一个用于画线的组件
使用它我们可以在场景中绘制线段
一般可以用于
1、绘制攻击范围 2、武器红外线 3、辅助功能 4、其它画线功能

2、LineRender参数相关

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

3、LineRender代码相关

private Material m;
void Start()
{
    //动态添加一个线段
    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, 0),
    new Vector3(0, 0, 5),new Vector3(5, 0, 5)}); //批量设置
    lineRenderer.SetPosition(3, new Vector3(5, 0, 0)); //索引设置
    //是否使用世界坐标系
    //决定了是否随对象移动而移动
    lineRenderer.useWorldSpace = true;
    //让线段受光影响会接受光数据进行着色器计算
    lineRenderer.generateLightingData = true;
}

思考1 请写一个方法,传入一个中心点,传入一个半径,用LineRender画个圆出来

using UnityEngine;

public class CicleLine : MonoBehaviour
{
    void Start()
    {
        DrawCicleLine(Vector3.zero, 5, 360);
    }
    public void DrawCicleLine(Vector3 centerPos, float r, int pointNum)
    {
        //画线对象
        GameObject obj = new GameObject();
        obj.name = "R";
        LineRenderer cicleLine = obj.AddComponent<LineRenderer>();
        cicleLine.loop = true;
        //设置点
        cicleLine.positionCount = pointNum;
        //圆的单位弧度
        float angle = 360f / pointNum;
        //画圆
        for (int i = 0; i < pointNum; i++)
        {
            cicleLine.SetPosition(i, centerPos + Quaternion.AngleAxis(angle * i, Vector3.up) * Vector3.forward * r);
        }
    }
}

思考2 在Game窗口长按鼠标用LineRender画出鼠标移动的轨迹

using UnityEngine;

public class MouseLine : MonoBehaviour
{
    private LineRenderer DrawLine;
    //为了设置画线位置的横截面(距离)
    private Vector3 nowMousePos;
    void Update()
    {
        if(Input.GetMouseButtonDown(0))
        {
            GameObject obj = new GameObject();
            DrawLine = obj.AddComponent<LineRenderer>();
            DrawLine.loop = false;
            DrawLine.startWidth = 0.5f;
            DrawLine.endWidth = 0.5f;
            DrawLine.positionCount = 0;
        }
        if (Input.GetMouseButton(0))
        {
            DrawLine.positionCount += 1;
            //得到鼠标的屏幕坐标转世界坐标的点位置
            //新加点为最后一个点
            //1、得到鼠标位置
            //Input.mousePosition
            //2、鼠标屏幕坐标转世界坐标
            //Camera.main.ScreenToWorldPoint(Input.mousePosition);
            nowMousePos = Input.mousePosition;
            nowMousePos.z = 10;
            DrawLine.SetPosition(DrawLine.positionCount-1, Camera.main.ScreenToWorldPoint(nowMousePos));
        }
    }
}

核心系统

在这里插入图片描述

1、物理系统之范围检测

回顾:物理系统之碰撞检测

碰撞产生的必要条件
1.至少一个物体有刚体
2.两个物体都必须有碰撞器
    
碰撞和触发
碰撞会产生实际的物理效果
触发看起来不会产生碰撞但是可以通过函数监听触发
碰撞检测主要用于实体物体之间产生物理效果时使用

什么是范围检测

游戏中瞬时的攻击范围判断一般会使用范围检测
没有实体物体只想要检测在指定某一范围是否让敌方受到伤害时便可以使用范围判断
在指定位置进行范围判断,我们可以得到处于指定范围内的对象
目的是对对象进行处理,比如受伤减血等

如何进行范围检测

必备条件
必备条件:想要被范围检测到的对象必须【具备碰撞器】
注意点:
1.范围检测相关API:只有当执行该代码时,进行一次范围检测,它是瞬时的
2.范围检测相关API:并不会真正产生一个碰撞器,只是碰撞判断计算而已
范围检测API
1.盒状范围检测
范围检测API
1.盒状范围检测
参数一:立方体中心点
参数二:立方体三边大小
参数三:立方体角度
参数四:检测指定层级(不填检测所有层)
参数五:是否忽略触发器(UseGlobal(默认)-使用全局设置 Collide-检测触发器 Ignore-忽略触发器)
返回值:在该范围内的触发器(得到了对象触发器就可以得到对象的所有信息)
    
重要知识点:关于层级
通过名字得到层级编号LayerMask.NameToLayer 我们需要通过编号左移构建二进制数
这样每一个编号的层级都是对应位为12进制数
我们通过位运算可以选择想要检测层级
好处一个int就可以表示所有想要检测的层级信息
    
    Collider[] colliders = Physics.OverlapBox(Vector3.zero, Vector3.one,
        Quaternion.AngleAxis(45, Vector3.up),
        1 << LayerMask.NameToLayer("UI"),
        QueryTriggerInteraction.UseGlobal);
    for (int i = 0; i < colliders.Length; i++)
    {
        print(colliders[i].gameObject.name);
    }
    
参数四的层级编号是0~31刚好32位是一个int数
每一个编号代表的都是二进制的一位
例如:
    0000 0001 左移5: 1 << 5
    0010 0000
    
另一个API
可以判断有无触发碰撞器
返回值:碰撞到的碰撞器数量
参数:传入一个数组进行存储
	if (Physics.OverlapBoxNonAlloc(Vector3.zero, Vector3.one, colliders) != 0){}
2.球形范围检测
2.球形范围检测
参数一:中心点
参数二:球半径
参数三:检测指定层级(不填检测所有层)
参数四:是否忽略触发器(UseGlobal(默认)-使用全局设置 Collide-检测触发器 Ignore-忽略触发器)
返回值:在该范围内的触发器(得到了对象触发器就可以得到对象的所有信息)
    Physics.OverlapSphere(Vector3.zero,3,1<<LayerMask.NameToLayer("UI"));

另一个API
可以判断有无触发碰撞器
返回值:碰撞到的碰撞器数量
参数:传入一个数组进行存储
    if (Physics.OverlapSphereNonAlloc(Vector3.zero, 3, colliders) != 0){}
3.胶囊范围检测
3.胶囊范围检测
参数一:半圆中心点
参数二:半圆二中心点
参数三:半圆半径
参数四:检测指定层级(不填检测所有层)
参数五:是否忽略触发器(UseGlobal(默认)-使用全局设置 Collide-检测触发器 Ignore-忽略触发器)
返回值:在该范围内的触发器(得到了对象触发器就可以得到对象的所有信息)
    Physics.OverlapCapsule(Vector3.zero, Vector3.up, 1, 1 << LayerMask.NameToLayer("UI"), 
                           QueryTriggerInteraction.UseGlobal);

另一个API
可以判断有无触发碰撞器
返回值:碰撞到的碰撞器数量
参数:传入一个数组进行存储
    if (Physics.OverlapCapsuleNonAlloc(Vector3.zero, Vector3.up, 1, colliders) != 0){}
总结
范围检测主要用于瞬时的碰撞范围检测
主要掌握
Physics类中的静态方法
球形、盒装、胶囊三种API的使用即可
思考 范围检测
世界坐标原点有一个立方体,键盘WASD键可以控制其前后移动和旋转
请结合所学知识实现
1.按J键在立方体面朝向前方1米处进行立方体范围检测
2.按K键在立方体前面5米范围内进行胶囊范围检测
3.按L键以立方体脚下为原点,半径10米内进行球形范围检测

using UnityEngine;

public class RangeSensing : MonoBehaviour
{
    public float moveSpeed = 8;
    public float rotateSpeed = 30;
    void Update()
    {
        transform.Translate(Vector3.forward * Time.deltaTime * moveSpeed * Input.GetAxis("Vertical"));
        transform.Rotate(Vector3.up, Input.GetAxis("Horizontal") * rotateSpeed * Time.deltaTime);

        if (Input.GetKeyDown(KeyCode.J))
        {
            Collider[] colliders = Physics.OverlapBox(transform.position + transform.forward,
                                                        Vector3.one * 0.5f, //此处为边长的一半
                                                        transform.rotation, //面朝向角度
                                                        1 << LayerMask.NameToLayer("Monster"));
            for (int i = 0; i < colliders.Length; i++)
            {
                print(colliders[i].gameObject.name + "受到伤害");
            }
        }

        if (Input.GetKeyDown(KeyCode.K))
        {
            Collider[] colliders = Physics.OverlapCapsule(transform.position,
                                            transform.position + transform.forward * 5,
                                            0.5f,
                                            1<<LayerMask.NameToLayer("Monster"));
            for (int i = 0; i < colliders.Length; i++)
            {
                print(colliders[i].gameObject.name + "受到伤害");
            }
        }

        if (Input.GetKeyDown(KeyCode.L))
        {
            Collider[] colliders = Physics.OverlapSphere(transform.position, 10, 1 << LayerMask.NameToLayer("Monster"));
            for (int i = 0; i < colliders.Length; i++)
            {
                print(colliders[i].gameObject.name + "受到伤害");
            }
        }
    }
}

2、物理系统之射线检测

在这里插入图片描述

在这里插入图片描述

1、什么是射线检测

物理系统中
目前我们学习的物体相交判断
1.碰撞检测的必备条件 
	1刚体 2碰撞器
2.范围检测的必备条件
	碰撞器
	
如果想要做这样的撞检测呢
1.鼠标选择场景上一物体
2.FPS射击游戏(无弹道-不产生实际的子弹对象进行移动)
等等需要判断一条线和物体的碰撞情况
射线检测就是来解决这些问题的
它可以在指定点发射一个指定方向的射线
判断该射线与哪些碰撞器相交,得到对应对象

2、射线对象

13D世界中的射线假设有一条
起点为坐标(1,0,0)
方向为世界坐标Z轴正方向的射线
注意:
理解参数含义
参数一:起点
参数二:方向向量(一定记住不是两点决定射线方向,第二个参数直接就代表方向向量)
	Ray ray = new Ray(Vector3.right,Vector3.forward);
Ray中的参数
    ray.origin; //起点
    ray.direction; //方向
    
2,摄像象机发射出的射线
得到一条从屏幕位置作为起点
摄像机视口方向为方向的射线
	Ray ray2 = Camera.main.ScreenPointToRay(Input.mousePosition);
	
注意
单独的射线对于我们来说没有实际的意义
我们需要用它结合物理系统进行射线碰撞判断

3、碰撞检测函数

Physics类中提供了很多进行射线检测的静态函数
它们有很多种重载类型,我们只需要掌握核心的几个函数
注意
射线检测也是瞬时的
执行代码时进行一次射线检测

1.最原始的射线检测(只会判断是否碰撞)
准备一条射线
	Ray ray = new Ray(Vector3.zero,Vector3.forward);
进行射线检测,如果碰撞到对象,则返回true
参数一:射线
参数二:检测的最大距离超出这个距离不检测
参数三:检测指定层级(不填检测所有层)
参数四:是否忽略触发器(UseGlobal(默认)-使用全局设置 Collide-检测触发器 Ignore-忽略触发器)
返回值:bool 当碰撞到对象时 返回true,没有返回false
    if (Physics.Raycast(ray, 1000, 1 << LayerMask.NameToLayer("UI"), QueryTriggerInteraction.UseGlobal))
    {
        print("碰撞到了对象");
    }
    
还有一种重载不用传入射线直接传入起点和方向也可以用于判断
就是把第一个参数,变成了射线的两个点(一个起点,一个方向)
    if (Physics.Raycast(Vector3.zero,Vector3.forward, 1000, 1 << LayerMask.NameToLayer("UI"), QueryTriggerInteraction.UseGlobal))
    {
    print("碰撞到了对象");
    }
2.获取相交的【单个物体信息】
物体信息类RaycastHit
	RaycastHit hitInfo;
参数一:射线
参数二:RaycastHit是结构体,是值类型Unity会通过out关键在在函数内部处理后,得到碰撞数据后返回到该参数中
参数三:检测距离
参数四:检测指定层级(不填检测所有层)
参数五:是否忽略触发器(UseGlobal(默认)-使用全局设置 Collide-检测触发器 Ignore-忽略触发器)
    if(Physics.Raycast(ray,out hitInfo,1000,1<<LayerMask.NameToLayer("UI"),QueryTriggerInteraction.UseGlobal))
    {
        print("得到了碰撞到物体的信息");
        //碰撞器信息
        print("碰撞到物体的名字" + hitInfo.collider.name);
        print("碰撞到物体的名字" + hitInfo.collider.gameObject.name);
        //碰撞到的点(相交位置)
        print("相交位置" + hitInfo.point);
        //平面的法线信息
        print("法线" + hitInfo.normal);
        //碰撞到对象的位置
        print("对象的位置" + hitInfo.transform.position);
        //碰撞到对象离自己的距离
        print("距离" + hitInfo.distance);
    }
    
另一种重载,不用传入射线(直接传入起点和方向也可以用于判断)
    if (Physics.Raycast(Vector3.zero, Vector3.forward, out hitInfo, 1000, 1 << LayerMask.NameToLayer("UI"), QueryTriggerInteraction.UseGlobal)){ }
3.获取相交的【多个物体信息】
可以得到碰撞到的多个对象
如果没有就是容量为0的数组
参数一:射线
参数二:检测距离
参数三:检测指定层级(不填检测所有层)
参数四:是否忽略触发器(UseGlobal(默认)-使用全局设置 Collide-检测触发器 Ignore-忽略触发器)
    RaycastHit[] hits = Physics.RaycastAll(ray,1000,1<<LayerMask.NameToLayer("UI"),QueryTriggerInteraction.UseGlobal);
    for (int i = 0; i < hits.Length; i++)
    {
        print(hits[i].collider.name);
    }
另一种重载,不用传入射线(直接传入起点和方向也可以用于判断)

	RaycastHit[] hits = Physics.RaycastAll(Vector3.zero, Vector3.forward, 1000,
			1<<LayerMask.NameToLayer("UI"),QueryTriggerInteraction.UseGlobal);
			
还有一种函数返回的碰撞的数量,通过out得到数据
	if(Physics.RaycastNonAlloc(ray, hits, 100, 1 << LayerMask.NameToLayer("UI"), QueryTriggerInteraction.UseGlobal) > 0)
    {
		//遍历hits
    }

4、使用时注意的问题

注意(使用参数)
距离、层级两个参数都是int类型
当我们传入参数时一定要明确传入的参数代表的是距离还是层级
举例
	if(Physics.Raycast(ray,1<<LayerMask.NameToLayer("UI")){}
这样写是错误的,第二个参数虽然是float但编辑器不会提示错误,因为第二个参数代表的是距离,不是层级

思考1 子弹射击到物体效果

1.请用资料区给的资源,实现鼠标点击场景上一面墙,在点击的位置创建子弹特效和弹孔
using UnityEngine;

public class ShootBullet : MonoBehaviour
{
    RaycastHit info;
    void Update()
    {
        //从屏幕发射一条射线
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        Debug.DrawRay(ray.origin, ray.direction);
        
        if (Input.GetMouseButtonDown(0))
        {
            //按下鼠标进行射线检测,从屏幕发射一条射线
            if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out info, 100, 1 << LayerMask.NameToLayer("Wall")))
            {
                //碰撞到的点和法向量
                //创建打击特效
                GameObject hitObj = Instantiate(Resources.Load<GameObject>("Effect/BoomEff"));
                //设置点位置
                hitObj.transform.position = info.point + info.normal * 0.1f;
                //设置角度,特效面向我们
                hitObj.transform.rotation = Quaternion.LookRotation(info.normal);
                //销毁特效
                Destroy(hitObj, 0.8f);

                hitObj = Instantiate(Resources.Load<GameObject>("Effect/BulletHole"));
                hitObj.transform.position = info.point + info.normal * 0.1f;
                hitObj.transform.rotation = Quaternion.LookRotation(info.normal);
            }
        }
    }
}

思考2 控制场景物体

2.场景上有一个平面,有一个立方体,当鼠标点击选中立方体时,
长按鼠标左键可以拖动立方体在平面上移动,松开鼠标左键取消选中
using UnityEngine;

public class SceneObject : MonoBehaviour
{
    private Transform nowSelObj;
    RaycastHit info;
    void Update()
    {
        //选中,鼠标左键按下选中
        if (Input.GetMouseButtonDown(0))
        {
            if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out info, 1000, 1 << LayerMask.NameToLayer("Monster")))
            {
                //记录选中的对象位置信息
                nowSelObj = info.transform;
            }
        }
        //长按拖动
        if (Input.GetMouseButton(0) && nowSelObj != null)
        {
            if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out info, 1000, 1 << LayerMask.NameToLayer("Wall")))
            {
                nowSelObj.position = info.point + Vector3.up * nowSelObj.transform.position.y;
            }
        }
        //取消选中,鼠标左键抬起取消选中
        if (Input.GetMouseButtonUp(0))
        {
            nowSelObj = null;
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值