脚本
阅读本文大概需要 5 分钟。
当创建了一个游戏脚本后,首先要了解游戏脚本的生命周期方法执行顺序,这样我们才能让游戏逻辑在正确的时间点触发。
更多脚本生命周期见产品文档:脚本的生命周期
1. 创建并挂载脚本
在编辑器主界面的下方区域,单击列表中的 “脚本” 选项,然后单击 “新建脚本” 按钮,就可以创建出新的脚本,如图:
- 点击 “新建脚本” 后,会新增一个脚本文件,同时处于可改名状态,此时输入文件名如
PlayerControl
,按下回车即可确定命名
注意
如果没有通过 “新建脚本” 按钮来创建脚本,是手动在文件夹或者 Visual Studio Code 中面创建的脚本需要做一些操作:
- 将类默认导出,也就是在类名前加上
export default
。 - 给类加上装饰器
@Component
。
完成上述操作后这个脚本才可以被挂载到对象上,并且会自动执行生命周期函数。
如果需要重命名等操作,可以右键单击该脚本 (或者选中脚本后按 F2 键) 进行操作,如图:
脚本创建完成后,怎么使用呢?那就需要挂载到游戏对象上了,只有挂载到游戏对象上的脚本才会自动执行脚本中的 onStart
等生命周期方法。
- 点击 “游戏功能对象” 按钮。
- 选中 “游戏功能对象” 页签
- 找到 “空锚点” 对象,将它拖拽到对象管理器中(或直接拖拽到主视口中)。
- 在对象管理器中选中刚刚拖出来的 “空锚点”。
- 选中
PlayerControl
脚本,鼠标左键长按拖动到 “空锚点” 上 - 在 “对象管理器” 中选中 “空锚点” 可以看到属性面板中多了一个脚本组件。
2. 脚本基础结构
双击打开脚本,若刚才文件名是 "PlayerContorl" ,那么现在脚本内容应该如下所示:
默认会使用 VSCode 打开该脚本,如果不是使用 VSCode 打开的,可以参考论坛的常见问题:安装和运行常见问题
typescript
@Component
export default class PlayerControl extends Script {
/** 当脚本被实例后,会在第一帧更新前调用此函数 */
protected onStart(): void { }
/**
* 周期函数 每帧执行
* 此函数执行需要将this.useUpdate赋值为true
* @param dt 当前帧与上一帧的延迟 / 秒
*/
protected onUpdate(dt: number): void { }
/** 脚本被销毁时最后一帧执行完调用此函数 */
protected onDestroy(): void { }
}
@Component
export default class PlayerControl extends Script {
/** 当脚本被实例后,会在第一帧更新前调用此函数 */
protected onStart(): void { }
/**
* 周期函数 每帧执行
* 此函数执行需要将this.useUpdate赋值为true
* @param dt 当前帧与上一帧的延迟 / 秒
*/
protected onUpdate(dt: number): void { }
/** 脚本被销毁时最后一帧执行完调用此函数 */
protected onDestroy(): void { }
}
基础结构如下:
typescript
@Component
export default class PlayerControl extends Script {
}
@Component
export default class PlayerControl extends Script {
}
@Component
,@expression 这种形式在 TypeScript 为 “装饰器”,@Component
这个装饰器是用来告诉我们游戏引擎该脚本的一些基本信息,方便游戏引擎获取到该类信息之后做一些处理,比如自动调用类里面的生命周期函数等export default
是 ts 语法,可以让该类在其他地方被调用(手动创建的类,需要自己手动添加该语句)class PlayerControl
定义了类名为PlayerControl
,这个类名需要与文件名保持一致extends Script
,使PlayerControl
这个类继承自Script
,Script
是编辑器定义的基础类,需要挂载到场景中使用的脚本,都需要继承自该类才可正常使用。
在上小节,我们把脚本挂载到了游戏对象窗口中,但是该脚本并不一定会成功执行,成功执行该游戏脚本必须有以下几个要求:
- 首先该脚本中一定包含一个类,并且类名与文件名相同,示例中为
PlayerControl
。 - 该类前要加 export default,设置为默认导出类。
- 该类要添加一个
@Component
装饰器。 - 该类继承自 Script 。
3. 脚本常用生命周期
生命周期代表着一个脚本从激活(Activate)到销毁(Destroy)的全过程,也代表着代码中脚本函数的执行过程与执行顺序,脚本常用的三个生命周期方法分别为 onStart、onUpdate、onDestroy ,如下所示:
typescript
@Component
export default class PlayerControl extends Script {
/** 当脚本被实例后,会在第一帧更新前调用此函数 */
protected onStart(): void { }
/**
* 周期函数 每帧执行
* 此函数执行需要将 this.useUpdate 赋值为 true
* @param dt 当前帧与上一帧的延迟 / 秒
*/
protected onUpdate(dt: number): void { }
/** 脚本被销毁时最后一帧执行完调用此函数 */
protected onDestroy(): void { }
}
@Component
export default class PlayerControl extends Script {
/** 当脚本被实例后,会在第一帧更新前调用此函数 */
protected onStart(): void { }
/**
* 周期函数 每帧执行
* 此函数执行需要将 this.useUpdate 赋值为 true
* @param dt 当前帧与上一帧的延迟 / 秒
*/
protected onUpdate(dt: number): void { }
/** 脚本被销毁时最后一帧执行完调用此函数 */
protected onDestroy(): void { }
}
- 如代码中注释所说,三个生命周期函数会在不同的阶段调用
onStart
函数是脚本第一个执行的函数,一般在里面处理一些初始化的逻辑,如事件监听、设置 useUpdate 为 true、获取数据等等onUpdate
函数在游戏运行过程中会不断的执行,每一帧执行一次。不过默认该函数是不会执行的,需要将 useUpdate 属性设置为 true 才会执行,比如在 onStart 函数中添加代码:this.useUpdate = true;
onDestroy
函数是脚本快销毁的时候执行,一般在函数中处理一些资源回收的逻辑。
注意
因为 onUpdate 执行频率非常高,一旦出现有耗时逻辑或者容易报错的逻辑,将会导致很严重的游戏性能问题,所以需要注意以下几点:
1)尽量减少在 onUpdate 函数中写循环逻辑,避免死循环或循环内出现空引用阻塞程序执行
2)在 onUpdate 函数执行的逻辑中,引用的对象尽量都做判空处理,提高定位逻辑问题效率,同时避免空引用阻塞程序执行
3)若不是必须使用 onUpdate 的场景,尽量使用其他函数代替(例:计时器可用 setInterval,延时可用 setTimeout)