闭包
- 捕获引用:当你在委托或 lambda 表达式中使用外部变量时,它们捕获的是这个变量的引用,而不是当时的值。
- 变量的生命周期:捕获的变量的生命周期不受限于它的作用域,委托可以在变量的作用域结束后继续访问它。
为了避免这种情况,可以在循环内引入一个新的局部变量,使每个委托捕获不同的变量引用。
List<Func<int>> funcs = new List<Func<int>>();
for (int i = 0; i < 5; i++)
{
int captured = i; // 引入局部变量
funcs.Add(() => captured);
}
foreach (var func in funcs)
{
Console.WriteLine(func()); // 输出 0, 1, 2, 3, 4
}
委托delegate
// 定义一个委托类型
public delegate int MyDelegate();
// 用Lambda表达式创建委托实例
MyDelegate myDelegate = () => { return _value; };
属性的表达式体定义
// 这是不合法的,不能在表达式体定义中使用代码块
public int MyProperty => { _value++; return _value * 10; };
为什么不能使用 { _value++; return _value * 10; }
{ _value++; return _value * 10; }
是一个包含多行语句的代码块,而表达式体定义的属性仅支持单行表达式。
实现包含逻辑的属性
如果你想在属性中包含多行代码或者复杂逻辑(比如递增 _value
,然后返回 _value * 10
),你需要使用传统的属性语法,显式地使用 get
访问器:
private int _value;
public int MyProperty
{
get
{
_value++;
return _value * 10;
}
}
合法的属性只读定义
public int MyProperty => _value++;
public int MyProperty => _value+10;
public int MyProperty => { return _value };
只有3不合法。表达式体定义的属性只能包含一个简单的表达式,不能包含块级代码(即不能包含 {}
以及 return
语句)。
即:
private int _value = 10;
public int MyProperty
{
get { return _value; }
}
lambda表达式
在 C# 中,=>
符号不仅仅用于 Lambda 表达式,还可以用于定义只读属性和表达式体成员(expression-bodied members)。虽然语法上看起来相似,但它们之间并没有直接的包含关系。
区分 =>
在不同上下文中的用途
-
Lambda 表达式中的
=>
:- 在 Lambda 表达式中,
=>
用于分隔参数列表和方法体。 - 例如:
(x, y) => x + y
是一个接收两个参数并返回它们之和的 Lambda 表达式。
Func<int, int, int> add = (x, y) => x + y;
- 在 Lambda 表达式中,
-
属性和方法的表达式体定义中的
=>
:- 在 C# 6.0 及以上版本中,
=>
也用于定义只读属性或方法的表达式体。这个用法简化了只返回一个表达式的属性或方法的定义。 - 例如:
public int MyProperty => 42;
定义了一个只读属性,直接返回42
。
public class MyClass { // 只读属性,返回字段 _value private int _value = 42; public int MyProperty => _value; // 方法的表达式体定义,返回平方 public int Square(int x) => x * x; }
- 在 C# 6.0 及以上版本中,
关系与区别
- Lambda 表达式和属性的表达式体定义 都使用了
=>
符号,但它们不是同一个概念。- Lambda 表达式用于定义匿名方法。
- 属性的表达式体定义用于简化属性和方法的实现。
=>
符号 在这两种不同的上下文中只是语法上的相似,而不意味着属性与 Lambda 表达式之间有直接的关系。
Lambda 表达式可以有返回值
实际上,Lambda 表达式最常见的用途之一就是用于定义带有返回值的匿名函数。
1. 带有返回值的 Lambda 表达式
如果 Lambda 表达式的主体只包含一个表达式(即没有花括号 {}
),这个表达式的结果会自动作为 Lambda 表达式的返回值。你不需要显式地使用 return
关键字。
// 一个带有返回值的Lambda表达式
Func<int, int, int> add = (x, y) => x + y;
// 调用Lambda表达式
int result = add(3, 4); // result = 7
2. 多行的 Lambda 表达式
如果 Lambda 表达式的主体包含多个语句,则需要使用花括号 {}
包裹整个主体,并且在需要返回值时,必须使用 return
关键字。
// 一个多行的Lambda表达式,带有返回值
Func<int, int, int> multiply = (x, y) =>
{
int product = x * y;
return product;
};
// 调用Lambda表达式
int result = multiply(3, 4); // result = 12
3. 无返回值的 Lambda 表达式
如果 Lambda 表达式没有返回值(即它是 void
类型的),你可以使用 Action
委托类型,或者在 Lambda 表达式中不返回任何值。
// 一个无返回值的Lambda表达式
Action<string> printMessage = message => Console.WriteLine(message);
// 调用Lambda表达式
printMessage("Hello, World!");
4. 总结
- Lambda 表达式可以有返回值,并且返回值的类型由使用的委托类型(如
Func<T>
)决定。 - 当 Lambda 表达式主体是单一表达式时,返回值会自动推导。
- 当 Lambda 表达式包含多行代码时,使用
{}
包裹主体,并显式使用return
关键字返回值。
谓词(Predicate)
1. 谓词(Predicate)是什么?
- 谓词 是一个定义好的委托类型,通常用于表示一个返回
bool
类型结果的方法签名。 Predicate<T>
是 C# 中一个内置的委托,它定义了一个接收类型T
的参数并返回bool
类型结果的方法。
public delegate bool Predicate<T>(T obj);
谓词通常用于像 List<T>.Find
、List<T>.FindAll
这样的泛型方法中,作为条件来筛选、查找或删除元素。
2. =>
一定要和谓词搭配使用吗?
- 不一定。
=>
是 Lambda 表达式的标志,Lambda 表达式可以用于创建匿名方法,而不仅仅用于谓词。Lambda 表达式可以用于任何接受委托(如Func
、Action
、Predicate
)的方法,而不仅仅是谓词。
3. =>
是 Lambda 表达式的专有符号吗?
- 是的,
=>
是 C# 中 Lambda 表达式的专有符号,用于定义匿名方法。Lambda 表达式允许你以简洁的方式定义内联的、无需命名的方法。
4. =>
和 Lambda 表达式的关系?
=>
是 Lambda 表达式的一部分。Lambda 表达式本质上是一个匿名函数,而=>
符号用于分隔参数和方法体。
5. =>
和 Lambda 表达式是包含关系还是被包含关系?
- 包含关系:
=>
是 Lambda 表达式的一部分,因此=>
是 Lambda 表达式所包含的符号。Lambda 表达式可以看作是一个完整的表达式,而=>
是其中用来分隔参数列表和表达式主体的符号。
6. 相关的概念有哪些?
- 委托(Delegate):委托是 C# 中的类型,表示对具有特定参数和返回类型的方法的引用。Lambda 表达式通常用于创建符合委托签名的匿名方法。
- 匿名方法:没有名称的方法,可以用 Lambda 表达式或
delegate
关键字定义。Lambda 表达式是一种更简洁的匿名方法定义方式。 Func
和Action
:这些是 C# 中内置的泛型委托类型,用于表示带有返回值和无返回值的方法。Func
通常用于带返回值的 Lambda 表达式,而Action
用于无返回值的情况。- 表达式树(Expression Trees):表达式树是 Lambda 表达式的另一种表示形式,通常用于 LINQ 查询中,它们允许你将 Lambda 表达式解析为数据结构,而不是编译为实际代码。
示例代码
// 使用Lambda表达式作为Predicate
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
// 直接使用Lambda表达式
int result = numbers.Find(n => n > 2);
// 使用显式Predicate委托
Predicate<int> predicate = n => n > 2;
int result2 = numbers.Find(predicate);
重要总结:
- 谓词 是一种委托类型,表示一个返回
bool
的方法。 =>
符号 是用于定义 Lambda 表达式的部分,它将参数列表和方法体分开。- Lambda 表达式 是匿名方法的一种形式,用于创建符合委托签名的内联方法。
- 相关的概念还包括委托、匿名方法、
Func
、Action
,以及表达式树。
Raycast和Collider
1. Collider 的存在与顺序
- 尽管
UISprite
的depth
高,但Raycast
检测顺序不完全取决于depth
,而是取决于Collider
在场景中的实际位置和存在。确保你的背景sprite
和目标sprite
只有一个Collider
,并且没有其他隐藏的Collider
影响检测。
2. Depth 与 Raycast
depth
是 NGUI 中的一个属性,用于控制绘制顺序,但这并不直接影响物理Raycast
的检测顺序。Raycast
的顺序由Collider
在3D空间中的实际位置决定,而不是depth
。
反射
//这里的fieldValue实际上是一个字符串的数据,但用object装了
object fieldValue = fieldInfo.GetValue(obj); //返回的是object,但任可以和int相加
+操作符会处理隐式转换
as
关键字在C#中用于尝试将对象转换为指定的类型,并且如果转换失败,则返回null
而不是抛出异常。这与+
操作符在字符串和整型之间的行为没有直接关系。
当使用+
操作符将字符串(string
)和整型(int
)相加时,隐式转换的过程大致可以描述为以下步骤(虽然实际的实现可能更复杂,涉及编译器和运行时环境):
-
识别操作数类型:编译器首先识别出
+
操作符的两个操作数的类型,一个是string
,另一个是int
。 -
查找合适的重载:由于
string
类为+
操作符定义了重载,该重载接受一个string
和一个object
作为参数(因为所有引用类型都是object
的子类,包括int
的装箱形式),编译器会选择这个重载版本。但更具体地说,C#编译器会优化这个过程,直接识别到整型到字符串的隐式转换,而不是先装箱成object
。 -
隐式转换整型到字符串:在这一步,整型值被隐式地转换为字符串。这通常是通过调用整型类型的
ToString
方法来实现的,尽管这个过程对程序员来说是透明的。 -
字符串连接:一旦整型被转换为字符串,两个字符串就可以通过
string
类的+
操作符重载进行连接。
Resources
Quaternion.identity
:对于不需要修改的默认旋转(即没有旋转),你可以直接使用Quaternion.identity
。这是一个静态的只读属性,表示没有旋转的四元数。
void Start()
{
// 假设你的预设体名为"MyPrefab",并且直接位于Resources文件夹下
GameObject prefab = Resources.Load<GameObject>("MyPrefab");
// 实例化预设体
GameObject instance = Instantiate(prefab, transform.position, Quaternion.identity);
// 你还可以将实例化的预设体设置为某个特定对象的子对象
// instance.transform.SetParent(transform, false); // false表示不改变实例化的预设体的世界位置(或不写,默认保留世界坐标)
}
如果你的预设体(Prefab)存放在Resources
文件夹下的某个子文件夹中,你需要在Resources.Load
方法中指定完整的路径来加载它。
假设你的预设体名为"MyPrefab",并且它位于Resources
文件夹下的一个名为"SubFolder"的子文件夹中,你可以按照以下方式加载它:
GameObject prefab = Resources.Load<GameObject>("SubFolder/MyPrefab");
Particle System(粒子系统)
粒子系统的基本概念
- 粒子(Particle):粒子是粒子系统的基本组成单位,代表系统中的每一个微小实体。每个粒子都拥有自身的属性,如位置、速度、加速度、颜色、生命值等。
- 属性(Attributes):粒子的属性决定了其外观和行为。常见的属性包括坐标、速度、加速度、颜色、生命值等。通过对这些属性的控制,可以实现不同的视觉效果。
- 规则(Rules):粒子系统的核心是规则提取,即确定粒子如何运动、变化以及相互作用。这些规则可以是简单的物理规律,如重力、阻力等,也可以是复杂的自定义规则。
粒子系统在Unity中的应用
Unity中的Particle System是一个专门用于制作特效的系统,它允许开发者以高效且易于控制的方式生成大量粒子,并通过各种参数来控制粒子的行为和外观。Unity粒子系统非常灵活,涵盖了多个模块,每个模块都可以独立执行不同的功能,如Emission(发射)、Shape(形状)、Color over Lifetime(生命周期颜色变化)、Size over Lifetime(生命周期大小变化)等。
在Unity中使用粒子系统时,可以通过以下步骤来创建和配置粒子效果:
- 创建粒子系统:在Hierarchy面板中右键点击空白处,选择Effects -> Particle System来创建一个新的粒子系统。
- 设置粒子系统的属性:在Inspector面板中,可以调整粒子系统的各种属性,包括粒子的形状、大小、颜色、速度等。Unity提供了丰富的选项和参数供开发者选择和调整。
- 添加粒子材质:点击粒子系统的Renderer组件,在Inspector面板中的Material属性中选择一个粒子材质。Unity自带了一些默认的粒子材质,也可以导入自定义的粒子材质。
- 设置粒子的发射位置和形式:可以通过设置粒子系统的Transform属性来控制粒子的发射位置和旋转角度。也可以通过编写脚本来动态控制粒子的发射位置和形式。
- 播放粒子效果:点击播放按钮即可看到粒子效果在场景中播放出来。可以通过调整粒子系统的播放速度、循环模式等属性来控制粒子效果的播放方式。
Input.GetAxis
如果你想要检测鼠标滚轮的滚动事件,你应该使用 Input.GetAxis("Mouse ScrollWheel")
。这个函数返回一个值,该值表示滚轮自上次调用以来滚动的量。如果滚轮向上滚动,这个值将大于0;如果滚轮向下滚动,这个值将小于0。请注意,这个值的大小(即滚动量)可能会因不同的硬件和操作系统而异,所以你可能需要根据自己的需求来对这个值进行缩放或解释。
Input.GetAxis
函数通常接受一个字符串参数,这个字符串是你在 Unity 的 Input Manager(输入管理器)中定义的轴(Axis)的名称。然后,它会根据当前绑定到这个轴上的输入设备(如游戏手柄的摇杆或键盘的某个键)的输入情况,返回一个介于 -1 到 1 之间的浮点数。
- 当输入处于“中立”位置时(例如,摇杆居中),返回 0。
- 当输入向一个方向完全倾斜时(例如,摇杆向左或向右推到极限),返回 -1 或 1,具体取决于方向。
示例
假设你有一个名为 "Horizontal" 的轴,它可能被绑定到了游戏手柄的左摇杆的水平移动或键盘的左右箭头键。你可以这样使用 Input.GetAxis
来获取这个轴的输入值:
float horizontalInput = Input.GetAxis("Horizontal");
// 使用 horizontalInput 来控制游戏中的角色或相机等
// 例如,将其乘以速度来改变角色的位置
transform.position += new Vector3(horizontalInput * speed * Time.deltaTime, 0, 0);
注意事项
- 在使用
Input.GetAxis
之前,确保在 Unity 的 Input Manager 中正确设置了轴的名称和绑定的输入设备。 Input.GetAxis
返回的是连续的值,而不仅仅是按下或释放的二进制状态。这使得它非常适合用于控制角色的移动速度或方向。- 如果你想要检测某个键是否被按下(而不是获取其连续的值),可以使用
Input.GetKeyDown
或Input.GetKey
。 - 对于游戏手柄,Unity 会自动处理大部分常见的输入映射,但你也可以自定义输入映射来满足特定需求。
为什么经常要乘缩放因子配上Time.deltaTime?
保持帧率独立性:
游戏在不同的设备和不同的性能设置下可能会有不同的帧率(FPS)。如果动画或物理模拟直接依赖于帧的数量而不是时间,那么在不同的帧率下,这些动画或模拟的速度可能会有所不同。通过使用 Time.deltaTime
,你可以确保无论帧率如何变化,每帧的更新都是基于相同的时间间隔进行的。乘以缩放因子(如 150f
)可以进一步调整这个速度,以适应游戏的需求。
* Time.deltaTime
Time.deltaTime
的作用就是提供一个表示自上一帧以来经过的时间(以秒为单位)的浮点数。通过乘以 Time.deltaTime
,开发者可以将游戏逻辑中的时间间隔从“每秒执行多少次”转换为“每帧执行多少量的工作”,从而确保无论游戏的帧率如何变化,游戏逻辑的执行频率都是相对稳定的。
Vector3.zero
(表示(0, 0, 0)),Vector3.one
(表示(1, 1, 1)),Vector3.forward
(表示(0, 0, 1),即正Z轴方向),Vector3.back
(表示(0, 0, -1),即负Z轴方向),Vector3.up
(表示(0, 1, 0),即正Y轴方向),和Vector3.down
(表示(0, -1, 0),即负Y轴方向)。
c#中的out和ref
想引用的变量必须要赋值,ref是想引用之前就要赋值好,不然不准用,out是可以在引用之前不赋值,但在out引用后不赋值不让出去
ref 关键字
ref
关键字用于按引用传递参数。这意味着方法接收的是原始变量的引用,而不是它的一个副本。- 在使用
ref
参数之前,必须先将其初始化为一个具体的值。换句话说,你不能在方法内部首次为ref
参数赋值,除非它作为out
参数传递。 - 使用
ref
可以让你在方法内部修改参数的值,并且这些修改会反映到原始变量上。
out 关键字
out
关键字也用于按引用传递参数,但它主要用于从方法中输出多个值。- 与
ref
不同的是,out
参数在方法内部不需要事先初始化。实际上,在方法返回之前,你必须为out
参数赋值。 - 使用
out
参数时,调用方法时不需要提供具体的初始值,但必须指定一个变量来接收输出值。
Lerp函数(暂时用不上)
基本格式Lerp(A, B, t)
A
是起始值。B
是目标值。t
是插值系数,通常是一个介于0和1之间的值,表示当前值在A和B之间的比例。当t=0
时,返回A
;当t=1
时,返回B
;当t
在0和1之间时,返回A和B之间的插值。
Lerp函数的应用场景
- 数值渐变:可以用于实现数值的平滑过渡,比如游戏中角色的生命值、能量值等的增减。
- 位置渐变:通过
Vector3.Lerp
函数,可以实现物体在两个位置之间的平滑移动。 - 颜色渐变:通过
Color.Lerp
函数,可以在两个颜色之间实现平滑的颜色渐变效果,常用于UI元素的视觉反馈。
限制Unity中变量的上下限
Mathf.Clamp
是Unity中最常用的方法来限制一个值在指定的最小值和最大值之间。它接受三个参数:要限制的值、最小值、和最大值,并返回被限制在最小值和最大值之间的值。
float value = 5.0f;
float min = 0.0f;
float max = 10.0f;
// 限制value的值在0到10之间
value = Mathf.Clamp(value, min, max);
Mathf
类进行圆形计算
Mathf.Sin
和Mathf.Cos
:这两个函数可以用来计算正弦和余弦值,这在计算圆上某点的位置时非常有用。Mathf.Sqrt
:计算平方根,这在计算圆的半径或距离时可能会用到。Mathf.PI
:表示π的值,是计算圆形相关角度和弧度的基础。
Mathf.Sin
Mathf.Sin
函数接受一个角度(以弧度为单位)作为参数,并返回该角度的正弦值。正弦函数在数学和物理学中有着广泛的应用,特别是在波形和振荡的描述中。在游戏开发中,它可以用于实现各种基于正弦波的效果,比如物体的周期性上下移动、光的闪烁效果等。
示例用法:
float angleInRadians = Mathf.PI / 4; // 45度转换为弧度 | |
float sineValue = Mathf.Sin(angleInRadians); | |
Debug.Log("Sine Value: " + sineValue); |
Mathf.Cos
与Mathf.Sin
类似,Mathf.Cos
函数也接受一个角度(以弧度为单位)作为参数,但返回的是该角度的余弦值。余弦函数与正弦函数密切相关,它们共同描述了直角三角形中边与角的关系。在游戏开发中,余弦函数同样可以用于实现各种基于余弦波的效果,或者与正弦函数结合使用来创建更复杂的运动模式。
示例用法:
float angleInRadians = Mathf.PI / 2; // 90度转换为弧度 | |
float cosineValue = Mathf.Cos(angleInRadians); | |
Debug.Log("Cosine Value: " + cosineValue); |
值得注意的是
Mathf.Sin
和 Mathf.Cos
函数都期望接收的参数是以弧度为单位的。
然而,在日常生活和许多其他领域,人们更习惯于使用角度来表示角度大小。Unity中的Mathf
类提供了Mathf.Deg2Rad
和Mathf.Rad2Deg
两个函数,用于在弧度和角度之间进行转换。
float angleInDegrees = 90;
float angleInRadians = Mathf.Deg2Rad(angleInDegrees);
float angleInRadians = Mathf.PI / 2;
float angleInDegrees = Mathf.Rad2Deg(angleInRadians);
在Unity中,如果你想要将sin
和cos
的值转换回对应的角度(通常是弧度转换为角度),你需要使用反三角函数,即Mathf.Asin
(对于sin
的逆运算)和Mathf.Acos
(对于cos
的逆运算)。但是,需要注意的是,这些函数返回的是弧度值,并且由于sin
和cos
函数的周期性,它们的逆运算可能返回的是在一个周期内的角度,而不是原始角度的精确值。