Patterns for creating and consuming plugins in the Better ECS workspace.
This skill outlines the necessary patterns for building reusable plugins and consuming them within the engine.
You should use this skill when:
packages/features or packages/foundation.packages/features/fps.Feature packages (for example @repo/fps) generally follow a factory pattern. They are functions that accept configuration options and return a system definition (created via createSystem).
Structure:
import { createSystem, useSystem } from "@repo/engine";
import { z } from "zod";
// define schema
const Schema = z.object({
enabled: z.boolean(),
configValue: z.number(),
});
type PluginOptions = {
initialValue: number;
enableByDefault?: boolean;
};
// 1. Export the system factory
export const MyPlugin = (opts: PluginOptions) => {
// 2. Return the createSystem call
return createSystem("plugin:my-plugin")({
system: Entrypoint,
initialize: () => Initialize(opts), // Pass options to init if needed
schema: {
schema: Schema,
default: {
enabled: opts.enableByDefault ?? true,
configValue: opts.initialValue,
},
},
});
function Initialize(options: PluginOptions) {
// setup resources (heavy lifting here, not in factory)
}
function Entrypoint() {
// Access own state
const { data } = useSystem("plugin:my-plugin");
if (!data.enabled) return;
// ... logic
}
};
Plugins are instantiated and passed to the createEngine function in the application.
import * as Engine from "@repo/engine";
import { MyPlugin } from "@repo/fps";
const engine = Engine.createEngine({
systems: [
// Instantiate the plugin with options
MyPlugin({
initialValue: 42,
enableByDefault: true,
}),
// ... other systems
],
});
plugin:) for system names to distinguish them from app-specific systems.initialize hook, not the factory function itself.