关于类与构造函数
类(class)是对象的一种类型,不再赘述其定义。在声明一个类的时候,我们只是声明了一种包含了数种特定 数据类型与方法 的集合的数据类型,并没有创建其实例,我们需要在代码的其他地方创建这些实例。
注意类只能在特定类型的代码块中声明,例如方法内部就不能声明类。一般来说直接在脚本类中声明就可以了。
以下是一些常见用法。
public class 脚本类:MonoBehaviour
{
class Item // Item 类名
{
public string name;
// 也可以直接初始化:
// string name = "aaa"
// 如果不写public,那么默认为private
public void LogInfo()
{
Debug.Log("aaa");
}
}
void Start()
{
// Item -> 类名
// item -> Item类的实例名
// Item item -> 声明一个名为item的Item对象,赋值为后面返回的实例
Item item = new Item();// Item() -> 构造函数调用,返回类的实例
item.name = "bbb";
// 如果要访问的数据是public,那么现在就可以从类外部访问数据了
item.LogInfo();
// 调用方法
}
void Update()
{
}
}
关于构造函数:
构造函数可以用来设置类的新实例。
class Item // Item 类名
{
public string name;
// 也可以直接初始化:
// string name = "aaa"
// 如果不写public,那么默认为private
public Item(string name)
{
this.name = name;
}
}
关键字 this ,它始终指向正在执行当前代码的实例。例如,在构造函数中,它就指向正在被创建的类实例;而在实例方法中,它指向调用了实例方法的实例
如上文在LogInfo()中如果有this,则其指向item实例。
当然,修改构造函数以后,我们使用它new一个实例的时候,也需要传入参数才行。
重载
在C#中,同一个类中允许存在多个同名方法,但它们的参数列表必须不同。
编译器会根据调用时传入的实参来决定具体调用哪一个版本。
这就是方法重载,它可以用于创建更具针对性的函数版本。除了普通函数,构造函数也可以被重载。
public class Logger
{
// 无参版本
public void Log()
{
Debug.Log("默认日志");
}
// 一个字符串参数
public void Log(string message)
{
Debug.Log(message);
}
// 一个字符串 + 一个整数
public void Log(string message, int severity)
{
Debug.Log($"[{severity}] {message}");
}
}
静态成员
静态成员属于“类”本身,而不属于某个具体的“对象”。
如果说普通的实例成员是与某一个类的实例关联的,那么静态成员就是与这个类的概念本身关联着的。它直接通过类名来调用,且不能访问任何一个实例成员。
可以视为是在所有的实例中,所有静态成员都指向它们对应的,相同的,唯一的内容。
静态方法也一样。
例如:
// 假设Item类中存在一个静态变量
public static int numberOfInstances=0;
// 可以在构造函数中添加以下内容来统计所有Item实例的数量
numberOfInstances += 1;
// 需要引用时直接使用类似如下的形式
Item.numberOfInstances;
属性
在 Unity以及 C#中,属性(Property) 是一种特殊的类成员。可以把它理解为 “智能字段(Smart Field)”。
它的本质是方法,但使用起来像变量。它通过 get(读)和 set(写)访问器,让我们在获取或设置数据时可以插入自定义逻辑。
private int health; // 私有的后备字段(Backing Field)
public int Health
{
// 或者get => health;
get
{
return health;
}
set
{
// value 是 C# 关键字,代表外部赋给它的值
if (value < 0)
health = 0;
else
health = value;
// 血量变化时触发事件(比如更新 UI)
OnHealthChanged?.Invoke();
}
}
我们可以随意地自定义其内的行为,包括设置边界,触发UI变化,同步更新其他内容等。
特性
特性(Attribute) 是“标签”或“注解”。它本身不改变代码的逻辑,而是给代码(类、方法、字段等)额外附加一些“元数据(Metadata)”,告诉编译器、Unity 编辑器或运行时“这个东西有某种特殊身份或需求”。
这些标签可以用来美化编辑器页面,方便调整数值,部分标签也有特殊的用处。以下是常用的几种。
[Header("标题")]:在面板里分组显示。[Tooltip("提示文字")]:鼠标悬停时显示说明。[Range(min, max)]:把数值变成滑块。[SerializeField]:强制序列化私有字段,让它显示并保存在 Inspector 中。
?.条件运算符
?. 为”空条件运算符“。
如果左边的对象不是 null,就继续执行右边的操作;如果是 null,就整个跳过,不再往后执行。
c#
if (OnButtonClick != null)
{
OnButtonClick.Invoke();
}
OnButtonClick?.Invoke();// 等价于上面的内容。
字符串插值
在 C# 中,$ 符号是字符串插值的语法标记。
它允许直接在字符串中用{ }花括号嵌入变量、表达式或方法返回值,编译器会自动将其替换为实际的值。
public void Log(int errorCode)
{
Debug.Log($"错误码:{errorCode}");
}
$ 让 {errorCode} 不再是普通的文本,而是指向方法传入的 errorCode 变量。如果调用 Log(404),实际输出到控制台的就是 "错误码:404"。
如果我们要在代码里把字符串内容写成多行(例如希望 HP 和数值分两行),我们也可以使用逐字插值字符串 $@,它允许我们直接enter键换行,但要小心缩进,它会把tab缩进与我们平时用的\n也识别为文字进行输出。
(这真的有用吗...?)
healthText.text = $@"HP
{currentHealth}/{maxHealth}";
ref的使用
ref基本上只是在优化的时候可以使用,了解一下即可。
ref 的作用是按引用传递,传入前必须初始化。
在游戏性能敏感的地方,ref 是 C# 开发者的利器。它允许方法修改值类型 , 避免值类型复制(GC 和 CPU 开销)。
用例:修改 Vector3、Rect 等大型结构体(Struct)
Unity 中的坐标、旋转、缩放(Vector3、Quaternion)都是结构体(值类型)。如果不加 ref,传入方法的是副本,修改毫无意义;加上 ref,就能直接修改实体的位置。
// tip:类似Mathf.Clamp(position.x, minBounds.x, maxBounds.x)的方法的作用是返回在范围内(minBounds.x到maxBounds.x)的值,若超出范围则返回上限或下限。
// 将坐标限定在边界内(直接修改原始坐标,不产生副本)
public void ClampPosition(ref Vector3 position, Vector3 minBounds, Vector3 maxBounds)
{
position.x = Mathf.Clamp(position.x, minBounds.x, maxBounds.x);
position.y = Mathf.Clamp(position.y, minBounds.y, maxBounds.y);
position.z = Mathf.Clamp(position.z, minBounds.z, maxBounds.z);
}
// 调用
// enemy.transform.position 是一个属性,必须先赋值给一个变量。
Vector3 enemyPos = enemy.transform.position;
// 直接修改了局部的 enemyPos
ClampPosition(ref enemyPos, minBounds,minBounds);
// 但其实...下面这种写法更常用一些。直接创造一个运算后的新的坐标覆盖上去,虽然要多一点性能开销,但更符合直觉
public Vector3 ClampPosition(Vector3 position, Vector3 minBounds, Vector3 maxBounds)
{
position.x = Mathf.Clamp(position.x, minBounds.x, maxBounds.x);
position.y = Mathf.Clamp(position.y, minBounds.y, maxBounds.y);
position.z = Mathf.Clamp(position.z, minBounds.z, maxBounds.z);
return position;
}
// 调用
enemyPos = ClampPosition(enemyPos, ...);
