Refactoring lets you rename custom Actions, Conditions, States, Event States, and Shared Variable types while retaining their graph settings. Use migration attributes when the implementation keeps the same role. Use the editor’s replacement controls when you intentionally want a different behavior.

Preserve settings through a rename

  1. Keep a recoverable copy of the affected scenes, prefabs, Subgraphs, variable containers, and any older save files that must remain compatible.
  2. Record the type’s old namespace, class name, and assembly name before moving or renaming it.
  3. Add MovedFrom to the new type. Add Unity’s FormerlySerializedAs to any serialized field that is also being renamed. Keep its data type and nesting unchanged during this step.
  4. Compile the code, then reopen the graph from its original serialized data. The node or row should resolve to the new type with its previous settings, rather than appearing as unknown or being replaced manually.
  5. Check the affected fields and Shared Variable assignments before saving. For a renamed Action with a Message setting, verify that its configured message remains and appears when that Action runs. Also check the transition Conditions and event connections that enter or leave the affected State.

For prefabs or Subgraphs, check both the source graph and the instances with overrides. Renaming a C# class does not require deleting and recreating the graph asset.

Rename a type with MovedFrom

Apply UnityEngine.Scripting.APIUpdating.MovedFrom to the new declaration. Supply both the old namespace and old class name explicitly, including whichever did not change. This avoids depending on different handling of omitted arguments in the editor and runtime type lookup.

This example renames Game.AI.LogAgent to Game.Characters.LogAgentStatus and changes the serialized field name from m_Text to m_Message:

using Opsive.GraphDesigner.Runtime.Variables;
using Opsive.StateDesigner.Runtime.States;
using UnityEngine;
using UnityEngine.Scripting.APIUpdating;

namespace Game.Characters
{
    [MovedFrom(false,
        sourceNamespace: "Game.AI",
        sourceAssembly: "Assembly-CSharp",
        sourceClassName: "LogAgent")]
    public class LogAgentStatus : Opsive.StateDesigner.Runtime.Actions.Action
    {
        [UnityEngine.Serialization.FormerlySerializedAs("m_Text")]
        [SerializeField] private SharedVariable<string> m_Message;

        public override StateStatus OnUpdate()
        {
            Debug.Log(m_Message.Value);
            return StateStatus.Finished;
        }
    }
}

Use your actual old assembly name. false leaves API updating out of this example; update C# callers yourself. State Designer’s shared type lookup matches the old namespace and class name. It does not distinguish old types by assembly alone, so keep old full-name aliases unambiguous.

Apply the same pattern to a custom Condition, State, Event State, or Shared Variable class while retaining its required base type and interfaces. Keep the migration attribute while any supported graph or save can contain the old type name. The old name is a fallback: if the old class still exists and resolves normally, it takes precedence over the mapping.

For a custom value type used inside SharedVariable<T>, put MovedFrom on the renamed value type. For example, a standalone variable record saved as SharedVariable<Game.AI.TargetData> can resolve as SharedVariable<Game.Characters.TargetInfo> through the alias on TargetInfo. Preserve the value’s serialized fields; changing its shape still requires a separate conversion.

An Action or Condition field declared with that wrapper needs additional attention. Changing its declaration from SharedVariable<TargetData> to SharedVariable<TargetInfo> also changes the field’s serialized identity. The alias on TargetInfo does not map that enclosing generic field, so its local value or Shared Variable binding can be skipped during loading. Record and explicitly migrate or reassign those node fields before saving, even if the standalone variable has recovered successfully.

Rename serialized fields

Use UnityEngine.Serialization.FormerlySerializedAs for a field rename. The shared field reader tries the current field name and then the declared old name. Keeping the field type unchanged allows values and Shared Variable assignments to use the same data layout.

This attribute does not migrate a property’s binding string, convert a value to a new type, or move it into another nested object. The reader retrieves one previous field name, so do not assume that stacking several historical field attributes migrates every older layout. Use an explicit conversion for larger changes and test each supported older save. See Unity’s field-renaming reference.

Existing Opsive type aliases

Opsive.Shared.Utility.FormerlySerializedAs is a separate class attribute that accepts an old full type name:

[Opsive.Shared.Utility.FormerlySerializedAs("Game.AI.LogAgent")]

It provides an alternative alias for the shared type resolver, including a class moved from the global namespace. It is not Unity’s field attribute and accepts one previous class name. Fully qualify the two attributes when both namespaces are in use.

Recover an unknown type

An unknown entry means its serialized type could not be reconstructed. Check compilation errors, missing scripts or packages, assembly references and defines, and the old type name before replacing it.

The Unknown State inspector identifies the missing Game.LegacyAI.PatrolState type.

Restore the original type or add its correct migration mapping, then load the original graph data again. If the graph or variable container was saved while the type was unknown, restore its earlier serialized data from version control or your recoverable copy first.

The unknown placeholders retain the old type name and limited common metadata. They do not provide a full backup of the missing implementation’s serialized fields. When a graph or variable container is reserialized, its current placeholder or null entries can replace that original payload. The editor can also resolve an already-created unknown node by constructing a new instance, which is different from deserializing the original field values. Check the retained values instead of treating the disappearance of an unknown label as proof of recovery.

Replace an implementation deliberately

Leave Play Mode and select one entry before using Replace. Replacement creates a new object; it does not apply a rename migration to the old serialized fields.

Entry Editor control What to review afterward
State or Event State on the graph Right-click the node and choose Replace. The replacement uses new type defaults. State replacement retains the current index, enabled flag, and currently available connections; compatible Action State containers can retain their Actions. Recheck custom fields and event connections, especially when replacing an unknown node.
Action inside an Action State Select the Action State, right-click one row in Actions, and choose Replace. The row receives a newly constructed Action and its custom row name is cleared. Configure its fields and variable assignments again.
Condition on a transition Select the transition, right-click one row in Conditions, and choose Replace. The row receives a new Condition and its custom row name is cleared. Recheck its fields and the transition’s condition mode.
Unknown Shared Variable Open the variable row’s options menu and choose Change Type. The new variable keeps its Name, Scope, ID, and Internal setting. Its value and type-specific settings start from the new type’s defaults. Recheck all users of that variable.

Rename on an Action or Condition row changes its editor display name. It does not rename the C# class or provide a serialization alias. Likewise, renaming a variable in the editor is separate from renaming the C# class that stores its value.

How unknown entries behave at runtime

Repair unknown entries before using the graph. Their fallback behavior does not execute the missing implementation:

  • An unknown State reports an initialization error instead of providing the original State’s behavior.
  • An unknown Event State has no usable connected State index, so it does not replace the missing event trigger.
  • An unknown Action inherits the base OnUpdate, which returns StateStatus.Finished without running the original Action’s work.
  • An unknown Condition’s IsValid returns false. This is not a guarantee that the whole transition or graph is disabled; other Conditions and the transition mode still matter.
  • An unknown Shared Variable exposes no stored value through GetValue; it returns null, and SetValue does nothing.

Renaming a type also does not convert every runtime save format. If a custom Action, State, or Condition implements its own save/load payload, migrate that payload and its loading code separately. Check Save and Load when old runtime saves must remain supported.

Troubleshooting

Symptom Check Fix
A renamed type remains unknown Compare the stored full type name with sourceNamespace and sourceClassName; confirm that the new type compiles in the required assembly. Correct the mapping and load the original serialized graph again.
The new type appears but its fields use defaults Check whether Replace, automatic unknown-node replacement, or saving an unknown graph created a fresh object. Restore the pre-refactor serialized data and deserialize it after adding the migration attributes.
A renamed field loses its value or Shared Variable assignment Check the old field name and whether its type or nesting also changed. Add Unity’s field rename attribute for a simple rename, or write the required data conversion.
An unknown variable loses its value after Change Type The operation creates a new variable and retains only common identity settings. Reconfigure its value and users, or recover the original variable type and original serialized data.
A Property Binding stops working after a method or property refactor Inspect the stored binding target and property name. Rebind the property; type and field migration attributes do not rewrite property-name strings.