Preserve saved task settings and variable values when you rename custom C# types or move them between namespaces. Use type and field migration attributes for compatible renames; use Replace when you deliberately want a different task and will configure its new settings.

Prepare a migration

Save a checkpoint of the scripts and every affected scene, prefab, subtree, and variable container before changing types. Include variables stored at GameObject, Scene, and Project scope as well as graph variables.

Add migration attributes as part of the rename, before reopening and saving affected graphs. After compilation, check a graph with nondefault field values, shared-variable references, and representative child connections. Repeat the check on prefab variants and subtrees that use the type.

Moving a script between folders leaves its serialized type identity unchanged when the declared namespace and class name stay the same. If you move it into another assembly, update the assembly-definition references and keep runtime types available to player builds.

Rename a task class or namespace

Place Unity’s MovedFrom attribute on the new class and identify its former namespace and class name. Behavior Designer Pro’s type resolver checks this metadata when the stored type cannot be found.

This example migrates Game.LegacyAI.WriteMessage from Assembly-CSharp to Game.AI.LogMessage. Its string field was previously named m_Message and already used SharedVariable<string>:

using Opsive.BehaviorDesigner.Runtime.Tasks;
using Opsive.GraphDesigner.Runtime.Variables;
using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.Scripting.APIUpdating;

namespace Game.AI
{
    [MovedFrom(false, sourceNamespace: "Game.LegacyAI", sourceAssembly: "Assembly-CSharp", sourceClassName: "WriteMessage")]
    public class LogMessage : Opsive.BehaviorDesigner.Runtime.Tasks.Actions.Action
    {
        [SerializeField, FormerlySerializedAs("m_Message")]
        private SharedVariable<string> m_Text = "Ready";

        public override TaskStatus OnUpdate()
        {
            Debug.Log(m_Text.Value);
            return TaskStatus.Success;
        }
    }
}

Use the actual former names from your project. Keep the old class removed or renamed: an existing type under the old name is resolved before the migration map. Keep the new type compatible with the old node role and fields. Moving a task into an unrelated base class does not convert its saved data or connections.

The resolver’s migration map uses the former namespace and class name. It does not use sourceAssembly to distinguish two types with the same former full name. For assembly-only migrations involving assembly-qualified generic arguments, supply the old namespace and class explicitly as well as the old assembly. Verify these values against the assembly-definition name, not its folder name.

For a class moved from the global namespace into a namespace, use the class alias Opsive.Shared.Utility.FormerlySerializedAs("OldClassName") on the new class. The shared resolver treats an empty MovedFrom namespace as the current namespace. This Opsive class attribute is separate from Unity’s field-renaming attribute and accepts one old full type name.

Rename a SharedVariable value type

Pro uses SharedVariable<T>. When a custom class or enum used as T is renamed, put MovedFrom on that class or enum. For standalone variable records, the loader resolves the generic definition and its argument types independently.

For example, a value formerly declared as Game.LegacyAI.EnemyMemory can become:

using System;
using UnityEngine.Scripting.APIUpdating;

namespace Game.AI
{
    [Serializable]
    [MovedFrom(false, sourceNamespace: "Game.LegacyAI", sourceAssembly: "Assembly-CSharp", sourceClassName: "EnemyMemory")]
    public class TargetMemory
    {
        public float Confidence;
    }
}

Update code to use SharedVariable<Game.AI.TargetMemory> while retaining compatible payload fields. Test the saved value itself, not only whether its variable name reappears. A new unrelated value type requires an explicit data conversion; MovedFrom is not a conversion function.

Changing a task field’s declaration from SharedVariable<EnemyMemory> to SharedVariable<TargetMemory> also changes that field’s serialized identity. The alias on the value type does not map the enclosing generic field, so its local value or shared-variable selection can be skipped during loading. Record and explicitly migrate or reassign those task fields before saving. Recovery of the standalone variable does not prove that its task bindings survived.

Convert a task value into a SharedVariable

Pro supports an explicit migration from a task’s old non-shared field to a SharedVariable<T> field. Mark the new field with Opsive.Shared.Utility.FormerlySerializedType, specifying the type actually stored by the old field:

[UnityEngine.SerializeField]
[Opsive.Shared.Utility.FormerlySerializedType(typeof(float))]
private Opsive.GraphDesigner.Runtime.Variables.SharedVariable<float> m_Duration = 1;

When the old declaration was float m_Duration, the loader reads that saved float and wraps it in a new variable with Self scope. Keep the field name, or also add Unity’s FormerlySerializedAs for a simultaneous field rename. The built-in Wait uses this pattern for float, bool, and RangeFloat values; Repeater also converts its old ushort repeat count into SharedVariable<int>.

This supports values that can be assigned or converted to the new variable’s value type. It does not create a shared-variable binding, recover a missing type, or migrate an existing SharedVariable<OldType> into SharedVariable<NewType>. Verify a saved nondefault value before saving the migrated graph.

Rename serialized fields or variable labels

Use UnityEngine.Serialization.FormerlySerializedAs on a renamed field, as shown above. Keep its type compatible. The serializer also checks former type names when locating a field’s saved data, but this does not make arbitrary changes to lists, payload structure, or value types safe.

To change a variable’s displayed name, use Rename on its variable row. This is separate from renaming the C# value type. Graph-variable renames update matching references in that graph’s logic and event nodes. Check other graphs, scoped-variable consumers, property bindings, and code that uses the old string name separately.

Use Change Type in the variable row’s options menu when you deliberately want a different value type. This creates a new variable with the new type’s defaults and retains its Name, Scope, identity, and Internal setting. It does not convert the old value or preserve property-binding settings. Reconfigure the value and check every task that used the previous type. Change Type is also the available menu action for an unknown variable; it creates replacement data rather than recovering its missing value.

Replace a graph node or stacked task

Outside Play Mode, right-click a node and choose Replace, or use Replace in its overflow menu. Choose the new task from the filter window. For a task inside Stacked Action or Stacked Conditional, right-click that task’s row and choose Replace instead of replacing the whole container.

The Tasks chooser opened by Replace lists Actions, Conditionals, Composites, and Decorators.

Replacement constructs a fresh task and attempts to preserve compatible serialized fields. It is not the type-migration operation used when the loader resolves a renamed type: fields unsupported by the new task cannot transfer, and incompatible shared-variable selections need to be configured again. The graph keeps the node’s index, parent/sibling relationships, and editor properties. If the new parent accepts fewer children, excess child branches are disconnected. Review the Inspector and child connections whenever you change task type or node kind.

Replacing a stacked container with another stacked container copies the contained tasks, but the new container’s own settings start from its defaults. Replacing an individual row creates a new task at that row. Event replacement retains the outgoing connection and enabled state; replacing it with an event type that allows only one instance removes an existing event of that same type.

Recover an unknown type before saving

Unknown-task errors identify the original type and suggest Replace or MovedFrom. An unresolved variable is shown as Unknown Variable with its original type. Its name can survive, but its value is unavailable through the placeholder.

Restore the missing script or add the correct migration attribute, then reload the original saved graph or variable container. Do this before saving unrelated edits. Unknown placeholders retain identification information, not a complete editable copy of the missing task or variable’s custom fields. Serializing a placeholder can replace the original field data with the placeholder’s data.

If an unknown task has already been saved, reopening the graph after its type becomes available can replace the placeholder with a fresh instance. That does not restore the lost settings. Restore the last good serialized asset as well as the type when you need those values back. Load-time repairs of prefab graphs remain in memory until Control-S; other graphs can save their load-time repair automatically, so work from a checkpoint.

See New Tasks, Variables, and Variable Scope.