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
InvokeFunctionandCallServiceaccess.
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:
- Add a new entry to the Services list.
- Set the Id to match your service (e.g.,
MyCustom). - Set the Lifetime and Binding Policy.
- 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
}
}