一、先搞清楚值类型和引用类型
C# 是强类型语言,每个变量在声明时就要确定类型,之后不能随便改成别的类型。不同数据类型决定了变量存的是什么、占多少内存、能做什么操作。
在 Unity 脚本里,我们常接触的数据类型可以分成两大类:
- 值类型:直接存数据本身。比如
int、float、bool、char、struct、enum。它们通常分配在栈上,读写快。 - 引用类型:存的是数据在内存中的地址,实际数据在堆上。比如
string、class、数组、委托。赋值时拷贝的是引用,两个变量可能指向同一个对象。
这个区别很重要,尤其是当你把一个对象赋值给另一个变量时,改动一个可能会影响另一个,而值类型就不会。后面讲类和对象时还会再提到。
二、常用内置数据类型
下面是我在 Unity 里最常用到的几种类型,每种都结合具体场景说一下。
1. 整型
| 类型 | 描述 | 范围 |
|---|---|---|
int | 32 位有符号整数 | 约 -21 亿 ~ 21 亿 |
long | 64 位有符号整数 | 很大,一般用不到 |
short | 16 位有符号整数 | -32768 ~ 32767 |
byte | 8 位无符号整数 | 0 ~ 255 |
Unity 中最常用的就是int,比如分数、索引、层级、数量等。其他几个类型很少直接用到,了解即可。
intscore=100;intenemyCount=5;需要注意int是有符号的,如果确认数值不会为负,可以考虑用uint(无符号 int),但 Unity 内部很多 API 还是用int,所以保持一致就好。
2. 浮点型
| 类型 | 描述 | 精度 |
|---|---|---|
float | 32 位单精度浮点数 | 约 7 位有效数字 |
double | 64 位双精度浮点数 | 约 15-16 位 |
decimal | 128 位高精度小数 | 金融计算 |
Unity 里几乎所有的位置、速度、时间都用float。写float变量时一定要记得加后缀f,否则编译器会当成double然后报错。
floatspeed=5.5f;doublepreciseValue=3.14159265358979;// 很少用,Unity API 大多只接受 floatdouble在 Unity 中偶尔用于需要高精度的计算,但大多数 API 接收float,所以最终还是要转。decimal基本不会在游戏逻辑里出现。
3. 布尔类型
bool只有两个值:true和false,用来做条件判断、开关状态。
boolisGrounded=true;boolhasKey=false;在if语句里直接写变量名就行,不用写== true。
4. 字符与字符串
char:单个 Unicode 字符,用单引号。string:一串字符,用双引号。它是引用类型,但使用起来像值类型(内容不可变,修改时会创建新字符串)。
chargrade='A';stringplayerName="UnityLearner";字符串在游戏中常用,但要注意频繁拼接字符串会产生 GC 垃圾,后面讲性能优化时会再提。
5. 枚举类型
枚举是一种自定义值类型,用来定义一组有名字的常量,让代码读起来更直观。
publicenumGameState{MainMenu,Playing,Paused,GameOver}GameStatecurrentState=GameState.Playing;相比直接用int表示状态(0=主菜单,1=游戏中…),枚举可读性高得多,也避免魔法数字。
6. 结构体(struct)
结构体是值类型,适合表示轻量级的数据集合,比如坐标、颜色、玩家属性等。Unity 内置的Vector3、Quaternion都是结构体。
publicstructPlayerData{publicstringname;publicintlevel;publicfloathealth;}结构体和类的区别在于:结构体赋值时是复制整个值,类是复制引用。所以结构体适合小数据且不需要继承的场景。
三、变量的声明与初始化
声明变量很简单:类型 变量名 = 初始值;。初始值可以不写,但使用前必须赋值,否则编译报错。
inthealth;// 声明,暂时没值health=100;// 赋值floatmana=50.0f;// 声明并直接初始化C# 还支持var关键字,让编译器自动推断类型。但要注意,var只是语法糖,类型在编译时就确定了,不是动态类型。
vardamage=25;// 推断为 intvarplayerName="Tom";// 推断为 string我的建议:如果类型很明显(比如var x = 10;),用var没问题;但如果类型不那么一目了然,最好还是写出具体类型,提高代码可读性。在 Unity 里,我通常直接写明确类型,因为一些组件类型很长,用var反而省事,但也要看上下文。
四、常量与只读字段
const:编译时常量,声明时必须赋值,之后不能改。适合放一些固定值,比如最大生命值、重力常数。readonly:运行时常量,可以在声明时或构造函数里赋值,之后也不能改。适合需要在不同实例中可能不同的值。
publicconstintMaxHealth=100;publicreadonlyfloatgravity=9.81f;在 Unity 中要注意:const字段不会显示在 Inspector 面板上,也就是说如果你想让策划在 Inspector 里调整数值,就不能用const。readonly也不会序列化。所以如果想暴露给 Inspector 调整,用public普通字段(或[SerializeField] private)。
五、类型转换
不同类型之间经常需要转换,C# 里分为隐式转换和显式转换两种。
1. 隐式转换(自动的)
当目标类型能安全容纳源类型且不会丢失精度时,编译器会自动转换。
intintValue=100;longlongValue=intValue;// int 转 long,安全floatfloatValue=intValue;// int 转 float,安全,但可能丢失精度(大数时)int到float虽然不用写转换,但float只有约 7 位有效数字,太大的int转过去会丢失精度。一般游戏里数值不大,问题不大。
2. 显式转换(强制转换)
当转换可能丢失数据或精度时,需要手动写(目标类型)。
floatfloatValue=3.14f;intintValue=(int)floatValue;// 结果是 3,直接截断小数部分,不是四舍五入doubledoubleValue=123.456;inttruncated=(int)doubleValue;// 123注意:强制转换可能溢出或精度损失,使用时要想清楚。比如float转int会把小数全部舍去,如果希望四舍五入,要用Mathf.RoundToInt。
3. 使用 Convert 类
System.Convert提供了一些转换方法,如果转换失败会抛异常。
stringnumberStr="123";intnumber=Convert.ToInt32(numberStr);// 123stringfloatStr="3.14";floatpi=Convert.ToSingle(floatStr);// 3.14f但在 Unity 中更常用的是Parse或TryParse,因为Convert遇到非法输入会直接抛异常,影响游戏稳定性。
4. 字符串转数值:Parse 和 TryParse
Parse:字符串格式错误时抛异常。TryParse:安全转换,返回bool表示是否成功,推荐使用。
stringinput="42";intresult;if(int.TryParse(input,outresult)){Debug.Log("转换成功:"+result);}else{Debug.Log("转换失败,可能不是数字");}在游戏里,用户输入或外部数据往往不可靠,一定要用TryParse,避免游戏崩溃。除非你百分百确定字符串一定合法,否则不要用Parse或Convert。
5. 装箱与拆箱
值类型转为object类型叫装箱,反向叫拆箱。这个操作会带来性能开销,因为要在堆上分配内存。
intvalue=10;objectboxed=value;// 装箱intunboxed=(int)boxed;// 拆箱在Update等频繁调用的地方尽量避免装箱拆箱,否则容易产生 GC 垃圾,导致卡顿。例如不要把值类型塞进ArrayList(旧的非泛型集合),改用List<int>这样的泛型集合。
六、总结
变量和数据类型是 C# 最基础的部分,但很多坑都藏在这里:float不加f、字符串转换用错方法、值类型和引用类型搞混等等。建议刚开始学的时候多动手试,把各种类型转换跑一跑,看看输出结果,比只看概念记得牢。