Make games with Superpowers — The extensible, collaborative HTML5 2D+3D game maker

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.

In Superpowers v1.0, systems and plugins will be moved to the user data folder.

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:

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:

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:

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.

  1. It first loads all APIs, components and runtime code exposed by the plugins
  2. Then it load all the project assets
  3. 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.

  1. It checks if the user has provided the path to the love executable, and if not, asks for it
  2. It then downloads all the game's assets from the server into a temporary folder
  3. 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

More soon! In the meantime, check out Florent Poujol's documentation (which might be a bit outdated since we've made a lot of changes recently) You should also check out this explanation of the structure of a plugin on Gitter.