组件:脚本
脚本组件中定义了大量可以继承的虚函数,通过在子类中实现这类虚函数,就可以在程序中插入自定义的逻辑。
/**
* Script class, used for logic writing.
*/
class Script : public Component {
public:
/**
* Called before the frame-level loop start for the first time, only once.
*/
virtual void onStart() {
}
/**
* The main loop, called frame by frame.
* @param deltaTime - The deltaTime when the script update.
*/
virtual void onUpdate(float deltaTime) {
}
/**
* Called after the onUpdate finished, called frame by frame.
* @param deltaTime - The deltaTime when the script update.
*/
virtual void onLateUpdate(float deltaTime) {
}
};
实际上,脚本中的函数分为两个类别:
- 和主循环一起的多次的调用
- 初始化或者销毁时的一次性调用
多次调用
回调机制
脚本组件的优点在于收口了所有面向用户的行为,例如针对物理组件,提供了三个函数:
/**
* Called when the collision enter.
* @param other ColliderShape
*/
virtual void onTriggerEnter(physics::ColliderShapePtr other) {}
/**
* Called when the collision stay.
* @remarks onTriggerStay is called every frame while the collision stay.
* @param other ColliderShape
*/
virtual void onTriggerExit(physics::ColliderShapePtr other) {}
/**
* Called when the collision exit.
* @param other ColliderShape
*/
virtual void onTriggerStay(physics::ColliderShapePtr other) {}
这三个函数分别对应于物理触发器的进入,离开和保持这三个状态。对于任意组件,都可以获得其在构造时就保存的指向 Entity
的指针。
而当 Script
及其子类被构造时,就会同时将指针保存到 Entity
当中:
void Script::_onEnable() {
auto &componentsManager = entity()->scene()->_componentsManager;
if (!_started) {
componentsManager.addOnStartScript(this);
}
componentsManager.addOnUpdateScript(this);
_entity->_addScript(this);
onEnable();
}
这样一来就可以获取 Entity
上所绑定的脚本组件,并执行对应的方法,例如:
onTriggerEnter = [&](PxShape *obj1, PxShape *obj2) {
const auto shape1 = _physicalObjectsMap[obj1->getQueryFilterData().word0];
const auto shape2 = _physicalObjectsMap[obj2->getQueryFilterData().word0];
auto scripts = shape1->collider()->entity()->scripts();
for (const auto &script: scripts) {
script->onTriggerEnter(shape2);
}
scripts = shape2->collider()->entity()->scripts();
for (const auto &script: scripts) {
script->onTriggerEnter(shape1);
}
};
所以,随着引擎的发展,越来越多的函数都可以通过类似的方式在 Script
中保留回调函数的接口,在用户的概念中,也只需要继承 Script
,就能为一些组件添加自定义的逻辑。
注册机制
对于多次调用来说,其实现机制在于覆盖了 Component
提供的四个虚函数:
void Script::_onAwake() {
onAwake();
}
void Script::_onEnable() {
auto &componentsManager = entity()->scene()->_componentsManager;
if (!_started) {
componentsManager.addOnStartScript(this);
}
componentsManager.addOnUpdateScript(this);
_entity->_addScript(this);
onEnable();
}
void Script::_onDisable() {
auto &componentsManager = entity()->scene()->_componentsManager;
// Use "xxIndex" is more safe.
// When call onDisable it maybe it still not in script queue,for example write "entity.isActive = false" in onWake().
if (_onStartIndex != -1) {
componentsManager.removeOnStartScript(this);
}
if (_onUpdateIndex != -1) {
componentsManager.removeOnUpdateScript(this);
}
if (_entityCacheIndex != -1) {
_entity->_removeScript(this);
}
onDisable();
}
void Script::_onDestroy() {
entity()->scene()->_componentsManager.addDestroyComponent(this);
}
这些函数会向 ComponentManager
注册脚本组件的指针,并且在主循环中一次性执行这些脚本:
void Scene::update(float deltaTime) {
_componentsManager.callScriptOnStart();
_physicsManager.callColliderOnUpdate();
_physicsManager.update(deltaTime);
_physicsManager.callColliderOnLateUpdate();
_physicsManager.callCharacterControllerOnLateUpdate();
_componentsManager.callScriptOnUpdate(deltaTime);
_componentsManager.callAnimatorUpdate(deltaTime);
_componentsManager.callSceneAnimatorUpdate(deltaTime);
_componentsManager.callScriptOnLateUpdate(deltaTime);
_componentsManager.callRendererOnUpdate(deltaTime);
updateShaderData();
}
一次性调用
一次性调用并不意味着这些函数只会被调用一次,而是这些函数并不随着主循环每帧都会触发,相关函数有五个
/**
* Called when be enabled first time, only once.
*/
virtual void onAwake() {}
/**
* Called when be enabled.
*/
virtual void onEnable() {}
/**
* Called when be disabled.
*/
virtual void onDisable() {}
/**
* Called at the end of the destroyed frame.
*/
virtual void onDestroy() {}
/**
* Called before the frame-level loop start for the first time, only once.
*/
virtual void onStart() {}
OnAwake
如果脚本添加到的实体的 isActiveInHierarchy 为 true,则在脚本初始化时回调函数将被调用,如果 isActiveInHierarchy 为 false,则在实体被激活,即 isActive 被设为 true 时被调用。
onAwake
只会被调用一次,并且在所有生命周期的最前面,通常我们会在 onAwake
中做一些初始化相关的操作。
onEnable
当脚本的 enabled 属性从 false 变为 true 时,或者所在实体的 isActiveInHierarchy 属性从 false 变为 true 时,会激活 onEnable
回调。倘若实体第一次被创建且 enabled 为
true,则会在 onAwake
之后,onStart
之前被调用。
onDisable
当组件的 enabled 属性从 true 变为 false 时,或者所在实体的 isActiveInHierarchy 属性从 true 变为 false 时,会激活 onDisable
回调
note
isActiveInHierarchy 的判断方法是:实体在层级树中是被激活状态即该实体为激活状态,它的父亲直到根实体都为激活状态 isActiveInHierarchy 才为 true
onStart
onStart 回调函数会在脚本第一次进入帧循环,也就是第一次执行 onUpdate 之前触发。onStart 通常用于初始化一些需要经常修改的数据,这些数据可能在 onUpdate 时会发生改变。
onDestroy
当组件或者所在实体调用了 destroy,则会调用 onDestroy
回调,并在当帧结束时统一回收组件。