Custom Configuration
Configuration objects are used to initialize and configure services and other engine systems.
By default, configuration objects are serialized as scriptable object assets and stored at NaninovelData/Resources/Naninovel/Configuration
project directory. The assets are automatically generated when opening corresponding configuration menus (Naninovel -> Configuration
) in the Unity editor for the first time.
To access configuration objects via C# use Engine.GetConfiguration<T>()
static method, where T
is type of the configuration object you wish to access. For example, the following example demonstrates how to access audio configuration object:
var audioConfig = Engine.GetConfiguration<AudioConfiguration>();
NOTE
The engine initialization procedure is asynchronous, so even when automatic initialization is enabled, engine APIs (eg, GetConfiguration
method) may not be available right after Unity loads a scene (eg, in Awake
, Start
and OnEnable
MonoBehaviour methods); see accessing engine API guide for more info.
While Engine.GetConfiguration
method can only be used when the engine is initialized, as it requires a configuration provider object, which is specified when initializing the engine to allow custom serving scenarios at runtime, it's possible to access a configuration asset via default provider even when the engine is not initialized with ProjectConfigurationProvider
, eg:
var config = ProjectConfigurationProvider.LoadOrDefault<AudioConfiguration>();
While the configuration properties are meant to be changed via editor menus, it's still possible to modify them at runtime. Be aware, that the objects returned by default project provider are the actual assets stored in the project; if you modify them, the changes will persist through play mode sessions. This is in contrast to the configuration objects provided with Engine.GetConfiguration
method, which are instances and won't mutate the original assets.
Below is an example on changing ReferenceResolution
property of camera configuration object right after the engine is initialized:
using Naninovel;
using UnityEngine;
public static class ModifyConfigAtRuntime
{
[RuntimeInitializeOnLoadMethod]
private static void ModifyConfig ()
{
if (Engine.Initialized) OnInitializationFinished();
else Engine.OnInitializationFinished += OnInitializationFinished;
void OnInitializationFinished ()
{
Engine.OnInitializationFinished -= OnInitializationFinished;
var cameraConfig = Engine.GetConfiguration<CameraConfiguration>();
cameraConfig.ReferenceResolution = new Vector2Int(3840, 2160);
}
}
}
NOTE
Naninovel doesn't expect configurations to change while the engine is initialized, so you may need to apply the modifications before initializing the engine with either ProjectConfigurationProvider
or a custom provider in order for some changes to take effect.
Adding Configuration
To add a new custom configuration, create a C# class and inherit it from Configuration
.
[EditInProjectSettings]
public class MyCustomConfiguration : Configuration
{
[Header("My Custom Header 1")]
[Tooltip("Tooltip for my custom string.")]
public string MyCustomString = "Default value";
[Range(0, 100), Tooltip("Tooltip for my custom float.")]
public float MyCustomFloat = 10;
[Header("My Custom Header 2")]
public int[] MyCustomArray;
}
Notice the EditInProjectSettings
attribute: an associated editor menu is automatically added to the project settings when the attribute is applied, where you can modify serializable properties of you custom configuration asset just like in all the built-in menus.
WARNING
When adding custom implementation types under a non-predefined assembly (via assembly definitions), add the assembly name to the Type Assemblies
list found in the engine configuration menu. Otherwise, the engine won't be able to locate your custom types.
To access your custom configuration via C# use the same API as for the built-in assets:
var myConfig = Engine.GetConfiguration<MyCustomConfiguration>();
EXAMPLE
Another example of adding a custom configuration menu to set up an inventory system can be found in the inventory example project on GitHub.
Specifically, the custom configuration is implemented via InventoryConfiguration.cs runtime script.
To customize editor behaviour of your custom configuration (when it's drawn in the Naninovel's project settings), create a class under an editor script and inherit it from ConfigurationSettings<T>
, where T
is the type of your custom configuration. You can use built-in settings editor scripts stored at Naninovel/Editor/Settings
package folder for reference when building your own editors.
Overriding Built-in Editors
It's possible to override the built-in configuration editors (Naninovel's project settings menus) by applying OverrideSettings
attribute to an editor class inherited from ConfigurationSettings<T>
(or any of its derivatives), where T
is the type of the configuration. Make sure to store the custom editor scripts under "Editor" folder to make them included to the editor assembly.
Below is an example on overriding the built-in character manager configuration editor. The new editor is inherited from the built-in one and will additionally insert a label under Shared Poses
field with the total number of shared poses.
[OverrideSettings]
public class CustomCharacterSettings : CharactersSettings
{
protected override Dictionary<string, Action<SerializedProperty>>
OverrideConfigurationDrawers ()
{
var drawers = base.OverrideConfigurationDrawers();
drawers[nameof(CharactersConfiguration.SharedPoses)] = property => {
ActorPosesEditor.Draw(property);
EditorGUILayout.LabelField(
$"Number of shared poses is {property.arraySize}.");
};
return drawers;
}
}
Given the above editor, characters configuration will now draw as follows:
It's also possible to override built-in actor metadata editors. Below will insert a label under Message Color
field of the inspected actor with the name of that color.
[OverrideSettings]
public class CustomCharacterSettings : CharactersSettings
{
protected override MetadataEditor<ICharacterActor,
CharacterMetadata> MetadataEditor { get; } = new MetaEditor();
private class MetaEditor : CharacterMetadataEditor
{
protected override Action<SerializedProperty>
GetCustomDrawer (string propertyName)
{
if (propertyName == nameof(CharacterMetadata.MessageColor))
return property => {
EditorGUILayout.PropertyField(property);
EditorGUILayout.LabelField($"Message color of " +
"'{Metadata.DisplayName}' is '{property.colorValue}'.");
};
return base.GetCustomDrawer(propertyName);
}
}
}
— will result in:
Configuration Provider
It's possible to change the way configuration objects are served at runtime. For example, instead of static project assets, you can read the configuration from JSON files stored on a remote host.
To specify a custom configuration serving scenario, create a C# class and implement IConfigurationProvider
interface. The interface has just one method, that expects a Type
argument and returns a Configuration
object. It's up to you on how to construct and populate requested configuration objects, just make sure type of the returned object is equal to the requested one.
Below is an example of a custom provider implementation, that just returns default configuration objects:
public class CustomConfigurationProvider : IConfigurationProvider
{
public Configuration GetConfiguration (System.Type type)
{
var defaultAsset = ScriptableObject.CreateInstance(type);
return defaultAsset as Configuration;
}
}
Another example on overriding project characters configuration to inject metadata at runtime:
public class CustomConfigurationProvider : ProjectConfigurationProvider
{
public override Configuration GetConfiguration (System.Type type)
{
// Return project configs as-is for everything but characters.
if (type != typeof(CharactersConfiguration))
return base.GetConfiguration(type);
// Inject (or override) metadata of the characters.
// The actual data can be retrieved via external sources at runtime.
var charsConfig = (CharactersConfiguration)base.GetConfiguration(type);
charsConfig.Metadata["NewCustomChar"] = new CharacterMetadata {
Implementation = typeof(NarratorCharacter).AssemblyQualifiedName,
DisplayName = "Custom Narrator",
UseCharacterColor = true,
MessageColor = Color.cyan,
// etc...
};
// Return our modified characters config.
return charsConfig;
}
}
Once the custom configuration provider is ready, you have to make the engine use it instead of the built-in one by creating a custom engine initialization script. By default, the engine is initialized via Naninovel/Runtime/Engine/RuntimeInitializer.cs
; feel free to use it as a reference when creating your own initialization script.
Alternatively, if your goal is just to use a custom configuration provider, but keep the default engine initialization routine, consider using RuntimeInitializer.InitializeAsync(IConfigurationProvider)
static method, which accepts an optional argument for configuration provider:
public class CustomInitializer
{
[RuntimeInitializeOnLoadMethod]
private static void InitializeWithCustomProvider ()
{
var customProvider = new CustomConfigurationProvider();
RuntimeInitializer.InitializeAsync(customProvider).Forget();
}
}
No matter which way you choose to initialize the engine, don't forget to disable Initialize On Application Load
option in the engine configuration menu to disable default initialization procedure.