UNPKG

kontra

Version:

Kontra HTML5 game development library

164 lines (142 loc) 4.78 kB
/** * A plugin system based on the [interceptor pattern](https://en.wikipedia.org/wiki/Interceptor_pattern), designed to share reusable code such as more advance collision detection or a 2D physics engine. * * ```js * import { registerPlugin, Sprite } from 'kontra'; * import loggingPlugin from 'path/to/plugin/code.js' * * // register a plugin that adds logging to all Sprites * registerPlugin(Sprite, loggingPlugin); * ``` * @sectionName Plugin */ /** * @docs docs/api_docs/plugin.js */ /** * Get the kontra object method name from the plugin. * * @param {String} methodName - Before/After function name * * @returns {String} */ function getMethod(methodName) { let methodTitle = methodName.substr( methodName.search(/[A-Z]/) ); return methodTitle[0].toLowerCase() + methodTitle.substr(1); } /** * Remove an interceptor. * * @param {function[]} interceptors - Before/After interceptor list * @param {function} fn - Interceptor function */ function removeInterceptor(interceptors, fn) { let index = interceptors.indexOf(fn); if (index !== -1) { interceptors.splice(index, 1); } } /** * Register a plugin to run a set of functions before or after the Kontra object functions. * @function registerPlugin * * @param {Object} kontraObj - Kontra object to attach the plugin to. * @param {Object} pluginObj - Plugin object with before and after intercept functions. */ export function registerPlugin(kontraObj, pluginObj) { let objectProto = kontraObj.prototype; if (!objectProto) return; // create interceptor list and functions if (!objectProto._inc) { objectProto._inc = {}; objectProto._bInc = function beforePlugins(context, method, ...args) { return this._inc[method].before.reduce((acc, fn) => { let newArgs = fn(context, ...acc); return newArgs ? newArgs : acc; }, args); }; objectProto._aInc = function afterPlugins(context, method, result, ...args) { return this._inc[method].after.reduce((acc, fn) => { let newResult = fn(context, acc, ...args); return newResult ? newResult : acc; }, result); }; } // add plugin to interceptors Object.getOwnPropertyNames(pluginObj).forEach(methodName => { let method = getMethod(methodName); if (!objectProto[method]) return; // override original method if (!objectProto['_o' + method]) { objectProto['_o' + method] = objectProto[method]; objectProto[method] = function interceptedFn(...args) { // call before interceptors let alteredArgs = this._bInc(this, method, ...args); let result = objectProto['_o' + method].call(this, ...alteredArgs); // call after interceptors return this._aInc(this, method, result, ...args); }; } // create interceptors for the method if (!objectProto._inc[method]) { objectProto._inc[method] = { before: [], after: [] }; } if (methodName.startsWith('before')) { objectProto._inc[method].before.push(pluginObj[methodName]); } else if (methodName.startsWith('after')) { objectProto._inc[method].after.push(pluginObj[methodName]); } }); } /** * Unregister a plugin from a Kontra object. * @function unregisterPlugin * * @param {Object} kontraObj - Kontra object to detach plugin from. * @param {Object} pluginObj - The plugin object that was passed during registration. */ export function unregisterPlugin(kontraObj, pluginObj) { let objectProto = kontraObj.prototype; if (!objectProto || !objectProto._inc) return; // remove plugin from interceptors Object.getOwnPropertyNames(pluginObj).forEach(methodName => { let method = getMethod(methodName); if (methodName.startsWith('before')) { removeInterceptor(objectProto._inc[method].before, pluginObj[methodName]); } else if (methodName.startsWith('after')) { removeInterceptor(objectProto._inc[method].after, pluginObj[methodName]); } }); } /** * Safely extend the functionality of a Kontra object. Any properties that already exist on the Kontra object will not be added. * * ```js * import { extendObject, Vector } from 'kontra'; * * // add a subtract function to all Vectors * extendObject(Vector, { * subtract(vec) { * return Vector(this.x - vec.x, this.y - vec.y); * } * }); * ``` * @function extendObject * * @param {Object} kontraObj - Kontra object to extend * @param {Object} properties - Properties to add. */ export function extendObject(kontraObj, properties) { let objectProto = kontraObj.prototype; if (!objectProto) return; Object.getOwnPropertyNames(properties).forEach(prop => { if (!objectProto[prop]) { objectProto[prop] = properties[prop]; } }); }