TypeScript是一种开源的编程语言,由微软开发和维护。它是JavaScript的一个超集,意味着任何合法的JavaScript代码也是合法的TypeScript代码。TypeScript通过添加静态类型检查和一些新特性来增强JavaScript的功能。
它在JavaScript的基础上添加了类型系统,使得TypeScript具备了静态语言的特点。
TypeScript具有以下特点:
-
静态类型检查:TypeScript引入了静态类型检查,可以在代码编译阶段发现潜在的错误,提供更好的代码可靠性和可维护性。
-
类和接口:TypeScript支持类和接口的定义,使代码结构更加清晰和可扩展。
-
泛型:TypeScript支持泛型,使代码可以适用于多种数据类型,提高代码的复用性。
-
模块化:TypeScript支持模块化的开发方式,可以将代码分割成多个模块,提高代码的可维护性。
-
兼容性:TypeScript可以兼容现有的JavaScript代码,可以直接使用现有的JavaScript库和框架。
-
工具支持:TypeScript具有完善的工具链,包括代码编辑器、调试器和构建工具等,提供更好的开发体验。
总而言之,TypeScript可以提供更好的代码可靠性和可维护性,使JavaScript开发更加高效和舒适。它广泛应用于大型项目的开发,也被许多开发者用于小规模项目的开发。
变量的声明
在TypeScript中,可以使用关键字let来声明一个变量,并通过在变量名之后添加冒号“:”来指定变量的类型(let变量名:类型=值):
let hp: number = 100; //基础血量
上面的代码通过let关键字定义了一个名为hp的数值类型变量,并将它的值设置为100。
代码中的“//”表示单行注释,“//”后面的文字是注释的内容。TypeScript中的基础类型,除了上面使用到的数值类型(number),还有字符串类型(string)和布尔类型(boolean),如下所示:
let skill_name: string = '技能名'; // 技能名字
let is_cd: boolean = false; // 技能冷却状态
在定义变量时也可以不进行显式的类型指定,此时的变量就会将初始定义时赋值的变量类型作为默认类型,如下所示:
let skill_name = '技能名'; // string
let is_cd = false; // boolean
这里需要注意的是,在指定了变量类型之后,如果对不同类型的值进行赋值,将会报错。
在阅读的过程中,您可以安装cocos creator,通过console.log输出语句来对代码进行测试。将start函数中的代码块进行如下修改,再次预览运行,查看输出结果。
start() {
//zachen
console.info('Hello world');
let hp: number = 100; // 基础血量
let skill_name: string = '烈焰斩'; // 技能名字
let is_cd: boolean = false; // 技能冷却状态
console.log(hp);
console.log (skill_name);
console.log(is_cd);
}
条件语句
使用条件语句可以基于不同的条件(true或false)执行对应的游戏逻辑,如下图所示:
(1)if语句。只有当指定条件为true时,if语句内的代码块才会被执行。
在执行上面的代码时,我们会发现只输出了一次【使用必杀技】,并没有输出【再次使用必杀技】。这是因为在第一次执行if判断的时候,mp满足了超过5点的条件,在第一个if语句块的代码被执行完成后mp被清空,在执行第二个if语句块时因为mp不足,所以if语句块内的代码块并没有被执行。
(2)if…else语句。if语句后面可以跟一个可选的else语句,else语句在布尔表达式为false时执行。
在执行上面的代码时,当前的mp不满足超过5点的条件,此时会输出【法力不足,使用普通攻击】。
(3)if…else if…else语句。在else之后可以跟一个新的判断分支,当使用该语句时会从多个代码块中选择一个来执行。
在执行上面的代码时,因为当前的mp不满足超过5点的条件,所以会继续执行下一个if语句,当mp满足使用烈焰斩的条件时,会输出【使用烈焰斩】。
switch语句
使用switch语句可以基于多个不同的条件执行对应的游戏逻辑。switch语句会将变量依次对比各个case的值,当匹配到对应的case时,会执行对应的case语句的代码块。当所有的case均匹配不到时,将会执行default代码块中的内容。
在执行上面的代码时,因为job的值匹配到了第三个case中的“法师”,所以程序运行后输出了【火球术】。
这里需要注意的是,当case的值匹配到变量后,与该case关联的代码块会被执行,因此在每个case执行结束后,还需要使用break来阻止程序自动执行下一个case的代码块。
循环基础
在编写代码的过程中,我们可能会遇到需要多次执行相同代码块的情况。假设我们需要实现一个5连击技能,每次攻击都会造成3点伤害,并在每一击之后都输出敌人当前的剩余血量。在不使用循环的情况下,我们需要把两行代码连续、重复地编写5次,如下所示。
虽然我们可以通过复制、粘贴的方式简化重复编写的过程,但是这种方式并不利于对代码的维护。比较推荐的方式是合理地使用for循环。for循环可以将重复的代码块移到循环逻辑中,从而简化代码,语法如下所示。
其中会首先执行init,且只会执行一次。init常用于初始化循环变量。condition是循环条件,在每次执行代码块之前都会进行一次判断,当条件为true时会执行循环中的代码块,为false时则停止执行循环。在每次执行完成循环中的代码块后,会执行一次increment语句。
现在我们尝试用for循环改写最初的代码,如下所示:
在上面的代码中,“let i:number=0”表示循环语句声明了变量i,并将其初始化为0。
“i<5”是循环的条件,只有在变量i的值小于5的情况下才会执行循环,否则将退出循环。
“i++”会在每次循环执行完成后对i的值进行+1,以此来保证循环可以达到退出条件。
除了for循环,我们也可以使用while循环。
while循环会一直执行循环语句的代码块,直到条件为false时,才会结束循环。
我们尝试将for循环改写为while循环,如下所示。
数组
假设现在有一个需求,需要将游戏中所有的职业存储起来,在不使用数组的情况下,我们需要定义多个变量来存储所有的职业。这里我们定义变量job_num来存储当前的职业的数量,并以“job_职业编号”的格式定义多个职业的变量名,如下所示:
目前只有3个职业,通过定义多个变量的方式来存储职业似乎没有什么问题,但是当游戏需要进行扩展时,添加新职业的操作就会变得特别烦琐。
假设此时添加了2个新职业:召唤师和圣骑士,代码如下所示:
仔细观察上面的代码,你有没有发现什么问题?相信细心的你已经发现了,在添加了新的职业后,我们忘记将job_num变量的值从3修改成5了,这将会导致游戏在运行的过程中出现不可预知的bug:
定义多个变量来存储一系列的职业的方式显然是低效且容易出问题的。此时可以选择定义一个名为jobs的数组,并用其将所有的职业进行存储。定义数组的方式和定义变量的方式类似(let数组名:类型[]=[值1,值2,值3...])。修改为数组之后,代码如下所示。
数组中的每个元素都对应一个编号,我们把这个编号称为下标。下标从0开始依次递增,通过jobs[下标]的方式可以获取对应的变量值。此外,我们也可以通过jobs.length获取当前数组中的元素数量。通过使用数组我们可以很好地管理需要存储的一系列变量,当需要继续添加新的职业时,只需要向数组中插入新的值即可。
值得一提的是,我们也可以通过for/of循环语句方便地遍历数组中的对象,代码如下所示。
对象
与数组类似,对象的作用之一也是更方便地组织和存储多个变量。对象是包含一组键-值对的实例(let对象名={键1:值1,键2:值2…}),对象的值可以是变量、函数、数组、对象等。
函数
如果说变量的作用是存储数据,那么函数就可以被理解为用于存储代码逻辑的特殊变量。我们可以把多行执行语句存储到一个函数中,在需要使用对应的语句时,只要调用相应的函数即可。
通过关键字function来定义一个函数,并使用花括号来包含函数的代码块,语法如下。
当函数有返回值时,需要在声明函数的时候指定返回值的类型,如下所示。
在函数定义的时候,也可以指定需要传递的参数,如下所示。
类
类用于定义事物的抽象特点。从代码组织的角度观察会发现,类和对象“长得非常像”,类同样描述了所创建的对象的共同的属性和方法。TypeScript类的定义方式如下。
观察之前在Cocos Creator中自动生成的脚本会发现,我们之前都是在Game类中编写逻辑代码的。这里需要注意的是,在资源管理器中创建的脚本,它的类名默认与脚本名保持一致。假设你创建了一个名为Hero的脚本,那么Cocos Creator就会默认生成一个Hero类,如下所示。
在默认生成的代码中,被注释掉的dummy和serializableDummy是类的成员属性,start和update是类的成员函数。
脚本组件基础
我们已经成功地在Cocos Creator中创建并运行了脚本,也学习了TypeScript的基础语法,现在让我们回过头来学习脚本组件的基础知识。
组件类
所有继承自Component的类都被称为组件类,其对象被称为组件。我们可以将组件挂载到场景中的节点上,用于控制节点的行为。需要注意的是,如果没有使用@ccclass将组件类声明成cc类,则无法将组件添加到节点上。
cc类
将装饰器ccclass应用在类上时,此类称为cc类。cc类注入了额外的信息以控制Cocos Creator编辑器对该类的序列化和场景编辑器对该类的展示等。因此,未声明ccclass的组件类无法作为组件被添加到节点上。
装饰器ccclass的参数name指定了cc类的名称,cc类名是独一无二的,这意味着同类名在不同目录下也是不被允许的。当需要获取相应的cc类时,可以通过cc类名来查找。
属性装饰器
属性装饰器property可以被应用在cc类的属性或访问器上,并用于控制Cocos Creator编辑器中cc类属性的序列化,以及该属性在属性检查器上的显示等。
这里需要注意的是,属性装饰器property的选项type会指定属性的cc类,若未指定,则Cocos Creator将从属性的默认值中推导其类型,完整的可选择参数可以参考Cocos Creator的官方文档。
我们可以将Game脚本进行修改,分别定义job变量和hp变量,同时在start函数中通过“this.变量名”的方式访问并输出成员变量的值,代码如下所示。
以普通方式声明的属性不能在编辑器中被访问,只有使用了属性装饰器property修饰的属性才能在编辑器中被访问。因此,在Node节点的属性检查器中,虽然我们可以查看并编辑在脚本中定义的【hp】属性,却看不到同时定义的【job】属性,如下图所示。
在编辑器中尝试将【hp】的值修改为【50】,再次进行预览运行,我们会发现输出结果已经变成了【50】,如下图所示。
与其他节点及组件交互
在游戏开发的过程中,脚本通常需要与多个节点进行交互。假设现在有一个需求,需要将Label节点的string属性进行动态调整,使默认的【Hello World】文本在游戏运行后变为【Game Start】。此时我们就需要让Game脚本与Label节点进行交互,代码修改如下所示。
在上面的代码中,我们定义了label变量并将其默认值设置为null,同时将它的type声明为Label组件。这里需要注意的是,为了能在脚本中正常使用Label类,还需要在脚本的头部对该类进行导入。
在通常情况下,使用VS Code编写代码时,编辑器会对代码进行实时检测,当发现我们使用了未导入的内置组件类时,该类将会被VS Code编辑器自动导入。如果编辑器未能正确检测到类的引用,那么也可以手动将用到的类进行导入,如下图所示。
与此同时,我们还需要在属性检查器中将Label节点与Game脚本进行绑定,否则在预览运行时会报错。回到Cocos Creator编辑器,将层级管理器中的Label节点拖动到Game脚本组件中的Label属性上,从而完成对组件的绑定,如下图所示。
完成拖动绑定后预览运行,浏览器中显示的文字将会从【Hello World】变成【Game Start】。
脚本的生命周期
何为生命周期?从字面上可以理解成事物从出生到死亡的过程,而对应到脚本中指的就是从创建到销毁的整个过程。在Cocos Creator中,每个脚本组件都有自己的生命周期。引擎为脚本组件提供了生命周期的回调函数,我们只需要在cc类中定义对应的函数即可。当执行到相应的周期时,就会调用该函数。
何为生命周期?从字面上可以理解成事物从出生到死亡的过程,而对应到脚本中指的就是从创建到销毁的整个过程。
在Cocos Creator中,每个脚本组件都有自己的生命周期。引擎为脚本组件提供了生命周期的回调函数,我们只需要在cc类中定义对应的函数即可。当执行到相应的周期时,就会调用该函数。
脚本组件的生命周期回调函数包括:onLoad、onEnable、start、update、lateUpdate、onDisable、onDestroy。脚本组件的生命周期如下图所示。
1.onLoad
onLoad回调函数会在脚本组件的初始化阶段被调用,比如所在的场景被载入或者所在的节点被激活。在onLoad阶段,可以获取场景中的其他节点,以及与节点关联的资源数据。onLoad总会在调用start方法前被执行,可以用于设置脚本的初始化顺序。通常我们会在onLoad阶段做一些与初始化相关的操作。
2.onEnable
当组件的enabled属性从false变为true时,或者所在节点的active属性从false变为true时,都会激活onEnable回调函数。如果节点第一次被创建且enabled属性为true,则会在onLoad之后、start之前调用onEnable回调函数。
3.start
start回调函数会在第一次激活组件之前,也就是第一次执行update之前被触发。start通常用于初始化一些中间状态的数据,这些数据可能会在update时发生改变,并且被频繁地进行enable和disable操作。
4.update
update回调函数会在游戏每一帧渲染之前被触发。游戏开发的一个关键点是在每一帧渲染前更新物体的行为、状态和方位。这些更新操作通常都被放在update回调函数中。
5.lateUpdate
lateUpdate回调函数会在游戏每一帧渲染之后被触发。update会在所有的动画更新之前被执行,但如果我们要在动效(如动画、粒子、物理等)更新之后进行额外的操作,或者希望在执行完成所有组件的update之后进行其他操作,就需要用到lateUpdate回调函数。
6.onDisable
当组件的enabled属性从true变为false时,或者所在节点的active属性从true变为false时,会激活onDisable回调函数。
7.onDestroy
当组件或者所在节点调用了destroy函数时,回调函数onDestroy也会被调用,并在当前帧结束时统一回收组件。
结论
现在,咱们了解了TypeScript的基础知识,也掌握了Cocos Creator脚本组件的创建与使用。
同时,我们还在项目中创建了第一个脚本组件,学习了如何让脚本组件与其他组件进行交互,并通过脚本组件对默认的Label文本进行了修改。最后我们还学到了脚本组件的生命周期的基础知识。
学好脚本编程语言是构建AI虚拟世界的条件之一,小伙伴们让我们加油!