Kirisame1969の魔法屋
首页项目归档轨迹照片墙音乐说说杂谈友链关于
封面

我要成为Unity糕手——VER.003

写作时间:2026-09-01 14:21:54
# C#
# 基础内容
# 编程语言
# 实用
# 我要成为unity糕手

关于类与构造函数

类(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, ...);

‍

avatar

Kirisame1969

软件工程在读小登,正在学习C#与神秘Unity魔法

RECOMMENDED

我要成为Unity糕手——VER.001

2026-06-06 20:24:14

我要成为Unity糕手——VER.002

2026-06-16 14:33:58

我要成为Unity糕手——VER.004

2026-09-01 17:26:16

Table of Contents