第1章 Unity中的C语言.docx
《第1章 Unity中的C语言.docx》由会员分享,可在线阅读,更多相关《第1章 Unity中的C语言.docx(49页珍藏版)》请在冰豆网上搜索。
第1章Unity中的C语言
第1章Unity中的C#语言
本书阐述Unity的脚本设计,因而读者需要了解Unity游戏开发环境下的C#语言。
在进一步阅读之前,读者有必要明晰相关概念,进而可在理论基础上掌握脚本设计这一高级内容,此类内容多具有衔接性和实践性特征。
关于衔接性,任何一种程序设计语言均会强调语法及其编程规则,这也是一种语言的正式内容之一,其中涉及变量、循环以及函数。
随着程序员经验的不断增加,其关注点逐渐从语言本身转向对实际问题的处理,即由语言自身内容转向特定环境下的语言应用。
因此,本书并非是一本C#语法书籍。
在结束本章的学习后,相信读者已经掌握了C#语言的基本内容,后续章节将运用C#语言处理相关案例以及实际问题,这也是本书的特点之一,并覆盖了C#语言的全部功能项,以使读者更好地理解相关操作结果。
无论经验如何,这里建议读者逐章阅读,对于期望解决复杂问题的C#语言新手而言尤其如此。
对于经验丰富的开发人员,本书则可强化其现有的知识,并在学习过程中提供新的建议和理念。
本章将采用循序渐进的方式,从头开始阐述C#语言的基础内容。
另外,如果读者熟悉另一门语言的编程知识,且尚未接触过C#语言,现在则是学习该语言的良好时机。
1.1为何选择C#语言
当提及Unity脚本设计时,面临的一个问题则是选取哪一种语言,Unity对此提供了解决方案。
相应地,官方选取方案则是C#和JavaScript语言。
然而,考虑到基于Unity的特定应用,JavaScript应称作JavaScript或是UnityScript尚存争论,但其中原因并非是本书讨论的重点。
当前问题是项目所选取的设计语言。
作为一种方案,可在项目中选择两种语言,同时在其中分别编写脚本文件,并对这两种语言进行混合。
当然,这在技术上是可行的,Unity对此并未加以限制,但这会导致混淆以及编译冲突,就像尝试同时以英里和千米为单位计算距离。
因此,这里建议采用一种语言,并在项目中作为主语言加以使用。
本书则选用了C#语言,其原因在于:
首先C#语言并非优于其他语言,根据个人观点,此处并不存在绝对意义上的优劣性,每种语言均包含各自的优点和应用场合;同时,所有Unity语言均可用于游戏制作。
这里选择C#语言的主要因素在于其应用的广泛性,以及对Unity的支持。
针对Unity,C#语言可最大限度地与开发人员现有的知识体系结构相结合。
大多数Unity教程均采用C#语言编写,同时也常见于其他应用开发领域中。
C#语言的历史可追溯至.NET框架,后者也可用于Unity中(称作Mono)。
另外,C#语言也借鉴了C++语言的内容。
在游戏开发中,C++则是一类主要的开发语言。
通过学习C#程序设计语言,读者可向当今游戏界的Unity程序开发人员看齐。
因此,本书选用了C#语言,进而扩大其应用范围,在现有教程以及资源的基础上,最大限度地发挥读者的知识水平。
1.2创建脚本文件
当定义游戏的逻辑或行为时,用户需要编辑相应的脚本文件。
Unity中的脚本机制始于新文件的创建,即添加至项目中的标准文本文件。
该文件定义了一个程序,并列出了Unity需要理解的全部指令。
如前所述,对应指令可通过C#、JavaScript或Boo语言编写,而本书则选用了C#语言。
在Unity中,存在多种方式可创建脚本文件。
其中,一种方法是从应用菜单中选择Assets|Create|C#Script命令,如图1-1所示。
图1-1
另一种方法则是右击Project面板中的空白区域,并在快捷菜单中选择Create1的C#Script命令,如图1-2所示。
这将在当前开启的文件夹中创建数据资源。
图1-2
当创建完毕后,新的脚本文件将位于Project文件夹内,且包含.cs扩展名(表示C#文件)。
该文件名十分重要,并对脚本文件的有效性产生重要影响——Unity使用该文件名确定创建于该文件内的C#类的名称。
本章稍后将对类加以深入讨论。
简而言之,用户应确保文件包含唯一且具有实际意义的名称。
关于唯一性,其含义是指项目中的其他文件名不应与此相同,无论该文件是否位于不同的文件夹内。
也就是说,全部脚本文件在项目中应具有唯一的名称。
另外,文件名应具有实际意义,并表达脚本行将执行的任务。
进一步讲,C#语言中存在多种有效规则可对文件名和类名予以限定。
关于此类规则的正式定义,读者可访问.com/en-us/library/aa664670%28VS.71%29.aspx。
简单地讲,文件名应始于字母或下划线字符(不允许采用数字作为首字符);同时,文件名不应包含空格。
对应示例如图1-3所示。
图1-3
Unity脚本文件可在任意文本编辑器或IDE中打开,包括VisualStudio和Notepad++,但Unity提供了免费的开源编辑器MonoDevelop。
该软件为主Unity包中的部分内容,并包含于安装过程中,因而无须单独下载。
当在Project面板中双击脚本文件时,Unity将在MonoDevelop内自动打开文件。
如果在后续操作中决定更改脚本文件名,则还需要在文件内修改C#类的名称,以使其与文件名准确匹配,如图1-4所示。
否则,这将生成无效代码以及编译错误,并在脚本文件与对象绑定时出现问题。
图1-4
当在Unity中编译代码时,需要在MonoDevelop中保存脚本文件,即选择应用菜单中File命令中的Save选项(或者按Ctrl+S快捷键),并于随后返回至UnityEditor中。
当Unity窗口再次处于焦点状态时,Unity可自动检测到文件中的变化,进而对代码进行编译。
如果存在错误,则游戏将无法正常运行,对应的错误信息将显示于Console窗口中。
若编译成功,单击Editor工具栏上的Paly按钮即可。
需要注意的是,如果在修改代码后未保存文件,Unity将使用之前的编译版本运行程序。
针对这一原因以及备份要求,建议用户定期保存文件(按Ctrl+S快捷键,将结果保存至MonoDevelop中)。
1.3脚本的实例化操作
Unity中的各个脚本文件定义了一个主类,这类似于设计蓝图,并可对其进行实例化操作。
该类可视为相关变量、函数以及事件的集合(稍后将对此进行分析)。
默认状态下,脚本文件类似于其他任意一种Unity数据资源,例如网格和音频文件。
特别地,脚本文件通常处于静止状态,且不执行任何操作,直至添加至某一特定的场景中(作为组件添加至某一对象中),并在运行期内处于活动状态。
当前,作为与网格类似的独立对象,包含逻辑和数学内容的脚本尚未添加至场景中,鉴于此类对象不具备视觉和音频特征,因而用户尚无法对其直接感受。
当作为组件加入至现有游戏对象中时,脚本定义了此类对象的相应行为。
针对特定对象上的组件,脚本的激活过程称作实例化。
当然,独立脚本可在多个对象上进行实例化操作,并针对全部对象复制某一行为,且无须针对各个对象制订多个脚本。
例如,多个敌方角色可采用相同的人工智能逻辑。
理想状态下,脚本核心内容可视为对象的抽象规则或行为模式,并可在可行方案中的多个相似对象间重复使用。
当向某一对象中添加脚本文件时,可从场景目标对象上的Project面板中简单地拖曳脚本。
该脚本将作为一个组件进行实例化,当该对象被选取时,其公有变量在ObjectInspector中处于可见状态,如图1-5所示。
图1-5
1.4节将对变量进行讨论。
关于Unity中脚本的创建和应用,读者可访问。
1.4变量
变量可视为C#以及其他程序设计语言中的核心概念。
变量通常对应于多个字母,并代表了某一数值量,例如X,Y,Z以及a,b,c。
如果用户需要跟踪某种信息,例如玩家的名字、得分、位置、方向、弹药量、健康值,以及其他多种可量化的数据(通过名词表示),则可通过变量体现这一类信息。
变量代表单一的信息单位,这也意味着,多个变量需要包含多个单位且一一对应。
进一步讲,各个单位表示为某一特定类型。
具体而言,玩家的名字表示为字母序列,例如"John"、"Tom"或"David"。
相比较而言,玩家的健康值则采用数值数据,例如100%
(1)或50%(0.5),这取决于玩家所受到的伤害程度。
因此,各个变量均需要包含一种数据类型。
在C#语言中,变量通过特定的语法加以定义,如示例代码1-1所示。
示例代码1-1
01usingUnityEngine;
02usingctions;
03
04publicclassMyNewScript:
MonoBehaviour
05{
06publicstringPlayerName="";
07publicintPlayerHealth=100;
08publicVector3Position=;
09
10//Usethisforinitialization
11voidStart(){
12
13}
14
15//Updateiscalledonceperframe
16voidUpdate(){
17
18}
19}
各个变量包含某一数据类型,较为常见的类型包括int、float、bool、string以及Vector3。
对应示例如下所示:
❑int(整数)=–3,–2,–1,0,1,2,3,…。
❑float(浮点数或小数)=–3.0,–2.5,0.0,1.7,3.9,…。
❑bool(布尔值或true/false)=true或false(1或0)。
❑string(字符串)="helloworld","a","anotherword…"。
❑Vector3(位置值)=(0,0,0),(10,5,0)…。
在示例代码1-1的06~08行中,各个变量赋予了一个初始值,其数据类型显式地标记为string、int(整型)和Vector3(表示为3D空间内的一点或者方向)。
此处并未列出完整的数据类型列表,其内容一般处于变化中,并取决于具体项目(用户也可定义自己的数据类型)。
本书将通过大量的示例展示常见的类型。
另外,各个变量声明始于关键字public。
通常情况下,变量可声明为public或private(以及protected,示例代码中未予显示)。
其中,public变量可在Unity的ObjectInspector中进行访问和编辑(稍后将对此加以解释,读者也可参考图1-5),同时还可通过其他类进行访问。
变量值可在一段时间内发生变化,当然,这种变化也应符合相应的规则。
一类显式的调整方法是,可在ObjectInspector中通过代码对其直接赋值;或者通过方法或函数调用。
除此之外,变量还可采用直接或间接方式赋值。
变量的直接赋值方式如下所示:
PlayerName="NewName";
另外,还可采取表达式实现间接赋值,也就是说,在执行赋值操作前需要对结果值进行计算,如下所示:
//Variablewillresultto50,because:
100x0.5=50
PlayerHealth=100*0.5;
各个变量的声明均包含了隐式范围,该范围用于确定变量的生命周期——在当前文件中,变量可引用和访问的位置。
作用域通过变量的声明位置予以确定。
示例代码1-1中声明的变量包含了类作用域,对应变量声明于类上方且位于函数之外。
也就是说,变量可在类中任意位置进行访问;同时,作为public变量,还可被其他类访问。
除此之外,变量还可声明于特定函数中,即局部变量,其作用域限定于该函数中。
相应地,局部变量无法在函数外部进行访问。
本章后续内容还将对类和函数进行讨论。
关于C#语言中变量及其应用的更多信息,读者可访问。
1.5条件语句
变量可在多种不同的环境下进行修改:
当玩家改变其位置时、当敌方角色被摧毁时、当关卡被调整后等。
因此,用户需要经常检测变量值,并依据该值控制脚本的执行流程,进而实现不同的行为操作集。
例如,如果PlayerHealth到达0%,则需要执行死亡操作序列;如果PlayerHealth为20%,则可显示一条警告消息。
在该示例中,变量PlayerHealth负责按照某一方向驱动脚本。
对此,C#语言提供了两种主要的条件语句,进而实现程序的分支操作,即if语句和switch语句。
1.5.1if语句
if语句包含了多种形式,其最为基本的形式负责检测某一条件,当且仅当该条件为true时,将执行某一代码块,如示例代码1-2所示。
示例代码1-2
01usingUnityEngine;
02usingctions;
03
04publicclassMyScriptFile:
MonoBehaviour
05{
06publicstringPlayerName="";
07publicintPlayerHealth=100;
08publicVector3Position=;
09
10//Usethisforinitialization
11voidStart(){
12}
13
14//Updateiscalledonceperframe
15voidUpdate()
16{
17//Checkplayerhealth-thebracessymbol{}areoption
forone-lineif-statements
18if(PlayerHealth==100)
19{
20("Playerhasfullhealth");
21}
22}
23}
上述代码的执行过程与Unity中的其他代码类型并无两样——针对活动场景中的某一对象,当脚本文件实例化后,可单击工具栏中的Play按钮。
其中,第18行的if语句针对当前值检测PlayerHealth类变量。
如果变量PlayerHealth等于(==)100,则执行{}中的代码(第19~21行)。
对应的工作流程可描述为:
全部条件结果表示为布尔值true或false,经检测后可查看对应结果是否为true(PlayerHealth==100)。
实际上,花括号中可包含大量的内容,而此处仅涉及一项功能,即Unity中的函数向控制台输出“Playerhasfullhealth”信息(第20行代码),如图1-6所示。
当然,if语句还可包含其他分支,例如,如果PlayerHealth值不等于100(99或101),则代码将不会输出任何信息。
对应的执行流程通常取决于计算结果为true的上一条if语句。
图1-6
关于C#语言中if语句、if-else语句应用的更多信息,读者可访问.com/en-GB/library/。
在图1-6中,Unity中的调试工具为控制台,并可通过语句(或Print函数)于此处输出消息,以供开发人员进行查看。
这对于诊断运行期或编译期内的问题十分有效。
针对编译期或运行期错误,相关信息显示于Console选项卡中。
默认状态下,Console选项卡于UnityEditor中处于可见状态;另外,也可采用手动方式显示该选项卡,即在Unity应用菜单中,选择Window菜单中的Console选项。
关于函数的更多信息,读者可访问。
除了相等条件(==)之外,用户还可对其他条件进行检测。
例如,可使用>或<操作符检测某一变量大于或小于另一个值。
另外,还可通过!
=操作符检测变量与另一个数值之间的不等关系。
进一步而言,通过&&(AND)或者||(OR)操作符,还可将多个条件检测组合至同一条if语句中。
在下列示例代码中,仅当PlayerHealth变量位于0和100之间,且不等于50时,方执行{}中的代码块。
if(PlayerHealth>=0&&PlayerHealth<=100&&PlayerHealth!
=50)
{
("Playerhasfullhealth");
}
if-else语句可视为if语句的变化版本。
如果对应条件计算为true,则执行if语句,而if-else语句则对此进行了扩展。
如果条件为true,则执行X代码块;若条件为false,则会执行Y代码块,如下所示:
if(MyCondition)
{
//X-performmycodeifMyConditionistrue
}
else
{
//Y–performmycodeifMyConditionisfalse
}
1.5.2switch语句
如前所述,if语句可用于确定某一条件为true或false,并在此基础上执行特定的代码块。
相比较之下,switch语句将对多种可能的条件或状态检测变量,并依照某一个方向选取程序分支。
例如,如果创建处于某一行为状态下的角色(CHASE、FLEE、FIGHT、HIDE等),则需要根据代码分支处理相应的状态。
其中,关键字break用于从某一状态中退出,并返回至switch语句的结束处。
示例代码1-3采用枚举值对敌方角色进行处理。
示例代码1-3
01usingUnityEngine;
02usingctions;
03
04publicclassMyScriptFile:
MonoBehaviour
05{
06//Definepossiblestatesforenemyusinganenum
07publicenumEnemyState{CHASE,FLEE,FIGHT,HIDE};
08
09//Thecurrentstateofenemy
10publicEnemyStateActiveState=;
11
12//Usethisforinitialization
13voidStart(){
14}
15
16//Updateiscalledonceperframe
17voidUpdate()
18{
19//ChecktheActiveStatevariable
20switch(ActiveState)
21{
22case:
23{
24//Performfightcodehere
25("Enteredfightstate");
26}
27break;
28
29
30case:
31case:
32{
33//Fleeandhideperformsthesamebehaviour
34("Enteredfleeorhidestate");
35}
36break;
37
38default:
39{
40//Defaultcasewhenallotherstatesfail
41//Thisisusedforthechasestate
42("Enteredchasestate");
43}
44break;
45}
46}
47}
示例代码1-3中的第07行声明了一个名为EnemyState的结构。
枚举表示为一类特殊结构,用于存储一个或多个变量的多种可能值,其自身并非是变量,但可用于确定变量值的限定范围。
在示例代码1-3中,声明于第10行的ActiveState变量使用了EnemyState,其值可以是源自ActiveState枚举的任意值。
枚举机制可在特定范围内或一系列选项中对数据值进行限制。
枚举结构的另一个优点是,基于该结构的变量,可令其值显示于ObjectInspector内下拉列表的可选项中,如图1-7所示。
图1-7
关于C#语言中的枚举和应用,读者可访问。
下列内容显示了示例代码中的某些解释信息。
❑第20行代码:
开始执行switch语句。
()中的内容用于选择变量,其值或状态将被检测。
在当前示例中,变量ActiveState将被检测。
❑第22行代码:
首个case语句位于switch语句内,如果变量ActiveState设置为,则执行后续代码块(第24、25行代码);否则代码将被忽略。
❑第30、31行代码:
此处包含了两个case语句。
当且仅当ActiveState表示为或时,将执行第33、34行中的代码块。
❑第38行代码:
default表示为switch语句的可选项。
如果不存在为true的case语句,则执行该部分内容。
在当前示例中,如果ActiveState为,则执行default部分中的代码。
❑第27、36和44行代码:
break语句应位于case语句的结尾处。
当到达break语句时,将退出其所属的switch语句,并继续执行switch语句之后的内容,在当前示例中为第45行代码。
关于C#语言中switch语句及其应用,读者可访问。
1.6数组
表和序列在游戏中随处可见。
对此,用户可能需要经常跟踪同一类型的数据表,例如关卡中的全部敌方角色、收集的全部武器装备、采集的全部能量棒以及存储的全部法术和装备等。
数组可用于表示同一类型的表,其中的各项内容表示为一个信息单位,并可在游戏体验过程中发生变化。
数组可将全部相关变量(所有的敌方角色、全部武器装备等)收集于某一独立、线性、可遍历的表结构中,这也是数组的用武之地。
在C#语言中,存在两种数组结构,即静态数组和动态数组。
其中,静态数组可在内存中加载固定数量的数据项,对应尺寸需要事先予以确定,即使实际数据项的数量小于数组的尺寸。
这也意味着,数组中的某些位置将被浪费。
动态数组可根据具体要求增加或减少其大小,并与所需的数据项数量实现准确的匹配。
相比较而言,静态数组执行速度较快,而动态数组则可避免内存空间的浪费。
本章仅考察静态数组(后续内容将对动态数组予以分析),如示例代码1-4所示。
示例代码1-4
01usingUnityEngine;
02usingctions;
03
04publicclassMyScriptFile:
MonoBehaviour
05{
06//Arrayofgameobjectsinthescene
07publicGameObject[]MyObjects;
08
09//Usethisforinitialization
10voidStart()
11{
12}
13
14//Updateiscalledonceperframe
15voidUpdate()
16{
17}
18}
在示例代码1-4中,第07行代码声明了一个名为MyObjects的空GameObject数组。
当对此进行创建时,代码在GameObject数据类型后使用了[]语法,进而指定了一个数组,并表明声明了一个GameObject表,而非单一的GameObject。
这里,声明后的数组表示为场景中所有对象的列表。
初始状态下,数组为空,但用户可通过UnityEditor中的ObjectInspector,并采用手动方式构建数组,即设置最大尺寸并将所需对象置于其中。
对此,可选取场景中脚本所绑定的对象,针对MyObjects字段输入Size值,进而确定该数组的尺寸,即需要加载的全部对象数量。
随后,可将对象从场景层次结构面板中简单地拖曳至ObjectInspector中的数组中,进而通过数据项设置列表,如图1-8所示。
图1-8
除此之外,