568 lines
29 KiB
C#
568 lines
29 KiB
C#
#if ENABLE_INPUT_SYSTEM
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text;
|
|
using Invector.vCharacterController;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
using UnityEngine.InputSystem;
|
|
using UnityEngine.InputSystem.UI;
|
|
using UnityEngine.EventSystems;
|
|
|
|
namespace Invector.vCharacterController.InputSystemEditor
|
|
{
|
|
internal static class vInputSystemMigrationUtility
|
|
{
|
|
private const string InputActionsPath = "Assets/Invector-3rdPersonController/Basic Locomotion/InputSystem/InvectorPlayerInputs.inputactions";
|
|
private const string BasicControllerPath = "Assets/Invector-3rdPersonController/Basic Locomotion/Prefabs/vBasicController.prefab";
|
|
private const string MeleeInventoryControllerPath = "Assets/Invector-3rdPersonController/Melee Combat/Prefabs/Player/vMeleeController_Inventory.prefab";
|
|
private const string MeleeNoInventoryControllerPath = "Assets/Invector-3rdPersonController/Melee Combat/Prefabs/Player/vMeleeController_NoInventory.prefab";
|
|
private const string ShooterMeleeInventoryControllerPath = "Assets/Invector-3rdPersonController/Shooter/Prefabs/Player/vShooterMelee_Inventory.prefab";
|
|
private const string ShooterMeleeNoInventoryControllerPath = "Assets/Invector-3rdPersonController/Shooter/Prefabs/Player/vShooterMelee_NoInventory.prefab";
|
|
private const string ShooterOnlyInventoryControllerPath = "Assets/Invector-3rdPersonController/Shooter/Prefabs/Player/vShooterOnly_Inventory.prefab";
|
|
private const string GameStyleSelectorMeleePath = "Assets/Invector-3rdPersonController/Melee Combat/Prefabs/UI/GameStyleSelector_Melee.prefab";
|
|
private const string GameStyleSelectorShooterPath = "Assets/Invector-3rdPersonController/Shooter/Prefabs/UI/GameStyleSelector_Shooter.prefab";
|
|
private const string InventoryMeleeOnlyPath = "Assets/Invector-3rdPersonController/ItemManager/Prefabs/Inventory_MeleeOnly.prefab";
|
|
private const string InventoryShooterMeleePath = "Assets/Invector-3rdPersonController/ItemManager/Prefabs/Inventory_ShooterMelee.prefab";
|
|
private const string InventoryShooterOnlyPath = "Assets/Invector-3rdPersonController/ItemManager/Prefabs/Inventory_ShooterOnly.prefab";
|
|
private static readonly string[] AddonControllerPrefabPaths =
|
|
{
|
|
"Assets/Invector-3rdPersonController/Add-ons/Controller_2.5D Platform/Basic/Prefabs/v2.5DController.prefab",
|
|
"Assets/Invector-3rdPersonController/Add-ons/Controller_2.5D Platform/Basic/Prefabs/PlatformLevelExample.prefab",
|
|
"Assets/Invector-3rdPersonController/Add-ons/Controller_2.5D Platform/Melee (Require MeleeCombat Template)/Prefabs/v2.5D_MeleeController.prefab",
|
|
"Assets/Invector-3rdPersonController/Add-ons/Controller_2.5D Platform/Shooter (Require Shooter Template)/Prefabs/Shooter2.5D.prefab",
|
|
"Assets/Invector-3rdPersonController/Add-ons/Controller_2.5D Platform/Shooter (Require Shooter Template)/Prefabs/MobileShooter2.5D.prefab",
|
|
"Assets/Invector-3rdPersonController/Add-ons/Controller_Point&Click/Basic/Prefabs/Basic_Point&ClickController.prefab",
|
|
"Assets/Invector-3rdPersonController/Add-ons/Controller_Point&Click/Melee (Require MeleeCombat Template)/Prefabs/Melee_Point&ClickController.prefab",
|
|
"Assets/Invector-3rdPersonController/Add-ons/Controller_Point&Click/Melee (Require MeleeCombat Template)/Prefabs/Melee_Point&ClickAgent.prefab",
|
|
"Assets/Invector-3rdPersonController/Add-ons/Controller_TopDown/Basic/Prefabs/vTopDownController.prefab",
|
|
"Assets/Invector-3rdPersonController/Add-ons/Controller_TopDown/Melee (Required Melee Template)/Prefabs/vTopDownMeleeController.prefab",
|
|
"Assets/Invector-3rdPersonController/Add-ons/Controller_TopDown/Shooter (Require Shooter Template)/Prefabs/ShooterMeleeTopDown.prefab"
|
|
};
|
|
|
|
private static readonly string[] MobileControllerPrefabPaths =
|
|
{
|
|
"Assets/Invector-3rdPersonController/Add-ons/Controller_Mobile/Basic/Prefabs/vBasicMobileController.prefab",
|
|
"Assets/Invector-3rdPersonController/Add-ons/Controller_Mobile/Melee (Require MeleeCombat Template)/Prefabs/MeleeControllerMobile_Inventory.prefab",
|
|
"Assets/Invector-3rdPersonController/Add-ons/Controller_Mobile/Melee (Require MeleeCombat Template)/Prefabs/MeleeControllerMobile_NoInventory.prefab",
|
|
"Assets/Invector-3rdPersonController/Add-ons/Controller_Mobile/Shooter (Require Shooter Template)/Prefabs/ShooterControllerMobile_Inventory.prefab",
|
|
"Assets/Invector-3rdPersonController/Add-ons/Controller_Mobile/Shooter (Require Shooter Template)/Prefabs/ShooterControllerMobile_NoInventory.prefab"
|
|
};
|
|
|
|
private readonly struct Binding
|
|
{
|
|
public readonly string action;
|
|
public readonly bool isAxis;
|
|
public readonly GenericInputValueComponent component;
|
|
public readonly bool invert;
|
|
|
|
public Binding(string action, bool isAxis = false, GenericInputValueComponent component = GenericInputValueComponent.Scalar, bool invert = false)
|
|
{
|
|
this.action = action;
|
|
this.isAxis = isAxis;
|
|
this.component = component;
|
|
this.invert = invert;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bindings keyed by (component type, field name), applied before <see cref="bindings"/> so
|
|
/// components that reuse a generic field name (e.g. "input") for different purposes don't
|
|
/// collide with each other or with unrelated fields sharing that name.
|
|
/// </summary>
|
|
private static readonly Dictionary<(string type, string field), Binding> typedBindings = new Dictionary<(string, string), Binding>
|
|
{
|
|
// The inventory grid is navigated with the D-Pad through the Navigate action, so both
|
|
// axes read the same Vector2 and pick the component they care about.
|
|
{ ("vInventory", "horizontal"), new Binding("Navigate", true, GenericInputValueComponent.X) },
|
|
{ ("vInventory", "vertical"), new Binding("Navigate", true, GenericInputValueComponent.Y) }
|
|
};
|
|
|
|
/// <summary>
|
|
/// Bindings for GenericInput fields nested inside serialized lists, keyed by
|
|
/// (component type, list field, nested field). Applied per element of the list.
|
|
/// </summary>
|
|
private static readonly Dictionary<(string type, string list, string field), Binding> nestedListBindings = new Dictionary<(string, string, string), Binding>
|
|
{
|
|
{ ("vInventory", "changeEquipmentControllers", "useItemInput"), new Binding("Use Item") }
|
|
};
|
|
|
|
private static readonly Dictionary<string, Binding> bindings = new Dictionary<string, Binding>
|
|
{
|
|
{ "horizontalInput", new Binding("Move", true, GenericInputValueComponent.X) },
|
|
{ "verticalInput", new Binding("Move", true, GenericInputValueComponent.Y) },
|
|
{ "rotateCameraXInput", new Binding("Look", true, GenericInputValueComponent.X) },
|
|
{ "rotateCameraYInput", new Binding("Look", true, GenericInputValueComponent.Y) },
|
|
{ "cameraZoomInput", new Binding("Zoom", true) },
|
|
{ "jumpInput", new Binding("Jump") },
|
|
{ "sprintInput", new Binding("Sprint") },
|
|
{ "crouchInput", new Binding("Crouch") },
|
|
{ "strafeInput", new Binding("Strafe") },
|
|
{ "rollInput", new Binding("Roll") },
|
|
{ "actionInput", new Binding("Interact") },
|
|
{ "enterInput", new Binding("Interact") },
|
|
{ "exitInput", new Binding("Jump") },
|
|
{ "fastClimbInput", new Binding("Sprint") },
|
|
{ "slideDownInput", new Binding("Roll") },
|
|
{ "weakAttackInput", new Binding("Weak Attack") },
|
|
{ "strongAttackInput", new Binding("Strong Attack") },
|
|
{ "blockInput", new Binding("Block") },
|
|
{ "lockOnInput", new Binding("Lock On") },
|
|
{ "previousTargetInput", new Binding("Previous Target", true) },
|
|
{ "nexTargetInput", new Binding("Next Target", true) },
|
|
{ "aimInput", new Binding("Aim") },
|
|
{ "shotInput", new Binding("Shoot") },
|
|
{ "reloadInput", new Binding("Reload") },
|
|
{ "scopeViewInput", new Binding("Scope") },
|
|
{ "switchCameraSideInput", new Binding("Switch Camera Side") },
|
|
{ "aimThrowInput", new Binding("Aim Throw") },
|
|
{ "throwInput", new Binding("Throw") },
|
|
{ "openInventory", new Binding("Toggle Inventory") },
|
|
{ "submit", new Binding("Submit") },
|
|
{ "cancel", new Binding("Cancel") },
|
|
{ "useItemInput", new Binding("Use Item") },
|
|
{ "removeEquipment", new Binding("Remove Equipment") },
|
|
{ "hideAndDrawWeaponsInput", new Binding("Draw Weapon") },
|
|
{ "pointClickInput", new Binding("Point Click") },
|
|
{ "rotateCameraInput", new Binding("Rotate Camera") },
|
|
{ "selectRightInput", new Binding("Toolbar Next") },
|
|
{ "selectLeftInput", new Binding("Toolbar Previous") }
|
|
};
|
|
|
|
/// <summary>
|
|
/// vSimpleInput serves two opposite roles depending on where it is placed: a "press to
|
|
/// continue" prompt and a "press to go back" prompt. The role is only distinguishable from
|
|
/// the legacy key the prefab already authored, so it is resolved per component instead of
|
|
/// being fixed by type.
|
|
/// </summary>
|
|
private static readonly Dictionary<string, string> simpleInputActionByKey = new Dictionary<string, string>
|
|
{
|
|
{ "Escape", "Cancel" },
|
|
{ "Backspace", "Cancel" },
|
|
{ "Return", "Continue" },
|
|
{ "KeypadEnter", "Continue" }
|
|
};
|
|
|
|
private const string SimpleInputFallbackAction = "Continue";
|
|
|
|
/// <summary>
|
|
/// Slot hotkeys are bound by list position rather than by field name: element i of
|
|
/// vControlAreaByInput.slotsSelectors maps to the "Equip Slot (i+1)" action.
|
|
/// </summary>
|
|
private const string SlotSelectorListField = "slotsSelectors";
|
|
private const string SlotSelectorInputField = "input";
|
|
private const string SlotActionPrefix = "Equip Slot ";
|
|
|
|
private static void MigrateBasicController()
|
|
{
|
|
MigratePrefab(BasicControllerPath);
|
|
}
|
|
|
|
private static void MigrateMeleeInventoryController()
|
|
{
|
|
MigratePrefab(MeleeInventoryControllerPath);
|
|
}
|
|
|
|
private static void MigrateMeleeNoInventoryController()
|
|
{
|
|
MigratePrefab(MeleeNoInventoryControllerPath);
|
|
}
|
|
|
|
private static void MigrateShooterMeleeInventoryController()
|
|
{
|
|
MigratePrefab(ShooterMeleeInventoryControllerPath);
|
|
}
|
|
|
|
private static void MigrateShooterMeleeNoInventoryController()
|
|
{
|
|
MigratePrefab(ShooterMeleeNoInventoryControllerPath);
|
|
}
|
|
|
|
private static void MigrateShooterOnlyInventoryController()
|
|
{
|
|
MigratePrefab(ShooterOnlyInventoryControllerPath);
|
|
}
|
|
|
|
private static void MigrateGameStyleSelectorMelee()
|
|
{
|
|
MigratePrefab(GameStyleSelectorMeleePath);
|
|
}
|
|
|
|
private static void MigrateGameStyleSelectorShooter()
|
|
{
|
|
MigratePrefab(GameStyleSelectorShooterPath);
|
|
}
|
|
|
|
private static void MigrateInventoryMeleeOnly()
|
|
{
|
|
MigratePrefab(InventoryMeleeOnlyPath);
|
|
}
|
|
|
|
private static void MigrateInventoryShooterMelee()
|
|
{
|
|
MigratePrefab(InventoryShooterMeleePath);
|
|
}
|
|
|
|
private static void MigrateInventoryShooterOnly()
|
|
{
|
|
MigratePrefab(InventoryShooterOnlyPath);
|
|
}
|
|
|
|
private static void MigrateAddonControllerPrefabs()
|
|
{
|
|
foreach (var prefabPath in AddonControllerPrefabPaths)
|
|
{
|
|
MigratePrefab(prefabPath);
|
|
}
|
|
}
|
|
|
|
private static void MigrateMobileControllerPrefabs()
|
|
{
|
|
foreach (var prefabPath in MobileControllerPrefabPaths)
|
|
{
|
|
MigratePrefab(prefabPath);
|
|
}
|
|
}
|
|
|
|
private static void MigratePrefab(string prefabPath)
|
|
{
|
|
var actions = AssetDatabase.LoadAssetAtPath<InputActionAsset>(InputActionsPath);
|
|
if (actions == null)
|
|
{
|
|
Debug.LogError($"Input action asset was not found at '{InputActionsPath}'.");
|
|
return;
|
|
}
|
|
|
|
var prefabRoot = PrefabUtility.LoadPrefabContents(prefabPath);
|
|
try
|
|
{
|
|
if (HasMissingScripts(prefabRoot))
|
|
{
|
|
if (MigrateSerializedPrefabWithoutSaving(prefabPath, actions))
|
|
{
|
|
Debug.Log($"Migrated GenericInput fields in '{prefabPath}' directly because Unity cannot save its existing missing scripts.");
|
|
}
|
|
return;
|
|
}
|
|
|
|
ConfigureInputModules(prefabRoot);
|
|
var inputManager = prefabRoot.GetComponent<vInput>();
|
|
if (inputManager == null) inputManager = prefabRoot.AddComponent<vInput>();
|
|
|
|
var migrated = 0;
|
|
foreach (var component in prefabRoot.GetComponentsInChildren<MonoBehaviour>(true))
|
|
{
|
|
if (component == null) continue;
|
|
var serializedObject = new SerializedObject(component);
|
|
var changed = false;
|
|
var typeName = component.GetType().Name;
|
|
var handledFields = new HashSet<string>();
|
|
foreach (var pair in typedBindings)
|
|
{
|
|
if (pair.Key.type != typeName) continue;
|
|
var property = serializedObject.FindProperty(pair.Key.field);
|
|
if (property == null || property.propertyType != SerializedPropertyType.Generic) continue;
|
|
if (!ConfigureGenericInput(property, pair.Value, actions)) continue;
|
|
handledFields.Add(pair.Key.field);
|
|
changed = true;
|
|
migrated++;
|
|
}
|
|
|
|
foreach (var pair in bindings)
|
|
{
|
|
if (handledFields.Contains(pair.Key)) continue;
|
|
var property = serializedObject.FindProperty(pair.Key);
|
|
if (property == null || property.propertyType != SerializedPropertyType.Generic) continue;
|
|
if (!ConfigureGenericInput(property, pair.Value, actions)) continue;
|
|
changed = true;
|
|
migrated++;
|
|
}
|
|
|
|
foreach (var pair in nestedListBindings)
|
|
{
|
|
if (pair.Key.type != typeName) continue;
|
|
var list = serializedObject.FindProperty(pair.Key.list);
|
|
if (list == null || !list.isArray) continue;
|
|
for (var i = 0; i < list.arraySize; i++)
|
|
{
|
|
var property = list.GetArrayElementAtIndex(i).FindPropertyRelative(pair.Key.field);
|
|
if (property == null || property.propertyType != SerializedPropertyType.Generic) continue;
|
|
if (!ConfigureGenericInput(property, pair.Value, actions)) continue;
|
|
changed = true;
|
|
migrated++;
|
|
}
|
|
}
|
|
|
|
if (typeName == "vInventory")
|
|
{
|
|
var controllers = serializedObject.FindProperty("changeEquipmentControllers");
|
|
if (controllers != null && controllers.isArray)
|
|
{
|
|
for (var i = 0; i < controllers.arraySize; i++)
|
|
{
|
|
var controller = controllers.GetArrayElementAtIndex(i);
|
|
migrated += ConfigureChangeEquipmentInput(controller, "previousItemInput", actions, ref changed);
|
|
migrated += ConfigureChangeEquipmentInput(controller, "nextItemInput", actions, ref changed);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (typeName == "vSimpleInput")
|
|
{
|
|
var property = serializedObject.FindProperty("input");
|
|
if (property != null && property.propertyType == SerializedPropertyType.Generic)
|
|
{
|
|
var key = property.FindPropertyRelative("keyboard").stringValue;
|
|
if (!simpleInputActionByKey.TryGetValue(key, out var actionName)) actionName = SimpleInputFallbackAction;
|
|
if (ConfigureGenericInput(property, new Binding(actionName), actions))
|
|
{
|
|
changed = true;
|
|
migrated++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (typeName == "vControlAreaByInput")
|
|
{
|
|
var selectors = serializedObject.FindProperty(SlotSelectorListField);
|
|
if (selectors != null && selectors.isArray)
|
|
{
|
|
for (var i = 0; i < selectors.arraySize; i++)
|
|
{
|
|
var property = selectors.GetArrayElementAtIndex(i).FindPropertyRelative(SlotSelectorInputField);
|
|
if (property == null || property.propertyType != SerializedPropertyType.Generic) continue;
|
|
// Empty legacy selectors are display-only; assigning an action creates unintended hotkeys.
|
|
if (string.IsNullOrEmpty(property.FindPropertyRelative("keyboard").stringValue) &&
|
|
string.IsNullOrEmpty(property.FindPropertyRelative("joystick").stringValue) &&
|
|
string.IsNullOrEmpty(property.FindPropertyRelative("mobile").stringValue))
|
|
{
|
|
property.FindPropertyRelative("useInput").boolValue = false;
|
|
property.FindPropertyRelative("inputAction").objectReferenceValue = null;
|
|
changed = true;
|
|
continue;
|
|
}
|
|
var slotIndex = selectors.GetArrayElementAtIndex(i).FindPropertyRelative("indexOfSlot").intValue;
|
|
if (!ConfigureGenericInput(property, new Binding(SlotActionPrefix + (slotIndex + 1)), actions)) continue;
|
|
changed = true;
|
|
migrated++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (component is vThirdPersonInput)
|
|
{
|
|
var toggleWalkAction = serializedObject.FindProperty("toggleWalkAction");
|
|
if (toggleWalkAction != null)
|
|
{
|
|
toggleWalkAction.objectReferenceValue = GetReference(actions.FindAction("Toggle Walk", true));
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
if (changed) serializedObject.ApplyModifiedPropertiesWithoutUndo();
|
|
}
|
|
|
|
AssetDatabase.SaveAssets();
|
|
var savedPrefab = PrefabUtility.SaveAsPrefabAsset(prefabRoot, prefabPath);
|
|
if (savedPrefab != null)
|
|
{
|
|
Debug.Log($"Migrated {migrated} GenericInput field(s) in '{prefabPath}' to the Input System.");
|
|
}
|
|
else if (MigrateSerializedPrefabWithoutSaving(prefabPath, actions))
|
|
{
|
|
Debug.Log($"Migrated GenericInput fields in '{prefabPath}' directly because Unity cannot save its existing missing scripts.");
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
PrefabUtility.UnloadPrefabContents(prefabRoot);
|
|
}
|
|
}
|
|
|
|
private static bool ConfigureGenericInput(SerializedProperty input, Binding binding, InputActionAsset actions)
|
|
{
|
|
if (input == null) return false;
|
|
var action = actions.FindAction(binding.action, false);
|
|
if (action == null)
|
|
{
|
|
Debug.LogError($"Action '{binding.action}' is missing from '{InputActionsPath}'.");
|
|
return false;
|
|
}
|
|
|
|
input.FindPropertyRelative("inputSource").enumValueIndex = (int)GenericInputSource.InputSystem;
|
|
input.FindPropertyRelative("inputAction").objectReferenceValue = GetReference(action);
|
|
input.FindPropertyRelative("inputSystemAxis").boolValue = binding.isAxis;
|
|
input.FindPropertyRelative("inputSystemValueComponent").enumValueIndex = (int)binding.component;
|
|
input.FindPropertyRelative("inputSystemAxisInvert").boolValue = binding.invert;
|
|
return true;
|
|
}
|
|
|
|
private static void ConfigureInputModules(GameObject prefabRoot)
|
|
{
|
|
foreach (var eventSystem in prefabRoot.GetComponentsInChildren<EventSystem>(true))
|
|
{
|
|
var backend = eventSystem.GetComponent<vEventSystemInputBackend>();
|
|
if (backend == null) backend = eventSystem.gameObject.AddComponent<vEventSystemInputBackend>();
|
|
backend.Configure();
|
|
}
|
|
}
|
|
|
|
private static int ConfigureChangeEquipmentInput(SerializedProperty controller, string fieldName, InputActionAsset actions, ref bool changed)
|
|
{
|
|
var input = controller.FindPropertyRelative(fieldName);
|
|
if (input == null || input.propertyType != SerializedPropertyType.Generic) return 0;
|
|
|
|
var joystick = input.FindPropertyRelative("joystick").stringValue;
|
|
var inverted = input.FindPropertyRelative("joystickAxisInvert").boolValue;
|
|
var actionName = ResolveDPadAction(joystick, inverted, fieldName == "previousItemInput");
|
|
if (!ConfigureGenericInput(input, new Binding(actionName), actions)) return 0;
|
|
|
|
changed = true;
|
|
return 1;
|
|
}
|
|
|
|
private static string ResolveDPadAction(string joystick, bool inverted, bool previous)
|
|
{
|
|
if (joystick == "D-Pad Vertical") return inverted ? "D-Pad Down" : "D-Pad Up";
|
|
if (joystick == "D-Pad Horizontal") return inverted ? "D-Pad Left" : "D-Pad Right";
|
|
return previous ? "D-Pad Left" : "D-Pad Right";
|
|
}
|
|
|
|
private static InputActionReference GetReference(InputAction action)
|
|
{
|
|
foreach (var asset in AssetDatabase.LoadAllAssetsAtPath(InputActionsPath))
|
|
{
|
|
if (asset is InputActionReference existingReference && existingReference.action != null && existingReference.action.id == action.id)
|
|
return existingReference;
|
|
}
|
|
|
|
var reference = InputActionReference.Create(action);
|
|
reference.name = action.name;
|
|
AssetDatabase.AddObjectToAsset(reference, InputActionsPath);
|
|
AssetDatabase.ImportAsset(InputActionsPath);
|
|
return reference;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Some legacy sample prefabs contain missing components and Unity refuses to save them, even
|
|
/// when the edited components are valid. This limited YAML fallback only updates GenericInput
|
|
/// data and leaves every missing component byte-for-byte untouched.
|
|
/// </summary>
|
|
private static bool MigrateSerializedPrefabWithoutSaving(string prefabPath, InputActionAsset actions)
|
|
{
|
|
var fullPath = Path.GetFullPath(prefabPath);
|
|
var lines = File.ReadAllLines(fullPath);
|
|
var output = new List<string>(lines.Length + 256);
|
|
var migrated = 0;
|
|
|
|
for (var i = 0; i < lines.Length; i++)
|
|
{
|
|
output.Add(lines[i]);
|
|
if (!TryGetGenericInputField(lines, i, out var fieldName, out var childIndent, out var blockEnd)) continue;
|
|
|
|
var binding = ResolveRawBinding(fieldName, lines, i, blockEnd);
|
|
if (binding == null) continue;
|
|
|
|
var action = actions.FindAction(binding.Value.action, false);
|
|
if (action == null) continue;
|
|
var reference = GetReference(action);
|
|
if (!AssetDatabase.TryGetGUIDAndLocalFileIdentifier(reference, out var guid, out long localId)) continue;
|
|
|
|
// A field might already be in the Input System form if an interrupted migration is retried.
|
|
var alreadyMigrated = false;
|
|
for (var j = i + 1; j < blockEnd; j++)
|
|
{
|
|
if (lines[j].TrimStart().StartsWith("inputSource:"))
|
|
{
|
|
alreadyMigrated = true;
|
|
break;
|
|
}
|
|
}
|
|
if (alreadyMigrated) continue;
|
|
|
|
for (var j = i + 1; j < blockEnd; j++)
|
|
{
|
|
output.Add(lines[j]);
|
|
if (!lines[j].TrimStart().StartsWith("useInput:")) continue;
|
|
output.Add($"{childIndent}inputSource: {(int)GenericInputSource.InputSystem}");
|
|
output.Add($"{childIndent}inputAction: {{fileID: {localId}, guid: {guid}, type: 3}}");
|
|
output.Add($"{childIndent}inputSystemAxis: {(binding.Value.isAxis ? 1 : 0)}");
|
|
output.Add($"{childIndent}inputSystemAxisInvert: {(binding.Value.invert ? 1 : 0)}");
|
|
output.Add($"{childIndent}inputSystemValueComponent: {(int)binding.Value.component}");
|
|
output.Add($"{childIndent}inputSystemRawThreshold: 0.1");
|
|
}
|
|
i = blockEnd - 1;
|
|
migrated++;
|
|
}
|
|
|
|
if (migrated == 0) return false;
|
|
File.WriteAllText(fullPath, string.Join("\n", output) + "\n", new UTF8Encoding(false));
|
|
AssetDatabase.ImportAsset(prefabPath, ImportAssetOptions.ForceUpdate);
|
|
AssetDatabase.SaveAssets();
|
|
return true;
|
|
}
|
|
|
|
private static bool HasMissingScripts(GameObject prefabRoot)
|
|
{
|
|
foreach (var component in prefabRoot.GetComponentsInChildren<MonoBehaviour>(true))
|
|
{
|
|
if (component == null) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private static bool TryGetGenericInputField(string[] lines, int lineIndex, out string fieldName, out string childIndent, out int blockEnd)
|
|
{
|
|
fieldName = null;
|
|
childIndent = null;
|
|
blockEnd = lineIndex;
|
|
var line = lines[lineIndex];
|
|
var colon = line.IndexOf(':');
|
|
if (colon <= 0 || line[(colon + 1)..].Trim().Length != 0) return false;
|
|
|
|
var parentIndentLength = line.Length - line.TrimStart().Length;
|
|
fieldName = line.Trim().TrimEnd(':');
|
|
if (fieldName.StartsWith("- ")) fieldName = fieldName[2..].TrimStart();
|
|
if (lineIndex + 1 >= lines.Length) return false;
|
|
childIndent = new string(' ', parentIndentLength + 2);
|
|
if (!lines[lineIndex + 1].StartsWith(childIndent + "useInput:")) return false;
|
|
|
|
blockEnd = lineIndex + 1;
|
|
while (blockEnd < lines.Length)
|
|
{
|
|
var candidate = lines[blockEnd];
|
|
if (candidate.Length > parentIndentLength && candidate.StartsWith(new string(' ', parentIndentLength + 1)))
|
|
{
|
|
blockEnd++;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private static Binding? ResolveRawBinding(string fieldName, string[] lines, int blockStart, int blockEnd)
|
|
{
|
|
if (bindings.TryGetValue(fieldName, out var binding)) return binding;
|
|
if (fieldName == "horizontal") return new Binding("Navigate", true, GenericInputValueComponent.X);
|
|
if (fieldName == "vertical" || fieldName == "verticallInput") return new Binding(fieldName == "vertical" ? "Navigate" : "Move", true, fieldName == "vertical" ? GenericInputValueComponent.Y : GenericInputValueComponent.Y);
|
|
if (fieldName == "selectRightInput") return new Binding("Toolbar Next");
|
|
if (fieldName == "selectLeftInput") return new Binding("Toolbar Previous");
|
|
if (fieldName == "previousItemInput") return new Binding("D-Pad Left");
|
|
if (fieldName == "nextItemInput") return new Binding("D-Pad Right");
|
|
if (fieldName != "input") return null;
|
|
|
|
for (var i = blockStart + 1; i < blockEnd; i++)
|
|
{
|
|
var trimmed = lines[i].Trim();
|
|
if (!trimmed.StartsWith("keyboard:")) continue;
|
|
var key = trimmed["keyboard:".Length..].Trim();
|
|
return simpleInputActionByKey.TryGetValue(key, out var action) ? new Binding(action) : new Binding(SimpleInputFallbackAction);
|
|
}
|
|
return new Binding(SimpleInputFallbackAction);
|
|
}
|
|
|
|
}
|
|
}
|
|
#endif
|