Scripts are the bridge between engine capabilities and game logic. They can be used to extend the engine's functionality and to write your own game logic code in the lifecycle hook functions provided by script components. The base class for custom scripts is Script, which extends from Component. Therefore, it not only supports the basic capabilities of components:
Additionally, it provides a rich set of lifecycle callback functions. As long as specific callback functions are overridden in the script, you don't need to manually call them; Galacean will automatically execute the relevant scripts at specific times.
onBeginRender and onEndRender are somewhat different from the others.
They are called only when the entity has a camera component mounted, meaning they are called when a camera component is added.
If the isActiveInHierarchy of the entity to which the script is added is true
, the callback function will be called when the script is initialized. If isActiveInHierarchy is false
, it will be called when the entity is activated, i.e., when isActive is set to true
. onAwake
will only be called once and is at the very beginning of all lifecycles. Typically, we perform some initialization-related operations in onAwake
:
onAwake() {
this.child = this.entity.getChild(0);
this.child.isActive = false;
}
The onEnable
callback is activated when the enabled property of the script changes from false
to true
, or when the isActiveInHierarchy property of the entity changes from false
to true
. If the entity is created for the first time and enabled is true
, it will be called after onAwake
and before onStart
.
The onDisable
callback is activated when the enabled property of the component changes from true
to false
, or when the isActiveInHierarchy property of the entity changes from true
to false
.
Note: The isActiveInHierarchy check means that the entity is in an active state in the hierarchy tree, i.e., the entity is active, and its parent and all ancestors up to the root entity are also active. Only then is isActiveInHierarchy true
.
The onStart
callback function is triggered the first time the script enters the frame loop, i.e., before the first execution of onUpdate
. onStart
is usually used to initialize some data that needs to be frequently modified, which may change during onUpdate
.
onStart() {
this.updateCount = 0
}
onUpdate() {
this.updateCount++;
}
It is important to note that Galacean executes the onStart
callbacks in bulk before executing the onUpdate
callbacks in bulk. The benefit of this approach is that you can access the initialized values of other entities in onUpdate
.
import { TheScript } from './TheScript'
onStart() {
this.otherEntity = Entity.findByName('otherEntity');
this.otherEntityScript = this.otherEntity.getComponent(TheScript)
}
onUpdate() {
console.log(this.otherEntityScript.updateCount)
}
The onPhysicsUpdate
callback function is called at the same frequency as the physics engine update rate. It may be called multiple times per render frame.
The onTriggerEnter
callback function is called when triggers come into contact to handle the logic when triggers meet, such as deleting an entity when the trigger occurs.
The onTriggerStay
callback function is called continuously during the trigger contact process, once per frame.
The onTriggerExit
callback function is called when two triggers separate, i.e., when the trigger relationship changes, and it is called only once.
The onCollisionEnter
callback function is called when colliders collide to handle the logic when colliders meet, such as deleting an entity when the collision occurs.
The onCollisionStay
callback function is called continuously during the collider collision process, once per frame.
The onCollisionExit
callback function is called when two colliders separate, i.e., when the collision relationship changes, and it is called only once.
A key point in game/animation development is to update the behavior, state, and position of objects before each frame is rendered. These update operations are usually placed in the onUpdate
callback. It receives a parameter representing the time difference since the last onUpdate
execution, of type number
.
onStart() {
this.rotationY = 0
}
onUpdate(deltaTime: number) {
this.entity.transform.rotate(new Vector3(0, this.rotationY++, 0))
}
onUpdate
is executed before all animation updates, but if we want to perform some additional operations after the effects (such as animations, particles, etc.) are updated, or if we want to perform other operations such as camera follow after all components' onUpdate
have been executed, we need to use the onLateUpdate
callback. It receives a parameter representing the time difference since the last onLateUpdate
execution, of type number
.
onStart() {
this.rotationY = 0
}
onUpdate() {
this.entity.transform.rotate(new Vector3(0, this.rotationY++, 0))
}
onLateUpdate(deltaTime: number) {
this.rotationY %= 360;
}
Only when the entity has a camera component attached, the onBeginRender
callback will be called before the camera component's render method is called.
Only when the entity has a camera component attached, the onEndRender
callback will be called after the camera component's render method is called.
When a component or its entity calls destroy, the onDestroy
callback will be called, and the component will be uniformly recycled at the end of the frame.
For input system interfaces, see Input Interaction.
Entities are the main objects operated by scripts. You can modify nodes and components in the editor's scene inspector, and you can also dynamically modify them in scripts. Scripts can respond to player input, modify, create, and destroy entities or components to achieve various game logic.
You can obtain the entity bound to the script in any lifecycle of the script, such as:
class MyScript extends Script {
onAwake() {
const entity = this.entity;
}
}
When we need to get other components on the same node, we use the getComponent API, which helps you find the component you need.
onAwake() {
const components = []
this.entity.getComponents(o3.Model, components);
}
Sometimes there may be multiple components of the same type, and the above method will only return the first found component. If you need to find all components, you can use getComponents.
For example, to rotate an entity in the onUpdate method using the setRotation method:
this.entity.transform.setRotation(0, 5, 0);
onAwake() {
const component = this.entity.getComponent(o3.Model);
}
Sometimes, there are many objects of the same type in the scene, such as multiple particle animations or multiple coins, which usually have a global script to manage them uniformly. If you associate them with this script one by one, the work will be cumbersome. To better manage these objects, we can place them under a unified parent object and then obtain all child objects through the parent object:
If you know the index of the child node in the parent node, you can directly use getChild:
onAwake() {
this.entity.getChild(0);
}
If you don't know the index of the child node, you can use findByName to find it by the node's name. findByName will search not only child nodes but also grandchild nodes.
onAwake() {
this.entity.findByName('model');
}
If there are nodes with the same name, you can use findByPath to pass in the path for step-by-step search. Using this API will also improve search efficiency to some extent.
onAwake() {
this.entity.findByPath('parent/child/grandson');
}