一、Mathf
一个unity中用于计算的结构体。
1、区别及介绍
2、常用方法
①、π、绝对值、向上向下取整:

向上和向下取整是直接取比当前这个数大1或小1的整数,不是四舍五入。
②、钳制函数:
钳制函数第一个参数是需要判定的值,第二三个是范围。判定参数1是否在范围内,是则取本身,否则取距离近的最大最小值。
③、取最大最小值:
④、n次幂:
参数1:底数,参数2:指数
⑤、四舍五入和平方根:
⑥、判断是否是2的n次方、判断正负数:
注意:0判断时是返回1。
⑦、插值运算:Lerp
一般用于在一个物体跟随另一个物体时。
3、练习题:
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、角度和弧度转换
①、转换关系:
②、代码转换
2、正、余弦函数:
正弦函数:对边/斜边
余弦函数:临边/斜边
①、特殊角度对应正、余弦值:
②、三角函数:
③、反三角函数
使用正、余弦函数反推弧度
3、练习题
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、坐标系
①、屏幕坐标系
②、视口坐标系
③、世界坐标系代码计算
④、相对于父类的物体坐标系代码
⑤、屏幕坐标系代码
⑥、视口坐标系
调整unity编辑器中摄像机的参数变化:
2、坐标系转换
四、向量
1、向量模长和单位向量
①、向量概念
向量既可以代表一个点的位置,也能代表一个方向。
②、向量计算
终点 - 起点 = 向量
unity中可以两个向量直接做减法。
③、零向量和负向量
④、向量的模长
英语生词:magnitude:数值。
⑤、单位向量
⑥、unity代码求单位向量
英语生词:normalized:标准化的。
⑦、练习题:
手写计算即为:根号下(x的平方+y的平方+z的平方)
2、向量的加减乘除
①、向量加减的几何意义-加法:
①位置 + 位置一般没有什么意义。
②向量 + 向量:得到一个新的向量。
③位置 + 向量 或 向量 + 位置:平移一个点。
②、向量加减的几何意义-减法:
①位置 - 位置:
②向量 - 向量:得到新向量
③位置 - 向量:相反方向位移。而向量-位置一般没有什么意义。
③、向量乘除:
④、Unity中计算:
⑤、练习题
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、点乘
①、计算公式:
②、点乘几何意义:判断前后位置和夹角。
③、辅助线:
④、如何在Unity中判断:
⑤、推导向量夹角:
另外一种计算方法:
⑥、练习题:
注意:使用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、叉乘
①、叉乘计算公式:
注意:叉乘计算公式目标向量的对应的Y计算过程是:Y = ZaXb - XaZb 其中Y是不参与计算的。然后先计算Y下面的Z轴,再循环回来重头计算X轴。
②、叉乘几何意义:
得到法向量和得到两个向量的左右位置关系。
③、练习题:
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的插值运算:
②、作用:让一个位置靠近另一个位置,有两种方法
③、线性插值:
④、球形插值:
⑤、作用:
⑥、练习题:
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、为何使用四元数
①、欧拉角
欧拉角旋转约定:
欧拉角优缺点:
同一旋转不唯一是:同一表现形式下,旋转数值可能不同。
②、万向节死锁
③、总结
2、四元数
①、四元数的构成
四元数概念:
四元数构成:
轴-角对概念:
轴-角对和四元数结合:
②、Unity中的四元数
Unity中的四元数是一个结构体。名为:Quaternion
两种初始化方式:
Unity中使用对公式化示例:
书写顺序:Quaternion()中先写x,y,z(即你要绕的轴)再写Mathf.cos或Mathf.sin,之后填写角度并乘上角度转弧度的对应值。
Unity中常用对方法示例:
注意这里是绕自身x轴的顺时针旋转60°
欧拉角和四元数转换:
③、四元数弥补欧拉角的缺点
解决欧拉角不唯一:四元数顺时针0-》180度,逆时针左半圆是0-》-180度。
解决万向节死锁:能在不影响其他轴的情况下旋转。但需要注意的是Vector3.up指的是物体本地坐标系,而不是世界坐标系。
this.transform.rotation *= Quaternion.AngleAxis(1, Vector3.up);
3、四元数常用方法
①、单位四元数
单位四元数概念:
其中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
代码展示:
③、向量方向转换为对应四元数角度
④、练习题:
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()
{
}
}
注意:四元数与向量相乘时,四元数必须写前面。
③、练习题
第一题:
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(预制体, 位置, 角度)创建,这是环形的发射:
可以根据全局公共变量roundNum来改变子弹数量。
第二题:
先分析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 调用
②、延迟重复执行
英语生词:Repeating 重复的
③、取消延迟函数
全部取消和指定名取消
④、判断存在延迟函数
⑤、失活对延迟函数影响
通常与OnEnable和OnDisable联用。
⑥、练习题
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
七、协同程序
1、多线程示例
线程休眠是:Thread.Sleep(1000);
void OnDestroy(){
t.Abort();
t = null;
}
新线程无法访问Unity中的内容是无法再新的线程中控制物体(如去移动或打印其信息)
由于副线程是无法访问Unity相关对象的,所以一般处理网络相关和复杂算法。
2、协同程序概念
//协同程序简称协程//它是“假”的多线程,它不是多线程
//它的主要作用//将代码分时执行,不卡主线程//简单理解,是把可能会让主线程卡顿的耗时的逻辑分时分步执行
//主要使用场景
//异步加载文件//异步下载文件//场景异步加载
//批量创建时防止卡顿
3、协程和线程区别
协程是在主线程执行到协程函数时,在主线程上开启的,可以分步执行,在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来开启
④、关闭协程
Coroutine c1=startcoroutine(Mycoroutine(1,“123"));
Coroutine c2=startcoroutine(MyCoroutine(1,“123"));
Coroutine c3=startcoroutine(Mycoroutine(1,"123"));
//第三步:关闭协程
//关闭所有协程
//stopAllCoroutines();
//关闭指定协程
Stopcoroutine(c1);
⑤、不同返回值(控制协程执行)
⑥、协程受对象和组件失活销毁的影响
//协程开启后
//组件和物体销毁,协程不执行
//物体失活协程不执行,组件失活协程执行
即只有脚本组件失活会继续执行协程
⑦、练习题
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、协程原理
①、协程的本质
迭代器遍历:
②、协程调度器
③、练习题
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);
}
②、方法二:协程
还需要startcoroutine(Load());
③、练习题:
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、异步加载练习题:
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:(终端顶点,圆角)终点圆角
注意:如果你要使用受到光影响的材质,先勾选Generate Lighting Data(生成照明数据)
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、练习题
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、碰撞检测回顾
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();
}