Best Practices
Split your plugin into multiple phases when it contains multiple features
When your plugin contains multiple features that can be logically separated, consider splitting them into different phases.
A single component or plugin can generate, transform, and optimize at once. If your plugin performs multiple operations, you should consider splitting the generating, transforming, and optimizing phases rather than running everything in a single phase.
Clean up your components when no longer needed
After your process is complete, you should consider calling Object.DestroyImmediate on your components.
If you destroy your components, later plugins (especially optimization plugins) won't need to consider your plugin's components.
In some rare cases, you may want to keep your components data on the state, but this is not common and should be done with caution.
Don't assume other plugins won't modify objects
Other plugins may run after or between your Sequences.
This is true even when you use Sequence.BeforePlugin or Sequence.AfterPlugin constraints, because multiple plugins may request to run just before or after the same plugin.
Therefore, you shouldn't assume your GameObjects, components, or other assets will remain unchanged after your process.
For example, you should not cache assets you retrieved from the avatar between Sequences, because other plugins may update or replace them.
A common mistake is reading settings and assets from the avatar and generating new objects in the generating phase, then assigning them in the transforming phase. You should instead assign objects in the same Sequences during the generating phase, or read and generate in the same Sequences during the transforming phase, depending on your use case.
Avoid cloning objects when possible
BuildContext.IsTemporaryAsset can be used to check if an object is already a temporary clone.
If your plugin is intended to apply avatar globally, you should consider avoiding cloning objects that are already temporary clones to reduce memory usage and improve performance.
Remember that you have to replace references to the original objects with the temporary clones when you do cloning to ensure the same behavior regardless of whether the object was cloned or not.
Please note that temporary clones are not recursively cloned. You should still clone child objects or referenced assets as needed.
If your plugin is indented to modify some limited portion of the avatar, you may still need to clone objects even if they are temporary clones. Any assets including temporary ones can be shared between multiple places on the avatar, like one material is shared between multiple renderers, so modifying them directly may modify unintended parts of the avatar.
Avoid passing data between phases or sequences with lambda captures
When writing plugins, you can create local variables on the Configure method and capture them in inline passes created with run with lambda expression.
However, you should avoid this pattern to pass data between phases or sequences.
One plugin instance may be used for multiple avatar builds, and single local variables may be shared between different builds.
Instead, you should use the build context's GetState method to store and retrieve data between phases or sequences.
You may also use Extension Contexts for some kinds of data.
Examples
Bad Example
public class MyPlugin : Plugin<MyPlugin>
{
protected override void Configure()
{
GameObject generatedObject = null;
InPhase(BuildPhase.Generating)
.Run("Pass", ctx =>
{
generatedObject = new GameObject("Generated");
});
}
}
Good Example
public class MyPlugin : Plugin<MyPlugin>
{
public class MyState
{
public GameObject GeneratedObject;
}
protected override void Configure()
{
InPhase(BuildPhase.Generating)
.Run("Pass", ctx =>
{
ctx.GetState<MyState>().GeneratedObject = new GameObject("Generated");
});
}
}
Create your own state type rather than using existing state types
The states are distinguished by their types.
Therefore, you should create your own state type rather than using existing types like Dictionary<string, object> or List<GameObject>.
This helps avoid conflicts with other plugins that may use the same state type for different purposes.
Register cloned objects with ObjectRegistry
If your plugin clones object to modify them, you should register relationships between original objects and cloned objects
with the RegisterReplacedObject.
This will help other plugins to find the correct objects to work with, and better error reports.
Provide objects relates to the error
When your plugin encounters an error related to a specific object, you should provide related object with additional parameter to the ReportError.
This will help users to identify and fix the issue more easily.