
Extending Superpowers
Superpowers is a highly extensible platform. While it is primarily used to make video games, it can be repurposed for any kind of creative work. Make sure you have the development version of Superpowers.
Systems and plugins
Superpowers currently ships with a single system, Superpowers Game
which lets you make 2D and 3D games with TypeScript.
You can build your own systems to provide entirely different project types.
The system itself provides the shared components (like a game engine for instance) upon which various plugins will build to provide editors and tools.
Setting up your own system
Systems are stored in the systems
folder, inside your Superpowers app.
You can create a new system by running:
cd superpowers
node server init $SYSTEM_ID
Then open systems/$SYSTEM_ID/package.json
and customize things as you like.
You'll want to edit systems/$SYSTEM_ID/public/locales/en/system.json
to describe your system, too:
{
"title": "Dummy",
"description": "A demo system that doesn't do much, but does it well."
}
These strings will appear in the project creation popup.
Setting up a plugin
Plugins are stored in systems/$SYSTEM_ID/plugins/$PLUGIN_AUTHOR/$PLUGIN_NAME
.
You can create a plugin by running:
cd superpowers
node server init $System_ID:$PLUGIN_AUTHOR/$PLUGIN_NAME
Plugin included by default with your system should be placed in the plugins/default
author folder.
We maintain a set of common plugins including the home chat,
the settings tool and the real-time collaborative text editor widget.
You can include them in your repository as a Git submodule in plugins/common
.
Anatomy of a plugin
At the root of your plugin, you'll find a package.json
file
(following npm's package.json file format).
Here's what it might look like:
{
"name": "superpowers-$SYSTEM_ID-$PLUGIN_AUTHOR-$PLUGIN_NAME-plugin",
"description": "My plugin for Superpowers Dummy",
"scripts": {
"build": "gulp --gulpfile=../../../../../scripts/pluginGulpfile.js --cwd=."
}
}
Besides package.json
, you'll find the following files at the root of your plugin:
tsconfig.json
is used bypluginGulpfile.js
to set TypeScript compiler optionsindex.d.ts
is used by TypeScript to find the type definition files for Superpowers (SupCore
,SupClient
, etc.)
The plugin's public
folder contains all the files that will be exposed to the client (the user's browser or the Superpowers app).
Many of them will be generated by compiling source files (written in Jade, Stylus and TypeScript for instance)
into files that can be consumed by the browser directly (HTML, CSS and JavaScript).
A plugin can do 3 types of things:
- Define asset and/or resource classes in
data/
- Provide asset editors and/or tools in
editors/
- System-specific stuff like exposing scripting APIs, scene components for Superpowers Game, etc.
Building your plugin
Gulp is the build tool used throughout Superpowers,
and pluginGulpfile.js
is a shared build file for your plugins that ships as part of Superpowers's main repository.
It assumes you're using Jade, Stylus and TypeScript to build your plugin. If you'd rather use something else,
you can write your own Gulpfile instead (or run whatever build command you want in your package.json
's build
script).
You can build just your plugin by typing npm run build $PLUGIN_AUTHOR/$PLUGIN_NAME
at the root of the Superpowers repository.
Or, if you have gulp
installed globally, you can directly run npm run build
inside your plugin's folder.
If you're using pluginGulpfile.js
, several things will happen when you run npm run build
in your plugin's folder.
If it exists, data/index.ts
will automatically be compiled to data/index.js
so it can be loaded by the server.
A browserified version of data/index.js
will then be generated at public/bundles/data.js
for
the client to load.
Any other folders at the root of your plugin containing an index.ts
file will have a browserified bundle generated
and placed in public/bundles/$FOLDER_NAME.js
so that it can be loaded from the client.
Editors and tools
Your plugin can expose zero or more editors in editors/*/
. Again, assuming you're using pluginGulpfile.js
:
editors/*/index.jade
will be transpiled topublic/editors/*/index.html
editors/*/index.styl
will be transpiled topublic/editors/*/index.css
editors/*/index.ts
will be browserified topublic/editors/*/index.js
When an editor is associated with an asset type, it becomes an asset editor. Otherwise, it'll appear in the tool shelf in the bottom-left corner of the project window.
The query string passed to your editor is parsed and stored in SupClient.query
.
When you're in an asset editor (as opposed to a tool), SupClient.query.asset
will contain the ID of the asset that should be edited.
An SVG icon for each editor should be placed in public/editors/*/icon.svg
.
Asset classes
Superpowers projects are basically made of a tree of assets. Your plugin can define one or more asset types.
Each asset type is a class that inherits from SupCore.Data.Base.Asset
(example).
The Asset
base class itself inherits from SupCore.Data.Base.Hash
,
which stores a dictionary of data in its .pub
property, with a schema for validating data.
Properties can be marked as mutable
in the schema, allowing clients to edit them directly through setProperty
(more on editing below).
Asset classes must be registered with SupCore.system.data.registerAssetClass
in data/index.ts
.
The first parameter should be the name of the editor associated with the asset type (example).
Resources
Resources are similar to assets, but for a particular resource class, there is always a single instance of it in every project and it doesn't appear in the asset tree. They are used to store project-wide settings or information. Tools will often subscribe to one or more resources that they allow editing. A tool might edit a settings resource that is then used in the associated asset editor for project-wide configuration.
A resource class must inherit from SupCore.Data.Base.Resource
and be registered under a unique name.
For instance, the startup scene of a Superpowers Game project is stored in the gameSettings
resource.
Subscriptions and editing
In order to allow editing the project's assets and resources, editors must subscribe to them.
First of all, your editor should open a connection to the server through SupClient.connect
(example).
Once connected, you'll probably want to create an instance of SupClient.ProjectClient
(example).
It can be used to manage subscriptions to the project's assets tree (name of each asset, type, parent, order, etc.), or to particular assets and resources
and it's easier than sending raw socket.io messages and keeping track of things yourself.
To subscribe to an asset, use projectClient.subAsset(assetId, assetType, subscriber);
(example).
The callbacks on your subscriber object will be called when various events are received from the server.
subscriber.onAssetReceived
will be called as soon as the asset has been received (example).
To edit an asset, you can use projectClient.editAsset(assetId, command, args..., callback);
.
The server, through RemoteProjectClient
will call a method starting with server_
followed by the command you specified.
If the command's callback doesn't return an error, the server will emit back the command to every client subscribed to the asset.
In turn, on the client-side, ProjectClient
will call the corresponding client_
method on your asset, applying the changes provided by the server.
Then it will notify all subscribers of the change.
In server_
methods, whenever the asset is edited, you should call this.emit("change");
to let the project server know that the asset has changed and should be scheduled for a write to disk soon.
The server saves a particular asset to disk no more often than once every 60s.
TODO: We still need to design a way to stream large assets.
Internationalization
You can place JSON localization files in public/locales/$LANGUAGE_CODE/$NAMESPACE.json
(example).
They will be made available through the t("namespace:path.to.key")
function to your Jade templates.
You can also load them up at runtime with SupClient.i18n.load
and use them with SupClient.i18n.t("namespace:path.to.key")
.
(example)
public/locales/$LANGUAGE_CODE/plugin.json
should contain an editors.*.title
key for each of your editors.
Generic plugin API
You can use SupCore.system.registerPlugin
to expose bits of code or data that can be reused by other plugins.
For instance, the Superpowers Game system uses this facility to let each plugin expose TypeScript APIs, as well as component configuration classes.
Each such plugin must be attached to a context (for instance "typescriptAPI"
) and given a name (example).
You should also define an interface to get type checking (example).
On the server-side, SupCore.system.requireForAllPlugins
lets you require a module for all plugins.
This can be used to conveniently load all registered plugins for a particular context (examples) .
On the client-side, you'll need to load them by appending a <script>
tag for each plugin.
To get a list of all plugins, you can fetch /systems/${SupCore.system.id}/plugins.json
(TODO: Update once plugins can be disabled per project)
(example).
Once the scripts are all loaded, you can use SupCore.system.getPlugins
to access the plugins in a typed fashion (examples).
Client-only plugins
For client-only stuff, a similar registration and retrieval API is available as SupClient.registerPlugin
and SupClient.getPlugins
.
It is used by Superpowers Game to register component editor classes and documentation pages (examples).
Running a project
When a project using your system is launched (or published), the public/index.html
file from your system will be its entry point.
It's up to you to decide what steps your system's public/index.html
should take in order to run a project. Here are a few examples:
In Superpowers Game, public/index.html
loads public/SupRuntime.js
, which is generated
from SupRuntime/src/index.ts
.
- It first loads all APIs, components and runtime code exposed by the plugins
- Then it load all the project assets
- Finally, it starts the game engine's main loop
In contrast, Superpowers LÖVE's public/index.html
loads public/index.js
, which is generated from player/src/index.ts
.
- It checks if the user has provided the path to the
love
executable, and if not, asks for it - It then downloads all the game's assets from the server into a temporary folder
- Finally, it launches the
love
executable, pointing it at the temporary folder
As a final example, Superpowers Web has basically no runtime to speak of.
It simply redirects to the exported project's own index.html
.
Version control
By convention, system repositories should be called superpowers-$SYSTEM_ID
while a repository for a single plugin should be called superpowers-$SYSTEM_ID-$PLUGIN_NAME-plugin
.
We recommend that you only version your source files, not the many generated HTML, CSS and JS files.
You can use the following .gitignore
file:
**/node_modules
**/*.html
**/*.css
**/*.js