#if UNITY_EDITOR && UNITY_ENABLE_STEAM_CONTROLLER_SUPPORT
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEngine.InputSystem.Controls;
using UnityEngine.InputSystem.Editor;
using UnityEngine.InputSystem.Utilities;
////TODO: motion data support
////TODO: haptics support
////TODO: ensure that no two actions have the same name even between maps
////TODO: also need to build a layout based on SteamController that has controls representing the current set of actions
//// (might need this in the runtime)
////TODO: localization support (allow loading existing VDF file and preserving localization strings)
////TODO: allow having actions that are ignored by Steam VDF export
////TODO: support for getting displayNames/glyphs from Steam
////TODO: polling in background
namespace UnityEngine.InputSystem.Steam.Editor
{
///
/// Converts input actions to and from Steam IGA file format.
///
///
/// The idea behind this converter is to enable users to use Unity's action editor to set up actions
/// for their game and the be able, when targeting desktops through Steam, to convert the game's actions
/// to a Steam VDF file that allows using the Steam Controller API with the game.
///
/// The generated VDF file is meant to allow editing by hand in order to add localization strings or
/// apply Steam-specific settings that cannot be inferred from Unity input actions.
///
public static class SteamIGAConverter
{
///
/// Generate C# code for an derived class that exposes the controls
/// for the actions found in the given Steam IGA description.
///
///
///
///
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1809:AvoidExcessiveLocals", Justification = "TODO: Refactor later.")]
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)((Dictionary)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.LowLevel;\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)setEntry.Value;
// StickPadGyros.
var stickPadGyros = (Dictionary)setEntryProperties["StickPadGyro"];
foreach (var entry in stickPadGyros)
{
var entryProperties = (Dictionary)entry.Value;
var isStick = entryProperties.ContainsKey("input_mode") && (string)entryProperties["input_mode"] == "joystick_move";
builder.Append(" [InputControl]\n");
builder.Append(
$" public {(isStick ? "StickControl" : "Vector2Control")} {CSharpCodeHelpers.MakeIdentifier(entry.Key)} {{ get; protected set; }}\n");
}
// Buttons.
var buttons = (Dictionary)setEntryProperties["Button"];
foreach (var entry in buttons)
{
builder.Append(" [InputControl]\n");
builder.Append(
$" public ButtonControl {CSharpCodeHelpers.MakeIdentifier(entry.Key)} {{ get; protected set; }}\n");
}
// AnalogTriggers.
var analogTriggers = (Dictionary)setEntryProperties["AnalogTrigger"];
foreach (var entry in analogTriggers)
{
builder.Append(" [InputControl]\n");
builder.Append(
$" public AxisControl {CSharpCodeHelpers.MakeIdentifier(entry.Key)} {{ get; protected set; }}\n");
}
}
// FinishSetup method.
builder.Append('\n');
builder.Append(" protected override void FinishSetup()\n");
builder.Append(" {\n");
builder.Append(" base.FinishSetup();\n");
foreach (var setEntry in actions)
{
var setEntryProperties = (Dictionary)setEntry.Value;
// StickPadGyros.
var stickPadGyros = (Dictionary)setEntryProperties["StickPadGyro"];
foreach (var entry in stickPadGyros)
{
var entryProperties = (Dictionary)entry.Value;
var isStick = entryProperties.ContainsKey("input_mode") && (string)entryProperties["input_mode"] == "joystick_move";
builder.Append(
$" {CSharpCodeHelpers.MakeIdentifier(entry.Key)} = GetChildControl<{(isStick ? "StickControl" : "Vector2Control")}>(\"{entry.Key}\");\n");
}
// Buttons.
var buttons = (Dictionary)setEntryProperties["Button"];
foreach (var entry in buttons)
{
builder.Append(
$" {CSharpCodeHelpers.MakeIdentifier(entry.Key)} = GetChildControl(\"{entry.Key}\");\n");
}
// AnalogTriggers.
var analogTriggers = (Dictionary)setEntryProperties["AnalogTrigger"];
foreach (var entry in analogTriggers)
{
builder.Append(
$" {CSharpCodeHelpers.MakeIdentifier(entry.Key)} = GetChildControl(\"{entry.Key}\");\n");
}
}
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)setEntry.Value;
// Set handle.
builder.Append(
$" {CSharpCodeHelpers.MakeIdentifier(setEntry.Key)}SetHandle = api.GetActionSetHandle(\"{setEntry.Key}\");\n");
// StickPadGyros.
var stickPadGyros = (Dictionary)setEntryProperties["StickPadGyro"];
foreach (var entry in stickPadGyros)
{
builder.Append(
$" {CSharpCodeHelpers.MakeIdentifier(entry.Key)}Handle = api.GetAnalogActionHandle(\"{entry.Key}\");\n");
}
// Buttons.
var buttons = (Dictionary)setEntryProperties["Button"];
foreach (var entry in buttons)
{
builder.Append(
$" {CSharpCodeHelpers.MakeIdentifier(entry.Key)}Handle = api.GetDigitalActionHandle(\"{entry.Key}\");\n");
}
// AnalogTriggers.
var analogTriggers = (Dictionary)setEntryProperties["AnalogTrigger"];
foreach (var entry in analogTriggers)
{
builder.Append(
$" {CSharpCodeHelpers.MakeIdentifier(entry.Key)}Handle = api.GetAnalogActionHandle(\"{entry.Key}\");\n");
}
}
builder.Append(" }\n");
// Handle cache fields.
builder.Append('\n');
foreach (var setEntry in actions)
{
var setEntryProperties = (Dictionary)setEntry.Value;
// Set handle.
builder.Append(
$" public SteamHandle {CSharpCodeHelpers.MakeIdentifier(setEntry.Key)}SetHandle {{ get; private set; }}\n");
// StickPadGyros.
var stickPadGyros = (Dictionary)setEntryProperties["StickPadGyro"];
foreach (var entry in stickPadGyros)
{
builder.Append(
$" public SteamHandle {CSharpCodeHelpers.MakeIdentifier(entry.Key)}Handle {{ get; private set; }}\n");
}
// Buttons.
var buttons = (Dictionary)setEntryProperties["Button"];
foreach (var entry in buttons)
{
builder.Append(
$" public SteamHandle {CSharpCodeHelpers.MakeIdentifier(entry.Key)}Handle {{ get; private set; }}\n");
}
// AnalogTriggers.
var analogTriggers = (Dictionary)setEntryProperties["AnalogTrigger"];
foreach (var entry in analogTriggers)
{
builder.Append(
$" public SteamHandle {CSharpCodeHelpers.MakeIdentifier(entry.Key)}Handle {{ get; private set; }}\n");
}
}
// steamActionSets property.
builder.Append('\n');
builder.Append(" private SteamActionSetInfo[] m_ActionSets;\n");
builder.Append(" public override ReadOnlyArray 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(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($" {stateStructName} state;\n");
var currentButtonBit = 0;
foreach (var setEntry in actions)
{
var setEntryProperties = (Dictionary)setEntry.Value;
// StickPadGyros.
var stickPadGyros = (Dictionary)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)setEntryProperties["Button"];
foreach (var entry in buttons)
{
builder.Append(
$" if (api.GetDigitalActionData(steamControllerHandle, {CSharpCodeHelpers.MakeIdentifier(entry.Key)}Handle).pressed)\n");
builder.Append($" state.buttons[{currentButtonBit / 8}] |= {currentButtonBit % 8};\n");
++currentButtonBit;
}
// AnalogTriggers.
var analogTriggers = (Dictionary)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(
$" return new FourCC('{className[0]}', '{className[1]}', '{className[2]}', '{className[3]}');\n");
builder.Append(" }\n");
builder.Append(" }\n");
builder.Append("\n");
var totalButtonCount = 0;
foreach (var setEntry in actions)
{
var setEntryProperties = (Dictionary)setEntry.Value;
// Buttons.
var buttons = (Dictionary)setEntryProperties["Button"];
var buttonCount = buttons.Count;
if (buttonCount > 0)
{
foreach (var entry in buttons)
{
builder.Append(
$" [InputControl(name = \"{entry.Key}\", layout = \"Button\", bit = {totalButtonCount})]\n");
++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)setEntry.Value;
// StickPadGyros.
var stickPadGyros = (Dictionary)setEntryProperties["StickPadGyro"];
foreach (var entry in stickPadGyros)
{
var entryProperties = (Dictionary)entry.Value;
var isStick = entryProperties.ContainsKey("input_mode") && (string)entryProperties["input_mode"] == "joystick_move";
builder.Append(
$" [InputControl(name = \"{entry.Key}\", layout = \"{(isStick ? "Stick" : "Vector2")}\")]\n");
builder.Append($" public Vector2 {CSharpCodeHelpers.MakeIdentifier(entry.Key)};\n");
}
// AnalogTriggers.
var analogTriggers = (Dictionary)setEntryProperties["AnalogTrigger"];
foreach (var entry in analogTriggers)
{
builder.Append($" [InputControl(name = \"{entry.Key}\", layout = \"Axis\")]\n");
builder.Append($" public float {CSharpCodeHelpers.MakeIdentifier(entry.Key)};\n");
}
}
builder.Append("}\n");
builder.Append("#endif\n");
return builder.ToString();
}
///
/// Convert an .inputactions asset to Steam VDF format.
///
///
///
/// A string in Steam VDF format describing "In Game Actions" corresponding to the actions in
/// .
/// is null.
public static string ConvertInputActionsToSteamIGA(InputActionAsset asset, string locale = "english")
{
if (asset == null)
throw new ArgumentNullException("asset");
return ConvertInputActionsToSteamIGA(asset.actionMaps, locale: locale);
}
public static string ConvertInputActionsToSteamIGA(IEnumerable actionMaps, string locale = "english")
{
if (actionMaps == null)
throw new ArgumentNullException("actionMaps");
var localizationStrings = new Dictionary();
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();
}
private static void ConvertInputActionToVDF(InputAction action, StringBuilder builder, Dictionary 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 static string GetSteamControllerInputType(InputAction action)
{
if (action == null)
throw new ArgumentNullException("action");
// Make sure we have an expected control layout.
var expectedControlLayout = action.expectedControlType;
if (string.IsNullOrEmpty(expectedControlLayout))
throw new ArgumentException($"Cannot determine Steam input type for action '{action}' that has no associated expected control layout",
nameof(action));
// Try to fetch the layout.
var layout = EditorInputControlLayoutCache.TryGetLayout(expectedControlLayout);
if (layout == null)
throw new ArgumentException($"Cannot determine Steam input type for action '{action}'; cannot find layout '{expectedControlLayout}'", nameof(action));
// Map our supported control types.
var controlType = layout.type;
if (typeof(ButtonControl).IsAssignableFrom(controlType))
return "Button";
if (typeof(InputControl).IsAssignableFrom(controlType))
return "AnalogTrigger";
if (typeof(Vector2Control).IsAssignableFrom(controlType))
return "StickPadGyro";
// Everything else throws.
throw new ArgumentException($"Cannot determine Steam input type for action '{action}'; layout '{expectedControlLayout}' with control type '{ controlType.Name}' has no known representation in the Steam controller API", nameof(action));
}
public static Dictionary ParseVDF(string vdf)
{
var parser = new VDFParser(vdf);
return parser.Parse();
}
private struct VDFParser
{
public string vdf;
public int length;
public int position;
public VDFParser(string vdf)
{
this.vdf = vdf;
length = vdf.Length;
position = 0;
}
public Dictionary Parse()
{
var result = new Dictionary();
ParseKeyValuePair(result);
SkipWhitespace();
if (position < length)
throw new InvalidOperationException($"Parse error at {position} in '{vdf}'; not expecting any more input");
return result;
}
private bool ParseKeyValuePair(Dictionary result)
{
var key = ParseString();
if (key.isEmpty)
return false;
SkipWhitespace();
if (position == length)
throw new InvalidOperationException($"Expecting value or object at position {position} in '{vdf}'");
var nextChar = vdf[position];
if (nextChar == '"')
{
var value = ParseString();
result[key.ToString()] = value.ToString();
}
else if (nextChar == '{')
{
var value = ParseObject();
result[key.ToString()] = value;
}
else
{
throw new InvalidOperationException($"Expecting value or object at position {position} in '{vdf}'");
}
return true;
}
private Substring ParseString()
{
SkipWhitespace();
if (position == length || vdf[position] != '"')
return new Substring();
++position;
var startPos = position;
while (position < length && vdf[position] != '"')
++position;
var endPos = position;
if (position < length)
++position;
return new Substring(vdf, startPos, endPos - startPos);
}
private Dictionary ParseObject()
{
SkipWhitespace();
if (position == length || vdf[position] != '{')
return null;
var result = new Dictionary();
++position;
while (position < length)
{
if (!ParseKeyValuePair(result))
break;
}
SkipWhitespace();
if (position == length || vdf[position] != '}')
throw new InvalidOperationException($"Expecting '}}' at position {position} in '{vdf}'");
++position;
return result;
}
private void SkipWhitespace()
{
while (position < length && char.IsWhiteSpace(vdf[position]))
++position;
}
}
[MenuItem("Assets/Steam/Export to Steam In-Game Actions File...", true)]
private static bool IsExportContextMenuItemEnabled()
{
return Selection.activeObject is InputActionAsset;
}
[MenuItem("Assets/Steam/Export to Steam In-Game Actions File...")]
private static void ExportContextMenuItem()
{
var selectedAsset = (InputActionAsset)Selection.activeObject;
// Determine default .vdf file name.
var defaultVDFName = "";
var directory = "";
var assetPath = AssetDatabase.GetAssetPath(selectedAsset);
if (!string.IsNullOrEmpty(assetPath))
{
defaultVDFName = Path.GetFileNameWithoutExtension(assetPath) + ".vdf";
directory = Path.GetDirectoryName(assetPath);
}
// Ask for save location.
var fileName = EditorUtility.SaveFilePanel("Export Steam In-Game Actions File", directory, defaultVDFName, "vdf");
if (!string.IsNullOrEmpty(fileName))
{
var text = ConvertInputActionsToSteamIGA(selectedAsset);
File.WriteAllText(fileName, text);
AssetDatabase.Refresh();
}
}
[MenuItem("Assets/Steam/Generate Unity Input Device...", true)]
private static bool IsGenerateContextMenuItemEnabled()
{
// VDF files have no associated importer and so come in as DefaultAssets.
if (!(Selection.activeObject is DefaultAsset))
return false;
var assetPath = AssetDatabase.GetAssetPath(Selection.activeObject);
if (!string.IsNullOrEmpty(assetPath) && Path.GetExtension(assetPath) == ".vdf")
return true;
return false;
}
////TODO: support setting class and namespace name
[MenuItem("Assets/Steam/Generate Unity Input Device...")]
private static void GenerateContextMenuItem()
{
var selectedAsset = Selection.activeObject;
var assetPath = AssetDatabase.GetAssetPath(selectedAsset);
if (string.IsNullOrEmpty(assetPath))
{
Debug.LogError("Cannot determine source asset path");
return;
}
var defaultClassName = Path.GetFileNameWithoutExtension(assetPath);
var defaultFileName = defaultClassName + ".cs";
var defaultDirectory = Path.GetDirectoryName(assetPath);
// Ask for save location.
var fileName = EditorUtility.SaveFilePanel("Generate C# Input Device Class", defaultDirectory, defaultFileName, "cs");
if (string.IsNullOrEmpty(fileName))
return;
// Load VDF file text.
var vdf = File.ReadAllText(assetPath);
// Generate and write output.
var className = Path.GetFileNameWithoutExtension(fileName);
var text = GenerateInputDeviceFromSteamIGA(vdf, className);
File.WriteAllText(fileName, text);
AssetDatabase.Refresh();
}
}
}
#endif // UNITY_EDITOR && UNITY_ENABLE_STEAM_CONTROLLER_SUPPORT