UI 자동화를 위해 바인딩 기능 구현
- 유니티 에셋 인증 오류로 meta 재생성
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
#if (UNITY_STANDALONE || UNITY_EDITOR) && UNITY_ENABLE_STEAM_CONTROLLER_SUPPORT
|
||||
|
||||
namespace UnityEngine.InputSystem.Steam
|
||||
{
|
||||
/// <summary>
|
||||
/// This is a wrapper around the Steamworks SDK controller API.
|
||||
/// </summary>
|
||||
/// <seealso href="https://partner.steamgames.com/doc/api/ISteamController"/>
|
||||
public interface ISteamControllerAPI
|
||||
{
|
||||
void RunFrame();
|
||||
|
||||
int GetConnectedControllers(SteamHandle<SteamController>[] outHandles);
|
||||
|
||||
SteamHandle<InputActionMap> GetActionSetHandle(string actionSetName);
|
||||
|
||||
SteamHandle<InputAction> GetDigitalActionHandle(string actionName);
|
||||
|
||||
SteamHandle<InputAction> GetAnalogActionHandle(string actionName);
|
||||
|
||||
void ActivateActionSet(SteamHandle<SteamController> controllerHandle, SteamHandle<InputActionMap> actionSetHandle);
|
||||
|
||||
SteamHandle<InputActionMap> GetCurrentActionSet(SteamHandle<SteamController> controllerHandle);
|
||||
|
||||
void ActivateActionSetLayer(SteamHandle<SteamController> controllerHandle,
|
||||
SteamHandle<InputActionMap> actionSetLayerHandle);
|
||||
|
||||
void DeactivateActionSetLayer(SteamHandle<SteamController> controllerHandle,
|
||||
SteamHandle<InputActionMap> actionSetLayerHandle);
|
||||
|
||||
void DeactivateAllActionSetLayers(SteamHandle<SteamController> controllerHandle);
|
||||
|
||||
int GetActiveActionSetLayers(SteamHandle<SteamController> controllerHandle,
|
||||
out SteamHandle<InputActionMap> handlesOut);
|
||||
|
||||
SteamAnalogActionData GetAnalogActionData(SteamHandle<SteamController> controllerHandle,
|
||||
SteamHandle<InputAction> analogActionHandle);
|
||||
|
||||
SteamDigitalActionData GetDigitalActionData(SteamHandle<SteamController> controllerHandle,
|
||||
SteamHandle<InputAction> digitalActionHandle);
|
||||
}
|
||||
|
||||
public struct SteamDigitalActionData
|
||||
{
|
||||
public bool active { get; set; }
|
||||
public bool pressed { get; set; }
|
||||
}
|
||||
|
||||
public struct SteamAnalogActionData
|
||||
{
|
||||
public bool active { get; set; }
|
||||
public Vector2 position { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
#endif // (UNITY_STANDALONE || UNITY_EDITOR) && UNITY_ENABLE_STEAM_CONTROLLER_SUPPORT
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 425bde5d5c63e4c5d8d95b068a911236
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,137 @@
|
||||
#if (UNITY_STANDALONE || UNITY_EDITOR) && UNITY_ENABLE_STEAM_CONTROLLER_SUPPORT
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEngine.InputSystem.Steam.Editor;
|
||||
#endif
|
||||
|
||||
////TODO: support action set layers
|
||||
|
||||
namespace UnityEngine.InputSystem.Steam
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for controllers made available through the Steam controller API.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Unlike other controllers, the Steam controller is somewhat of an amorphous input
|
||||
/// device which gains specific shape only in combination with an "In-Game Action File"
|
||||
/// in VDF format installed alongside the application. These files specify the actions
|
||||
/// that are supported by the application and are internally bound to specific controls
|
||||
/// on a controller inside the Steam runtime. The bindings are set up in the Steam client
|
||||
/// through its own binding UI.
|
||||
///
|
||||
/// Note that as the Steam controller API supports PS4 and Xbox controllers as well,
|
||||
/// the actual hardware device behind a SteamController instance may not be a
|
||||
/// Steam Controller. The <see cref="steamControllerType"/> property can be used what kind
|
||||
/// of controller the Steam runtime is talking to internally.
|
||||
///
|
||||
/// This class is abstract. Specific Steam controller interfaces can either be implemented
|
||||
/// manually based on this class or generated automatically from Steam IGA files using
|
||||
/// <see cref="SteamIGAConverter"/>. This can be done in the editor by right-clicking
|
||||
/// a .VDF file containing the actions and then selecting "Steam >> Generate Unity Input Device...".
|
||||
/// The result is a newly generated device layout that will automatically register itself
|
||||
/// with the input system and will represent a Steam controller with the specific action
|
||||
/// sets and actions found in the .VDF file. The Steam handles for sets and actions will
|
||||
/// automatically be exposed from the device and controls will be set up that correspond
|
||||
/// to each action defined in the .VDF file.
|
||||
///
|
||||
/// Devices based on SteamController can be used in one of two ways.
|
||||
///
|
||||
/// The first method is by manually managing active action sets on a controller. This is done by
|
||||
/// calling the various APIs (such as <see cref="ActivateSteamActionSet"/>) that correspond
|
||||
/// to the methods in the <see cref="ISteamControllerAPI">Steam controller API</see>. The
|
||||
/// controller handle is implicit in this case and corresponds to the <see cref="steamControllerHandle"/>
|
||||
/// of the controller the methods are called on.
|
||||
///
|
||||
/// The second method is by using
|
||||
///
|
||||
///
|
||||
///
|
||||
///
|
||||
///
|
||||
/// By default, Steam controllers will automatically activate action set layers in
|
||||
/// response to action maps being enabled and disabled. The correlation between Unity
|
||||
/// actions and action maps and Steam actions and action sets happens entirely by name.
|
||||
/// E.g. a Unity action map called "gameplay" will be looked up as a Steam action set
|
||||
/// using the same name "gameplay".
|
||||
/// </remarks>
|
||||
public abstract class SteamController : InputDevice
|
||||
{
|
||||
internal const string kSteamInterface = "Steam";
|
||||
|
||||
/// <summary>
|
||||
/// Handle in the <see cref="ISteamControllerAPI">Steam API</see> for the controller.
|
||||
/// </summary>
|
||||
public SteamHandle<SteamController> steamControllerHandle { get; internal set; }
|
||||
|
||||
/*
|
||||
* TODO
|
||||
public SteamControllerType steamControllerType
|
||||
{
|
||||
get { throw new NotImplementedException(); }
|
||||
}*/
|
||||
|
||||
/// <summary>
|
||||
/// The list of Steam action sets supported by this controller.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Steam action sets are implicitly supplied to the Steam runtime rather than explicitly configured
|
||||
/// by the application. ...
|
||||
/// </remarks>
|
||||
public abstract ReadOnlyArray<SteamActionSetInfo> steamActionSets { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Determine whether the controller automatically activates and deactivates action set
|
||||
/// layers in response to <see cref="InputActionMap">input action map</see> being enabled
|
||||
/// and disabled.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is on by default.
|
||||
///
|
||||
/// When on, if an <see cref="InputActionMap">action map</see> has bindings to a SteamController
|
||||
/// and is enabled or disabled, the SteamController will automatically enable or disable
|
||||
/// the correspondingly named Steam action set.
|
||||
/// </remarks>
|
||||
public bool autoActivateSets { get; set; }
|
||||
|
||||
protected SteamController()
|
||||
{
|
||||
autoActivateSets = true;
|
||||
}
|
||||
|
||||
public void ActivateSteamActionSet(SteamHandle<InputActionMap> actionSet)
|
||||
{
|
||||
SteamSupport.GetAPIAndRequireItToBeSet().ActivateActionSet(steamControllerHandle, actionSet);
|
||||
}
|
||||
|
||||
public SteamHandle<InputActionMap> currentSteamActionSet
|
||||
{
|
||||
get { return SteamSupport.GetAPIAndRequireItToBeSet().GetCurrentActionSet(steamControllerHandle); }
|
||||
}
|
||||
|
||||
protected abstract void ResolveSteamActions(ISteamControllerAPI api);
|
||||
|
||||
protected abstract void Update(ISteamControllerAPI api);
|
||||
|
||||
// These methods avoid having an 'internal' modifier that every override needs to carry along.
|
||||
internal void InvokeResolveSteamActions()
|
||||
{
|
||||
ResolveSteamActions(SteamSupport.GetAPIAndRequireItToBeSet());
|
||||
}
|
||||
|
||||
internal void InvokeUpdate()
|
||||
{
|
||||
Update(SteamSupport.GetAPIAndRequireItToBeSet());
|
||||
}
|
||||
|
||||
public struct SteamActionSetInfo
|
||||
{
|
||||
public string name { get; set; }
|
||||
public SteamHandle<InputActionMap> handle { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // (UNITY_STANDALONE || UNITY_EDITOR) && UNITY_ENABLE_STEAM_CONTROLLER_SUPPORT
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ab9baa68d9998419ba6b1bd76b11b630
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace UnityEngine.InputSystem.Steam
|
||||
{
|
||||
/*
|
||||
TODO
|
||||
public enum SteamControllerType
|
||||
{
|
||||
Unknown,
|
||||
SteamController,
|
||||
Xbox360,
|
||||
XboxOne,
|
||||
GenericXInput,
|
||||
PS4
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7ffe30fd9a2d64031a59c21418fb2067
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,59 @@
|
||||
#if (UNITY_STANDALONE || UNITY_EDITOR) && UNITY_ENABLE_STEAM_CONTROLLER_SUPPORT
|
||||
using System;
|
||||
|
||||
namespace UnityEngine.InputSystem.Steam
|
||||
{
|
||||
/// <summary>
|
||||
/// A handle for a Steam controller API object typed <typeparamref name="TObject"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TObject">A type used to type the Steam handle. The type itself isn't used other than
|
||||
/// for providing type safety to the Steam handle.</typeparam>
|
||||
public struct SteamHandle<TObject> : IEquatable<SteamHandle<TObject>>
|
||||
{
|
||||
private ulong m_Handle;
|
||||
|
||||
public SteamHandle(ulong handle)
|
||||
{
|
||||
m_Handle = handle;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("Steam({0}): {1}", typeof(TObject).Name, m_Handle);
|
||||
}
|
||||
|
||||
public bool Equals(SteamHandle<TObject> other)
|
||||
{
|
||||
return m_Handle == other.m_Handle;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj))
|
||||
return false;
|
||||
return obj is SteamHandle<TObject> && Equals((SteamHandle<TObject>)obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return m_Handle.GetHashCode();
|
||||
}
|
||||
|
||||
public static bool operator==(SteamHandle<TObject> a, SteamHandle<TObject> b)
|
||||
{
|
||||
return a.m_Handle == b.m_Handle;
|
||||
}
|
||||
|
||||
public static bool operator!=(SteamHandle<TObject> a, SteamHandle<TObject> b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
public static explicit operator ulong(SteamHandle<TObject> handle)
|
||||
{
|
||||
return handle.m_Handle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // (UNITY_STANDALONE || UNITY_EDITOR) && UNITY_ENABLE_STEAM_CONTROLLER_SUPPORT
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2f971d46c202b4b7a90865bf5600683f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,782 @@
|
||||
#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
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts input actions to and from Steam IGA file format.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
public static class SteamIGAConverter
|
||||
{
|
||||
/// <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>
|
||||
[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<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.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<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(" [InputControl]\n");
|
||||
builder.Append(
|
||||
$" public {(isStick ? "StickControl" : "Vector2Control")} {CSharpCodeHelpers.MakeIdentifier(entry.Key)} {{ get; protected set; }}\n");
|
||||
}
|
||||
|
||||
// Buttons.
|
||||
var buttons = (Dictionary<string, object>)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<string, object>)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<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(
|
||||
$" {CSharpCodeHelpers.MakeIdentifier(entry.Key)} = GetChildControl<{(isStick ? "StickControl" : "Vector2Control")}>(\"{entry.Key}\");\n");
|
||||
}
|
||||
|
||||
// Buttons.
|
||||
var buttons = (Dictionary<string, object>)setEntryProperties["Button"];
|
||||
foreach (var entry in buttons)
|
||||
{
|
||||
builder.Append(
|
||||
$" {CSharpCodeHelpers.MakeIdentifier(entry.Key)} = GetChildControl<ButtonControl>(\"{entry.Key}\");\n");
|
||||
}
|
||||
|
||||
// AnalogTriggers.
|
||||
var analogTriggers = (Dictionary<string, object>)setEntryProperties["AnalogTrigger"];
|
||||
foreach (var entry in analogTriggers)
|
||||
{
|
||||
builder.Append(
|
||||
$" {CSharpCodeHelpers.MakeIdentifier(entry.Key)} = GetChildControl<AxisControl>(\"{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<string, object>)setEntry.Value;
|
||||
|
||||
// Set handle.
|
||||
builder.Append(
|
||||
$" {CSharpCodeHelpers.MakeIdentifier(setEntry.Key)}SetHandle = api.GetActionSetHandle(\"{setEntry.Key}\");\n");
|
||||
|
||||
// StickPadGyros.
|
||||
var stickPadGyros = (Dictionary<string, object>)setEntryProperties["StickPadGyro"];
|
||||
foreach (var entry in stickPadGyros)
|
||||
{
|
||||
builder.Append(
|
||||
$" {CSharpCodeHelpers.MakeIdentifier(entry.Key)}Handle = api.GetAnalogActionHandle(\"{entry.Key}\");\n");
|
||||
}
|
||||
|
||||
// Buttons.
|
||||
var buttons = (Dictionary<string, object>)setEntryProperties["Button"];
|
||||
foreach (var entry in buttons)
|
||||
{
|
||||
builder.Append(
|
||||
$" {CSharpCodeHelpers.MakeIdentifier(entry.Key)}Handle = api.GetDigitalActionHandle(\"{entry.Key}\");\n");
|
||||
}
|
||||
|
||||
// AnalogTriggers.
|
||||
var analogTriggers = (Dictionary<string, object>)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<string, object>)setEntry.Value;
|
||||
|
||||
// Set handle.
|
||||
builder.Append(
|
||||
$" public SteamHandle<InputActionMap> {CSharpCodeHelpers.MakeIdentifier(setEntry.Key)}SetHandle {{ get; private set; }}\n");
|
||||
|
||||
// StickPadGyros.
|
||||
var stickPadGyros = (Dictionary<string, object>)setEntryProperties["StickPadGyro"];
|
||||
foreach (var entry in stickPadGyros)
|
||||
{
|
||||
builder.Append(
|
||||
$" public SteamHandle<InputAction> {CSharpCodeHelpers.MakeIdentifier(entry.Key)}Handle {{ get; private set; }}\n");
|
||||
}
|
||||
|
||||
// Buttons.
|
||||
var buttons = (Dictionary<string, object>)setEntryProperties["Button"];
|
||||
foreach (var entry in buttons)
|
||||
{
|
||||
builder.Append(
|
||||
$" public SteamHandle<InputAction> {CSharpCodeHelpers.MakeIdentifier(entry.Key)}Handle {{ get; private set; }}\n");
|
||||
}
|
||||
|
||||
// AnalogTriggers.
|
||||
var analogTriggers = (Dictionary<string, object>)setEntryProperties["AnalogTrigger"];
|
||||
foreach (var entry in analogTriggers)
|
||||
{
|
||||
builder.Append(
|
||||
$" public SteamHandle<InputAction> {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<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($" {stateStructName} state;\n");
|
||||
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(
|
||||
$" 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<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(
|
||||
$" 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<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(
|
||||
$" [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<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(
|
||||
$" [InputControl(name = \"{entry.Key}\", layout = \"{(isStick ? "Stick" : "Vector2")}\")]\n");
|
||||
builder.Append($" public Vector2 {CSharpCodeHelpers.MakeIdentifier(entry.Key)};\n");
|
||||
}
|
||||
|
||||
// AnalogTriggers.
|
||||
var analogTriggers = (Dictionary<string, object>)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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert an .inputactions asset to Steam VDF format.
|
||||
/// </summary>
|
||||
/// <param name="asset"></param>
|
||||
/// <param name="locale"></param>
|
||||
/// <returns>A string in Steam VDF format describing "In Game Actions" corresponding to the actions in
|
||||
/// <paramref name="asset"/>.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="asset"/> is null.</exception>
|
||||
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<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();
|
||||
}
|
||||
|
||||
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 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<float>).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<string, object> 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<string, object> Parse()
|
||||
{
|
||||
var result = new Dictionary<string, object>();
|
||||
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<string, object> 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<string, object> ParseObject()
|
||||
{
|
||||
SkipWhitespace();
|
||||
if (position == length || vdf[position] != '{')
|
||||
return null;
|
||||
|
||||
var result = new Dictionary<string, object>();
|
||||
|
||||
++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
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bb4d45fbf0aa84484b251242fdb21800
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,219 @@
|
||||
#if (UNITY_STANDALONE || UNITY_EDITOR) && UNITY_ENABLE_STEAM_CONTROLLER_SUPPORT
|
||||
using System;
|
||||
using UnityEngine.InputSystem.Layouts;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
|
||||
namespace UnityEngine.InputSystem.Steam
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds support for Steam controllers.
|
||||
/// </summary>
|
||||
#if UNITY_DISABLE_DEFAULT_INPUT_PLUGIN_INITIALIZATION
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
static class SteamSupport
|
||||
{
|
||||
/// <summary>
|
||||
/// Wrapper around the Steam controller API.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This must be set by user code for Steam controller support to become functional.
|
||||
/// </remarks>
|
||||
public static ISteamControllerAPI api
|
||||
{
|
||||
get { return s_API; }
|
||||
set
|
||||
{
|
||||
s_API = value;
|
||||
InstallHooks(s_API != null);
|
||||
}
|
||||
}
|
||||
|
||||
internal static ISteamControllerAPI GetAPIAndRequireItToBeSet()
|
||||
{
|
||||
if (s_API == null)
|
||||
throw new InvalidOperationException("ISteamControllerAPI implementation has not been set on SteamSupport");
|
||||
return s_API;
|
||||
}
|
||||
|
||||
internal static SteamHandle<SteamController>[] s_ConnectedControllers;
|
||||
internal static SteamController[] s_InputDevices;
|
||||
internal static int s_InputDeviceCount;
|
||||
internal static bool s_HooksInstalled;
|
||||
internal static ISteamControllerAPI s_API;
|
||||
|
||||
private const int STEAM_CONTROLLER_MAX_COUNT = 16;
|
||||
|
||||
/// <summary>
|
||||
/// Enable support for the Steam controller API.
|
||||
/// </summary>
|
||||
public static void Initialize()
|
||||
{
|
||||
// We use this as a base layout.
|
||||
InputSystem.RegisterLayout<SteamController>();
|
||||
|
||||
if (api != null)
|
||||
InstallHooks(true);
|
||||
}
|
||||
|
||||
private static void InstallHooks(bool state)
|
||||
{
|
||||
Debug.Assert(api != null);
|
||||
if (state && !s_HooksInstalled)
|
||||
{
|
||||
InputSystem.onBeforeUpdate += OnUpdate;
|
||||
InputSystem.onActionChange += OnActionChange;
|
||||
}
|
||||
else if (!state && s_HooksInstalled)
|
||||
{
|
||||
InputSystem.onBeforeUpdate -= OnUpdate;
|
||||
InputSystem.onActionChange -= OnActionChange;
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnActionChange(object mapOrAction, InputActionChange change)
|
||||
{
|
||||
// We only care about action map activations. Steam has no support for enabling or disabling
|
||||
// individual actions and also has no support disabling sets once enabled (can only switch
|
||||
// to different set).
|
||||
if (change != InputActionChange.ActionMapEnabled)
|
||||
return;
|
||||
|
||||
// See if the map has any bindings to SteamControllers.
|
||||
// NOTE: We only support a single SteamController on any action map here. The first SteamController
|
||||
// we find is the one we're doing all the work on.
|
||||
var actionMap = (InputActionMap)mapOrAction;
|
||||
foreach (var action in actionMap.actions)
|
||||
{
|
||||
foreach (var control in action.controls)
|
||||
{
|
||||
var steamController = control.device as SteamController;
|
||||
if (steamController == null)
|
||||
continue;
|
||||
|
||||
// Yes, there's active bindings to a SteamController on the map. Look through the Steam action
|
||||
// sets on the controller for a name match on the action map. If we have one, sync the enable/
|
||||
// disable status of the set.
|
||||
var actionMapName = actionMap.name;
|
||||
foreach (var set in steamController.steamActionSets)
|
||||
{
|
||||
if (string.Compare(set.name, actionMapName, StringComparison.InvariantCultureIgnoreCase) != 0)
|
||||
continue;
|
||||
|
||||
// Nothing to do if the Steam controller has auto-syncing disabled.
|
||||
if (!steamController.autoActivateSets)
|
||||
return;
|
||||
|
||||
// Sync status.
|
||||
steamController.ActivateSteamActionSet(set.handle);
|
||||
|
||||
// Done.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnUpdate()
|
||||
{
|
||||
if (api == null)
|
||||
return;
|
||||
|
||||
// Update controller state.
|
||||
api.RunFrame();
|
||||
|
||||
// Check if we have any new controllers have appeared.
|
||||
if (s_ConnectedControllers == null)
|
||||
s_ConnectedControllers = new SteamHandle<SteamController>[STEAM_CONTROLLER_MAX_COUNT];
|
||||
var numConnectedControllers = api.GetConnectedControllers(s_ConnectedControllers);
|
||||
for (var i = 0; i < numConnectedControllers; ++i)
|
||||
{
|
||||
var handle = s_ConnectedControllers[i];
|
||||
|
||||
// See if we already have a device for this one.
|
||||
if (s_InputDevices != null)
|
||||
{
|
||||
SteamController existingDevice = null;
|
||||
for (var n = 0; n < s_InputDeviceCount; ++n)
|
||||
{
|
||||
if (s_InputDevices[n].steamControllerHandle == handle)
|
||||
{
|
||||
existingDevice = s_InputDevices[n];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Yes, we do.
|
||||
if (existingDevice != null)
|
||||
continue;
|
||||
}
|
||||
|
||||
////FIXME: this should not create garbage
|
||||
// No, so create a new device.
|
||||
var controllerLayouts = InputSystem.ListLayoutsBasedOn("SteamController");
|
||||
foreach (var layout in controllerLayouts)
|
||||
{
|
||||
// Rather than directly creating a device with the layout, let it go through
|
||||
// the usual matching process.
|
||||
var device = InputSystem.AddDevice(new InputDeviceDescription
|
||||
{
|
||||
interfaceName = SteamController.kSteamInterface,
|
||||
product = layout
|
||||
});
|
||||
|
||||
// Make sure it's a SteamController we got.
|
||||
var steamDevice = device as SteamController;
|
||||
if (steamDevice == null)
|
||||
{
|
||||
Debug.LogError(string.Format(
|
||||
"InputDevice created from layout '{0}' based on the 'SteamController' layout is not a SteamController",
|
||||
device.layout));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Resolve the controller's actions.
|
||||
steamDevice.InvokeResolveSteamActions();
|
||||
|
||||
// Assign it the Steam controller handle.
|
||||
steamDevice.steamControllerHandle = handle;
|
||||
|
||||
ArrayHelpers.AppendWithCapacity(ref s_InputDevices, ref s_InputDeviceCount, steamDevice);
|
||||
}
|
||||
}
|
||||
|
||||
// Update all controllers we have.
|
||||
for (var i = 0; i < s_InputDeviceCount; ++i)
|
||||
{
|
||||
var device = s_InputDevices[i];
|
||||
var handle = device.steamControllerHandle;
|
||||
|
||||
// Check if the device still exists.
|
||||
var stillExists = false;
|
||||
for (var n = 0; n < numConnectedControllers; ++n)
|
||||
if (s_ConnectedControllers[n] == handle)
|
||||
{
|
||||
stillExists = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// If not, remove it.
|
||||
if (!stillExists)
|
||||
{
|
||||
ArrayHelpers.EraseAtByMovingTail(s_InputDevices, ref s_InputDeviceCount, i);
|
||||
////REVIEW: should this rather queue a device removal event?
|
||||
InputSystem.RemoveDevice(device);
|
||||
--i;
|
||||
continue;
|
||||
}
|
||||
|
||||
////TODO: support polling Steam controllers on an async polling thread adhering to InputSystem.pollingFrequency
|
||||
// Otherwise, update it.
|
||||
device.InvokeUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // (UNITY_STANDALONE || UNITY_EDITOR) && UNITY_ENABLE_STEAM_CONTROLLER_SUPPORT
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7bef150b341c4d0dbab3db81ccf2ea81
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user