private static void WriteActionEventInitializer(string setName, string actionName, InputActionPhase phase, Writer writer, bool removeCallback = false) { var actionNameCased = actionName; if (char.IsLower(actionNameCased[0])) { actionNameCased = char.ToUpper(actionNameCased[0]) + actionNameCased.Substring(1); } string callbackName; switch (phase) { case InputActionPhase.Started: callbackName = "started"; break; case InputActionPhase.Performed: callbackName = "performed"; break; case InputActionPhase.Cancelled: callbackName = "cancelled"; break; default: throw new Exception("Internal error: No known callback for " + phase); } writer.WriteLine($"if (m_{setName}{actionNameCased}Action{phase} != null)"); ++writer.indentLevel; writer.WriteLine($"m_{setName}_{CSharpCodeHelpers.MakeIdentifier(actionName)}.{callbackName} {(removeCallback ? "-" : "+")}= m_{setName}{actionNameCased}Action{phase}.Invoke;"); --writer.indentLevel; }
public static string GenerateWrapperCode(InputActionAsset asset, Options options = new Options()) { if (string.IsNullOrEmpty(options.sourceAssetPath)) { options.sourceAssetPath = AssetDatabase.GetAssetPath(asset); } if (string.IsNullOrEmpty(options.className) && !string.IsNullOrEmpty(asset.name)) { options.className = CSharpCodeHelpers.MakeTypeName(asset.name); } return(GenerateWrapperCode(asset.actionMaps, options)); }
public override void OnInspectorGUI() { // 'assetEditor' is set only after the editor is enabled so do the // initialization here. if (!m_Initialized) { // Read current asset as backup. if (m_Backup == null) { m_Backup = GetAsset().ToJson(); } m_Initialized = true; } if (GUILayout.Button("Edit asset")) { ActionInspectorWindow.OnOpenAsset(GetAsset().GetInstanceID(), 0); } EditorGUILayout.Space(); // Look up properties on importer object. var generateWapperCodeProperty = serializedObject.FindProperty("m_GenerateWrapperCode"); // Add settings UI. EditorGUILayout.PropertyField(generateWapperCodeProperty, Contents.generateWrapperCode); if (generateWapperCodeProperty.boolValue) { var wrapperCodePathProperty = serializedObject.FindProperty("m_WrapperCodePath"); var wrapperClassNameProperty = serializedObject.FindProperty("m_WrapperClassName"); var wrapperCodeNamespaceProperty = serializedObject.FindProperty("m_WrapperCodeNamespace"); ////TODO: tie a file selector to this EditorGUILayout.PropertyField(wrapperCodePathProperty); EditorGUILayout.PropertyField(wrapperClassNameProperty); if (!CSharpCodeHelpers.IsEmptyOrProperIdentifier(wrapperClassNameProperty.stringValue)) { EditorGUILayout.HelpBox("Must be a valid C# identifier", MessageType.Error); } EditorGUILayout.PropertyField(wrapperCodeNamespaceProperty); if (!CSharpCodeHelpers.IsEmptyOrProperNamespaceName(wrapperCodeNamespaceProperty.stringValue)) { EditorGUILayout.HelpBox("Must be a valid C# namespace name", MessageType.Error); } } ApplyRevertGUI(); }
private static void ConvertInputActionToVDF(InputAction action, StringBuilder builder, Dictionary <string, string> localizationStrings) { builder.Append("\t\t\t\t\""); builder.Append(action.name); var mapIdentifier = CSharpCodeHelpers.MakeIdentifier(action.actionMap.name); var actionIdentifier = CSharpCodeHelpers.MakeIdentifier(action.name); var titleId = "Action_" + mapIdentifier + "_" + actionIdentifier; localizationStrings[titleId] = action.name; // StickPadGyros are objects. Everything else is just strings. var inputType = GetSteamControllerInputType(action); if (inputType == "StickPadGyro") { builder.Append("\"\n"); builder.Append("\t\t\t\t{\n"); // Title. builder.Append("\t\t\t\t\t\"title\"\t\"#"); builder.Append(titleId); builder.Append("\"\n"); // Decide on "input_mode". Assume "absolute_mouse" by default and take // anything built on StickControl as "joystick_move". var inputMode = "absolute_mouse"; var controlType = EditorInputControlLayoutCache.TryGetLayout(action.expectedControlType).type; if (typeof(StickControl).IsAssignableFrom(controlType)) { inputMode = "joystick_move"; } builder.Append("\t\t\t\t\t\"input_mode\"\t\""); builder.Append(inputMode); builder.Append("\"\n"); builder.Append("\t\t\t\t}\n"); } else { builder.Append("\"\t\""); builder.Append(titleId); builder.Append("\"\n"); } }
public override void OnInspectorGUI() { // Button to pop up window to edit the asset. if (GUILayout.Button("Edit asset")) { AssetInspectorWindow.OnOpenAsset(GetAsset().GetInstanceID(), 0); } EditorGUILayout.Space(); // Importer settings UI. var generateWapperCodeProperty = serializedObject.FindProperty("m_GenerateWrapperCode"); EditorGUILayout.PropertyField(generateWapperCodeProperty, m_GenerateWrapperCodeLabel); if (generateWapperCodeProperty.boolValue) { var wrapperCodePathProperty = serializedObject.FindProperty("m_WrapperCodePath"); var wrapperClassNameProperty = serializedObject.FindProperty("m_WrapperClassName"); var wrapperCodeNamespaceProperty = serializedObject.FindProperty("m_WrapperCodeNamespace"); var generateActionEventsProperty = serializedObject.FindProperty("m_GenerateActionEvents"); var generateInterfacesProperty = serializedObject.FindProperty("m_GenerateInterfaces"); ////TODO: tie a file selector to this EditorGUILayout.PropertyField(wrapperCodePathProperty, m_WrapperCodePathLabel); EditorGUILayout.PropertyField(wrapperClassNameProperty, m_WrapperClassNameLabel); if (!CSharpCodeHelpers.IsEmptyOrProperIdentifier(wrapperClassNameProperty.stringValue)) { EditorGUILayout.HelpBox("Must be a valid C# identifier", MessageType.Error); } EditorGUILayout.PropertyField(wrapperCodeNamespaceProperty, m_WrapperCodeNamespaceLabel); if (!CSharpCodeHelpers.IsEmptyOrProperNamespaceName(wrapperCodeNamespaceProperty.stringValue)) { EditorGUILayout.HelpBox("Must be a valid C# namespace name", MessageType.Error); } EditorGUILayout.PropertyField(generateActionEventsProperty, m_GenerateActionEventsLabel); EditorGUILayout.PropertyField(generateInterfacesProperty); } ApplyRevertGUI(); }
public override void OnInspectorGUI() { if (GUILayout.Button("Edit asset")) { ActionInspectorWindow.OnOpenAsset(GetAsset().GetInstanceID(), 0); } EditorGUILayout.Space(); // Look up properties on importer object. var generateWapperCodeProperty = serializedObject.FindProperty("m_GenerateWrapperCode"); // Add settings UI. EditorGUILayout.PropertyField(generateWapperCodeProperty, Contents.generateWrapperCode); if (generateWapperCodeProperty.boolValue) { var wrapperCodePathProperty = serializedObject.FindProperty("m_WrapperCodePath"); var wrapperClassNameProperty = serializedObject.FindProperty("m_WrapperClassName"); var wrapperCodeNamespaceProperty = serializedObject.FindProperty("m_WrapperCodeNamespace"); ////TODO: tie a file selector to this EditorGUILayout.PropertyField(wrapperCodePathProperty); EditorGUILayout.PropertyField(wrapperClassNameProperty); if (!CSharpCodeHelpers.IsEmptyOrProperIdentifier(wrapperClassNameProperty.stringValue)) { EditorGUILayout.HelpBox("Must be a valid C# identifier", MessageType.Error); } EditorGUILayout.PropertyField(wrapperCodeNamespaceProperty); if (!CSharpCodeHelpers.IsEmptyOrProperNamespaceName(wrapperCodeNamespaceProperty.stringValue)) { EditorGUILayout.HelpBox("Must be a valid C# namespace name", MessageType.Error); } } ApplyRevertGUI(); }
public override void OnInspectorGUI() { // Button to pop up window to edit the asset. if (GUILayout.Button("Edit asset")) { AssetInspectorWindow.OnOpenAsset(GetAsset().GetInstanceID(), 0); } EditorGUILayout.Space(); // Importer settings UI. var generateWapperCodeProperty = serializedObject.FindProperty("m_GenerateWrapperCode"); EditorGUILayout.PropertyField(generateWapperCodeProperty, m_GenerateWrapperCodeLabel); if (generateWapperCodeProperty.boolValue) { var wrapperCodePathProperty = serializedObject.FindProperty("m_WrapperCodePath"); var wrapperClassNameProperty = serializedObject.FindProperty("m_WrapperClassName"); var wrapperCodeNamespaceProperty = serializedObject.FindProperty("m_WrapperCodeNamespace"); var generateActionEventsProperty = serializedObject.FindProperty("m_GenerateActionEvents"); var generateInterfacesProperty = serializedObject.FindProperty("m_GenerateInterfaces"); EditorGUILayout.BeginHorizontal(); EditorGUILayout.PropertyField(wrapperCodePathProperty, m_WrapperCodePathLabel); if (GUILayout.Button("...", EditorStyles.miniButton, GUILayout.MaxWidth(20))) { var assetPath = AssetDatabase.GetAssetPath(GetAsset()); var defaultFileName = Path.ChangeExtension(assetPath, ".cs"); var fileName = EditorUtility.SaveFilePanel("Location for generated C# file", Path.GetDirectoryName(defaultFileName), Path.GetFileName(defaultFileName), "cs"); if (!string.IsNullOrEmpty(fileName)) { if (fileName.StartsWith(Application.dataPath)) { fileName = "Assets/" + fileName.Substring(Application.dataPath.Length + 1); } wrapperCodePathProperty.stringValue = fileName; } } EditorGUILayout.EndHorizontal(); EditorGUILayout.PropertyField(wrapperClassNameProperty, m_WrapperClassNameLabel); if (!CSharpCodeHelpers.IsEmptyOrProperIdentifier(wrapperClassNameProperty.stringValue)) { EditorGUILayout.HelpBox("Must be a valid C# identifier", MessageType.Error); } EditorGUILayout.PropertyField(wrapperCodeNamespaceProperty, m_WrapperCodeNamespaceLabel); if (!CSharpCodeHelpers.IsEmptyOrProperNamespaceName(wrapperCodeNamespaceProperty.stringValue)) { EditorGUILayout.HelpBox("Must be a valid C# namespace name", MessageType.Error); } EditorGUILayout.PropertyField(generateActionEventsProperty, m_GenerateActionEventsLabel); EditorGUILayout.PropertyField(generateInterfacesProperty); } ApplyRevertGUI(); }
public static string GenerateInputDeviceFromSteamIGA(string vdf, string namespaceAndClassName) { if (string.IsNullOrEmpty(vdf)) { throw new ArgumentNullException("vdf"); } if (string.IsNullOrEmpty(namespaceAndClassName)) { throw new ArgumentNullException("namespaceAndClassName"); } // Parse VDF. var parsedVdf = ParseVDF(vdf); var actions = (Dictionary <string, object>)((Dictionary <string, object>)parsedVdf["In Game Actions"])["actions"]; // Determine class and namespace name. var namespaceName = ""; var className = ""; var indexOfLastDot = namespaceAndClassName.LastIndexOf('.'); if (indexOfLastDot != -1) { namespaceName = namespaceAndClassName.Substring(0, indexOfLastDot); className = namespaceAndClassName.Substring(indexOfLastDot + 1); } else { className = namespaceAndClassName; } var stateStructName = className + "State"; var builder = new StringBuilder(); builder.Append("// THIS FILE HAS BEEN AUTO-GENERATED\n"); builder.Append("#if (UNITY_EDITOR || UNITY_STANDALONE) && UNITY_ENABLE_STEAM_CONTROLLER_SUPPORT\n"); builder.Append("using UnityEngine;\n"); builder.Append("using UnityEngine.InputSystem;\n"); builder.Append("using UnityEngine.InputSystem.Controls;\n"); builder.Append("using UnityEngine.InputSystem.Layouts;\n"); builder.Append("using UnityEngine.InputSystem.Utilities;\n"); builder.Append("using UnityEngine.InputSystem.Steam;\n"); builder.Append("#if UNITY_EDITOR\n"); builder.Append("using UnityEditor;\n"); builder.Append("#endif\n"); builder.Append("\n"); if (!string.IsNullOrEmpty(namespaceName)) { builder.Append("namespace "); builder.Append(namespaceName); builder.Append("\n{\n"); } // InitializeOnLoad attribute. builder.Append("#if UNITY_EDITOR\n"); builder.Append("[InitializeOnLoad]\n"); builder.Append("#endif\n"); // Control layout attribute. builder.Append("[InputControlLayout(stateType = typeof("); builder.Append(stateStructName); builder.Append("))]\n"); // Class declaration. builder.Append("public class "); builder.Append(className); builder.Append(" : SteamController\n"); builder.Append("{\n"); // Device matcher. builder.Append(" private static InputDeviceMatcher deviceMatcher\n"); builder.Append(" {\n"); builder.Append(" get { return new InputDeviceMatcher().WithInterface(\"Steam\").WithProduct(\""); builder.Append(className); builder.Append("\"); }\n"); builder.Append(" }\n"); // Static constructor. builder.Append('\n'); builder.Append("#if UNITY_EDITOR\n"); builder.Append(" static "); builder.Append(className); builder.Append("()\n"); builder.Append(" {\n"); builder.Append(" InputSystem.RegisterLayout<"); builder.Append(className); builder.Append(">(matches: deviceMatcher);\n"); builder.Append(" }\n"); builder.Append("#endif\n"); // RuntimeInitializeOnLoadMethod. // NOTE: Not relying on static ctor here. See il2cpp bug 1014293. builder.Append('\n'); builder.Append(" [RuntimeInitializeOnLoadMethod(loadType: RuntimeInitializeLoadType.BeforeSceneLoad)]\n"); builder.Append(" private static void RuntimeInitializeOnLoad()\n"); builder.Append(" {\n"); builder.Append(" InputSystem.RegisterLayout<"); builder.Append(className); builder.Append(">(matches: deviceMatcher);\n"); builder.Append(" }\n"); // Control properties. builder.Append('\n'); foreach (var setEntry in actions) { var setEntryProperties = (Dictionary <string, object>)setEntry.Value; // StickPadGyros. var stickPadGyros = (Dictionary <string, object>)setEntryProperties["StickPadGyro"]; foreach (var entry in stickPadGyros) { var entryProperties = (Dictionary <string, object>)entry.Value; var isStick = entryProperties.ContainsKey("input_mode") && (string)entryProperties["input_mode"] == "joystick_move"; builder.Append(string.Format(" public {0} {1} {{ get; protected set; }}\n", isStick ? "StickControl" : "Vector2Control", CSharpCodeHelpers.MakeIdentifier(entry.Key))); } // Buttons. var buttons = (Dictionary <string, object>)setEntryProperties["Button"]; foreach (var entry in buttons) { builder.Append(string.Format(" public ButtonControl {0} {{ get; protected set; }}\n", CSharpCodeHelpers.MakeIdentifier(entry.Key))); } // AnalogTriggers. var analogTriggers = (Dictionary <string, object>)setEntryProperties["AnalogTrigger"]; foreach (var entry in analogTriggers) { builder.Append(string.Format(" public AxisControl {0} {{ get; protected set; }}\n", CSharpCodeHelpers.MakeIdentifier(entry.Key))); } } // FinishSetup method. builder.Append('\n'); builder.Append(" protected override void FinishSetup(InputDeviceBuilder builder)\n"); builder.Append(" {\n"); builder.Append(" base.FinishSetup(builder);\n"); foreach (var setEntry in actions) { var setEntryProperties = (Dictionary <string, object>)setEntry.Value; // StickPadGyros. var stickPadGyros = (Dictionary <string, object>)setEntryProperties["StickPadGyro"]; foreach (var entry in stickPadGyros) { var entryProperties = (Dictionary <string, object>)entry.Value; var isStick = entryProperties.ContainsKey("input_mode") && (string)entryProperties["input_mode"] == "joystick_move"; builder.Append(string.Format(" {0} = builder.GetControl<{1}>(\"{2}\");\n", CSharpCodeHelpers.MakeIdentifier(entry.Key), isStick ? "StickControl" : "Vector2Control", entry.Key)); } // Buttons. var buttons = (Dictionary <string, object>)setEntryProperties["Button"]; foreach (var entry in buttons) { builder.Append(string.Format(" {0} = builder.GetControl<ButtonControl>(\"{1}\");\n", CSharpCodeHelpers.MakeIdentifier(entry.Key), entry.Key)); } // AnalogTriggers. var analogTriggers = (Dictionary <string, object>)setEntryProperties["AnalogTrigger"]; foreach (var entry in analogTriggers) { builder.Append(string.Format(" {0} = builder.GetControl<AxisControl>(\"{1}\");\n", CSharpCodeHelpers.MakeIdentifier(entry.Key), entry.Key)); } } builder.Append(" }\n"); // ResolveSteamActions method. builder.Append('\n'); builder.Append(" protected override void ResolveSteamActions(ISteamControllerAPI api)\n"); builder.Append(" {\n"); foreach (var setEntry in actions) { var setEntryProperties = (Dictionary <string, object>)setEntry.Value; // Set handle. builder.Append(string.Format(" {0}SetHandle = api.GetActionSetHandle(\"{1}\");\n", CSharpCodeHelpers.MakeIdentifier(setEntry.Key), setEntry.Key)); // StickPadGyros. var stickPadGyros = (Dictionary <string, object>)setEntryProperties["StickPadGyro"]; foreach (var entry in stickPadGyros) { builder.Append(string.Format(" {0}Handle = api.GetAnalogActionHandle(\"{1}\");\n", CSharpCodeHelpers.MakeIdentifier(entry.Key), entry.Key)); } // Buttons. var buttons = (Dictionary <string, object>)setEntryProperties["Button"]; foreach (var entry in buttons) { builder.Append(string.Format(" {0}Handle = api.GetDigitalActionHandle(\"{1}\");\n", CSharpCodeHelpers.MakeIdentifier(entry.Key), entry.Key)); } // AnalogTriggers. var analogTriggers = (Dictionary <string, object>)setEntryProperties["AnalogTrigger"]; foreach (var entry in analogTriggers) { builder.Append(string.Format(" {0}Handle = api.GetAnalogActionHandle(\"{1}\");\n", CSharpCodeHelpers.MakeIdentifier(entry.Key), entry.Key)); } } builder.Append(" }\n"); // Handle cache fields. builder.Append('\n'); foreach (var setEntry in actions) { var setEntryProperties = (Dictionary <string, object>)setEntry.Value; // Set handle. builder.Append(string.Format(" public SteamHandle<InputActionMap> {0}SetHandle {{ get; private set; }}\n", CSharpCodeHelpers.MakeIdentifier(setEntry.Key))); // StickPadGyros. var stickPadGyros = (Dictionary <string, object>)setEntryProperties["StickPadGyro"]; foreach (var entry in stickPadGyros) { builder.Append(string.Format(" public SteamHandle<InputAction> {0}Handle {{ get; private set; }}\n", CSharpCodeHelpers.MakeIdentifier(entry.Key))); } // Buttons. var buttons = (Dictionary <string, object>)setEntryProperties["Button"]; foreach (var entry in buttons) { builder.Append(string.Format(" public SteamHandle<InputAction> {0}Handle {{ get; private set; }}\n", CSharpCodeHelpers.MakeIdentifier(entry.Key))); } // AnalogTriggers. var analogTriggers = (Dictionary <string, object>)setEntryProperties["AnalogTrigger"]; foreach (var entry in analogTriggers) { builder.Append(string.Format(" public SteamHandle<InputAction> {0}Handle {{ get; private set; }}\n", CSharpCodeHelpers.MakeIdentifier(entry.Key))); } } // steamActionSets property. builder.Append('\n'); builder.Append(" private SteamActionSetInfo[] m_ActionSets;\n"); builder.Append(" public override ReadOnlyArray<SteamActionSetInfo> steamActionSets\n"); builder.Append(" {\n"); builder.Append(" get\n"); builder.Append(" {\n"); builder.Append(" if (m_ActionSets == null)\n"); builder.Append(" m_ActionSets = new[]\n"); builder.Append(" {\n"); foreach (var setEntry in actions) { builder.Append(string.Format( " new SteamActionSetInfo {{ name = \"{0}\", handle = {1}SetHandle }},\n", setEntry.Key, CSharpCodeHelpers.MakeIdentifier(setEntry.Key))); } builder.Append(" };\n"); builder.Append(" return new ReadOnlyArray<SteamActionSetInfo>(m_ActionSets);\n"); builder.Append(" }\n"); builder.Append(" }\n"); // Update method. builder.Append('\n'); builder.Append(" protected override unsafe void Update(ISteamControllerAPI api)\n"); builder.Append(" {\n"); builder.Append(string.Format(" {0} state;\n", stateStructName)); var currentButtonBit = 0; foreach (var setEntry in actions) { var setEntryProperties = (Dictionary <string, object>)setEntry.Value; // StickPadGyros. var stickPadGyros = (Dictionary <string, object>)setEntryProperties["StickPadGyro"]; foreach (var entry in stickPadGyros) { builder.Append(string.Format(" state.{0} = api.GetAnalogActionData(steamControllerHandle, {0}Handle).position;\n", CSharpCodeHelpers.MakeIdentifier(entry.Key))); } // Buttons. var buttons = (Dictionary <string, object>)setEntryProperties["Button"]; foreach (var entry in buttons) { builder.Append(string.Format(" if (api.GetDigitalActionData(steamControllerHandle, {0}Handle).pressed)\n", CSharpCodeHelpers.MakeIdentifier(entry.Key))); builder.Append(string.Format(" state.buttons[{0}] |= {1};\n", currentButtonBit / 8, currentButtonBit % 8)); ++currentButtonBit; } // AnalogTriggers. var analogTriggers = (Dictionary <string, object>)setEntryProperties["AnalogTrigger"]; foreach (var entry in analogTriggers) { builder.Append(string.Format(" state.{0} = api.GetAnalogActionData(steamControllerHandle, {0}Handle).position.x;\n", CSharpCodeHelpers.MakeIdentifier(entry.Key))); } } builder.Append(" InputSystem.QueueStateEvent(this, state);\n"); builder.Append(" }\n"); builder.Append("}\n"); if (!string.IsNullOrEmpty(namespaceName)) { builder.Append("}\n"); } // State struct. builder.Append("public unsafe struct "); builder.Append(stateStructName); builder.Append(" : IInputStateTypeInfo\n"); builder.Append("{\n"); builder.Append(" public FourCC format\n"); builder.Append(" {\n"); builder.Append(" get {\n"); ////TODO: handle class names that are shorter than 4 characters ////TODO: uppercase characters builder.Append(string.Format(" return new FourCC('{0}', '{1}', '{2}', '{3}');\n", className[0], className[1], className[2], className[3])); builder.Append(" }\n"); builder.Append(" }\n"); builder.Append("\n"); var totalButtonCount = 0; foreach (var setEntry in actions) { var setEntryProperties = (Dictionary <string, object>)setEntry.Value; // Buttons. var buttons = (Dictionary <string, object>)setEntryProperties["Button"]; var buttonCount = buttons.Count; if (buttonCount > 0) { foreach (var entry in buttons) { builder.Append(string.Format( " [InputControl(name = \"{0}\", layout = \"Button\", bit = {1})]\n", entry.Key, totalButtonCount)); ++totalButtonCount; } } } if (totalButtonCount > 0) { var byteCount = (totalButtonCount + 7) / 8; builder.Append(" public fixed byte buttons["); builder.Append(byteCount.ToString()); builder.Append("];\n"); } foreach (var setEntry in actions) { var setEntryProperties = (Dictionary <string, object>)setEntry.Value; // StickPadGyros. var stickPadGyros = (Dictionary <string, object>)setEntryProperties["StickPadGyro"]; foreach (var entry in stickPadGyros) { var entryProperties = (Dictionary <string, object>)entry.Value; var isStick = entryProperties.ContainsKey("input_mode") && (string)entryProperties["input_mode"] == "joystick_move"; builder.Append(string.Format(" [InputControl(name = \"{0}\", layout = \"{1}\")]\n", entry.Key, isStick ? "Stick" : "Vector2")); builder.Append(string.Format(" public Vector2 {0};\n", CSharpCodeHelpers.MakeIdentifier(entry.Key))); } // AnalogTriggers. var analogTriggers = (Dictionary <string, object>)setEntryProperties["AnalogTrigger"]; foreach (var entry in analogTriggers) { builder.Append(string.Format(" [InputControl(name = \"{0}\", layout = \"Axis\")]\n", entry.Key)); builder.Append(string.Format(" public float {0};\n", CSharpCodeHelpers.MakeIdentifier(entry.Key))); } } builder.Append("}\n"); builder.Append("#endif\n"); return(builder.ToString()); }
public static string ConvertInputActionsToSteamIGA(IEnumerable <InputActionMap> actionMaps, string locale = "english") { if (actionMaps == null) { throw new ArgumentNullException("actionMaps"); } var localizationStrings = new Dictionary <string, string>(); var builder = new StringBuilder(); builder.Append("\"In Game Actions\"\n"); builder.Append("{\n"); // Add actions. builder.Append("\t\"actions\"\n"); builder.Append("\t{\n"); // Add each action map. foreach (var actionMap in actionMaps) { var actionMapName = actionMap.name; var actionMapIdentifier = CSharpCodeHelpers.MakeIdentifier(actionMapName); builder.Append("\t\t\""); builder.Append(actionMapName); builder.Append("\"\n"); builder.Append("\t\t{\n"); // Title. builder.Append("\t\t\t\"title\"\t\"#Set_"); builder.Append(actionMapIdentifier); builder.Append("\"\n"); localizationStrings["Set_" + actionMapIdentifier] = actionMapName; // StickPadGyro actions. builder.Append("\t\t\t\"StickPadGyro\"\n"); builder.Append("\t\t\t{\n"); foreach (var action in actionMap.actions.Where(x => GetSteamControllerInputType(x) == "StickPadGyro")) { ConvertInputActionToVDF(action, builder, localizationStrings); } builder.Append("\t\t\t}\n"); // AnalogTrigger actions. builder.Append("\t\t\t\"AnalogTrigger\"\n"); builder.Append("\t\t\t{\n"); foreach (var action in actionMap.actions.Where(x => GetSteamControllerInputType(x) == "AnalogTrigger")) { ConvertInputActionToVDF(action, builder, localizationStrings); } builder.Append("\t\t\t}\n"); // Button actions. builder.Append("\t\t\t\"Button\"\n"); builder.Append("\t\t\t{\n"); foreach (var action in actionMap.actions.Where(x => GetSteamControllerInputType(x) == "Button")) { ConvertInputActionToVDF(action, builder, localizationStrings); } builder.Append("\t\t\t}\n"); builder.Append("\t\t}\n"); } builder.Append("\t}\n"); // Add localizations. builder.Append("\t\"localization\"\n"); builder.Append("\t{\n"); builder.Append("\t\t\""); builder.Append(locale); builder.Append("\"\n"); builder.Append("\t\t{\n"); foreach (var entry in localizationStrings) { builder.Append("\t\t\t\""); builder.Append(entry.Key); builder.Append("\"\t\""); builder.Append(entry.Value); builder.Append("\"\n"); } builder.Append("\t\t}\n"); builder.Append("\t}\n"); builder.Append("}\n"); return(builder.ToString()); }
// Generate a string containing C# code that simplifies working with the given // action sets in code. public static string GenerateWrapperCode(IEnumerable <InputActionMap> sets, Options options) { if (string.IsNullOrEmpty(options.sourceAssetPath)) { throw new ArgumentException("options.sourceAssetPath"); } if (string.IsNullOrEmpty(options.className)) { options.className = CSharpCodeHelpers.MakeTypeName(Path.GetFileNameWithoutExtension(options.sourceAssetPath)); } var writer = new Writer { buffer = new StringBuilder() }; // Header. writer.WriteLine(string.Format("// GENERATED AUTOMATICALLY FROM '{0}'\n", options.sourceAssetPath)); // Begin namespace. var haveNamespace = !string.IsNullOrEmpty(options.namespaceName); if (haveNamespace) { writer.WriteLine(string.Format("namespace {0}", options.namespaceName)); writer.BeginBlock(); } // Begin class. writer.WriteLine("[System.Serializable]"); writer.WriteLine(string.Format("public class {0} : UnityEngine.Experimental.Input.InputActionWrapper", options.className)); writer.BeginBlock(); // Initialize method. writer.WriteLine("private bool m_Initialized;"); writer.WriteLine("private void Initialize()"); writer.BeginBlock(); foreach (var set in sets) { var setName = CSharpCodeHelpers.MakeIdentifier(set.name); writer.WriteLine(string.Format("// {0}", set.name)); writer.WriteLine(string.Format("m_{0} = asset.GetActionMap(\"{1}\");", setName, set.name)); foreach (var action in set.actions) { writer.WriteLine(string.Format("m_{0}_{1} = m_{2}.GetAction(\"{3}\");", setName, CSharpCodeHelpers.MakeIdentifier(action.name), setName, action.name)); } } writer.WriteLine("m_Initialized = true;"); writer.EndBlock(); // Action set accessors. foreach (var set in sets) { writer.WriteLine(string.Format("// {0}", set.name)); var setName = CSharpCodeHelpers.MakeIdentifier(set.name); var setStructName = CSharpCodeHelpers.MakeTypeName(setName, "Actions"); // Caching field for action set. writer.WriteLine(string.Format("private UnityEngine.Experimental.Input.InputActionMap m_{0};", setName)); // Caching fields for all actions. foreach (var action in set.actions) { writer.WriteLine(string.Format("private UnityEngine.Experimental.Input.InputAction m_{0}_{1};", setName, CSharpCodeHelpers.MakeIdentifier(action.name))); } // Struct wrapping access to action set. writer.WriteLine(string.Format("public struct {0}", setStructName)); writer.BeginBlock(); // Constructor. writer.WriteLine(string.Format("private {0} m_Wrapper;", options.className)); writer.WriteLine(string.Format("public {0}({1} wrapper) {{ m_Wrapper = wrapper; }}", setStructName, options.className)); // Getter for each action. foreach (var action in set.actions) { var actionName = CSharpCodeHelpers.MakeIdentifier(action.name); writer.WriteLine(string.Format( "public UnityEngine.Experimental.Input.InputAction @{0} {{ get {{ return m_Wrapper.m_{1}_{2}; }} }}", actionName, setName, actionName)); } // Action set getter. writer.WriteLine(string.Format("public UnityEngine.Experimental.Input.InputActionMap Get() {{ return m_Wrapper.m_{0}; }}", setName)); // Enable/disable methods. writer.WriteLine("public void Enable() { Get().Enable(); }"); writer.WriteLine("public void Disable() { Get().Disable(); }"); // Clone method. writer.WriteLine("public UnityEngine.Experimental.Input.InputActionMap Clone() { return Get().Clone(); }"); // Implicit conversion operator. writer.WriteLine(string.Format( "public static implicit operator UnityEngine.Experimental.Input.InputActionMap({0} set) {{ return set.Get(); }}", setStructName)); writer.EndBlock(); // Getter for instance of struct. writer.WriteLine(string.Format("public {0} @{1}", setStructName, setName)); writer.BeginBlock(); writer.WriteLine("get"); writer.BeginBlock(); writer.WriteLine("if (!m_Initialized) Initialize();"); writer.WriteLine(string.Format("return new {0}(this);", setStructName)); writer.EndBlock(); writer.EndBlock(); } // End class. writer.EndBlock(); // End namespace. if (haveNamespace) { writer.EndBlock(); } return(writer.buffer.ToString()); }
// Generate a string containing C# code that simplifies working with the given // action sets in code. public static string GenerateWrapperCode(IEnumerable <InputActionMap> maps, IEnumerable <InputControlScheme> schemes, Options options) { if (string.IsNullOrEmpty(options.sourceAssetPath)) { throw new ArgumentException("options.sourceAssetPath"); } if (string.IsNullOrEmpty(options.className)) { options.className = CSharpCodeHelpers.MakeTypeName(Path.GetFileNameWithoutExtension(options.sourceAssetPath)); } var writer = new Writer { buffer = new StringBuilder() }; // Header. writer.WriteLine(string.Format("// GENERATED AUTOMATICALLY FROM '{0}'\n", options.sourceAssetPath)); // Usings. writer.WriteLine("using System;"); writer.WriteLine("using UnityEngine;"); if (options.generateEvents) { writer.WriteLine("using UnityEngine.Events;"); } writer.WriteLine("using UnityEngine.Experimental.Input;"); writer.WriteLine("\n"); // Begin namespace. var haveNamespace = !string.IsNullOrEmpty(options.namespaceName); if (haveNamespace) { writer.WriteLine(string.Format("namespace {0}", options.namespaceName)); writer.BeginBlock(); } // Begin class. writer.WriteLine("[Serializable]"); writer.WriteLine(string.Format("public class {0} : InputActionAssetReference", options.className)); writer.BeginBlock(); // Default constructor. writer.WriteLine(string.Format("public {0}()", options.className)); writer.BeginBlock(); writer.EndBlock(); // Explicit constructor. writer.WriteLine(string.Format("public {0}(InputActionAsset asset)", options.className)); ++writer.indentLevel; writer.WriteLine(": base(asset)"); --writer.indentLevel; writer.BeginBlock(); writer.EndBlock(); // Initialize method. writer.WriteLine("private bool m_Initialized;"); writer.WriteLine("private void Initialize()"); writer.BeginBlock(); foreach (var set in maps) { var setName = CSharpCodeHelpers.MakeIdentifier(set.name); writer.WriteLine(string.Format("// {0}", set.name)); writer.WriteLine(string.Format("m_{0} = asset.GetActionMap(\"{1}\");", setName, set.name)); foreach (var action in set.actions) { var actionName = CSharpCodeHelpers.MakeIdentifier(action.name); writer.WriteLine(string.Format("m_{0}_{1} = m_{2}.GetAction(\"{3}\");", setName, actionName, setName, action.name)); if (options.generateEvents) { WriteActionEventInitializer(setName, actionName, InputActionPhase.Started, writer); WriteActionEventInitializer(setName, actionName, InputActionPhase.Performed, writer); WriteActionEventInitializer(setName, actionName, InputActionPhase.Cancelled, writer); } } } writer.WriteLine("m_Initialized = true;"); writer.EndBlock(); // Uninitialize method. writer.WriteLine("private void Uninitialize()"); writer.BeginBlock(); foreach (var set in maps) { var setName = CSharpCodeHelpers.MakeIdentifier(set.name); writer.WriteLine(string.Format("m_{0} = null;", setName)); foreach (var action in set.actions) { var actionName = CSharpCodeHelpers.MakeIdentifier(action.name); writer.WriteLine(string.Format("m_{0}_{1} = null;", setName, actionName)); if (options.generateEvents) { WriteActionEventInitializer(setName, actionName, InputActionPhase.Started, writer, removeCallback: true); WriteActionEventInitializer(setName, actionName, InputActionPhase.Performed, writer, removeCallback: true); WriteActionEventInitializer(setName, actionName, InputActionPhase.Cancelled, writer, removeCallback: true); } } } writer.WriteLine("m_Initialized = false;"); writer.EndBlock(); // SwitchAsset method. writer.WriteLine("public void SwitchAsset(InputActionAsset newAsset)"); writer.BeginBlock(); writer.WriteLine("if (newAsset == asset) return;"); writer.WriteLine("if (m_Initialized) Uninitialize();"); writer.WriteLine("asset = newAsset;"); writer.EndBlock(); ////REVIEW: DuplicateActionsAndBindings? // DuplicateAndSwitchAsset method. writer.WriteLine("public void DuplicateAndSwitchAsset()"); writer.BeginBlock(); writer.WriteLine("SwitchAsset(ScriptableObject.Instantiate(asset));"); writer.EndBlock(); // Action map accessors. foreach (var map in maps) { writer.WriteLine(string.Format("// {0}", map.name)); var setName = CSharpCodeHelpers.MakeIdentifier(map.name); var setStructName = CSharpCodeHelpers.MakeTypeName(setName, "Actions"); // Caching field for action set. writer.WriteLine(string.Format("private InputActionMap m_{0};", setName)); // Caching fields for all actions. foreach (var action in map.actions) { var actionName = CSharpCodeHelpers.MakeIdentifier(action.name); writer.WriteLine(string.Format("private InputAction m_{0}_{1};", setName, actionName)); if (options.generateEvents) { WriteActionEventField(setName, actionName, InputActionPhase.Started, writer); WriteActionEventField(setName, actionName, InputActionPhase.Performed, writer); WriteActionEventField(setName, actionName, InputActionPhase.Cancelled, writer); } } // Struct wrapping access to action set. writer.WriteLine(string.Format("public struct {0}", setStructName)); writer.BeginBlock(); // Constructor. writer.WriteLine(string.Format("private {0} m_Wrapper;", options.className)); writer.WriteLine(string.Format("public {0}({1} wrapper) {{ m_Wrapper = wrapper; }}", setStructName, options.className)); // Getter for each action. foreach (var action in map.actions) { var actionName = CSharpCodeHelpers.MakeIdentifier(action.name); writer.WriteLine(string.Format( "public InputAction @{0} {{ get {{ return m_Wrapper.m_{1}_{2}; }} }}", actionName, setName, actionName)); // Action event getters. if (options.generateEvents) { WriteActionEventGetter(setName, actionName, InputActionPhase.Started, writer); WriteActionEventGetter(setName, actionName, InputActionPhase.Performed, writer); WriteActionEventGetter(setName, actionName, InputActionPhase.Cancelled, writer); } } // Action set getter. writer.WriteLine(string.Format("public InputActionMap Get() {{ return m_Wrapper.m_{0}; }}", setName)); // Enable/disable methods. writer.WriteLine("public void Enable() { Get().Enable(); }"); writer.WriteLine("public void Disable() { Get().Disable(); }"); // Clone method. writer.WriteLine("public InputActionMap Clone() { return Get().Clone(); }"); // Implicit conversion operator. writer.WriteLine(string.Format( "public static implicit operator InputActionMap({0} set) {{ return set.Get(); }}", setStructName)); writer.EndBlock(); // Getter for instance of struct. writer.WriteLine(string.Format("public {0} @{1}", setStructName, setName)); writer.BeginBlock(); writer.WriteLine("get"); writer.BeginBlock(); writer.WriteLine("if (!m_Initialized) Initialize();"); writer.WriteLine(string.Format("return new {0}(this);", setStructName)); writer.EndBlock(); writer.EndBlock(); } // Control scheme accessors. foreach (var scheme in schemes) { var identifier = CSharpCodeHelpers.MakeIdentifier(scheme.name); writer.WriteLine(string.Format("private int m_{0}SchemeIndex = -1;", identifier)); writer.WriteLine(string.Format("public InputControlScheme {0}Scheme", identifier)); writer.BeginBlock(); writer.WriteLine("get\n"); writer.BeginBlock(); writer.WriteLine(string.Format( "if (m_{0}SchemeIndex == -1) m_{0}SchemeIndex = asset.GetControlSchemeIndex(\"{1}\");", identifier, scheme.name)); writer.WriteLine(string.Format("return asset.controlSchemes[m_{0}SchemeIndex];", identifier)); writer.EndBlock(); writer.EndBlock(); } // Action event class. if (options.generateEvents) { writer.WriteLine("[Serializable]"); writer.WriteLine("public class ActionEvent : UnityEvent<InputAction.CallbackContext>"); writer.BeginBlock(); writer.EndBlock(); } // End class. writer.EndBlock(); // End namespace. if (haveNamespace) { writer.EndBlock(); } return(writer.buffer.ToString()); }
public override void OnInspectorGUI() { // ScriptedImporterEditor in 2019.2 now requires explicitly updating the SerializedObject // like in other types of editors. serializedObject.Update(); // Button to pop up window to edit the asset. if (GUILayout.Button("Edit asset")) { InputActionEditorWindow.OnOpenAsset(GetAsset().GetInstanceID(), 0); } EditorGUILayout.Space(); // Importer settings UI. var generateWrapperCodeProperty = serializedObject.FindProperty("m_GenerateWrapperCode"); EditorGUILayout.PropertyField(generateWrapperCodeProperty, m_GenerateWrapperCodeLabel); if (generateWrapperCodeProperty.boolValue) { var wrapperCodePathProperty = serializedObject.FindProperty("m_WrapperCodePath"); var wrapperClassNameProperty = serializedObject.FindProperty("m_WrapperClassName"); var wrapperCodeNamespaceProperty = serializedObject.FindProperty("m_WrapperCodeNamespace"); EditorGUILayout.BeginHorizontal(); var assetPath = AssetDatabase.GetAssetPath(GetAsset()); var defaultFileName = Path.ChangeExtension(assetPath, ".cs"); wrapperCodePathProperty.PropertyFieldWithDefaultText(m_WrapperCodePathLabel, defaultFileName); if (GUILayout.Button("…", EditorStyles.miniButton, GUILayout.MaxWidth(20))) { var fileName = EditorUtility.SaveFilePanel("Location for generated C# file", Path.GetDirectoryName(defaultFileName), Path.GetFileName(defaultFileName), "cs"); if (!string.IsNullOrEmpty(fileName)) { if (fileName.StartsWith(Application.dataPath)) { fileName = "Assets/" + fileName.Substring(Application.dataPath.Length + 1); } wrapperCodePathProperty.stringValue = fileName; } } EditorGUILayout.EndHorizontal(); wrapperClassNameProperty.PropertyFieldWithDefaultText(m_WrapperClassNameLabel, CSharpCodeHelpers.MakeTypeName(GetAsset().name)); if (!CSharpCodeHelpers.IsEmptyOrProperIdentifier(wrapperClassNameProperty.stringValue)) { EditorGUILayout.HelpBox("Must be a valid C# identifier", MessageType.Error); } wrapperCodeNamespaceProperty.PropertyFieldWithDefaultText(m_WrapperCodeNamespaceLabel, "<Global namespace>"); if (!CSharpCodeHelpers.IsEmptyOrProperNamespaceName(wrapperCodeNamespaceProperty.stringValue)) { EditorGUILayout.HelpBox("Must be a valid C# namespace name", MessageType.Error); } } // Using ApplyRevertGUI requires calling Update and ApplyModifiedProperties around the serializedObject, // and will print warning messages otherwise (see warning message in ApplyRevertGUI implementation). serializedObject.ApplyModifiedProperties(); ApplyRevertGUI(); }
public static string GenerateWrapperCode(InputActionAsset asset, Options options = new Options()) { if (asset == null) { throw new ArgumentNullException(nameof(asset)); } if (string.IsNullOrEmpty(options.sourceAssetPath)) { options.sourceAssetPath = AssetDatabase.GetAssetPath(asset); } if (string.IsNullOrEmpty(options.className) && !string.IsNullOrEmpty(asset.name)) { options.className = CSharpCodeHelpers.MakeTypeName(asset.name); } if (string.IsNullOrEmpty(options.className)) { if (string.IsNullOrEmpty(options.sourceAssetPath)) { throw new ArgumentException("options.sourceAssetPath"); } options.className = CSharpCodeHelpers.MakeTypeName(Path.GetFileNameWithoutExtension(options.sourceAssetPath)); } var writer = new Writer { buffer = new StringBuilder() }; // Header. if (!string.IsNullOrEmpty(options.sourceAssetPath)) { writer.WriteLine($"// GENERATED AUTOMATICALLY FROM '{options.sourceAssetPath}'\n"); } // Usings. writer.WriteLine("using System.Collections;"); writer.WriteLine("using System.Collections.Generic;"); writer.WriteLine("using UnityEngine.InputSystem;"); writer.WriteLine("using UnityEngine.InputSystem.Utilities;"); writer.WriteLine(""); // Begin namespace. var haveNamespace = !string.IsNullOrEmpty(options.namespaceName); if (haveNamespace) { writer.WriteLine($"namespace {options.namespaceName}"); writer.BeginBlock(); } // Begin class. writer.WriteLine($"public class {options.className} : IInputActionCollection"); writer.BeginBlock(); writer.WriteLine($"private InputActionAsset asset;"); // Default constructor. writer.WriteLine($"public {options.className}()"); writer.BeginBlock(); writer.WriteLine($"asset = InputActionAsset.FromJson(@\"{asset.ToJson().Replace("\"", "\"\"")}\");"); var maps = asset.actionMaps; var schemes = asset.controlSchemes; foreach (var map in maps) { var mapName = CSharpCodeHelpers.MakeIdentifier(map.name); writer.WriteLine($"// {map.name}"); writer.WriteLine($"m_{mapName} = asset.FindActionMap(\"{map.name}\", throwIfNotFound: true);"); foreach (var action in map.actions) { var actionName = CSharpCodeHelpers.MakeIdentifier(action.name); writer.WriteLine($"m_{mapName}_{actionName} = m_{mapName}.FindAction(\"{action.name}\", throwIfNotFound: true);"); } } writer.EndBlock(); writer.WriteLine(); writer.WriteLine($"~{options.className}()"); writer.BeginBlock(); writer.WriteLine("UnityEngine.Object.Destroy(asset);"); writer.EndBlock(); writer.WriteLine(); writer.WriteLine("public InputBinding? bindingMask"); writer.BeginBlock(); writer.WriteLine("get => asset.bindingMask;"); writer.WriteLine("set => asset.bindingMask = value;"); writer.EndBlock(); writer.WriteLine(); writer.WriteLine("public ReadOnlyArray<InputDevice>? devices"); writer.BeginBlock(); writer.WriteLine("get => asset.devices;"); writer.WriteLine("set => asset.devices = value;"); writer.EndBlock(); writer.WriteLine(); writer.WriteLine("public ReadOnlyArray<InputControlScheme> controlSchemes => asset.controlSchemes;"); writer.WriteLine(); writer.WriteLine("public bool Contains(InputAction action)"); writer.BeginBlock(); writer.WriteLine("return asset.Contains(action);"); writer.EndBlock(); writer.WriteLine(); writer.WriteLine("public IEnumerator<InputAction> GetEnumerator()"); writer.BeginBlock(); writer.WriteLine("return asset.GetEnumerator();"); writer.EndBlock(); writer.WriteLine(); writer.WriteLine("IEnumerator IEnumerable.GetEnumerator()"); writer.BeginBlock(); writer.WriteLine("return GetEnumerator();"); writer.EndBlock(); writer.WriteLine(); writer.WriteLine("public void Enable()"); writer.BeginBlock(); writer.WriteLine("asset.Enable();"); writer.EndBlock(); writer.WriteLine(); writer.WriteLine("public void Disable()"); writer.BeginBlock(); writer.WriteLine("asset.Disable();"); writer.EndBlock(); // Action map accessors. foreach (var map in maps) { writer.WriteLine(); writer.WriteLine($"// {map.name}"); var mapName = CSharpCodeHelpers.MakeIdentifier(map.name); var mapTypeName = CSharpCodeHelpers.MakeTypeName(mapName, "Actions"); // Caching field for action map. writer.WriteLine($"private readonly InputActionMap m_{mapName};"); writer.WriteLine(string.Format("private I{0} m_{0}CallbackInterface;", mapTypeName)); // Caching fields for all actions. foreach (var action in map.actions) { var actionName = CSharpCodeHelpers.MakeIdentifier(action.name); writer.WriteLine($"private readonly InputAction m_{mapName}_{actionName};"); } // Struct wrapping access to action set. writer.WriteLine($"public struct {mapTypeName}"); writer.BeginBlock(); // Constructor. writer.WriteLine($"private {options.className} m_Wrapper;"); writer.WriteLine($"public {mapTypeName}({options.className} wrapper) {{ m_Wrapper = wrapper; }}"); // Getter for each action. foreach (var action in map.actions) { var actionName = CSharpCodeHelpers.MakeIdentifier(action.name); writer.WriteLine( $"public InputAction @{actionName} => m_Wrapper.m_{mapName}_{actionName};"); } // Action map getter. writer.WriteLine($"public InputActionMap Get() {{ return m_Wrapper.m_{mapName}; }}"); // Enable/disable methods. writer.WriteLine("public void Enable() { Get().Enable(); }"); writer.WriteLine("public void Disable() { Get().Disable(); }"); writer.WriteLine("public bool enabled => Get().enabled;"); // Implicit conversion operator. writer.WriteLine( $"public static implicit operator InputActionMap({mapTypeName} set) {{ return set.Get(); }}"); // SetCallbacks method. writer.WriteLine($"public void SetCallbacks(I{mapTypeName} instance)"); writer.BeginBlock(); ////REVIEW: this would benefit from having a single callback on InputActions rather than three different endpoints // Uninitialize existing interface. writer.WriteLine($"if (m_Wrapper.m_{mapTypeName}CallbackInterface != null)"); writer.BeginBlock(); foreach (var action in map.actions) { var actionName = CSharpCodeHelpers.MakeIdentifier(action.name); var actionTypeName = CSharpCodeHelpers.MakeTypeName(action.name); writer.WriteLine($"{actionName}.started -= m_Wrapper.m_{mapTypeName}CallbackInterface.On{actionTypeName};"); writer.WriteLine($"{actionName}.performed -= m_Wrapper.m_{mapTypeName}CallbackInterface.On{actionTypeName};"); writer.WriteLine($"{actionName}.canceled -= m_Wrapper.m_{mapTypeName}CallbackInterface.On{actionTypeName};"); } writer.EndBlock(); // Initialize new interface. writer.WriteLine($"m_Wrapper.m_{mapTypeName}CallbackInterface = instance;"); writer.WriteLine("if (instance != null)"); writer.BeginBlock(); foreach (var action in map.actions) { var actionName = CSharpCodeHelpers.MakeIdentifier(action.name); var actionTypeName = CSharpCodeHelpers.MakeTypeName(action.name); writer.WriteLine($"{actionName}.started += instance.On{actionTypeName};"); writer.WriteLine($"{actionName}.performed += instance.On{actionTypeName};"); writer.WriteLine($"{actionName}.canceled += instance.On{actionTypeName};"); } writer.EndBlock(); writer.EndBlock(); writer.EndBlock(); // Getter for instance of struct. writer.WriteLine($"public {mapTypeName} @{mapName} => new {mapTypeName}(this);"); } // Control scheme accessors. foreach (var scheme in schemes) { var identifier = CSharpCodeHelpers.MakeIdentifier(scheme.name); writer.WriteLine($"private int m_{identifier}SchemeIndex = -1;"); writer.WriteLine($"public InputControlScheme {identifier}Scheme"); writer.BeginBlock(); writer.WriteLine("get"); writer.BeginBlock(); writer.WriteLine($"if (m_{identifier}SchemeIndex == -1) m_{identifier}SchemeIndex = asset.FindControlSchemeIndex(\"{scheme.name}\");"); writer.WriteLine($"return asset.controlSchemes[m_{identifier}SchemeIndex];"); writer.EndBlock(); writer.EndBlock(); } // Generate interfaces. foreach (var map in maps) { var typeName = CSharpCodeHelpers.MakeTypeName(map.name); writer.WriteLine($"public interface I{typeName}Actions"); writer.BeginBlock(); foreach (var action in map.actions) { var methodName = CSharpCodeHelpers.MakeTypeName(action.name); writer.WriteLine($"void On{methodName}(InputAction.CallbackContext context);"); } writer.EndBlock(); } // End class. writer.EndBlock(); // End namespace. if (haveNamespace) { writer.EndBlock(); } return(writer.buffer.ToString()); }
private void OnNotificationBehaviorChange() { Debug.Assert(m_ActionAssetInitialized); serializedObject.ApplyModifiedProperties(); var notificationBehavior = (PlayerNotifications)m_NotificationBehaviorProperty.intValue; switch (notificationBehavior) { // Create text that lists all the messages sent by the component. case PlayerNotifications.BroadcastMessages: case PlayerNotifications.SendMessages: { var builder = new StringBuilder(); builder.Append("Will "); if (notificationBehavior == PlayerNotifications.BroadcastMessages) { builder.Append("BroadcastMessage()"); } else { builder.Append("SendMessage()"); } builder.Append(" to GameObject: "); builder.Append(PlayerInput.DeviceLostMessage); builder.Append(", "); builder.Append(PlayerInput.DeviceRegainedMessage); builder.Append(", "); builder.Append(PlayerInput.ControlsChangedMessage); var playerInput = (PlayerInput)target; var asset = playerInput.m_Actions; if (asset != null) { foreach (var action in asset) { builder.Append(", On"); builder.Append(CSharpCodeHelpers.MakeTypeName(action.name)); } } m_SendMessagesHelpText = new GUIContent(builder.ToString()); break; } case PlayerNotifications.InvokeUnityEvents: { var playerInput = (PlayerInput)target; if (playerInput.m_DeviceLostEvent == null) { playerInput.m_DeviceLostEvent = new PlayerInput.DeviceLostEvent(); } if (playerInput.m_DeviceRegainedEvent == null) { playerInput.m_DeviceRegainedEvent = new PlayerInput.DeviceRegainedEvent(); } if (playerInput.m_ControlsChangedEvent == null) { playerInput.m_ControlsChangedEvent = new PlayerInput.ControlsChangedEvent(); } serializedObject.Update(); // Force action refresh. m_ActionAssetInitialized = false; Refresh(); break; } } m_NotificationBehaviorInitialized = true; }
/// <summary> /// Generate C# code that for the given device layout called <paramref name="layoutName"/> instantly creates /// an <see cref="InputDevice"/> equivalent to what the input system would create by manually interpreting /// the given <see cref="InputControlLayout"/>. /// </summary> /// <param name="layoutName">Name of the device layout to generate code for.</param> /// <param name="defines">Null/empty or a valid expression for an #if conditional compilation statement.</param> /// <param name="namePrefix">Prefix to prepend to the type name of <paramref name="layoutName"/>.</param> /// <param name="visibility">C# access modifier to use with the generated class.</param> /// <param name="namespace">Namespace to put the generated class in. If <c>null</c>, namespace of type behind <paramref name="layoutName"/> will be used.</param> /// <returns>C# source code for a precompiled version of the device layout.</returns> /// <remarks> /// The code generated by this method will be many times faster than the reflection-based <see cref="InputDevice"/> /// creation normally performed by the input system. It will also create less GC heap garbage. /// /// The downside to the generated code is that the makeup of the device is hardcoded and can no longer /// be changed by altering the <see cref="InputControlLayout"/> setup of the system. /// /// Note that it is possible to use this method with layouts generated on-the-fly by layout builders such as /// the one employed for <see cref="HID"/>. However, this must be done at compile/build time and can thus not /// be done for devices dynamically discovered at runtime. When this is acceptable, it is a way to dramatically /// speed up the creation of these devices. /// </remarks> /// <seealso cref="InputSystem.RegisterPrecompiledLayout{T}"/> public static string GenerateCodeForDeviceLayout(string layoutName, string defines = null, string namePrefix = "Fast", string visibility = "public", string @namespace = null) { if (string.IsNullOrEmpty(layoutName)) { throw new ArgumentNullException(nameof(layoutName)); } // Produce a device from the layout. var device = InputDevice.Build <InputDevice>(layoutName, noPrecompiledLayouts: true); // Get info about base type. var baseType = device.GetType(); var baseTypeName = baseType.Name; var baseTypeNamespace = baseType.Namespace; // Begin generating code. var writer = new InputActionCodeGenerator.Writer { buffer = new StringBuilder() }; writer.WriteLine(CSharpCodeHelpers.MakeAutoGeneratedCodeHeader("com.unity.inputsystem:InputLayoutCodeGenerator", InputSystem.version.ToString(), $"\"{layoutName}\" layout")); // Defines. if (defines != null) { writer.WriteLine($"#if {defines}"); writer.WriteLine(); } if (@namespace == null) { @namespace = baseTypeNamespace; } writer.WriteLine("using UnityEngine.InputSystem;"); writer.WriteLine("using UnityEngine.InputSystem.LowLevel;"); writer.WriteLine("using UnityEngine.InputSystem.Utilities;"); writer.WriteLine(""); if (@namespace != "") { writer.WriteLine("namespace " + @namespace); } writer.BeginBlock(); writer.WriteLine($"{visibility} partial class {namePrefix}{baseTypeName} : {baseTypeNamespace}.{baseTypeName}"); writer.BeginBlock(); // "Metadata". ATM this is simply a flat, semicolon-separated list of names for layouts and processors that // we depend on. If any of them are touched, the precompiled layout should be considered invalidated. var internedLayoutName = new InternedString(layoutName); var allControls = device.allControls; var usedControlLayouts = allControls.Select(x => x.m_Layout).Distinct().ToList(); var layoutDependencies = string.Join(";", usedControlLayouts.SelectMany(l => InputControlLayout.s_Layouts.GetBaseLayouts(l)) .Union(InputControlLayout.s_Layouts.GetBaseLayouts(internedLayoutName))); var processorDependencies = string.Join(";", allControls.SelectMany(c => c.GetProcessors()).Select(p => InputProcessor.s_Processors.FindNameForType(p.GetType())) .Where(n => !n.IsEmpty()).Distinct()); var metadata = string.Join(";", processorDependencies, layoutDependencies); writer.WriteLine($"public const string metadata = \"{metadata}\";"); // Constructor. writer.WriteLine($"public {namePrefix}{baseTypeName}()"); writer.BeginBlock(); var usagesForEachControl = device.m_UsagesForEachControl; var usageToControl = device.m_UsageToControl; var aliasesForEachControl = device.m_AliasesForEachControl; var controlCount = allControls.Count; var usageCount = usagesForEachControl?.Length ?? 0; var aliasCount = aliasesForEachControl?.Length ?? 0; // Set up device control info. writer.WriteLine($"var builder = this.Setup({controlCount}, {usageCount}, {aliasCount})"); writer.WriteLine($" .WithName(\"{device.name}\")"); writer.WriteLine($" .WithDisplayName(\"{device.displayName}\")"); writer.WriteLine($" .WithChildren({device.m_ChildStartIndex}, {device.m_ChildCount})"); writer.WriteLine($" .WithLayout(new InternedString(\"{device.layout}\"))"); if (device.noisy) { writer.WriteLine(" .IsNoisy(true)"); } writer.WriteLine($" .WithStateBlock(new InputStateBlock {{ format = new FourCC({(int)device.stateBlock.format}), sizeInBits = {device.stateBlock.sizeInBits} }});"); // Add controls to device. writer.WriteLine(); foreach (var layout in usedControlLayouts) { writer.WriteLine($"var k{layout}Layout = new InternedString(\"{layout}\");"); } for (var i = 0; i < controlCount; ++i) { var control = allControls[i]; var controlType = control.GetType(); var controlVariableName = MakeControlVariableName(control); var controlFieldInits = control.GetInitializersForPublicPrimitiveTypeFields(); writer.WriteLine(""); writer.WriteLine($"// {control.path}"); writer.WriteLine($"var {controlVariableName} = new {controlType.FullName.Replace('+', '.')}{controlFieldInits};"); writer.WriteLine($"{controlVariableName}.Setup()"); writer.WriteLine($" .At(this, {i})"); writer.WriteLine(control.parent == device ? " .WithParent(this)" : $" .WithParent({MakeControlVariableName(control.parent)})"); if (control.children.Count > 0) { writer.WriteLine($" .WithChildren({control.m_ChildStartIndex}, {control.m_ChildCount})"); } writer.WriteLine($" .WithName(\"{control.name}\")"); writer.WriteLine($" .WithDisplayName(\"{control.m_DisplayNameFromLayout.Replace("\\", "\\\\")}\")"); if (!string.IsNullOrEmpty(control.m_ShortDisplayNameFromLayout)) { writer.WriteLine($" .WithShortDisplayName(\"{control.m_ShortDisplayNameFromLayout.Replace("\\", "\\\\")}\")"); } writer.WriteLine($" .WithLayout(k{control.layout}Layout)"); if (control.usages.Count > 0) { writer.WriteLine($" .WithUsages({control.m_UsageStartIndex}, {control.m_UsageCount})"); } if (control.aliases.Count > 0) { writer.WriteLine($" .WithAliases({control.m_AliasStartIndex}, {control.m_AliasCount})"); } if (control.noisy) { writer.WriteLine(" .IsNoisy(true)"); } if (control.synthetic) { writer.WriteLine(" .IsSynthetic(true)"); } writer.WriteLine(" .WithStateBlock(new InputStateBlock"); writer.WriteLine(" {"); writer.WriteLine($" format = new FourCC({(int)control.stateBlock.format}),"); writer.WriteLine($" byteOffset = {control.stateBlock.byteOffset},"); writer.WriteLine($" bitOffset = {control.stateBlock.bitOffset},"); writer.WriteLine($" sizeInBits = {control.stateBlock.sizeInBits}"); writer.WriteLine(" })"); if (control.hasDefaultState) { writer.WriteLine($" .WithDefaultState({control.m_DefaultState})"); } if (control.m_MinValue != default || control.m_MaxValue != default) { writer.WriteLine($" .WithMinAndMax({control.m_MinValue}, {control.m_MaxValue})"); } foreach (var processor in control.GetProcessors()) { var isEditorWindowSpaceProcessor = processor is EditorWindowSpaceProcessor; if (isEditorWindowSpaceProcessor) { writer.WriteLine(" #if UNITY_EDITOR"); } var processorType = processor.GetType().FullName.Replace("+", "."); var valueType = InputProcessor.GetValueTypeFromType(processor.GetType()); var fieldInits = processor.GetInitializersForPublicPrimitiveTypeFields(); writer.WriteLine($" .WithProcessor<InputProcessor<{valueType}>, {valueType}>(new {processorType}{fieldInits})"); if (isEditorWindowSpaceProcessor) { writer.WriteLine(" #endif"); } } writer.WriteLine(" .Finish();"); if (control is KeyControl key) { writer.WriteLine($"{controlVariableName}.keyCode = UnityEngine.InputSystem.Key.{key.keyCode};"); } else if (control is DpadControl.DpadAxisControl dpadAxis) { writer.WriteLine($"{controlVariableName}.component = {dpadAxis.component};"); } } // Initialize usages array. if (usageCount > 0) { writer.WriteLine(); writer.WriteLine("// Usages."); for (var i = 0; i < usageCount; ++i) { writer.WriteLine( $"builder.WithControlUsage({i}, new InternedString(\"{usagesForEachControl[i]}\"), {MakeControlVariableName(usageToControl[i])});"); } } // Initialize aliases array. if (aliasCount > 0) { writer.WriteLine(); writer.WriteLine("// Aliases."); for (var i = 0; i < aliasCount; ++i) { writer.WriteLine($"builder.WithControlAlias({i}, new InternedString(\"{aliasesForEachControl[i]}\"));"); } } // Emit initializers for control getters and control arrays. This is usually what's getting set up // in FinishSetup(). We hardcode the look results here. var controlGetterProperties = new Dictionary <Type, List <PropertyInfo> >(); var controlArrayProperties = new Dictionary <Type, List <PropertyInfo> >(); writer.WriteLine(); writer.WriteLine("// Control getters/arrays."); writer.EmitControlArrayInitializers(device, "this", controlArrayProperties); writer.EmitControlGetterInitializers(device, "this", controlGetterProperties); for (var i = 0; i < controlCount; ++i) { var control = allControls[i]; var controlVariableName = MakeControlVariableName(control); writer.EmitControlArrayInitializers(control, controlVariableName, controlArrayProperties); writer.EmitControlGetterInitializers(control, controlVariableName, controlGetterProperties); } writer.WriteLine("builder.Finish();"); writer.EndBlock(); writer.EndBlock(); writer.EndBlock(); if (defines != null) { writer.WriteLine($"#endif // {defines}"); } return(writer.buffer.ToString()); }
// Generate a string containing C# code that simplifies working with the given // action sets in code. public static string GenerateWrapperCode(IEnumerable <InputActionMap> maps, IEnumerable <InputControlScheme> schemes, Options options) { if (string.IsNullOrEmpty(options.className)) { if (string.IsNullOrEmpty(options.sourceAssetPath)) { throw new ArgumentException("options.sourceAssetPath"); } options.className = CSharpCodeHelpers.MakeTypeName(Path.GetFileNameWithoutExtension(options.sourceAssetPath)); } var writer = new Writer { buffer = new StringBuilder() }; // Header. if (!string.IsNullOrEmpty(options.sourceAssetPath)) { writer.WriteLine($"// GENERATED AUTOMATICALLY FROM '{options.sourceAssetPath}'\n"); } // Usings. writer.WriteLine("using System;"); writer.WriteLine("using UnityEngine;"); if (options.generateEvents) { writer.WriteLine("using UnityEngine.Events;"); } writer.WriteLine("using UnityEngine.Experimental.Input;"); writer.WriteLine("\n"); // Begin namespace. var haveNamespace = !string.IsNullOrEmpty(options.namespaceName); if (haveNamespace) { writer.WriteLine($"namespace {options.namespaceName}"); writer.BeginBlock(); } // Begin class. writer.WriteLine("[Serializable]"); writer.WriteLine($"public class {options.className} : InputActionAssetReference"); writer.BeginBlock(); // Default constructor. writer.WriteLine($"public {options.className}()"); writer.BeginBlock(); writer.EndBlock(); // Explicit constructor. writer.WriteLine($"public {options.className}(InputActionAsset asset)"); ++writer.indentLevel; writer.WriteLine(": base(asset)"); --writer.indentLevel; writer.BeginBlock(); writer.EndBlock(); // Initialize method. writer.WriteLine("[NonSerialized] private bool m_Initialized;"); writer.WriteLine("private void Initialize()"); writer.BeginBlock(); foreach (var set in maps) { var setName = CSharpCodeHelpers.MakeIdentifier(set.name); writer.WriteLine($"// {set.name}"); writer.WriteLine($"m_{setName} = asset.GetActionMap(\"{set.name}\");"); foreach (var action in set.actions) { var actionName = CSharpCodeHelpers.MakeIdentifier(action.name); writer.WriteLine($"m_{setName}_{actionName} = m_{setName}.GetAction(\"{action.name}\");"); if (options.generateEvents) { WriteActionEventInitializer(setName, actionName, InputActionPhase.Started, writer); WriteActionEventInitializer(setName, actionName, InputActionPhase.Performed, writer); WriteActionEventInitializer(setName, actionName, InputActionPhase.Cancelled, writer); } } } writer.WriteLine("m_Initialized = true;"); writer.EndBlock(); // Uninitialize method. writer.WriteLine("private void Uninitialize()"); writer.BeginBlock(); foreach (var map in maps) { var mapName = CSharpCodeHelpers.MakeIdentifier(map.name); if (options.generateInterfaces) { var mapTypeName = CSharpCodeHelpers.MakeTypeName(map.name, "Actions"); writer.WriteLine($"if (m_{mapTypeName}CallbackInterface != null)"); writer.BeginBlock(); writer.WriteLine($"{mapName}.SetCallbacks(null);"); writer.EndBlock(); } writer.WriteLine($"m_{mapName} = null;"); foreach (var action in map.actions) { var actionName = CSharpCodeHelpers.MakeIdentifier(action.name); writer.WriteLine($"m_{mapName}_{actionName} = null;"); if (options.generateEvents) { WriteActionEventInitializer(mapName, actionName, InputActionPhase.Started, writer, removeCallback: true); WriteActionEventInitializer(mapName, actionName, InputActionPhase.Performed, writer, removeCallback: true); WriteActionEventInitializer(mapName, actionName, InputActionPhase.Cancelled, writer, removeCallback: true); } } } writer.WriteLine("m_Initialized = false;"); writer.EndBlock(); // SwitchAsset method. writer.WriteLine("public void SetAsset(InputActionAsset newAsset)"); writer.BeginBlock(); writer.WriteLine("if (newAsset == asset) return;"); if (options.generateInterfaces) { foreach (var map in maps) { var mapName = CSharpCodeHelpers.MakeIdentifier(map.name); var mapTypeName = CSharpCodeHelpers.MakeTypeName(map.name, "Actions"); writer.WriteLine($"var {mapName}Callbacks = m_{mapTypeName}CallbackInterface;"); } } writer.WriteLine("if (m_Initialized) Uninitialize();"); writer.WriteLine("asset = newAsset;"); if (options.generateInterfaces) { foreach (var map in maps) { var mapName = CSharpCodeHelpers.MakeIdentifier(map.name); writer.WriteLine(string.Format("{0}.SetCallbacks({0}Callbacks);", mapName)); } } writer.EndBlock(); // MakePrivateCopyOfActions method. writer.WriteLine("public override void MakePrivateCopyOfActions()"); writer.BeginBlock(); writer.WriteLine("SetAsset(ScriptableObject.Instantiate(asset));"); writer.EndBlock(); // Action map accessors. foreach (var map in maps) { writer.WriteLine($"// {map.name}"); var mapName = CSharpCodeHelpers.MakeIdentifier(map.name); var mapTypeName = CSharpCodeHelpers.MakeTypeName(mapName, "Actions"); // Caching field for action map. writer.WriteLine($"private InputActionMap m_{mapName};"); if (options.generateInterfaces) { writer.WriteLine(string.Format("private I{0} m_{0}CallbackInterface;", mapTypeName)); } // Caching fields for all actions. foreach (var action in map.actions) { var actionName = CSharpCodeHelpers.MakeIdentifier(action.name); writer.WriteLine($"private InputAction m_{mapName}_{actionName};"); if (options.generateEvents) { WriteActionEventField(mapName, actionName, InputActionPhase.Started, writer); WriteActionEventField(mapName, actionName, InputActionPhase.Performed, writer); WriteActionEventField(mapName, actionName, InputActionPhase.Cancelled, writer); } } // Struct wrapping access to action set. writer.WriteLine($"public struct {mapTypeName}"); writer.BeginBlock(); // Constructor. writer.WriteLine($"private {options.className} m_Wrapper;"); writer.WriteLine($"public {mapTypeName}({options.className} wrapper) {{ m_Wrapper = wrapper; }}"); // Getter for each action. foreach (var action in map.actions) { var actionName = CSharpCodeHelpers.MakeIdentifier(action.name); writer.WriteLine( $"public InputAction @{actionName} {{ get {{ return m_Wrapper.m_{mapName}_{actionName}; }} }}"); // Action event getters. if (options.generateEvents) { WriteActionEventGetter(mapName, actionName, InputActionPhase.Started, writer); WriteActionEventGetter(mapName, actionName, InputActionPhase.Performed, writer); WriteActionEventGetter(mapName, actionName, InputActionPhase.Cancelled, writer); } } // Action map getter. writer.WriteLine($"public InputActionMap Get() {{ return m_Wrapper.m_{mapName}; }}"); // Enable/disable methods. writer.WriteLine("public void Enable() { Get().Enable(); }"); writer.WriteLine("public void Disable() { Get().Disable(); }"); writer.WriteLine("public bool enabled { get { return Get().enabled; } }"); // Clone method. writer.WriteLine("public InputActionMap Clone() { return Get().Clone(); }"); // Implicit conversion operator. writer.WriteLine( $"public static implicit operator InputActionMap({mapTypeName} set) {{ return set.Get(); }}"); // SetCallbacks method. if (options.generateInterfaces) { writer.WriteLine($"public void SetCallbacks(I{mapTypeName} instance)"); writer.BeginBlock(); ////REVIEW: this would benefit from having a single callback on InputActions rather than three different endpoints // Uninitialize existing interface. writer.WriteLine($"if (m_Wrapper.m_{mapTypeName}CallbackInterface != null)"); writer.BeginBlock(); foreach (var action in map.actions) { var actionName = CSharpCodeHelpers.MakeIdentifier(action.name); var actionTypeName = CSharpCodeHelpers.MakeTypeName(action.name); writer.WriteLine($"{actionName}.started -= m_Wrapper.m_{mapTypeName}CallbackInterface.On{actionTypeName};"); writer.WriteLine($"{actionName}.performed -= m_Wrapper.m_{mapTypeName}CallbackInterface.On{actionTypeName};"); writer.WriteLine($"{actionName}.cancelled -= m_Wrapper.m_{mapTypeName}CallbackInterface.On{actionTypeName};"); } writer.EndBlock(); // Initialize new interface. writer.WriteLine($"m_Wrapper.m_{mapTypeName}CallbackInterface = instance;"); writer.WriteLine("if (instance != null)"); writer.BeginBlock(); foreach (var action in map.actions) { var actionName = CSharpCodeHelpers.MakeIdentifier(action.name); var actionTypeName = CSharpCodeHelpers.MakeTypeName(action.name); writer.WriteLine($"{actionName}.started += instance.On{actionTypeName};"); writer.WriteLine($"{actionName}.performed += instance.On{actionTypeName};"); writer.WriteLine($"{actionName}.cancelled += instance.On{actionTypeName};"); } writer.EndBlock(); writer.EndBlock(); } writer.EndBlock(); // Getter for instance of struct. writer.WriteLine($"public {mapTypeName} @{mapName}"); writer.BeginBlock(); writer.WriteLine("get"); writer.BeginBlock(); writer.WriteLine("if (!m_Initialized) Initialize();"); writer.WriteLine($"return new {mapTypeName}(this);"); writer.EndBlock(); writer.EndBlock(); } // Control scheme accessors. foreach (var scheme in schemes) { var identifier = CSharpCodeHelpers.MakeIdentifier(scheme.name); writer.WriteLine($"private int m_{identifier}SchemeIndex = -1;"); writer.WriteLine($"public InputControlScheme {identifier}Scheme"); writer.BeginBlock(); writer.WriteLine("get\n"); writer.BeginBlock(); writer.WriteLine($"if (m_{identifier}SchemeIndex == -1) m_{identifier}SchemeIndex = asset.GetControlSchemeIndex(\"{scheme.name}\");"); writer.WriteLine($"return asset.controlSchemes[m_{identifier}SchemeIndex];"); writer.EndBlock(); writer.EndBlock(); } // Action event class. if (options.generateEvents) { writer.WriteLine("[Serializable]"); writer.WriteLine("public class ActionEvent : UnityEvent<InputAction.CallbackContext>"); writer.BeginBlock(); writer.EndBlock(); } // End class. writer.EndBlock(); // Generate interfaces. if (options.generateInterfaces) { foreach (var map in maps) { var typeName = CSharpCodeHelpers.MakeTypeName(map.name); writer.WriteLine($"public interface I{typeName}Actions"); writer.BeginBlock(); foreach (var action in map.actions) { var methodName = CSharpCodeHelpers.MakeTypeName(action.name); writer.WriteLine($"void On{methodName}(InputAction.CallbackContext context);"); } writer.EndBlock(); } } // End namespace. if (haveNamespace) { writer.EndBlock(); } return(writer.buffer.ToString()); }
/// <summary> /// Generate C# code for an <see cref="InputDevice"/> derived class that exposes the controls /// for the actions found in the given Steam IGA description. /// </summary> /// <param name="vdf"></param> /// <param name="namespaceAndClassName"></param> /// <returns></returns> public static string GenerateInputDeviceFromSteamIGA(string vdf, string namespaceAndClassName) { if (string.IsNullOrEmpty(vdf)) { throw new ArgumentNullException("vdf"); } if (string.IsNullOrEmpty(namespaceAndClassName)) { throw new ArgumentNullException("namespaceAndClassName"); } // Parse VDF. var parsedVdf = ParseVDF(vdf); var actions = (Dictionary <string, object>)((Dictionary <string, object>)parsedVdf["In Game Actions"])["actions"]; // Determine class and namespace name. var namespaceName = ""; var className = ""; var indexOfLastDot = namespaceAndClassName.LastIndexOf('.'); if (indexOfLastDot != -1) { namespaceName = namespaceAndClassName.Substring(0, indexOfLastDot); className = namespaceAndClassName.Substring(indexOfLastDot + 1); } else { className = namespaceAndClassName; } var stateStructName = className + "State"; var builder = new StringBuilder(); builder.Append("// THIS FILE HAS BEEN AUTO-GENERATED\n"); builder.Append("#if (UNITY_EDITOR || UNITY_STANDALONE) && UNITY_ENABLE_STEAM_CONTROLLER_SUPPORT\n"); builder.Append("using UnityEngine;\n"); builder.Append("using UnityEngine.Experimental.Input;\n"); builder.Append("using UnityEngine.Experimental.Input.Controls;\n"); builder.Append("using UnityEngine.Experimental.Input.Utilities;\n"); builder.Append("using UnityEngine.Experimental.Input.Plugins.Steam;\n"); builder.Append("#if UNITY_EDITOR\n"); builder.Append("using UnityEditor;\n"); builder.Append("#endif\n"); builder.Append("\n"); if (!string.IsNullOrEmpty(namespaceName)) { builder.Append("namespace "); builder.Append(namespaceName); builder.Append("\n{\n"); } // InitializeOnLoad attribute. builder.Append("#if UNITY_EDITOR\n"); builder.Append("[InitializeOnLoad]\n"); builder.Append("#endif\n"); // Control layout attribute. builder.Append("[InputControlLayout(stateType = typeof("); builder.Append(stateStructName); builder.Append("))]\n"); // Class declaration. builder.Append("public class "); builder.Append(className); builder.Append(" : SteamController, IInputUpdateCallbackReceiver\n{\n"); // Device matcher. builder.Append(" private static InputDeviceMatcher deviceMatcher\n"); builder.Append(" {\n"); builder.Append(" get { return new InputDeviceMatcher().WithInterface(\"Steam\").WithProduct(\""); builder.Append(className); builder.Append("\"); }\n"); builder.Append(" }\n"); // Static constructor. builder.Append("#if UNITY_EDITOR\n"); builder.Append(" static "); builder.Append(className); builder.Append("()\n"); builder.Append(" {\n"); builder.Append(" InputSystem.RegisterLayout<"); builder.Append(className); builder.Append(">(matches: deviceMatcher);\n"); builder.Append(" }\n"); builder.Append("#endif\n"); // Update method. builder.Append(" public void OnUpdate(InputUpdateType updateType)\n"); builder.Append(" {\n"); builder.Append(" ////TODO\n"); builder.Append(" }\n"); // RuntimeInitializeOnLoadMethod. // NOTE: Not relying on static ctor here. See il2cpp bug 1014293. builder.Append(" [RuntimeInitializeOnLoadMethod(loadType: RuntimeInitializeLoadType.BeforeSceneLoad)]\n"); builder.Append(" private static void RuntimeInitializeOnLoad()\n"); builder.Append(" {\n"); builder.Append(" InputSystem.RegisterLayout<"); builder.Append(className); builder.Append(">(matches: deviceMatcher);\n"); builder.Append(" }\n"); // Control properties. foreach (var setEntry in actions) { var setEntryProperties = (Dictionary <string, object>)setEntry.Value; // StickPadGyros. var stickPadGyros = (Dictionary <string, object>)setEntryProperties["StickPadGyro"]; foreach (var entry in stickPadGyros) { var entryProperties = (Dictionary <string, object>)entry.Value; var isStick = entryProperties.ContainsKey("input_mode") && (string)entryProperties["input_mode"] == "joystick_move"; builder.Append(string.Format(" public {0} {1} {{ get; protected set; }}\n", isStick ? "StickControl" : "Vector2Control", CSharpCodeHelpers.MakeIdentifier(entry.Key))); } // Buttons. var buttons = (Dictionary <string, object>)setEntryProperties["Button"]; foreach (var entry in buttons) { builder.Append(string.Format(" public ButtonControl {0} {{ get; protected set; }}\n", CSharpCodeHelpers.MakeIdentifier(entry.Key))); } // AnalogTriggers. var analogTriggers = (Dictionary <string, object>)setEntryProperties["AnalogTrigger"]; foreach (var entry in analogTriggers) { builder.Append(string.Format(" public AxisControl {0} {{ get; protected set; }}\n", CSharpCodeHelpers.MakeIdentifier(entry.Key))); } } // FinishSetup method. builder.Append(" protected override void FinishSetup(InputDeviceBuilder builder)\n"); builder.Append(" {\n"); builder.Append(" base.FinishSetup(builder);\n"); foreach (var setEntry in actions) { var setEntryProperties = (Dictionary <string, object>)setEntry.Value; // StickPadGyros. var stickPadGyros = (Dictionary <string, object>)setEntryProperties["StickPadGyro"]; foreach (var entry in stickPadGyros) { var entryProperties = (Dictionary <string, object>)entry.Value; var isStick = entryProperties.ContainsKey("input_mode") && (string)entryProperties["input_mode"] == "joystick_move"; builder.Append(string.Format(" {0} = builder.GetControl<{1}>(\"{2}\");\n", CSharpCodeHelpers.MakeIdentifier(entry.Key), isStick ? "StickControl" : "Vector2Control", entry.Key)); } // Buttons. var buttons = (Dictionary <string, object>)setEntryProperties["Button"]; foreach (var entry in buttons) { builder.Append(string.Format(" {0} = builder.GetControl<ButtonControl>(\"{1}\");\n", CSharpCodeHelpers.MakeIdentifier(entry.Key), entry.Key)); } // AnalogTriggers. var analogTriggers = (Dictionary <string, object>)setEntryProperties["AnalogTrigger"]; foreach (var entry in analogTriggers) { builder.Append(string.Format(" {0} = builder.GetControl<AxisControl>(\"{1}\");\n", CSharpCodeHelpers.MakeIdentifier(entry.Key), entry.Key)); } } builder.Append(" }\n"); builder.Append("}\n"); if (!string.IsNullOrEmpty(namespaceName)) { builder.Append("}\n"); } // State struct. builder.Append("public unsafe struct "); builder.Append(stateStructName); builder.Append(" : IInputStateTypeInfo\n"); builder.Append("{\n"); builder.Append(" public FourCC GetFormat()\n"); builder.Append(" {\n"); ////TODO: handle class names that are shorter than 4 characters ////TODO: uppercase characters builder.Append(string.Format(" return new FourCC('{0}', '{1}', '{2}', '{3}');\n", className[0], className[1], className[2], className[3])); builder.Append(" }\n"); builder.Append("\n"); foreach (var setEntry in actions) { var setEntryProperties = (Dictionary <string, object>)setEntry.Value; // StickPadGyros. var stickPadGyros = (Dictionary <string, object>)setEntryProperties["StickPadGyro"]; foreach (var entry in stickPadGyros) { var entryProperties = (Dictionary <string, object>)entry.Value; var isStick = entryProperties.ContainsKey("input_mode") && (string)entryProperties["input_mode"] == "joystick_move"; builder.Append(string.Format(" [InputControl(name = \"{0}\", layout = \"{1}\")]\n", entry.Key, isStick ? "Stick" : "Vector2")); builder.Append(string.Format(" public Vector2 {0};\n", CSharpCodeHelpers.MakeIdentifier(entry.Key))); } // Buttons. var buttons = (Dictionary <string, object>)setEntryProperties["Button"]; var buttonCount = buttons.Count; if (buttonCount > 0) { var bit = 0; foreach (var entry in buttons) { builder.Append(string.Format( " [InputControl(name = \"{0}\", layout = \"Button\", bit = {1})]\n", entry.Key, bit)); ++bit; } var byteCount = (buttonCount + 7) / 8; builder.Append(" public fixed byte buttons["); builder.Append(byteCount.ToString()); builder.Append("];\n"); } // AnalogTriggers. var analogTriggers = (Dictionary <string, object>)setEntryProperties["AnalogTrigger"]; foreach (var entry in analogTriggers) { builder.Append(string.Format(" [InputControl(name = \"{0}\", layout = \"Axis\")]\n", entry.Key)); builder.Append(string.Format(" public float {0};\n", CSharpCodeHelpers.MakeIdentifier(entry.Key))); } } builder.Append("}\n"); builder.Append("#endif\n"); return(builder.ToString()); }
/// <summary> /// Generate C# code that for the given device layout called <paramref name="layoutName"/> instantly creates /// an <see cref="InputDevice"/> equivalent to what the input system would create by manually interpreting /// the given <see cref="InputControlLayout"/>. /// </summary> /// <param name="layoutName">Name of the device layout to generate code for.</param> /// <param name="defines">Null/empty or a valid expression for an #if conditional compilation statement.</param> /// <param name="namePrefix">Prefix to prepend to the type name of <paramref name="layoutName"/>.</param> /// <param name="visibility">C# access modifier to use with the generated class.</param> /// <param name="namespace">Namespace to put the generated class in. If <c>null</c>, namespace of type behind <paramref name="layoutName"/> will be used.</param> /// <returns>C# source code for a precompiled version of the device layout.</returns> /// <remarks> /// The code generated by this method will be many times faster than the reflection-based <see cref="InputDevice"/> /// creation normally performed by the input system. It will also create less GC heap garbage. /// /// The downside to the generated code is that the makeup of the device is hardcoded and can no longer /// be changed by altering the <see cref="InputControlLayout"/> setup of the system. /// /// Note that it is possible to use this method with layouts generated on-the-fly by layout builders such as /// the one employed for <see cref="HID"/>. However, this must be done at compile/build time and can thus not /// be done for devices dynamically discovered at runtime. When this is acceptable, it is a way to dramatically /// speed up the creation of these devices. /// </remarks> /// <seealso cref="InputSystem.RegisterPrecompiledLayout{T}"/> public static string GenerateCodeForDeviceLayout(string layoutName, string defines = null, string namePrefix = "Fast", string visibility = "public", string @namespace = null) { if (string.IsNullOrEmpty(layoutName)) { throw new ArgumentNullException(nameof(layoutName)); } // Produce a device from the layout. var device = InputDevice.Build <InputDevice>(layoutName, noPrecompiledLayouts: true); // Get info about base type. var baseType = device.GetType(); var baseTypeName = baseType.Name; var baseTypeNamespace = baseType.Namespace; // Begin generating code. var writer = new InputActionCodeGenerator.Writer { buffer = new StringBuilder() }; writer.WriteLine(CSharpCodeHelpers.MakeAutoGeneratedCodeHeader("com.unity.inputsystem:InputLayoutCodeGenerator", InputSystem.version.ToString(), $"\"{layoutName}\" layout")); // Defines. if (defines != null) { writer.WriteLine($"#if {defines}"); writer.WriteLine(); } if (@namespace == null) { @namespace = baseTypeNamespace; } writer.WriteLine("using UnityEngine.InputSystem;"); writer.WriteLine("using UnityEngine.InputSystem.LowLevel;"); writer.WriteLine("using UnityEngine.InputSystem.Utilities;"); writer.WriteLine(""); writer.WriteLine("// Suppress warnings from local variables for control references"); writer.WriteLine("// that we don't end up using."); writer.WriteLine("#pragma warning disable CS0219"); writer.WriteLine(""); if (@namespace != "") { writer.WriteLine("namespace " + @namespace); } writer.BeginBlock(); writer.WriteLine($"{visibility} partial class {namePrefix}{baseTypeName} : {baseTypeNamespace}.{baseTypeName}"); writer.BeginBlock(); // "Metadata". ATM this is simply a flat, semicolon-separated list of names for layouts and processors that // we depend on. If any of them are touched, the precompiled layout should be considered invalidated. var internedLayoutName = new InternedString(layoutName); var allControls = device.allControls; var usedControlLayouts = allControls.Select(x => x.m_Layout).Distinct().ToList(); var layoutDependencies = string.Join(";", usedControlLayouts.SelectMany(l => InputControlLayout.s_Layouts.GetBaseLayouts(l)) .Union(InputControlLayout.s_Layouts.GetBaseLayouts(internedLayoutName))); var processorDependencies = string.Join(";", allControls.SelectMany(c => c.GetProcessors()).Select(p => InputProcessor.s_Processors.FindNameForType(p.GetType())) .Where(n => !n.IsEmpty()).Distinct()); var metadata = string.Join(";", processorDependencies, layoutDependencies); writer.WriteLine($"public const string metadata = \"{metadata}\";"); // Constructor. writer.WriteLine($"public {namePrefix}{baseTypeName}()"); writer.BeginBlock(); var usagesForEachControl = device.m_UsagesForEachControl; var usageToControl = device.m_UsageToControl; var aliasesForEachControl = device.m_AliasesForEachControl; var controlCount = allControls.Count; var usageCount = usagesForEachControl?.Length ?? 0; var aliasCount = aliasesForEachControl?.Length ?? 0; // Set up device control info. writer.WriteLine($"var builder = this.Setup({controlCount}, {usageCount}, {aliasCount})"); writer.WriteLine($" .WithName(\"{device.name}\")"); writer.WriteLine($" .WithDisplayName(\"{device.displayName}\")"); writer.WriteLine($" .WithChildren({device.m_ChildStartIndex}, {device.m_ChildCount})"); writer.WriteLine($" .WithLayout(new InternedString(\"{device.layout}\"))"); writer.WriteLine($" .WithStateBlock(new InputStateBlock {{ format = new FourCC({(int)device.stateBlock.format}), sizeInBits = {device.stateBlock.sizeInBits} }});"); if (device.noisy) { writer.WriteLine("builder.IsNoisy(true);"); } // Add controls to device. writer.WriteLine(); foreach (var layout in usedControlLayouts) { writer.WriteLine($"var k{layout}Layout = new InternedString(\"{layout}\");"); } for (var i = 0; i < controlCount; ++i) { var control = allControls[i]; var controlVariableName = MakeControlVariableName(control); writer.WriteLine(""); writer.WriteLine($"// {control.path}"); var parentName = "this"; if (control.parent != device) { parentName = MakeControlVariableName(control.parent); } writer.WriteLine($"var {controlVariableName} = {NameOfControlMethod(controlVariableName)}(k{control.layout}Layout, {parentName});"); } // Initialize usages array. if (usageCount > 0) { writer.WriteLine(); writer.WriteLine("// Usages."); for (var i = 0; i < usageCount; ++i) { writer.WriteLine( $"builder.WithControlUsage({i}, new InternedString(\"{usagesForEachControl[i]}\"), {MakeControlVariableName(usageToControl[i])});"); } } // Initialize aliases array. if (aliasCount > 0) { writer.WriteLine(); writer.WriteLine("// Aliases."); for (var i = 0; i < aliasCount; ++i) { writer.WriteLine($"builder.WithControlAlias({i}, new InternedString(\"{aliasesForEachControl[i]}\"));"); } } // Emit initializers for control getters and control arrays. This is usually what's getting set up // in FinishSetup(). We hardcode the look results here. var controlGetterProperties = new Dictionary <Type, List <PropertyInfo> >(); var controlArrayProperties = new Dictionary <Type, List <PropertyInfo> >(); writer.WriteLine(); writer.WriteLine("// Control getters/arrays."); writer.EmitControlArrayInitializers(device, "this", controlArrayProperties); writer.EmitControlGetterInitializers(device, "this", controlGetterProperties); for (var i = 0; i < controlCount; ++i) { var control = allControls[i]; var controlVariableName = MakeControlVariableName(control); writer.EmitControlArrayInitializers(control, controlVariableName, controlArrayProperties); writer.EmitControlGetterInitializers(control, controlVariableName, controlGetterProperties); } // State offset to control index map. if (device.m_StateOffsetToControlMap != null) { writer.WriteLine(); writer.WriteLine("// State offset to control index map."); writer.WriteLine("builder.WithStateOffsetToControlIndexMap(new uint[]"); writer.WriteLine("{"); ++writer.indentLevel; var map = device.m_StateOffsetToControlMap; var entryCount = map.Length; for (var index = 0; index < entryCount;) { if (index != 0) { writer.WriteLine(); } // 10 entries a line. writer.WriteIndent(); for (var i = 0; i < 10 && index < entryCount; ++index, ++i) { writer.Write((index != 0 ? ", " : "") + map[index] + "u"); } } writer.WriteLine(); --writer.indentLevel; writer.WriteLine("});"); } writer.WriteLine(); writer.WriteLine("builder.Finish();"); writer.EndBlock(); for (var i = 0; i < controlCount; ++i) { var control = allControls[i]; var controlType = control.GetType(); var controlVariableName = MakeControlVariableName(control); var controlFieldInits = control.GetInitializersForPublicPrimitiveTypeFields(); writer.WriteLine(); EmitControlMethod(writer, controlVariableName, controlType, controlFieldInits, i, control); } writer.EndBlock(); writer.EndBlock(); if (defines != null) { writer.WriteLine($"#endif // {defines}"); } return(writer.buffer.ToString()); }
private static string MakeControlVariableName(InputControl control) { return("ctrl" + CSharpCodeHelpers.MakeIdentifier(control.path)); }