Repository: playcanvas/engine
Pull request #6249 (2024-04-12 10:58:06)

Exposed gizmo color alpha
Exposes color alpha for gizmo mesh color

### Preview
![image](https://github.com/playcanvas/engine/assets/48248865/83684131-d5d9-4cc6-8693-1b05fe0ac4c4)




Patch:
diff --git a/examples/src/examples/misc/gizmos/controls.mjs b/examples/src/examples/misc/gizmos/controls.mjs
index dd06c7356..d0e8a0712 100644
--- a/examples/src/examples/misc/gizmos/controls.mjs
+++ b/examples/src/examples/misc/gizmos/controls.mjs
@@ -95,6 +95,17 @@ export function controls({ observer, ReactPCUI, React, jsx, fragment }) {
                     binding: new BindingTwoWay(),
                     link: { observer, path: 'gizmo.zAxisColor' }
                 })
+            ),
+            jsx(
+                LabelGroup,
+                { text: 'Color Alpha' },
+                jsx(SliderInput, {
+                    binding: new BindingTwoWay(),
+                    link: { observer, path: 'gizmo.colorAlpha' },
+                    min: 0,
+                    max: 1,
+                    precision: 2
+                })
             )
         ),
         jsx(
diff --git a/examples/src/examples/misc/gizmos/example.mjs b/examples/src/examples/misc/gizmos/example.mjs
index 036e5206e..6ad266413 100644
--- a/examples/src/examples/misc/gizmos/example.mjs
+++ b/examples/src/examples/misc/gizmos/example.mjs
@@ -101,6 +101,7 @@ class GizmoHandler {
             xAxisColor: Object.values(gizmo.xAxisColor),
             yAxisColor: Object.values(gizmo.yAxisColor),
             zAxisColor: Object.values(gizmo.zAxisColor),
+            colorAlpha: gizmo.colorAlpha,
             coordSpace: gizmo.coordSpace,
             axisLineTolerance: gizmo.axisLineTolerance,
             axisCenterTolerance: gizmo.axisCenterTolerance,
diff --git a/extras/gizmo/transform-gizmo.js b/extras/gizmo/transform-gizmo.js
index 6c7f284c3..2a965cdac 100644
--- a/extras/gizmo/transform-gizmo.js
+++ b/extras/gizmo/transform-gizmo.js
@@ -30,16 +30,6 @@ const FACING_EPSILON = 0.2;
 const SPANLINE_SIZE = 1e3;
 const ROTATE_SCALE = 900;
 
-/**
- * @param {Color} color - The color.
- * @returns {Color} - The semi transparent color.
- */
-const colorSemi = (color) => {
-    const clone = color.clone();
-    clone.a = 0.6;
-    return clone;
-};
-
 /**
  * Shape axis for the line X.
  *
@@ -96,6 +86,17 @@ export const SHAPEAXIS_XYZ = 'xyz';
  */
 export const SHAPEAXIS_FACE = 'face';
 
+
+/**
+ * Converts Color4 to Color3.
+ *
+ * @param {Color} color - Color4
+ * @returns {Color} - Color3
+ */
+function color3from4(color) {
+    return new Color(color.r, color.g, color.b);
+}
+
 /**
  * The base class for all transform gizmos.
  *
@@ -138,6 +139,14 @@ class TransformGizmo extends Gizmo {
      */
     static EVENT_TRANSFORMEND = 'transform:end';
 
+    /**
+     * Internal color alpha value.
+     *
+     * @type {number}
+     * @private
+     */
+    _colorAlpha = 0.6;
+
     /**
      * Internal color for meshs.
      *
@@ -146,11 +155,11 @@ class TransformGizmo extends Gizmo {
      */
     _meshColors = {
         axis: {
-            x: colorSemi(COLOR_RED),
-            y: colorSemi(COLOR_GREEN),
-            z: colorSemi(COLOR_BLUE),
-            xyz: colorSemi(Color.WHITE),
-            face: colorSemi(Color.WHITE)
+            x: this._colorSemi(COLOR_RED),
+            y: this._colorSemi(COLOR_GREEN),
+            z: this._colorSemi(COLOR_BLUE),
+            xyz: this._colorSemi(Color.WHITE),
+            face: this._colorSemi(Color.WHITE)
         },
         hover: {
             x: COLOR_RED.clone(),
@@ -430,10 +439,38 @@ class TransformGizmo extends Gizmo {
         return this._meshColors.axis.z;
     }
 
+    /**
+     * The color alpha for all axes.
+     *
+     * @type {number}
+     */
+    set colorAlpha(value) {
+        this._colorAlpha = math.clamp(value, 0, 1);
+
+        this._meshColors.axis.x.copy(this._colorSemi(this._meshColors.axis.x));
+        this._meshColors.axis.y.copy(this._colorSemi(this._meshColors.axis.y));
+        this._meshColors.axis.z.copy(this._colorSemi(this._meshColors.axis.z));
+        this._meshColors.axis.xyz.copy(this._colorSemi(this._meshColors.axis.xyz));
+        this._meshColors.axis.face.copy(this._colorSemi(this._meshColors.axis.face));
+    }
+
+    get colorAlpha() {
+        return this._colorAlpha;
+    }
+
+    _colorSemi(color) {
+        const clone = color.clone();
+        clone.a = this._colorAlpha;
+        return clone;
+    }
+
     _updateAxisColor(axis, value) {
-        this._guideColors[axis].copy(value);
-        this._meshColors.axis[axis].copy(colorSemi(value));
-        this._meshColors.hover[axis].copy(value);
+        const color3 = color3from4(value);
+        const color4 = this._colorSemi(value);
+
+        this._guideColors[axis].copy(color3);
+        this._meshColors.axis[axis].copy(color4);
+        this._meshColors.hover[axis].copy(color3);
 
         for (const name in this._shapes) {
             this._shapes[name].hover(!!this._hoverAxis);
diff --git a/src/core/preprocessor.js b/src/core/preprocessor.js
index 43490ffc7..b2dd54370 100644
--- a/src/core/preprocessor.js
+++ b/src/core/preprocessor.js
@@ -44,17 +44,12 @@ class Preprocessor {
      * Run c-like preprocessor on the source code, and resolves the code based on the defines and ifdefs
      *
      * @param {string} source - The source code to work on.
-     * @param {Map<string, string>} [defines] - A map containing key-value pairs of define names
-     * and their values. These are used for resolving #ifdef style of directives in the source.
      * @param {Map<string, string>} [includes] - A map containing key-value pairs of include names
      * and their content. These are used for resolving #include directives in the source.
      * @param {boolean} [stripUnusedColorAttachments] - If true, strips unused color attachments.
      * @returns {string|null} Returns preprocessed source code, or null in case of error.
      */
-    static run(source, defines, includes = new Map(), stripUnusedColorAttachments = false) {
-
-        // shallow clone as we will modify the map
-        defines = defines ? new Map(defines) : new Map();
+    static run(source, includes = new Map(), stripUnusedColorAttachments = false) {
 
         // strips comments, handles // and many cases of /*
         source = source.replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '$1');
@@ -65,6 +60,7 @@ class Preprocessor {
             .join('\n');
 
         // generate defines to remove unused color attachments
+        const defines = new Map();
         if (stripUnusedColorAttachments) {
 
             // find out how many times pcFragColorX is used (see gles3.js)
diff --git a/src/deprecated/deprecated.js b/src/deprecated/deprecated.js
index 1ad88648e..e2af6dcd4 100644
--- a/src/deprecated/deprecated.js
+++ b/src/deprecated/deprecated.js
@@ -118,6 +118,9 @@ import { RigidBodyComponentSystem } from '../framework/components/rigid-body/sys
 import { basisInitialize } from '../framework/handlers/basis.js';
 import { LitShader } from '../scene/shader-lib/programs/lit-shader.js';
 
+// ScriptType alias
+export { Script as ScriptType } from '../framework/script/script.js';
+
 // CORE
 export const LINEBATCH_WORLD = 0;
 export const LINEBATCH_OVERLAY = 1;
diff --git a/src/framework/components/script/component.js b/src/framework/components/script/component.js
index c182759bf..cc918e721 100644
--- a/src/framework/components/script/component.js
+++ b/src/framework/components/script/component.js
@@ -19,7 +19,7 @@ import { Entity } from '../../entity.js';
  */
 class ScriptComponent extends Component {
     /**
-     * Fired when a {@link ScriptType} instance is created and attached to the script component.
+     * Fired when a {@link Script} instance is created and attached to the script component.
      * This event is available in two forms. They are as follows:
      *
      * 1. `create` - Fired when a script instance is created. The name of the script type and the
@@ -40,7 +40,7 @@ class ScriptComponent extends Component {
     static EVENT_CREATE = 'create';
 
     /**
-     * Fired when a {@link ScriptType} instance is destroyed and removed from the script component.
+     * Fired when a {@link Script} instance is destroyed and removed from the script component.
      * This event is available in two forms. They are as follows:
      *
      * 1. `destroy` - Fired when a script instance is destroyed. The name of the script type and
@@ -109,7 +109,7 @@ class ScriptComponent extends Component {
     static EVENT_STATE = 'state';
 
     /**
-     * Fired when the index of a {@link ScriptType} instance is changed in the script component.
+     * Fired when the index of a {@link Script} instance is changed in the script component.
      * This event is available in two forms. They are as follows:
      *
      * 1. `move` - Fired when a script instance is moved. The name of the script type, the script
@@ -130,7 +130,7 @@ class ScriptComponent extends Component {
     static EVENT_MOVE = 'move';
 
     /**
-     * Fired when a {@link ScriptType} instance had an exception. The handler is passed the script
+     * Fired when a {@link Script} instance had an exception. The handler is passed the script
      * instance, the exception and the method name that the exception originated from.
      *
      * @event
@@ -154,7 +154,7 @@ class ScriptComponent extends Component {
         /**
          * Holds all script instances for this component.
          *
-         * @type {import('../../script/script-type.js').ScriptType[]}
+         * @type {import('../../script/script.js').Script[]}
          * @private
          */
         this._scripts = [];
@@ -191,7 +191,7 @@ class ScriptComponent extends Component {
      * An array of all script instances attached to an entity. This array is read-only and should
      * not be modified by developer.
      *
-     * @type {import('../../script/script-type.js').ScriptType[]}
+     * @type {import('../../script/script.js').Script[]}
      */
     set scripts(value) {
         this._scriptsData = value;
@@ -546,8 +546,8 @@ class ScriptComponent extends Component {
     /**
      * Detect if script is attached to an entity.
      *
-     * @param {string|typeof import('../../script/script-type.js').ScriptType} nameOrType - The
-     * name or type of {@link ScriptType}.
+     * @param {string|typeof import('../../script/script.js').Script} nameOrType - The
+     * name or type of {@link Script}.
      * @returns {boolean} If script is attached to an entity.
      * @example
      * if (entity.script.has('playerController')) {
@@ -570,9 +570,9 @@ class ScriptComponent extends Component {
     /**
      * Get a script instance (if attached).
      *
-     * @param {string|typeof import('../../script/script-type.js').ScriptType} nameOrType - The
-     * name or type of {@link ScriptType}.
-     * @returns {import('../../script/script-type.js').ScriptType|null} If script is attached, the
+     * @param {string|typeof import('../../script/script.js').Script} nameOrType - The
+     * name or type of {@link Script}.
+     * @returns {import('../../script/script.js').Script|null} If script is attached, the
      * instance is returned. Otherwise null is returned.
      * @example
      * const controller = entity.script.get('playerController');
@@ -594,8 +594,8 @@ class ScriptComponent extends Component {
     /**
      * Create a script instance and attach to an entity script component.
      *
-     * @param {string|typeof import('../../script/script-type.js').ScriptType} nameOrType - The
-     * name or type of {@link ScriptType}.
+     * @param {string|typeof import('../../script/script.js').Script} nameOrType - The
+     * name or type of {@link Script}.
      * @param {object} [args] - Object with arguments for a script.
      * @param {boolean} [args.enabled] - If script instance is enabled after creation. Defaults to
      * true.
@@ -605,9 +605,9 @@ class ScriptComponent extends Component {
      * script and attributes must be initialized manually. Defaults to false.
      * @param {number} [args.ind] - The index where to insert the script instance at. Defaults to
      * -1, which means append it at the end.
-     * @returns {import('../../script/script-type.js').ScriptType|null} Returns an instance of a
-     * {@link ScriptType} if successfully attached to an entity, or null if it failed because a
-     * script with a same name has already been added or if the {@link ScriptType} cannot be found
+     * @returns {import('../../script/script.js').Script|null} Returns an instance of a
+     * {@link Script} if successfully attached to an entity, or null if it failed because a
+     * script with a same name has already been added or if the {@link Script} cannot be found
      * by name in the {@link ScriptRegistry}.
      * @example
      * entity.script.create('playerController', {
@@ -699,8 +699,8 @@ class ScriptComponent extends Component {
     /**
      * Destroy the script instance that is attached to an entity.
      *
-     * @param {string|typeof import('../../script/script-type.js').ScriptType} nameOrType - The
-     * name or type of {@link ScriptType}.
+     * @param {string|typeof import('../../script/script.js').Script} nameOrType - The
+     * name or type of {@link Script}.
      * @returns {boolean} If it was successfully destroyed.
      * @example
      * entity.script.destroy('playerController');
@@ -756,8 +756,8 @@ class ScriptComponent extends Component {
     /**
      * Swap the script instance.
      *
-     * @param {string|typeof import('../../script/script-type.js').ScriptType} nameOrType - The
-     * name or type of {@link ScriptType}.
+     * @param {string|typeof import('../../script/script.js').Script} nameOrType - The
+     * name or type of {@link Script}.
      * @returns {boolean} If it was successfully swapped.
      * @private
      */
@@ -927,8 +927,8 @@ class ScriptComponent extends Component {
     /**
      * Move script instance to different position to alter update order of scripts within entity.
      *
-     * @param {string|typeof import('../../script/script-type.js').ScriptType} nameOrType - The
-     * name or type of {@link ScriptType}.
+     * @param {string|typeof import('../../script/script.js').Script} nameOrType - The
+     * name or type of {@link Script}.
      * @param {number} ind - New position index.
      * @returns {boolean} If it was successfully moved.
      * @example
diff --git a/src/framework/entity.js b/src/framework/entity.js
index 08b808127..e4ca2cb4b 100644
--- a/src/framework/entity.js
+++ b/src/framework/entity.js
@@ -400,8 +400,8 @@ class Entity extends GraphNode {
     /**
      * Search the entity and all of its descendants for the first script instance of specified type.
      *
-     * @param {string|typeof import('./script/script-type.js').ScriptType} nameOrType - The name or type of {@link ScriptType}.
-     * @returns {import('./script/script-type.js').ScriptType|undefined} A script instance of specified type, if the entity or any of its descendants
+     * @param {string|typeof import('./script/script.js').Script} nameOrType - The name or type of {@link Script}.
+     * @returns {import('./script/script.js').Script|undefined} A script instance of specified type, if the entity or any of its descendants
      * has one. Returns undefined otherwise.
      * @example
      * // Get the first found "playerController" instance in the hierarchy tree that starts with this entity
@@ -415,8 +415,8 @@ class Entity extends GraphNode {
     /**
      * Search the entity and all of its descendants for all script instances of specified type.
      *
-     * @param {string|typeof import('./script/script-type.js').ScriptType} nameOrType - The name or type of {@link ScriptType}.
-     * @returns {import('./script/script-type.js').ScriptType[]} All script instances of specified type in the entity or any of its
+     * @param {string|typeof import('./script/script.js').Script} nameOrType - The name or type of {@link Script}.
+     * @returns {import('./script/script.js').Script[]} All script instances of specified type in the entity or any of its
      * descendants. Returns empty array if none found.
      * @example
      * // Get all "playerController" instances in the hierarchy tree that starts with this entity
diff --git a/src/framework/handlers/script.js b/src/framework/handlers/script.js
index d77f0f71f..c63caab4e 100644
--- a/src/framework/handlers/script.js
+++ b/src/framework/handlers/script.js
@@ -1,8 +1,8 @@
 import { platform } from '../../core/platform.js';
 import { script } from '../script.js';
-import { ScriptType } from '../script/script-type.js';
+import { Script } from '../script/script.js';
 import { ScriptTypes } from '../script/script-types.js';
-import { registerScript } from '../script/script.js';
+import { registerScript } from '../script/script-create.js';
 import { ResourceLoader } from './loader.js';
 
 import { ResourceHandler } from './handler.js';
@@ -154,7 +154,7 @@ class ScriptHandler extends ResourceHandler {
 
             for (const key in module) {
                 const scriptClass = module[key];
-                const extendsScriptType = scriptClass.prototype instanceof ScriptType;
+                const extendsScriptType = scriptClass.prototype instanceof Script;
 
                 if (extendsScriptType) {
 
diff --git a/src/framework/script/script-attributes.js b/src/framework/script/script-attributes.js
index 0d0dc1d90..7a718b401 100644
--- a/src/framework/script/script-attributes.js
+++ b/src/framework/script/script-attributes.js
@@ -150,8 +150,8 @@ function rawToValue(app, args, value, old) {
 
 /**
  * Container of Script Attribute definitions. Implements an interface to add/remove attributes and
- * store their definition for a {@link ScriptType}. Note: An instance of ScriptAttributes is
- * created automatically by each {@link ScriptType}.
+ * store their definition for a {@link Script}. Note: An instance of ScriptAttributes is
+ * created automatically by each {@link Script}.
  *
  * @category Script
  */
@@ -159,7 +159,7 @@ class ScriptAttributes {
     /**
      * Create a new ScriptAttributes instance.
      *
-     * @param {typeof import('./script-type.js').ScriptType} scriptType - Script Type that attributes relate to.
+     * @param {typeof import('./script.js').Script} scriptType - Script Type that attributes relate to.
      */
     constructor(scriptType) {
         this.scriptType = scriptType;
diff --git a/src/framework/script/script-create.js b/src/framework/script/script-create.js
new file mode 100644
index 000000000..cd31cf49d
--- /dev/null
+++ b/src/framework/script/script-create.js
@@ -0,0 +1,156 @@
+import { Debug } from '../../core/debug.js';
+import { EventHandler } from '../../core/event-handler.js';
+
+import { script } from '../script.js';
+import { AppBase } from '../app-base.js';
+
+import { ScriptAttributes } from './script-attributes.js';
+import { Script } from './script.js';
+import { ScriptTypes } from './script-types.js';
+
+const reservedScriptNames = new Set([
+    'system', 'entity', 'create', 'destroy', 'swap', 'move', 'data',
+    'scripts', '_scripts', '_scriptsIndex', '_scriptsData',
+    'enabled', '_oldState', 'onEnable', 'onDisable', 'onPostStateChange',
+    '_onSetEnabled', '_checkState', '_onBeforeRemove',
+    '_onInitializeAttributes', '_onInitialize', '_onPostInitialize',
+    '_onUpdate', '_onPostUpdate',
+    '_callbacks', '_callbackActive', 'has', 'get', 'on', 'off', 'fire', 'once', 'hasEvent'
+]);
+
+function getReservedScriptNames() {
+    return reservedScriptNames;
+}
+
+/**
+ * Create and register a new {@link Script}. It returns new class type (constructor function),
+ * which is auto-registered to {@link ScriptRegistry} using its name. This is the main interface to
+ * create Script Types, to define custom logic using JavaScript, that is used to create interaction
+ * for entities.
+ *
+ * @param {string} name - Unique Name of a Script Type. If a Script Type with the same name has
+ * already been registered and the new one has a `swap` method defined in its prototype, then it
+ * will perform hot swapping of existing Script Instances on entities using this new Script Type.
+ * Note: There is a reserved list of names that cannot be used, such as list below as well as some
+ * starting from `_` (underscore): system, entity, create, destroy, swap, move, scripts, onEnable,
+ * onDisable, onPostStateChange, has, on, off, fire, once, hasEvent.
+ * @param {AppBase} [app] - Optional application handler, to choose which {@link ScriptRegistry}
+ * to add a script to. By default it will use `Application.getApplication()` to get current
+ * {@link AppBase}.
+ * @returns {typeof Script|null} A class type (constructor function) that inherits {@link Script},
+ * which the developer is meant to further extend by adding attributes and prototype methods.
+ * Returns null if there was an error.
+ * @example
+ * var Turning = pc.createScript('turn');
+ *
+ * // define 'speed' attribute that is available in Editor UI
+ * Turning.attributes.add('speed', {
+ *     type: 'number',
+ *     default: 180,
+ *     placeholder: 'deg/s'
+ * });
+ *
+ * // runs every tick
+ * Turning.prototype.update = function (dt) {
+ *     this.entity.rotate(0, this.speed * dt, 0);
+ * };
+ * @category Script
+ */
+function createScript(name, app) {
+    if (script.legacy) {
+        Debug.error('This project is using the legacy script system. You cannot call pc.createScript().');
+        return null;
+    }
+
+    if (reservedScriptNames.has(name))
+        throw new Error(`Script name '${name}' is reserved, please rename the script`);
+
+    const scriptType = function (args) {
+        EventHandler.prototype.initEventHandler.call(this);
+        Script.prototype.initScriptType.call(this, args);
+    };
+
+    scriptType.prototype = Object.create(Script.prototype);
+    scriptType.prototype.constructor = scriptType;
+
+    scriptType.extend = Script.extend;
+    scriptType.attributes = new ScriptAttributes(scriptType);
+
+    registerScript(scriptType, name, app);
+    return scriptType;
+}
+
+// Editor uses this - migrate to ScriptAttributes.reservedNames and delete this
+const reservedAttributes = {};
+ScriptAttributes.reservedNames.forEach((value, value2, set) => {
+    reservedAttributes[value] = 1;
+});
+createScript.reservedAttributes = reservedAttributes;
+
+/* eslint-disable jsdoc/check-examples */
+/**
+ * Register a existing class type as a Script Type to {@link ScriptRegistry}. Useful when defining
+ * a ES6 script class that extends {@link Script} (see example).
+ *
+ * @param {typeof Script} script - The existing class type (constructor function) to be
+ * registered as a Script Type. Class must extend {@link Script} (see example). Please note: A
+ * class created using {@link createScript} is auto-registered, and should therefore not be pass
+ * into {@link registerScript} (which would result in swapping out all related script instances).
+ * @param {string} [name] - Optional unique name of the Script Type. By default it will use the
+ * same name as the existing class. If a Script Type with the same name has already been registered
+ * and the new one has a `swap` method defined in its prototype, then it will perform hot swapping
+ * of existing Script Instances on entities using this new Script Type. Note: There is a reserved
+ * list of names that cannot be used, such as list below as well as some starting from `_`
+ * (underscore): system, entity, create, destroy, swap, move, scripts, onEnable, onDisable,
+ * onPostStateChange, has, on, off, fire, once, hasEvent.
+ * @param {AppBase} [app] - Optional application handler, to choose which {@link ScriptRegistry}
+ * to register the script type to. By default it will use `Application.getApplication()` to get
+ * current {@link AppBase}.
+ * @example
+ * // define a ES6 script class
+ * class PlayerController extends pc.Script {
+ *
+ *     initialize() {
+ *         // called once on initialize
+ *     }
+ *
+ *     update(dt) {
+ *         // called each tick
+ *     }
+ * }
+ *
+ * // register the class as a script
+ * pc.registerScript(PlayerController);
+ *
+ * // declare script attributes (Must be after pc.registerScript())
+ * PlayerController.attributes.add('attribute1', {type: 'number'});
+ * @category Script
+ */
+function registerScript(script, name, app) {
+    if (script.legacy) {
+        Debug.error('This project is using the legacy script system. You cannot call pc.registerScript().');
+        return;
+    }
+
+    if (typeof script !== 'function')
+        throw new Error(`script class: '${script}' must be a constructor function (i.e. class).`);
+
+    if (!(script.prototype instanceof Script))
+        throw new Error(`script class: '${Script.__getScriptName(script)}' does not extend pc.Script.`);
+
+    name = name || script.__name || Script.__getScriptName(script);
+
+    if (reservedScriptNames.has(name))
+        throw new Error(`script name: '${name}' is reserved, please change script name`);
+
+    script.__name = name;
+
+    // add to scripts registry
+    const registry = app ? app.scripts : AppBase.getApplication().scripts;
+    registry.add(script);
+
+    ScriptTypes.push(script, script.legacy);
+}
+/* eslint-enable jsdoc/check-examples */
+
+export { createScript, registerScript, getReservedScriptNames };
diff --git a/src/framework/script/script-registry.js b/src/framework/script/script-registry.js
index 12f08201c..78db4b531 100644
--- a/src/framework/script/script-registry.js
+++ b/src/framework/script/script-registry.js
@@ -1,7 +1,7 @@
 import { EventHandler } from '../../core/event-handler.js';
 
 /**
- * Container for all {@link ScriptType}s that are available to this application. Note that
+ * Container for all {@link Script}s that are available to this application. Note that
  * PlayCanvas scripts can access the Script Registry from inside the application with
  * {@link AppBase#scripts}.
  *
@@ -9,13 +9,13 @@ import { EventHandler } from '../../core/event-handler.js';
  */
 class ScriptRegistry extends EventHandler {
     /**
-     * @type {Object<string, typeof import('./script-type.js').ScriptType>}
+     * @type {Object<string, typeof import('./script.js').Script>}
      * @private
      */
     _scripts = {};
 
     /**
-     * @type {typeof import('./script-type.js').ScriptType[]}
+     * @type {typeof import('./script.js').Script[]}
      * @private
      */
     _list = [];
@@ -37,12 +37,12 @@ class ScriptRegistry extends EventHandler {
     }
 
     /**
-     * Add {@link ScriptType} to registry. Note: when {@link createScript} is called, it will add
-     * the {@link ScriptType} to the registry automatically. If a script already exists in
+     * Add {@link Script} to registry. Note: when {@link createScript} is called, it will add
+     * the {@link Script} to the registry automatically. If a script already exists in
      * registry, and the new script has a `swap` method defined, it will perform code hot swapping
      * automatically in async manner.
      *
-     * @param {typeof import('./script-type.js').ScriptType} script - Script Type that is created
+     * @param {typeof import('./script.js').Script} script - Script Type that is created
      * using {@link createScript}.
      * @returns {boolean} True if added for the first time or false if script already exists.
      * @example
@@ -146,10 +146,10 @@ class ScriptRegistry extends EventHandler {
     }
 
     /**
-     * Remove {@link ScriptType}.
+     * Remove {@link Script}.
      *
-     * @param {string|typeof import('./script-type.js').ScriptType} nameOrType - The name or type
-     * of {@link ScriptType}.
+     * @param {string|typeof import('./script.js').Script} nameOrType - The name or type
+     * of {@link Script}.
      * @returns {boolean} True if removed or False if already not in registry.
      * @example
      * app.scripts.remove('playerController');
@@ -178,10 +178,10 @@ class ScriptRegistry extends EventHandler {
     }
 
     /**
-     * Get {@link ScriptType} by name.
+     * Get {@link Script} by name.
      *
-     * @param {string} name - Name of a {@link ScriptType}.
-     * @returns {typeof import('./script-type.js').ScriptType} The Script Type if it exists in the
+     * @param {string} name - Name of a {@link Script}.
+     * @returns {typeof import('./script.js').Script} The Script Type if it exists in the
      * registry or null otherwise.
      * @example
      * var PlayerController = app.scripts.get('playerController');
@@ -191,11 +191,11 @@ class ScriptRegistry extends EventHandler {
     }
 
     /**
-     * Check if a {@link ScriptType} with the specified name is in the registry.
+     * Check if a {@link Script} with the specified name is in the registry.
      *
-     * @param {string|typeof import('./script-type.js').ScriptType} nameOrType - The name or type
-     * of {@link ScriptType}.
-     * @returns {boolean} True if {@link ScriptType} is in registry.
+     * @param {string|typeof import('./script.js').Script} nameOrType - The name or type
+     * of {@link Script}.
+     * @returns {boolean} True if {@link Script} is in registry.
      * @example
      * if (app.scripts.has('playerController')) {
      *     // playerController is in pc.ScriptRegistry
@@ -212,9 +212,9 @@ class ScriptRegistry extends EventHandler {
     }
 
     /**
-     * Get list of all {@link ScriptType}s from registry.
+     * Get list of all {@link Script}s from registry.
      *
-     * @returns {Array<typeof import('./script-type.js').ScriptType>} list of all {@link ScriptType}s
+     * @returns {Array<typeof import('./script.js').Script>} list of all {@link Script}s
      * in registry.
      * @example
      * // logs array of all Script Type names available in registry
diff --git a/src/framework/script/script-type.js b/src/framework/script/script-type.js
deleted file mode 100644
index 3a1ff14a3..000000000
--- a/src/framework/script/script-type.js
+++ /dev/null
@@ -1,393 +0,0 @@
-import { Debug } from '../../core/debug.js';
-import { EventHandler } from '../../core/event-handler.js';
-
-import { SCRIPT_INITIALIZE, SCRIPT_POST_INITIALIZE } from './constants.js';
-import { ScriptAttributes } from './script-attributes.js';
-
-const funcNameRegex = new RegExp('^\\s*function(?:\\s|\\s*\\/\\*.*\\*\\/\\s*)+([^\\(\\s\\/]*)\\s*');
-
-/**
- * Represents the type of a script. It is returned by {@link createScript}. Also referred to as
- * Script Type.
- *
- * The type is to be extended using its JavaScript prototype. There is a list of methods that will
- * be executed by the engine on instances of this type, such as:
- *
- * - `initialize`
- * - `postInitialize`
- * - `update`
- * - `postUpdate`
- * - `swap`
- *
- * `initialize` and `postInitialize` - are called (if defined) when a script is about to run for
- * the first time - `postInitialize` will run after all `initialize` methods are executed in the
- * same tick or enabling chain of actions.
- *
- * `update` and `postUpdate` - are called (if defined) for enabled (running state) scripts on each
- * tick.
- *
- * `swap` - is called when a ScriptType that already exists in the registry gets redefined. If the
- * new ScriptType has a `swap` method in its prototype, then it will be executed to perform hot-
- * reload at runtime.
- *
- * @category Script
- */
-class ScriptType extends EventHandler {
-    /**
-     * Fired when a script instance becomes enabled.
-     *
-     * @event
-     * @example
-     * PlayerController.prototype.initialize = function () {
-     *     this.on('enable', () => {
-     *         // Script Instance is now enabled
-     *     });
-     * };
-     */
-    static EVENT_ENABLE = 'enable';
-
-    /**
-     * Fired when a script instance becomes disabled.
-     *
-     * @event
-     * @example
-     * PlayerController.prototype.initialize = function () {
-     *     this.on('disable', () => {
-     *         // Script Instance is now disabled
-     *     });
-     * };
-     */
-    static EVENT_DISABLE = 'disable';
-
-    /**
-     * Fired when a script instance changes state to enabled or disabled. The handler is passed a
-     * boolean parameter that states whether the script instance is now enabled or disabled.
-     *
-     * @event
-     * @example
-     * PlayerController.prototype.initialize = function () {
-     *     this.on('state', (enabled) => {
-     *         console.log(`Script Instance is now ${enabled ? 'enabled' : 'disabled'}`);
-     *     });
-     * };
-     */
-    static EVENT_STATE = 'state';
-
-    /**
-     * Fired when a script instance is destroyed and removed from component.
-     *
-     * @event
-     * @example
-     * PlayerController.prototype.initialize = function () {
-     *     this.on('destroy', () => {
-     *         // no longer part of the entity
-     *         // this is a good place to clean up allocated resources used by the script
-     *     });
-     * };
-     */
-    static EVENT_DESTROY = 'destroy';
-
-    /**
-     * Fired when script attributes have changed. This event is available in two forms. They are as follows:
-     *
-     * 1. `attr` - Fired for any attribute change. The handler is passed the name of the attribute
-     * that changed, the value of the attribute before the change and the value of the attribute
-     * after the change.
-     * 2. `attr:[name]` - Fired for a specific attribute change. The handler is passed the value of
-     * the attribute before the change and the value of the attribute after the change.
-     *
-     * @event
-     * @example
-     * PlayerController.prototype.initialize = function () {
-     *     this.on('attr', (name, newValue, oldValue) => {
-     *         console.log(`Attribute '${name}' changed from '${oldValue}' to '${newValue}'`);
-     *     });
-     * };
-     * @example
-     * PlayerController.prototype.initialize = function () {
-     *     this.on('attr:speed', (newValue, oldValue) => {
-     *         console.log(`Attribute 'speed' changed from '${oldValue}' to '${newValue}'`);
-     *     });
-     * };
-     */
-    static EVENT_ATTR = 'attr';
-
-    /**
-     * Fired when a script instance had an exception. The script instance will be automatically
-     * disabled. The handler is passed an {@link Error} object containing the details of the
-     * exception and the name of the method that threw the exception.
-     *
-     * @event
-     * @example
-     * PlayerController.prototype.initialize = function () {
-     *     this.on('error', (err, method) => {
-     *         // caught an exception
-     *         console.log(err.stack);
-     *     });
-     * };
-     */
-    static EVENT_ERROR = 'error';
-
-    /**
-     * The {@link AppBase} that the instance of this type belongs to.
-     *
-     * @type {import('../app-base.js').AppBase}
-     */
-    app;
-
-    /**
-     * The {@link Entity} that the instance of this type belongs to.
-     *
-     * @type {import('../entity.js').Entity}
-     */
-    entity;
-
-    /** @private */
-    _enabled;
-
-    /** @private */
-    _enabledOld;
-
-    /** @private */
-    _initialized;
-
-    /** @private */
-    _postInitialized;
-
-    /** @private */
-    __destroyed;
-
-    /** @private */
-    __attributes;
-
-    /** @private */
-    __attributesRaw;
-
-    /** @private */
-    __scriptType;
-
-    /**
-     * The order in the script component that the methods of this script instance will run
-     * relative to other script instances in the component.
-     *
-     * @type {number}
-     * @private
-     */
-    __executionOrder;
-
-    /**
-     * Create a new ScriptType instance.
-     *
-     * @param {object} args - The input arguments object.
-     * @param {import('../app-base.js').AppBase} args.app - The {@link AppBase} that is running the
-     * script.
-     * @param {import('../entity.js').Entity} args.entity - The {@link Entity} that the script is
-     * attached to.
-     */
-    constructor(args) {
-        super();
-        this.initScriptType(args);
-    }
-
-    /**
-     * True if the instance of this type is in running state. False when script is not running,
-     * because the Entity or any of its parents are disabled or the {@link ScriptComponent} is
-     * disabled or the Script Instance is disabled. When disabled no update methods will be called
-     * on each tick. initialize and postInitialize methods will run once when the script instance
-     * is in `enabled` state during app tick.
-     *
-     * @type {boolean}
-     */
-    set enabled(value) {
-        this._enabled = !!value;
-
-        if (this.enabled === this._enabledOld) return;
-
-        this._enabledOld = this.enabled;
-        this.fire(this.enabled ? 'enable' : 'disable');
-        this.fire('state', this.enabled);
-
-        // initialize script if not initialized yet and script is enabled
-        if (!this._initialized && this.enabled) {
-            this._initialized = true;
-
-            this.__initializeAttributes(true);
-
-            if (this.initialize)
-                this.entity.script._scriptMethod(this, SCRIPT_INITIALIZE);
-        }
-
-        // post initialize script if not post initialized yet and still enabled
-        // (initialize might have disabled the script so check this.enabled again)
-        // Warning: Do not do this if the script component is currently being enabled
-        // because in this case post initialize must be called after all the scripts
-        // in the script component have been initialized first
-        if (this._initialized && !this._postInitialized && this.enabled && !this.entity.script._beingEnabled) {
-            this._postInitialized = true;
-
-            if (this.postInitialize)
-                this.entity.script._scriptMethod(this, SCRIPT_POST_INITIALIZE);
-        }
-    }
-
-    get enabled() {
-        return this._enabled && !this._destroyed && this.entity.script.enabled && this.entity.enabled;
-    }
-
-    /**
-     * @param {{entity: import('../entity.js').Entity, app: import('../app-base.js').AppBase}} args -
-     * The entity and app.
-     * @private
-     */
-    initScriptType(args) {
-        const script = this.constructor; // get script type, i.e. function (class)
-        Debug.assert(args && args.app && args.entity, `script [${script.__name}] has missing arguments in constructor`);
-
-        this.app = args.app;
-        this.entity = args.entity;
-
-        this._enabled = typeof args.enabled === 'boolean' ? args.enabled : true;
-        this._enabledOld = this.enabled;
-
-        this.__destroyed = false;
-        this.__attributes = { };
-        this.__attributesRaw = args.attributes || { }; // need at least an empty object to make sure default attributes are initialized
-        this.__scriptType = script;
-        this.__executionOrder = -1;
-    }
-
-    /**
-     * Name of a Script Type.
-     *
-     * @type {string}
-     * @private
-     */
-    static __name = null; // Will be assigned when calling createScript or registerScript.
-
-    /**
-     * @param {*} constructorFn - The constructor function of the script type.
-     * @returns {string} The script name.
-     * @private
-     */
-    static __getScriptName(constructorFn) {
-        if (typeof constructorFn !== 'function') return undefined;
-        if ('name' in Function.prototype) return constructorFn.name;
-        if (constructorFn === Function || constructorFn === Function.prototype.constructor) return 'Function';
-        const match = ('' + constructorFn).match(funcNameRegex);
-        return match ? match[1] : undefined;
-    }
-
-    /**
-     * Name of a Script Type.
-     *
-     * @type {string|null}
-     */
-    static get scriptName() {
-        return this.__name;
-    }
-
-    /**
-     * The interface to define attributes for Script Types. Refer to {@link ScriptAttributes}.
-     *
-     * @type {ScriptAttributes}
-     * @example
-     * var PlayerController = pc.createScript('playerController');
-     *
-     * PlayerController.attributes.add('speed', {
-     *     type: 'number',
-     *     title: 'Speed',
-     *     placeholder: 'km/h',
-     *     default: 22.2
-     * });
-     */
-    static get attributes() {
-        if (!this.hasOwnProperty('__attributes')) this.__attributes = new ScriptAttributes(this);
-        return this.__attributes;
-    }
-
-    /**
-     * @param {boolean} [force] - Set to true to force initialization of the attributes.
-     * @private
-     */
-    __initializeAttributes(force) {
-        if (!force && !this.__attributesRaw)
-            return;
-
-        // set attributes values
-        for (const key in this.__scriptType.attributes.index) {
-            if (this.__attributesRaw && this.__attributesRaw.hasOwnProperty(key)) {
-                this[key] = this.__attributesRaw[key];
-            } else if (!this.__attributes.hasOwnProperty(key)) {
-                if (this.__scriptType.attributes.index[key].hasOwnProperty('default')) {
-                    this[key] = this.__scriptType.attributes.index[key].default;
-                } else {
-                    this[key] = null;
-                }
-            }
-        }
-
-        this.__attributesRaw = null;
-    }
-
-    /**
-     * Shorthand function to extend Script Type prototype with list of methods.
-     *
-     * @param {object} methods - Object with methods, where key - is name of method, and value - is function.
-     * @example
-     * var PlayerController = pc.createScript('playerController');
-     *
-     * PlayerController.extend({
-     *     initialize: function () {
-     *         // called once on initialize
-     *     },
-     *     update: function (dt) {
-     *         // called each tick
-     *     }
-     * });
-     */
-    static extend(methods) {
-        for (const key in methods) {
-            if (!methods.hasOwnProperty(key))
-                continue;
-
-            this.prototype[key] = methods[key];
-        }
-    }
-
-    /**
-     * @function
-     * @name ScriptType#[initialize]
-     * @description Called when script is about to run for the first time.
-     */
-
-    /**
-     * @function
-     * @name ScriptType#[postInitialize]
-     * @description Called after all initialize methods are executed in the same tick or enabling chain of actions.
-     */
-
-    /**
-     * @function
-     * @name ScriptType#[update]
-     * @description Called for enabled (running state) scripts on each tick.
-     * @param {number} dt - The delta time in seconds since the last frame.
-     */
-
-    /**
-     * @function
-     * @name ScriptType#[postUpdate]
-     * @description Called for enabled (running state) scripts on each tick, after update.
-     * @param {number} dt - The delta time in seconds since the last frame.
-     */
-
-    /**
-     * @function
-     * @name ScriptType#[swap]
-     * @description Called when a ScriptType that already exists in the registry
-     * gets redefined. If the new ScriptType has a `swap` method in its prototype,
-     * then it will be executed to perform hot-reload at runtime.
-     * @param {ScriptType} old - Old instance of the scriptType to copy data to the new instance.
-     */
-}
-
-export { ScriptType };
diff --git a/src/framework/script/script.js b/src/framework/script/script.js
index b25939d38..a136619f1 100644
--- a/src/framework/script/script.js
+++ b/src/framework/script/script.js
@@ -1,156 +1,404 @@
 import { Debug } from '../../core/debug.js';
 import { EventHandler } from '../../core/event-handler.js';
 
-import { script } from '../script.js';
-import { AppBase } from '../app-base.js';
-
+import { SCRIPT_INITIALIZE, SCRIPT_POST_INITIALIZE } from './constants.js';
 import { ScriptAttributes } from './script-attributes.js';
-import { ScriptType } from './script-type.js';
-import { ScriptTypes } from './script-types.js';
-
-const reservedScriptNames = new Set([
-    'system', 'entity', 'create', 'destroy', 'swap', 'move', 'data',
-    'scripts', '_scripts', '_scriptsIndex', '_scriptsData',
-    'enabled', '_oldState', 'onEnable', 'onDisable', 'onPostStateChange',
-    '_onSetEnabled', '_checkState', '_onBeforeRemove',
-    '_onInitializeAttributes', '_onInitialize', '_onPostInitialize',
-    '_onUpdate', '_onPostUpdate',
-    '_callbacks', '_callbackActive', 'has', 'get', 'on', 'off', 'fire', 'once', 'hasEvent'
-]);
-
-function getReservedScriptNames() {
-    return reservedScriptNames;
-}
+
+const funcNameRegex = new RegExp('^\\s*function(?:\\s|\\s*\\/\\*.*\\*\\/\\s*)+([^\\(\\s\\/]*)\\s*');
 
 /**
- * Create and register a new {@link ScriptType}. It returns new class type (constructor function),
- * which is auto-registered to {@link ScriptRegistry} using its name. This is the main interface to
- * create Script Types, to define custom logic using JavaScript, that is used to create interaction
- * for entities.
+ * The Script class is a base class that you must extend to receive
+ * {@link https://developer.playcanvas.com/user-manual/scripting/anatomy/ various lifecycle updates}
+ * from the engine.
+ *
+ * You can create a Script using either {@link createScript} or by extending the class directly.
+ *
+ * ```javascript
+ * import { Script } from 'playcanvas';
+ * class Rotator extends Script {
+ *     update() {
+ *         this.entity.rotate(0, 0.1, 0);
+ *     }
+ * }
+ * ```
+ * The following methods are called if they exist on the Script instance:
+ *
+ * - `initialize`
+ * - `postInitialize`
+ * - `update`
+ * - `postUpdate`
+ * - `swap`
  *
- * @param {string} name - Unique Name of a Script Type. If a Script Type with the same name has
- * already been registered and the new one has a `swap` method defined in its prototype, then it
- * will perform hot swapping of existing Script Instances on entities using this new Script Type.
- * Note: There is a reserved list of names that cannot be used, such as list below as well as some
- * starting from `_` (underscore): system, entity, create, destroy, swap, move, scripts, onEnable,
- * onDisable, onPostStateChange, has, on, off, fire, once, hasEvent.
- * @param {AppBase} [app] - Optional application handler, to choose which {@link ScriptRegistry}
- * to add a script to. By default it will use `Application.getApplication()` to get current
- * {@link AppBase}.
- * @returns {typeof ScriptType|null} A class type (constructor function) that inherits {@link ScriptType},
- * which the developer is meant to further extend by adding attributes and prototype methods.
- * Returns null if there was an error.
- * @example
- * var Turning = pc.createScript('turn');
+ * `initialize` and `postInitialize` - are called (if defined) when a script is about to run for
+ * the first time - `postInitialize` will run after all `initialize` methods are executed in the
+ * same tick or enabling chain of actions.
  *
- * // define 'speed' attribute that is available in Editor UI
- * Turning.attributes.add('speed', {
- *     type: 'number',
- *     default: 180,
- *     placeholder: 'deg/s'
- * });
+ * `update` and `postUpdate` - are called (if defined) for enabled (running state) scripts on each
+ * tick.
  *
- * // runs every tick
- * Turning.prototype.update = function (dt) {
- *     this.entity.rotate(0, this.speed * dt, 0);
- * };
+ * `swap` - is called when a Script that already exists in the registry gets redefined. If the
+ * new Script has a `swap` method in its prototype, then it will be executed to perform hot-
+ * reload at runtime.
+ *
+ * @see {@link https://developer.playcanvas.com/user-manual/scripting/anatomy/} for more information.
  * @category Script
  */
-function createScript(name, app) {
-    if (script.legacy) {
-        Debug.error('This project is using the legacy script system. You cannot call pc.createScript().');
-        return null;
+class Script extends EventHandler {
+    /**
+     * Fired when a script instance becomes enabled.
+     *
+     * @event
+     * @example
+     * PlayerController.prototype.initialize = function () {
+     *     this.on('enable', () => {
+     *         // Script Instance is now enabled
+     *     });
+     * };
+     */
+    static EVENT_ENABLE = 'enable';
+
+    /**
+     * Fired when a script instance becomes disabled.
+     *
+     * @event
+     * @example
+     * PlayerController.prototype.initialize = function () {
+     *     this.on('disable', () => {
+     *         // Script Instance is now disabled
+     *     });
+     * };
+     */
+    static EVENT_DISABLE = 'disable';
+
+    /**
+     * Fired when a script instance changes state to enabled or disabled. The handler is passed a
+     * boolean parameter that states whether the script instance is now enabled or disabled.
+     *
+     * @event
+     * @example
+     * PlayerController.prototype.initialize = function () {
+     *     this.on('state', (enabled) => {
+     *         console.log(`Script Instance is now ${enabled ? 'enabled' : 'disabled'}`);
+     *     });
+     * };
+     */
+    static EVENT_STATE = 'state';
+
+    /**
+     * Fired when a script instance is destroyed and removed from component.
+     *
+     * @event
+     * @example
+     * PlayerController.prototype.initialize = function () {
+     *     this.on('destroy', () => {
+     *         // no longer part of the entity
+     *         // this is a good place to clean up allocated resources used by the script
+     *     });
+     * };
+     */
+    static EVENT_DESTROY = 'destroy';
+
+    /**
+     * Fired when script attributes have changed. This event is available in two forms. They are as follows:
+     *
+     * 1. `attr` - Fired for any attribute change. The handler is passed the name of the attribute
+     * that changed, the value of the attribute before the change and the value of the attribute
+     * after the change.
+     * 2. `attr:[name]` - Fired for a specific attribute change. The handler is passed the value of
+     * the attribute before the change and the value of the attribute after the change.
+     *
+     * @event
+     * @example
+     * PlayerController.prototype.initialize = function () {
+     *     this.on('attr', (name, newValue, oldValue) => {
+     *         console.log(`Attribute '${name}' changed from '${oldValue}' to '${newValue}'`);
+     *     });
+     * };
+     * @example
+     * PlayerController.prototype.initialize = function () {
+     *     this.on('attr:speed', (newValue, oldValue) => {
+     *         console.log(`Attribute 'speed' changed from '${oldValue}' to '${newValue}'`);
+     *     });
+     * };
+     */
+    static EVENT_ATTR = 'attr';
+
+    /**
+     * Fired when a script instance had an exception. The script instance will be automatically
+     * disabled. The handler is passed an {@link Error} object containing the details of the
+     * exception and the name of the method that threw the exception.
+     *
+     * @event
+     * @example
+     * PlayerController.prototype.initialize = function () {
+     *     this.on('error', (err, method) => {
+     *         // caught an exception
+     *         console.log(err.stack);
+     *     });
+     * };
+     */
+    static EVENT_ERROR = 'error';
+
+    /**
+     * The {@link AppBase} that the instance of this type belongs to.
+     *
+     * @type {import('../app-base.js').AppBase}
+     */
+    app;
+
+    /**
+     * The {@link Entity} that the instance of this type belongs to.
+     *
+     * @type {import('../entity.js').Entity}
+     */
+    entity;
+
+    /** @private */
+    _enabled;
+
+    /** @private */
+    _enabledOld;
+
+    /** @private */
+    _initialized;
+
+    /** @private */
+    _postInitialized;
+
+    /** @private */
+    __destroyed;
+
+    /** @private */
+    __attributes;
+
+    /** @private */
+    __attributesRaw;
+
+    /** @private */
+    __scriptType;
+
+    /**
+     * The order in the script component that the methods of this script instance will run
+     * relative to other script instances in the component.
+     *
+     * @type {number}
+     * @private
+     */
+    __executionOrder;
+
+    /**
+     * Create a new Script instance.
+     *
+     * @param {object} args - The input arguments object.
+     * @param {import('../app-base.js').AppBase} args.app - The {@link AppBase} that is running the
+     * script.
+     * @param {import('../entity.js').Entity} args.entity - The {@link Entity} that the script is
+     * attached to.
+     */
+    constructor(args) {
+        super();
+        this.initScriptType(args);
     }
 
-    if (reservedScriptNames.has(name))
-        throw new Error(`Script name '${name}' is reserved, please rename the script`);
+    /**
+     * True if the instance of this type is in running state. False when script is not running,
+     * because the Entity or any of its parents are disabled or the {@link ScriptComponent} is
+     * disabled or the Script Instance is disabled. When disabled no update methods will be called
+     * on each tick. initialize and postInitialize methods will run once when the script instance
+     * is in `enabled` state during app tick.
+     *
+     * @type {boolean}
+     */
+    set enabled(value) {
+        this._enabled = !!value;
 
-    const scriptType = function (args) {
-        EventHandler.prototype.initEventHandler.call(this);
-        ScriptType.prototype.initScriptType.call(this, args);
-    };
+        if (this.enabled === this._enabledOld) return;
 
-    scriptType.prototype = Object.create(ScriptType.prototype);
-    scriptType.prototype.constructor = scriptType;
+        this._enabledOld = this.enabled;
+        this.fire(this.enabled ? 'enable' : 'disable');
+        this.fire('state', this.enabled);
 
-    scriptType.extend = ScriptType.extend;
-    scriptType.attributes = new ScriptAttributes(scriptType);
+        // initialize script if not initialized yet and script is enabled
+        if (!this._initialized && this.enabled) {
+            this._initialized = true;
 
-    registerScript(scriptType, name, app);
-    return scriptType;
-}
+            this.__initializeAttributes(true);
 
-// Editor uses this - migrate to ScriptAttributes.reservedNames and delete this
-const reservedAttributes = {};
-ScriptAttributes.reservedNames.forEach((value, value2, set) => {
-    reservedAttributes[value] = 1;
-});
-createScript.reservedAttributes = reservedAttributes;
+            if (this.initialize)
+                this.entity.script._scriptMethod(this, SCRIPT_INITIALIZE);
+        }
 
-/* eslint-disable jsdoc/check-examples */
-/**
- * Register a existing class type as a Script Type to {@link ScriptRegistry}. Useful when defining
- * a ES6 script class that extends {@link ScriptType} (see example).
- *
- * @param {typeof ScriptType} script - The existing class type (constructor function) to be
- * registered as a Script Type. Class must extend {@link ScriptType} (see example). Please note: A
- * class created using {@link createScript} is auto-registered, and should therefore not be pass
- * into {@link registerScript} (which would result in swapping out all related script instances).
- * @param {string} [name] - Optional unique name of the Script Type. By default it will use the
- * same name as the existing class. If a Script Type with the same name has already been registered
- * and the new one has a `swap` method defined in its prototype, then it will perform hot swapping
- * of existing Script Instances on entities using this new Script Type. Note: There is a reserved
- * list of names that cannot be used, such as list below as well as some starting from `_`
- * (underscore): system, entity, create, destroy, swap, move, scripts, onEnable, onDisable,
- * onPostStateChange, has, on, off, fire, once, hasEvent.
- * @param {AppBase} [app] - Optional application handler, to choose which {@link ScriptRegistry}
- * to register the script type to. By default it will use `Application.getApplication()` to get
- * current {@link AppBase}.
- * @example
- * // define a ES6 script class
- * class PlayerController extends pc.ScriptType {
- *
- *     initialize() {
- *         // called once on initialize
- *     }
- *
- *     update(dt) {
- *         // called each tick
- *     }
- * }
- *
- * // register the class as a script
- * pc.registerScript(PlayerController);
- *
- * // declare script attributes (Must be after pc.registerScript())
- * PlayerController.attributes.add('attribute1', {type: 'number'});
- * @category Script
- */
-function registerScript(script, name, app) {
-    if (script.legacy) {
-        Debug.error('This project is using the legacy script system. You cannot call pc.registerScript().');
-        return;
+        // post initialize script if not post initialized yet and still enabled
+        // (initialize might have disabled the script so check this.enabled again)
+        // Warning: Do not do this if the script component is currently being enabled
+        // because in this case post initialize must be called after all the scripts
+        // in the script component have been initialized first
+        if (this._initialized && !this._postInitialized && this.enabled && !this.entity.script._beingEnabled) {
+            this._postInitialized = true;
+
+            if (this.postInitialize)
+                this.entity.script._scriptMethod(this, SCRIPT_POST_INITIALIZE);
+        }
+    }
+
+    get enabled() {
+        return this._enabled && !this._destroyed && this.entity.script.enabled && this.entity.enabled;
     }
 
-    if (typeof script !== 'function')
-        throw new Error(`script class: '${script}' must be a constructor function (i.e. class).`);
+    /**
+     * @param {{entity: import('../entity.js').Entity, app: import('../app-base.js').AppBase}} args -
+     * The entity and app.
+     * @private
+     */
+    initScriptType(args) {
+        const script = this.constructor; // get script type, i.e. function (class)
+        Debug.assert(args && args.app && args.entity, `script [${script.__name}] has missing arguments in constructor`);
 
-    if (!(script.prototype instanceof ScriptType))
-        throw new Error(`script class: '${ScriptType.__getScriptName(script)}' does not extend pc.ScriptType.`);
+        this.app = args.app;
+        this.entity = args.entity;
+
+        this._enabled = typeof args.enabled === 'boolean' ? args.enabled : true;
+        this._enabledOld = this.enabled;
+
+        this.__destroyed = false;
+        this.__attributes = { };
+        this.__attributesRaw = args.attributes || { }; // need at least an empty object to make sure default attributes are initialized
+        this.__scriptType = script;
+        this.__executionOrder = -1;
+    }
+
+    /**
+     * Name of a Script.
+     *
+     * @type {string}
+     * @private
+     */
+    static __name = null; // Will be assigned when calling createScript or registerScript.
+
+    /**
+     * @param {*} constructorFn - The constructor function of the script type.
+     * @returns {string} The script name.
+     * @private
+     */
+    static __getScriptName(constructorFn) {
+        if (typeof constructorFn !== 'function') return undefined;
+        if ('name' in Function.prototype) return constructorFn.name;
+        if (constructorFn === Function || constructorFn === Function.prototype.constructor) return 'Function';
+        const match = ('' + constructorFn).match(funcNameRegex);
+        return match ? match[1] : undefined;
+    }
+
+    /**
+     * Name of a Script Type.
+     *
+     * @type {string|null}
+     */
+    static get scriptName() {
+        return this.__name;
+    }
+
+    /**
+     * The interface to define attributes for Script Types. Refer to {@link ScriptAttributes}.
+     *
+     * @type {ScriptAttributes}
+     * @example
+     * var PlayerController = pc.createScript('playerController');
+     *
+     * PlayerController.attributes.add('speed', {
+     *     type: 'number',
+     *     title: 'Speed',
+     *     placeholder: 'km/h',
+     *     default: 22.2
+     * });
+     */
+    static get attributes() {
+        if (!this.hasOwnProperty('__attributes')) this.__attributes = new ScriptAttributes(this);
+        return this.__attributes;
+    }
+
+    /**
+     * @param {boolean} [force] - Set to true to force initialization of the attributes.
+     * @private
+     */
+    __initializeAttributes(force) {
+        if (!force && !this.__attributesRaw)
+            return;
+
+        // set attributes values
+        for (const key in this.__scriptType.attributes.index) {
+            if (this.__attributesRaw && this.__attributesRaw.hasOwnProperty(key)) {
+                this[key] = this.__attributesRaw[key];
+            } else if (!this.__attributes.hasOwnProperty(key)) {
+                if (this.__scriptType.attributes.index[key].hasOwnProperty('default')) {
+                    this[key] = this.__scriptType.attributes.index[key].default;
+                } else {
+                    this[key] = null;
+                }
+            }
+        }
+
+        this.__attributesRaw = null;
+    }
+
+    /**
+     * Shorthand function to extend Script Type prototype with list of methods.
+     *
+     * @param {object} methods - Object with methods, where key - is name of method, and value - is function.
+     * @example
+     * var PlayerController = pc.createScript('playerController');
+     *
+     * PlayerController.extend({
+     *     initialize: function () {
+     *         // called once on initialize
+     *     },
+     *     update: function (dt) {
+     *         // called each tick
+     *     }
+     * });
+     */
+    static extend(methods) {
+        for (const key in methods) {
+            if (!methods.hasOwnProperty(key))
+                continue;
+
+            this.prototype[key] = methods[key];
+        }
+    }
 
-    name = name || script.__name || ScriptType.__getScriptName(script);
+    /**
+     * @function
+     * @name Script#[initialize]
+     * @description Called when script is about to run for the first time.
+     */
 
-    if (reservedScriptNames.has(name))
-        throw new Error(`script name: '${name}' is reserved, please change script name`);
+    /**
+     * @function
+     * @name Script#[postInitialize]
+     * @description Called after all initialize methods are executed in the same tick or enabling chain of actions.
+     */
 
-    script.__name = name;
+    /**
+     * @function
+     * @name Script#[update]
+     * @description Called for enabled (running state) scripts on each tick.
+     * @param {number} dt - The delta time in seconds since the last frame.
+     */
 
-    // add to scripts registry
-    const registry = app ? app.scripts : AppBase.getApplication().scripts;
-    registry.add(script);
+    /**
+     * @function
+     * @name Script#[postUpdate]
+     * @description Called for enabled (running state) scripts on each tick, after update.
+     * @param {number} dt - The delta time in seconds since the last frame.
+     */
 
-    ScriptTypes.push(script, script.legacy);
+    /**
+     * @function
+     * @name Script#[swap]
+     * @description Called when a Script that already exists in the registry
+     * gets redefined. If the new Script has a `swap` method in its prototype,
+     * then it will be executed to perform hot-reload at runtime.
+     * @param {Script} old - Old instance of the script to copy data to the new instance.
+     */
 }
-/* eslint-enable jsdoc/check-examples */
 
-export { createScript, registerScript, getReservedScriptNames };
+export { Script };
diff --git a/src/index.js b/src/index.js
index f4d29cf8f..29579aec0 100644
--- a/src/index.js
+++ b/src/index.js
@@ -349,10 +349,10 @@ export { ElementInput, ElementInputEvent, ElementMouseEvent, ElementSelectEvent,
 export { JsonStandardMaterialParser } from './framework/parsers/material/json-standard-material.js';
 
 // FRAMEWORK /SCRIPTS
-export { createScript, registerScript, getReservedScriptNames } from './framework/script/script.js';
+export { createScript, registerScript, getReservedScriptNames } from './framework/script/script-create.js';
 export { ScriptAttributes } from './framework/script/script-attributes.js';
 export { ScriptRegistry } from './framework/script/script-registry.js';
-export { ScriptType } from './framework/script/script-type.js';
+export { Script } from './framework/script/script.js';
 
 // FRAMEWORK / LOCALIZATION
 export { I18n } from './framework/i18n/i18n.js';
diff --git a/src/platform/graphics/shader-utils.js b/src/platform/graphics/shader-utils.js
index c35bb59ae..d60eca621 100644
--- a/src/platform/graphics/shader-utils.js
+++ b/src/platform/graphics/shader-utils.js
@@ -44,24 +44,22 @@ class ShaderUtils {
      * @param {object} [options.attributes] - Attributes. Will be extracted from the vertexCode if
      * not provided.
      * @param {string} options.vertexCode - The vertex shader code.
-     * @param {string} [options.vertexDefines] - The vertex shader defines.
      * @param {string} [options.vertexExtensions] - The vertex shader extensions code.
      * @param {string} [options.fragmentCode] - The fragment shader code.
-     * @param {string} [options.fragmentDefines] - The fragment shader defines.
      * @param {string} [options.fragmentExtensions] - The fragment shader extensions code.
      * @param {string} [options.fragmentPreamble] - The preamble string for the fragment shader.
      * @param {boolean} [options.useTransformFeedback] - Whether to use transform feedback. Defaults
      * to false.
-     * @param {Map<string, string>} [options.vertexIncludesMap] - A map containing key-value pairs of
+     * @param {Map<string, string>} [options.vertexIncludes] - A map containing key-value pairs of
      * include names and their content. These are used for resolving #include directives in the
      * vertex shader source.
-     * @param {Map<string, string>} [options.vertexDefinesMap] - A map containing key-value pairs of
+     * @param {Map<string, string>} [options.vertexDefines] - A map containing key-value pairs of
      * define names and their values. These are used for resolving #ifdef style of directives in the
      * vertex code.
-     * @param {Map<string, string>} [options.fragmentIncludesMap] - A map containing key-value pairs
+     * @param {Map<string, string>} [options.fragmentIncludes] - A map containing key-value pairs
      * of include names and their content. These are used for resolving #include directives in the
      * fragment shader source.
-     * @param {Map<string, string>} [options.fragmentDefinesMap] - A map containing key-value pairs of
+     * @param {Map<string, string>} [options.fragmentDefines] - A map containing key-value pairs of
      * define names and their values. These are used for resolving #ifdef style of directives in the
      * fragment code.
      * @param {string | string[]} [options.fragmentOutputTypes] - Fragment shader output types,
@@ -71,6 +69,10 @@ class ShaderUtils {
      */
     static createDefinition(device, options) {
         Debug.assert(options);
+        Debug.assert(!options.vertexDefines || options.vertexDefines instanceof Map);
+        Debug.assert(!options.vertexIncludes || options.vertexIncludes instanceof Map);
+        Debug.assert(!options.fragmentDefines || options.fragmentDefines instanceof Map);
+        Debug.assert(!options.fragmentIncludes || options.fragmentIncludes instanceof Map);
 
         const getDefines = (gpu, gl2, gl1, isVertex, options) => {
 
@@ -101,18 +103,18 @@ class ShaderUtils {
         const name = options.name ?? 'Untitled';
 
         // vertex code
-        const vertDefines = options.vertexDefines || getDefines(webgpuVS, gles3VS, '', true, options);
         const vertCode = ShaderUtils.versionCode(device) +
-            vertDefines +
+            getDefines(webgpuVS, gles3VS, '', true, options) +
+            ShaderUtils.getDefinesCode(options.vertexDefines) +
             sharedFS +
             ShaderUtils.getShaderNameCode(name) +
             options.vertexCode;
 
         // fragment code
-        const fragDefines = options.fragmentDefines || getDefines(webgpuFS, gles3FS, gles2FS, false, options);
         const fragCode = (options.fragmentPreamble || '') +
             ShaderUtils.versionCode(device) +
-            fragDefines +
+            getDefines(webgpuFS, gles3FS, gles2FS, false, options) +
+            ShaderUtils.getDefinesCode(options.fragmentDefines) +
             ShaderUtils.precisionCode(device) + '\n' +
             sharedFS +
             ShaderUtils.getShaderNameCode(name) +
@@ -125,15 +127,26 @@ class ShaderUtils {
             name: name,
             attributes: attribs,
             vshader: vertCode,
-            vdefines: options.vertexDefinesMap,
-            vincludes: options.vertexIncludesMap,
-            fdefines: options.fragmentDefinesMap,
-            fincludes: options.fragmentIncludesMap,
+            vincludes: options.vertexIncludes,
+            fincludes: options.fragmentIncludes,
             fshader: fragCode,
             useTransformFeedback: options.useTransformFeedback
         };
     }
 
+    /**
+     * @param {Map<string, string>} [defines] - A map containing key-value pairs.
+     * @returns {string} The shader code for the defines.
+     * @private
+     */
+    static getDefinesCode(defines) {
+        let code = '';
+        defines?.forEach((value, key) => {
+            code += `#define ${key} ${value}\n`;
+        });
+        return code;
+    }
+
     // SpectorJS integration
     static getShaderNameCode(name) {
         return `#define SHADER_NAME ${name}\n`;
diff --git a/src/platform/graphics/shader.js b/src/platform/graphics/shader.js
index e7266a8ba..b3692897a 100644
--- a/src/platform/graphics/shader.js
+++ b/src/platform/graphics/shader.js
@@ -56,15 +56,9 @@ class Shader {
      * @param {Map<string, string>} [definition.vincludes] - A map containing key-value pairs of
      * include names and their content. These are used for resolving #include directives in the
      * vertex shader source.
-     * @param {Map<string, string>} [definition.vdefines] - A map containing key-value pairs of
-     * define names and their values. These are used for resolving #ifdef style of directives in the
-     * vertex code.
      * @param {Map<string, string>} [definition.fincludes] - A map containing key-value pairs
      * of include names and their content. These are used for resolving #include directives in the
      * fragment shader source.
-     * @param {Map<string, string>} [definition.fdefines] - A map containing key-value pairs of
-     * define names and their values. These are used for resolving #ifdef style of directives in the
-     * fragment code.
      * @param {boolean} [definition.useTransformFeedback] - Specifies that this shader outputs
      * post-VS data to a buffer.
      * @param {string | string[]} [definition.fragmentOutputTypes] - Fragment shader output types,
@@ -118,13 +112,13 @@ class Shader {
             Debug.assert(definition.fshader, 'No fragment shader has been specified when creating a shader.');
 
             // pre-process shader sources
-            definition.vshader = Preprocessor.run(definition.vshader, definition.vdefines, definition.vincludes);
+            definition.vshader = Preprocessor.run(definition.vshader, definition.vincludes);
 
             // Strip unused color attachments from fragment shader.
             // Note: this is only needed for iOS 15 on WebGL2 where there seems to be a bug where color attachments that are not
             // written to generate metal linking errors. This is fixed on iOS 16, and iOS 14 does not support WebGL2.
             const stripUnusedColorAttachments = graphicsDevice.isWebGL2 && (platform.name === 'osx' || platform.name === 'ios');
-            definition.fshader = Preprocessor.run(definition.fshader, definition.fdefines, definition.fincludes, stripUnusedColorAttachments);
+            definition.fshader = Preprocessor.run(definition.fshader, definition.fincludes, stripUnusedColorAttachments);
         }
 
         this.impl = graphicsDevice.createShaderImpl(this);
diff --git a/src/scene/materials/lit-material-options-builder.js b/src/scene/materials/lit-material-options-builder.js
index ec8a44370..78b8df0e7 100644
--- a/src/scene/materials/lit-material-options-builder.js
+++ b/src/scene/materials/lit-material-options-builder.js
@@ -130,7 +130,7 @@ class LitMaterialOptionsBuilder {
         }
 
         const hasSkybox = !!litOptions.reflectionSource;
-        litOptions.skyboxIntensity = hasSkybox && (scene.skyboxIntensity !== 1 || scene.physicalUnits);
+        litOptions.skyboxIntensity = hasSkybox;
         litOptions.useCubeMapRotation = hasSkybox && scene._skyboxRotationShaderInclude;
     }
 
diff --git a/src/scene/materials/standard-material-options-builder.js b/src/scene/materials/standard-material-options-builder.js
index bf95be9d0..b4e35897a 100644
--- a/src/scene/materials/standard-material-options-builder.js
+++ b/src/scene/materials/standard-material-options-builder.js
@@ -355,7 +355,7 @@ class StandardMaterialOptionsBuilder {
         }
 
         // TODO: add a test for if non skybox cubemaps have rotation (when this is supported) - for now assume no non-skybox cubemap rotation
-        options.litOptions.skyboxIntensity = usingSceneEnv && (scene.skyboxIntensity !== 1 || scene.physicalUnits);
+        options.litOptions.skyboxIntensity = usingSceneEnv;
         options.litOptions.useCubeMapRotation = usingSceneEnv && scene._skyboxRotationShaderInclude;
     }
 
diff --git a/src/scene/shader-lib/chunks/chunks.js b/src/scene/shader-lib/chunks/chunks.js
index e29204db2..57a41c01e 100644
--- a/src/scene/shader-lib/chunks/chunks.js
+++ b/src/scene/shader-lib/chunks/chunks.js
@@ -40,7 +40,6 @@ import encodePS from './common/frag/encode.js';
 import endPS from './lit/frag/end.js';
 import endVS from './lit/vert/end.js';
 import envAtlasPS from './common/frag/envAtlas.js';
-import envConstPS from './common/frag/envConst.js';
 import envMultiplyPS from './common/frag/envMultiply.js';
 import extensionPS from './lit/frag/extension.js';
 import extensionVS from './lit/vert/extension.js';
@@ -253,7 +252,6 @@ const shaderChunks = {
     endPS,
     endVS,
     envAtlasPS,
-    envConstPS,
     envMultiplyPS,
     extensionPS,
     extensionVS,
diff --git a/src/scene/shader-lib/chunks/common/frag/envConst.js b/src/scene/shader-lib/chunks/common/frag/envConst.js
deleted file mode 100644
index 1a01a129b..000000000
--- a/src/scene/shader-lib/chunks/common/frag/envConst.js
+++ /dev/null
@@ -1,5 +0,0 @@
-export default /* glsl */`
-vec3 processEnvironment(vec3 color) {
-    return color;
-}
-`;
diff --git a/src/scene/shader-lib/chunks/skybox/frag/skyboxHDR.js b/src/scene/shader-lib/chunks/skybox/frag/skyboxHDR.js
index 7469d1832..13e9f9a36 100644
--- a/src/scene/shader-lib/chunks/skybox/frag/skyboxHDR.js
+++ b/src/scene/shader-lib/chunks/skybox/frag/skyboxHDR.js
@@ -26,7 +26,7 @@ void main(void) {
     #endif
 
     dir.x *= -1.0;
-    vec3 linear = $DECODE(textureCube(texture_cubeMap, fixSeamsStatic(dir, $FIXCONST)));
+    vec3 linear = $DECODE(textureCube(texture_cubeMap, fixSeamsStatic(dir, SKYBOX_MIP)));
     gl_FragColor = vec4(gammaCorrectOutput(toneMap(processEnvironment(linear))), 1.0);
 }
 `;
diff --git a/src/scene/shader-lib/programs/basic.js b/src/scene/shader-lib/programs/basic.js
index b00868015..87227ac87 100644
--- a/src/scene/shader-lib/programs/basic.js
+++ b/src/scene/shader-lib/programs/basic.js
@@ -169,8 +169,8 @@ class ShaderGeneratorBasic extends ShaderGenerator {
         if (options.diffuseMap) defines.set('DIFFUSE_MAP', true);
 
         definitionOptions.vertexCode = vShader;
-        definitionOptions.vertexIncludesMap = includes;
-        definitionOptions.vertexDefinesMap = defines;
+        definitionOptions.vertexIncludes = includes;
+        definitionOptions.vertexDefines = defines;
     }
 
     createFragmentDefinition(definitionOptions, options, shaderPassInfo) {
@@ -189,8 +189,8 @@ class ShaderGeneratorBasic extends ShaderGenerator {
         if (options.alphaTest) defines.set('ALPHA_TEST', true);
 
         definitionOptions.fragmentCode = fShader;
-        definitionOptions.fragmentIncludesMap = includes;
-        definitionOptions.fragmentDefinesMap = defines;
+        definitionOptions.fragmentIncludes = includes;
+        definitionOptions.fragmentDefines = defines;
     }
 
     createShaderDefinition(device, options) {
diff --git a/src/scene/shader-lib/programs/lit-shader.js b/src/scene/shader-lib/programs/lit-shader.js
index f8d8628af..fbd3c07a2 100644
--- a/src/scene/shader-lib/programs/lit-shader.js
+++ b/src/scene/shader-lib/programs/lit-shader.js
@@ -770,7 +770,7 @@ class LitShader {
         if (this.needsNormal) {
             func.append(chunks.cubeMapRotatePS);
             func.append(options.cubeMapProjection > 0 ? chunks.cubeMapProjectBoxPS : chunks.cubeMapProjectNonePS);
-            func.append(options.skyboxIntensity ? chunks.envMultiplyPS : chunks.envConstPS);
+            func.append(chunks.envMultiplyPS);
         }
 
         if ((this.lighting && options.useSpecular) || this.reflections) {
diff --git a/src/scene/shader-lib/programs/skybox.js b/src/scene/shader-lib/programs/skybox.js
index 454a93e65..0fc37dcf3 100644
--- a/src/scene/shader-lib/programs/skybox.js
+++ b/src/scene/shader-lib/programs/skybox.js
@@ -8,27 +8,33 @@ import { SKYTYPE_INFINITE } from '../../constants.js';
 
 class ShaderGeneratorSkybox extends ShaderGenerator {
     generateKey(options) {
-        const sharedKey = `skybox-${options.type}-${options.encoding}-${options.useIntensity}-${options.gamma}-${options.toneMapping}-${options.skymesh}`;
-        return sharedKey + (options.type === 'cubemap' ? `-${options.fixSeams}-${options.mip}` : '');
+        const sharedKey = `skybox-${options.type}-${options.encoding}-${options.gamma}-${options.toneMapping}-${options.skymesh}`;
+        return sharedKey + (options.type === 'cubemap' ? `-${options.mip}` : '');
     }
 
     createShaderDefinition(device, options) {
-        const defines = options.skymesh === SKYTYPE_INFINITE ? '' : '#define SKYMESH\n';
-        const vshader = defines + shaderChunks.skyboxVS;
-        let fshader = defines;
 
+        // shared defines
+        const defines = new Map();
+        if (options.skymesh !== SKYTYPE_INFINITE) defines.set('SKYMESH', '');
+        if (options.type === 'cubemap') defines.set('SKY_CUBEMAP', '');
+
+        // fragment shader
+        let fshader = '';
         if (options.type === 'cubemap') {
+
+            // mip level
             const mip2size = [128, 64, /* 32 */ 16, 8, 4, 2];
+            defines.set('SKYBOX_MIP', (1 - 1 / mip2size[options.mip]).toString());
+
             fshader += options.mip ? shaderChunks.fixCubemapSeamsStretchPS : shaderChunks.fixCubemapSeamsNonePS;
-            fshader += options.useIntensity ? shaderChunks.envMultiplyPS : shaderChunks.envConstPS;
+            fshader += shaderChunks.envMultiplyPS;
             fshader += shaderChunks.decodePS;
             fshader += ShaderGenerator.gammaCode(options.gamma);
             fshader += ShaderGenerator.tonemapCode(options.toneMapping);
-            fshader += shaderChunks.skyboxHDRPS
-                .replace(/\$DECODE/g, ChunkUtils.decodeFunc(options.encoding))
-                .replace(/\$FIXCONST/g, (1 - 1 / mip2size[options.mip]) + "");
+            fshader += shaderChunks.skyboxHDRPS.replace(/\$DECODE/g, ChunkUtils.decodeFunc(options.encoding));
         } else {
-            fshader += options.useIntensity ? shaderChunks.envMultiplyPS : shaderChunks.envConstPS;
+            fshader += shaderChunks.envMultiplyPS;
             fshader += shaderChunks.decodePS;
             fshader += ShaderGenerator.gammaCode(options.gamma);
             fshader += ShaderGenerator.tonemapCode(options.toneMapping);
@@ -42,8 +48,10 @@ class ShaderGeneratorSkybox extends ShaderGenerator {
             attributes: {
                 aPosition: SEMANTIC_POSITION
             },
-            vertexCode: vshader,
-            fragmentCode: fshader
+            vertexCode: shaderChunks.skyboxVS,
+            vertexDefines: defines,
+            fragmentCode: fshader,
+            fragmentDefines: defines
         });
     }
 }
diff --git a/src/scene/skybox/sky-mesh.js b/src/scene/skybox/sky-mesh.js
index 9c32b8b17..3b1152671 100644
--- a/src/scene/skybox/sky-mesh.js
+++ b/src/scene/skybox/sky-mesh.js
@@ -38,7 +38,6 @@ class SkyMesh {
             const options = {
                 pass: pass,
                 encoding: texture.encoding,
-                useIntensity: scene.skyboxIntensity !== 1 || scene.physicalUnits,
                 gamma: (pass === SHADER_FORWARDHDR ? (scene.gammaCorrection ? GAMMA_SRGBHDR : GAMMA_NONE) : scene.gammaCorrection),
                 toneMapping: (pass === SHADER_FORWARDHDR ? TONEMAP_LINEAR : scene.toneMapping),
                 skymesh: type
diff --git a/utils/plugins/rollup-types-fixup.mjs b/utils/plugins/rollup-types-fixup.mjs
index d33e81551..8f2a445ab 100644
--- a/utils/plugins/rollup-types-fixup.mjs
+++ b/utils/plugins/rollup-types-fixup.mjs
@@ -209,7 +209,7 @@ import { Texture } from '../../platform/graphics/texture.js';
 `
     }
 }, {
-    path: `${TYPES_PATH}/framework/script/script-type.d.ts`,
+    path: `${TYPES_PATH}/framework/script/script.d.ts`,
     replacement: {
         from: 'get enabled(): boolean;',
         to: `get enabled(): boolean;
@@ -232,12 +232,12 @@ import { Texture } from '../../platform/graphics/texture.js';
      */
     postUpdate?(dt: number): void;
     /**
-     * Called when a ScriptType that already exists in the registry gets redefined. If the new
-     * ScriptType has a \`swap\` method in its prototype, then it will be executed to perform
+     * Called when a Script that already exists in the registry gets redefined. If the new
+     * Script has a \`swap\` method in its prototype, then it will be executed to perform
      * hot-reload at runtime.
-     * @param old - Old instance of the scriptType to copy data to the new instance.
+     * @param old - Old instance of the script to copy data to the new instance.
      */
-    swap?(old: ScriptType): void;
+    swap?(old: Script): void;
 `
     }
 }];


Test patch:
diff --git a/test/core/preprocessor.test.mjs b/test/core/preprocessor.test.mjs
index 2995abe6b..79503ca4b 100644
--- a/test/core/preprocessor.test.mjs
+++ b/test/core/preprocessor.test.mjs
@@ -14,8 +14,6 @@ describe('Preprocessor', function () {
         ['inc2', 'block2']
     ]);
 
-    const defines = new Map();
-
     const srcData = `
         
         #define FEATURE1
@@ -78,82 +76,82 @@ describe('Preprocessor', function () {
     `;
 
     it('returns false for MORPH_A', function () {
-        expect(Preprocessor.run(srcData, defines, includes).includes('MORPH_A')).to.equal(false);
+        expect(Preprocessor.run(srcData, includes).includes('MORPH_A')).to.equal(false);
     });
 
     it('returns false for MORPH_B', function () {
-        expect(Preprocessor.run(srcData, defines, includes).includes('MORPH_B')).to.equal(false);
+        expect(Preprocessor.run(srcData, includes).includes('MORPH_B')).to.equal(false);
     });
 
     it('returns true for $', function () {
-        expect(Preprocessor.run(srcData, defines, includes).includes('$')).to.equal(true);
+        expect(Preprocessor.run(srcData, includes).includes('$')).to.equal(true);
     });
 
     it('returns true for TEST1', function () {
-        expect(Preprocessor.run(srcData, defines, includes).includes('TEST1')).to.equal(true);
+        expect(Preprocessor.run(srcData, includes).includes('TEST1')).to.equal(true);
     });
 
     it('returns true for TEST2', function () {
-        expect(Preprocessor.run(srcData, defines, includes).includes('TEST2')).to.equal(true);
+        expect(Preprocessor.run(srcData, includes).includes('TEST2')).to.equal(true);
     });
 
     it('returns true for TEST3', function () {
-        expect(Preprocessor.run(srcData, defines, includes).includes('TEST3')).to.equal(true);
+        expect(Preprocessor.run(srcData, includes).includes('TEST3')).to.equal(true);
     });
 
     it('returns true for TEST4', function () {
-        expect(Preprocessor.run(srcData, defines, includes).includes('TEST4')).to.equal(true);
+        expect(Preprocessor.run(srcData, includes).includes('TEST4')).to.equal(true);
     });
 
     it('returns false for TEST5', function () {
-        expect(Preprocessor.run(srcData, defines, includes).includes('TEST5')).to.equal(false);
+        expect(Preprocessor.run(srcData, includes).includes('TEST5')).to.equal(false);
     });
 
     it('returns true for TEST6', function () {
-        expect(Preprocessor.run(srcData, defines, includes).includes('TEST6')).to.equal(true);
+        expect(Preprocessor.run(srcData, includes).includes('TEST6')).to.equal(true);
     });
 
     it('returns false for TEST7', function () {
-        expect(Preprocessor.run(srcData, defines, includes).includes('TEST7')).to.equal(false);
+        expect(Preprocessor.run(srcData, includes).includes('TEST7')).to.equal(false);
     });
 
     it('returns false for TEST8', function () {
-        expect(Preprocessor.run(srcData, defines, includes).includes('TEST8')).to.equal(false);
+        expect(Preprocessor.run(srcData, includes).includes('TEST8')).to.equal(false);
     });
 
     it('returns false for TEST9', function () {
-        expect(Preprocessor.run(srcData, defines, includes).includes('TEST9')).to.equal(false);
+        expect(Preprocessor.run(srcData, includes).includes('TEST9')).to.equal(false);
     });
 
     it('returns true for TEST10', function () {
-        expect(Preprocessor.run(srcData, defines, includes).includes('TEST10')).to.equal(true);
+        expect(Preprocessor.run(srcData, includes).includes('TEST10')).to.equal(true);
     });
 
     it('returns false for TEST11', function () {
-        expect(Preprocessor.run(srcData, defines, includes).includes('TEST11')).to.equal(false);
+        expect(Preprocessor.run(srcData, includes).includes('TEST11')).to.equal(false);
     });
 
     it('returns false for TEST12', function () {
-        expect(Preprocessor.run(srcData, defines, includes).includes('TEST12')).to.equal(false);
+        expect(Preprocessor.run(srcData, includes).includes('TEST12')).to.equal(false);
     });
 
     it('returns true for TEST13', function () {
-        expect(Preprocessor.run(srcData, defines, includes).includes('TEST13')).to.equal(true);
+        expect(Preprocessor.run(srcData, includes).includes('TEST13')).to.equal(true);
     });
 
     it('returns false for TEST14', function () {
-        expect(Preprocessor.run(srcData, defines, includes).includes('TEST14')).to.equal(false);
+        expect(Preprocessor.run(srcData, includes).includes('TEST14')).to.equal(false);
     });
 
     it('returns true for INC1', function () {
-        expect(Preprocessor.run(srcData, defines, includes).includes('block1')).to.equal(true);
+        expect(Preprocessor.run(srcData, includes).includes('block1')).to.equal(true);
     });
 
     it('returns false for INC2', function () {
-        expect(Preprocessor.run(srcData, defines, includes).includes('block2')).to.equal(false);
+        expect(Preprocessor.run(srcData, includes).includes('block2')).to.equal(false);
     });
 
     it('returns true for nested', function () {
-        expect(Preprocessor.run(srcData, defines, includes).includes('nested')).to.equal(true);
+        expect(Preprocessor.run(srcData, includes).includes('nested')).to.equal(true);
     });
 });
diff --git a/test/framework/entity.test.mjs b/test/framework/entity.test.mjs
index 4bbd61bc0..38f5d80c8 100644
--- a/test/framework/entity.test.mjs
+++ b/test/framework/entity.test.mjs
@@ -1,4 +1,4 @@
-import { createScript } from '../../src/framework/script/script.js';
+import { createScript } from '../../src/framework/script/script-create.js';
 import { Color } from '../../src/core/math/color.js';
 
 import { AnimComponent } from '../../src/framework/components/anim/component.js';