Skip to content

Latest commit

 

History

History
308 lines (212 loc) · 10.4 KB

CREATING_PLUGINS.md

File metadata and controls

308 lines (212 loc) · 10.4 KB

Table of Contents

ℹ️ Plugin API Version is presently 2.0.0

ℹ️ For API Type documentation visit the Plugin API documentation.

ℹ️ Checkout the official plugins for several examples.

Overview

Plugins in Titan Reactor allow you to display custom charts, messages, player scores, control cameras, change player colors, etc. Plugins are written using Javascript/JSX or TypeScript/TSX.

For the sake of clarity we will assume TypeScript is being used for the remainder of the documentation.

Getting Started

You'll first need a package.json file under your plugin folder with at least a unique name and version.

{
    "name": "@titan-reactor-plugin/my-cool-plugin",
    "description": "My Cool Plugin",
    "version": "1.0.0",
    "keywords": ["titan-reactor-plugin"],
    "peerDependencies": {
        "titan-reactor-runtime/ui": "2.0.0",
        "titan-reactor-runtime/host": "2.0.0"
    }
}

We use the npm package.json for our conventions here. titan-reactor-runtime is used for UI plugins and contains type definitions. titan-reactor-host is used for native plugins.

Plugin Files

Aside from the package.json file, there may also be either index.tsx or plugin.ts or both.

image An example plugin directory structure.

  • (Host Plugin) The index.ts file is runs in the same process as the game loop and is for sophisticated logic.
  • (React Plugin) The components/index.tsx file is for primarily for your UI components, which live in an iframe with other plugins in a separate UI process.

Your React plugin will be treated by the browser as an ES6 module, meaning you have full access to the ES6 module system. You are free to import additional packages from services such as skypack or have them bundled with titan-plugin-cli. Only mouse clicks events (onClick) will be available for listening to any of your react components.

For your host plugin, core dependencies are provided in the globalThis object such as THREE, postprocessing, and more.

Allowing For User Configuration

We use the config value in package.json to define user configuration options and default values. Titan Reactor will then provide the UI to modify these settings to the user (via Command Center).

We use Leva for this config window and so the values follow the Leva convention, you can see also their storybook examples.

{
    "name": "@titan-reactor-plugin/my-cool-plugin",
    "description": "My Cool Plugin",
    "version": "1.0.0",
    "keywords": ["titan-reactor-plugin"],
    "peerDependencies": {
        "titan-reactor-runtime": "2.0.0",
        "titan-reactor-host": "2.0.0"
    },
    "config": {
        "userName": {
            "value": "this value will be modifiable by the user since I have a value property!"
        }
    }
}

image

React Plugin

Once we've got our basic package.json we can start with index.tsx:

import React from "react";
import { usePluginConfig, useFrame, getFriendlyTime } from "titan-reactor-runtime/ui";

const MyComponent = () => {

    // will update any time the user changes config
    const config = usePluginConfig();
    // will update on every game second with latest frame data
    const frame = useFrame();

    return <h1>Hello { config.userName }. Game time is {getFriendlyTime(frame) </h1>
};

registerComponent({ snap: "left" }, MyComponent);

Calling registerComponent lets Titan Reactor know about our new React Component.

registerComponent()

Example:

registerComponent(
    {
        snap: "center",
        order: 0,
    },
    MyElement
);

snap (optional)

top | left | right | bottom | center

  • If multiple components are snapped to the center we provide a tabbing system.

  • If snap is omitted, positioning is upto the plugin author and the root node must have style position: absolute.

order (optional)

  • If multiple components are snapped into the same location, we use order to determine an ordering.
  • Defaults to 0

CSS

The following font-families are available to your styles:

  • Inter the default body font.

  • Conthrax and Conthrax-Bold are good for scores and numbers.

The following css variables are available to your styles:

  • --minimap-width and --minimap-height

If the minimap is not visible minimap-height will go to 0.

Open Props is also made available.

See Open Props for more details.

Host Plugin

When a game is started apis are made available to your Host plugin for that instance of the game. Your plugin runs in a sandboxed container so most browser APIs will not be available.

Your plugin.ts is loaded in the same process space as Titan Reactor itself. You can listen to hooks and modify scene and state objects, as well as create custom hooks for other plugins to listen to.

Hooks

Every host plugin can react to hooks.

Full documentation is available via typings.

import { PluginBase } from "titan-reactor-host";

// global dependencies are made available
// THREE - three.js
// STDLIB - additional three.js objects
// postprocessing - from the `postprocessing` npm package
// enums - several Starcraft constants and enums like unitTypes and orderTypes

// your plugin must return an object with keynames matching hook names that you want to listen to
export default class Plugin extends PluginBase {
    // init fires when everything is loaded but before the first frame is run
    async init() {}

    // Titan Reactor provides a UI for users to change config of your plugin
    // this hook fires anytime a user has changed a config field
    onConfigChanged(oldConfig) {
        // in this example we use goToFrame api call to jump to a user changed config value
        if (this.config.jumpToFrame !== oldConfig.jumpToFrame) {
            this.goToFrame(this.config.jumpToFrame);
        }
    }

    // when the plugin is reloaded, disabled, or when the current game/map is over
    dispose() {}

    // every bw game frame
    // frame - the frame number
    // commands - an array of replay commands
    // note some frames may be skipped for fps reasons
    // commands will include upto 5s of skipped frames
    onFrame(frame, commands) {}
}

Scene Controller Plugins

A scene controller is a special plugin that responds to user input in order to change camera positions and transitions.

Unlike regular plugins, hooks won't be executed unless the scene is active, including the regular plugin hooks.

export default class PluginAddon extends SceneController implements SceneController {

// REQUIRED.
async onEnterScene(prevSceneExitData) {
  // try our best to transition the camera location
  // to the previous target for this scene
  if (prevSceneExitData?.target?.isVector3) {
        this.viewport.orbit.setTarget(prevSceneExitData.target.x, 0, prevSceneExitData.target.z, false);
    } else {
        this.viewport.orbit.setTarget(0, 0, 0, false);
    }
}

// here we pass along target to the next scene
// the current target and position of the camera is provided
// for convenience
onExitScene(target, position) {
  return {
    target,
    position
  };
}

// When updating the mouse.
onCameraMouseUpdate(delta, elapsed, scrollY, screenDrag, lookAt, mouse, clientX, clientY, clicked);

// ...

Communicating between Game and your UI component

You can send messages back and forth to your react components.

In your index.tsx:

import React from "react";
import { useMessage, useSendMessage } from "titan-reactor/runtime";

const MyComponent = ({ config }) => {

  const sendMessage = useSendMessage();

  // host plugin sent a message
  useMessage(incomingMessage => {
    console.log(incomingMessage);
  });

  // send a message to host
  return <p onClick={() => sendMessage("To Host plugin")}>Send to host</p>
});

In your plugin.ts:

import { PluginBase } from "titan-reactor-host";

export default class Plugin extends PluginBase {
    onSceneReady() {
        // send message to react plugin
        this.sendUIMessage("Hello React plugin, from host.");
    }

    // react plugin sent a message
    onUIMessage(message) {
        console.log(message);
    }
}

Your messages may be throttled due to them needing to be serialized and sent via window.postMessage to the iframe container. It is recommended you reduce how much you send messages across to the bare minimum.

Additional Information

README.md

By including a README.md Titan Reactor will include a Readme tab in the users configuration window.

Deprecating Your Plugin

If you wish to decommission your plugin you may mark a release as deprecated by adding deprecated to the keywords in package.json.

Publishing Your Plugin

In order to make your plugin available to others, simply publish your package to npm and ensure you have titan-reactor-plugin in keywords. If you'd like to publish under the official @titan-reactor-plugins scope please see the Titan Reactor Plugins repository.