Unity AR/VR开发:实战高手训练营
上QQ阅读APP看书,第一时间看更新

3.2 C#的基本语法和使用

本节将通过一系列示例介绍C#语言的基本语法及其使用方法。

3.2.1 变量和数据类型

在编程语言的世界,变量由变量名称和数据类型构成。变量的数据类型决定了我们可以在其中存储哪种类型的数据。我们可以把变量看作存储某个数据的临时储物箱。正如储物箱有各种类型和尺寸一样,数据也五花八门。

你不能把东西扔到储物箱里面后就撒手不管了,因为储物箱经常会放入一些新的东西。当你的应用需要记住一些变化时,就需要把旧的数据拿出来,然后把新的数据放进去。

这就是变量(Variable)的本质—变(Vary)。变量就像小孩的玩具积木一样,如图3-1所示。

000

图3-1 变量的例子

我们需要把正确的形状放到正确的储物箱里。储物箱就是变量,而它的数据类型(Datatype)决定了里面能放什么形状的东西。形状就是你可以放入变量的可能数值。

我们可以更换每个箱子里面的东西,比如可以拿出蓝色的方形积木,然后放进红色的方形积木,但前提是它们都是方形。我们不能把方形积木放到一个圆孔里面,因为数值的数据类型和变量的数据类型必须是匹配的。

常见的数据类型有如下几种。

1. 数字型的变量

在C#中,数字型的变量主要包括整数(int)、单精度浮点数(float)和双精度浮点数(double)。float和double类型变量的区别是:float是单精度类型,有效位数是6位,占用4字节的存储空间;而double是双精度类型,有效位数是15位,占用8字节的存储空间。默认情况下,小数使用double来表示。如果需要使用float,需要在末尾加上f。比如:

float myNumber = 1.23f;

在上面这行代码中,我们定义了一个名为myNumber的变量,其类型是float(单精度浮点数),初始值是1.23。

除了以上3种常用的数字变量类型外,在C#中还有其他类型的数字型变量,如表3-1所示。

表3-1 基本变量及其取值范围

000

表3-1中所示变量使用方法大同小异,这里就不再一一赘述了。

2. 文本型的变量

文本型的变量主要是char和string,其中char类型变量用于保存单个字符的值,而string类型变量则用于保存字符串的值。

比如:

string playerName = "Steve Jobs";

在上面这行代码中,我们定义了一个名为playerName的变量,其类型是string(字符串),初始值是Steve Jobs。

也可以用string类型保存数字:

string myString = "1";

不过此时,myString中保存的数据是字符串“1”,并不是数字1。

3. 布尔型的变量

在C#中,布尔型的变量是bool,用于保存逻辑状态的变量,包含两个值—true和false。比如:

bool isPlayerDead = true;

在上面这行代码中,我们定义了一个名为isPlayerDead的变量,其类型是bool(布尔),初始值是true。需要注意的是,C#是大小写敏感的,不能将大小写混淆。比如:

Bool bool1 = true;

该语句会报错,因为C#中并没有Bool类型的变量,只有bool类型。

关于变量命名需要提醒大家的是,在C#中允许字母、数字、下划线“_”出现在变量名中,但不能以数字开头,也不允许以int、bool这些系统保留字作为变量名。

除了以上3种基本类型的变量外,在C#中还支持引用类型、枚举类型等数据类型,在后续的实战学习中我们会逐渐接触到。

此外,C#还支持一种特殊的变量—常量。所谓的“常量”,是指在程序运行过程中值不会发生变化的量。在声明变量时,在变量的前面加上关键字const或readonly即可把该变量指定为一个常量。比如:

const int constNum = 2;
readonly int readonlyNum = 3;

常量必须在声明时赋值,且赋值后通常不允许再改变其值。如果将其他值赋给常量,编译器就会报错。

3.2.2 表达式与运算符

表达式与运算符的作用是对数据或信息进行各种形式的运算处理,这构成了程序代码的主体。

表达式由运算符和操作数组成。其中,运算符比较好理解,用于设置对操作数的运算,例如+、-、*和/分别代表加、减、乘、除4种运算。而操作数是运算符作用的实体,指出指令执行的操作所需要的数据来源。操作数的概念最早来源于汇编语言,其代表参与运算的数据及其单元地址。在C#中,操作数可以简单地理解为参与运算的各类变量和表达式等。

在C#中,我们需要了解以下几种主要的运算符。

1. 算术运算符

算术运算符是我们都很熟悉的基本数学运算,主要是加、减、乘、除、求余。

【示例3-1】基本数学运算

打开Unity,新建一个项目,将其命名为BasicMath。使用快捷键Ctrl+N新建一个场景,然后使用Ctrl+S保存场景,将其命名为MainScene。在Project视图中右击空白处,在弹出的快捷菜单中依次选择Create→Folder命令,将项目命名为Scripts。

双击进入项目文件夹并右击,在快捷菜单中依次选择Create→C# Script命令,如图3-2所示。

将上述创建的脚本命名为BasicCalculator,双击该脚本在Visual Studio中打开,并更改其中的代码,如代码清单3-1所示。

代码清单3-1 基础数学运算

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class BasicCalculator : MonoBehaviour
{
    // 初始化方法
    void Start()
    {
        //1.这是一行注释,解释了下面代码的作用,即定义两个整型变量
 
        int firstNumber = 1;   //第1个数
        int secondNumber = 5;  //第2个数
        //2.求二者的和
        int sumOfNumbers = firstNumber + secondNumber;
        //3.求二者的积
        int mulOfNumbers = firstNumber * secondNumber;
        //4.求二者的差
        int difOfNumbers = firstNumber - secondNumber;
        //5.求二者的商并进行强制类型转换
        double divOfNumbers = (double)firstNumber / secondNumber;
        //6.求二者的余数
        int remOfNumbers = firstNumber % secondNumber;
        //7.输出以上计算结果到Console中
        Debug.Log("两个数字的和是: " + sumOfNumbers);
        Debug.Log("两个数字的乘积是: " + mulOfNumbers);
        Debug.Log("两个数字之间的差是: " + difOfNumbers);
        Debug.Log("两个数字相除的商是: " + divOfNumbers);
        Debug.Log("两个数字相除的余数是: " + remOfNumbers);
 
    }
}
000

图3-2 创建新脚本

下面我们按照数字编号简单解释上述代码的作用。

首先要说明的是,在C#中//(双斜杠)的作用代表注释。那么,什么是注释呢?在开发产品的时候,我们需要借助注释提高代码的可读性,从而让接手项目的其他人,或者自己在若干天、若干周、若干月之后能看得懂当初的创作。单行注释用双斜杠“//”即可,如果是多行注释,就需要用/* */的形式,所有的注释内容都在星号中间。

下面对上述代码进行简单分析。

第1处:分别定义了两个整数变量firstNumber和secondNumber。

第2处:定义了一个名为sumOfNumbers的整数变量,用来保存两个整数的和。

第3处:定义了一个名为mulOfNumbers的整数变量,用来保存两个整数的乘积。

第4处:定义了一个名为difOfNumbers的整数变量,用来保存两个整数的差值。

第5处:定义了一个名为divOfNumbers的浮点型变量,用来保存两个整数相除的商,并将运算结果强制转换成浮点数。

第6处:定义了一个名为remOfNumbers的整数变量,用来保存两个整数相除的余数。

第7处:分别输出上述5种计算结果。

通过Ctrl+S组合键保存代码修改,回到Unity编辑器主界面。

在层级视图中勾选Main Camera复选框,然后在检视视图中点击Add Component按钮,在下拉列表中搜索刚才添加的Basic Calculator脚本,最后单击回车键以确定,如图3-3所示。

000

图3-3 添加Basic Calculator脚本

需要特别说明的是,Script脚本的文件名和Visual Studio中的类名必须完全一致,否则在这里是找不到对应的脚本的。如果因为误操作使脚本文件名和类名不一致,就需要手动在代码中更改类名,以保持一致。

单击工具栏上的“播放”控制按钮,即可在Console里面看到输出的结果,如图3-4所示。

000

图3-4 Console视图中输出的运算结果

2. 赋值运算符

在C#中,赋值运算符用于将一个数据赋予一个变量、属性或者引用。数据本身是常量、变量或者表达式。赋值运算符本身又分为简单赋值和复合赋值。其作用如表3-2所示。

表3-2 赋值运算符

000

【示例3-2】使用赋值运算符进行计算

回到BasicMath项目,打开MainScene场景,在Project视图中的Scripts子目录下创建一个新的C# script,将其命名为AssignmentCal。双击在编辑器中打开该脚本文件,并输入代码清单3-2所示的代码。

代码清单3-2 使用赋值运算符

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class AssignmentCal : MonoBehaviour
{
    // 初始化方法
    void Start()
    {
        //1.定义一个常数变量
        const int basicNumber = 10;
        //2.定义一个普通的整型变量
        int x = basicNumber;
 
        //3.输出不同赋值计算的结果
        Debug.Log("x=" + x);
        Debug.Log("x+=2的运算结果为:" + (x += 2));
 
        x = basicNumber;
        Debug.Log("x-=2的运算结果为:" + (x -= 2));
        x = basicNumber;
        Debug.Log("x*=2的运算结果为:" + (x *= 2));
        x = basicNumber;
        Debug.Log("x/=2的运算结果为:" + (x /= 2));
        x = basicNumber;
        Debug.Log("x%=2的运算结果为:" + (x %= 2));
        x = basicNumber;
        Debug.Log(“x >>= 2的运算结果为:" + (x >>= 2));
        x = basicNumber;
        Debug.Log("x<<=2的运算结果为:" + (x <<= 2));
        x = basicNumber;
        Debug.Log("x&=2的运算结果为:" + (x &= 2));
 
        x = basicNumber;
        Debug.Log("x|=2的运算结果为:" + (x |= 2));
        x = basicNumber;
        Debug.Log("x^=2的运算结果为:" + (x ^= 2));
 
    }
}

需要注意的是,我们仅仅修改了void Start(){}方法里花括号中的代码,其他地方的代码并没有做任何调整。

另外,需要特别注意的是,对于初学者来说,以上代码中所用到的双引号和分号必须采用半角输入,如果使用全角输入,则没办法得到想要的结果。

小练习

给以上代码添加完整的注释。从Hierarchy视图中选中Main Camera对象,取消对Numbers Operation组件的勾选,然后关联AssignmentCal组件,并点击Play按钮,在Console中查看计算结果。

注意

为了不影响查看计算结果,我们需要在测试运行之前手动切换到Console视图,点击Clear按钮以清除之前的显示内容。

3. 关系运算符

关系运算符用于比较两个值之间的关系,并在比较之后返回一个布尔类型的运算结果。常用的关系运算符如表3-3所示。

表3-3 关系运算符

000

【示例3-3】使用关系运算符

回到BasicMath项目,打开MainScene场景,在Project视图中的Scripts子目录下创建一个新的C# script,将其命名为RelationCal。双击在编辑器中打开该脚本文件,并输入代码清单3-3所示的代码。

代码清单3-3 使用关系运算符

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class RelationCal : MonoBehaviour {
 
  // 初始化方法
   
  void Start () {
      int firstNumber = 3;
      int secondNumber = 5;
 
      if (firstNumber == secondNumber) {
        Debug.Log ("The First number is equal to the second number.");
      }
      if (firstNumber < secondNumber) {
        Debug.Log ("The First number is smaller than the second number.");
      }
      if (firstNumber <= secondNumber) {
        Debug.Log ("The First number is smaller or equal to the second number.");
      }
      if (firstNumber > secondNumber) {
        Debug.Log ("The First number is bigger than the second number.");        
      }
      if (firstNumber >= secondNumber) {
        Debug.Log ("The First number is bigger or equal to the second number.");        
      }
      if (firstNumber != secondNumber) {
        Debug.Log ("The First number is not equal to the second number.");
      }
    }
}

以上代码比较简单,我们使用了逻辑判断,比较firstNumber和secondNumber的大小,并根据比较的结果输出不同的内容。

小练习

给以上代码添加完整的注释,解释各行代码的作用。从Hierarchy视图中选中Main Camera对象,取消对Numbers Operation组件和AssignmentCal的勾选,然后关联RelationCal组件,并点击Play按钮,在Console视图中查看计算结果。

4. 条件运算符

条件运算符用于进行逻辑判断,并返回一个布尔类型的运算结果。常用的条件运算符如表3-4所示。

表3-4 条件运算符

000

【示例3-4】使用条件运算符

回到BasicMath项目,打开MainScene场景,在Project视图中的Scripts子目录下创建一个新的C# script,将其命名为ConditionalCal。双击在编辑器中打开该脚本文件,并输入代码清单3-4所示的代码。

代码清单3-4 使用条件运算符

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class ConditionalCal : MonoBehaviour {
 
    // 初始化方法
    void Start () {
     //定义了两个布尔类型变量
    bool isPlayer1Dead = true;
    bool isPlayer2Dead = false;
    //定义了3个整型变量 
    int player1KilledLives = 1;
    int player1InitialLives = 5;
    int player1CurrentLives;
 
    //如果两个玩家角色都已死亡
    if (isPlayer1Dead && isPlayer2Dead) {
        Debug.Log ("所有玩家角色均已死亡");
        }
        //如果至少有一个玩家角色已经死亡
        if (isPlayer1Dead || isPlayer2Dead) {
                       Debug.Log ("至少有一个玩家角色已死亡");
        }
        //如果第一个玩家角色还没有死亡
        if(!isPlayer1Dead == true){
                       Debug.Log ("第一个玩家角色还没有死亡");
        }
        /*如果第一个玩家角色已经死亡,那么他当前的生命值等于初始生命值减1,如果玩家角色还没有
          死亡,那么他当前的生命值等于初始生命值*/
        player1CurrentLives = isPlayer1Dead ? (player1InitialLives - 
            player1KilledLives) : player1InitialLives;
        Debug.Log ("第一个玩家还剩下的重生机会是:" + player1CurrentLives);
    }
    
}

小练习

从Hierarchy视图中选中Main Camera对象,取消对Numbers Operation、AssignmentCal和RelationCal组件的勾选,然后关联ConditionalCal组件,并点击Play按钮,在Console中查看计算结果。

3.2.3 流程控制

C#中的流程控制方法和其他语言基本相同,支持if…else、while、do…while、for、foreach、switch这些流程控制语句。

所谓的流程控制,就是对某个条件进行判断,如果判断通过,就执行对应的语句。

1. if…else

if…else是最基本也是最常用的流程控制语句,表示在满足某种特定条件的情况下,执行某种操作,否则执行else后面的操作。例如:

int num1 = 3;
if(num1 == 3){
    Console.WriteLine("你好呀");  
}

在以上的代码中,我们首先定义了一个int类型的变量,值为3。随后判断num1的值是否等于3,如果判断成立,则执行花括号中的语句。执行num1 == 3得到的结果会是一个布尔类型的值。

在这里,num1 ==3结果成立,所以得到的值为true。如果我们想让判断更清晰,执行逻辑可以进一步优化,比如如果条件成立,就去做第一件事,否则就去做第二件事:

int num1 = 3;
if(num1 == 4){
    //如果判断成立,会执行该语句
    Console.WriteLine("你好呀");
} else {
    //如果判断不成立,会执行该语句
    Console.WriteLine("我不好");  
}

此时,num1 == 4的判断并不成立,所以会执行else之后花括号中的语句,也就是输出“我不好”。

2. while

while和if…else语句相似,表示当满足某个条件的情况下将执行某些操作。注意,while没有else部分。例如:

int num1 = 3;
while(num1 <= 4){
    Console.WriteLine("你好呀");
}

在以上代码中,我们首先定义了一个整型变量,然后将其和4进行比较,最后根据比较的结果来决定是否执行花括号中的代码。

通过if进行的判断,语句只会执行一次。而使用while后,如果结果为true,那么该语句会一直重复执行,直到判断不成立。所以这里的“你好呀”会执行无数次。

3. do…while

do…while和while的区别在于,do...while语句并不会先进行判定,而是会先执行一次括号中的语句,再进行判定,如果判定成立,再继续执行括号中的语句。例如:

int num1 = 3;
do{
    Console.WriteLine("你好呀");  
}while(num1 > 5);

在以上代码中,首先输出一次“你好呀”,然后再判断num1 > 5是否成立,如果成立,重复输出“你好呀”。

4. for

for循环的语法示例如下:

for(int i = 0; i <= 10 ; i++){
    Console.WriteLine("你好呀"):  
}    

该示例的执行顺序为:首先执行int i = 0,然后判断i≤10是否成立,如果成立则执行花括号中的语句,执行完花括号中的语句后,执行i++,然后再判断i≤10是否成立,如果不成立则跳出循环。

for(初始值; 判断语句; 值变化语句){}

也就是说,初始值语句只会执行一次,随后进行判断,如果判定成立,执行花括号中的语句,再执行值变化语句,然后执行判断,如果判断不成立,跳出循环。

5. foreach

foreach并不算严格意义上的流程控制语句,它的作用是依次遍历集合中的数据。例如:

//定义数组
int[] numbers = new int[10];
//遍历数组中的每一个元素
foreach(var num in numbers){
     Console.WriteLine(num);
}

该示例中,首先创建一个int数组,然后通过foreach遍历数组中的每一个元素。foreach会按顺序依次取出数组中的每一个元素。

foreach(类型 变量名 in 集合){}

上述示例中,var是变量类型,num为变量名,numbers为集合名。foreach支持对任何类型的集合进行遍历,不限于数组。foreach的性能开销很大,在Unity开发中应尽量避免使用。

3.2.4 函数

在数学领域,函数的作用是让输入值根据特定的规则计算出某个结果并输出。在编程领域,函数的作用与之类似,不过不局限于计算数值,而是可以实现任何所需要的功能。简单来说,函数就是可以完成特定功能且可以重复执行的代码块。

为了说明函数的作用,我们把3.2.2节中的所有代码重构,使用函数的方式来实现所需要的功能。

【示例3-5】使用函数重构3.2.2节中的所有运算

回到BasicMath项目,打开MainScene场景,在Project视图中的Scripts子目录下创建一个新的C# script,将其命名为FunctionCal。双击在编辑器中打开该脚本文件,并输入代码清单3-5所示的代码。

代码清单3-5 使用函数重构运算

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class FunctionCal : MonoBehaviour {
 
    //初始化方法
    void Start () {
 
        //分别调用每个函数
        AssignmentCal();
        MathCal ();
        ConditionalCal ();
        RelationCal ();
    }
 
    void AssignmentCal(){
        
        const int basicNumber = 10;
        int x = basicNumber;
 
        Debug.Log("x=" + x);
        Debug.Log("x+=2的运算结果为:" + (x += 2));
 
        x = basicNumber;
        Debug.Log("x-=2的运算结果为:" + (x -= 2));
 
        x = basicNumber;
        Debug.Log("x*=2的运算结果为:" + (x *= 2));
 
        x = basicNumber;
        Debug.Log("x/=2的运算结果为:" + (x /= 2));
 
        x = basicNumber;
        Debug.Log("x%=2的运算结果为:" + (x %= 2));
 
        x = basicNumber;
        Debug.Log("x>>=2的运算结果为:" + (x >>= 2));
 
        x = basicNumber;
        Debug.Log("x<<=2的运算结果为:" + (x <<= 2));
 
        x = basicNumber;
        Debug.Log("x&=2的运算结果为:" + (x &= 2));
 
        x = basicNumber;
        Debug.Log("x|=2的运算结果为:" + (x |= 2));
 
        x = basicNumber;
        Debug.Log("x^=2的运算结果为:" + (x ^= 2));
    }
 
    void ConditionalCal(){
 
        bool isPlayer1Dead = true;
        bool isPlayer2Dead = false;
 
        int player1KilledLives = 1;
        int player1InitialLives = 5;
        int player1CurrentLives;
 
        //如果两个玩家都已死亡
 
        if (isPlayer1Dead && isPlayer2Dead) {
            Debug.Log ("所有玩家均已死亡");
        }
        //如果至少有一个玩家已经死亡
        if (isPlayer1Dead || isPlayer2Dead) {
            Debug.Log ("至少有一个玩家已死亡");
        }
        //如果第一个玩家还没有死亡
        if(!isPlayer1Dead == true){
            Debug.Log ("第一个玩家还没有死亡");
        }
    /*如果第一个玩家已经死亡,那么他当前的生命值等于初始生命值减1,如果还没有死亡,那么他当前的
      生命值等于初始生命值*/
        player1CurrentLives = isPlayer1Dead ? (player1InitialLives - 
            player1KilledLives) : player1InitialLives;
 
        Debug.Log ("第一个玩家还剩下的重生机会是:" + player1CurrentLives);
    }
 
    void MathCal(){
        int firstNumber = 1;
        int secondNumber = 5;
 
        //求二者的和
        int sumOfNumbers = firstNumber + secondNumber;
        //求二者的积
        int mulOfNumbers = firstNumber * secondNumber;
        //求二者的差
        int difOfNumbers = firstNumber - secondNumber;
        //求二者的商
        double divOfNumbers = (double)firstNumber / secondNumber;
        //求二者的余数
        int remOfNumbers = firstNumber % secondNumber;
        //输出以上计算结果到Console中
        Debug.Log ("The sum of two numbers is: " + sumOfNumbers);
        Debug.Log ("The multiply of two numbers is: " + mulOfNumbers);
        Debug.Log ("The difference of two numbers is: " + difOfNumbers);
        Debug.Log ("The division of two numbers is: " + divOfNumbers);
        Debug.Log ("The remainder of two numbers is: " + remOfNumbers);
    }
 
    void RelationCal(){
        int firstNumber = 3;
        int secondNumber = 5;
        if (firstNumber == secondNumber) {
            Debug.Log ("The First number is equal to the second number.");
        }
        if (firstNumber < secondNumber) {
            Debug.Log ("The First number is smaller than the second number.");
        }
 
        if (firstNumber <= secondNumber) {
            Debug.Log ("The First number is smaller or equal to the second number.");
        }
        if (firstNumber > secondNumber) {
            Debug.Log ("The First number is bigger than the second number.");
        }
        if (firstNumber >= secondNumber) {
            Debug.Log ("The First number is bigger or equal to the second number.");
        }
        if (firstNumber != secondNumber) {
            Debug.Log ("The First number is not equal to the second number.");
        }
    }
}

在以上代码中,我们分别用4个不同的函数来替代之前想要实现的数学运算,然后调用每个函数,并输出结果。当然,严格来说这里的函数其实属于方法。关于函数和方法的区别,我们将在下一节进一步说明。

小练习

给以上代码添加完整的注释,然后将FunctionCal脚本关联到Main Camera对象,并取消之前对几个脚本组件的勾选,点击工具栏上的Play按钮,在Console视图中查看函数运行结果。

3.2.5 类、对象和方法

C#是一门面向对象的编程语言,类、对象和方法则是面向对象的语言中重要的概念。具有相同属性和功能的一组对象的集合就是一个类,比如人是一个类,猫是一个类。类的一个实体就是对象。类和对象的示意如图3-5所示。

000

图3-5 类和对象

至于方法,其形式和函数类似,也是为了实现某些特定功能的代码块。但是方法和函数的区别在于,方法通常是某个对象所特有的。换言之,函数是代码世界里独立的生命体,它不依赖于某个对象而存在。但方法不同,任何一个方法都是某个对象的附属生命,同时只有它的宿主对象才能调用这个方法。

这么说有点抽象,我们以实际的例子来说明。比如某个幻想类RPG游戏中有多种角色,如刺客、战士、法师、术士、弓箭手等。以弓箭手为例,他有一些基本的属性,比如昵称、生命值、法术值、法术攻击力、耐力、敏捷等。这些属性值通常是以变量的形式来保存的。与此同时,弓箭手具备很多技能,比如后羿射日、元气弹、时空穿梭等。而这些技能通常体现在特定的方法中。通过将这些变量和方法放在一起,我们就创造了一个弓箭手的类,这个过程称为封装。而该弓箭手的类代表了一个抽象的弓箭手。

在Unity中,每个脚本文件都对应一个对象。如果想要在游戏中初始化某个对象,需要将其添加到GameObject中。正如我们之前所看到的,Unity中的类以组件的形式附着在游戏对象上。每个组件都是一个对象,而多个组件共同组成了一个GameObject。关于这一点,下一节将进一步说明。

【示例3-6】创建并引用一个弓箭手类

回到刚才的BasicMath项目,在Project视图中的Scripts子目录下创建一个新的C# script,将其命名为ArcherClass。双击在代码编辑器中将其打开,并输入代码清单3-6所示代码。

代码清单3-6 创建弓箭手类

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class ArcherClass {
    //定义弓箭手的各种属性
 
    public string playerName;
    private int lifeLeft = 5;
    public int attackForce = 15;
    public int magicForce = 20;
    public float playerSpeed = 5.0f;
    public bool isPlayerDead = false;
    //定义弓箭手的各种技能
    //范围攻击
    public void AttackRange(){
        Debug.Log ("开始范围攻击");
    }
 
    //单体攻击
    public void AttackSingle(){
        Debug.Log ("开始单体攻击");
    }
 
    //打招呼
    public void SayHello(){
        Debug.Log ("我是弓箭手");
    }
 
    //显示弓箭手的状态
    public void ShowStatus(){
        Debug.Log ("弓箭手的生命值是: " + lifeLeft);
        Debug.Log ("弓箭手的移动速度是: " + playerSpeed);
    }
 
}

在Project视图中的Scripts子目录下创建一个新的C# script,并将其命名为Archer-Command。双击在代码编辑器中将其打开,并更改代码。更改后的代码如代码清单3-7所示。

代码清单3-7 调用弓箭手类

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class ArcherCommand : MonoBehaviour {
 
    // 初始化方法
    void Start () {
        ArcherClass myArcher = new ArcherClass();
        myArcher.SayHello ();
        myArcher.ShowStatus ();
        myArcher.playerSpeed = 10.0f;
        myArcher.ShowStatus ();
    }
    
}

在Hierarchy视图中选中Main Camera选项,然后在Inspector视图中单击Add Component按钮,最后从下拉列表框中依次选择Script→ArcherCommand选项,将ArcherCommand.cs脚本添加为Main Camera对象的组件,同时取消对其他代码组件的勾选。

点击工具栏上的“播放”按钮,就可以在Console面板中看到对应的输出结果了。

在以上代码中,ArcherClass.cs脚本文件中定义了弓箭手类,而在ArcherCommand.cs脚本文件中创建了一个新的弓箭手,并调用其SayHello()方法和ShowStatus()方法,然后访问了public类型的属性变量playerSpeed,更改其数值为10.0f,最后再次调用ShowStatus()方法。

需要注意的是,在C#中,类名需要和文件名保持一致,如果文件名为Archer.cs,那么类名应该为Archer。根据代码规范可知,类名和方法名的首字母都应该大写。此外,在Archer类的定义中,我们看到变量前面有一个修饰符public。实际上,C#中共有5种修饰符:public、private、protected、internal、protected internal。

如果不给属性指定访问修饰符,默认使用private。private类型的变量只有在类的内部才能够被访问。如果希望在外部能够直接对属性进行修改,那么需要指定变量为public类型。

比如在示例3-6中,我们在ArcherCommand类中对Archer对象的playerSpeed属性成功进行了修改。而使用private修饰符定义的变量lifeLeft则无法在ArcherCommand中被访问并修改。

Unity中有一些非常重要的类,其中最为重要的就是MonoBehaviour。它是所有Unity脚本的基类。关于Unity中这些重要类的详细信息,请参考Unity3D的官方文档[1]

通过对本节的学习,我们掌握了C#的基本语法和使用方法。在下一节中,我们将继续学习如何在Unity中进行C#脚本的开发。


[1] 参见https://docs.unity3d.com/Manual/ScriptingImportantClasses.html。