X Tutup
pc.extend(pc, function () { var rawToValue = function(app, args, value, old) { var i; // TODO scripts2 // arrays switch(args.type) { case 'boolean': return !! value; case 'number': if (typeof(value) === 'number') { return value; } else if (typeof(value) === 'string') { var v = parseInt(value, 10); if (isNaN(v)) return null; return v; } else if (typeof(value) === 'boolean') { return 0 + value; } else { return null; } break; case 'json': if (typeof(value) === 'object') { return value; } else { try { return JSON.parse(value); } catch(ex) { return null; } } break; case 'asset': if (args.array) { var result = [ ]; if (value instanceof Array) { for(i = 0; i < value.length; i++) { if (value[i] instanceof pc.Asset) { result.push(value[i]); } else if (typeof(value[i]) === 'number') { result.push(app.assets.get(value[i]) || null); } else if (typeof(value[i]) === 'string') { result.push(app.assets.get(parseInt(value[i], 10)) || null); } else { result.push(null); } } } return result; } else { if (value instanceof pc.Asset) { return value; } else if (typeof(value) === 'number') { return app.assets.get(value) || null; } else if (typeof(value) === 'string') { return app.assets.get(parseInt(value, 10)) || null; } else { return null; } } break; case 'entity': if (value instanceof pc.GraphNode) { return value; } else if (typeof(value) === 'string') { return app.root.findByGuid(value); } else { return null; } break; case 'rgb': case 'rgba': if (value instanceof pc.Color) { if (old instanceof pc.Color) { old.copy(value); return old; } else { return value.clone(); } } else if (value instanceof Array && value.length >= 3 && value.length <= 4) { for(i = 0; i < value.length; i++) { if (typeof(value[i]) !== 'number') return null; } if (! old) old = new pc.Color(); for(i = 0; i < 4; i++) old.data[i] = (i === 4 && value.length === 3) ? 1 : value[i]; return old; } else if (typeof(value) === 'string' && /#([0-9abcdef]{2}){3,4}/i.test(value)) { if (! old) old = new pc.Color(); old.fromString(value); return old; } else { return null; } break; case 'vec2': case 'vec3': case 'vec4': var len = parseInt(args.type.slice(3), 10); if (value instanceof pc['Vec' + len]) { if (old instanceof pc['Vec' + len]) { old.copy(value); return old; } else { return value.clone(); } } else if (value instanceof Array && value.length === len) { for(i = 0; i < value.length; i++) { if (typeof(value[i]) !== 'number') return null; } if (! old) old = new pc['Vec' + len](); for(i = 0; i < len; i++) old.data[i] = value[i]; return old; } else { return null; } break; case 'curve': if (value) { var curve; if (value instanceof pc.Curve || value instanceof pc.CurveSet) { curve = value.clone(); } else { var CurveType = value.keys[0] instanceof Array ? pc.CurveSet : pc.Curve; curve = new CurveType(value.keys); curve.type = value.type; } return curve; } break; } return value; }; /** * @name pc.ScriptAttributes * @class Container of Script Attribute definitions. Implements an interface to add/remove attributes and store their definition for a {@link ScriptType}. * Note: An instance of pc.ScriptAttributes is created automatically by each {@link ScriptType}. * @param {ScriptType} scriptType Script Type that attributes relate to. */ var ScriptAttributes = function(scriptType) { this.scriptType = scriptType; this.index = { }; }; /** * @function * @name pc.ScriptAttributes#add * @description Add Attribute * @param {String} name Name of an attribute * @param {Object} args Object with Arguments for an attribute * @param {String} args.type Type of an attribute value, list of possible types: * boolean, number, string, json, asset, entity, rgb, rgba, vec2, vec3, vec4, curve * @param {?} [args.default] Default attribute value * @param {String} [args.title] Title for Editor's for field UI * @param {String} [args.description] Description for Editor's for field UI * @param {(String|String[])} [args.placeholder] Placeholder for Editor's for field UI. * For multi-field types, such as vec2, vec3, and others use array of strings. * @param {Boolean} [args.array] If attribute can hold single or multiple values * @param {Number} [args.size] If attribute is array, maximum number of values can be set * @param {Number} [args.min] Minimum value for type 'number', if max and min defined, slider will be rendered in Editor's UI * @param {Number} [args.max] Maximum value for type 'number', if max and min defined, slider will be rendered in Editor's UI * @param {Number} [args.precision] Level of precision for field type 'number' with floating values * @param {String} [args.assetType] Name of asset type to be used in 'asset' type attribute picker in Editor's UI, defaults to '*' (all) * @param {String[]} [args.curves] List of names for Curves for field type 'curve' * @param {String} [args.color] String of color channels for Curves for field type 'curve', can be any combination of `rgba` characters. * Defining this property will render Gradient in Editor's field UI * @param {Object[]} [args.enum] List of fixed choices for field, defined as array of objects, where key in object is a title of an option * @example * PlayerController.attributes.add('fullName', { * type: 'string', * }); * @example * PlayerController.attributes.add('speed', { * type: 'number', * title: 'Speed', * placeholder: 'km/h', * default: 22.2 * }); * @example * PlayerController.attributes.add('resolution', { * type: 'number', * default: 32, * enum: [ * { '32x32': 32 }, * { '64x64': 64 }, * { '128x128': 128 } * ] * }); */ ScriptAttributes.prototype.add = function(name, args) { if (this.index[name]) { console.warn('attribute \'' + name + '\' is already defined for script type \'' + this.scriptType.name + '\''); return; } else if (pc.createScript.reservedAttributes[name]) { console.warn('attribute \'' + name + '\' is a reserved attribute name'); return; } this.index[name] = args; Object.defineProperty(this.scriptType.prototype, name, { get: function() { return this.__attributes[name]; }, set: function(raw) { var old = this.__attributes[name]; // convert to appropriate type this.__attributes[name] = rawToValue(this.app, args, raw, old); this.fire('attr', name, this.__attributes[name], old); this.fire('attr:' + name, this.__attributes[name], old); } }); }; /** * @function * @name pc.ScriptAttributes#remove * @description Remove Attribute. * @param {String} name Name of an attribute * @returns {Boolean} True if removed or false if not defined * @example * PlayerController.attributes.remove('fullName'); */ ScriptAttributes.prototype.remove = function(name) { if (! this.index[name]) return false; delete this.index[name]; delete this.scriptType.prototype[name]; return true; }; /** * @function * @name pc.ScriptAttributes#has * @description Detect if Attribute is added. * @param {String} name Name of an attribute * @returns {Boolean} True if Attribute is defined * @example * if (PlayerController.attributes.has('fullName')) { * // attribute `fullName` is defined * }); */ ScriptAttributes.prototype.has = function(name) { return !! this.index[name]; }; /** * @function * @name pc.ScriptAttributes#get * @description Get object with attribute arguments. * Note: Changing argument properties will not affect existing Script Instances. * @param {String} name Name of an attribute * @returns {?Object} Arguments with attribute properties * @example * // changing default value for an attribute 'fullName' * var attr = PlayerController.attributes.get('fullName'); * if (attr) attr.default = 'Unknown'; */ ScriptAttributes.prototype.get = function(name) { return this.index[name] || null; }; /** * @static * @function * @name pc.createScript * @description Method to create named {@link ScriptType}. * It returns new function (class) "Script Type", which is auto-registered to {@link pc.ScriptRegistry} using it's 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 {pc.Application} [app] Optional application handler, to choose which {@link pc.ScriptRegistry} to add a script to. * By default it will use `pc.Application.getApplication()` to get current {@link pc.Application}. * @returns {Function} The constructor of a {@link ScriptType}, which the developer is meant to extend by adding attributes and prototype methods. * @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); * }; */ var createScript = function(name, app) { if (pc.script.legacy) { console.error("This project is using the legacy script system. You cannot call pc.createScript(). See: http://developer.playcanvas.com/en/user-manual/scripting/legacy/"); return null; } if (createScript.reservedScripts[name]) throw new Error('script name: \'' + name + '\' is reserved, please change script name'); /** * @name ScriptType * @class Represents the type of a script. It is returned by {@link pc.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 and postInitialize - are called if defined when 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 - methods are called if defined for enabled (running state) scripts on each tick.
* swap - This method will be called when a {@link ScriptType} that already exists in the registry gets redefined. * If the new {@link ScriptType} has a `swap` method in its prototype, then it will be executed to perform hot-reload at runtime. * @property {pc.Application} app The {@link pc.Application} that the instance of this type belongs to. * @property {pc.Entity} entity The {@link pc.Entity} that the instance of this type belongs to. * @property {Boolean} enabled 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 Script Component 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. */ var script = function(args) { if (! args || ! args.app || ! args.entity) console.warn('script \'' + name + '\' has missing arguments in consructor'); pc.events.attach(this); this.app = args.app; this.entity = args.entity; this._enabled = typeof(args.enabled) === 'boolean' ? args.enabled : true; this._enabledOld = this.enabled; this.__attributes = { }; this.__attributesRaw = args.attributes || null; this.__scriptType = script; }; /** * @private * @readonly * @static * @name ScriptType.__name * @type String * @description Name of a Script Type. */ script.__name = name; /** * @field * @static * @readonly * @type pc.ScriptAttributes * @name ScriptType.attributes * @description The interface to define attributes for Script Types. Refer to {@link pc.ScriptAttributes} * @example * var PlayerController = pc.createScript('playerController'); * * PlayerController.attributes.add('speed', { * type: 'number', * title: 'Speed', * placeholder: 'km/h', * default: 22.2 * }); */ script.attributes = new ScriptAttributes(script); // initialize attributes script.prototype.__initializeAttributes = function(force) { if (! force && ! this.__attributesRaw) return; // set attributes values for(var key in script.attributes.index) { if (this.__attributesRaw && this.__attributesRaw.hasOwnProperty(key)) { this[key] = this.__attributesRaw[key]; } else if (! this.__attributes.hasOwnProperty(key)) { if (script.attributes.index[key].hasOwnProperty('default')) { this[key] = script.attributes.index[key].default; } else { this[key] = null; } } } this.__attributesRaw = null; }; /** * @readonly * @static * @function * @name ScriptType.extend * @param {Object} methods Object with methods, where key - is name of method, and value - is function. * @description Shorthand function to extend Script Type prototype with list of methods. * @example * var PlayerController = pc.createScript('playerController'); * * PlayerController.extend({ * initialize: function() { * // called once on initialize * }, * update: function(dt) { * // called each tick * } * }) */ script.extend = function(methods) { for(var key in methods) { if (! methods.hasOwnProperty(key)) continue; script.prototype[key] = methods[key]; } }; /** * @event * @name ScriptType#enable * @description Fired when a script instance becomes enabled * @example * PlayerController.prototype.initialize = function() { * this.on('enable', function() { * // Script Instance is now enabled * }); * }; */ /** * @event * @name ScriptType#disable * @description Fired when a script instance becomes disabled * @example * PlayerController.prototype.initialize = function() { * this.on('disable', function() { * // Script Instance is now disabled * }); * }; */ /** * @event * @name ScriptType#state * @description Fired when a script instance changes state to enabled or disabled * @param {Boolean} enabled True if now enabled, False if disabled * @example * PlayerController.prototype.initialize = function() { * this.on('state', function(enabled) { * console.log('Script Instance is now ' + (enabled ? 'enabled' : 'disabled')); * }); * }; */ /** * @event * @name ScriptType#destroy * @description Fired when a script instance is destroyed and removed from component * @example * PlayerController.prototype.initialize = function() { * this.on('destroy', function() { * // no more part of an entity * // good place to cleanup entity from destroyed script * }); * }; */ /** * @event * @name ScriptType#attr * @description Fired when any script attribute has been changed * @param {String} name Name of attribute * @param {Object} value New value * @param {Object} valueOld Old value * @example * PlayerController.prototype.initialize = function() { * this.on('attr', function(name, value, valueOld) { * console.log(name + ' been changed from ' + valueOld + ' to ' + value); * }); * }; */ /** * @event * @name ScriptType#attr:[name] * @description Fired when a specific script attribute has been changed * @param {Object} value New value * @param {Object} valueOld Old value * @example * PlayerController.prototype.initialize = function() { * this.on('attr:speed', function(value, valueOld) { * console.log('speed been changed from ' + valueOld + ' to ' + value); * }); * }; */ /** * @event * @name ScriptType#error * @description Fired when a script instance had an exception. The script instance will be automatically disabled. * @param {Error} err Native JavaScript Error object with details of error * @param {String} method The method of the script instance that the exception originated from. * @example * PlayerController.prototype.initialize = function() { * this.on('error', function(err, method) { * // caught an exception * console.log(err.stack); * }); * }; */ Object.defineProperty(script.prototype, 'enabled', { get: function() { return this._enabled && this.entity.script.enabled && this.entity.enabled; }, set: function(value) { value = !!value; if (this._enabled !== value) this._enabled = value; if (this.enabled !== this._enabledOld) { this._enabledOld = this.enabled; this.fire(this.enabled ? 'enable' : 'disable'); this.fire('state', this.enabled); } } }); // add to scripts registry var registry = app ? app.scripts : pc.Application.getApplication().scripts; registry.add(script); pc.ScriptHandler._push(script); return script; }; // reserved scripts createScript.reservedScripts = [ 'system', 'entity', 'create', 'destroy', 'swap', 'move', 'scripts', '_scripts', '_scriptsIndex', '_scriptsData', 'enabled', '_oldState', 'onEnable', 'onDisable', 'onPostStateChange', '_onSetEnabled', '_checkState', '_onBeforeRemove', '_onInitializeAttributes', '_onInitialize', '_onPostInitialize', '_onUpdate', '_onPostUpdate', '_callbacks', 'has', 'on', 'off', 'fire', 'once', 'hasEvent' ]; var reservedScripts = { }; var i; for (i = 0; i < createScript.reservedScripts.length; i++) reservedScripts[createScript.reservedScripts[i]] = 1; createScript.reservedScripts = reservedScripts; // reserved script attribute names createScript.reservedAttributes = [ 'app', 'entity', 'enabled', '_enabled', '_enabledOld', '__attributes', '__attributesRaw', '__scriptType', '_callbacks', 'has', 'on', 'off', 'fire', 'once', 'hasEvent' ]; var reservedAttributes = { }; for (i = 0; i < createScript.reservedAttributes.length; i++) reservedAttributes[createScript.reservedAttributes[i]] = 1; createScript.reservedAttributes = reservedAttributes; return { createScript: createScript }; }());
X Tutup