# How the Rule-Based Event Triggering System Works in Undertale-Changer-Template

> Discover how the rule-based event triggering system in Undertale-Changer-Template operates. Learn how it evaluates rules, fires events, and updates values to enhance your game development.

- Repository: [Archived AIk/undertale-changer-template](https://github.com/arch-aik/undertale-changer-template)
- Tags: how-to-guide
- Published: 2026-02-25

---

**The rule-based event triggering system evaluates ScriptableObject rules against active events every frame, automatically firing new events, invoking C# methods, and updating fact values when trigger conditions and optional fact-based criteria are satisfied.**

The Undertale-Changer-Template (UCT) implements a data-driven rule-based event triggering system that powers all gameplay logic through Unity ScriptableObjects. This architecture allows designers to author complex game behavior without writing code, while the runtime engine handles the frame-by-frame evaluation of conditions and execution of actions.

## Core Architecture Components

The system relies on four primary data structures and one runtime controller to manage the flow from trigger detection to action execution.

### RuleTable and RuleEntry Data Containers

**[`RuleTable.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/RuleTable.cs)** defines a `ScriptableObject` that stores a serializable list of `RuleEntry` objects. Designers create these assets at [`Assets/Scripts/UCT/EventSystem/RuleTable.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/EventSystem/RuleTable.cs) to hold scene-specific or global rule definitions. Each **`RuleEntry`** (defined in [`RuleEntry.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/RuleEntry.cs)) contains the trigger event names, target events to fire, method calls to invoke, fact modifications to apply, and an optional logical criterion for conditional evaluation.

### EventController Runtime Engine

**[`EventController.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/EventController.cs)** ([`Assets/Scripts/UCT/EventSystem/EventController.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/EventSystem/EventController.cs)) is a persistent `MonoBehaviour` that serves as the runtime engine. It loads rule tables, iterates through active events during each `Update` cycle, and orchestrates the evaluation and execution phases. This controller maintains references to both global and scene-specific rule tables, concatenating them during evaluation.

### Event and Fact State Tables

**`EventTable`** and **`FactTable`** ScriptableObjects track the current runtime state. The `EventTable` maintains a list of `EventEntry` objects with boolean `isTriggering` flags, while the `FactTable` stores integer-based `FactEntry` values that rules read and modify. These tables provide the volatile state that rules query during criterion evaluation.

## The Rule Evaluation Flow

The engine processes rules through a deterministic pipeline that runs every frame, ensuring immediate response to gameplay state changes.

### 1. Table Loading and Initialization

During `EventController.Start()`, the system loads the global rule table from `Tables/RuleTable` and the scene-specific table from `Tables/<SceneName>/RuleTable`. These `RuleTable` assets populate the runtime evaluation lists.

### 2. Update Loop and Event Detection

Every frame, `EventController.UpdateEvent()` processes both the global and scene event tables. For each `EventEntry` where `isTriggering` equals `true`, the `Detection` method gathers all rules by concatenating `ruleTable.rules` with `globalRuleTable.rules`, then evaluates them sequentially via `DetectionRule`.

### 3. Criterion and Trigger Validation

`DetectionRule` performs two validation checks before firing:
- **Criterion Evaluation**: If `useRuleCriterion` is enabled, the system calls `rule.ruleCriterion.GetResult()` to evaluate the boolean expression against current fact values.
- **Trigger Matching**: The current event name must exist in the `rule.triggeredBy` list, or the rule must be forced via a direct call.

### 4. Action Execution

When validation succeeds, the rule executes three action types in this specific order:
- **Event Firing**: `SetTriggering` adds names from `rule.triggers` to the appropriate `EventTable`, setting their `isTriggering` flags to `true`.
- **Method Invocation**: `InvokeRuleMethod` walks the `methodNames` list and executes corresponding delegates from the `MethodDictionary`, parsing prefixes like `bool:`, `Vector2Ease:`, or `scene:` to handle type conversion.
- **Fact Modification**: `SetFact` applies the `FactModification` list (operations like `Change`, `Add`, or `Subtract`) to local or global `FactTable` instances.

By default, the detection loop breaks immediately when a rule returns `isTriggered == true`, ensuring only the first matching rule fires per event. Enable `isExecuteAllRules` on the trigger component to allow multiple rule matches per frame.

## Rule Criteria and Logical Evaluation

Complex conditional logic is handled through the recursive `RuleCriterion` structure without requiring custom code.

### Fact-Based Conditions with RuleCriterion

The `RuleCriterion.GetResult()` method in [`RuleEntry.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/RuleEntry.cs) evaluates boolean expressions through recursive descent:

```csharp
public bool GetResult()
{
    // Base case: direct fact comparison
    if (criteria.Count == 0) 
    { 
        // Compare fact.value vs detection using CriteriaCompare operators
    }
    
    // Recursive case: combine child criteria with And/Or/None operations
}

```

The system looks up facts from the local `FactTable` by default, or the global table when `isGlobal` is `true`. The `CriteriaCompare` enum supports `GreaterThan`, `Equal`, `LessThan`, and other relational operators. Set `isResultReversed` to `true` to invert the final boolean result.

### Method Invocation via MethodDictionary

`EventController` maintains a `MethodDictionary` that maps string descriptors to static method delegates. When `InvokeMethodByName` processes entries like `"bool:PlayerCanMove"`, it parses the prefix to determine the return type and parameter signature, converts string parameters to C# types, and invokes the delegate. This allows designers to call gameplay methods directly from data.

### Fact Modifications and Execution Order

Fact modifications occur **after** method invocation completes. This sequencing guarantees that any invoked method reading a fact value sees the **pre-modification** state, preventing race conditions between the action and the state change.

## Practical Implementation Examples

### Defining Rules in the Unity Inspector

Create a `RuleTable` asset via `Assets → Create → UCT-EventSystem → RuleTable`, then add a `RuleEntry` with these fields:

| Field | Value |
|-------|-------|
| `name` | `OpenDoorIfHasKey` |
| `triggeredBy` | `["PlayerNearDoor"]` |
| `useRuleCriterion` | `true` |
| `ruleCriterion` | fact: `hasKey`, compare: `Equal`, detection: `1`, isGlobal: `false` |
| `triggers` | `["DoorOpened"]` |
| `methodNames` | `["bool:PlayerCanMove"]` |
| `firstStringParams` | `["false"]` |
| `useMethodEvents` | `[true]` |
| `methodEvents` | `["PlayDoorSound"]` |
| `factModifications` | `[{fact: "hasKey", operation: Subtract, number: 1}]` |

This configuration requires the `hasKey` fact to equal `1`, fires the `DoorOpened` event, disables player movement, plays a sound event, and consumes one key unit.

### Creating Rules Programmatically

Instantiate rules at runtime using the `RuleEntry` struct:

```csharp
using UCT.EventSystem;
using System.Collections.Generic;

public static RuleEntry CreateDoorRule()
{
    return new RuleEntry
    {
        name = "OpenDoorIfHasKey",
        triggeredBy = new List<string> { "PlayerNearDoor" },
        useRuleCriterion = true,
        ruleCriterion = new RuleCriterion
        {
            isResultReversed = false,
            isGlobal = false,
            fact = new FactEntry { name = "hasKey", value = 0 },
            compare = CriteriaCompare.Equal,
            detection = 1,
            operation = RuleLogicalOperation.And,
            criteria = new List<RuleCriterion>()
        },
        triggers = new List<string> { "DoorOpened" },
        methodNames = new List<string> { "bool:PlayerCanMove" },
        firstStringParams = new List<string> { "false" },
        useMethodEvents = new List<bool> { true },
        methodEvents = new List<string> { "PlayDoorSound" },
        factModifications = new List<FactModification>
        {
            new FactModification
            {
                fact = new FactEntry { name = "hasKey" },
                operation = FactModification.Operation.Subtract,
                number = 1
            }
        }
    };
}

```

### Triggering Events from Gameplay Code

Activate events from anywhere in your codebase to initiate rule evaluation:

```csharp
// In player collision detection or input handling:
EventController.SetTriggering("PlayerNearDoor");

```

This sets the `isTriggering` flag on the corresponding `EventEntry`, causing the `EventController` to evaluate matching rules during the next `Update` cycle.

## Summary

- **Data-Driven Architecture**: Rules are authored as `RuleEntry` objects inside `RuleTable` ScriptableObjects, separating gameplay logic from code.
- **Frame-Synchronous Evaluation**: `EventController.UpdateEvent()` checks all triggered events against loaded rules every frame via the `Detection` and `DetectionRule` methods.
- **Hierarchical Logic**: `RuleCriterion` supports nested boolean expressions with AND/OR operations, fact comparisons, and negation through `isResultReversed`.
- **Sequential Execution**: Rules fire events first, then invoke methods via `MethodDictionary`, and finally apply `FactModification` operations to ensure consistent state reads.
- **Scoped State**: The system distinguishes between global and scene-local facts and rules, loading tables from `Tables/RuleTable` and `Tables/<SceneName>/RuleTable` respectively.

## Frequently Asked Questions

### What file contains the main rule evaluation loop?

**[`EventController.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/EventController.cs)** ([`Assets/Scripts/UCT/EventSystem/EventController.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/EventSystem/EventController.cs)) contains the core evaluation loop. The `UpdateEvent()` method drives the process, while `Detection()` and `DetectionRule()` handle the specific logic for matching triggered events against rule criteria and executing the resulting actions.

### How does the system handle complex logical conditions?

The `RuleCriterion` struct implements recursive boolean evaluation through the `GetResult()` method. It supports nested sub-criteria combined via the `RuleLogicalOperation` enum (`And`, `Or`, `None`), fact comparisons using the `CriteriaCompare` operators, and result inversion via the `isResultReversed` flag. This allows designers to build complex conditional trees entirely within the Unity Inspector without code changes.

### Can multiple rules fire from a single event trigger?

By default, the system stops evaluating after the first matching rule returns `isTriggered == true` to prevent cascading conflicts. However, you can enable the `isExecuteAllRules` flag in the trigger component configuration to allow all matching rules to fire during the same evaluation frame, processing them in the order they appear in the `RuleTable`.

### What is the execution order when a rule fires?

When a rule's conditions are satisfied, the `EventController` executes actions in this strict sequence: first it fires new events via `SetTriggering`, then it invokes all specified methods through `InvokeRuleMethod`, and finally it applies fact modifications via `SetFact`. This ordering ensures that invoked methods read the pre-modification fact values, while subsequent rules in the chain see the updated values.