当前位置:   article > 正文

unity中的问题记录(角色的控制)_unity 跟c#的修饰符

unity 跟c#的修饰符

unity中的默认访问修饰符

与c#相同,class不写public,则默认同一程序集(internal)中可以访问,在unity中,程序集表现为项目,即同一项目可以互相访问

类里的成员默认与c#同样相同,都是private

在C#中,将字段和方法都设为私有(private)并使用static修饰符并不是“多此一举”,而是根据具体的设计需求和场景来决定的。下面我会详细解释为什么在某些情况下使用static是有意义的,即使字段和方法都是私有的。

静态字段(Static Fields)

  1. 全局访问性静态字段属于类本身,而不是类的任何实例。这意味着,无论创建了多少个类的实例,静态字段都只有一份拷贝,并且可以在类的所有实例之间共享。即使字段是私有的,它仍然可以在类的内部被所有静态和非静态方法访问,这有助于在类的不同部分之间共享数据。

  2. 内存管理:由于静态字段只存在于内存中的一份拷贝,因此它们有助于减少内存的使用,特别是在需要跨多个实例共享数据时。

  3. 单例模式:在实现单例模式时,静态字段常用于存储类的唯一实例。虽然在这种情况下,你可能会将访问该实例的方法设为公共的,但实例本身通常是私有的静态字段。

如果是建立在类不是静态类,可以实例的基础上,静态私有就有意义 

而如果类本身是静态类,private static和private就没区别了,但是,在C#中,如果类本身是静态类(static class),那么该类中的所有成员(包括字段、方法、属性等)都必须是静态的static),因为静态类不能被实例化,

在这种情况下,private staticprivate在静态类内部确实在语法上看起来没有区别,因为所有成员都已经是隐式静态的。但是,从语义和代码清晰度的角度来看,使用private static明确指定成员的静态性质仍然是一个好习惯

在unity里,类不写public

通常意味着它可以在同一个Unity项目中的任何脚本中被访问,但如果你想从另一个Unity项目或DLL中访问它,则不可能了,如果是写了public,则还需要确保该程序集(或DLL)被正确地引用或加载。

character controller

主要特点

  • 非物理驱动:与Rigidbody不同,CharacterController不依赖于物理引擎来计算移动和碰撞。这意味着它不会受到物理力(如重力)的影响,除非你手动实现。
  • 简单移动:提供了SimpleMoveMove方法来简化角色的移动控制。
  • 碰撞检测:能够检测与场景中其他物体的碰撞,并自动处理这些碰撞,防止角色穿过墙壁或其他障碍物。
  • 高度控制:可以自动处理台阶和斜坡等高度变化,使角色能够自然地行走在不同地形上。

RaycastHit

基本属性

RaycastHit结构体包含了一系列有用的属性,这些属性提供了关于射线与物体相交的详细信息,包括但不限于:

  1. point:碰撞点的世界坐标位置(Vector3类型)。这是射线与碰撞体相交的确切位置。
  2. normal:碰撞点处的表面法线(Vector3类型)。这个法线垂直于碰撞体在碰撞点处的表面。
  3. distance:从射线的起点到碰撞点的距离(float类型)。这个距离以世界单位表示。
  4. collider:与射线相交的碰撞体(Collider类型)。通过这个属性,可以访问碰撞体的各种属性和方法,如名称、标签、刚体组件等。
  5. rigidbody:如果碰撞体附加了刚体组件(Rigidbody),则此属性会返回该刚体组件的引用(Rigidbody类型)。否则,此属性为null。
  6. triangleIndex:如果碰撞体是Mesh Collider,则此属性表示射线命中的三角形索引(int类型)。这对于处理复杂网格碰撞体特别有用。

Physics.Raycast

Physics.Raycast

  • 描述:Physics.Raycast用于检测从起点沿指定方向的射线是否与任何碰撞体相交。这是最基本的射线检测方法,适用于需要检测单个碰撞点的场景,如射击、视线检测、点击物体等。
  • 特点
    • 简单易用,返回第一个相交的碰撞体信息。
    • 无法检测射线与多个碰撞体之间的相交。

Physics.RaycastAll

  • 描述:Physics.RaycastAll与Physics.Raycast类似,但它返回所有与射线相交的碰撞体的信息,而不仅仅是第一个碰撞点。这适用于需要检测射线路径上所有物体的场景,如激光光束穿过多个目标。
  • 特点
    • 可以检测射线与多个碰撞体之间的相交。
    • 返回所有相交的碰撞体信息,提供更多数据。
    • 需要额外处理返回的碰撞信息,包括距离、碰撞点等。

Physics.RaycastNonAlloc

  • 描述:Physics.RaycastNonAlloc是Physics.Raycast的一个优化版本,它允许将结果存储在预先分配的数组中,从而避免在每次调用时都分配新的内存。这在高频率的射线检测场景中非常有用,可以减少垃圾回收和内存分配的开销。
  • 特点
    • 与Physics.Raycast类似,但可以自定义返回结果数组,提高性能。
    • 需要手动管理返回结果数组,稍显复杂。

Input.GetMouseButton

执行

if(Input.GetMouseButtonDown(0))
{
    print("ButtonDown");
}
if (Input.GetMouseButton(0))
{
    print("Button");
}
if (Input.GetMouseButtonUp(0))
{
    print("ButtonUp");
}

3d模型是怎么转成2d呈现在屏幕上

3D模型转换成2D并呈现在屏幕上的过程,在计算机图形学中,通常被称为图形渲染管线(Graphics Rendering Pipeline)。这个过程涉及多个步骤,包括建模、变换、视图变换、投影变换、裁剪、光栅化、着色和纹理映射、深度测试和混合、帧缓冲等。以下是这些步骤的详细解释:

1. 建模

首先,3D模型在游戏或应用程序中以模型的形式存在,这些模型由顶点(vertices)和面(faces)组成,可以是简单的几何形状,也可以是复杂的多边形网格。

2. 变换

3D模型需要经过一系列的变换,以确定它们在游戏世界中的位置、方向和大小。这些变换包括平移(translation)、旋转(rotation)和缩放(scaling)

3. 视图变换

接下来,需要将3D世界中的坐标转换为相对于摄像机(或观察者)的视图空间坐标。这通常通过一个视图矩阵(View Matrix)来完成,它描述了摄像机在世界空间中的位置和方向。

这个摄像机对象并不是物理上的新相机,而是用于渲染过程中的一个虚拟对象,它定义了如何从特定位置和角度观察3D场景。

通过这些属性和功能,摄像机对象能够将3D世界中的坐标转换到相对于摄像机视图的坐标系统中。这样,后续的投影变换才能正确地将这些坐标映射到2D屏幕上,形成我们最终看到的图像。

在这个过程中,3D模型仍然是数据意义上的3D模型,但它们的表示方式已经转换为了从摄像机视角观察的结果,这是通过视图变换和后续的渲染管线步骤共同实现的。 

4. 投影变换

为了将3D场景投影到2D屏幕上,需要进行投影变换。这可以通过正投影(Orthographic Projection)或透视投影(Perspective Projection)来实现。

  • 投影:保持物体的尺寸不变,常用于工程制图或需要精确比例的场景。
  • 透视投影模仿人眼观察的方式,使远处的物体看起来更小,从而增加场景的深度感和真实感。

可以理解为在视图变换之后,对3d模型的数据进行2d化的处理

5. 裁剪

在投影变换之后,一些位于摄像机视野之外的物体或部分会被裁剪掉,因为它们不会出现在最终的2D图像中。

6. 光栅化

经过投影变换的3D物体现在需要转换为2D图像上的像素。这个过程称为光栅化(Rasterization),它将3D物体的边缘映射到屏幕上的像素网格,并确定哪些像素属于该物体。

7. 着色和纹理映射

在确定了哪些像素属于特定物体后,接下来是着色(Shading)和纹理映射(Texture Mapping)。

  • 着色:根据光照模型计算每个像素的颜色和亮度。
  • 纹理映射:将2D图像(纹理)应用到3D物体上,以增加细节和现实感。

8. 深度测试和混合

  • 深度测试:为了正确处理物体的前后关系,需要进行深度测试(Depth Testing)。每个像素都有一个深度值,只有当新像素的深度值小于(即更靠近摄像机)当前像素的深度值时,才会更新该像素的颜色。
  • 混合:处理半透明物体的过程,它会根据透明度将像素颜色与背景颜色混合。

9. 帧缓冲

所有处理过的像素会存储在帧缓冲(Frame Buffer)中,然后显示器会读取这些像素数据并在屏幕上显示出来。

10. 后处理和显示

  • 后处理:渲染管线的最后阶段,可以应用多种图像处理效果来增强最终图像的视觉质量,如色彩校正、亮度和对比度调整、边缘锐化等。
  • 显示:渲染好的帧缓冲内容会被发送到显示设备,如监视器或电视屏幕,由显示适配器(如GPU)控制,按照设定的刷新率将帧缓冲中的数据转换为可以在屏幕上显示的信号。

11. 优化和性能考虑

为了提高渲染效率和性能,开发者会进行各种优化,如减少绘制调用、使用级联阴影映射、实施遮挡剔除、使用LOD技术等。

12. 用户输入和交互

虽然不直接涉及渲染,但用户输入(如键盘、鼠标或游戏手柄)是游戏体验的重要组成部分。用户的输入会影响游戏逻辑,进而影响渲染管线中的视图变换和其他渲染决策。

unity里的射线监测

射线(Ray)是从一个点出发沿直线方向无限延伸的线段。在Unity中,我们可以使用Ray类来创建这样的射线,并通过Physics.RaycastPhysics.RaycastAllPhysics.RaycastNonAlloc等方法来检测这条射线是否与场景中的物体相交。

使用方法

  1. 创建射线:首先,你需要定义一个射线的起点和方向。通常,起点是相机位置或玩家角色的眼睛位置,方向是从起点到鼠标点击的屏幕位置在3D空间中的向量

  2. 发送射线:使用Physics.Raycast等函数将射线发送到场景中,并获取碰撞信息(如果有的话)。

  3. 处理碰撞:如果射线与物体相交,Physics.Raycast会返回一个布尔值true以及一个RaycastHit结构体,其中包含了碰撞点的位置、碰撞的物体、碰撞的法线等信息。

  1. using UnityEngine;
  2. public class RaycastExample : MonoBehaviour
  3. {
  4. void Update()
  5. {
  6. // 创建从相机到鼠标位置在屏幕上的射线
  7. Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
  8. // 声明RaycastHit用于存储碰撞信息
  9. RaycastHit hit;
  10. // 发送射线,并检测是否有碰撞,最大距离为100
  11. if (Physics.Raycast(ray, out hit, 100f))
  12. {
  13. // 如果有碰撞,打印碰撞物体的名称
  14. Debug.Log("Hit: " + hit.collider.gameObject.name);
  15. // 可选:你可以在这里添加更多代码来处理碰撞,比如显示UI提示等
  16. }
  17. }
  18. }

注意事项

  • 确保场景中需要被射线检测到的物体上附加了Collider组件。
  • 你可以通过调整Physics.Raycast方法的layerMask参数来限制射线检测的层。
  • 在性能敏感的应用中,要注意减少不必要的射线检测调用,或者使用Physics.RaycastNonAlloc等优化手段来减少内存分配。

Camera.main.ScreenPointToRay(Input.mousePosition)

  • Camera.main:这是Unity提供的一个快捷方式,用于访问场景中被标记为“MainCamera”的相机。如果你的场景中只有一个相机,或者你有意地将某个相机设置为“MainCamera”,那么Camera.main就会引用这个相机。(默认的初始相机即使改了名也可以main出来)

  • ScreenPointToRay():这是Camera类的一个方法,它接受一个屏幕上的点(通常是一个Vector3,但在这个上下文中,由于屏幕是二维的,我们通常只关心x和y坐标,而z总是0),并返回一个从相机位置出发,穿过该屏幕点的射线。

  • Input.mousePosition:这是Unity的Input类的一个属性,它返回一个Vector3,表示鼠标当前在屏幕上的位置。然而,这个Vector3的z分量总是0,因为屏幕是二维的。

  • 将这三者结合起来,Camera.main.ScreenPointToRay(Input.mousePosition) 就创建了一条从主相机的位置出发,穿过当前鼠标在屏幕上的位置的射线。(仅仅是使这样的一条射线存在)

补充:

Camera.main 是通过以下步骤来确定哪个相机是主相机的:

  1. 遍历场景中的所有相机。
  2. 检查每个相机的 Tag 属性。
  3. 返回第一个 Tag 为 "MainCamera" 的相机。
  4. 如果没有任何相机的 Tag 是 "MainCamera",则返回 null

ScreenPointToRay

  • 在Unity中,相机(Camera)负责将世界坐标系中的3D场景投影到2D屏幕上。这个投影过程涉及到相机的位置、旋转、视场角(Field of View, FOV)等参数。
  • 当你点击屏幕上的一个点时,这个点实际上是2D屏幕上的一个像素位置。为了将它转换回3D世界中的一条射线,我们需要知道相机是如何将这个3D世界投影到屏幕上的。

Instantiate

public static T Instantiate<T>(T original)

public static T Instantiate<T>(T original, Vector3 position, Quaternion rotation)

public static T Instantiate<T>(T original, Vector3 position, Quaternion rotation, Transform parent)

public static T Instantiate<T>(T original, Transform parent)

public static T Instantiate<T>(T original, Transform parent, bool worldPositionStays)

Awake覆盖

Awake是在脚本实例后调用,而Start是在Update第一次唤醒,的前一刻才开始调用

  • 如果在Awake方法中给变量赋值,并且该变量之前已经在Inspector中或通过脚本直接赋值了,那么Awake方法中的赋值会覆盖之前的值。
  • 如果在Start方法中给变量赋值,并且该变量之前已经在Awake方法中赋值了,那么Start方法中的赋值会覆盖Awake方法中的值,因为Start方法在Awake方法之后调用。

yield return

在迭代器块中使用

在迭代器块中(即实现了 IEnumerable 或 IEnumerator 接口的方法中),yield return 用于逐个返回集合中的元素,而不需要显式地创建集合的完整副本。每次调用 MoveNext() 方法时,迭代器都会执行到下一个 yield return 语句,并返回该语句后面的值。然后,迭代器的状态会被保存,以便在下次调用 MoveNext() 时从该点继续执行。

  1. public IEnumerable<int> GetNumbers()
  2. {
  3. for (int i = 0; i < 5; i++)
  4. {
  5. yield return i; // 逐个返回数字
  6. }
  7. }
  8. // 使用示例
  9. foreach (var number in GetNumbers())
  10. {
  11. Console.WriteLine(number); // 输出 0, 1, 2, 3, 4
  12. }

在 Unity 协程中使用

在 Unity 中,yield return 被用于协程中,以实现非阻塞的等待。协程方法返回一个 IEnumerator,并且在方法体内使用 yield return 来暂停执行,直到满足某个条件(如等待一段时间、等待直到下一帧等)。Unity 的游戏循环会在适当的时候恢复协程的执行。

  1. IEnumerator MyCoroutine()
  2. {
  3. Debug.Log("开始协程");
  4. yield return new WaitForSeconds(1f); // 等待1秒
  5. Debug.Log("等待了1秒后继续");
  6. // 可以继续添加更多的 yield return 语句
  7. }
  8. void Start()
  9. {
  10. StartCoroutine(MyCoroutine()); // 启动协程
  11. }

 在上面的例子中,MyCoroutine 方法是一个协程,它首先打印出“开始协程”,然后使用 yield return new WaitForSeconds(1f); 暂停执行1秒。在这1秒内,Unity 的游戏循环会继续运行,处理其他事件和更新。1秒后,Unity 会自动恢复 MyCoroutine 协程的执行,并打印出“等待了1秒后继续”。

注意事项

  • 在迭代器块或协程中,一旦执行到 yield return 语句,当前方法的执行就会暂停,控制权会返回到调用者。
  • 在迭代器块中,你不能在 yield return 语句之后添加任何代码,因为那些代码将永远不会被执行(除非在另一个迭代周期中)。但在协程中,你可以在 yield return 之后添加代码,这些代码将在下一次协程恢复执行时运行。
  • 迭代器块和协程都是 C# 语言的特性,但它们被 Unity 以不同的方式用于游戏开发中。迭代器块主要用于集合的遍历,而协程则用于实现非阻塞的等待和延迟执行。

IEnumerator

IEnumerator 是 C# 中的一个接口,它定义了一个简单的迭代机制,用于按顺序访问集合中的元素。然而,在 Unity 的上下文中,IEnumerator 有一个额外的用途,即作为协程的基础。

在 C# 集合中的用途

在标准的 C# 集合框架中,IEnumerator 和 IEnumerable 接口一起工作,允许对集合进行迭代。IEnumerable 接口要求实现一个 GetEnumerator() 方法,该方法返回一个 IEnumerator 对象。然后,你可以使用 IEnumerator 的 MoveNext() 方法来遍历集合中的元素,并使用 Current 属性来访问当前元素

  1. foreach (var item in collection) // 背后使用了 IEnumerable 和 IEnumerator
  2. {
  3. // 处理 item
  4. }

在 Unity 协程中的用途

在 Unity 中,IEnumerator 被赋予了额外的含义,用于实现协程。Unity 的协程是一种允许你编写非阻塞代码的机制,这些代码可以在未来的某个时间点继续执行,而不会阻塞游戏的主循环。

当你定义一个返回 IEnumerator 的方法时,这个方法就可以被当作协程来执行。在 Unity 中,你通过调用 StartCoroutine(IEnumerator routine) 方法来启动协程,其中 routine 是你想要作为协程执行的方法的调用(注意,你实际上是传递了方法的返回值,即一个 IEnumerator 对象)。

在协程方法中,你可以使用 yield return 语句来暂停协程的执行。Unity 会记住协程的当前状态,并在适当的时候(如下一帧或指定的等待时间之后)恢复协程的执行。yield return 后面可以跟多种类型,比如 WaitForSecondsWaitForFixedUpdatenull(表示下一帧继续)等,以实现不同的等待效果。

  1. IEnumerator MyCoroutine()
  2. {
  3. // 做一些事情...
  4. yield return new WaitForSeconds(1f); // 等待1秒
  5. // 继续做一些事情...
  6. }
  7. void Start()
  8. {
  9. StartCoroutine(MyCoroutine()); // 启动协程
  10. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/喵喵爱编程/article/detail/1011599
推荐阅读
相关标签
  

闽ICP备14008679号