> For the complete documentation index, see [llms.txt](https://tools.continis.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tools.continis.io/hats/building-on-hats/adding-new-rules.md).

# Adding new rules

The set of rules is open: you can write your own and they'll show up in the workspace's rule list automatically, right next to the built-in ones. A rule is just a small C# class.

## How to create a new rule

A rule needs four things:

1. It must live in an **Editor** assembly (an `Editor` folder, or an asmdef that references `Hats.Editor`).
2. It must inherit from `RuleBase` and be marked `[System.Serializable]`.
3. It must carry a `[RuleInteractionType(...)]` attribute that says **when** it acts.
4. It must implement `RuleName`, `RuleDescription`, `OnBecameActive` and `OnBecameInactive`.

That's it. Public serializable fields appear in the Inspector on their own, and the rule is added to the **+** menu of Workspaces automatically.

An example of a simple custom Rule:

```csharp
using Hats.Editor;
using Hats.Editor.Attributes;
using Hats.Editor.Rules;

namespace MyGame.HatsRules
{
    [System.Serializable]
    [RuleInteractionType(RuleInteractionType.OnWorkspaceActivated)]
    public class MyRule : RuleBase
    {
        public override string RuleName => "My Rule";

        public override string RuleDescription =>
            "A short explanation of what this rule does. " +
            "It's shown in the (?) tooltip next to the rule in the Inspector.";

        public override bool OnBecameActive(Hats.ChangeReason reason)
        {
            // Do the thing here.
            // Return true if it worked, false if it couldn't run.
            return true;
        }

        public override bool OnBecameInactive() => true;
    }
}
```

#### Interaction type

The `[RuleInteractionType(...)]` attribute is required, and decides which submenu the rule lands in:

* `OnWorkspaceActivated` — the rule does something only when the workspace is turned on (**On Activation**).
* `OnWorkspaceDeactivated` — the rule does something only when the workspace is turned off (**On Deactivation**).
* `OnOff` — the rule does something on activation and undoes it on deactivation (**On & Off**).

{% hint style="warning" %}
Don't forget the attribute. A rule without `[RuleInteractionType(...)]` won't be picked up correctly by the **+** menu.
{% endhint %}

#### Return values

`OnBecameActive` and `OnBecameInactive` return a `bool`: return `true` when the rule applied successfully, and `false` when it couldn't (for example because a required field is empty or the Prefab it was trying to load has been destroyed, etc.). When returning `false`, it's good practice to log a warning prefixed with `(Hats)` so the user knows why nothing happened.

Also, the return is used to decide whether to execute following rules in the Workspace. This is to avoid that if a rule doesn't execute correctly, following rules fail too.

#### Handling recompiles

`OnBecameActive` is also called after a domain reload (a script recompile) for whichever workspace is active, with `reason == Hats.ChangeReason.Recompile`.

Many rules early-return in that case, because whatever they did is already in place, but rules can be set to act again after a recompile if that makes sense.

If you don't need a rule to act again on recompile, you can early exit using this line:

```csharp
if (reason == Hats.ChangeReason.Recompile) return true;
```

#### Remembering previous state

Since rules are not serialised by default, an `OnOff` rule that needs to restore something to a previous state might lose the original state if a recompile happens while it's active.

As such, a good idea is to store the state value in a `ScriptableSingleton`, so that the value will survive a recompile:

```csharp
[RuleInteractionType(RuleInteractionType.OnOff)]
[System.Serializable]
public class MyToggleRule : RuleBase
{
    public override string RuleName => "My Toggle Rule";
    public override string RuleDescription => "...";

    public override bool OnBecameActive(Hats.ChangeReason reason)
    {
        // On recompile, don't overwrite the value we already stored.
        if (reason != Hats.ChangeReason.Recompile)
        {
            MyRuleMemory.instance.previousValue = SomeSetting;
            MyRuleMemory.instance.hasStored = true;
        }

        SomeSetting = myDesiredValue;
        return true;
    }

    public override bool OnBecameInactive()
    {
        if (!MyRuleMemory.instance.hasStored) return true;

        SomeSetting = MyRuleMemory.instance.previousValue;
        MyRuleMemory.instance.hasStored = false;
        return true;
    }
}

public class MyRuleMemory : ScriptableSingleton<MyRuleMemory>
{
    public bool previousValue;
    public bool hasStored;
}
```

{% hint style="info" %}
Some Editor APIs aren't ready the instant a workspace switches (the Scene View, for example). If you hit this, defer the work with `EditorApplication.delayCall += () => { ... };`.
{% endhint %}

#### Renaming a rule later

If you rename a rule's class after it's already been used in some workspaces, add the `[MovedFrom]` attribute so existing references keep working:

```csharp
using UnityEngine.Scripting.APIUpdating;

[System.Serializable]
[MovedFrom(true, sourceClassName: "OldRuleName")]
[RuleInteractionType(RuleInteractionType.OnOff)]
public class NewRuleName : RuleBase { /* ... */ }
```

## Custom Inspector (optional)

By default the rule's fields are drawn with Unity's standard property field, and the `(?)` tooltip is filled from `RuleDescription`. If you want a custom layout (extra buttons, a different control), add a drawer that inherits from `RulePropertyDrawer` and override `CreateMainElement` (to replace the body) or `AddExtraElements` (to append to the foldout).

{% hint style="info" icon="lightbulb" %}
For instance, the **Scene Camera to Position** rule does this to add its **Record Camera Position** button.
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://tools.continis.io/hats/building-on-hats/adding-new-rules.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
