Skip to Content
ExtendingCreating a new service

Creating a New Gnosis Service

Services are the modular building blocks of Gnosis Engine. Whether you need a simple score tracker, a complex inventory system, or a bridge to a third-party SDK, you do it by creating a service.

1. Inheritance: GnosisServiceBase

You should always subclass GnosisServiceBase to create a new service. It provides:

  • Automatic context management.
  • Scoped state helpers (GetInt, SetNode, etc.).
  • Built-in logging with service-id prefixing.
  • Simplified InvokeFunction and CallService access.
public class MyCustomService : GnosisServiceBase { // Implementation goes here }

2. Required Properties

Every service must define its identity and lifecycle.

Service ID

The Id is a unique string used by other services, rules, and JSON invocations to find your service.

public override string Id => "MyCustom";

Lifetime

The Lifetime determines when the service is created and destroyed.

  • Persistent: Created once when the engine starts. Lives for the entire application session.
  • Ephemeral: Created and destroyed with every “Run” or “Match”. Ideal for match-specific logic.
public override GnosisLifetime Lifetime => GnosisLifetime.Persistent;

3. Implementing Functions

To make your service interact with the rest of the engine (especially via JSON), you must implement function handling.

InvokeFunction

This is the entry point for all dynamic calls (from Rules, Invocations, or other services).

public override GnosisFunctionResult InvokeFunction(string name, GnosisNode parameters) { switch (name) { case "DoAction": int amount = ReadIntParameter(parameters, "amount") ?? 0; return ExecuteAction(amount); default: return base.InvokeFunction(name, parameters); } }

GetFunctions (Optional)

Return a list of GnosisFunctionDescriptor objects to “advertise” what your service can do. This is mostly used for debugging and auto-documentation tools.


4. State Management

Use the built-in scoped helpers to store data in the shared GnosisStore. These helpers automatically place your data under the correct Persistent or Ephemeral branch.

// Increment a persistent "TotalUses" stat Increment("TotalUses", persistent: true); // Set a temporary "CurrentLevel" value Set("CurrentLevel", 5, persistent: false); // Read a value int level = GetInt("CurrentLevel", persistent: false);

5. Registration

A service won’t run unless it’s registered with the engine. For Unity-based projects, this is handled via the UnityGnosisEngine component.

Unity Adapter (Unity Integration)

On your UnityGnosisEngine GameObject in the Unity Inspector:

  1. Add a new entry to the Services list.
  2. Set the Id to match your service (e.g., MyCustom).
  3. Set the Lifetime and Binding Policy.
  4. If the service requires a factory (e.g., for custom constructor logic), ensure it is registered in the engine’s config, though most services are automatically instantiated by the adapter if an entry is present.

Lifecycle Hooks

You can override these methods to react to engine events:

  • OnInitialize(): Called when the service is first created.
  • OnShutdown(): Called when the service is being destroyed.
  • OnRunStarted(): Called when a new match/run begins.
  • OnRunEnded(): Called when a match/run concludes.

Full Example: Minimal Health Service

Here is a complete example of a service that manages player health using the engine’s REQUEST/FACT pattern.

using GnosisEngine; using System; public static class HealthEvents { public const string REQUEST_TAKE_DAMAGE = "REQUEST_HEALTH_TAKE_DAMAGE"; public const string FACT_DAMAGED = "FACT_HEALTH_DAMAGED"; } public class HealthService : GnosisServiceBase { // 1. Identity public override string Id => "Health"; public override GnosisLifetime Lifetime => GnosisLifetime.Ephemeral; private int GetHP() => GetInt("CurrentHP", persistent: false, defaultValue: 100); // 2. Handle incoming calls (from JSON Invocations or Rules) public override GnosisFunctionResult InvokeFunction(string name, GnosisNode parameters) { if (name == "TakeDamage") { int amount = ReadIntParameter(parameters, "amount") ?? 0; return TakeDamage(amount); } return base.InvokeFunction(name, parameters); } // 3. Core Logic using REQUEST/FACT pattern private GnosisFunctionResult TakeDamage(int amount) { int currentHp = GetHP(); // A. Publish REQUEST to allow Interceptors (e.g. Shields/Armor rules) to modify damage var requestPayload = Context.Store.CreateObject(); requestPayload.Set("amount", amount); var publishResult = Context.EventBus.Publish( new GnosisEvent(HealthEvents.REQUEST_TAKE_DAMAGE, requestPayload, false)); // B. Get the final amount after rules have processed the event int finalDamage = ReadIntParameter(publishResult.FinalEvent.Data, "amount") ?? amount; // C. Apply change to the state store int newHp = Math.Max(0, currentHp - finalDamage); Set("CurrentHP", newHp, persistent: false); // Log for debugging Log(GnosisLogLevel.Info, $"Applied {finalDamage} damage. HP: {currentHp} -> {newHp}"); // D. Publish FACT to notify observers (e.g. UI, VFX, or Achievements) var factPayload = Context.Store.CreateObject(); factPayload.Set("damageTaken", finalDamage); factPayload.Set("previousHp", currentHp); factPayload.Set("currentHp", newHp); Context.EventBus.Publish(new GnosisEvent(HealthEvents.FACT_DAMAGED, factPayload, false)); return GnosisFunctionResult.Ok(); } }

Initializing State via JSON

Instead of hard-coding initial values, you can define them in your project’s ephemeral.json (or persistent.json). The service will automatically use these values as the “source of truth” when calling GetInt or GetNode.

ephemeral.json

{ "Health": { "CurrentHP": 150 } }
Last updated on