Unity学习笔记二

文章目录

  • 3D数学
    • 公共计算结构体Mathf
      • 常用成员
      • 三角函数
    • 向量Vector3
      • 基本成员
      • 点乘
      • 叉乘
      • 插值运算
    • 四元数
      • 引出
      • 基本概念
      • Quaternion结构体成员
      • 四元数运算
  • 更多的Mono
    • 延迟函数
    • 协同程序
      • 多线程相关
      • 协程
        • 概念辨析
        • 协程本体
        • 协程调度器
  • Resources资源动态加载
    • 特殊文件夹
    • Resources同步加载
    • Resources异步加载
      • 监听回调实现(线性)
      • **协程实现(并行)**
      • 封装一个简单的资源加载器
    • 资源卸载
  • 场景异步切换
    • 事件回调
    • 协程
    • 封装一个简单的场景加载器
  • LineRendnerer画线组件
    • 面板参数
    • 代码控制
  • 范围检测
    • 基本概念
    • 代码实现
      • 盒状的两个重要方法
      • 其他方法
  • 射线检测
    • 基本概念
    • 代码控制

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

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

3D数学

公共计算结构体Mathf

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

常用成员

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

三角函数

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

//初始化
//法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

协程基本方法:

//若现在有自定义协程
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内部实现的调度器,当然可以自己大概实现一个简单的只有读秒的调度器

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:加载对象时要实例化

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

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

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

    Texture t = Resources.Load("文件路径") as Texturevoid OnGUI() {DrawTexture(t);
    }
    
  • 加载其他动画模型等

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

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

Texture t = Resources.Load<Texture>("文件路径");

Resources异步加载

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

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

监听回调实现(线性)

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成员用于存放加载后的资源
}

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

但是呢语法很简单

协程实现(并行)

//同样假设加载一张图片
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读条(唉忽然想到造梦西游三悟空踩云那个过场)

~~但是呢写法明显复杂

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

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("场景名称")

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

事件回调

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

协程

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

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

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

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

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

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

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:输入模式

代码控制

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打人,子弹是没有实体的,用入门中学习的方法太损耗性能了,以及选中物体移动

代码控制

//创建射线对象
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站唐老狮

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/80001.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

为什么Transformer推理需要做KV缓存

一、我们先来回忆一下在transformer中KV在哪里出现过&#xff0c;都有什么作用&#xff1f; α的计算过程&#xff1a; 这里引入三个向量&#xff1a; 图中的q为Query&#xff0c;用来匹配key值 图中的k为key,用来被Query匹配 图中的Value&#xff0c;是用来被进行加权平均的 由…

【大模型面试】大模型(LLMs)高频面题全面整理(★2025年5月最新版★)

【大模型面试】大模型&#xff08;LLMs&#xff09;高频面题全面整理&#xff08;★2025年5月最新版★&#xff09; &#x1f31f; 嗨&#xff0c;你好&#xff0c;我是 青松 &#xff01; &#x1f308; 自小刺头深草里&#xff0c;而今渐觉出蓬蒿。 本笔记适合大模型初学者和…

JAVA:使用 iTextPDF 处理 PDF 的技术详解

1、简述 iTextPDF 是一个功能强大的 Java PDF 库,可以用来创建、修改和处理 PDF 文档。通过它,我们可以完成如生成 PDF、读取 PDF 内容、添加水印、合并 PDF 等多种操作。本篇博客将详细介绍 iTextPDF 的使用方法,并提供一些实践样例,帮助开发者快速上手。 样例代码: htt…

模态与非模态窗口及使用时的数据交互

模态窗口使用exec()方法显示&#xff0c;会阻塞父窗口&#xff0c;直到对话框关闭&#xff1b; 非模态对话框允许同时操作主窗口和设置窗口&#xff0c;使用show()。 模态和非模态的主要区别在于用户能否与父窗口交互&#xff0c;非模态更适合需要频繁切换的场景。非模态窗口需…

Docker进入MySQL之后如何用sql文件初始化数据

关闭Docker-compose.yml里面所有容器 docker compose -f docker_compose.yml down后台形式开启Docker-compose.yml所有容器 docker compose -f docker_compose.yml up -d罗列出所有启动过的&#xff08;包括退出过的&#xff09;容器 docker ps -a进入指定容器ID内部 docke…

MAC 地址

MAC地址&#xff08;Media Access Control Address&#xff09;是指网络设备在数据链路层使用的唯一标识符&#xff0c;也称为硬件地址或物理地址。它用于标识设备之间的网络通信&#xff0c;是网络适配器&#xff08;如网卡、Wi-Fi适配器等&#xff09;的唯一标识。每个网络设…

Redis 7.0中5种新特性及实战应用

Redis 7.0引入了多项革命性的新特性&#xff0c;不仅在性能和可靠性方面有所提升&#xff0c;更在功能和使用体验上有了质的飞跃。本文将介绍Redis 7.0的五大关键新特性&#xff0c;可以根据实际情况利用Redis 7.0的强大功能&#xff0c;构建更高效、更可靠的应用系统。 特性一…

PHP实现PDF自动签名

技术要点&#xff1a;在PDF中找到一个固定锚点&#xff0c;在需要放置图片的地方找到测试出锚点对应的XY位 // 使用了poppler方法&#xff0c;其他PDF库在获取坐标方面有各种问题&#xff0c;他的安装是在Linux底层&#xff0c;比在PHP项目中用Composer安装的库看上去更稳定&a…

中达瑞和便携式高光谱相机:珠宝鉴定领域的“光谱之眼”

在珠宝行业中&#xff0c;真伪鉴定始终是核心需求。随着合成技术与优化处理手段的日益精进&#xff0c;传统鉴定方法逐渐面临挑战。中达瑞和推出的便携式高光谱相机&#xff0c;凭借其独特的“图谱合一”技术&#xff0c;为珠宝真假鉴定提供了科学、高效且无损的解决方案&#…

2025年渗透测试面试题总结-某战队红队实习面经(附回答)(题目+回答)

网络安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 某战队红队实习面经 个人经历与技术能力 2. HVV/攻防演练成绩 3. 上一个工作主要内容 4. 有意思的逻…

【PostgreSQL数据分析实战:从数据清洗到可视化全流程】5.1 描述性统计分析(均值/方差/分位数计算)

&#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 文章大纲 5.1 描述性统计分析&#xff1a;均值、方差与分位数计算实战5.1.1 数据准备与分析目标数据集介绍分析目标 5.1.2 均值计算&#xff1a;从整体到分组分析总体均值计算加权均值…

npm下载插件无法更新package.json和package-lock.json文件的解决办法

经过多番查证&#xff0c;使用npm config ls查看相关配置等方式&#xff0c;最后发现全局的.npmrc文件的配置多写了globaltrue&#xff0c;去掉就好了 如果参数很多&#xff0c;不知道是哪个参数引起的&#xff0c;先只保留registryhttp://xxx/&#xff0c;试试下载&#xff0…

基于Anaconda的Pycharm环境配置

一、前提条件&#xff1a; 1、默认已安装完Anaconda&#xff0c;且创建虚拟环境&#xff0c;参见https://blog.csdn.net/XIAOWEI_JIN/article/details/147657029?spm1001.2014.3001.5501 2、已安装pycharm&#xff0c;下载链接见Pycharm官网&#xff0c;以下以PyCharm 2024.…

Word域操作记录(从1开始的毕业论文格式排版)

傻逼Word。 写在最前面 如果你的文章不包括&#xff1a;自动目录、交叉引用、自动题注。请关闭此页面。继续阅读本文是在浪费您用于跟格式如泥潭里缠斗的时间。 本文内容概述 从指导手册到毕设初稿 基于多级列表的自动目录生成方法 正片开始 关于文字 拿到毕设手册&#…

Linux中的web服务

什么是www www是world wide web的缩写&#xff0c;及万维网&#xff0c;也就是全球信息广播的意思 通常说的上网就是使用www来查询用户所需要的信息。 www可以结合文字、图形、影像以及声音等多媒体&#xff0c;超链接的方式将信息以Internet传递到世界各 处去。 当你连接w…

linux -c程序开发

目的是在linux中创建可执行的c语言程序的步骤 和gcc,make和git的简单运用 建立可执行程序的步骤: -1:预处理: --:头文件展开;--去掉注释;--宏替换;--条件编译 -2:编译 --:将预处理之后的c语言替换为汇编语言带阿米 --:语法分析,语义分析,代码生成 --:检查语法正确性并且优…

Netty 是一个基于 Java NIO 的高性能网络通信框架

Netty 是一个基于 Java NIO 的高性能网络通信框架&#xff0c;广泛应用于构建分布式系统、RPC 框架、即时通信系统等场景。它的核心设计目标是 异步、非阻塞、高可扩展性&#xff0c;其底层原理涉及 事件驱动模型、线程模型、内存管理 等关键技术。以下是 Netty 的核心原理和架…

UI 库 Ant Design 中的 Table 表格和分页器:快速实现数据展示和分页功能

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》、《前端求职突破计划》 &#x1f35a; 蓝桥云课签约作者、…

Java实现堆排序算法

1. 堆排序原理图解 堆排序是一种基于二叉堆&#xff08;通常使用最大堆&#xff09;的排序算法。其核心思想是利用堆的性质&#xff08;父节点的值大于或等于子节点的值&#xff09;来高效地进行排序。堆排序分为两个主要阶段&#xff1a;建堆和排序。 堆排序步骤&#xff1a; …

【Hive入门】Hive安全管理与权限控制:审计日志全解析,构建完善的操作追踪体系

目录 引言 1 Hive审计日志概述 1.1 审计日志的核心价值 1.2 Hive审计日志类型 2 HiveServer2操作日志配置 2.1 基础配置方案 2.2 日志格式解析 2.3 日志轮转配置 3 Metastore审计配置 3.1 Metastore审计启用 3.2 审计事件类型 4 高级审计方案 4.1 与Apache Ranger…