Anatomy of a script
Here is a basic script. We can learn about the structure of a PlayCanvas script from it.
var Rotate = pc.createScript('rotate');
Rotate.attributes.add('speed', { type: 'number', default: 10 });
// initialize code called once per entity
Rotate.prototype.initialize = function() {
this.local = false; // choose local rotation or world rotation
};
// update code called every frame
Rotate.prototype.update = function(dt) {
if (this.local) {
this.entity.rotateLocal(0, this.speed * dt, 0);
} else {
this.entity.rotate(0, this.speed * dt, 0);
}
};
// swap method called for script hot-reloading
// inherit your script state here
Rotate.prototype.swap = function(old) {
this.local = old.local;
};
We'll break down each section of the script
Declaration of Script Type
var Rotate = pc.createScript('rotate');
This line creates a new ScriptType called 'rotate'. The name of the script is used to identify the script in script components. Each ScriptType that is declared in a project must have a unique name. The returned function Rotate
is a javascript function which is ready to have its prototype extended with a standard set of methods. Somewhat like class inheritance.
Script Attributes
Rotate.attributes.add('speed', { type: 'number', default: 10 });
This line declares a script attribute. A script attribute is a property of the script instance and it is exposed to the Editor UI. This allows you to customize individual entities in the Editor. In the above example, the attribute is called 'speed' and would be accessible in the script code as this.speed
. It is a number and by default is initialized to 10.
Attributes are automatically inherited from a new script instance during code hot-swap.
Script Methods
Initialize
// initialize code called once per entity
Rotate.prototype.initialize = function() {
// local rotation or world rotation
this.local = false;
};
The initialize
method is called on each entity that has the script attached to it. It is called after application loading is complete and the entity hierarchy has been constructed but before the first update loop or frame is rendered. The initialize
method is only called once for each entity. You can use it to define and initialize member variables of the script instance. If an entity or script is disabled when the application starts, the initialize method will be called the first time the entity is enabled.
When an entity is cloned using the entity.clone
method, the initialize
method on the script is only called when the cloned entity is added to the scene hierarchy; as long as both the entity and script are enabled as well.
If a script component has multiple scripts attached to it, the initialize
method is called in the order of the scripts on the component.
Update
// update code called every frame
Rotate.prototype.update = function(dt) {
if (this.local) {
this.entity.rotateLocal(0, this.speed * dt, 0);
} else {
this.entity.rotate(0, this.speed * dt, 0);
}
};
The update method is called for every frame; it is invoked within each entity that has an enabled script component and enabled script instance. Each frame is passed the dt
argument containing the time, in seconds, since the last frame.
If a script component has multiple scripts attached to it, update
is called in the order of the scripts on the component.
Swap
// swap method called for script hot-reloading
// inherit your script state here
Rotate.prototype.swap = function(old) {
this.local = old.local;
};
The swap
method is called whenever a ScriptType with same is added to registry. This is done automatically during Launch when a script is changed at runtime from the Editor. This method allows you to support "code hot reloading" whilst you continue to run your application. It is extremely useful if you wish to iterate on code that takes a while to reach while running your app. You can make changes and see them without having to reload and run through lots of set up or restoring the game state.
The swap
method is passed the old script instance as an argument and you can use this to copy the state from the old instance into the new one. You should also ensure that events are unsubscribed and re-subscribed to.
If you do not wish to support hot-swapping of code, you can delete the swap method and the engine will not attempt to refresh the script.
Additional Methods: postInitialize and postUpdate
There are two more methods that are called by the engine on scripts if they are present. postInitialize
is called on all scripts that implement it after all scripts have been initialized. Use this method to perform functions that can assume all scripts are initialized. postUpdate
is an update method that is called after all scripts have been updated. Use this to perform functions that can assume that all scripts have been updated. For example, a camera that is tracking another entity should update its position in postUpdate
so that the other entity has completed its motion for the frame.
Events
Script instances fire a number of events that can be used to respond to specific circumstances.
state and enable/disable
The state
event is fired when the script instance changes running state from enabled to disabled or vice versa. The script instance state can be changed by enabling/disabling the script itself, the component the script is a member of, or the entity that the script component is attached to. The enable
event fires only when the state changes from disabled to enabled, and the disable
event fires only when the state changes from enabled to disabled.
Rotate.prototype.initialize = function () {
this.on("state", function (enabled) {
// play a sound effect when the entity is enabled or disabled
if (enabled) {
this.entity.sound.play("bell");
} else {
this.entity.sound.play("horn");
}
});
};
or the equivalent using enable
and disable
Rotate.prototype.initialize = function () {
this.on("enable", function () {
this.entity.sound.play("bell");
});
this.on("disable", function () {
this.entity.sound.play("horn");
});
};
destroy
The destroy
event is fired when the script instance is destroyed. This could be because the script was removed from the component by calling the destroy()
method, or script component been removed from Entity, or because the Entity it was attached to was destroyed.
Rotate.prototype.initialize = function () {
this.on("destroy", function () {
// remove a DOM event listener when the entity is destroyed
window.removeEventListener("resize", this._onResize);
});
};
attr and attr:[name]
The attr
and attr:[name]
events are fired when a declared script attribute value is changed. This could be in the course of running the application or it could be when changes are made to the value via the Editor. The attr
is fired for every attribute changed. The attr:[name]
is fired only for a specific attribute e.g. if you have an attribute called 'speed' the event attr:speed
would be fired when the speed is changed.
Rotate.prototype.initialize = function () {
this.on("attr:speed", function (value, prev) {
// speed attribute has changed
});
};