WIP: Steam controller support (#221).

* NEW: Add Steamworks.NET to demo.

* NEW: Can associate control layouts with actions.

* NEW: Can export .inputactions to Steam IGA files.

* NEW: Add Steamworks.NET to demo.

* NEW: Can associate control layouts with actions.

* NEW: Can export .inputactions to Steam IGA files.

* NEW: Add Steam IGA version of DemoControls.

* NEW: Can generate input devices from Steam IGA files.
This commit is contained in:
Rene Damm 2018-08-14 11:36:26 -07:00 коммит произвёл GitHub
Родитель fe2054b9c2
Коммит 538d2601da
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
46 изменённых файлов: 2203 добавлений и 29 удалений

Просмотреть файл

@ -0,0 +1,64 @@
#if (UNITY_EDITOR || UNITY_STANDALONE) && UNITY_ENABLE_STEAM_CONTROLLER_SUPPORT
using UnityEngine;
using UnityEngine.Experimental.Input;
using UnityEngine.Experimental.Input.Controls;
using UnityEngine.Experimental.Input.Utilities;
using UnityEngine.Experimental.Input.Plugins.Steam;
#if UNITY_EDITOR
using UnityEditor;
#endif
#if UNITY_EDITOR
[InitializeOnLoad]
#endif
[InputControlLayout(stateType = typeof(DemoControllerState))]
public class DemoController : SteamController, IInputUpdateCallbackReceiver
{
private static InputDeviceMatcher deviceMatcher
{
get { return new InputDeviceMatcher().WithInterface("Steam").WithProduct("DemoController"); }
}
#if UNITY_EDITOR
static DemoController()
{
InputSystem.RegisterControlLayout<DemoController>(matches: deviceMatcher);
}
#endif
public void OnUpdate(InputUpdateType updateType)
{
////TODO
}
[RuntimeInitializeOnLoadMethod(loadType: RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void RuntimeInitializeOnLoad()
{
InputSystem.RegisterControlLayout<DemoController>(matches: deviceMatcher);
}
public Vector2Control move { get; protected set; }
public Vector2Control look { get; protected set; }
public ButtonControl fire { get; protected set; }
protected override void FinishSetup(InputDeviceBuilder builder)
{
base.FinishSetup(builder);
move = builder.GetControl<Vector2Control>("move");
look = builder.GetControl<Vector2Control>("look");
fire = builder.GetControl<ButtonControl>("fire");
}
}
public unsafe struct DemoControllerState : IInputStateTypeInfo
{
public FourCC GetFormat()
{
return new FourCC('D', 'e', 'm', 'o');
}
[InputControl(name = "move", layout = "Vector2")]
public Vector2 move;
[InputControl(name = "look", layout = "Vector2")]
public Vector2 look;
[InputControl(name = "fire", layout = "Button", bit = 0)]
public fixed byte buttons[1];
}
#endif

Просмотреть файл

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b4e398b2b56be40c190845a5001d2276
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

Просмотреть файл

@ -1 +1,134 @@
{"maps":[{"name":"gameplay","actions":[{"name":"fire","bindings":[]},{"name":"move","bindings":[]},{"name":"look","bindings":[]}],"bindings":[{"name":"","path":"*/{PrimaryAction}","interactions":"Tap,SlowTap","groups":"","action":"fire","chainWithPrevious":false,"isComposite":false,"isPartOfComposite":false,"modifiers":""},{"name":"","path":"<Gamepad>/leftStick","interactions":"","groups":"","action":"move","chainWithPrevious":false,"isComposite":false,"isPartOfComposite":false,"modifiers":""},{"name":"Dpad","path":"Dpad","interactions":"","groups":"","action":"move","chainWithPrevious":false,"isComposite":true,"isPartOfComposite":false,"modifiers":""},{"name":"up","path":"<Keyboard>/w","interactions":"","groups":"","action":"move","chainWithPrevious":false,"isComposite":false,"isPartOfComposite":true,"modifiers":""},{"name":"down","path":"<Keyboard>/s","interactions":"","groups":"","action":"move","chainWithPrevious":false,"isComposite":false,"isPartOfComposite":true,"modifiers":""},{"name":"left","path":"<Keyboard>/a","interactions":"","groups":"","action":"move","chainWithPrevious":false,"isComposite":false,"isPartOfComposite":true,"modifiers":""},{"name":"right","path":"<Keyboard>/d","interactions":"","groups":"","action":"move","chainWithPrevious":false,"isComposite":false,"isPartOfComposite":true,"modifiers":""},{"name":"","path":"<Gamepad>/rightStick","interactions":"","groups":"","action":"look","chainWithPrevious":false,"isComposite":false,"isPartOfComposite":false,"modifiers":""},{"name":"","path":"<Pointer>/delta","interactions":"","groups":"","action":"look","chainWithPrevious":false,"isComposite":false,"isPartOfComposite":false,"modifiers":""}]}]}
{
"maps": [
{
"name": "gameplay",
"actions": [
{
"name": "fire",
"expectedControlLayout": "Button",
"bindings": []
},
{
"name": "move",
"expectedControlLayout": "Stick",
"bindings": []
},
{
"name": "look",
"expectedControlLayout": "Vector2",
"bindings": []
}
],
"bindings": [
{
"name": "",
"path": "*/{PrimaryAction}",
"interactions": "Tap,SlowTap",
"processors": "",
"groups": "",
"action": "fire",
"chainWithPrevious": false,
"isComposite": false,
"isPartOfComposite": false,
"modifiers": ""
},
{
"name": "",
"path": "<Gamepad>/leftStick",
"interactions": "",
"processors": "",
"groups": "",
"action": "move",
"chainWithPrevious": false,
"isComposite": false,
"isPartOfComposite": false,
"modifiers": ""
},
{
"name": "Dpad",
"path": "Dpad",
"interactions": "",
"processors": "",
"groups": "",
"action": "move",
"chainWithPrevious": false,
"isComposite": true,
"isPartOfComposite": false,
"modifiers": ""
},
{
"name": "up",
"path": "<Keyboard>/w",
"interactions": "",
"processors": "",
"groups": "",
"action": "move",
"chainWithPrevious": false,
"isComposite": false,
"isPartOfComposite": true,
"modifiers": ""
},
{
"name": "down",
"path": "<Keyboard>/s",
"interactions": "",
"processors": "",
"groups": "",
"action": "move",
"chainWithPrevious": false,
"isComposite": false,
"isPartOfComposite": true,
"modifiers": ""
},
{
"name": "left",
"path": "<Keyboard>/a",
"interactions": "",
"processors": "",
"groups": "",
"action": "move",
"chainWithPrevious": false,
"isComposite": false,
"isPartOfComposite": true,
"modifiers": ""
},
{
"name": "right",
"path": "<Keyboard>/d",
"interactions": "",
"processors": "",
"groups": "",
"action": "move",
"chainWithPrevious": false,
"isComposite": false,
"isPartOfComposite": true,
"modifiers": ""
},
{
"name": "",
"path": "<Gamepad>/rightStick",
"interactions": "",
"processors": "",
"groups": "",
"action": "look",
"chainWithPrevious": false,
"isComposite": false,
"isPartOfComposite": false,
"modifiers": ""
},
{
"name": "",
"path": "<Pointer>/delta",
"interactions": "",
"processors": "",
"groups": "",
"action": "look",
"chainWithPrevious": false,
"isComposite": false,
"isPartOfComposite": false,
"modifiers": ""
}
]
}
]
}

Просмотреть файл

@ -0,0 +1,40 @@
"In Game Actions"
{
"actions"
{
"gameplay"
{
"title" "#Set_gameplay"
"StickPadGyro"
{
"move"
{
"title" "#Action_gameplay_move"
"input_mode" "joystick_move"
}
"look"
{
"title" "#Action_gameplay_look"
"input_mode" "absolute_mouse"
}
}
"AnalogTrigger"
{
}
"Button"
{
"fire" "Action_gameplay_fire"
}
}
}
"localization"
{
"english"
{
"Set_gameplay" "gameplay"
"Action_gameplay_move" "move"
"Action_gameplay_look" "look"
"Action_gameplay_fire" "fire"
}
}
}

Просмотреть файл

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 3e43849d533b147caa9ad95417108e62
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Просмотреть файл

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: eb53bdebdf3f542d79f3f6626f274a7c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Просмотреть файл

@ -0,0 +1,279 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!29 &1
OcclusionCullingSettings:
m_ObjectHideFlags: 0
serializedVersion: 2
m_OcclusionBakeSettings:
smallestOccluder: 5
smallestHole: 0.25
backfaceThreshold: 100
m_SceneGUID: 00000000000000000000000000000000
m_OcclusionCullingData: {fileID: 0}
--- !u!104 &2
RenderSettings:
m_ObjectHideFlags: 0
serializedVersion: 9
m_Fog: 0
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
m_FogMode: 3
m_FogDensity: 0.01
m_LinearFogStart: 0
m_LinearFogEnd: 300
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
m_AmbientIntensity: 1
m_AmbientMode: 0
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
m_HaloStrength: 0.5
m_FlareStrength: 1
m_FlareFadeSpeed: 3
m_HaloTexture: {fileID: 0}
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
m_DefaultReflectionMode: 0
m_DefaultReflectionResolution: 128
m_ReflectionBounces: 1
m_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0}
m_Sun: {fileID: 0}
m_IndirectSpecularColor: {r: 0.44657856, g: 0.49641234, b: 0.57481724, a: 1}
m_UseRadianceAmbientProbe: 0
--- !u!157 &3
LightmapSettings:
m_ObjectHideFlags: 0
serializedVersion: 11
m_GIWorkflowMode: 0
m_GISettings:
serializedVersion: 2
m_BounceScale: 1
m_IndirectOutputScale: 1
m_AlbedoBoost: 1
m_TemporalCoherenceThreshold: 1
m_EnvironmentLightingMode: 0
m_EnableBakedLightmaps: 1
m_EnableRealtimeLightmaps: 1
m_LightmapEditorSettings:
serializedVersion: 10
m_Resolution: 2
m_BakeResolution: 40
m_AtlasSize: 1024
m_AO: 0
m_AOMaxDistance: 1
m_CompAOExponent: 1
m_CompAOExponentDirect: 0
m_Padding: 2
m_LightmapParameters: {fileID: 0}
m_LightmapsBakeMode: 1
m_TextureCompression: 1
m_FinalGather: 0
m_FinalGatherFiltering: 1
m_FinalGatherRayCount: 256
m_ReflectionCompression: 2
m_MixedBakeMode: 2
m_BakeBackend: 1
m_PVRSampling: 1
m_PVRDirectSampleCount: 32
m_PVRSampleCount: 500
m_PVRBounces: 2
m_PVRFilterTypeDirect: 0
m_PVRFilterTypeIndirect: 0
m_PVRFilterTypeAO: 0
m_PVRFilteringMode: 1
m_PVRCulling: 1
m_PVRFilteringGaussRadiusDirect: 1
m_PVRFilteringGaussRadiusIndirect: 5
m_PVRFilteringGaussRadiusAO: 2
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
m_PVRFilteringAtrousPositionSigmaIndirect: 2
m_PVRFilteringAtrousPositionSigmaAO: 1
m_ShowResolutionOverlay: 1
m_LightingDataAsset: {fileID: 0}
m_UseShadowmask: 1
--- !u!196 &4
NavMeshSettings:
serializedVersion: 2
m_ObjectHideFlags: 0
m_BuildSettings:
serializedVersion: 2
agentTypeID: 0
agentRadius: 0.5
agentHeight: 2
agentSlope: 45
agentClimb: 0.4
ledgeDropHeight: 0
maxJumpAcrossDistance: 0
minRegionArea: 2
manualCellSize: 0
cellSize: 0.16666667
manualTileSize: 0
tileSize: 256
accuratePlacement: 0
debug:
m_Flags: 0
m_NavMeshData: {fileID: 0}
--- !u!1 &534744310
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 534744313}
- component: {fileID: 534744312}
- component: {fileID: 534744311}
- component: {fileID: 534744314}
m_Layer: 0
m_Name: Main Camera
m_TagString: MainCamera
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!81 &534744311
AudioListener:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 534744310}
m_Enabled: 1
--- !u!20 &534744312
Camera:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 534744310}
m_Enabled: 1
serializedVersion: 2
m_ClearFlags: 1
m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
m_projectionMatrixMode: 1
m_SensorSize: {x: 36, y: 24}
m_LensShift: {x: 0, y: 0}
m_GateFitMode: 2
m_FocalLength: 50
m_NormalizedViewPortRect:
serializedVersion: 2
x: 0
y: 0
width: 1
height: 1
near clip plane: 0.3
far clip plane: 1000
field of view: 60
orthographic: 0
orthographic size: 5
m_Depth: -1
m_CullingMask:
serializedVersion: 2
m_Bits: 4294967295
m_RenderingPath: -1
m_TargetTexture: {fileID: 0}
m_TargetDisplay: 0
m_TargetEye: 3
m_HDR: 1
m_AllowMSAA: 1
m_AllowDynamicResolution: 0
m_ForceIntoRT: 0
m_OcclusionCulling: 1
m_StereoConvergence: 10
m_StereoSeparation: 0.022
--- !u!4 &534744313
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 534744310}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 1, z: -10}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &534744314
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 534744310}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: ef4bffeda13d7a748973ff9204401c07, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!1 &575173927
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 575173929}
- component: {fileID: 575173928}
m_Layer: 0
m_Name: Directional Light
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!108 &575173928
Light:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 575173927}
m_Enabled: 1
serializedVersion: 8
m_Type: 1
m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1}
m_Intensity: 1
m_Range: 10
m_SpotAngle: 30
m_CookieSize: 10
m_Shadows:
m_Type: 2
m_Resolution: -1
m_CustomResolution: -1
m_Strength: 1
m_Bias: 0.05
m_NormalBias: 0.4
m_NearPlane: 0.2
m_Cookie: {fileID: 0}
m_DrawHalo: 0
m_Flare: {fileID: 0}
m_RenderMode: 0
m_CullingMask:
serializedVersion: 2
m_Bits: 4294967295
m_Lightmapping: 4
m_LightShadowCasterMode: 0
m_AreaSize: {x: 1, y: 1}
m_BounceIntensity: 1
m_ColorTemperature: 6570
m_UseColorTemperature: 0
m_ShadowRadius: 0
m_ShadowAngle: 0
--- !u!4 &575173929
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 575173927}
m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261}
m_LocalPosition: {x: 0, y: 3, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0}

Просмотреть файл

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: a5e48e1b2f6494a789eda22444bcbcc7
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Просмотреть файл

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7d28372a5c21f48b49a86afae368349b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Просмотреть файл

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 34e2320451cf74f17b639e64fa9b35aa
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Просмотреть файл

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: bfdc0bd207d0a440c8962e62bfe4db51
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Просмотреть файл

@ -0,0 +1,135 @@
// This file is provided under The MIT License as part of Steamworks.NET.
// Copyright (c) 2013-2018 Riley Labrecque
// Please see the included LICENSE.txt for additional information.
#if UNITY_ANDROID || UNITY_IOS || UNITY_TIZEN || UNITY_TVOS || UNITY_WEBGL || UNITY_WSA || UNITY_PS4 || UNITY_WII || UNITY_XBOXONE || UNITY_SWITCH
#define DISABLESTEAMWORKS
#endif
#if !DISABLESTEAMWORKS
// Add 'DISABLEREDISTCOPY' to your custom platform defines to disable automatic copying!
#if UNITY_5_3_OR_NEWER
#define DISABLEREDISTCOPY
#endif
using UnityEngine;
using UnityEditor;
using UnityEditor.Callbacks;
using Steamworks;
using System.IO;
public class RedistCopy
{
[PostProcessBuild]
public static void OnPostprocessBuild(BuildTarget target, string pathToBuiltProject)
{
string baseDir;
string pluginsDir;
switch (target)
{
case BuildTarget.StandaloneWindows:
case BuildTarget.StandaloneWindows64:
baseDir = Path.Combine(Path.GetDirectoryName(pathToBuiltProject), Path.GetFileNameWithoutExtension(pathToBuiltProject) + "_Data");
pluginsDir = Path.Combine(baseDir, "Plugins");
break;
case BuildTarget.StandaloneLinux:
baseDir = Path.Combine(Path.GetDirectoryName(pathToBuiltProject), Path.GetFileNameWithoutExtension(pathToBuiltProject) + "_Data");
pluginsDir = Path.Combine(Path.Combine(baseDir, "Plugins"), "x86");
break;
case BuildTarget.StandaloneLinux64:
case BuildTarget.StandaloneLinuxUniversal:
baseDir = Path.Combine(Path.GetDirectoryName(pathToBuiltProject), Path.GetFileNameWithoutExtension(pathToBuiltProject) + "_Data");
pluginsDir = Path.Combine(Path.Combine(baseDir, "Plugins"), "x86_64");
break;
#if UNITY_2017_3_OR_NEWER
case BuildTarget.StandaloneOSX:
#else
case BuildTarget.StandaloneOSXIntel:
case BuildTarget.StandaloneOSXIntel64:
case BuildTarget.StandaloneOSXUniversal:
#endif
baseDir = Path.Combine(Path.GetDirectoryName(pathToBuiltProject), Path.GetFileNameWithoutExtension(pathToBuiltProject) + ".app");
baseDir = Path.Combine(baseDir, "Contents");
pluginsDir = Path.Combine(baseDir, "Plugins");
break;
default:
return;
}
string[] DebugInfo =
{
"Steamworks.NET created by Riley Labrecque",
"http://steamworks.github.io",
"",
"Steamworks.NET Version: " + Steamworks.Version.SteamworksNETVersion,
"Steamworks SDK Version: " + Steamworks.Version.SteamworksSDKVersion,
"Steam API DLL Version: " + Steamworks.Version.SteamAPIDLLVersion,
"Steam API DLL Size: " + Steamworks.Version.SteamAPIDLLSize,
"Steam API64 DLL Size: " + Steamworks.Version.SteamAPI64DLLSize,
""
};
File.WriteAllLines(Path.Combine(pluginsDir, "Steamworks.NET.txt"), DebugInfo);
#if !DISABLEREDISTCOPY
if (target == BuildTarget.StandaloneWindows64)
{
CopyFile("steam_api64.dll", "steam_api64.dll", "Assets/Plugins/x86_64", pathToBuiltProject);
}
else if (target == BuildTarget.StandaloneWindows)
{
CopyFile("steam_api.dll", "steam_api.dll", "Assets/Plugins/x86", pathToBuiltProject);
}
string controllerCfg = Path.Combine(Application.dataPath, "controller.vdf");
if (File.Exists(controllerCfg))
{
string strFileDest = Path.Combine(baseDir, "controller.vdf");
File.Copy(controllerCfg, strFileDest);
File.SetAttributes(strFileDest, File.GetAttributes(strFileDest) & ~FileAttributes.ReadOnly);
if (!File.Exists(strFileDest))
{
Debug.LogWarning("[Steamworks.NET] Could not copy controller.vdf into the built project. File.Copy() Failed. Place controller.vdf from the Steamworks SDK in the output dir manually.");
}
}
#endif
}
static void CopyFile(string filename, string outputfilename, string pathToFile, string pathToBuiltProject)
{
string strCWD = Directory.GetCurrentDirectory();
string strSource = Path.Combine(Path.Combine(strCWD, pathToFile), filename);
string strFileDest = Path.Combine(Path.GetDirectoryName(pathToBuiltProject), outputfilename);
if (!File.Exists(strSource))
{
Debug.LogWarning(string.Format("[Steamworks.NET] Could not copy {0} into the project root. {0} could not be found in '{1}'. Place {0} from the redist into the project root manually.", filename, pathToFile));
return;
}
if (File.Exists(strFileDest))
{
if (File.GetLastWriteTime(strSource) == File.GetLastWriteTime(strFileDest))
{
FileInfo fInfo = new FileInfo(strSource);
FileInfo fInfo2 = new FileInfo(strFileDest);
if (fInfo.Length == fInfo2.Length)
{
return;
}
}
}
File.Copy(strSource, strFileDest, true);
File.SetAttributes(strFileDest, File.GetAttributes(strFileDest) & ~FileAttributes.ReadOnly);
if (!File.Exists(strFileDest))
{
Debug.LogWarning(string.Format("[Steamworks.NET] Could not copy {0} into the built project. File.Copy() Failed. Place {0} from the redist folder into the output dir manually.", filename));
}
}
}
#endif // !DISABLESTEAMWORKS

Просмотреть файл

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 53d09de5fac8f4a9aae30822cccf007c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

Просмотреть файл

@ -0,0 +1,298 @@
// This file is provided under The MIT License as part of Steamworks.NET.
// Copyright (c) 2013-2018 Riley Labrecque
// Please see the included LICENSE.txt for additional information.
// Uncomment this out or add it to your custom platform defines to disable checking the plugin platform settings.
//#define DISABLEPLATFORMSETTINGS
using UnityEngine;
using UnityEditor;
using System.IO;
// This copys various files into their required locations when Unity is launched to make installation a breeze.
[InitializeOnLoad]
public class RedistInstall
{
static RedistInstall()
{
CopyFile("Assets/Plugins/Steamworks.NET/redist", "steam_appid.txt", false);
// We only need to copy the dll into the project root on <= Unity 5.0
#if UNITY_EDITOR_WIN && (UNITY_4_7 || UNITY_5_0)
#if UNITY_EDITOR_64
CopyFile("Assets/Plugins/x86_64", "steam_api64.dll", true);
#else
CopyFile("Assets/Plugins/x86", "steam_api.dll", true);
#endif
#endif
#if UNITY_5 || UNITY_2017
#if !DISABLEPLATFORMSETTINGS
SetPlatformSettings();
#endif
#endif
}
static void CopyFile(string path, string filename, bool bCheckDifference)
{
string strCWD = Directory.GetCurrentDirectory();
string strSource = Path.Combine(Path.Combine(strCWD, path), filename);
string strDest = Path.Combine(strCWD, filename);
if (!File.Exists(strSource))
{
Debug.LogWarning(string.Format("[Steamworks.NET] Could not copy {0} into the project root. {0} could not be found in '{1}'. Place {0} from the Steamworks SDK in the project root manually.", filename, Path.Combine(strCWD, path)));
return;
}
if (File.Exists(strDest))
{
if (!bCheckDifference)
return;
if (File.GetLastWriteTime(strSource) == File.GetLastWriteTime(strDest))
{
FileInfo fInfo = new FileInfo(strSource);
FileInfo fInfo2 = new FileInfo(strDest);
if (fInfo.Length == fInfo2.Length)
{
return;
}
}
Debug.Log(string.Format("[Steamworks.NET] {0} in the project root differs from the Steamworks.NET redistributable. Updating.... Please relaunch Unity.", filename));
}
else
{
Debug.Log(string.Format("[Steamworks.NET] {0} is not present in the project root. Copying...", filename));
}
File.Copy(strSource, strDest, true);
File.SetAttributes(strDest, File.GetAttributes(strDest) & ~FileAttributes.ReadOnly);
if (File.Exists(strDest))
{
Debug.Log(string.Format("[Steamworks.NET] Successfully copied {0} into the project root. Please relaunch Unity.", filename));
}
else
{
Debug.LogWarning(string.Format("[Steamworks.NET] Could not copy {0} into the project root. File.Copy() Failed. Please copy {0} into the project root manually.", Path.Combine(path, filename)));
}
}
#if UNITY_5 || UNITY_2017 || UNITY_2017_1_OR_NEWER
static void SetPlatformSettings()
{
foreach (var plugin in PluginImporter.GetAllImporters())
{
// Skip any null plugins, why is this a thing?!
if (plugin == null)
{
continue;
}
// Skip any absolute paths, as they are only builtin plugins.
if (Path.IsPathRooted(plugin.assetPath))
{
continue;
}
bool didUpdate = false;
string filename = Path.GetFileName(plugin.assetPath);
switch (filename)
{
case "libsteam_api.dylib":
didUpdate |= ResetPluginSettings(plugin, "AnyCPU", "OSX");
didUpdate |= SetCompatibleWithOSX(plugin);
break;
case "libsteam_api.so":
if (plugin.assetPath.Contains("x86_64"))
{
didUpdate |= ResetPluginSettings(plugin, "x86_64", "Linux");
didUpdate |= SetCompatibleWithLinux(plugin, BuildTarget.StandaloneLinux64);
}
else
{
didUpdate |= ResetPluginSettings(plugin, "x86", "Linux");
didUpdate |= SetCompatibleWithLinux(plugin, BuildTarget.StandaloneLinux);
}
break;
case "steam_api.dll":
case "steam_api64.dll":
if (plugin.assetPath.Contains("x86_64"))
{
didUpdate |= ResetPluginSettings(plugin, "x86_64", "Windows");
#if UNITY_5_3_OR_NEWER
didUpdate |= SetCompatibleWithWindows(plugin, BuildTarget.StandaloneWindows64);
#endif
}
else
{
didUpdate |= ResetPluginSettings(plugin, "x86", "Windows");
#if UNITY_5_3_OR_NEWER
didUpdate |= SetCompatibleWithWindows(plugin, BuildTarget.StandaloneWindows);
#endif
}
#if !UNITY_5_3_OR_NEWER
// We do this because Unity had a bug where dependent dll's didn't get loaded from the Plugins
// folder in actual builds. But they do in the editor now! So close... Unity bug number: 728945
// So ultimately we must keep using RedistCopy to copy steam_api[64].dll next to the .exe on builds, and
// we don't want a useless duplicate version of the dll ending up in the Plugins folder.
// This was fixed in Unity 5.3!
didUpdate |= SetCompatibleWithEditor(plugin);
#endif
break;
}
if (didUpdate)
{
plugin.SaveAndReimport();
}
}
}
static bool ResetPluginSettings(PluginImporter plugin, string CPU, string OS)
{
bool didUpdate = false;
if (plugin.GetCompatibleWithAnyPlatform() != false)
{
plugin.SetCompatibleWithAnyPlatform(false);
didUpdate = true;
}
if (plugin.GetCompatibleWithEditor() != true)
{
plugin.SetCompatibleWithEditor(true);
didUpdate = true;
}
if (plugin.GetEditorData("CPU") != CPU)
{
plugin.SetEditorData("CPU", CPU);
didUpdate = true;
}
if (plugin.GetEditorData("OS") != OS)
{
plugin.SetEditorData("OS", OS);
didUpdate = true;
}
return didUpdate;
}
static bool SetCompatibleWithOSX(PluginImporter plugin)
{
bool didUpdate = false;
#if UNITY_2017_3_OR_NEWER
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneOSX, true);
#else
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneOSXIntel, true);
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneOSXIntel64, true);
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneOSXUniversal, true);
#endif
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneLinux, false);
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneLinux64, false);
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneLinuxUniversal, false);
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneWindows, false);
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneWindows64, false);
return didUpdate;
}
static bool SetCompatibleWithLinux(PluginImporter plugin, BuildTarget platform)
{
bool didUpdate = false;
if (platform == BuildTarget.StandaloneLinux)
{
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneLinux, true);
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneLinux64, false);
}
else
{
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneLinux, false);
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneLinux64, true);
}
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneLinuxUniversal, true);
#if UNITY_2017_3_OR_NEWER
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneOSX, false);
#else
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneOSXIntel, false);
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneOSXIntel64, false);
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneOSXUniversal, false);
#endif
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneWindows, false);
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneWindows64, false);
return didUpdate;
}
static bool SetCompatibleWithWindows(PluginImporter plugin, BuildTarget platform)
{
bool didUpdate = false;
if (platform == BuildTarget.StandaloneWindows)
{
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneWindows, true);
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneWindows64, false);
}
else
{
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneWindows, false);
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneWindows64, true);
}
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneLinux64, false);
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneLinux, false);
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneLinuxUniversal, false);
#if UNITY_2017_3_OR_NEWER
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneOSX, false);
#else
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneOSXIntel, false);
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneOSXIntel64, false);
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneOSXUniversal, false);
#endif
return didUpdate;
}
static bool SetCompatibleWithEditor(PluginImporter plugin)
{
bool didUpdate = false;
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneLinux64, false);
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneLinux, false);
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneLinuxUniversal, false);
#if UNITY_2017_3_OR_NEWER
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneOSX, false);
#else
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneOSXIntel, false);
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneOSXIntel64, false);
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneOSXUniversal, false);
#endif
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneWindows, false);
didUpdate |= SetCompatibleWithPlatform(plugin, BuildTarget.StandaloneWindows64, false);
return didUpdate;
}
static bool SetCompatibleWithPlatform(PluginImporter plugin, BuildTarget platform, bool enable)
{
if (plugin.GetCompatibleWithPlatform(platform) == enable)
{
return false;
}
plugin.SetCompatibleWithPlatform(platform, enable);
return true;
}
#endif // UNITY_5 || UNITY_2017 || UNITY_2017_1_OR_NEWER
}

Просмотреть файл

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0900d89463138441bb65f51e556baabe
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

Просмотреть файл

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2013-2018 Riley Labrecque
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

Просмотреть файл

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 58d2228fde8c747dbb016e0cf7e340c4
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Просмотреть файл

@ -0,0 +1,32 @@
Steamworks.NET
=======
_Steamworks.NET_ is a C# Wrapper for Valve's Steamworks API, it can be used either with Unity or your C# based Application.
_Steamworks.NET_ was designed to be as close as possible to the original C++ API, as such the documentation provided from Valve largely covers usage of _Steamworks.NET_.
Niceties and C# Idioms can be easily implemented on top of _Steamworks.NET_.
_Steamworks.NET_ currently fully supports Windows, OSX, and Linux in both 32 and 64bit varieties. Currently building against Steamworks SDK 1.42.
* Author: [Riley Labrecque](https://github.com/rlabrecque)
* License: [MIT](http://www.opensource.org/licenses/mit-license.php)
* [Documentation](https://steamworks.github.io/)
* [Discussion Thread](http://steamcommunity.com/groups/steamworks/discussions/0/666827974770212954/)
* [Reporting Issues](https://github.com/rlabrecque/Steamworks.NET/issues)
* 1-on-1 support is available by donating $100 USD or greater.
* Support can be obtained via [Email](mailto:support@rileylabrecque.com), [Skype](http://rileylabrecque.com/skype), or [Steam](http://steamcommunity.com/id/rlabrecque)
* I can only help with Steamworks.NET specific issues, general API questions should be asked on the [Steamworks discussion board](http://steamcommunity.com/groups/steamworks/discussions).
[![Support via Paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YFZZER8VNXKRC)
[Installation Instructions](http://steamworks.github.io/installation/)
-----
Samples
-----
Check out these sample projects to get started:
* [Steamworks.NET Example](https://github.com/rlabrecque/Steamworks.NET-Example)
* [Steamworks.NET Test](https://github.com/rlabrecque/Steamworks.NET-Test)
* [Steamworks.NET ChatClient](https://github.com/rlabrecque/Steamworks.NET-ChatClient)
* [Steamworks.NET GameServerTest](https://github.com/rlabrecque/Steamworks.NET-GameServerTest)

Просмотреть файл

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 008d7012a0f2449fcaced391169a881f
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Просмотреть файл

@ -0,0 +1,178 @@
// The SteamManager is designed to work with Steamworks.NET
// This file is released into the public domain.
// Where that dedication is not recognized you are granted a perpetual,
// irrevokable license to copy and modify this file as you see fit.
//
// Version: 1.0.5
using UnityEngine;
using System.Collections;
using Steamworks;
//
// The SteamManager provides a base implementation of Steamworks.NET on which you can build upon.
// It handles the basics of starting up and shutting down the SteamAPI for use.
//
[DisallowMultipleComponent]
public class SteamManager : MonoBehaviour
{
private static SteamManager s_instance;
private static SteamManager Instance
{
get
{
if (s_instance == null)
{
return new GameObject("SteamManager").AddComponent<SteamManager>();
}
else
{
return s_instance;
}
}
}
private static bool s_EverInialized;
private bool m_bInitialized;
public static bool Initialized
{
get
{
return Instance.m_bInitialized;
}
}
private SteamAPIWarningMessageHook_t m_SteamAPIWarningMessageHook;
private static void SteamAPIDebugTextHook(int nSeverity, System.Text.StringBuilder pchDebugText)
{
Debug.LogWarning(pchDebugText);
}
private void Awake()
{
// Only one instance of SteamManager at a time!
if (s_instance != null)
{
Destroy(gameObject);
return;
}
s_instance = this;
if (s_EverInialized)
{
// This is almost always an error.
// The most common case where this happens is when SteamManager gets destroyed because of Application.Quit(),
// and then some Steamworks code in some other OnDestroy gets called afterwards, creating a new SteamManager.
// You should never call Steamworks functions in OnDestroy, always prefer OnDisable if possible.
throw new System.Exception("Tried to Initialize the SteamAPI twice in one session!");
}
// We want our SteamManager Instance to persist across scenes.
DontDestroyOnLoad(gameObject);
if (!Packsize.Test())
{
Debug.LogError("[Steamworks.NET] Packsize Test returned false, the wrong version of Steamworks.NET is being run in this platform.", this);
}
if (!DllCheck.Test())
{
Debug.LogError("[Steamworks.NET] DllCheck Test returned false, One or more of the Steamworks binaries seems to be the wrong version.", this);
}
try
{
// If Steam is not running or the game wasn't started through Steam, SteamAPI_RestartAppIfNecessary starts the
// Steam client and also launches this game again if the User owns it. This can act as a rudimentary form of DRM.
// Once you get a Steam AppID assigned by Valve, you need to replace AppId_t.Invalid with it and
// remove steam_appid.txt from the game depot. eg: "(AppId_t)480" or "new AppId_t(480)".
// See the Valve documentation for more information: https://partner.steamgames.com/doc/sdk/api#initialization_and_shutdown
if (SteamAPI.RestartAppIfNecessary(AppId_t.Invalid))
{
Application.Quit();
return;
}
}
catch (System.DllNotFoundException e) // We catch this exception here, as it will be the first occurence of it.
{
Debug.LogError("[Steamworks.NET] Could not load [lib]steam_api.dll/so/dylib. It's likely not in the correct location. Refer to the README for more details.\n" + e, this);
Application.Quit();
return;
}
// Initializes the Steamworks API.
// If this returns false then this indicates one of the following conditions:
// [*] The Steam client isn't running. A running Steam client is required to provide implementations of the various Steamworks interfaces.
// [*] The Steam client couldn't determine the App ID of game. If you're running your application from the executable or debugger directly then you must have a [code-inline]steam_appid.txt[/code-inline] in your game directory next to the executable, with your app ID in it and nothing else. Steam will look for this file in the current working directory. If you are running your executable from a different directory you may need to relocate the [code-inline]steam_appid.txt[/code-inline] file.
// [*] Your application is not running under the same OS user context as the Steam client, such as a different user or administration access level.
// [*] Ensure that you own a license for the App ID on the currently active Steam account. Your game must show up in your Steam library.
// [*] Your App ID is not completely set up, i.e. in [code-inline]Release State: Unavailable[/code-inline], or it's missing default packages.
// Valve's documentation for this is located here:
// https://partner.steamgames.com/doc/sdk/api#initialization_and_shutdown
m_bInitialized = SteamAPI.Init();
if (!m_bInitialized)
{
Debug.LogError("[Steamworks.NET] SteamAPI_Init() failed. Refer to Valve's documentation or the comment above this line for more information.", this);
return;
}
s_EverInialized = true;
}
// This should only ever get called on first load and after an Assembly reload, You should never Disable the Steamworks Manager yourself.
private void OnEnable()
{
if (s_instance == null)
{
s_instance = this;
}
if (!m_bInitialized)
{
return;
}
if (m_SteamAPIWarningMessageHook == null)
{
// Set up our callback to recieve warning messages from Steam.
// You must launch with "-debug_steamapi" in the launch args to recieve warnings.
m_SteamAPIWarningMessageHook = new SteamAPIWarningMessageHook_t(SteamAPIDebugTextHook);
SteamClient.SetWarningMessageHook(m_SteamAPIWarningMessageHook);
}
}
// OnApplicationQuit gets called too early to shutdown the SteamAPI.
// Because the SteamManager should be persistent and never disabled or destroyed we can shutdown the SteamAPI here.
// Thus it is not recommended to perform any Steamworks work in other OnDestroy functions as the order of execution can not be garenteed upon Shutdown. Prefer OnDisable().
private void OnDestroy()
{
if (s_instance != this)
{
return;
}
s_instance = null;
if (!m_bInitialized)
{
return;
}
SteamAPI.Shutdown();
}
private void Update()
{
if (!m_bInitialized)
{
return;
}
// Run Steam client callbacks
SteamAPI.RunCallbacks();
}
}

Просмотреть файл

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ef4bffeda13d7a748973ff9204401c07
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

1
Assets/steam_appid.txt Executable file
Просмотреть файл

@ -0,0 +1 @@
480

Просмотреть файл

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 03a6b6df3b6824d6eb29597e4ab12575
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Просмотреть файл

@ -100,6 +100,22 @@ namespace UnityEngine.Experimental.Input
get { return m_Name; }
}
/// <summary>
/// Name of control layout expected for controls bound to this action.
/// </summary>
/// <remarks>
/// This is optional and is null by default.
///
/// Constraining an action to a particular control layout allows determine the value
/// type and expected input behavior of an action without being reliant on any particular
/// binding.
/// </remarks>
public string expectedControlLayout
{
get { return m_ExpectedControlLayout; }
set { m_ExpectedControlLayout = value; }
}
/// <summary>
/// The map the action belongs to.
/// </summary>
@ -347,8 +363,9 @@ namespace UnityEngine.Experimental.Input
return Clone();
}
////REVIEW: for binding resolution, it would be best if this was an InternedString; however, for serialization, it has to be a string
////REVIEW: it would be best if these were InternedStrings; however, for serialization, it has to be strings
[SerializeField] internal string m_Name;
[SerializeField] internal string m_ExpectedControlLayout;
// For singleton actions, we serialize the bindings directly as part of the action.
// For any other type of action, this is null.

Просмотреть файл

@ -638,6 +638,7 @@ namespace UnityEngine.Experimental.Input
private struct ActionJson
{
public string name;
public string expectedControlLayout;
// Bindings can either be on the action itself (in which case the action name
// for each binding is implied) or listed separately in the action file.
@ -646,7 +647,11 @@ namespace UnityEngine.Experimental.Input
public static ActionJson FromAction(InputAction action)
{
// Bindings don't go on the actions when we write them.
return new ActionJson {name = action.m_Name};
return new ActionJson
{
name = action.m_Name,
expectedControlLayout = action.m_ExpectedControlLayout,
};
}
}
@ -787,6 +792,9 @@ namespace UnityEngine.Experimental.Input
// Create action.
var action = new InputAction(actionName);
action.m_ExpectedControlLayout = !string.IsNullOrEmpty(jsonAction.expectedControlLayout)
? jsonAction.expectedControlLayout
: null;
actionLists[mapIndex].Add(action);
// Add bindings.
@ -846,6 +854,9 @@ namespace UnityEngine.Experimental.Input
// Create action.
var action = new InputAction(jsonAction.name);
action.m_ExpectedControlLayout = !string.IsNullOrEmpty(jsonAction.expectedControlLayout)
? jsonAction.expectedControlLayout
: null;
actionLists[mapIndex].Add(action);
// Add bindings.

Просмотреть файл

@ -213,7 +213,8 @@ namespace UnityEngine.Experimental.Input
}
}
public static InputAction AddAction(this InputActionMap map, string name, string binding = null, string interactions = null, string groups = null)
public static InputAction AddAction(this InputActionMap map, string name, string binding = null,
string interactions = null, string groups = null, string expectedControlLayout = null)
{
if (map == null)
throw new ArgumentNullException("map");
@ -228,6 +229,7 @@ namespace UnityEngine.Experimental.Input
// Append action to array.
var action = new InputAction(name);
action.expectedControlLayout = expectedControlLayout;
ArrayHelpers.Append(ref map.m_Actions, action);
action.m_ActionMap = map;

Просмотреть файл

@ -92,6 +92,7 @@ namespace UnityEngine.Experimental.Input.Editor
public static InputControlLayout TryGetLayout(string name)
{
Refresh();
return s_Cache.FindOrLoadLayout(name);
}

Просмотреть файл

@ -40,8 +40,10 @@ namespace UnityEngine.Experimental.Input.Editor
: base(state)
{
m_ApplyAction = applyAction;
//FIXME: this requires 2018.3 to compile
//foldoutOverride += OnFoldoutDraw;
////REVIEW: good enough like this for 2018.2?
#if UNITY_2018_3_OR_NEWER
foldoutOverride += OnFoldoutDraw;
#endif
Reload();
}

Просмотреть файл

@ -7,6 +7,8 @@ using UnityEditor;
using UnityEditorInternal;
using UnityEngine.Experimental.Input.Utilities;
////REVIEW: "properties" seems wrong; these seems to revert to "parameters" specifically
namespace UnityEngine.Experimental.Input.Editor.Lists
{
class InteractionsReorderableReorderableList : PropertiesReorderableList

Просмотреть файл

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0332615c34a2b464680d0b6435a22738
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Просмотреть файл

@ -264,8 +264,8 @@ namespace UnityEngine.Experimental.Input.Editor
var id = 1;
var usages = BuildTreeForUsages(ref id);
var devices = AddChild(root, "Devices", ref id);
var products = AddChild(root, "Products", ref id);
var devices = AddChild(root, "Abstract Devices", ref id);
var products = AddChild(root, "Specific Devices", ref id);
foreach (var layout in EditorInputControlLayoutCache.allDeviceLayouts)
{

Просмотреть файл

@ -1282,7 +1282,7 @@ namespace UnityEngine.Experimental.Input
#endif
// Send an initial Update so that user methods such as Start and Awake
// can access the input devices prior to thier Upate methods.
// can access the input devices prior to their Update methods.
Update();
}
@ -1322,6 +1322,10 @@ namespace UnityEngine.Experimental.Input
#if UNITY_EDITOR || UNITY_ANDROID || UNITY_IOS || UNITY_TVOS || UNITY_WSA
Plugins.OnScreen.OnScreenSupport.Initialize();
#endif
#if (UNITY_EDITOR || UNITY_STANDALONE) && UNITY_ENABLE_STEAM_CONTROLLER_SUPPORT
Plugins.Steam.SteamSupport.Initialize();
#endif
}
#endif

Просмотреть файл

@ -0,0 +1,10 @@
#if (UNITY_STANDALONE || UNITY_EDITOR) && UNITY_ENABLE_STEAM_CONTROLLER_SUPPORT
namespace UnityEngine.Experimental.Input.Plugins.Steam
{
public interface IStreamControllerAPI
{
}
}
#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:

Просмотреть файл

@ -1,3 +1,5 @@
#if (UNITY_STANDALONE || UNITY_EDITOR) && UNITY_ENABLE_STEAM_CONTROLLER_SUPPORT
namespace UnityEngine.Experimental.Input.Plugins.Steam
{
/// <summary>
@ -5,7 +7,7 @@ namespace UnityEngine.Experimental.Input.Plugins.Steam
/// </summary>
/// <remarks>
/// Unlike other controllers, the Steam controller is somewhat of an amorphic input
/// device which gains specfic shape only in combination with VDF files. These files
/// device which gains specific shape only in combination with VDF files. 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.
///
@ -15,7 +17,8 @@ namespace UnityEngine.Experimental.Input.Plugins.Steam
/// </remarks>
public class SteamController : InputDevice
{
public const string kSteamInterface = "Steam";
}
//add support for constructing InputControlLayouts from Steam VDF files
}
#endif // (UNITY_STANDALONE || UNITY_EDITOR) && UNITY_ENABLE_STEAM_CONTROLLER_SUPPORT

Просмотреть файл

@ -1,4 +1,4 @@
////TODO: write a ScriptedImporter for VDF files which automatically generates a layout
#if (UNITY_STANDALONE || UNITY_EDITOR) && UNITY_ENABLE_STEAM_CONTROLLER_SUPPORT
namespace UnityEngine.Experimental.Input.Plugins.Steam
{
@ -11,3 +11,5 @@ namespace UnityEngine.Experimental.Input.Plugins.Steam
}
}
}
#endif // (UNITY_STANDALONE || UNITY_EDITOR) && UNITY_ENABLE_STEAM_CONTROLLER_SUPPORT

Просмотреть файл

@ -1,6 +0,0 @@
namespace UnityEngine.Experimental.Input.Plugins.Steam
{
public class SteamTests
{
}
}

Просмотреть файл

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: e66d8e0ae15b4ae5ab2d917c90cd824b
timeCreated: 1511133961

Просмотреть файл

@ -1,10 +1,641 @@
////REVIEW: What we really want, I think, is a *layout* in the system that represents the device
//// with the actions being controls. Exporting InputActions to .VDF seems to not make much sense
#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.Experimental.Input.Controls;
using UnityEngine.Experimental.Input.Editor;
using UnityEngine.Experimental.Input.Utilities;
namespace UnityEngine.Experimental.Input.Plugins.Steam
////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.Experimental.Input.Plugins.Steam.Editor
{
// Exports an .inputactions asset into Steam .VDF format.
public class SteamVDFExporter
/// <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>
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("#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.RegisterControlLayout<");
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.RegisterControlLayout<");
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") && 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") && 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
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") && 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>
/// 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.expectedControlLayout).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 InputActionMap[] ConvertInputActionsFromVDF(string vdf)
{
throw new NotImplementedException();
}
public static string GetSteamControllerInputType(InputAction action)
{
if (action == null)
throw new ArgumentNullException("action");
// Make sure we have an expected control layout.
var expectedControlLayout = action.expectedControlLayout;
if (string.IsNullOrEmpty(expectedControlLayout))
throw new Exception(string.Format(
"Cannot determine Steam input type for action '{0}' that has no associated expected control layout",
action));
// Try to fetch the layout.
var layout = EditorInputControlLayoutCache.TryGetLayout(expectedControlLayout);
if (layout == null)
throw new Exception(string.Format(
"Cannot determine Steam input type for action '{0}'; cannot find layout '{1}'", action,
expectedControlLayout));
// 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 Exception(string.Format(
"Cannot determine Steam input type for action '{0}'; layout '{1}' with control type '{2}' has no known representation in the Steam controller API",
action, expectedControlLayout, controlType.Name));
}
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 Exception(string.Format("Parse error at {0} in '{1}'; not expecting any more input", position, vdf));
return result;
}
private bool ParseKeyValuePair(Dictionary<string, object> result)
{
var key = ParseString();
if (key.isEmpty)
return false;
SkipWhitespace();
if (position == length)
throw new Exception(string.Format("Expecting value or object at position {0} in '{1}'",
position, 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 Exception(string.Format("Expecting value or object at position {0} in '{1}'",
position, 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 Exception(string.Format("Expecting '}}' at position {0} in '{1}'", position, vdf));
++position;
return result;
}
private void SkipWhitespace()
{
while (position < length && char.IsWhiteSpace(vdf[position]))
++position;
}
}
[MenuItem("Assets/Export to Steam In-Game Actions File...", true)]
private static bool IsExportContextMenuItemEnabled()
{
return Selection.activeObject is InputActionAsset;
}
[MenuItem("Assets/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/Generate C# Input Device Class...", 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/Generate C# Input Device Class...")]
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,10 @@
#if (UNITY_STANDALONE || UNITY_EDITOR) && UNITY_ENABLE_STEAMWORKSNET_STEAM_CONTRLLER_SUPPORT
namespace UnityEngine.Experimental.Input.Plugins.Steam
{
public class SteamworksNETControllerAPI
{
}
}
#endif // (UNITY_STANDALONE || UNITY_EDITOR) && UNITY_ENABLE_STEAMWORKSNET_STEAM_CONTROLLER_SUPPORT

Просмотреть файл

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4c4583dbeb0e84e0ab6ef74b8aa8ad86
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

Просмотреть файл

@ -681,7 +681,7 @@ partial class CoreTests
{
var map = new InputActionMap("test");
map.AddAction(name: "action1", binding: "/gamepad/leftStick")
map.AddAction(name: "action1", expectedControlLayout: "Button", binding: "/gamepad/leftStick")
.AppendBinding("/gamepad/rightStick")
.WithGroup("group")
.WithProcessor("deadzone");
@ -695,6 +695,8 @@ partial class CoreTests
Assert.That(maps[0].actions, Has.Count.EqualTo(2));
Assert.That(maps[0].actions[0].name, Is.EqualTo("action1"));
Assert.That(maps[0].actions[1].name, Is.EqualTo("action2"));
Assert.That(maps[0].actions[0].expectedControlLayout, Is.EqualTo("Button"));
Assert.That(maps[0].actions[1].expectedControlLayout, Is.Null);
Assert.That(maps[0].actions[0].bindings, Has.Count.EqualTo(2));
Assert.That(maps[0].actions[1].bindings, Has.Count.EqualTo(1));
Assert.That(maps[0].actions[0].bindings[0].groups, Is.Null);
@ -2445,6 +2447,22 @@ partial class CoreTests
InputSystem.Update();
}
// It's possible to associate a control layout name with an action. This is useful both for
// characterizing the expected input behavior as well as to make control picking (both at
// edit time and in the game) easier.
[Test]
[Category("Actions")]
public void Actions_CanHaveExpectedControlLayout()
{
var action = new InputAction();
Assert.That(action.expectedControlLayout, Is.Null);
action.expectedControlLayout = "Button";
Assert.That(action.expectedControlLayout, Is.EqualTo("Button"));
}
[Test]
[Category("Actions")]
public void TODO_Actions_HaveStableIDs()

Просмотреть файл

@ -0,0 +1,133 @@
#if (UNITY_STANDALONE || UNITY_EDITOR) && UNITY_ENABLE_STEAM_CONTROLLER_SUPPORT
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.Experimental.Input;
using UnityEngine.Experimental.Input.Plugins.Steam.Editor;
public class SteamTests : InputTestFixture
{
#if UNITY_EDITOR
[Test]
[Category("Editor")]
public void Editor_CanGenerateInputDeviceBasedOnSteamIGAFile()
{
// Create an InputActions setup and convert it to Steam IGA.
var asset = ScriptableObject.CreateInstance<InputActionAsset>();
var actionMap1 = new InputActionMap("map1");
var actionMap2 = new InputActionMap("map2");
actionMap1.AddAction("buttonAction", expectedControlLayout: "Button");
actionMap1.AddAction("axisAction", expectedControlLayout: "Axis");
actionMap1.AddAction("stickAction", expectedControlLayout: "Stick");
actionMap2.AddAction("vector2Action", expectedControlLayout: "Vector2");
asset.AddActionMap(actionMap1);
asset.AddActionMap(actionMap2);
var vdf = SteamIGAConverter.ConvertInputActionsToSteamIGA(asset);
// Generate a C# input device from the Steam IGA file.
var generatedCode = SteamIGAConverter.GenerateInputDeviceFromSteamIGA(vdf, "My.Namespace.MySteamController");
Assert.That(generatedCode, Contains.Substring("#if (UNITY_EDITOR || UNITY_STANDALONE) && UNITY_ENABLE_STEAM_CONTROLLER_SUPPORT"));
Assert.That(generatedCode, Contains.Substring("namespace My.Namespace\n"));
Assert.That(generatedCode, Contains.Substring("public class MySteamController : SteamController, IInputUpdateCallbackReceiver\n"));
Assert.That(generatedCode, Contains.Substring("public unsafe struct MySteamControllerState : IInputStateTypeInfo\n"));
Assert.That(generatedCode, Contains.Substring("[InitializeOnLoad]"));
Assert.That(generatedCode, Contains.Substring("[RuntimeInitializeOnLoadMethod"));
Assert.That(generatedCode, Contains.Substring("new FourCC('M', 'y', 'S', 't')"));
Assert.That(generatedCode, Contains.Substring("protected override void FinishSetup(InputDeviceBuilder builder)"));
Assert.That(generatedCode, Contains.Substring("base.FinishSetup(builder);"));
Assert.That(generatedCode, Contains.Substring("new InputDeviceMatcher"));
Assert.That(generatedCode, Contains.Substring("WithInterface(\"Steam\")"));
}
[Test]
[Category("Editor")]
public void Editor_CanConvertInputActionsToSteamIGAFormat()
{
var asset = ScriptableObject.CreateInstance<InputActionAsset>();
var actionMap1 = new InputActionMap("map1");
var actionMap2 = new InputActionMap("map2");
actionMap1.AddAction("buttonAction", expectedControlLayout: "Button");
actionMap1.AddAction("axisAction", expectedControlLayout: "Axis");
actionMap1.AddAction("stickAction", expectedControlLayout: "Stick");
actionMap2.AddAction("vector2Action", expectedControlLayout: "Vector2");
asset.AddActionMap(actionMap1);
asset.AddActionMap(actionMap2);
var vdf = SteamIGAConverter.ConvertInputActionsToSteamIGA(asset);
var dictionary = SteamIGAConverter.ParseVDF(vdf);
// Top-level key "In Game Actions".
Assert.That(dictionary.Count, Is.EqualTo(1));
Assert.That(dictionary, Contains.Key("In Game Actions").With.TypeOf<Dictionary<string, object>>());
// "actions" and "localization" inside "In Game Actions".
var inGameActions = (Dictionary<string, object>)dictionary["In Game Actions"];
Assert.That(inGameActions, Contains.Key("actions"));
Assert.That(inGameActions["actions"], Is.TypeOf<Dictionary<string, object>>());
Assert.That(inGameActions, Contains.Key("localization"));
Assert.That(inGameActions["localization"], Is.TypeOf<Dictionary<string, object>>());
Assert.That(inGameActions.Count, Is.EqualTo(2));
// Two action maps inside "actions".
var actions = (Dictionary<string, object>)inGameActions["actions"];
Assert.That(actions, Contains.Key("map1"));
Assert.That(actions["map1"], Is.TypeOf<Dictionary<string, object>>());
Assert.That(actions, Contains.Key("map2"));
Assert.That(actions["map2"], Is.TypeOf<Dictionary<string, object>>());
Assert.That(actions.Count, Is.EqualTo(2));
// Three actions inside "map1".
var map1 = (Dictionary<string, object>)actions["map1"];
Assert.That(map1, Contains.Key("title"));
Assert.That(map1, Contains.Key("StickPadGyro"));
Assert.That(map1, Contains.Key("AnalogTrigger"));
Assert.That(map1, Contains.Key("Button"));
Assert.That(map1.Count, Is.EqualTo(4));
Assert.That(map1["title"], Is.EqualTo("#Set_map1"));
Assert.That(map1["StickPadGyro"], Is.TypeOf<Dictionary<string, object>>());
Assert.That(map1["AnalogTrigger"], Is.TypeOf<Dictionary<string, object>>());
Assert.That(map1["Button"], Is.TypeOf<Dictionary<string, object>>());
var stickPadGyro1 = (Dictionary<string, object>)map1["StickPadGyro"];
Assert.That(stickPadGyro1, Has.Count.EqualTo(1));
Assert.That(stickPadGyro1, Contains.Key("stickAction"));
Assert.That(stickPadGyro1["stickAction"], Is.TypeOf<Dictionary<string, object>>());
var stickAction = (Dictionary<string, object>)stickPadGyro1["stickAction"];
Assert.That(stickAction, Contains.Key("title"));
Assert.That(stickAction.Count, Is.EqualTo(2));
Assert.That(stickAction["title"], Is.EqualTo("#Action_map1_stickAction"));
// One action inside "map2".
var map2 = (Dictionary<string, object>)actions["map2"];
Assert.That(map2, Contains.Key("title"));
Assert.That(map2["title"], Is.EqualTo("#Set_map2"));
// Localization strings.
var localization = (Dictionary<string, object>)inGameActions["localization"];
Assert.That(localization.Count, Is.EqualTo(1));
Assert.That(localization, Contains.Key("english"));
Assert.That(localization["english"], Is.TypeOf<Dictionary<string, object>>());
var english = (Dictionary<string, object>)localization["english"];
Assert.That(english, Contains.Key("Set_map1"));
Assert.That(english, Contains.Key("Set_map2"));
Assert.That(english, Contains.Key("Action_map1_buttonAction"));
Assert.That(english, Contains.Key("Action_map1_axisAction"));
Assert.That(english, Contains.Key("Action_map1_stickAction"));
Assert.That(english, Contains.Key("Action_map2_vector2Action"));
Assert.That(english["Set_map1"], Is.EqualTo("map1"));
Assert.That(english["Set_map2"], Is.EqualTo("map2"));
Assert.That(english["Action_map1_buttonAction"], Is.EqualTo("buttonAction"));
Assert.That(english["Action_map1_axisAction"], Is.EqualTo("axisAction"));
Assert.That(english["Action_map1_stickAction"], Is.EqualTo("stickAction"));
Assert.That(english["Action_map2_vector2Action"], Is.EqualTo("vector2Action"));
Assert.That(english.Count, Is.EqualTo(6));
}
#endif
}
#endif // (UNITY_STANDALONE || UNITY_EDITOR) && UNITY_ENABLE_STEAM_CONTROLLER_SUPPORT

Просмотреть файл

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 471f8fe5e815749d9bf78d2517877ff4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

Просмотреть файл

@ -645,7 +645,9 @@ PlayerSettings:
webGLUseEmbeddedResources: 0
webGLCompressionFormat: 1
webGLLinkerTarget: 0
scriptingDefineSymbols: {}
webGLThreadsSupport: 0
scriptingDefineSymbols:
1: UNITY_ENABLE_STEAM_CONTROLLER_SUPPORT;UNITY_ENABLE_STEAMWORKSNET_STEAM_CONTROLLER_SUPPORT
platformArchitecture:
iOS: 0
scriptingBackend:
@ -654,6 +656,7 @@ PlayerSettings:
iOS: 1
il2cppCompilerConfiguration:
Android: 0
managedStrippingLevel: {}
incrementalIl2cppBuild: {}
allowUnsafeCode: 0
additionalIl2CppArgs:

1
steam_appid.txt Executable file
Просмотреть файл

@ -0,0 +1 @@
480