Skip to content

Instantly share code, notes, and snippets.

@lonewolfwilliams
Created October 22, 2016 07:26
Show Gist options
  • Save lonewolfwilliams/58c01117c008a6ccc4bf35b189d2ff69 to your computer and use it in GitHub Desktop.
Save lonewolfwilliams/58c01117c008a6ccc4bf35b189d2ff69 to your computer and use it in GitHub Desktop.
Simple, fluent, wrapper for SteamController in Unity3d (requires Steamworks.NET & valid app ID)
using UnityEngine;
using System.Collections;
using Steamworks;
using UnityEngine.UI;
using System.Collections.Generic;
using System;
/// <summary>
/// Gareth Williams - Lonewolfwilliams LTD
/// Wraps the Steamcontroller part of the Steamworks API in a simplified, fluent interface
/// Interaction with the Steamcontroller is based on a concept of ActionSets and Actions, where ActionSets define 'contexts' for user interaction and
/// Actions are the actions available to the player in that 'context' ActionSets are defined via a global configuration file (game_actions_<appID>.vdf) and
/// Actions are mapped to controls via the steam client in big-picture mode (big picture mode->library-><game name>->manage game->controller configuration)
/// More info here: https://partner.steamgames.com/documentation/steamcontroller
///
/// You can find the global configuration file for the game in <steam install dir>/controller_config/game_actions_<appID>.vdf
/// More info on install directories here:http://pcgamingwiki.com/wiki/Glossary:Game_data#Steam
/// This wrapper depends on Steamworks.NET
/// https://steamworks.github.io
///
/// NOTE:
/// To test this in-editor, it seems like you need to:
/// - follow the instructions for Steamworks.NET
/// - follow the instructions in the steamworks steamcontroller doc (linked above)
/// - launch the steam client with -forceControllerAppID<My APP> (IE from terminal on mac)
/// - put the steam client in big-picture mode
/// - connect the controller
/// - run the unity game in editor.
///
/// I found -dev and -console options are also useful when launching the steam client to verify which controllers and configs were being loaded.
///
/// An example of usage could look like this:
/// SteamController sc = SteamController.Create().Init().First().ActivateActionSet(SCConsts.IN_GAME_ACTION_SET);
/// bool pausePressed = sc.GetDigitalAction(SCConsts.PAUSE_ACTION_DIGITAL);
/// ...rest of code...
/// sc.Shutdown();
///
/// </summary>
public class SteamController : MonoBehaviour
{
private const int UNINITIALISED = -1;
private int m_currentController = UNINITIALISED;
private Dictionary<string, ControllerAnalogActionHandle_t> m_cachedActionsAnalogue = new Dictionary<string, ControllerAnalogActionHandle_t>();
private Dictionary<string, ControllerDigitalActionHandle_t> m_cachedActionsDigital = new Dictionary<string, ControllerDigitalActionHandle_t>();
private Dictionary<string, ControllerActionSetHandle_t> m_cachedActionSets = new Dictionary<string, ControllerActionSetHandle_t>();
private Vector2 m_cachedPosition = Vector2.zero;
private ControllerHandle_t[] m_controllers;
private bool m_initialised;
/// <summary>
/// Create an instance of the SteamController wrapper as a component added to a new GameObject in the root of the scene
/// </summary>
public static SteamController Create()
{
GameObject go = new GameObject();
go.name = "SteamController";
return SteamController.Create (go.transform);
}
/// <summary>
/// Create an instance of the SteamController wrapper as a component added to the specified parent
/// </summary>
/// <param name="parent">Parent to add the SteamController component to</param>
public static SteamController Create(Transform parent)
{
return parent.gameObject.AddComponent<SteamController>();
}
/// <summary>
/// Initialise the SteamController API
/// </summary>
public SteamController Init()
{
if(false == m_initialised) StartCoroutine(InitialiseSteamController());
return this;
}
/// <summary>
/// Make the first controller the current controller for the SteamController Wrapper
/// </summary>
public SteamController First()
{
StartCoroutine(WaitForInit(()=>
{
m_currentController = 0;
Debug.Log("got controller: " + m_controllers[m_currentController]);
}));
return this;
}
/// <summary>
/// Activates the specified action set.
/// Action Sets are the actions available to the player in a given 'context'
/// EG: player may be able to Run and Walk in the 'context' of the Game, but not in the context of the Menu.
/// Action sets are identified by name in the controller_config .vdf file
/// </summary>
/// <param name="named">The name of the action set as defined in the .vdf config file</param>
public SteamController ActivateActionSet(string named)
{
StartCoroutine(WaitForInit(()=>
{
if(false == m_cachedActionSets.ContainsKey(named)) m_cachedActionSets.Add(named, Steamworks.SteamController.GetActionSetHandle(named));
ControllerActionSetHandle_t h = m_cachedActionSets[named];
Steamworks.SteamController.ActivateActionSet(m_controllers[m_currentController], h);
//m_currentActionSet = Steamworks.SteamController.GetCurrentActionSet(m_controllers[m_currentController]);
}));
return this;
}
/// <summary>
/// Get the state of a named digital action from the currently active action set
/// Digital actions are actions that can be mapped to buttons (Note: not triggers) on the controller
/// </summary>
/// <returns><c>true</c>if the button is down<c>false</c>if the button is up (or the button is not part of the set)</returns>
/// <param name="named">The name of the action from the actionset as defined in the .vdf config file</param>
public bool GetDigitalAction(string named)
{
if(false == m_initialised) return false;
if(false == m_cachedActionsDigital.ContainsKey(named)) m_cachedActionsDigital.Add(named, Steamworks.SteamController.GetDigitalActionHandle(named));
ControllerDigitalActionHandle_t h = m_cachedActionsDigital[named];
ControllerDigitalActionData_t dData = Steamworks.SteamController.GetDigitalActionData(m_controllers[m_currentController], h);
//if the digital controller isn't part of the ActionSet, it isn't active...
return dData.bActive && dData.bState;
}
/// <summary>
/// Get the state of a named analogue action from the currently active action set
/// Analogue actions are actions that can be mapped to either the thumbstick, trackpads, triggers or gyro on the controller
/// </summary>
/// <returns>the x and y values for the analogue action (will be zero if the action is not part of the set)</returns>
/// <param name="named">The name of the action from the actionset as defined in the .vdf config file</param>
public Vector2 GetAnalogueAction(string named)
{
if(false == m_initialised) return Vector2.zero;
if(false == m_cachedActionsAnalogue.ContainsKey(named)) m_cachedActionsAnalogue.Add(named, Steamworks.SteamController.GetAnalogActionHandle(named));
ControllerAnalogActionHandle_t h = m_cachedActionsAnalogue[named];
ControllerAnalogActionData_t aData = Steamworks.SteamController.GetAnalogActionData(m_controllers[m_currentController], h);
//if the analogue controller isn't part of the ActionSet, x and y will be 0;
m_cachedPosition.x = aData.x;
m_cachedPosition.y = aData.y;
return m_cachedPosition;
}
/// <summary>
/// Shutdown the steamcontroller API and clear internal state of wrapper
/// (NOTE: this does not shut down the Steam API)
/// </summary>
public SteamController Shutdown()
{
Debug.Log("shutting down");
Steamworks.SteamController.Shutdown();
m_cachedActionsAnalogue.Clear ();
m_cachedActionsDigital.Clear ();
m_cachedActionSets.Clear ();
m_currentController = UNINITIALISED;
m_cachedPosition = Vector2.zero;
m_controllers = new ControllerHandle_t[16];
return this;
}
private IEnumerator InitialiseSteamController()
{
while(false == SteamManager.Initialized)
{
Debug.Log("waiting for api...");
yield return null;
}
Debug.Log("initialised with user: " + SteamFriends.GetPersonaName() + " app id: " + Steamworks.SteamUtils.GetAppID());
Debug.Log("is steam running: " + Steamworks.SteamAPI.IsSteamRunning());
Steamworks.SteamController.Init();
m_controllers = new ControllerHandle_t[Constants.STEAM_CONTROLLER_MAX_COUNT];
int found = 0;
while(found == 0)
{
Debug.Log("waiting for at least one active controller...");
found = Steamworks.SteamController.GetConnectedControllers(m_controllers);
yield return null;
}
Debug.Log("controller found - Initialised");
m_initialised = true;
}
private IEnumerator WaitForInit(Action then)
{
while(false == m_initialised) yield return null;
then();
}
}
/// <summary>
/// Helper class defines constants specific to the game configuration (.vdf file)
/// </summary>
public class SCConsts
{
//action sets form the 'context' for the actions that are available
public const string IN_GAME_ACTION_SET = "InGameControls";
//analogue actions
public const string MOVE_ACTION_ANALOGUE = "Move";
public const string CAMERA_ACTION_ANALAOGUE = "Camera";
public const string THROTTLE_ACTION_ANALOGUE = "Throttle";
public const string MENU_ACTION_ANALOGUE = "MenuControls";
//digital actions
public const string PAUSE_ACTION_DIGITAL = "pause_menu";
}
public class TestSteamController : MonoBehaviour
{
IEnumerator Start()
{
SteamController sc = SteamController.Create().Init().First().ActivateActionSet(SCConsts.IN_GAME_ACTION_SET);
bool pausePressed = false;
while(false == pausePressed)
{
pausePressed = sc.GetDigitalAction(SCConsts.PAUSE_ACTION_DIGITAL);
yield return null;
}
Debug.Log("pause pressed");
yield return null;
sc.Shutdown();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment