基础3D数学Mathf函数库
print(Mathf.PI);print(Mathf.Abs(-10));print(Mathf.CeilToInt(1.2f));//向上取整print(Mathf.FloorToInt(1.2f));//向下取整//钳制函数//数值 最小值 最大值//如果数值超出范围 则返回最值 print(Mathf.Clamp(15,9,12));//返回12 最大才可以是12 print(Mathf.Clamp(10,9,15));//返回10 在合法范围中print(Mathf.Clamp(7,10,15));//返回10 最小的是10print(Mathf.Max(12,15,4,5,6,7));//取最大值 支持多参数print(Mathf.Max(5,9));print(Mathf.Min(5,9));print(Mathf.Min(5,9,6,4,2,1));//支持多参数取最小值print(Mathf.Pow(2,3));//2^3=8 乘方print(Mathf.RoundToInt(3.5f));//四舍五入 4print(Mathf.RoundToInt(3.2f));// 3print(Mathf.Sqrt(16));//开方运算 4print(Mathf.IsPowerOfTwo(4));//判断某数是否是2的n次方print(Mathf.Sign(-5));//判断某数的正负 返回-1print(Mathf.Sign(5));//正数 返回1//插值运 Lerp //result = start+(end-start)*t (一般是0<t=1时//速度表现上看可以实现匀速运动效果 float time = 0;time += Time.deltaTime;start = Mathf.Lerp(start, 30, time);
三角函数
1.角度和弧度转换
2.三角函数和反三角函数
print(Mathf.PI / 2*Mathf.Rad2Deg);//弧度转角度 Rad表示弧度 deg表角度print(56.5f*Mathf.Deg2Rad);//角度转弧度//三角函数//Mathf中计算三角函数值 传入的参数必须是弧度 Radprint(Mathf.Sin(30*Mathf.Deg2Rad));print(Mathf.Cos(60*Mathf.Deg2Rad));//反三角函数 根据三角函数值 逆向求出度数print(Mathf.Asin(0.5f));// Π/6print(Mathf.Acos(1));//0
坐标系
- 物体坐标系
- 世界坐标系
- 屏幕坐标系
- 视口坐标系(左下角 (0,0)右上角(1,1))
不同坐标系下的相互转换:
//世界坐标系 下的坐标信息//物体的世界坐标系坐标print(transform.position);//相对于父物体的位置 在父物体坐标系下的位置信息print(transform.localPosition);//屏幕坐标系//Input.mousePoistion//Screen.width;//Screen.height;//视口坐标//摄像机面板上设置//世界转本地(逆向的本地转世界)transform.InverseTransformDirection(Vector3.forward);transform.InverseTransformPoint(Vector3.down);transform.InverseTransformVector(Vector3.forward);//本地转世界transform.TransformDirection(Vector3.forward);transform.TransformPoint(Vector3.forward);transform.TransformVector(Vector3.forward);//世界转屏幕Camera.main.WorldToScreenPoint(Vector3.forward);//屏幕转世界Vector3 point = new Vector3(5, 3, 15);//注意z值的提供Camera.main.ScreenToWorldPoint(point);//世界转视口Camera.main.WorldToViewportPoint(point);//视口转世界Camera.main.ViewportToWorldPoint(point);//视口转屏幕Camera.main.ViewportToScreenPoint(point);//屏幕转视口Camera.main.ScreenToViewportPoint(point);
向量
向量模长和单位向量
//Vector3 可以表示点坐标 位置方向(该点和原点(0,0,0)连接成的向量 )Vector3 v1 = new Vector3(5, 6, 7);//Vector2 二维向量Vector2 v2 = new Vector2(3, 5);//向量表示//AB--> 终点B-起点A 表示ABVector3 bPoint = new Vector3(3, 5, 6);Vector3 aPoint = new Vector3(1, 1, 1);Vector3 AB = bPoint - aPoint;//高中知识//零向量print(Vector3.zero);//负向量print(-Vector3.forward);//向量的模长float size = AB.magnitude;Vector3.Distance(bPoint, aPoint);//同为size//单位向量//模长为1的向量 对向量归一化得到 向量值/自己的模长print(AB.normalized);
向量的运算的意义
- 位置+位置 无意义
- 向量+向量=向量(首尾相连)
- 位置+向量 (平移位置)
- 位置-位置 = 向量
- 向量-向量 = 向量(指向被减向量)
- 位置-向量=位置+(-向量)(反向移动)
- 向量-位置 无意义
- A*vector2 放大A倍
- vector2/B 缩小B倍
向量的点乘 A·B(标量) (判断对象的方位)
问:如何判断敌人是否在玩家的视角范围内?
答:向量点乘 单位向量的点乘结果=向量夹角的余弦值
问:如何判断敌人在自己的前方还是后方?
答:目标物体和当前位置组成的向量规格化与正前方的方向向量点乘,结果大于零则在前方,小于零则在后方。
float dotResult = Vector3.Dot(transform.forward, (target.position - transform.position).normalized);if(dotResult>0) print("目标物体在该物体的前方 ");
向量的叉乘 AxB(叉乘)
Vector3.Cross(A,B);
几何意义:A,B向量组成的平面的法向量
利用:
(AXB).y>0: B在A的右侧
(AXB).y<0 :B在A的左侧
//叉乘判断物体方位public Transform A, B;//目标物体Vector3 c = Vector3.Cross(B.position, A.position);if (c.y > 0){ print("A在B的右侧");}else{ print("A在B的左侧");}
点乘和叉乘来判断目标物体与自己的方位!
public Transform target;void Update(){ //点乘结果管前后 float dotResult = Vector3.Dot(transform.forward, (target.position - transform.position).normalized); //叉乘纵轴管左右 float cossY = Vector3.Cross(transform.forward, (target.position - transform.position).normalized).y; if (dotResult > 0) { // print("目标物体在前方"); if (cossY > 0) { print("目标物体在右前方"); } else { print("目标物体在左前方"); } } else { if (cossY > 0) { print("目标物体在右后方"); } else { print("目标物体在左后方"); } }}
线性插值
Vector3.Lerp(startPos,endPos,t);
同普通的Lerp,在向量中同时改变x,y,z的数值。球形插值
Vector3.Slerp(startPos,endPos,t)
;可以使向量沿弧线样式的移动。
四元数
欧拉角与四元数
欧拉角和四元数在Unity中用来表示物体的角度信息
四元数是简单的超复数,由实数加上三个虚数单位组成。
四元数的构成:一个标量+3维向量
可以通过轴角对来表示,
单位四元数、插值、向量指向四元数
//单位四元数//没有进行任何旋转//单位四元数print(Quaternion.identity);//相当于欧拉角(0,0,0)//插值方法//此物体逐渐接近 目标物体的旋转角度transform.rotation=Quaternion.Slerp(transform.rotation,target.rotation,Time.deltaTime);//向量指数转向四元数//旋转自身 面向目标物体transform.rotation=Quaternion.LookRotation(target.position - transform.position);//个人认为此功能很鸡肋 直接LookAt蛮方便的
四元数的运算:
//四元数的相乘//表示对象的旋转 绕自身的轴的旋转Quaternion q2 = Quaternion.AngleAxis(60, Vector3.up);transform.rotation *= q2;//四元数* 向量Vector3 v3=Vector3.forward;print(v3);//二者相乘 向量在后v3 = Quaternion.AngleAxis(45, Vector3.up) * v3;print(v3);//相当于Z轴向绕着y轴右转向45°//应用 散弹枪的子弹方向 子弹方向的偏移
MonoBehavior中重要内容延迟函数
可以定时执行的函数
该函数在同一个类中
延迟重复执行函数与延迟函数相似
判断延迟函数是否执行 ,取消延迟执行
延迟函数的“顽固”
//延时重复执行函数//函数名称 第一次执行的时间 之后每次执行的时间间隔InvokeRepeating("FunDelay",5,2);//取消延迟函数//取消该脚本中所有的延迟函数CancelInvoke();//取消指定的延迟函数//只要有取消指定的延迟函数 尽管该延迟函数被多次开启 均会被取消(只要取消一定会取消)CancelInvoke("FunDelay");//判断该脚本中是否存在延迟函数if (IsInvoking()){ print("该脚本中存在延迟函数");}if (IsInvoking("FunDelay")){ print("存在FunDelay函数延迟执行");}//同脚本类中的延迟执行的延迟函数public void FunDelay(){ print("FunDelay执行了!"); //间接地执行有参函数 FunDelay(true);}public void FunDelay(bool flag){ print("FunDelay执行了!"+flag);}//挂载脚本上有延迟函数执行//1. 挂载脚本销毁 (死了就不行了吧!哈哈哈哈)// 无法执行//2. 失活对象、失活脚本 不影响(小子挺顽固啊!!)// 延迟函数依旧//综上 我们使用时候在OnEable()中开启延时函数执行//在OnDisable()中关闭延时函数的执行 //这样就做到失活就不再让顽固的延迟函数执行了
协同程序
unity中支持使用多线程吗?
答:支持多线程。但是只有主线程才有权限来访问unity的内容。但是我们可以开启新的线程来完成相关的功能的设置,如完成消息的收发,进行复杂的算法计算,完成之后存储结果,供主线程访问使用。
协程:协程不是一个新的线程,它可以将代码逻辑进行分布执行,不会造成对主线程的卡顿
协同程序的使用:
- 异步加载文件
- 异步下载文件
- 场景异步加载
- 批量创建时候的防止卡顿
注意:协同程序不是多线程,而是在主线程中开启,对代码逻辑分时分步执行。而多线程是与主线程之间是并行执行。
private Thread thread;void Start(){ thread = new Thread(TestFun); thread.Start();}void Update(){ //在update函数中获取副线程 //运算结果 if (queue.Count > 0) { //获取thread中计算的位置信息 transform.position = queue.Peek(); queue.Dequeue(); }}public void TestFun(){ while (true) { Thread.Sleep(1000); queue.Enqueue(new Vector3(1,2,3)); //企图在新线程中给物体坐标赋值 //会报错! //transform.position = new Vector3(4, 5, 6); }}
企图在副线程中访问设置物体坐标,会报错!除主线程外的线程不被允许访问unity包里的内容!
协程函数使用
//协程函数的返回值是 IEnumerator类型IEnumerator myCoroutine(int i, string str){ print(i); yield return new WaitForSeconds(2f); print(str);}//协程的开启//开启协程方法一StartCoroutine(myCoroutine(1,"协程函数"));//开启协程方法二IEnumerator enumerable = myCoroutine(1, "123");StartCoroutine(enumerable);//开启协程//同时开启三个协程//注意看协程函数//都是先执行第一步打印 然后等5s 再同时执行第二部打印//这样印证它是在主线程中执行的Coroutine c1 = StartCoroutine(myCoroutine(1, "协程"));Coroutine c2 = StartCoroutine(myCoroutine(1, "协程"));Coroutine c3 = StartCoroutine(myCoroutine(1, "协程"));//结束协程//结束所有的协程StopAllCoroutines();//结束指定的协程StopCoroutine(c2);//协程中的yield return 不同内容含义//1. 下一帧执行yield return 10;//数字yield return null;//2. 等待指定秒数执行yield return new WaitForSeconds(5f);//等待指定秒数执行//在update 和LateUpdate之间执行//3. 等待下一个固定物理函数帧更新时候执行yield return new WaitForFixedUpdate();//在FixedUpdate和碰撞检测相关函数之后执行//4. 等待摄像机和GUI渲染完成之后执行yield return new WaitForEndOfFrame();//可以在此进行截图功能调用//在LateUpdate之后的渲染相关处理完毕之后执行//5.一些册数类型的对象 比如异步加载相关函数//返回之后 再执行//6 跳出协程 yield break;
协程开启后,脚本或者物体销毁,协程不执行;
物体失活协程不执行,脚本失活协程继续执行。(其余都不执行)
协程常见应用—计时器
//协程应用--计时器//开启协程StartCoroutine(MyCoroutine());IEnumerator MyCoroutine(){ int time=0; while(true) { print(time+"秒"); time++; yield return new WaitForSeconds(1); }}
协同程序原理
协程本质
1.1 协程函数本体
协程是一个能够中间暂停执行的函数
1.2 协程调度器
协程调度器是unity内部实现的,会在对应时机帮助我们继续执行写成函数的一个调度器。unity仅仅是实现协程调度部分,协程本体本质上是 C#的迭代器方法。
协程本体是迭代器方法的体现
//协程本体----迭代器函数public class Test3{ public Test3() { }}IEnumerator Test(){ print("第一次执行"); yield return 1; print("第二次执行"); yield return 2; print("第三次执行"); yield return "333"; print("第四次执行"); yield return new Test3();//可以返回对象}IEnumerator ie = Test();//自行迭代器函数内容ie.MoveNext();//执行函数逻辑直至遇到返回条件print(ie.Current);//得到返回的内容//自行迭代器函数内容ie.MoveNext();//执行函数逻辑直至遇到返回条件print(ie.Current);//得到返回的内容//自行迭代器函数内容ie.MoveNext();//执行函数逻辑直至遇到返回条件print(ie.Current);//得到返回的内容//自行迭代器函数内容ie.MoveNext();//执行函数逻辑直至遇到返回条件Test3 t3=ie.Current as Test3;//等同于 迭代器函数是否迭代完毕while(ie.MoveNext()){ print(ie.Current);}//而Unity中是根据返回的终止条件来进行下一次执行的检查//满足yeild return 的条件之后继续执行函数中的下一个逻辑块
练习:自行实现协程。(仅支持延时时间返回)
//分时分步 执行函数//只支持 yeild return int类型的处理//存储迭代器和下一次执行时间的数据结构public class YieldReturnTime{ public IEnumerator ie;//当前位置的迭代器 public float time;//下一次执行的时间}//设置成单例类//这里继承MonoBehaviour来使用其Update函数进行计时判断public class CoroutineManager :MonoBehaviour{ private static CoroutineManager instance; public static CoroutineManager Instance => instance; //存储多个协程同时执行时候各自的迭代器时间对象 private List list = new List(); private void Awake() { instance = this; } public void MyStartCoroutine(IEnumerator ie) { //首先进行函数的执行 //遇到第一个等待时间条件再存储执行节点 //等待时机再次执行 if (ie.MoveNext()) { if (ie.Current is int) { YieldReturnTime yi = new YieldReturnTime(); yi.ie = ie; //下一次执行的时间节点 yi.time = Time.time + (int) ie.Current; //存储到容器中 list.Add(yi); } } } private void Update() { //时刻检查是否有满足执行条件的迭代器 让其执行 //遍历检查 (尾部遍历,因为要对list可能存在移除操作,移除操作之后尾部的内容会逐个前移填补空位) for (int i = list.Count-1; i >=0; i--) { //当前时间点满足执行的条件 if (list[i].time <= Time.time) { //则进行执行 if (list[i].ie.MoveNext()) { //只支持返回int类型的数据 if (list[i].ie.Current is int) { list[i].time = Time.time + (int) list[i].ie.Current; } else { //返回其它类型的数据则移除 list.RemoveAt(i); } } else { //执行完毕当前逻辑段之后 //说明整个函数逻辑完全执行完毕! list.RemoveAt(i); } } } }}
//测试脚本中的测试代码IEnumerator MyTest(){ print("第一次执行"); yield return 1; print("第二次执行"); yield return 2; print("第三次执行"); yield return 3; print("第四次执行");}void Start(){ //使用自定义的管理执行迭代器开始协程CoroutineManager.Instance.MyStartCoroutine(MyTest());}
Resources资源动态加载Unity中的特殊文件夹
工程路径的获取
print(Application.dataPath);//编辑项目时候的路径 供开发阶段使用
Resources资源文件夹
- 需要通过Resources相关的API动态加载的资源需要放在其中
- 该文件夹下所有的文件都会被打包出去
- 打包时Unity会对其进行压缩
- 该文件夹打包后只读 并且只能通过Resources相关API加载
StreamingAssets(流文件夹)
print(Application.streamingAssetsPath);
- 打包出去不会被压缩加密
- 移动平台只读,PC平台可读可写
- 可以存放一些需要自定义加载的初始资源
persistentDataPath持久数据文件夹(无需自己手动创建)
print(Application.persistentDataPath);//获取文件路径
- 存储持续性数据的文件夹(一般是再运行设备的用户名相关的文件目录下自动创建)
- 所有平台都是可读可写
- 一般用处放置动态下载或者动态创建的文件,游戏中创建的或者获取的文件都放在其中
Plugins插件文件夹(手动创建 存放插件资源 例如Ios Android SDK工具包)
Editor编辑器文件夹(手动创建 )
- 开发扩充unity编辑器功能(方便开发)时,存放编辑器相关脚本
- 该文件夹内容不会被打包出去
默认资源文件夹 Standard Assets (不常用,了解即可)
Resource
资源动态加载
可以避免繁琐的拖拽加载。
资源类型
- 预设体对象–GameObjet
2. 音效文件–AudioClip
3. 文本文件–TextAsset
4. 图片文件–Texture
5. 其它类型
注意:预设体加载需要实例化
- 预设体对象–GameObjet
资源同步加载–普通方法
//1. 预制体资源的加载Object obj=Resources.Load("Cube");//记载资源Instantiate(obj);//实例化之后才会出现到场景中//Resource文件夹可以有多个(可以存放在其它文件目录下) 但每次系统都可以找到加载内容//2. 音效文件夹的加载 Object obj2=Resources.Load("Music/xxxMusic");public AudioSource audioSource;audioSource.clip=obj2 as AudioClip;//播放audioSource.Play();//3.文本资源//文本文件类型 txt xml bytes json html csv//此处读取的是.txt文件TextAsset textAsset = Resources.Load("Text/test") as TextAsset;print(textAsset.text);//4.图片资源Texture texture = Resources.Load("Text/text.jpg") as Texture; GUI.DrawTexture(new Rect(0,0,300,300),texture);//其它 --动画文件等//如何解决重名问题?//使用指定类型来加载Texture tex=Resources.Load("Tet/TestJPJ",typeof(Texture)) as Texture;//加载指定名字的所有资源(极少使用)object[] objs=Resources.LoadAll("Tex/TestJPG");for(Object item in objs){ if(item is Texture) { //... }}
资源同步加载 泛型方法
//指定类型加载TextAsset textAsset = Resources.Load("Text/test") as TextAsset;print(textAsset.text);
异步加载
异步加载,不会等待加载完成之后继续走之后的逻辑,而是在加载资源时候,主线程继续走接下来的代码逻辑,检查是否加载完毕,然后获取使用,异步加载不会产生卡顿。
//异步加载//1. 通过异步加载完成事件监听 //进行异步加载//可以理解unity内部开启一个线程来进行资源的加载//加载完毕之后会执行completed事件ResourceRequest rq=Resources.LoadAsync("Text/TestJPJ");rq.completed += LoadOver;//资源加载结束加载的事件添加之后的调用函数private void LoadOver(AsyncOperation asyncOperation){ print("加载完成了!"); Texture texture = (asyncOperation as ResourceRequest).asset as Texture;}//2. 通过协程来异步加载//可以在协程中处理其它的相关加载内容函数IEnumerator Load(){ ResourceRequest rq=Resources.LoadAsync("Text/TestJPJ"); //unity内部中本身知道实在加载资源 //因为加载资源和协程有继承同一个基类 yield return rq; Texture tex=rq.asset as Texture;}IEnumerator Load1(){ ResourceRequest rq=Resources.LoadAsync("Text/TestJPJ"); //不停的检查是否结束 //如果未加载结束就每一帧都检查 while (!rq.isDone) { print(rq.priority); yield return null; } Texture tex=rq.asset as Texture;}
资源卸载
重复加载资源会浪费内存吗?
答:不会。因为每一次加载都会在内存中检查该资源是否已经加载到内存中,有的话不会重复再次加载到内存中,所以不会浪费内存。但是重复加载会耗费性能,每一次的加载都面临对资源的查找。
如何手动释放掉缓存中的资源?
答:1. 卸载已使用的资源
Resources.UnloadAsset();
该方法不能释放掉GameObject对象,但是可以卸载图片、文本、音频,而GameObject因为实例化场景中。
卸载未使用的资源(使用较多)
Resources.UnloadUnusedAssets();
一般在过场景时候搭配GC使用
GC.Collect();
场景异步切换
- 场景同步切换
//同步加载SceneManager.LoadScene("Game");
同步切换的缺点:
同步切换场景时候,unity会删除当前场景中的所有对象,然后进行新场景资源的加载,处理事件较长,造成卡顿。
- 场景的异步加载
2.1 通过事件回调函数异步加载
//加载完毕之后自动跳转到新场景AsyncOperation asyncOperation = SceneManager.LoadSceneAsync("Game");asyncOperation.completed += (a) =>{ print("加载结束!!");};
2.2 通过协程进行异步加载
IEnumerator LoadScene(string name){ //异步加载函数 AsyncOperation asyncOperation = SceneManager.LoadSceneAsync(name); yield return asyncOperation; //异步加载会把场景中的资源删除 //该脚本也可能被消除 所以场景加载完成之后 //协程的逻辑可能之后不会执行 print("可能失效 不打印"); while (!asyncOperation.isDone) { print(asyncOperation.progress); yield return null; } //也可以根据自己的规则来进行场景过度 //比如 动态加载怪物 这时候的进度条更新20% //等到怪物加载完成 动态加载模型 完成之后再拉满 隐藏进度条 //进入场景}//保证过场景不会被消除//协程中的逻辑执行完整DontDestroyOnLoad(this.gameObject);StartCoroutine(LoadScene("Game001"));//可以在协程中的执行逻辑//进行进度条设置
LineRender
LineRenderer是Unity中专门用来绘制线的组件/类。
LineRender代码相关
//LineRender划线APIGameObject objLine = new GameObject();LineRenderer line=objLine.AddComponent();//是否首尾相连line.loop = true;//首位宽度line.startWidth = 0.02f;line.endWidth = 0.02f;//首位颜色line.startColor=Color.cyan;line.endColor=Color.cyan;//设置材质m = Resources.Load("M");line.material = m;//先设置点的个数//再给点坐标line.positionCount = 4;line.SetPositions(new Vector3[]{ new Vector3(0,0,0), new Vector3(0,0,4), new Vector3(5,6,7),});//单独设置一个点line.SetPosition(2,new Vector3(1,2,3));//不使用世界坐标系line.useWorldSpace = false;//线段受光影响line.generateLightingData = true;
核心系统范围检测
游戏中的顺势攻击时候会判断杀伤力范围,再杀伤力范围的物体要进行伤害。
//范围检测//创建一个瞬时的碰撞器 执行代码时刻创建 之后消失(相当于拍照)//不会真实的创建一个碰撞器,而是进行计算检测//1. 盒状范围检测 //中心点 长宽高 旋转角 检测层(二进制表示1<<层号 |是叠加)//是否忽略触发器 UseGlobal-使用全局设置(默认) Collider-检测触发器 Ignore-忽略触发器//返回值是个数组 内容是处于当前范围中所有对象Collider[] colliders = Physics.OverlapBox(Vector3.zero, new Vector3(2, 5, 7), Quaternion.AngleAxis(45, Vector3.up), 1 << LayerMask.NameToLayer("UI") | 1 << LayerMask.NameToLayer("Default"), QueryTriggerInteraction.Ignore);//返回值:碰撞到的碰撞器的数目//传入一个要检测的碰撞体对象数组//检测在范围中的数目//后两个参数是 层级 是否忽略触发器if (Physics.OverlapBoxNonAlloc(Vector3.zero, Vector3.one, colliders) != 0){}//2.球状范围检测Collider[] colliders2=Physics.OverlapSphere(Vector3.zero, 5f, 1 << LayerMask.NameToLayer("Default"));//传入一个要检测的碰撞体对象数组//检测在范围中的数目//后两个参数是 层级 是否忽略触发器if (Physics.OverlapSphereNonAlloc(Vector3.zero, 3f, colliders2) != 0){}//3.胶囊//上半圆的中心点//下半圆中心点//半径colliders2 = Physics.OverlapCapsule(Vector3.zero, Vector3.up, 2f, 1 << LayerMask.NameToLayer("UI"));//传入一个要检测的碰撞体对象数组//检测在范围中的数目//后两个参数是 层级 是否忽略触发器if (Physics.OverlapCapsuleNonAlloc(Vector3.zero,Vector3.up, 1f, colliders) != 0){}
射线检测
射线检测,可以指定发射射线,检测碰撞器是否被射中。
(FPS中的枪械射击)
//射线机发射射线检测//从鼠标点的位置发射射线 Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);//射线检测函数//执行代码进行射线检测Ray ray2 = new Ray(Vector3.zero, Vector3.forward);//物理检测//传入射线 设置射线距离 检测层if(Physics.Raycast(ray2,1000,1<<LayerMask.NameToLayer("Monster"),QueryTriggerInteraction.Collide)){ print("射中了");}//给两个点 确定发射点和射线方向if(Physics.Raycast(Vector3.zero, Vector3.forward,1000,1<<LayerMask.NameToLayer("Monster"))){ print("射中了");}//返回被射中的碰撞体//可以获取物体碰撞的信息RaycastHit hitInfo;//存储被射线击中的信息//也可以不赋值给射线 传入两个点 确定发射点 发射方向if (Physics.Raycast(ray2, out hitInfo, 1000)){ print("碰撞到"+hitInfo.collider.name); //碰撞体的点 //方便 子弹击中受伤效果 print(hitInfo.point); //法线信息 //方便贴图 print(hitInfo.normal); //碰撞到的对象位置 print(hitInfo.transform.position); //击中点距离 print(hitInfo.distance);}//获取相交的多个物体//也可以将射线 替换成两个点 //后面参数设置检测层 是否忽略触发器RaycastHit[] hits=Physics.RaycastAll(ray2, 1000);for (int i = 0; i 0){ //射中物体了}
注意:API参数 传入射线或者两个点(起点,确定方向的第二个点),最远检测距离
检测层级,使用二进制表示1<<
,是否忽略触发器。