# Options Manager

{% hint style="info" %}
The Options Manager component allows you to define options according to the project requirements. These can be settings related to graphics, sound, controls and other game elements. You can customize the different options according to what your game needs, creating a more customizable experience.
{% endhint %}

### Adding New Options

1. Locate the **Game Options** asset in the **Scriptables** folder.

<figure><img src="https://2665493337-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fh8bp6Jx5qkS4c0NEaWR1%2Fuploads%2FFBmnSBQqYxAhgReHZ7M4%2Foptions_asset.png?alt=media&#x26;token=04ce1ac9-2083-4c23-ac30-842aedf36e7d" alt=""><figcaption></figcaption></figure>

2. Select the **Game Options** asset and click the **Open Options Builder** button. This will launch the Options Builder window, allowing you to edit existing options or add new ones.

<figure><img src="https://2665493337-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fh8bp6Jx5qkS4c0NEaWR1%2Fuploads%2F4bi2sGSFIt31Pem6YZZW%2Foptions_asset_1.png?alt=media&#x26;token=dd1e43ee-66b8-44f6-b21a-1ee19877d4be" alt=""><figcaption></figcaption></figure>

3. To add new sections or options, click the plus icon beside the **Options Profile** name or the **Section** name.

<figure><img src="https://2665493337-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fh8bp6Jx5qkS4c0NEaWR1%2Fuploads%2FQVxEyepmdysegYZypySS%2Foptions_builder.png?alt=media&#x26;token=205610f5-1cea-4768-94ff-94d7fe7988d6" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
You can move or rearrange sections or options by simply dragging the element to the desired location or section.
{% endhint %}

4. After adding an option to the section, you must define the following values: Name, Title, and Prefab.
   * **Name** serves as the key used in the configuration file or as an identifier for the option observer.
   * **Title** is the display name of the option shown in the UI.
   * **Prefab** is the object instantiated when the options are built.

<figure><img src="https://2665493337-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fh8bp6Jx5qkS4c0NEaWR1%2Fuploads%2FKty5bdOlR8WbOcaBYptQ%2Foption.png?alt=media&#x26;token=06a4315d-e230-46cc-8afc-4b82f4d7a117" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
Under the prefab selection, choose the prefab you believe is best suited for your option. Keep in mind that some options use specific value types, so you cannot use a Slider (float) for an option that is based on an int value type (e.g., Language).
{% endhint %}

5. Once you've finished modifying the options, be sure to save the asset by clicking the **Save Asset** button.
6. Navigate to the **Options Manager** component within the **GAMEMANAGER** object and click **Refresh Options**. This will update and display any newly added sections or options under **Option Links**.

<figure><img src="https://2665493337-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fh8bp6Jx5qkS4c0NEaWR1%2Fuploads%2FaIZmtqhoHIplbj3KuJ0N%2Foptions_manager.png?alt=media&#x26;token=e86055b3-6e2c-42db-a8ac-0b5a6e065973" alt=""><figcaption></figcaption></figure>

{% hint style="danger" %}
When new sections are added or a section transform is not assigned, you’ll need to manually update the options UI by creating a new layout group similar to the existing ones. After that, assign a transform where the options for that section will be instantiated.
{% endhint %}

7. Finally, click **Build Options** to automatically remove the old option references and generate new ones based on the current **Options Asset**.

### Observing Options

There are three ways to observe your newly created options:

* Use the `ObserveOption()` method directly within your component to monitor the option value.
* Utilize the **CustomOptionObservers** component to create and attach a custom observer module.
* Employ the **OptionObserver** component to invoke a specified reflected type.

#### ObserverOption Method

Here is the code that observes the option directly (the value must be converted to the option type):

```csharp
using UnityEngine;

namespace UHFPS.Runtime
{
    public class TestObserver : MonoBehaviour
    {
        public bool BoolValue;

        private void Start()
        {
            OptionsManager.ObserveOption("some_option", (value) => BoolValue = (bool)value);
        }
    }
}
```

#### **CustomOptionObservers Method**

In some circumstances, you will need to write a custom options observer that does not belong to any script, such as the **Audio Listener** class. In that case, you can write your own **OptionObserverType**.

```csharp
using System;
using UnityEngine;

namespace UHFPS.Runtime
{
    [Serializable]
    public class OptionListenerVolume : OptionObserverType
    {
        public override string Name => "Audio Listener Volume";

        public override void OptionUpdate(object value)
        {
            if (value == null)
                return;

            AudioListener.volume = (float)value;
        }
    }
}
```

After writing a custom **OptionObserverType**, you can add the **CustomOptionObservers** component to the desired location and add your newly created option to the list of option observers. The **Observe Option Name** must be the same as the option name in the **Options Builder**.

<figure><img src="https://2665493337-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fh8bp6Jx5qkS4c0NEaWR1%2Fuploads%2Fzj4NSL0ifNoYgSb7bu9M%2FScreenshot_8.png?alt=media&#x26;token=95b89f5e-36e7-48a1-894d-341c6a8f19d8" alt=""><figcaption></figcaption></figure>

#### OptionObserver Method

In certain situations, you may simply want to observe an option that reflects its value onto a field, property, or method. For this, the **OptionObserver** component is ideal. For example, if you only want to display the FPS Counter when the `show_fps` option is set to `true`, you can create a custom method that accepts a `bool` parameter and use it with the **OptionObserver**.

<figure><img src="https://2665493337-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fh8bp6Jx5qkS4c0NEaWR1%2Fuploads%2Fk48nMsJSOKwCZNnopQzP%2FScreenshot_9.png?alt=media&#x26;token=d49b8365-7be9-4f52-89d7-c0569773762d" alt=""><figcaption></figcaption></figure>

### Creating New Options

1. Create a new class that will inherit from the `OptionModule` class.

{% hint style="info" %}
There are several functions you can override to define how an option should behave:

* `ContextName` - Specifies the path and name used for the option selector.
* `OnApplyOption()` - Called when options are applied through the UI. Use this to implement what happens when a user confirms their settings.
* `OnLoadOption(bool fromFile)` - Invoked at `Start`, this loads the option settings. The `fromFile` parameter indicates if the data is being loaded from saved settings.
* `OnBuildOption(OptionBehaviour behaviour)` - Triggered when you click **Build Options.** Use this to define how the option should be constructed.
* `OnBuildOptionRuntime()` - A helper function to define or modify option values during runtime, useful for dynamic setups.
  {% endhint %}

2. Override `OnApplyOption()` so we can define what happens after the settings are confirmed.

```csharp
public override void OnApplyOption()
{
    int value = (int)Value;
    bool converted = value != 0;

    if (Behaviour.IsChanged)
    {
        // do something when the setting changes
        // e.g. apply the setting
    }

    // store the value in SerializableData, which is then serialized into the config file.
    Options.SerializableData[Name] = new(converted);
}
```

{% hint style="danger" %}
In this example, we'll define a simple boolean setting:

* You can use the `Value` parameter to quickly access the current value set through the UI.
* The data type of the `Value` parameter depends on the associated **Option Behaviour** component. For instance, the **OptionsRadio** behaviour returns an integer type. Be careful to correctly convert the `Value` into the appropriate type.
* You can use `Behaviour.IsChanged` to verify if the option was changed through the UI.
* Finally, make sure to store the `Value` into `SerializableData`, using the option's `Name` as the key.
  {% endhint %}

3. Override `OnLoadOption(bool fromFile)` to define what happens when the setting is loaded.

```csharp
public override void OnLoadOption(bool fromFile)
{
    bool optionValue = DefaultValue;

    if (fromFile && CheckOption(JTokenType.Boolean, out bool value))
        optionValue = value;

    // do something after loading the value
    // e.g. apply loaded setting

    Behaviour.SetOptionValue(optionValue);
}
```

{% hint style="info" %}
Within this method, you can use the `fromFile` parameter to determine whether the option is being loaded from a file. If it is, use `CheckOption` to retrieve the correct value from the config file.

Finally, assign the value back to the `Behaviour` to ensure the settings UI reflects the correct value whether it's loaded from the file or using a default fallback.
{% endhint %}

4. Override `OnBuildOption(OptionBehaviour behavior)` to define how the setting will be constructed.

```csharp
public override void OnBuildOption(OptionBehaviour behaviour)
{
    behaviour.SetOptionData(new StorableCollection()
    {
        { "options", new GString[] { OffName, OnName } },
        { "defaultValue", DefaultValue ? 1 : 0 }
    });
}
```

{% hint style="info" %}
In my case, I passed the option names and the default value into the **OptionBehaviour**, where I then populated the **Options** variable. This variable is used to cycle between the **On** and **Off** states of the setting.
{% endhint %}

> In some cases, you may also need to override the **`OnBuildOptionRuntime()`** method, which allows you to populate option states dynamically when the game starts.
>
> In this example, we used it to populate the option states with available screen resolutions, ensuring the settings reflect the current environment at runtime.

```csharp
public override void OnBuildOptionRuntime()
{
    string[] resolutions = Options.Resolutions.Select(x => $"{x.width}x{x.height}").ToArray();
    GString[] gStrings = resolutions.Select(x => new GString($"{x}")).ToArray();
    Behaviour.SetOptionData(new StorableCollection() { { "options", gStrings } });
}
```

### Creating New OptionBehaviour

1. Create a new class that will inherit from the `OptionBehaviour` class.

{% hint style="info" %}
You’ll be prompted to implement a few essential methods when creating a custom option:

* **`GetOptionValue()`** – Return the actual value of the setting here. This value will be used during the **`OnApplyOption()`** call to apply the current setting.
* **`SetOptionValue(object value)`** – Define how the setting should behave when a value is loaded, typically used in the **`OnLoadOption()`** method.
* **`SetOptionData(StorableCollection data)`** – Use this method to define how the option states should be populated when using **`OnBuildOptionRuntime()`**, such as loading data like available screen resolutions or custom values.
  {% endhint %}

```csharp
public override void SetOptionValue(object value)
{
    int radio = Convert.ToInt32(value);
    SetOption(radio);
    IsChanged = false;
}

public override object GetOptionValue()
{
    return RadioIndex;
}

public override void SetOptionData(StorableCollection data)
{
    if(data.TryGetValue("options", out GString[] options))
        Options = options.Select(x => new GString(x)).ToArray();

    if (data.TryGetValue("defaultValue", out int value))
        RadioIndex = value;
}

public void SetOption(int index)
{
    RadioIndex = index;
    IsChanged = true;
}
```

{% hint style="info" %}
There are additional helpful properties available, such as:

* **`IsChanged`** – Indicates whether the option value has been modified through the UI. This is useful for checking if an update or save action is necessary.
  {% endhint %}

{% hint style="info" %}
There are also several predefined **OptionBehaviours** you can use out of the box:

* **`Radio Indicators M`** – *(Int)* A radio selector with large horizontal indicators.
* **`Radio Indicators S`** – *(Int)* A radio selector with smaller horizontal indicators.
* **`Radio Many`** – *(Int)* A radio selector without horizontal indicators, suitable for a larger set of options.
* **`Slider`** – *(Int/Float)* A simple slider for adjusting values.
* **`SEPARATOR`** – A visual separator used to organize and distinguish different option groups in the UI.
  {% endhint %}

2. Newly created **OptionBehaviours** can be added to the **Options Asset**, which makes them available for selection within the **Options Builder**.

<figure><img src="https://2665493337-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fh8bp6Jx5qkS4c0NEaWR1%2Fuploads%2FJ1nvhCt1CVdQXV7cQSbd%2Foptions_asset_prefabs.png?alt=media&#x26;token=e0fd24fa-5d15-4fe2-8668-9e1de9dd915a" alt=""><figcaption></figcaption></figure>
