FlorentPoujol

build plugins superpowers

Jun 7th, 2015
352
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. # Build plugins for Superpowers
  2.  
  3. - [api](#api)
  4. - [data](#data)
  5. - [runtime](#runtime)
  6. - [editors](#editors)
  7. - [settingsEditors](#settingseditors)
  8. - [components](#components)
  9. - [componentEditors](#componenteditors)
  10.  
  11. <a name="api"></a>
  12. ## API
  13.  
  14. The api folder contains the typescript code that is accessible by the projects scripts in the client and (if needed) during the runtime.
  15.  
  16. It typically contains 3 files
  17.  
  18. - `index.js` where you register the plugin.
  19. - `MyPlugin.ts` contains the typescript code (typically a module).
  20. - `MyPlugin.d.ts` contains the definitions.
  21.  
  22. For some examples, check out my `fLang` or `fTween` plugins, for instance.
  23.  
  24. The `export` keyword in the code is important.  
  25. If you omit it, it's like the var, function, class or interface doesn't exist if you try to access it from the project's scripts.
  26.  
  27. If your module needs to be accessed during runtime too (when the game runs), you need to expose the module to the `window` variable like so :
  28.  
  29.    (<any>window).MyModule = MyModule;
  30.  
  31. The definitions are not technically mandatory but you won't be able to do much without them.  
  32. They defines what exists in the eye of the compiler, and are used to build the autocompletion information, so the definitions typically match the content exported by the code.  
  33. Definitions are also what is shown by the Typescript API browser.
  34.  
  35. ### Registering an api plugin
  36.  
  37. The content of an `index.js` typically looks like this:
  38.  
  39.     var fs = require("fs");
  40.  
  41.     SupAPI.registerPlugin("typescript", "fLang", {
  42.       code: fs.readFileSync(__dirname + "/fLang.ts", { encoding: "utf8" }).replace("reference path", "_reference path"),
  43.       defs: fs.readFileSync(__dirname + "/fLang.d.ts", { encoding: "utf8" })
  44.     });
  45.  
  46. The first argument is the context (the scripting language you want to expose the api to). Leave `typescript`.  
  47. The second argument is the plugin's name, the one that appears in the Typescript API bowser.
  48. The third argument is an object with a `code` and a `defs` properties.
  49.  
  50. Here we use `fs.readFileSync(__dirname + "/[path to file]", { encoding: "utf8" })` to read the .ts and .d.ts files.
  51.  
  52. ### Exposing a JS library
  53.  
  54. When you want to expose a JS library, you need to get its definition files, usually from [the DefinitelyTyped repo](https://github.com/borisyankov/DefinitelyTyped), or write it yourself.
  55.  
  56. Yo don't have to do anything for the code, except declare the main module name
  57.  
  58.     var fs = require("fs");
  59.  
  60.     SupAPI.registerPlugin("typescript", "FTWEEN", {
  61.       code: "declare var FTWEEN;",
  62.       defs: fs.readFileSync(__dirname + "/../lib/ftween.d.ts", { encoding: "utf8" })
  63.     });
  64.  
  65. You can see here I wrote code directly in the index files but also decided to put the definition files elsewhere than the api folder.
  66.  
  67. This expose the library to Superpowers client only, another step is required to expose the library to the runtime.  
  68. This will be done from the runtime folder.
  69.  
  70. ---
  71. <a name="data"></a>
  72. ## Data
  73.  
  74. The data folder contains the assets, resources and component config classes.
  75.  
  76. Assets are the main components of the games, are created/modified via an __editor__ in Sup's client.
  77. They are composed of a main asset.json file, plus other files as needed.
  78. They can be found in the project's asset folder ("assets/[asset id]/asset.json").
  79.  
  80. Resources are used for storing settings (like global editor parameters or assets default parameters).  
  81. They are modified via __settings editors__ accessed through the Settings tool (Sparklinlabs's Settings plugin).
  82. They are composed of a single resource.json file and are found in the project's resources folder ("resources/[resource name]/resource.json")
  83.  
  84. Component config ??
  85.  
  86. See the Sprite plugin for an example of those classes.
  87.  
  88. ### Resource
  89.  
  90. From an asset class, resources can be accessed via the serverData property.  
  91.  
  92. Ie (from the SpriteAsset.ts) :
  93.    
  94.     // called when the asset is created via Sup's client
  95.     init(options: any, callback: Function) {
  96.       this.serverData.resources.acquire("spriteSettings", null, (err: Error, spriteSettings: any) => {
  97.        
  98.         // this.pub is the asset's data
  99.         // most of which is saved in the asset.json file (but that depends on how it's done in asset's save() method)
  100.  
  101.         // spriteSettings.pub is the resource data
  102.  
  103.         this.pub = {
  104.           image: new Buffer(0),
  105.           filtering: spriteSettings.pub.filtering,
  106.           pixelsPerUnit: spriteSettings.pub.pixelsPerUnit,
  107.           framesPerSecond: spriteSettings.pub.framesPerSecond,
  108.           alphaTest: spriteSettings.pub.alphaTest,
  109.  
  110.           grid: { width: 100, height: 100 },
  111.           origin: { x: 0.5, y: 0.5 },
  112.  
  113.           animations: []
  114.         };
  115.  
  116.         this.serverData.resources.release("spriteSettings", null);
  117.         super.init(options, callback);
  118.       });
  119.     }
  120.  
  121. From an asset editor (or a settings editor class), a resource can be accessed by subscribing to it through projectClient:
  122.  
  123. Ie (from the Scene editor):
  124.  
  125.     function onConnected() {
  126.       data = { projectClient: new SupClient.ProjectClient(socket, { subEntries: true }) };
  127.  
  128.       data.projectClient.subResource("sceneSettings", sceneSettingSubscriber);
  129.     }
  130.  
  131.     var sceneSettingSubscriber = {
  132.       onResourceReceived: (resourceId: string, resource: SceneSettingsResource) => {
  133.         setCameraMode(resource.pub.defaultCameraMode);
  134.        
  135.         // the resource object can also be stored for later use
  136.       },
  137.  
  138.       onResourceEdited: (resourceId: string, command: string, propertyName: string) => {
  139.         // do stuff if needed
  140.       }
  141.     }
  142.  
  143. ### Registering
  144.  
  145. In the data's index file (Example from the sprite plugin) :
  146.  
  147.    import SpriteSettingsResource from "./SpriteSettingsResource";
  148.    import SpriteAsset from "./SpriteAsset";
  149.    import SpriteRendererConfig from "./SpriteRendererConfig";
  150.  
  151.    SupCore.data.registerResource("spriteSettings", SpriteSettingsResource);
  152.  
  153.    SupCore.data.registerAssetClass("sprite", SpriteAsset);
  154.    // note that the first argument here is the asset's type
  155.  
  156.     SupCore.data.registerComponentConfigClass("SpriteRenderer", SpriteRendererConfig);
  157.  
  158. ---
  159. <a name="runtime"></a>
  160. ## Runtime
  161.  
  162. The runtime folder contains an index file too, and other files as needed.
  163.  
  164. These files may contain some of theses functions:
  165.  
  166. Note :
  167.  
  168. - all code shown below is Typescript
  169. - See `app/system/SupRuntime/SupRuntime.d.ts` for the definitions of `SupRuntime.Player`.
  170.  
  171.  
  172. #### init(player: SupRuntime.Player, callback: Function)
  173. #### start(player: SupRuntime.Player, callback: Function)
  174. Both are called when the game launches.
  175. The callback method needs to be called at the end.
  176.  
  177.  
  178. #### loadAsset(player: SupRuntime.Player, entry: any, callback: (err: Error, asset?: any) => any)
  179.  
  180. Allow to load any files in the asset folder.
  181. Called once per asset of type introduced by the plugin.
  182. The callback needs to be called at some point too.
  183.  
  184. Ie:
  185.  
  186.     export function loadAsset(player: SupRuntime.Player, entry: any, callback: (err: Error, asset?: any) => any) {
  187.       // can access :
  188.       // entry.id, entry.name
  189.  
  190.       //  how to read a file in the asset's folder:
  191.  
  192.       player.getAssetData(`assets/${entry.id}/script.txt`, "text", (err, data) => {
  193.         //  data is a string (the text content of a script for instance)
  194.         callback(null, script);
  195.       });
  196.  
  197.       player.getAssetData(`assets/${entry.id}/asset.json`, "json", (err, data) => {
  198.         // data is a JS object, the asset parameters
  199.         callback(null, data);
  200.       });
  201.  
  202.       // can be as simple as :
  203.       // if you don' need to do stuff with the asset's data at this point
  204.       player.getAssetData(`assets/${entry.id}/asset.json`, "json", callback);
  205.     }
  206.  
  207.  
  208. #### createOuterAsset(player: SupRuntime.Player, asset: any)
  209.  
  210. Ie (from the scene plugin) :
  211.  
  212.     export function createOuterAsset(player: SupRuntime.Player, asset: any) {
  213.       return new (<any>window).Sup.Scene(asset);
  214.     }
  215.  
  216. #### setupComponent(player: SupRuntime.Player, component: any, config: any)
  217.  
  218. Do stuff with components (like get a resource then apply the info to the components).
  219.  
  220. #### loadResource(player: SupRuntime.Player, resourceName: string, callback: (err: Error, resource?: any) => any)
  221.  
  222. Ie (similar to assets) :
  223.  
  224.     export function loadResource(player: SupRuntime.Player, resourceName: string, callback: (err: Error, resource?: any) => any) {
  225.       player.getAssetData(`resources/${resourceName}/resource.json`, "json", (err, data) => {
  226.         if (err != null) { callback(err); return; }
  227.  
  228.         // do stuff if needed
  229.  
  230.         callback(null, data);
  231.       });
  232.     }
  233.  
  234.  
  235.  
  236. For these functions to be called, you need to register plugins and resources to the SupRuntime object:
  237.  
  238. Ie (from typescript plugin) :
  239.  
  240.     import * as script from "./script"; // contains init), start(), loadAsset()
  241.     import * as Behavior from "./Behavior"; //  contains setupComponent()
  242.     import * as behaviorProperties from "./behaviorProperties"; // contains loadResource()
  243.  
  244.     SupRuntime.registerPlugin("script", script);
  245.     SupRuntime.registerPlugin("Behavior", Behavior);
  246.     SupRuntime.registerResource("behaviorProperties", behaviorProperties);
  247.  
  248. ### Expose a JS library
  249.  
  250. Just require the library and expose it to the window variable;
  251.  
  252. Ie (from the fTween plugin) :
  253.  
  254.     window.FTWEEN = require("../lib/ftween.js");
  255.  
  256. ### Register an early update functions
  257.  
  258. This is a way to have a function runs every frame (before the scripts update() functions) :
  259.  
  260. Ie (from the fTween plugin) :
  261.  
  262.     SupEngine.registerEarlyUpdateFunction("fTween", function() {
  263.       window.FTWEEN.update();
  264.     });
  265.  
  266. ---
  267. <a name="editors"></a>
  268. ## Editors
  269.  
  270. The editors folder contains sub-folders which contains the assets editors.
  271.  
  272. The sub-folders must be named after the asset's type (the first argument of SupCore.data.registerAssetClass()).
  273.  
  274. An asset editor is a standard webpage so the folder typically contains one html, css and js files.
  275.  
  276. Each editor's folders inside the public folder may in addition contains an icon.svg file as well as a locale folder.  
  277.  
  278.  
  279. ---
  280. <a name="settingseditors"></a>
  281. ## SettingsEditors
  282.  
  283. Settings Editor classes are used to build the editors to update resources through the Settings tool (provided by the Settings plugin).
  284.  
  285. Example of a minimal class, to edit a single param:
  286.  
  287.     import SpriteSettingsResource from "../data/SpriteSettingsResource";
  288.  
  289.     export default class SpriteSettingsEditor {
  290.  
  291.       projectClient: SupClient.ProjectClient;
  292.       resource: SpriteSettingsResource;
  293.  
  294.       filteringRow: SupClient.table.RowParts;
  295.       // define other row properties here
  296.  
  297.       fields: { [name: string]: HTMLInputElement|HTMLSelectElement } = {};
  298.  
  299.       constructor(container: HTMLDivElement, projectClient: SupClient.ProjectClient) {
  300.         this.projectClient = projectClient;
  301.  
  302.         let { tbody } = SupClient.table.createTable(container);
  303.  
  304.         // start appending rows to the table
  305.  
  306.         this.filteringRow = SupClient.table.appendRow(tbody, "Filtering");
  307.         this.fields["filtering"] = SupClient.table.appendSelectBox(this.filteringRow.valueCell, { pixelated: "Pixelated", smooth: "Smooth" });
  308.  
  309.         // append other rows here...
  310.         // check app/SupClient/SupClient.d.ts for type of fields other than select box
  311.  
  312.         // set the chenge event listener here
  313.  
  314.         this.fields["filtering"].addEventListener("change", (event: any) => {
  315.           this.projectClient.socket.emit("edit:resources", "spriteSettings", "setProperty", "filtering", event.target.value, (err: string) => { if (err != null) alert(err); });
  316.         });
  317.  
  318.         // do the same for other rows...
  319.  
  320.         this.projectClient.subResource("spriteSettings", this);
  321.       }
  322.  
  323.       onResourceReceived = (resourceId: string, resource: SpriteSettingsResource) => {
  324.         this.resource = resource;
  325.  
  326.         for (let setting in resource.pub) {
  327.           this.fields[setting].value = resource.pub[setting];
  328.         }
  329.       }
  330.  
  331.       onResourceEdited = (resourceId: string, command: string, propertyName: string) => {
  332.         this.fields[propertyName].value = this.resource.pub[propertyName];
  333.       }
  334.     }
  335.  
  336. In this case, the Ressorce class (in the data folder) would look like this:
  337.  
  338.     export default class SpriteSettingsResource extends SupCore.data.base.Resource {
  339.  
  340.       static schema = {
  341.         filtering: { type: "enum", items: [ "pixelated", "smooth" ], mutable: true },
  342.         // other fields here...
  343.         // ie:
  344.         // alphaTest: { type: "number", min: 0, max: 1, mutable: true }
  345.       }
  346.  
  347.       constructor(pub: any, serverData: any) {
  348.         super(pub, SpriteSettingsResource.schema, serverData);
  349.       }
  350.  
  351.       setup() {}
  352.  
  353.       init(callback: Function) {
  354.         this.pub = {
  355.           filtering: "pixelated",
  356.           // other default values here...
  357.           // alphaTest: 0.1
  358.         };
  359.  
  360.         super.init(callback);
  361.       }
  362.     }
  363.  
  364.  
  365. ### Registering
  366.  
  367. Ie:
  368.  
  369.     import SpriteSettingsEditor from "./SpriteSettingsEditor";
  370.  
  371.     SupClient.registerSettingsEditorClass("Sprite", SpriteSettingsEditor);
  372.     // the first argument is the name of the editor as displayed in the tool
  373.  
  374.  
  375.  
  376. ---
  377. ## Components
  378.  
  379. ---
  380. ## ComponentEditors
Advertisement
Add Comment
Please, Sign In to add comment