Unity学习笔记二

文章目录

整个学的过程中碰到参数介绍就很痛苦.jpg

相比于入门来讲小上了点强度了,但也还行,里面有谈到GUI,但是但是我那个笔记一直拖到现在还没写完哈哈哈。。。。(明明是学基础前就该写完了)所以就将就看吧(卡姿兰大眼睛),等把其他UI学了专门发一篇UI的笔记吧。。。。

3D数学

公共计算结构体Mathf

和c#的静态类Math是一个定位,虽然是结构体,但它的成员全是conststatic

常用成员

c# 复制代码
Mathf.PI;//后续描述省略Mathf.都是可以直接.出来的
Abs(n);
CeilToInt(n);//向上取整
FloorToInt(n);//向下取整
Clamp(n, min, max);//钳制方法,取n值,但范围限制在min到max间
Max(a, b, c,...);
Min(a, b, b,...);
Pow(n, 幂);
RoundToInt(n);//四舍五入
Sqrt(n);
IsPowerOfTwo(n);
Sign(n);//判断正负数

int start = Mathf.Lerp(start, end, t);//插值运算=》可以放在Update里用来做跟随系统
//start为初始位置,end为结束位置,t为0~1的插值系数
//通常end为需要跟随的对象位置,将运算的结果赋给start并每帧计算就可以实现跟随
//Lerp方法返回值的内部运算逻辑为start + (end - start) * t
//根据公式可以有先快后慢和匀速两种表现形式的跟随

//先快后慢:令t = Time.deltaTime即可

//匀速:假设是当前对象跟随对象other
if (endpos != other.transform.position) {
    t = 0;
    startpos = this.transform.position;
    endpos = other.transform.position;
}
t += Time.deltaTime;
nowpos.x = Mathf.Lerp(startpos.x, endpos.x, t);
nowpos.y = Mathf.Lerp(startpos.y, endpos.y, t);
nowpos.z = Mathf.Lerp(startpos.z, endpos.z, t);
this.transform.position = nowpos;
//因为匀速实现传入的t总会超过1,会导致后续更新时变成瞬移
//所以在运算前前需要进行位置的判断,每次重置t

三角函数

c# 复制代码
Mathf.Rad2Deg;//弧度=》角度需要*的值,即180/Π(下同样省略Mathf.)
Deg2Rad;//角度=》弧度需要*的值,即Π/180
Sin();
Cos();
Asin();//反正弦
Acos();//反余弦
//以上四个三角函数均只能传弧度,同时返回弧度结果

//利用正弦函数实现波浪式移动
this.transform.Translate(Vector3.forward * Speed * Time.deltaTime);
t += Time.deltaTime * Speed;
this.transform.Translate(Vector3.right * y * Time.deltaTime * Mathf.Sin(t));

向量Vector3

数学回顾:可以±,可以取负,可以*/标量,为负数时方向取反(AB = B - A

基本成员

现在有对象Vector3 v

  • v.magnitude:求模长
  • v.normalized:取方向向量

点乘

数学表示:a · b = x1 * x2 + y1 * y2 + z1 * z2(两向量点乘得到标量)

几何意义:ba 方向上的投影

实际用途:> 0 两向量夹角为锐角,= 0 直角,< 0 钝角 =>判断敌人的前后大致方位

点乘方法Vector3.Dot(a, b):返回float

补充方法调试画线

  • Debug.DrawLine(a, b, color):画线段
  • Debug.DrawRay(a, b, Color):画射线

获取两向量夹角Vector3.Angle(a, b)

=》原理 :若b 为方向向量,则两向量夹角余弦值cosβ = ba 方向上的投影 / b的模长(点乘的几何意义就是投影)

即cosβ = a · b =》取反余弦则β = Acos(a · b)

tips:调用Angle方法时不用取b的方向向量,内部处理好了

叉乘

数学表示:a × b = c ,其中c = (Ya * Zb - Za * Yb, -(Xa * Zb - Za * Xb), Xa * Yb - Xb * Ya)

(两向量点乘得到向量,该向量为法向量,垂直于两向量所在平面)(相乘顺序不能变,不然结果不同,a × b = -(b × a

tips:高数里的内容,不用死记,把两个向量的xyz列出来,要算哪个轴把哪个轴盖住,剩下的交叉相乘,只不过算y的时候要取反,判断c的方向时采用右手螺旋定则

实际用途:算出来的c ,它的y > 0时说明ab 的左边,< 0时说明在右边 =》结合点乘可直接判断出敌人的具体方向 ,再加上入门里里学的Vector3.Distance(a, b)方法可直接得到敌人的具体位置

叉乘方法Vector3.Cross(a, b)

插值运算

线性插值Vector3.Lerp(start, end, t):和Mathf里的用法基本一致,只不过传入的参数直接是Vector3了,不用之前那么麻烦

球形插值Vector3.Slerp(start, end, t):用法和线性一摸一样,表现形式上为弧状靠近,用的少

四元数

引出

在入门中控制旋转时我们采用的方法是改变欧拉角,即面板上的参数来描述旋转量

但是他不好=》

  • 同一旋转表示不唯一,比如360和0是一样的
  • 万向节死锁=》简单解释:
    • 通常旋转遵循yzx约定,即三个轴向的旋转顺序,转y会带动z,转z会带动x
    • 在这种约定下转着转着,当两个轴重合时他就变不回去了(当然其他所有约定都会出现这种情况)
    • 在unity里的表现是当x轴达到90°时,控制y和z在表现上都是在控制z

为了解决这两个问题就有了四元数

基本概念

数学里四元数由一个实数 + 三个虚数组成

而在unity里则是一个标量w + 一个向量(x, y, z)组成

假设现在有一个轴n,绕着它转β°,则有四元数Q = [cos(β/2), sin(β/2) * x, sin(β/2) * y, sin(β/2) * z]

(数学里的结论,感兴趣的可以去看一下怎么推的)

Quaternion结构体成员

入门里其实提过一嘴transform.rotation是一个四元数,它的类型在unity里就是Quaternion

c# 复制代码
//初始化
//法1new一个
Quaternion q = new Quaternion(sin(β/2) * x, sin(β/2) * y, sin(β/2) * z, cos(β/2));//一般不用这个
//法2轴角对
Quaternion q = Quaternion.AngleAxis(角度, 轴);//封装万岁,绕着哪个轴转多少度
//这个角度必须在正负180°间

//欧拉数=》四元数
Quaternion q = Quaternion.Eular(x, y, z);
//四元数=》欧拉数(就是入门那个获取角度)
Vector3 v = q.eulerAngles;

//物体的旋转
transform.rotation *= Quaternion.AngleAxis(30, Vector3.forward);
//两个四元数相乘代表旋转四元数,是相对自身的旋转量叠加

//单位四元数:[±1, (0, 0, 0)]
Quaternion.identity;
//作用:Object里克隆方法重载Instantiate(obj, 生成点, 生成时的角度),如果要生成一个全新的物体这个角度就可以填单位四元数

//插值运算:分为线性和球形,
//对于旋转来说,球形的效果好,线性虽然快但是在范围比较大的时候效果不好
//先快后慢:
target.transform.rotation = Quaternion.Slerp(this.transform.rotation, target.transform.rotation, t);
//匀速:参考上面的传参和Mathf线性的处理逻辑

//向量指向四元数
transform.rotation = Quaternion.LookRotation(面朝向量);
//这个传参让两个物体的position一减就行
//效果上和入门里transform.LookAt(obj)一样,它要更精细一点,可以和插值运算配合
//插值运算实现时把这个返回值作为目标即可

四元数运算

两四元数相乘,即上面部分的旋转

四元数 * 向量 = 向量(把原向量旋转了,顺序不可变不然报错)=》可以做什么呢

  • 发射不同角度子弹,用四元数去乘面朝的方向向量即可改变子弹的方向
  • 第三人称 ,其实就是把摄像机扔到人物后上方去,扔过去分为后方和上方两部分,都需要用四元数去控制 ,这一部分知识加上上面的向量指向四元数 让摄像机一直看向人物,再加上向量部分的插值运算实现跟随效果,就可以实现一个简易的第三人称了~~

更多的Mono

延迟函数

直入主题

  • Invoke("函数名", 时间)
    • 时间的单位是秒,所调用的函数必须在当前脚本中,如果没有的话会提示
    • 既然是传入字符串找,那么底层实现必然是反射,传入字符串也意味着不能传参
    • 当前上面这两点都可以间接解决,调用的无参函数中再调用其他有参或者其他脚本中的函数。即包装一层
  • InvokeRepeating("函数名", 时间, 每次间隔的时间)
  • 取消延迟函数
    • 取消该脚本上的所有CancelInvoke():有传字符串指定取消的重载,没有也不会报错
    • 判断有没有if(IsInvoking()):也有传字符串指定的重载

销毁失活影响:延迟函数在对象销毁或移除时不能执行,但在失活时仍然会执行(直接在面板上把脚本失活了实际上也还是不能执行)

有什么用=》做个计数器,或者延时销毁前加入处理逻辑 (入门里学过Destroy(obj, 时间),这种延时方法没有办法去处理逻辑,若是现在,那么把Destrot(obj)放在Invoke()里即可实现延迟销毁并处理逻辑)

协同程序

多线程相关

unity支持多线程,但是在除主线程的其他线程内无法访问对象

所以一般开的其他线程用来完成一些耗时的操作,比如寻路算法或者网络相关,如果写在主线程会导致主线程卡死

另外,开的其他线程实际上是编辑器里开的,无法通过unity的运行停止关闭,所以要在代码里记得关

协程

概念辨析

协程是一种假的多线程,效果类似于多线程,但是运行实际上还是在主线程上

能达到类似于多线程的效果,主要在于它是将主线程分时分步 执行,在异步下载文件时很有用

unity里的协程分为了协程本体协程调度器两部分

协程本体

协程本体是Mono里的一个方法,这个方法本质上是一个迭代器

写法:方法名和参数由自己决定,返回值必须是IEnumerator或继承它的类型,在方法内必须使用yield return

协程基本方法:

c# 复制代码
//若现在有自定义协程
IEnumerator MyCoroutine() {
    yield return 1;
}

//开启协程
Coroutine i = StartCoroutine(MyCoroutine());
//若直接使用MyCoroutine()是不能执行协程的,因为它本质上是一个迭代器
//有重载参数为方法名字符串,但不建议使用
//可以同时开启多个相同协程,或者下面的方法也可以开启协程
IEnumerator ii = MyCoroutine();
StartCoroutine(ii);


//关闭所有协程
StopAllCoroutines();
//该方法无法阻止第一个yield前的逻辑执行

//关闭指定协程
StopAllCoroutines(i);
//传入的参数必须为Coroutine类型,通常为开启的那个协程的返回值

yield return方案:

  • 数字/null:下一帧执行
  • new WaitForSeconds(秒数):停几秒,和上面的都在UpdateLateUpdate间执行
  • new WaitForFixedUpdate():在FixedUpdate和碰撞函数后执行
  • new WaitForEndOfFrame():在CameraGUI渲染后执行=,时间上是在LateUpdate后执行》拍照截图功能可以放到这后面
  • break:跳出
  • 其他如异步加载对象:一般都是在UpdateLateUpdate间执行

销毁失活影响:物体销毁和失活均不执行,但是组件失活时仍然执行(同样直接在面板上把脚本失活了实际上也还是不能执行)

协程调度器

上面的StartCoroutine()方法实际上是unity内部实现的调度器,当然可以自己大概实现一个简单的只有读秒的调度器

c# 复制代码
public class Data
{
    public float time;
    public IEnumerator ie;

    public Data(IEnumerator ie, float time)
    {
        this.time = time;
        this.ie = ie;
    }
}

public class Manager : MonoBehaviour
{
    private static Manager instance;//单例模式实现
    public static Manager Instance => instance;

    private List<Data> list;//容器,处理多个相同的协程函数一起调用

    private void Awake()
    {
        instance = this;
        list = new List<Data>();
    }

    private void Update()
    {
        for (int i = list.Count - 1; i >= 0; i--)//每帧都去检测容器里面有没有东西
        {
            if (list[i].time <= Time.time)//list只是检测秒数的容器
            {
                if (list[i].ie.MoveNext())//这里的写法和最开始基本相同
                {
                    if (list[i].ie.Current is int)
                    {
                        list[i].time = Time.time + (int)list[i].ie.Current;
                    }
                    else//同样的,如果是其他的yield return在这里else if
                    {
                        list.RemoveAt(i);
                    }
                }
                else
                {
                    list.RemoveAt(i);
                }
            }
        }
    }

    public void MyStartCoroutine(IEnumerator ie)
    {
        if (ie.MoveNext())
        {
            if (ie.Current is int)//这里只处理了int的情况,list容器储存时间以便等待的时间打印
            {
                float time = Time.time + (int)ie.Current;
                list.Add(new Data(ie, time));
            }//如果有其他类型的yield return在下面else if就行
        }
    }
}

//调用的时候用Manager.Instance.MyStartCoroutine(MyCoroutine());既可以了

Resources资源动态加载

特殊文件夹

获取工程路径Application.dataPath:获取的是Assets的(发布游戏的时候不用这个路径)

下面未说明的都是需要自己创建文件夹

  • Resources资源文件夹:一般全部打包出去,打包后压缩加密,只读,只能API加载,放动态加载的资源,一般不获取路径,要获取只能拼接字符串,打包时如果有多个同名,则会自动合并
  • StreamingAssets流动资源文件夹:打包后不会压缩加密,移动平台只读,PC端可读可写,放自定义动态加载的初始资源,可以通过Application.streamingAssetsPath获取路径
  • persistentDataPath持久数据文件夹:可读可写,放动态下载或创建的文件,不用自己创建=》多用于存档和热更 ,可以通过Application.persistentDataPath获取路径

以下为相对而言不那么重要的

  • Plugins插件文件夹:放Android和IOS提供的移动端功能
  • Editor编辑器文件夹:不会被打包,放一些GUI做的之类的
  • Standard Assets默认资源文件夹:放unity自带的资源,unity会优先编译这部分(很少用)

Resources同步加载

干什么的=》避免大量的拖曳关联

普通方法Resources.Load()

  • 加载预设体GameObject:加载对象时要实例化

    c# 复制代码
    Object obj = Resources.Load("预设体名称");//实际上是把配置文件加载到内存中
    Instantiate(obj);
  • 加载音效AudioChip

    c# 复制代码
    Object obj = Resources.Load("文件路径");//如Music/Mymusic
    AudioSource audio;
    audio.chip = obj as AudioChip;//这步可以和第一步直接合并
    audio.play();
  • 加载文本TextAsset:支持txt,xml,bytes,json,html,csv

    c# 复制代码
    TextAsset t = Resources.Load("文件路径") as TextAsset;
    //很眼熟啊,长得和入门里获取脚本那个方法有异曲同工之妙,可以猜到一会还有更简单的泛型方法了吧
    print(t.text);//文本内容
    print(t.bytes);//字节数组
  • 加载图片Texture

    c# 复制代码
    Texture t = Resources.Load("文件路径") as Texture
    
    void OnGUI() {
        DrawTexture(t);
    }
  • 加载其他动画模型等

如果有同一路经下同名的不同类型资源,有两种解决方案,一是使用重载Resources.Load("路径", typeof(Texture)),二是获取所有Object[] objs = Resources.LoadAll()

泛型方法Resources.Load<类型>("文件路径"):上面已经对不同类型阐述了,这里就简单举个例子,以后经常用的也是泛型方法

c# 复制代码
Texture t = Resources.Load<Texture>("文件路径");

Resources异步加载

内部开一个假线程,不卡主线程,但是不能马上得到资源(最少要等1帧),适合用来加载大资源

同样的有普通和泛型方法,这里就以泛型方法为例了

监听回调实现(线性)

c# 复制代码
ResourceRequest r = Resources.LoadAsync<>("文件路径");
//ResourceRequest类继承自AsyncOperation类,在这个类中有一个成员事件completed
//类型是Action<AsyncOperation>即有参无返回值类型
//这个事件会在资源加载完成后自动回调,所以如果要执行相关逻辑,可以给completed加上自定义函数
r.completed += LoadOver;

//假设要加载一张图片
public Texture t;
public void LoadOver(AsyncOperation r) {
    t = (r as ResourceRequest).asset as Texture;
    //asset成员用于存放加载后的资源
}

很容易能看出缺点:只能等资源加载完后处理逻辑

但是呢语法很简单

协程实现(并行)

c# 复制代码
//同样假设加载一张图片
public Texture t;
IEnumerator Load() {
    ResourceRequest r = Resources.LoadAsync<>("文件路径");
    //这里就可以写逻辑了
    yield return r;//yield return的另一种用法,只有等待加载完后才会处理下面的逻辑
    t = r.asset as Texture;
}

//yield return也不一定要返回r,这一行可以替换为以下写法
while (!r.isDone) {
    print(r.priority);//打印进度,这个不太准确,以后可以写写其他UI的东西在这
    yield return null;
}
//isDone和priority都是AsyncOperation内的其他成员

优点很显著:在加载和yield return间可以写逻辑,比如UI读条(唉忽然想到造梦西游三悟空踩云那个过场)

~~但是呢写法明显复杂

封装一个简单的资源加载器

c# 复制代码
public class ResourceMgr
{
    private static ResourceMgr instance = new ResourceMgr();
    public static ResourceMgr Instance => instance;
    ResourceMgr()
    {

    }

    public void LoadRes<T>(string name, UnityAction<T> callback) where T : Object//必须写这个约束,不然不给过编译
    {
        ResourceRequest r = Resources.LoadAsync<T>(name);
        r.completed += (a) =>
        {
            callback((a as ResourceRequest).asset as T);
        };//回调中的回调。。。新手懵逼丝滑小连招。。。
        //最大的疑惑其实在这个参数a,你怎么知道他就是获取了资源的那个对象
        //AI解答:Unity 的 API 设计保证了 completed 回调传入的 AsyncOperation 一定是触发该事件的 ResourceRequest 实例,因此类型转换是安全的。
    }
}

//外部调用只需
ResourceMgr.Instance.LoadRes<Texture>("hh", (obj) =>
{
    tex = obj;
});

资源卸载

卸载指定Resources.UnloadAsset(要卸载的资源):不能卸载预设体资源,因为他实例化了,直接卸了要出事(少用)

自动卸载未使用的 Resources.UnloadUnusedAssets():这个就有用了,和GC.collect()在过场时一块用

场景异步切换

同步切换回顾SceneManager.LoadScene("场景名称")

异步切换和异步资源加载的方法差不多,因为场景的东西一般都很多,同步加载会卡死,所以一般用异步

事件回调

c# 复制代码
AsyncOperation ao = SceneManager.LoadSceneAsync("场景名称");
ao.completed += ...;
//需要注意的点在于,completed是c#中的事件,由GC管
//只要他还在回调就不会被回收,所以在场景切换依附对象删除时照样执行其中的逻辑

协程

和异步资源加载的步骤一摸一样的,方法换成上面那个,yield returnreturn ao即可,不写代码了偷个懒

因为协程在物体销毁后就会失效,场景切换会先把当前场景所有物体销毁再创建另一个场景中的内容,所以你懂的会有问题

有没有什么办法解决呢=》入门时我们学过一个Mono里的方法DontDestroyOnLoad(obj),即过场景不删

同样的可以加进度条,只不过这里的进度条可以有设计一点,比如加载出所有怪物后加百分之二十,加载出所有物品后再加百分之二十等等(越写越想笑服了...)

封装一个简单的场景加载器

别忘了把要切换的场景加到Build Setting

c# 复制代码
public class SceneMgr
{
    private static SceneMgr instance = new SceneMgr();
    public static SceneMgr Instance => instance;
    SceneMgr()
    {

    }

    public void LoadScene(string name, UnityAction action)
    {
        AsyncOperation ao = SceneManager.LoadSceneAsync(name);
        ao.completed += (a) =>//这个a没啥用,因为场景资源自己就切换了,不需要自己存
        {
            action();
        };
    }
}

//外部调用,这个就要简单得多了,不需要接返回值
SceneMgr.Instance.LoadScene("testScene", () =>
{
    print("加载完成");
});

LineRendnerer画线组件

右下角加脚本,可用于画攻击范围,红外线等等

面板参数

  • Loop:起始点是否自动相连
  • Positions:线段点的坐标(世界坐标系下)
  • Color:有材质才有用
  • Corner Vertices:角顶点,增大时会使棱角更平滑
  • End Cap Vertices:终端顶点
  • Use World Space:不勾的话画的线会跟着物体动
  • Meterials:材质球,使用时需要勾选下面的接收光

以下相对不重要:

  • Alignment:对齐方式
  • Texture Mode:纹理模式,会影响材质球
  • Shadow Bias:阴影偏移
  • Generate Lighting Data:生成光源数据,即接收光
  • Lighting:开启阴影和接受阴影
  • Probes:光照探针,以后学
  • Additional Settings:以后学

新版功能(用的少):

选择左边选项:

  • Simplify Preview:简化预览
  • Subdivide Selected:启用后可以拉点加线
  • Show Wireframe:显示线框

选择右边功能:

  • Input:输入模式

代码控制

c# 复制代码
GameObject obj = new GameObject();
obj.name = "line";
LineRenderer l = obj.AddComponent<LineRenderer>();

LineRenderer.loop = true;//即面板上是否自动相连,以下方法直接用l点出来也是可以的

//改变线段的宽度(面板上Positions里的Width)和颜色
LineRenderer.startWidth / endWidth = 10;
LineRenderer.startColor / endColor = Color.Red;

//设置点连成线
LineRenderer.positionCount = 4;//设置点数,在进行其他设置前必须先设置这个
LineRenderer.SetPositions(new Vector3[]{...});//设置每个点的位置
LineRenderer.SetPosition(0, new Vector3(1, 1, 1));//设置指定索引
LineRenderer.useWorldSpace = false;//不用世界坐标系,即线跟着物体动
LineRenderer.generateLightingData = true;//接收光

//有了这些方法便可以实现传入中心点和半径绘制圆:
//知识回顾:点+向量->平移点	四元数*向量->旋转向量
//核心代码
l.SetPosition(i, centerPos + Quaternion.AngleAxis(angle * i, Vector3.up) * Vector3.forward * r);

//同样,也可以实现长按鼠标画线
//核心代码
Vector3 v = Input.mousePosition;
v.z = 10;
if (Input.GetMouseButton(0)) {
    l.positionCount += 1;
    l.SetPositions(l.positionCount - 1, Camera.main.ScreenToWorldPoint(v));
}
//这样写下来的代码每次都用的是同一个画线脚本,每次画线时都会相连
//如果不想自动相连再加一条按下即新建一个脚本的判断即可

范围检测

基本概念

和碰撞检测一样属于物理系统,主要用于瞬时的攻击范围判定

=》本质上是在指定范围内产生一个碰撞器效果(并没有真的创一个碰撞器),碰了就认为在范围内

基于这个本质,那么范围内要被检测的物体就必须要有碰撞器

代码实现

主要分为盒装,球形和胶囊状检测,参数大差不差,只是方法不同

盒状的两个重要方法

以盒状为例,重载最多的参数情况下

Collider[] c = Physics.OverlapBox(中心点,边长,角度,检测层级,是否忽略触发器)

  • 返回范围内所有符合条件物体的碰撞体数组
  • 中心点和边长都是Vector3的形式传入,其中边长仅为实际的一半
  • 角度是Quaternion传入,一般传this.transform.rotation,实现当前物体面向检测
  • 检测层级是一个int,需要左移构建一个二进制数 ,在内部用位运算 检测是第几层(避免32个if的判断)
    • 为什么是一个int:整形有32位,刚好对应了右上角Layer中的0到31层
    • 到底怎么写:1 << LayerMask.NameToLayer("层名") | 其他层,当前直接写具体的层数也可以,但一般为了可读性,采用LayerMask里的方法,若要检测多层,则使用或运算将相应位置为1,但是如果要检测非常多层,可以直接用异或算出结果传入
    • 若不传该参数,则默认检测所有
  • 是否忽略触发器填枚举QuergTriggerInteraction,有UsGlocal全局(左上角环境设置里的),Collide不忽略,Ignore忽略三个成员,不填默认使用全局

除了上面的方法,还有一个

int count = Physics.OverlapBoxNonAlloc(中心点,边长,碰撞器数组,...)

  • 该方法返回范围内符合条件的个数,但需要在第三个参数传一个数组进去接收符合条件的物体碰撞器,其余参数与上面的方法一致

其他方法

球形:Physics.OverlapSpherePhysics.OverlapSphereNonAlloc,传边长变成传半径,没角度

胶囊状:Physics.OverlapCapsulePhysics.OverlapCapsuleNonAlloc,前两个参数变成三个参数=》上下半圆中心,半圆半径

射线检测

基本概念

同样是和碰撞检测一样属于物理系统,和范围检测一样是瞬时

=》有什么用:FPS打人 ,子弹是没有实体的,用入门中学习的方法太损耗性能了,以及选中物体移动

代码控制

c# 复制代码
//创建射线对象
Ray r = new Ray(起点,方向);
r.origin;
r.direction;
//由摄像机发射创建
Ray r = Camera.main.ScreenPointToRay(Input.mousePosition);//屏幕转视口

//射线检测
bool b = Physics.Raycast(r, 最大距离, 检测层级, 是否忽略触发器);
//有重载,第一个参数换成两个参数起点和方向也可以
//返回值为打中没
//相对于范围检测有一个坑点,最大距离和检测层级都是int
//且没有单个参数的重载,要填检测层级一定要先填最大距离

//重载之获取相交物体
RaycastHit hitInfo;//这是一个物体信息结构体
bool b = Physics.Raycast(r, out hitInfo, ...);
//第二个参数传RaycastHit,且必须用out修饰(c#那个传全局变量的关键字,传入后函数内部必须赋值)

//RaycastHit的有用成员
hitInfo.collider;
hitInfo.transform;
hitInfo.distance;//如果子弹受重力影响,那么这个成员就很有用了(斜抛运动)
hitInfo.point;//碰撞点
hitInfo.normal;//法线向量
//碰撞点和法线向量可以用来创建特效,前者创建,后者矫正
//创建特效伪代码
if (鼠标按下) {
    if (射线检测) {
        obj = Instantiate(资源加载);
        obj.position = info.point;
        obj.rotation = Quaternion.LookRotation(info.normal);
        延时销毁;
    }
}

//获取多个相交物体,参数大差不差不再演示
RaycastHit[] hits = Physics.RaycastAll(...);
int count = Physics.RaycastNonAlloc(r, hits, ...);

哎呀没了,就这么多再挤也挤不出来了

赶紧学核心了,基础实践会做的会做的。。。有个比较重要的配置文件,不在这写了

参考资料与学习课程:

b站唐老狮

相关推荐
hzj62 小时前
Sentinel学习
分布式·学习·sentinel
独行soc3 小时前
2025年渗透测试面试题总结-某战队红队实习面经(附回答)(题目+回答)
linux·运维·服务器·学习·面试·职场和发展·渗透测试
奋斗者1号4 小时前
神经网络:节点、隐藏层与非线性学习
网络·神经网络·学习
真的想上岸啊5 小时前
学习Linux的第二天
学习
吃货界的硬件攻城狮5 小时前
【STM32 学习笔记】EXTI外部中断
笔记·stm32·学习
吃货界的硬件攻城狮5 小时前
【STM32 学习笔记 】OLED显示屏及Keil调试
笔记·stm32·学习
njsgcs6 小时前
chili3d调试笔记12 deepwiki viewport svg雪碧图 camera three.ts
笔记
非凡ghost6 小时前
NoxLucky:个性化动态桌面,打造独一无二的手机体验
学习·智能手机·软件需求
海尔辛6 小时前
学习黑客 linux 提权
linux·网络·学习
FBI HackerHarry浩6 小时前
Linux云计算训练营笔记day02(Linux、计算机网络、进制)
linux·运维·网络·笔记·计算机网络·进制