Module Creation - VolcanicArts/VRCOSC Wiki

Creating a Module

This application is still under heavy design and module creation and maintenance will change heavily at times. I (VolcanicArts) will update all modules when breaking changes occur to keep them working, but keep in mind that if you're working on a module update you will need to keep your branch up-to-date with the latest changes.

Notes

Every module class is instantiated when VRCOSC is started. So if you have a module that has class-wide variables that control states, make sure to reset them in the OnStart event.

Section 0 - Class name

The class name when creating a module must explicitly be the title of the module plus Module. This is to differentiate the class from anything else that may be inside the framework.

This class name is used when saving data, so if this class name is changed at any point, user data will not be read from storage and they will need to redefine all the settings and parameters they may have customised. It is vital this does not get changed, so choose a name that best fits the module's purpose.

Section 1 - Metadata

All modules are provided with a set of properties to override which define the metadata of the module.

    public virtual string Title => string.Empty;
    public virtual string Description => string.Empty;
    public virtual string Author => string.Empty;
    public virtual string Prefab => string.Empty;
    public virtual IEnumerable<string> Tags => Array.Empty<string>();
    public virtual double DeltaUpdate => double.MaxValue;
    public virtual Colour4 Colour => Colour4.Black;
    public virtual ModuleType ModuleType => ModuleType.General;

Above is the metadata defined for a default module. Note that these are default settings and are already defined for you if you don't need to override one or more of the properties.

Title

The title of the module is what's shown to the user. This can be different from the class name explained before, but in practise it should match as to not cause confusion.

Description

A customisable description of your module. Keep this short, it gets displayed on the module card in the GUI.

Author

Your name. Can be your real name, online pseudonym, though we suggest it be your GitHub username.

Prefab

If this module is associated with a prefab, and if so the name of the unitypackage.

Tags

The tags your module is associated with. This is used for searching purposes alongside your module's name

DeltaUpdate

This changes the duration between OnUpdate calls. When set to positive infinity, update calls are disabled, so if you're using OnUpdate make sure to set this to a sensible number that corresponds to what your module is doing. This is done in milliseconds, so an update rate of once per second would be 1000

Colour

This is purely for GUI purposes, and is the colour that gets displayed. This can be any colour you like, though having it correspond to what your module is doing is preferred (for example, the Spotify module is Spotify green). Darker colours work better, though any colour can be used and darkened. The Colour4 class or VRCOSCColour should be used here.

ModuleType

For grouping your module. This is more for organisation than anything else, but allows the user to filter modules a lot easier. If you think your module requires a new module type, feel free to make one.

Section 2 - Attributes

Attributes of a module are the settings and outgoing parameters. They allow the user to customise any settings you require for your module, as well as the destination of outgoing OSC parameters.

Attributes get stored locally on a user's machine under their keys. There are a few notes on how the storage works:

  • If a key is changed or removed, the user's value for the previous key is deleted
  • If a new key is added, the default setting will be used
  • If a user hasn't changed the default value of an attribute, and you change the default value, it will reflect on their end

To create attributes, you must call CreateSetting() and CreateOutputParameter() inside the overridden CreateAttributes() method. Below is an example taken from the HypeRate module.

    public override void CreateAttributes()
    {
        CreateSetting(HypeRateSetting.Id, "HypeRate ID", "Your HypeRate ID given on your device", string.Empty);

        CreateOutputParameter(HypeRateOutputParameter.HeartrateEnabled, "Heartrate Enabled", "Whether this module is attempting to emit values", "/avatar/parameters/HeartrateEnabled");
        CreateOutputParameter(HypeRateOutputParameter.HeartrateNormalised, "Heartrate Normalised", "The heartrate value normalised to 60bpm", "/avatar/parameters/HeartrateNormalised");
        CreateOutputParameter(HypeRateOutputParameter.HeartrateUnits, "Heartrate Units", "The units digit 0-9 mapped to a float", "/avatar/parameters/HeartrateUnits");
        CreateOutputParameter(HypeRateOutputParameter.HeartrateTens, "Heartrate Tens", "The tens digit 0-9 mapped to a float", "/avatar/parameters/HeartrateTens");
        CreateOutputParameter(HypeRateOutputParameter.HeartrateHundreds, "Heartrate Hundreds", "The hundreds digit 0-9 mapped to a float", "/avatar/parameters/HeartrateHundreds");
    }

Settings

A setting requires an Enum as a key. This can be any Enum, however it's recommended you create an Enum along the lines of [ModuleName]Setting to keep things organised.

Following that is the display name and description of the setting. These are cosmetic and only serve the purpose of describing what the setting does to the user. You have more space with the description that in the module metadata so this can be more in-depth.

Finally you define the default value of the setting. This must be set, even if it's an empty string, or a false bool, as this tells the module what type the setting is.

Settings Retrieval

You can access settings by calling the GetSetting<T>() method where T is the setting's type. This can be done in any of the events (defined below), and it will return the latest value of what the user entered. Settings are not editable at runtime and so you can guarantee that a setting will stay consistent during the duration of the module's uptime.

Setting retrieval can also be useful for metadata.

For example, DeltaUpdate can be changed using settings as that property is read on module start. Below is an example taken from the Clock module.

    protected override double DeltaUpdate => GetSetting<bool>(ClockSetting.SmoothSecond) ? 50d : 1000d;

You could also set delta update directly from an integer setting if you wanted the user to have complete customisation.

    protected override double DeltaUpdate => GetSetting<int>(ExampleSetting.DeltaUpdate);

Output Parameters

A parameter also requires an Enum as a key. This is recommended as [ModuleName]OutputParameter. The definition is the exact same as the setting, with a display name and description, however the default value is always a string, as this is the default address the value of the parameter gets sent to. This means you have to define the /avatar/parameters prefix if you are sending the value to avatar parameters.

Section 3 - Events

All modules come with default module events. These are separate from events that occur due to OSC, which will be covered later. Each event can be accessed by overriding the base method.

Events can be accessed by overriding the OnStart, OnUpdate, OnStop, and OnAvatarChange methods.

Start

The Start event occurs whenever VRChat is started or a user manually started the modules. This is only called once on start so is the perfect place to do initial setup of anything your module may need to function, but which is dynamic at runtime.

Update

The Update event occurs once right after the module is started, and then x amount of time later on repeat. The time is defined by the DeltaUpdate property in the metadata.

Stop

The Stop event occurs when all modules are stopped due to VRChat closing or a user manually stopping the modules. This is only called once on stop so can be used to clear any OSC parameters on a user's avatar and reset local module settings (if they aren't already set to a default in Start)

Avatar Change

The avatar change methods occurs whenever a user enters a new avatar. This also occurs when a user loads into a world after starting the game. Bear in mind that if the user hasn't started VRCOSC before VRChat then you will not receive this event for the first avatar they load into.

Section 4 - Input Parameters

Input parameters are the parameters that VRChat is sending out to OSC applications. Since this just reflects all the avatar parameters, the OSC parameters that are sent out from VRC (provided they are present on the avatar the user is using) will also be received. This enables modules to effectively "talk" to each other if needed

To define input modules, you have to register them in the CreateAttributes() method. Below is an example taken from the Spotify module:

    RegisterInputParameter<bool>(SpotifyInputParameter.SpotifyPlayPause, ActionMenu.Button);
    RegisterInputParameter<bool>(SpotifyInputParameter.SpotifyNext, ActionMenu.Button);
    RegisterInputParameter<bool>(SpotifyInputParameter.SpotifyPrevious, ActionMenu.Button);
    RegisterInputParameter<bool>(SpotifyInputParameter.SpotifyVolumeUp, ActionMenu.Button);
    RegisterInputParameter<bool>(SpotifyInputParameter.SpotifyVolumeDown, ActionMenu.Button);

Registering input parameters takes in the type you expect the parameter to be, the lookup for the parameter, and if the parameter is attached to a control on the action menu. In the example above, because all the Spotify controls are buttons, I've defined them as such, which tells VRCOSC to not forward the input parameter when the parameter's value comes in as false, as we only care about when the button is pressed, not released.

To handle input parameters, 3 methods can be overridden:

    protected virtual void OnBoolParameterReceived(Enum lookup, bool value) { }
    protected virtual void OnIntParameterReceived(Enum lookup, int value) { }
    protected virtual void OnFloatParameterReceived(Enum lookup, float value) { }

Section 5 - Integration Modules

Integration modules allow for a module to execute keyboard shortcuts on either the OS or a specific window. This can be useful for automated control (I.E, the Spotify and Discord integrations).

To create an integration module, simply make your module but have it extend the IntegrationModule class instead of the Module class.

The attributes of an integration module are as follows:

    protected virtual string TargetProcess => string.Empty;
    protected virtual string ReturnProcess => "vrchat";
    protected virtual string TargetExe => [email protected]"{TargetProcess}.exe";

TargetProcess

Used to define the target process that you want the keyboard shortcut to be executed on. For example, for Spotify it's overridden as "spotify".

ReturnProcess

The process with which to switch back to after the keyboard shortcut has been executed. This is defaulted to VRChat and shouldn't need to be changed.

TargetExe

The full file path of the exe of your target process. This is useful for being able to start the target process.

Section 5.1 - Registering Shortcuts

To register a keyboard shortcut, you need to call RegisterKeyCombination() from inside the CreateAttributes() method. Below is an example taken from the Spotify module:

    RegisterKeyCombination(SpotifyInputParameter.SpotifyPlayPause, WindowsVKey.VK_SPACE);
    RegisterKeyCombination(SpotifyInputParameter.SpotifyNext, WindowsVKey.VK_LCONTROL, WindowsVKey.VK_RIGHT);
    RegisterKeyCombination(SpotifyInputParameter.SpotifyPrevious, WindowsVKey.VK_LCONTROL, WindowsVKey.VK_LEFT);
    RegisterKeyCombination(SpotifyInputParameter.SpotifyVolumeUp, WindowsVKey.VK_LCONTROL, WindowsVKey.VK_UP);
    RegisterKeyCombination(SpotifyInputParameter.SpotifyVolumeDown, WindowsVKey.VK_LCONTROL, WindowsVKey.VK_DOWN);

This method takes in a lookup, and then a list of keys to press during the shortcut's execution.

Section 5.2 - Executing Shortcuts

To execute a shortcut you need to call the ExecuteKeyCombination() method from anywhere within your module. It takes in a lookup previously defined.

You might've noticed that the key combinations were registered with the input parameters inside the Spotify module. This is because I want the input parameters to directly execute the shortcut and do nothing else. This allows for behaviour that can be written as such:

    protected override void OnBoolParameterReceived(Enum lookup, bool value)
    {
        ExecuteKeyCombination(lookup);
    }
⚠️ **GitHub.com Fallback** ⚠️