本篇博客开始,我们将开启一个新的篇章------C#的学习。
C#(读作"C Sharp")是由微软开发的一种现代、面向对象、类型安全的编程语言,它在2000年首次发布,至今已发展成为一个功能强大且应用广泛的编程平台。
目录
[值类型 vs 引用类型(防坑核心)](#值类型 vs 引用类型(防坑核心))
[变量修饰符与 Unity 的"化学反应"](#变量修饰符与 Unity 的“化学反应”)
[编译时常量 const](#编译时常量 const)
[运行时常量 readonly](#运行时常量 readonly)
[const vs readonly 对比速查](#const vs readonly 对比速查)
[逐字字符串 @"" ------ 让反斜杠"变老实"](#逐字字符串 @"" —— 让反斜杠“变老实”)
[原始字符串字面量(C# 11+)](# 11+))
[Unity 结构体的特殊隐式转换](#Unity 结构体的特殊隐式转换)
[装箱 (Boxing) 与 拆箱 (Unboxing) ------ 性能隐形杀手](#装箱 (Boxing) 与 拆箱 (Unboxing) —— 性能隐形杀手)
[Unity 的核心应用场景](#Unity 的核心应用场景)
C#的介绍
C#诞生于世纪之交(1990s-2000)。是微软为了推动其革命性的 .NET Framework 战略,需要一款全新的、现代化的、专门为.NET平台设计的编程语言。由Anders Hejlsberg, Turbo Pascal 和 Delphi 之父,担纲C#的首席架构师
C#自诞生开始就有以下几个目标:
-
简单易学: 比C++更简洁,减少复杂性(如指针的直接操作)。
-
完全面向对象: 一切皆对象(比Java更彻底,连基本类型也是
struct对象)。 -
类型安全: 强大的编译时和运行时类型检查,提高代码健壮性。
-
现代化特性: 内置支持垃圾回收 (自动内存管理)、异常处理 、委托 (类型安全的函数指针)、属性 、事件等。
-
强大的组件化能力: 方便构建可复用的软件组件。
C#的应用领域
C#凭借其强大的功能和跨平台能力,在多个领域有着广泛应用:
- 桌面应用开发:通过WPF、WinForms等框架
- Web开发:ASP.NET Core框架
- 游戏开发:Unity游戏引擎的主要编程语言
- 移动应用:Xamarin跨平台移动开发
- 云服务:Azure云平台开发
- 物联网(IoT):嵌入式系统开发
C#程序编写
像以前些C++那样,我们推荐以VS2022为编写C#的主平台。首先我们先创立一个项目


(一个解决方案可以有多个项目)

我们简单编写一段代码
cs
using System;
namespace Primer
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("hello world");
}
}
}
结果为:

C#知识点
变量
变量是程序中最基本的存储单元,它的值在运行时可以被改变。但写好变量,远不止"声明后使用"这么简单。
声明与初始化
-
语法 :
类型 变量名 = 初始值; -
局部变量 必须显式初始化才能使用;成员变量(字段) 会获得默认值(数字为
0,引用为null),但始终建议初始化,避免产生歧义。 -
使用
var关键字可以隐式类型推断,必须声明时赋值。
cs
float speed = 5.0f; // float字面量必须有f后缀
var enemyList = new List<Enemy>(); // 编译器推断为List<Enemy>
值类型 vs 引用类型(防坑核心)
理解这个分类,是写出正确逻辑的关键。
| 分类 | 存储内容 | 赋值行为 | Unity 典型案例 |
|---|---|---|---|
| 值类型 | 数据本身 | 完整拷贝,互不影响 | int, float, bool, struct (如 Vector3, Color, Quaternion) |
| 引用类型 | 堆上对象的地址 | 传递地址,多个变量指向同一对象 | class (如 GameObject, Transform, MonoBehaviour), string, 数组 |
注意:string 虽然是引用类型,但因为不可变性,任何修改都会生成新串,行为上像值类型,但需注意拼接时的性能问题。
变量修饰符与 Unity 的"化学反应"
-
publicvsprivate:public字段会自动序列化并在 Inspector 中显示;private则不会。 -
[SerializeField]:强制让private字段也显示在 Inspector 中,兼具封装与可视化调参,是强烈推荐的编码习惯。cs[SerializeField] private float moveSpeed = 6f; -
[HideInInspector]:在 Inspector 中隐藏public字段,保持面板简洁。 -
static静态变量 :属于类本身,全局唯一。在 Unity 中,静态变量的生命周期贯穿整个游戏运行,切换场景也不会销毁 ,常用来做跨场景全局数据,但滥用会导致数据残留和强耦合,可用单例模式或ScriptableObject代替。
常量
常量一旦赋值,在程序运行时绝不可改变。合理使用常量,可以消灭"魔法数字",让代码意图清晰。
编译时常量 const
-
是什么 :在编译时值就已经完全确定,编译器会直接把常量值内联到所有引用它的地方,类似 C++ 的宏替换。
-
硬性限制:
-
只能修饰简单的值类型、
string或null。 -
不能使用
new关键字,不能调用任何方法(包括Mathf、LayerMask等)。 -
隐式为
static,因此不能再加static关键字。
-
-
适用场景:永远不会改变的数学常量、标签名、固定参数。
cpppublic const string PlayerTag = "Player"; public const float Gravity = -9.81f; public const int MaxEnemies = 20; -
⚠️ 严重陷阱 :由于常量值被编译进调用程序集,如果你修改了一个
const的值,所有引用它的代码必须重新编译才会生效,否则会继续使用旧值。对于分模块开发或发布到 DLL 的项目,这会造成难以排查的 Bug。
运行时常量 readonly
-
是什么 :只能在声明时或构造函数中赋值,之后不可更改。它是在运行时持有的常量,不会被内联。
-
优势:
-
可以用运行时计算的结果赋值,比如从方法返回值初始化。
-
可以是实例字段,让每个对象都有属于自己的一份不可变数据。
-
可以修饰任何类型。
-
-
Unity 必用场景:
cs// 编译时无法获取的物理层 private readonly int enemyLayer = LayerMask.GetMask("Enemy"); // 实例只读ID,构造函数确定 private readonly string characterId; public Character(string id) { characterId = id; } -
静态运行时常量
static readonly:结合了全局唯一和运行时初始化,非常适合替代那些需要方法调用的"全局常量"。cspublic static readonly int GroundLayer = LayerMask.GetMask("Ground");
const vs readonly 对比速查
| 特性 | const |
readonly |
|---|---|---|
| 赋值时机 | 编译时 | 声明时 / 构造函数中 |
| 隐式静态 | 是,不能加 static |
否,可声明为 static |
| 修饰类型 | 简单值类型、string、null |
任何类型 |
| 运行时调用 | 不允许(如 Mathf.Sqrt(2)) |
允许 |
| 内联行为 | 值直接嵌入引用处 | 保持变量引用,取值在运行时 |
| Inspector 显示 | 不序列化,不显示 | 不序列化,不显示 |
使用枚举作为一组关联常量
当你有几个互斥的固定选项时,枚举 是比 const 更专业的表达方式。
cs
public enum GameState { MainMenu, Playing, Paused, GameOver }
public GameState currentState = GameState.Playing;
它比直接用 const int MAIN_MENU = 0; 更具可读性,也能利用 IDE 的代码补全和编译期检查。
注释
C#的注释有三种,除了与C/C++的注释相同外,还有一个三杠的注释。用于XML的注释
cs
//这是一个注释
/*这是一个注释*/
XML注释
cs
/// <summary>
/// 计算两个数字的和
/// </summary>
/// <param name="a">第一个加数</param>
/// <param name="b">第二个加数</param>
/// <returns>两个数字的和</returns>
/// <exception cref="ArgumentException">当参数无效时抛出</exception>
/// <example>
/// <code>
/// int result = Add(5, 3); // 返回 8
/// </code>
/// </example>
/// <remarks>
/// 这是一个简单的加法方法示例
/// </remarks>
同样在VS中我们可以用Ctrl+K和Ctrl+C注释,Ctrl+K和Ctrl+U取消注释
程序结构
我们的程序卸载语句块中。语句块就是在大括号包裹之中。不同语句块之间书写规则不同。这与之前学习C语言的规则相同。同时C#也和C/C++一样每个语句后都要有分号
以以下代码为例
cs
using System;
//命名空间
namespace Primer
{
//类
//面向对象代码主要写在类里
class Program
{
//函数
static void Main(string[] args)
{
Console.WriteLine("hello world");
//这是一个注释
/*这是一个注释*/
}
}
}
以上代码与C++类似我们逐一拆解。
引入命名空间
using System为引用命名空间,如果没有的话就要这么写
cs
namespace Primer
{
//类
//面向对象代码主要写在类里
//这一点类似于Java
class Program
{
//函数
static void Main(string[] args)
{
System.Console.WriteLine("hello world");
//这是一个注释
/*这是一个注释*/
}
}
}
引用也可以单独引用命名空间的一部分
cs
using System.Text; // 引入文本处理命名空间
或者在C#6.0后,可以直接静态引用
cs
using static System.Math; // 静态引入,可直接使用数学函数
命名空间
命名空间与C++类似,可以嵌套定义
cs
// 多层嵌套命名空间(两种写法等价):
namespace Outer.Inner
{
class MyClass { }
}
// 等价于:
namespace Outer
{
namespace Inner
{
class MyClass { }
}
}
为了避免命名冲突可以这么写
cs
using SysIO = System.IO; // 创建别名
SysIO.File.WriteAllText(...); // 使用别名
类
C#中,类内可以定义:字段(成员变量)、属性、方法(类内函数)、事件、索引器、运算符重载、嵌套类型。
类的修饰符也有三种
cs
public class MyClass { } // 任何地方可访问
internal class MyClass { } // 同一程序集内可访问(默认)
private class MyInnerClass { }// 只能在包含类中访问
主函数
主函数必须为以下其一:
cs
// 程序入口点,必须满足以下条件之一:
static void Main() { }
static void Main(string[] args) { }
static int Main() { }
static int Main(string[] args) { }
static async Task Main() { }
static async Task<int> Main() { }
static async Task Main(string[] args) { }
输入输出
主要学习以下四种
cs
Console.WriteLine("hello world");
Console.Write("你好世界");
Console.ReadLine();
Console.ReadKey();
输出
cs
//打印后换行
Console.WriteLine("hello world");
//打印后没有空行
Console.Write("你好世界");
输入
cs
//打印后换行
Console.WriteLine("hello world");
//打印后没有空行
Console.Write("你好世界");
//检测用户输入的内容,忽略换行,直到回车才会结束
Console.ReadLine();
//检测用户是否按键,只要按任意键就结束
Console.ReadKey();
折叠代码
C#开发中,折叠代码(Code Folding / Outlining)是集成开发环境(如 Visual Studio)提供的一种功能,允许开发者将代码块收缩为一行或一个标题,从而在编辑器中隐藏细节,只显示代码的高层结构,主要的作用是:
-
提高可读性:将长方法、多个成员或逻辑相关的代码分组折叠,使文件结构更清晰。
-
隐藏实现细节:在查看类或模块的概览时,可以只关注公共接口,暂时屏蔽内部实现。
-
便于团队协作 :通过
#region指令标记代码块,可以让团队成员快速定位到特定功能区域。 -
导航效率提升 :配合 IDE 的折叠/展开快捷键(如
Ctrl+M, Ctrl+O折叠到定义,Ctrl+M, Ctrl+L切换所有折叠),能快速在不同抽象层次间切换。
写法如下:
cs
//引用命名空间
using System;
//命名空间
namespace Primer
{
//类
//面向对象代码主要写在类里
//这一点类似于Java
class Program
{
//函数
static void Main(string[] args)
{
#region
//这是一个区域
#endregion
}
}
}
C#中的转义字符
| 转义序列 | 名称 | 功能效果 | Unity 常用度与示例 |
|---|---|---|---|
\' |
单引号 | 在 char 字面量中插入一个单引号 |
⭐⭐⭐⭐⭐ char c = '\''; // 存储一个引号字符 |
\" |
双引号 | 在字符串中插入一个双引号,避免语法冲突 | ⭐⭐⭐⭐⭐ string s = "他说:\"你好\""; |
\\ |
反斜杠 | 插入一个真正的反斜杠字符 | ⭐⭐⭐⭐⭐ 文件路径必备:"Assets\\Scripts" 但更推荐用逐字字符串:@"Assets\Scripts" 或直接使用正斜杠 / |
\0 |
空字符 (Null) | 字符值为 0 的字符,常用作字符串终止符 |
⭐ 极少在游戏逻辑中使用,偶尔在与底层 API 交互时出现 |
\a |
响铃 (警报) | 触发系统提示音(如果硬件支持),ASCII 码 7 | ⭐ 控制台程序偶尔会用到,Unity 中基本无实际意义,编辑器控制台可能产生"哔"声 |
\b |
退格 | 将光标向左移动一个位置,模拟"删除"效果 | ⭐ 控制台界面会用到,Unity 游戏内 UI 文本不处理退格,很少用 |
\f |
换页 | 打印机走纸到下一页,ASCII 码 12 | ⭐ 视觉上通常显示为奇怪符号,Unity 文本组件无法正确处理,基本弃用 |
\n |
换行 | 将光标移动到下一行开头,跨平台的换行标准 | ⭐⭐⭐⭐⭐ Debug.Log("第一行\n第二行"); Unity UI 的 Text 和 TextMeshPro 组件都完美支持 |
\r |
回车 | 将光标移动到当前行开头(常用于 \r\n 组合) |
⭐⭐⭐ Windows 系统经典换行是 \r\n,单独使用 \r 可以覆盖当前行输出,如进度条效果 |
\t |
水平制表符 | 插入一个制表符,产生空白对齐 | ⭐⭐⭐⭐ Debug.Log($"名字:\t{name}") 可对齐输出,UI 文本中也能产生缩进 |
\v |
垂直制表符 | 垂直方向制表,极少使用 | ⭐ 历史上用于打印机,Unity 文本组件无法正常处理,基本忽略 |
\uXXXX |
Unicode 字符 (4位16进制) | 插入一个 UTF-16 代码单元,XXXX 为 4 位十六进制数 |
⭐⭐⭐⭐⭐ string heart = "\u2665"; // ♥ 非常适合在 UI 中显示特殊符号 |
\UXXXXXXXX |
Unicode 字符 (8位16进制) | 插入一个 UTF-32 字符,自动转换为代理项对(需要时) | ⭐⭐⭐ 输入表情符号或生僻字:string emoji = "\U0001F60A"; // 😊 注意:字符串长度可能为 2 (char 代理对) |
\xH[H][H][H] |
十六进制转义 (1-4位) | 以十六进制值直接指定一个字符,后跟 1~4 个十六进制数字 | ⭐⭐ "\x41" 即 'A',"\x0D\x0A" 即 \r\n ⚠️ 极易出错:后面如果紧跟也是合法十六进制字符,会被吞噬,如 "\x41BC" 实际会解析成 \x41BC 然后报错,必须用 "\x41" + "BC" 隔开 |
逐字字符串 @"" ------ 让反斜杠"变老实"
文件路径、正则表达式这类包含大量反斜杠的字符串,如果每个都写成 \\ 会非常痛苦。在字符串前加 @ 即可让反斜杠不再具有转义功能 ,双引号本身使用两个连续双引号 "" 转义。
cpp
string path = @"C:\Users\Unity\Documents"; // 干净清爽
string quote = @"他说:""你好"""; // 输出:他说:"你好"
Unity 最佳实践:任何文件系统路径、资源路径,优先使用逐字字符串。
在 Resources.Load("Prefabs/Player") 这类 Unity 内部路径中,使用正斜杠 / 即可跨平台,无需转义。
字符串插值中的大括号转义
当你使用 $"{变量}" 插值时,想要输出真正的大括号 { 或 },需要用两个相同的大括号来转义:
cpp
int count = 5;
string msg = $"{{{count}}}"; // 输出 "{5}"
原始字符串字面量(C# 11+)
如果你使用的是较新的 C# 版本(Unity 2022.2 开始支持 C# 9/10,最新版本已支持 C# 11),可以用三个以上的双引号包裹字符串,里面的内容几乎不需要任何转义,连引号都能直接写(只要不与边界冲突)。它非常适合内嵌 JSON 或代码片段。
cs
string json = """
{
"name": "Unity",
"version": 2022
}
""";
类型转化
类型转换的本质
类型转换就是把一个值的类型 "看作" 或 "变作" 另一种类型。这分为两种情况:
-
隐式转换 (Implicit) :你什么都不用写,编译器自动帮你完成。一定是安全的,不会丢数据,不会抛异常。
-
显式转换 (Explicit) :你必须使用强制转换语法,告诉编译器"我知道我在做什么"。可能会丢失数据或引发异常。
核心铁律:隐式转换必然安全,显式转换需要你主动承担风险。
隐式转换
数值的隐式扩大转换
规则:小范围转大范围、整数转浮点数,都安全无痛。
cs
int hp = 100;
long longHp = hp; // int → long 隐式
float floatHp = hp; // int → float 隐式
float speed = 5.5f;
double dSpeed = speed; // float → double 隐式
特别注意:
-
int→float虽然隐式,但大整数(超过 16,777,216)转换时会损失精度,C# 不会警告你。 -
decimal和float/double之间没有隐式转换 ,必须显式,因为decimal高精度,来回转不安全。
引用类型的隐式上行转换
规则:派生类对象可以直接当作基类对象使用。
cs
class Enemy : MonoBehaviour { }
class Orc : Enemy { }
Orc orc = GetComponent<Orc>();
Enemy enemy = orc; // 隐式,Orc 就是一个 Enemy
在 Unity 中,所有脚本都是 MonoBehaviour 的派生类,所以你可以把任何脚本赋给 MonoBehaviour 类型的变量。
Unity 结构体的特殊隐式转换
Unity 为了方便,在它的数学库中预置了一些隐式转换,非常常用:
-
Vector2→Vector3:Vector2可以隐式转换为Vector3,新增的z分量会自动设为0。csVector2 v2 = new Vector2(1, 2); Vector3 v3 = v2; // 隐式,v3 = (1, 2, 0) -
Color→Vector4:Color可以隐式转换为Vector4,分量对应 (r,g,b,a)。 -
UnityEngine.Object的布尔隐式转换 :任何继承自UnityEngine.Object的变量(GameObject、Component等),都可以直接放进if语句,它重载了向bool的隐式转换,用来检查对象是否被销毁。csGameObject player; if (player) // 实际调用了 player != null 的引擎判空逻辑 { ... }
显式转换
数值的强制转换(缩小转换)
语法 :(目标类型) 变量
风险:精度丢失、溢出。
cs
float damage = 9.9f;
int intDamage = (int)damage; // 显式,结果为 9,直接截断小数部分
int big = 1000;
byte small = (byte)big; // 显式,1000 超过 255,溢出后结果为 232
可在 checked 代码块中检测溢出。
引用类型的下行转换(父类转子类)
风险 :如果父类引用实际指向的不是那个子类,运行时会抛出 InvalidCastException。
cs
Enemy enemy = new Orc();
Orc orc = (Orc)enemy; // 安全,因为 enemy 本质是 Orc
Enemy enemy2 = new Enemy();
Orc orc2 = (Orc)enemy2; // 抛出 InvalidCastException!
Unity 高频场景:
-
GetComponent<Transform>()等内部已经做完了转换。 -
老式写法
(Rigidbody)GetComponent(typeof(Rigidbody))非常不推荐,直接用泛型。
更优雅的显式转换方式
除了硬括号,C# 提供了更安全的操作符:
-
as操作符 :只适用于引用类型,转换失败返回null,不抛异常。csOrc orc = enemy as Orc; if (orc != null) { /* 安全使用 */ } -
is操作符 :类型检查,返回bool,不转换。csif (enemy is Orc) { Orc orc = (Orc)enemy; // 这时候可以放心强制转 } // 模式匹配版本(推荐) if (enemy is Orc myOrc) { myOrc.OrcSkill(); // 直接使用,免去二次转换 }
字符串到数值的解析
玩家输入、配置文件读取经常需要把字符串变为数字,这是典型的显式转换。
cs
string input = "123";
int num = int.Parse(input); // 若格式错,抛异常
bool ok = int.TryParse(input, out int safeNum); // 安全版本,失败返回 false
Unity 常用的是 float.TryParse 处理 UI 输入文本。
装箱 (Boxing) 与 拆箱 (Unboxing) ------ 性能隐形杀手
-
装箱 :值类型隐式转换为
object,在堆上复制一份数据,产生垃圾。 -
拆箱 :从
object显式转回值类型。
cs
int score = 100;
object box = score; // 装箱,隐式
int unbox = (int)box; // 拆箱,必须显式,类型必须完全一致
Unity 注意 :Debug.Log(123) 这种古老 API 会触发装箱(新版已优化),string.Format 的值参数也是 object,循环中大量使用会产生 GC,影响性能。尽可能用字符串插值 $"{score}" 替代。
枚举和整数之间的转换
枚举与 int 等基础整型之间是显式转换。
cs
GameState state = GameState.Playing;
int index = (int)state; // 显式,索引 1
GameState restored = (GameState)index; // 显式
算术表达式
C#自带的算术表达式如下:
| 运算符 | 含义 | 特别注意 |
|---|---|---|
+ |
加法 | 也用于字符串拼接,字符串与其他类型相加会调用 ToString(),可能产生装箱与内存分配。 |
- |
减法 | 取负用单目 -,如 int a = -5; |
* |
乘法 | 两个 int 相乘可能溢出,可改用 long 或 float。 |
/ |
除法 | 整数除法会截断小数部分 ,5 / 2 结果是 2;至少有一个浮点数时才会得到浮点结果,如 5f / 2f 得 2.5。 |
% |
取模(求余) | 常用于循环索引、奇偶判断,例如 index % array.Length。 |
++ / -- |
自增 / 自减 | 有前缀 ++i(先增再用)和后缀 i++(先用再增)的区别,在表达式中谨慎使用。 |
此外,Unity 对 Vector3、Vector2、Color 等结构体重载了算术运算符,这让移动、颜色混合等逻辑变得非常自然。
cs
// === 基础算术演示 ===
int baseDamage = 50;
float critMultiplier = 1.5f;
int totalDamage = (int)(baseDamage * critMultiplier); // 需要显式转换
float cooldown = 3.0f;
cooldown -= Time.deltaTime; // 复合赋值,实现计时器
int index = (currentIndex + 1) % enemies.Length; // 循环索引
// === Unity 向量算术 ===
Vector3 move = (transform.forward * Input.GetAxis("Vertical") +
transform.right * Input.GetAxis("Horizontal"))
* speed * Time.deltaTime;
transform.position += move; // Vector3 加法
Vector3 direction = (target.position - transform.position).normalized; // 减法得方向
float force = damage * 100f; // 根据伤害算力
避坑指南:
-
向量之间不能直接相乘(没有
Vector3 * Vector3重载),点乘用Vector3.Dot,叉乘用Vector3.Cross。 -
不要每帧都使用
Vector3.Distance开根号,比较距离平方用sqrMagnitude更省性能。
条件表达式
语法结构如下:
cs
条件 ? 结果1 : 结果2
C#中,该逻辑与C、C++中一致。如果"条件"为 true,则求值"结果1",否则求值"结果2"。它本质上是一个 if-else 的表达式版,可以返回一个值。且满足以下条件:
-
两端的表达式类型必须一致,或存在隐式转换。
-
右结合性:多个三元嵌套时从右向左分组,但为了可读性,强烈不建议嵌套超过一层。
-
它是短路求值:只会执行满足条件的那一个分支。
cs// 1. 根据状态切换文本 string stateText = isDead ? "阵亡" : "存活"; healthBarText.text = $"HP: {currentHealth}/{maxHealth} ({(isLow ? "危险" : "健康")})"; // 2. 根据距离改变颜色 float dist = Vector3.Distance(player.position, enemy.position); Color alertColor = dist < 5f ? Color.red : Color.green; renderer.material.color = alertColor; // 3. 伤害公式分支 int damage = isCritical ? (int)(baseDamage * 2.5f) : baseDamage; // 4. 安全处理 null string displayName = playerName != null ? playerName : "未知玩家"; // 更现代的写法可以用 null 合并运算符:playerName ?? "未知玩家"
逻辑表达式
逻辑表达式组合多个布尔值(true/false)并产生最终的布尔结果,使用的运算符有:
-
&&(逻辑与):两边都为true时才为true。 -
||(逻辑或):至少一边为true时即为true。 -
!(逻辑非):翻转布尔值。
操作数通常来自关系表达式 (==, !=, >, <, >=, <=)或本身就是布尔变量。
短路求值机制(非常重要)
&& 和 || 都是短路运算符:
-
对于
A && B,若A为false,则不会计算B。 -
对于
A || B,若A为true,则不会计算B。
利用这一点可以写出安全且高效的代码,例如先判空再访问成员:
cs
if (enemy != null && enemy.Health > 0) // 如果 enemy 为 null,不会执行 enemy.Health,避免空引用异常
{
Attack(enemy);
}
示例代码:
cs
// 典型输入判断(多重条件组合)
bool isGrounded = Physics.Raycast(transform.position, Vector3.down, 0.2f);
bool jumpPressed = Input.GetButtonDown("Jump");
if (isGrounded && jumpPressed && !isStunned)
{
rigidbody.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
}
// 是否在攻击范围内
float attackRange = 2.0f;
bool inRange = Vector3.Distance(transform.position, target.position) <= attackRange;
bool hasLineOfSight = !Physics.Linecast(transform.position, target.position, obstacleLayer);
if (inRange && hasLineOfSight)
{
PerformAttack();
}
// 逻辑非的妙用
bool isDead = health <= 0;
if (!isDead) // 等价于 isDead == false
{
Patrol();
}
位运算表达式
位运算直接对整型(int、long、enum 等)的二进制位进行操作,非常适合管理开关状态、层级掩码等。运算符包括:
| 运算符 | 名称 | 含义 | 示例 (设 a=5, b=3) |
|------|------|----------------|------------------------------------|--------------|
| & | 按位与 | 两位同时为1,结果位才为1 | 5 & 3 → 1 (0101 & 0011 = 0001) |
| | | 按位或 | 任意一位为1,结果位就是1 | 5 | 3 → 7 (0101 | 0011 = 0111) |
| ^ | 按位异或 | 两位不同时结果为1 | 5 ^ 3 → 6 (0101 ^ 0011 = 0110) |
| ~ | 按位取反 | 翻转所有位 | ~5 → -6 (取决于补码) |
| << | 左移 | 各二进制位左移,低位补0 | 5 << 1 → 10 (0101 << 1 = 1010) |
| >> | 右移 | 各二进制位右移,正数高位补0 | 5 >> 1 → 2 (0101 >> 1 = 0010) |
Unity 的核心应用场景
(1) 层掩码 (LayerMask)
物理检测时经常用到,通过位运算组合多个层。
cs
// 获取 "Enemy" 层和 "Boss" 层的掩码
int enemyLayer = 1 << LayerMask.NameToLayer("Enemy");
int bossLayer = 1 << LayerMask.NameToLayer("Boss");
int targetMask = enemyLayer | bossLayer; // 同时检测两个层
// 球形检测时使用
Collider[] hits = Physics.OverlapSphere(transform.position, 10f, targetMask);
(2) 枚举标志 (Flags)
将多个选项打包在一个变量里,用于状态管理、武器类型等。
cs
[System.Flags]
public enum WeaponType
{
None = 0,
Sword = 1 << 0, // 1
Bow = 1 << 1, // 2
Staff = 1 << 2, // 4
Dagger = 1 << 3 // 8
}
// 组合武器
WeaponType knightGear = WeaponType.Sword | WeaponType.Shield;
// 检查是否装备剑
bool hasSword = (knightGear & WeaponType.Sword) != 0;
// 添加匕首
knightGear |= WeaponType.Dagger;
// 移除弓(假设已有)
knightGear &= ~WeaponType.Bow;
(3) 快速运算
-
x << n相当于x * 2^n,x >> n相当于x / 2^n(整数除法),效率极高,常用于一些算法优化(但现代编译器通常会自动优化简单的乘除法,不必刻意使用)。 -
判断奇偶:
(number & 1) == 0为偶数,比% 2更快,但实际影响微乎其微。
本期内容到这里就结束了
封面图自取:
