Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ESM Script Documentation #644

Open
wants to merge 74 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
28ecc52
Upgrading Script examples to include ESM variants
marklundin Mar 27, 2024
7dd39e0
Add esm variants for tutorials
marklundin Mar 27, 2024
36f094f
linting
marklundin Mar 27, 2024
fa3a447
linting
marklundin Mar 27, 2024
58c6e7b
Merge branch 'main' into esm-script-examples
willeastcott Apr 5, 2024
dbf459a
Update docs/tutorials/collision-and-triggers.md
marklundin Apr 8, 2024
4ea970d
Update docs/tutorials/collision-and-triggers.md
marklundin Apr 8, 2024
34bc4db
Update docs/tutorials/Using-forces-on-rigid-bodies.md
marklundin Apr 8, 2024
a9709a3
Update docs/tutorials/entity-picking.md
marklundin Apr 8, 2024
441f8c6
Update docs/tutorials/facebook-api.md
marklundin Apr 8, 2024
492543d
Update docs/tutorials/facebook-api.md
marklundin Apr 8, 2024
577ac0e
Update docs/tutorials/facebook-api.md
marklundin Apr 8, 2024
3ef4a68
Update docs/tutorials/facebook-api.md
marklundin Apr 8, 2024
e183493
Update docs/tutorials/facebook-api.md
marklundin Apr 8, 2024
77f40e3
Update docs/tutorials/first-person-movement.md
marklundin Apr 8, 2024
67360a7
Update docs/tutorials/keepyup-part-four.md
marklundin Apr 8, 2024
e6fae27
Update docs/tutorials/keepyup-part-three.md
marklundin Apr 8, 2024
e1c53dd
Update docs/tutorials/google-ads-for-games.md
marklundin Apr 8, 2024
9ed44e7
Update docs/tutorials/keepyup-part-six.md
marklundin Apr 8, 2024
99e777b
Update docs/tutorials/facebook-api.md
marklundin Apr 8, 2024
57f0e73
Update docs/tutorials/facebook-api.md
marklundin Apr 8, 2024
ecb0387
Update docs/tutorials/facebook-api.md
marklundin Apr 8, 2024
728b9bf
replaced attributesDefinition with attributes
marklundin Apr 10, 2024
0123222
Add ESM Scripts user manual page
marklundin Apr 11, 2024
302f7ab
fix link
marklundin Apr 11, 2024
42ce655
fixed admonition
marklundin Apr 11, 2024
3af9af9
content update
marklundin Apr 11, 2024
3903927
updates
marklundin Apr 12, 2024
d9dfedf
CDN
marklundin Apr 12, 2024
90493a7
warning
marklundin Apr 12, 2024
b9087bc
esm-scripts typos (#651)
LeXXik Apr 13, 2024
bf138a1
Update docs/user-manual/publishing/web/communicating-webpage.md
marklundin Apr 15, 2024
1663a93
Remove ScriptType reference
marklundin Apr 15, 2024
4127e46
Merge branch 'esm-script-examples' of https://github.com/playcanvas/d…
marklundin Apr 15, 2024
f976527
Update docs/user-manual/scripting/script-attributes.md
marklundin Apr 15, 2024
5e290e6
Update docs/user-manual/scripting/migration-guide.md
marklundin Apr 15, 2024
385873b
Update docs/user-manual/scripting/communication.md
marklundin Apr 15, 2024
e11d4b9
arrow function
marklundin Apr 15, 2024
7e11268
Update docs/user-manual/scripting/script-attributes.md
marklundin Apr 15, 2024
99201ad
Update docs/user-manual/scripting/script-attributes.md
marklundin Apr 15, 2024
fe5cd7c
linting fixes
marklundin Apr 15, 2024
86a9b67
add version flag
marklundin Apr 15, 2024
71e7789
renamed Classic -> Legacy. Script Attributes docs
marklundin Jun 17, 2024
0957290
Completed Script Attribute docs
marklundin Jun 17, 2024
755b744
Merge branch 'main' into esm-script-examples
marklundin Jun 17, 2024
30641fc
replicate legacy in ja, to fix build
marklundin Jun 17, 2024
5552740
Refactored attribute grouping and added interface attributes
marklundin Jul 1, 2024
979a114
Update legacy scripting system documentation
marklundin Jul 1, 2024
4c495f2
Update script attributes and loading order documentation
marklundin Sep 2, 2024
f1ba724
Refactor script creation and import statements
marklundin Sep 2, 2024
43e3664
Refactor script attributes documentation
marklundin Sep 2, 2024
b421e09
Fix linting issues and update script documentation
marklundin Sep 2, 2024
2aa64f2
Update linting command in package.json
marklundin Sep 2, 2024
1adbabd
Merge branch 'main' into esm-script-examples
marklundin Sep 2, 2024
a0b1a24
Update migration guide and add legacy loading order documentation
marklundin Sep 2, 2024
7f3cca7
Merge branch 'esm-script-examples' of https://github.com/playcanvas/d…
marklundin Sep 2, 2024
793a4f8
Update loading order title and add redirect for legacy loading order
marklundin Sep 2, 2024
cedc5d0
Remove unnecessary blank line in esm-scripts.md
marklundin Sep 2, 2024
9466eb2
Refactor watch function to use Object.defineProperty
marklundin Sep 5, 2024
3c6530f
reverting tutorials back to main
marklundin Sep 17, 2024
e32a1a7
revert animation docs
marklundin Sep 17, 2024
0606a53
revert getting started + asset types
marklundin Sep 17, 2024
a3d165b
revert physics
marklundin Sep 17, 2024
75cac59
revert publishing
marklundin Sep 17, 2024
6087ea8
revert user interface
marklundin Sep 17, 2024
1e266db
revert package-lock.json to main
marklundin Sep 17, 2024
437712c
Merge branch 'main' into esm-script-examples
marklundin Sep 17, 2024
574964c
package-lock fix
marklundin Sep 17, 2024
587f807
Update docs/user-manual/scripting/anatomy.md
marklundin Sep 17, 2024
e2dbbe4
legacy -> classic
marklundin Sep 26, 2024
154785d
fix links
marklundin Sep 26, 2024
57fa097
Update ESM Scripts description
marklundin Sep 26, 2024
00f67df
fix linting
marklundin Sep 26, 2024
4bbc962
fixed i18n link
marklundin Sep 26, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
218 changes: 169 additions & 49 deletions docs/user-manual/scripting/anatomy.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,47 @@ title: Anatomy of a script
sidebar_position: 3
---

Here is a basic script. We can learn about the structure of a PlayCanvas script from it.
Scripts provide powerful features for adding interactivity to your project. To learn more about the anatomy of a Script we've provided a simple `Rotate` script below. Let's break down what this means.

:::note
Scripts can be defined as either **[ES Modules](./esm-scripts.md)**, or **legacy scripts**.
**[Learn more](./esm-scripts.md)**.
marklundin marked this conversation as resolved.
Show resolved Hide resolved
:::

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

<Tabs defaultValue="legacy" groupId='script-code'>
<TabItem value="esm" label="ESM">

```javascript
import { Script } from 'playcanvas';

export class Rotate extends Script {

/** @attribute */
speed = 10;

initialize() {
this.local = false; // choose local rotation or world rotation
}

update(dt) {
if (this.local) {
this.entity.rotateLocal(0, this.speed * dt, 0);
} else {
this.entity.rotate(0, this.speed * dt, 0);
}
}

swap(old) {
this.local = old.local;
}
}
```

</TabItem>
<TabItem value="legacy" label="Legacy">

```javascript
var Rotate = pc.createScript('rotate');
Expand Down Expand Up @@ -31,29 +71,78 @@ Rotate.prototype.swap = function(old) {
};
```

We'll break down each section of the script
</TabItem>
</Tabs>

## Declaring a Script

<Tabs defaultValue="legacy" groupId='script-code'>
<TabItem value="esm" label="ESM">

```javascript
import { Script } from 'playcanvas';
export class Rotate extends Script {};
```

You declare a script by **exporting a class that extends `Script`**. The class name is used to identify the script. Each script in a project must have a unique name.

## Declaration of Script Type
</TabItem>
<TabItem value="legacy" label="Legacy">

```javascript
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.
This line creates a new Script called 'rotate'. The name of the script is used to identify the script in script components. Each Script 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.

</TabItem>
</Tabs>

## Attributes

## Script Attributes
<Tabs defaultValue="legacy" groupId='script-code'>
<TabItem value="esm" label="ESM">

```javascript
/** @attribute */
speed = 10
```

</TabItem>
<TabItem value="legacy" label="Legacy">

```javascript
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.
</TabItem>
</Tabs>

This exposes a Script member as an attribute. [Attributes](./script-attributes.md) are a powerful feature that allow you to expose parameters of your script to the editor, and manually set these at author time.

Attributes are automatically inherited from a new script instance during code hot-swap.
Learn more about [Script Attributes](./script-attributes.md)

## Script Methods
## Life-Cycle Methods

The lifecycle hooks are optional methods called at various stages in the application. They allow scripts to initialize and react to frame updates and more. For convenience, they're listed below in the order of execution.

If an entity has multiple scripts attached to it, the methods are called in the order specified in the component.

### Initialize()

<Tabs defaultValue="legacy" groupId='script-code'>
<TabItem value="esm" label="ESM">

```javascript
// initialize code called once per entity
initialize() {
// local rotation or world rotation
this.local = false;
}
```

### Initialize
</TabItem>
<TabItem value="legacy" label="Legacy">

```javascript
// initialize code called once per entity
Expand All @@ -63,13 +152,37 @@ Rotate.prototype.initialize = function() {
};
```

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.
</TabItem>
</Tabs>

The `initialize()` method is called before any other life-cycle method. It's useful for setting up event listeners or any other data needed by the Script. It will only ever be called once when the script and it's entity are enabled.

More formally, `initialize` is called after the application has loaded and the entity hierarchy has been constructed but before the first update loop or frame is rendered. 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.
:::tip
`postInitialize()` is an additional life-cycle hook invoked after `initialize()` has been called on all scripts in the scene. This can be particularly useful when you need to perform setup actions that depend on the results of other scripts' `initialize()` methods.
:::

### Update()

<Tabs defaultValue="legacy" groupId='script-code'>
<TabItem value="esm" label="ESM">

```javascript
// update code called every frame
update(dt) {
if (this.local) {
this.entity.rotateLocal(0, this.speed * dt, 0);
} else {
this.entity.rotate(0, this.speed * dt, 0);
}
}
```

### Update
</TabItem>
<TabItem value="legacy" label="Legacy">

```javascript
// update code called every frame
Expand All @@ -82,11 +195,30 @@ Rotate.prototype.update = function(dt) {
};
```

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.
</TabItem>
</Tabs>

The `update()` method is called every frame whilst enabled. It's useful for animating things or handling anything that needs to happen every frame. It's called with the `dt` argument which specifies the time, in seconds, since the last frame.

:::tip
The **`postUpdate()`** method is an additional life-cycle hook invoked after **`update()`** has been called on all scripts in the scene. This can be particularly useful when you need to perform actions that depend on the results of other scripts' **`update()`** methods.
:::

If a script component has multiple scripts attached to it, `update` is called in the order of the scripts on the component.
### Swap()

### Swap
<Tabs defaultValue="legacy" groupId='script-code'>
<TabItem value="esm" label="ESM">

```javascript
// swap method called for script hot-reloading
// inherit your script state here
swap(old) {
this.local = old.local;
}
```

</TabItem>
<TabItem value="legacy" label="Legacy">

```javascript
// swap method called for script hot-reloading
Expand All @@ -96,64 +228,51 @@ Rotate.prototype.swap = function(old) {
};
```

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.
</TabItem>
</Tabs>

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.
The `swap()` method is used to enable [hot-reloading](./hot-reloading.md) of scripts. In practice this means you can update code at author-time, and use the `swap()` method to copy state over from the previous script instance, to the new one. This is extremely useful if you wish to iterate on code without refreshing your project.

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.
The `swap` method is passed the old script instance as an argument. You should use this to copy any state from the old instance into the new one. You should also ensure that events are unsubscribed and re-subscribed to.

### 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.
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.

## Events

Script instances fire a number of events that can be used to respond to specific circumstances.

### state and enable/disable
### state

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.
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.

```javascript
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");
}
});
};
this.on("state", (enabled) => {
// play a sound effect when the entity is enabled or disabled
this.entity.sound.play(enabled ? "bell" : "horn");
});
```

or the equivalent using `enable` and `disable`
### enable/disable

```javascript
Rotate.prototype.initialize = function () {
this.on("enable", function () {
this.entity.sound.play("bell");
});
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.

this.on("disable", function () {
this.entity.sound.play("horn");
});
};
```javascript
this.on("enable", () => this.entity.sound.play("bell"));
this.on("disable", () => 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.

```javascript
Rotate.prototype.initialize = function () {
this.on("destroy", function () {
// remove a DOM event listener when the entity is destroyed
window.removeEventListener("resize", this._onResize);
});
};
this.on("destroy", () => {
// 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.
Expand All @@ -165,3 +284,4 @@ Rotate.prototype.initialize = function () {
});
};
```
-->
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
---
title: Loading Order
title: Loading Order [Legacy]
sidebar_position: 7
---
:::warning
**This documentation is for the legacy scripting system.**

See [**Script Attributes**](../script-attributes.md) for the latest documentation.
:::

Generally all scripts are loaded at the beginning of your application. The loading order is determined by a setting in your project which you can access from the main Editor menu or Scene Settings

Expand All @@ -13,6 +18,10 @@ The loading order panel shows all your scripts that marked as `preload` and the

You can drag to move individual scripts around in order.

:::info
ESM Scripts do not have an explicit loading order, and should not be relied upon loading in a specific order. Instead you should use regular imports to declare dependencies between modules.
:::

When scripts are first loaded, they are immediately executed, that means that the scripts are first executed in the order that they are loaded. However, the loading order of the script **does not** effect the execution of order of script methods within script component. E.g. the initialize methods of scripts on the same entity are called in the order that they are listed on the Entity not the loading order.

## Preloading
Expand Down
Loading