The Godot Barn
Sign in
Sign in
Home
News & updates
Explore
Articles
Snippets
Shaders
Themes
Submit content
Sign in to submit content
Give or get help
Tutorials
Questions
Conversations
Coming soon!
GodotSteam Tutorials - Input
0
Description
"This tutorial covers setting up and handling controller input through the Steam Input class. Written by HarmanSS and Furd.\r\n\r\n??? guide Relevant GodotSteam classes and functions\r\n* [Input class](https:\/\/godotsteam.com\/classes\/input.md)\r\n\t* [activateActionSet()](https:\/\/godotsteam.com\/classes\/input.md#activateactionset)\r\n\t* [enableDeviceCallbacks()](https:\/\/godotsteam.com\/classes\/input.md#enabledevicecallbacks)\r\n\t* [getActionSetHandle()](https:\/\/godotsteam.com\/classes\/input.md#getactionsethandle)\r\n\t* [getAnalogActionData()](https:\/\/godotsteam.com\/classes\/input.md#getanalogactiondata)\r\n\t* [getAnalogActionHandle()](https:\/\/godotsteam.com\/classes\/input.md#getanalogactionhandle)\r\n\t* [getAnalogActionOrigins()](https:\/\/godotsteam.com\/classes\/input.md#getanalogactionorigins)\r\n\t* [getDigitalActionData()](https:\/\/godotsteam.com\/classes\/input.md#getdigitalactiondata)\r\n\t* [getDigitalActionHandle()](https:\/\/godotsteam.com\/classes\/input.md#getdigitalactionhandle)\r\n\t* [getDigitalActionOrigins()](https:\/\/godotsteam.com\/classes\/input.md#getdigitalactionorigins)\r\n\t* [inputInit()](https:\/\/godotsteam.com\/classes\/input.md#inputinit)\r\n\t* [runFrame()](https:\/\/godotsteam.com\/classes\/input.md#runframe)\r\n???\r\n\t\r\n!!! warning Note\r\nYou may want to [double-check our Initialization tutorial](https:\/\/thegodotbarn.com\/contributions\/tutorial\/49\/godotsteam-tutorials-initializing-steam) to set up initialization and callbacks functionality if you haven't done so already.\r\n!!!\r\n\r\n## Prerequisites{.block}\r\n\r\nMake sure you have [setup GodotSteam](https:\/\/godotsteam.com\/tutorials\/initializing\/) and have an online depot \/ build on Steamworks; it does not have to be released to the public.\r\n\r\n!!! info Assumption\r\nThis guide assumes the player will use at most use 1 controller. There are some changes suggested that you can use to handle multiple controllers.\r\n!!!\r\n\r\n## Setting up .vdf files{.block}\r\n\r\nThis info is available on [Steamworks](https:\/\/partner.steamgames.com\/doc\/features\/steam_controller\/getting_started_for_devs#iga), although it is a bit dated. So I am gonna provide more of high-level info here.\r\n\r\n### In Game Actions file\r\n\r\nYou can download this simple [IGA example](https:\/\/steamcdn-a.akamaihd.net\/steam\/partner\/controller\/game_actions_X.vdf) to get started. Alternatively, you can also download one of the [templates provided by Steam for specific genres](https:\/\/partner.steamgames.com\/doc\/features\/steam_controller\/iga_examples). To understand how IGA works, [see this breakdown of IGA format](https:\/\/partner.steamgames.com\/doc\/features\/steam_controller\/iga_file).\r\n\r\nRename the downloaded file to whatever you see fit and place wherever works in your project. I call it **steam_input_manifest.vdf** and place it in my **{game}\/steam_input\/** folder.\r\n\r\n[Log into the Steamworks Partner site](https:\/\/partner.steamgames.com) and go to your Steamworks Admin in the Application tab:\r\n\r\n- Choose the **Steam Input** from the drop down.\r\n- Choose the controllers you want to support.\r\n- Choose **Custom Configuration** for the most amount of control utilizing Steam Input and sort of Godot-independent controls.\r\n- Enter the path for this custom config (.vdf) file relative to your game directory; using my example it would be **steam_input\/steam_input_manifest.vdf**.\r\n- Save and publish your changes on Steamworks.\r\n \r\n\r\n\r\nCopy the example\/template **IGA** file to the correct location that you set in your game folder; you can right-click on your game in Steam and use **Manage -> Browse local files** to quickly open the game folder. Set up all the action-sets, actions and layers in the **.vdf** file as per your needs.\r\n\r\n### Action Manifest file\r\n\r\nYou might need to restart steam with **-dev** argument. Open Steam settings, there should be **Developer** tab, and enable both options under **Steam Input**.\r\n\r\n\r\n\r\nConnect a controller then press the controller icon to open controller configuration. If you see any errors, it is usually because of a typo or incorrect case.\r\n\r\nYou should see your action-sets and layers. Make a default config and save it using **Export Layout**. Below is an example from Euro Truck Simulator 2, as you can see it has multiple action-sets and action-set layers as well as custom inputs.\r\n\r\n\r\n\r\nUse the commands below to dump the config in your home or documents folder (it will be called **config_{appid}\\_controller\\_{type}.vdf**). You need the controller to be connected, otherwise these commands might not work.\r\n\r\n::: tabs\r\n@tab:active Windows Powershell\r\n```powershell\r\nstart steam:\/\/dumpcontrollerconfig?appid=X\r\n```\r\n\r\n@tab Linux (bash)\r\n```bash\r\nxdg-open \"steam:\/\/dumpcontrollerconfig?appid=X\"\r\n```\r\n:::\r\n\r\nMove the config to your game input location (for me, it would be **{game}\/steam_input\/**). Rename the file if you want to.\r\n\r\nOpen your IGA file and change the first line from **In Game Actions** to **Action Manifest** and add a **configuration** block (before the actions block).\r\nEach controller has a specific name that you can find in [Action Manifest guide](https:\/\/partner.steamgames.com\/doc\/features\/steam_controller\/action_manifest_file#3) and the path is relative to the manifest.\r\n\r\n```diff title=\"steam_input_manifest.vdf\"\r\n-\"In Game Actions\"\r\n+\"Action Manifest\"\r\n+\"configurations\"\r\n+\t{\r\n+\t\t\"controller_
\"\r\n+\t\t{\r\n+\t\t\t\"0\"\r\n+\t\t\t{\r\n+\t\t\t\t\"path\" \"
.vdf\"\r\n+\t\t\t}\r\n+\t\t}\r\n+ }\r\n \"actions\"\r\n```\r\n\r\n!!! info Exporting Official Configuration\r\nAfter you have a finalized configuration, you can use the **Export Offical Configuration** option from the Steam client app, then dump that config and ship with your game.\r\n!!!\r\n\r\n## InputController{.block}\r\n\r\nThis section uses [Furd's blog](https:\/\/furd.dev\/blog\/steam-input\/) as a base with some modification and additions.\r\n\r\n### Setup\r\n\r\nCreate a script **input_controller.gd** and make it an autoload named **InputController**.\r\nNext, in your **Steam** autoload\/script, add the initialization logic inside the block where Steam is detected as running. This ensures that Steam Input is only set up when the Steam client is available.\r\n\r\nIf Steam is not running, disable processing for InputController so that only Godot's default input system is used. A simple structure for this logic could look like:\r\n\r\n```gdscript title=\"steam.gd\"\r\nfunc _ready() -> void:\r\n# steam initialization (I have auto-init enabled, otherwise you would need to manually initialise it), like this:\r\n# var status := Steam.steamInitEx(app_id, true\/false)\r\n\tvar status := Steam.get_steam_init_result()\r\n\tif status.status == Steam.STEAM_API_INIT_RESULT_OK:\r\n\t\tSteam.inputInit()\r\n\t\tSteam.enableDeviceCallbacks()\r\n\t\tInputController.init()\r\n\r\n# if steam does not initialise, disable the process mode of input controller\r\n# doing so will prevent the _process() method from being called, so no logic in it will function\r\n# if you want to add logic in the _process() method of InputController, then you can implement a check using Steam.isSteamRunning()\r\n\telse:\r\n\t\tInputController.process_mode = Node.PROCESS_MODE_DISABLED\r\n\r\n# other steam stuff\r\n```\r\n\r\nBack in your **InputController**, make a dictionary for each action-set, where the key is the name that you defined in your **IGA** (.vdf) file, and the value is a boolean: true for analog actions that require an analog input like joystick \/ trigger for character movement and false for all digital actions (buttons).\r\n\r\nYour actions in your **IGA** file and Godot's input map should share the names for all actions for passing the input to Godot if a controller is not connected or if Steam is not running.\r\n\r\n!!! info Note\r\nJoysticks \/ triggers can be used as a digital action if you don't need the precise strength. As an example, I use left joystick for navigating UI.\r\n!!!\r\n\r\n```gdscript title=\"input_controller.gd\"\r\nextends Node\r\n\r\nconst set1_action_names := {\r\n \"ui_accept\": false,\r\n \"ui_cancel\": false,\r\n \"ui_focus_next\":false,\r\n \"ui_focus_prev\":false,\r\n \"ui_up\": false,\r\n \"ui_down\": false,\r\n \"ui_left\": false,\r\n \"ui_right\": false,\r\n}\r\n\r\nconst set2_action_names := {\r\n \"move\": true,\r\n \"jump\": false,\r\n \"shoot\": false,\r\n}\r\n\r\nenum INPUT_SOURCE { KEYBOARD_MOUSE, JOYPAD}\r\nvar last_input_source : INPUT_SOURCE = INPUT_SOURCE.KEYBOARD_MOUSE\r\nvar controller_id := -1\r\nvar got_handles := false\r\nvar actions := {}\r\nvar action_states := {}\r\n\r\nvar set1 := 0\r\nvar set2 := 0\r\nvar current_action_set: int:\r\n\tset(value):\r\n\t\tif Steam.isSteamRunning():\r\n\t\t\tSteam.activateActionSet(controller_id, value)\r\n\t\t\tcurrent_action_set = value\r\n\r\nvar action_glyphs := {}\r\nvar cooldown: float = 0.0\r\nvar gdt_cooldown: float = 0.0\r\n\r\n# The main nodes that needs controller glyphs\/icons,\r\n# try to keep this array as small as possible to avoid breaking connections\r\nconst nodes_needing_glyphs := [\r\n# nodes that I use for my game, the main `Game` node &\r\n# `Level` that is instantiated after clicking the start game button\r\n \"\/root\/Game\",\r\n \"\/root\/Game\/Level\",\r\n]\r\n\r\nfunc init() -> void:\r\n\tprocess_mode = Node.PROCESS_MODE_ALWAYS\r\n\tSteam.input_device_connected.connect(_on_controller_connect)\r\n\tSteam.input_device_disconnected.connect(_on_controller_disconnect)\r\n\tSteam.runFrame()\r\n```\r\n\r\n!!! warning Note\r\nRemember to change the **process_mode** to **PROCESS_MODE_ALWAYS** for both **InputController** and **Steam** autoloads, this makes them run even when the game is paused.\r\n!!!\r\n\r\n### Connecting controllers\r\n\r\nThis guide uses the latest connected controller - only 1 controller at a time. If you want your game to handle multiple controllers, make the variable **controller_id** an array and append the **input_handle** then remove it when disconnected.\r\n\r\n``` gdscript\r\nfunc _on_controller_connect(input_handle):\r\n\tcontroller_id = input_handle\r\n\tget_handles()\r\n\t# a timer for a delayed reload of glyphs\/icons as sometimes, Steam loads the wrong ones\r\n\tvar timer: Timer = Timer.new()\r\n\ttimer.one_shot = true\r\n\ttimer.timeout.connect(_delayed_reload.bind(timer))\r\n\tadd_child(timer)\r\n\ttimer.start(10)\r\n\r\n\tSteam.run_callbacks()\r\n\tSteam.runFrame()\r\n\r\n\t_preload_glyphs()\r\n\tset_glyphs()\r\n\r\n\t# have some sort of logic to activate the correct set as\r\n\t# user can connect controller in the middle of a game\r\n\tcurrent_action_set = set1\r\n\r\n\r\nfunc _on_controller_disconnect(input_handle):\r\n\tactions.clear()\r\n\taction_states.clear()\r\n\taction_glyphs.clear()\r\n\tgot_handles = false\r\n\t# to pause the game if controller is disconnected while in game\r\n\tget_node(\"\/root\/Game\").on_controller_disconnect()\r\n\tcontroller_id = -1\r\n\tset_glyphs()\r\n\tlast_input_source = INPUT_SOURCE.KEYBOARD_MOUSE\r\n```\r\n\r\n### Getting action handles\r\n\r\n!!! warning Note\r\nThese methods only work if a controller is connected, otherwise they return 0.\r\n!!!\r\n\r\n```gdscript\r\nfunc get_handles() -> void:\r\n\tgot_handles = true\r\n\tset1 = Steam.getActionSetHandle(\"Set1\")\r\n\tset2 = Steam.getActionSetHandle(\"Set2\")\r\n\tget_action_handles()\r\n\r\n\r\nfunc get_action_handles() -> void:\r\n\tfor action in set1_action_names:\r\n\t\tif set1_action_names[action]:\r\n\t\t\tactions[action] = Steam.getAnalogActionHandle(action)\r\n\t\telse:\r\n\t\t\tactions[action] = Steam.getDigitalActionHandle(action)\r\n\r\n\tfor action in set2_action_names:\r\n\t\tif set2_action_names[action]:\r\n\t\t\tactions[action] = Steam.getAnalogActionHandle(action)\r\n\t\telse:\r\n\t\t\tactions[action] = Steam.getDigitalActionHandle(action)\r\n```\r\n\r\nYou might need a helper function to change the current action set. For example, if you have two sets, one for in-game and one for UI, you can change to **set1** for UI navigation when you pause and revert to **set2** when you resume the game.\r\n\r\nHere's how I do it in my game: I have a global enum for location and whenever I change my location, I call this method:\r\n\r\n```gdscript\r\nfunc change_action_set(location):\r\n\tmatch location:\r\n\t\tEnums.START_MENU:\r\n\t\t\tcurrent_action_set = set1\r\n\t\tEnums.LEVEL:\r\n\t\t\tcurrent_action_set = set2\r\n\t\tEnums.PAUSE_MENU:\r\n\t\t\tcurrent_action_set = set1\r\n```\r\n\r\n## Input Wrappers{.block}\r\n\r\nTo be able to use handle both keyboard and controller inputs at the same time, we are going to create some wrapper functions that fall back to\r\nGodot input if no controller is connected; controller_id = -1 is used for keyboard.\r\n\r\n### Analog actions\r\n\r\nSince analog actions are typically used for getting strength \/ direction from an input, I am not implementing any input cooldown for these. I didn't use any analog actions for my project, as such these functions are ripped right out of [Furd's blog](https:\/\/furd.dev\/blog\/steam-input\/).\r\n\r\n#### get_action_strength()\r\n\r\nUseful for single axis of a joystick or a trigger; returns a float from 0 to 1. \r\n\r\nSteam's **getAnalogActionData()** function returns a **Vector2** with the x and y values of the action but the y value is always 0 for single axis inputs such as triggers. By only returning the x value we can emulate Godot's **get_action_strength()** function.\r\n\r\n```gdscript\r\nfunc get_action_strength(action: StringName, device: int = controller_id, exact_match: bool = false) -> float:\r\n\tif device >= 0:\r\n\t\tif not got_handles:\r\n\t\t\treturn 0\r\n\r\n\t\tvar action_data = Steam.getAnalogActionData(device, actions[action])\r\n\t\treturn action_data.x\r\n\treturn Input.get_action_strength(action, exact_match)\r\n```\r\n\r\n#### get_axis()\r\n\r\nUseful for creating a single value from two opposing actions; returns a float between -1 to +1.\r\n\r\n```gdscript\r\nfunc get_axis(negative_action: StringName, positive_action: StringName, device: int = controller_id) -> float:\r\n\tif device >= 0:\r\n\t\tif not got_handles: return 0\r\n\r\n\t\tvar negative = Steam.getAnalogActionData(device, actions[negative_action])\r\n\t\tvar positive = Steam.getAnalogActionData(device, actions[positive_action])\r\n\t\treturn positive.x - negative.x\r\n\treturn Input.get_axis(negative_action, positive_action)\r\n```\r\n\r\n#### get_vector()\r\n\r\nUseful for movement\/steering using joysticks; returns a normalized Vector2.\r\n\r\nThis function is intended for directional movement only, not analog magnitude. The returned vector has a maximum length of 1.0. The \"y\" value from Steam is inverted from Godot, so we invert it to make it consistent to the existing Godot function:\r\n\r\n```gdscript\r\nfunc get_vector(negative_x: StringName, positive_x: StringName, negative_y: StringName, positive_y: StringName,\r\n\tdevice: int = controller_id, deadzone: float = -1.0) -> Vector2:\r\n\tif device >= 0:\r\n\t\tif not got_handles:\r\n\t\t\treturn Vector2.ZERO\r\n\r\n\t\tvar negative_x_val = Steam.getAnalogActionData(device, actions[negative_x])\r\n\t\tvar positive_x_val = Steam.getAnalogActionData(device, actions[positive_x])\r\n\t\tvar negative_y_val = Steam.getAnalogActionData(device, actions[negative_y])\r\n\t\tvar positive_y_val = Steam.getAnalogActionData(device, actions[positive_y])\r\n\r\n\t\treturn Vector2(positive_x_val - negative_x_val, -(positive_y_val - negative_y_val)).normalized()\r\n return Input.get_vector(negative_x, positive_x, negative_y, positive_y, deadzone)\r\n```\r\n\r\n#### get_move_input()\r\n\r\nIn order to use an analog joystick from Steam Input, we define a name for the input in the Steam In-Game Actions File. In this example, I've named it \"move\". In Godot, I'm querying the Left \/ Right and Up \/ Down actions but they could be named whatever you define yourself.\r\n\r\nThis is one of the few functions that don't share the same name action names between Steam Input and Godot Input:\r\n\r\n```gdscript\r\nfunc get_move_input(device: int = controller_id) -> Vector2:\r\n\tif device >= 0:\r\n\t\tif not got_handles:\r\n\t\t\treturn Vector2.ZERO\r\n\r\n\t\tvar action_data = Steam.getAnalogActionData(device, actions[\"move\"])\r\n\t\treturn Vector2(action_data.x, -action_data.y).normalized()\r\n\treturn Vector2(Input.get_axis(\"Left\", \"Right\"), Input.get_axis(\"Up\", \"Down\")).normalized()\r\n```\r\n\r\n### Digital actions\r\n\r\nI have a cooldown mechanism for **is_action_just_pressed()** which prevents any double inputs, especially when pausing \/ resuming the game. You can implement that for **is_action_pressed()** here, if you want to create pseudo-echo effect or better yet; implement the cooldown where necessary in the specific place you want it for more granular control. The value for cooldown can be whatever you want it to be; I found that '0.15' is the lowest that prevents most double inputs.\r\n\r\n#### is_action_pressed()\r\n\r\nWill return true for as long as the input is held.\r\n\r\n```gdscript\r\nfunc is_action_pressed(action: StringName, device: int = controller_id, exact_match: bool = false) -> bool:\r\n\tif device >= 0:\r\n\t\tif not got_handles:\r\n\t\t\treturn false\r\n\r\n\tvar current_frame = Engine.get_process_frames()\r\n\tif last_input_source == INPUT_SOURCE.JOYPAD:\r\n\t\tvar currently_held = Steam.getDigitalActionData(device, actions[action]).state\r\n\t\tset_action_state(action, currently_held, current_frame)\r\n\t\treturn currently_held\r\n\r\n\treturn Input.is_action_pressed(action, exact_match)\r\n```\r\n\r\n#### is_action_just_pressed()\r\n\r\nWill return true once, when the button is pressed.\r\n\r\n```gdscript\r\nfunc is_action_just_pressed(action: StringName, device: int = controller_id, exact_match: bool = false) -> bool:\r\n\tif device >= 0:\r\n\t\tif not got_handles:\r\n\t\t\treturn false\r\n\r\n\tif cooldown <= 0:\r\n\t\tvar current_frame = Engine.get_process_frames()\r\n\t\tif last_input_source == INPUT_SOURCE.JOYPAD:\r\n\t\t\tvar currently_held = Steam.getDigitalActionData(device, actions[action]).state\r\n\t\t\tvar action_state = set_action_state(action, currently_held, current_frame)\r\n\t\t\tif currently_held:\r\n\t\t\t\tcooldown = 0.15\r\n\t\t\treturn currently_held and action_state.press_frame == current_frame\r\n\r\n\telse:\r\n\t\treturn false\r\n\r\n\treturn Input.is_action_just_pressed(action, exact_match)\r\n```\r\n\r\n#### is_action_just_released()\r\n\r\nWill return true once, when the button is released.\r\n\r\n```gdscript\r\nfunc is_action_just_released(action: StringName, device: int = controller_id, exact_match: bool = false) -> bool:\r\n\tif device >= 0:\r\n\t\tif not got_handles:\r\n\t\t\treturn false\r\n\r\n\t\tvar current_frame = Engine.get_process_frames()\r\n\t\tvar currently_held = Steam.getDigitalActionData(device, actions[action]).state\r\n\t\tvar action_state = set_action_state(action, currently_held, current_frame)\r\n\t\treturn not currently_held and action_state.release_frame == current_frame\r\n\r\n\treturn Input.is_action_just_released(action, exact_match)\r\n```\r\n\r\n### Setting \/ getting action states\r\n\r\nAutomatically creates a value for an action in **get_action_state** if it does not exist. For supporting multiple controllers, pass **device_id** as an argument and create a dictionary for each id and this dictionary as the value:\r\n\r\n```gdscript\r\n# key\/value pairs for action_states dictionary\r\n\r\n# single controller\r\naction_name: { \"held\": false, \"press_frame\": -1, \"release_frame\": -1 }\r\n\r\n# multiple controllers\r\n# here, the input_handle for controller1 is the key and its value is the above dictionary\r\ncontroller1: {\r\n action_name1: { \"held\": false, \"press_frame\": -1, \"release_frame\": -1 },\r\n action_name2: { \"held\": false, \"press_frame\": -1, \"release_frame\": -1 }\r\n}\r\n```\r\n\r\n```gdscript\r\nfunc get_action_state(action: String) -> Dictionary:\r\n\tif not action_states.get(action):\r\n\t\taction_states[action] = { \"held\": false, \"press_frame\": -1, \"release_frame\": -1 }\r\n\t\treturn action_states[action]\r\n\r\n\r\nfunc set_action_state(action: StringName, currently_held: bool, current_frame: int) -> Dictionary:\r\n\tvar previous_action_state = get_action_state(action)\r\n\r\n\tif currently_held and not previous_action_state.held:\r\n\t\taction_states[action].held = true\r\n\t\taction_states[action].press_frame = current_frame\r\n\telif not currently_held and previous_action_state.held:\r\n\t\taction_states[action].held = false\r\n\t\taction_states[action].release_frame = current_frame\r\n\treturn action_states[action]\r\n```\r\n\r\n### Handling input source change and input cooldown\r\n\r\n```gdscript\r\nfunc _input(event: InputEvent) -> void:\r\n\tif event is InputEventKey or event is InputEventMouse or event is InputEventMouseMotion:\r\n\t\tlast_input_source = INPUT_SOURCE.KEYBOARD_MOUSE\r\n\t\t# you can remove controller glyphs here,\r\n\t\t# but remember we are simply \"changing\" input source, \r\n\t\t# the controller is still connected\r\n\r\n\r\nfunc _process(delta: float) -> void:\r\n\tSteam.runFrame()\r\n\tif cooldown > 0:\r\n\t\tcooldown -= delta\r\n\tif gdt_cooldown > 0:\r\n\t\tgdt_cooldown -= delta\r\n\t\t\r\n\tif controller_id != -1 and last_input_source == INPUT_SOURCE.KEYBOARD_MOUSE:\r\n\t\tfor action in actions.keys():\r\n\t\t\tif Steam.getDigitalActionData(controller_id, actions[action]).state:\r\n\t\t\t\tlast_input_source = INPUT_SOURCE.JOYPAD\r\n\t\t\t\tbreak\r\n```\r\n\r\n### Controller glyphs \/ icons\r\n\r\nWhenever a controller is connected, **\\_preload_glyphs()** is called which loads glyphs; Steam automatically detects the controller type and gets the glyphs for that controller. If you add an action-set, remember to add it in the local **sets** variable.\r\n\r\nSometimes incorrect glyphs are loaded; to counteract this, I have a **\\_delayed_reload()** method which reloads the glyphs after ten seconds and deletes the temporary timer. You should keep the size as large for maximum clarity and adjust the node's property to fit the glyph as per your needs. [You can look other glyph styles here](https:\/\/godotsteam.com\/classes\/input\/#inputglyphstyle).\r\n\r\nThe functions **getAnalogActionOrigins()** and **getDigitalActionOrigins()** return an array, containing all buttons that the action might be bound to. I use the first element for glyphs but you can use all the origins and have the icons rotate between themselves. If you decide to do so, remember to modify the **load_glyphs()** (which is discussed later) function to accommodate it.\r\n\r\n```gdscript\r\nfunc _preload_glyphs() -> void:\r\n\tvar sets := [set1, set2]\r\n\r\n\tfor set_id in sets:\r\n\t\tSteam.activateActionSet(controller_id, set_id)\r\n\t\tSteam.runFrame()\r\n\t\t\r\n\t\tfor action_name in actions.keys():\r\n\t\t\tvar origins := []\r\n\t\t\t\r\n\t\t\tif is_analog_action(action_name):\r\n\t\t\t\torigins = Steam.getAnalogActionOrigins(controller_id, set_id, actions[action_name])\r\n\t\t\telse:\r\n\t\t\t\torigins = Steam.getDigitalActionOrigins(controller_id, set_id, actions[action_name])\r\n\t\t\t\r\n\t\t\tif origins.size() == 0:\r\n\t\t\t\tcontinue\r\n\t\t\t\r\n\t\t\tvar style := Steam.INPUT_GLYPH_STYLE_DARK | Steam.INPUT_GLYPH_STYLE_SOLID_ABXY\r\n\t\t\tvar path = Steam.getGlyphPNGForActionOrigin(origins[0], Steam.INPUT_GLYPH_SIZE_LARGE, style)\r\n\t\t\tvar img = Image.new()\r\n\t\t\t\r\n\t\t\tif img.load(path) == OK:\r\n\t\t\t\tvar tex :Texture2D = ImageTexture.create_from_image(img)\r\n\t\t\t\taction_glyphs[action_name] = tex\r\n\r\n\r\nfunc get_glyph(action_name: StringName):\r\n\tif action_glyphs.has(action_name):\r\n\t\treturn action_glyphs[action_name]\r\n\telse:\r\n\t\treturn null\r\n\r\n\r\nfunc _delayed_reload(timer: Timer):\r\n\tvar current_set: int = current_action_set\r\n\t_preload_glyphs()\r\n\tset_glyphs()\r\n\tcurrent_action_set = current_set\r\n\tSteam.runFrame()\r\n\ttimer.queue_free()\r\n```\r\n\r\n#### Forcing a specific controller's glyphs\r\n\r\nIf you want to give the player the option to display a specific controller's glyphs, you can use **translateActionOrigin()**, which translates (or gives the best guess for) the origin to the equivalent of that controller.\r\n\r\n```gdscript\r\nvar player_option := Steam.INPUT_TYPE_XBOXONE_CONTROLLER\r\nvar origin = origins[0]\r\nif player_option != Steam.getInputTypeForHandle(controller_id):\r\n\torigin = Steam.translateActionOrigin(player_option, origin)\r\n\r\nvar path := Steam.getGlyphPNGForActionOrigin(origin, Steam.INPUT_GLYPH_SIZE_LARGE, style)\r\n```\r\n!!! info Note\r\nYou can see the enums for each controller type [here](https:\/\/godotsteam.com\/classes\/input.md#inputtype).\r\n!!!\r\n\r\n### Converting SteamInput to Godot input\r\n\r\nThere are some inputs (namely **ui_left**, **ui_right**, **ui_up**, **ui_down**, **ui_focus_next** and **ui_focus_prev**) that are kinda annoying to translate using SteamInput, as you can manually change focus but setting that up is extremely tedious. As such, I use this function for creating an **InputEvent** in Godot for UI navigation. **gdt_cooldown** is used to prevent weird input-chaining when creating an event:\r\n\r\n```gdscript\r\nfunc si_to_godot(event_action: StringName):\r\n\tif last_input_source == INPUT_SOURCE.JOYPAD and gdt_cooldown <= 0:\r\n\t\tgdt_cooldown = 0.01\r\n\t\tvar event := InputEventAction.new()\r\n\t\tevent.action = event_action\r\n\t\tevent.pressed = true\r\n\t\tInput.parse_input_event(event)\r\n\r\n\t\tvar event_release := InputEventAction.new()\r\n\t\tevent_release.action = event_action\r\n\t\tevent_release.pressed = false\r\n\t\tInput.parse_input_event(event_release)\r\n```\r\n\r\n!!! warning Note\r\nThe UI action names in your **IGA** file and Godot's input-map should be the same.\r\n!!!\r\n\r\n## Using InputController{.block}\r\n\r\nNow that you have setup the InputController class, it's time to use it. You simply call the functions that you want to use.\r\n\r\n### Running game from editor\r\n\r\nWhen running the game from editor, you might have to use the following commands to force SteamInput to work:\r\n\r\n::: tabs\r\n@tab:active Windows Powershell\r\n```powershell\r\nstart steam:\/\/forceinputappid\/x\r\n```\r\n\r\n@tab Linux (bash)\r\n```bash\r\nxdg-open \"steam:\/\/forceinputappid\/x\"\r\n```\r\n:::\r\n\r\n### Loading glyphs \/ icons\r\n\r\nA node in the var **nodes_needing_glyphs** gets its glyphs like this, where it can set the glyph to a specific node like a button or call **load_glyphs()** for its children. Remember, **get_glyph()** returns null or **Texture2D**.\r\n\r\n```gdscript title=\"other_node.gd\"\r\nfunc _ready(): -> void:\r\n\tload_glyphs()\r\n\r\n\r\n# you can provide a default value here so you don't have it pass it every time\r\nfunc load_glyphs(controller_connected: bool = InputController.controller_id != -1):\r\n\tif controller_connected:\r\n\t\t# for a button, set its property `expand_icon` to true, so the icon shrinks to the button size\r\n\t\t$Button.set_button_icon(InputController.get_glyph(\"action_name\"))\r\n\r\n\t\t# for TextureRect, set its property `expand_mode` according to your needs\r\n\t\t$TextureRect.set_texture(InputController.get_glyph(\"action_name\"))\r\n\r\n\t\t$ChildNode.load_glyphs(controller_connected)\r\n\r\n\telse:\r\n\t\t$Button.set_button_icon(null)\r\n\t\t$TextureRect.set_texture(null)\r\n\t\t$ChildNode.load_glyphs(controller_connected)\r\n```\r\n\r\n### Getting Input data\r\n\r\n#### Movement\r\n\r\nSince we created a specific function for getting the movement vector, we can simply use this:\r\n\r\n```gdscript title=\"player.gd\"\r\nfunc _process(delta) -> void:\r\n\tvelocity = Vector2.ZERO\r\n\tvelocity = InputController.get_move_input() * speed\r\n\tposition += velocity * delta\r\n```\r\n\r\n#### UI navigation\r\n\r\nI have this logic in my main \"game\" node, whose **process_mode** is **PROCESS_MODE_ALWAYS**, and this automatically changes the focus how Godot would do\r\nnatively. If you have a really complex scene, you can [manually define the focus neighbors explained here in the Godot docs](https:\/\/docs.godotengine.org\/en\/stable\/tutorials\/ui\/gui_navigation.html); for me, Godot's default guesses usually work.\r\n\r\n```gdscript title=\"other_node.gd\"\r\nfunc _process(_delta: float) -> void:\r\n\tif InputController.is_action_just_pressed(\"ui_focus_next\"):\r\n\t\tInputController.si_to_godot(\"ui_focus_next\")\r\n\r\n\telif InputController.is_action_just_pressed(\"ui_focus_prev\"):\r\n\t\tInputController.si_to_godot(\"ui_focus_prev\")\r\n\r\n\telif InputController.is_action_just_pressed(\"ui_left\"):\r\n\t\tInputController.si_to_godot(\"ui_left\")\r\n\r\n\telif InputController.is_action_just_pressed(\"ui_right\"):\r\n\t\tInputController.si_to_godot(\"ui_right\")\r\n\r\n\telif InputController.is_action_just_pressed(\"ui_up\"):\r\n\t\tInputController.si_to_godot(\"ui_up\")\r\n\r\n\telif InputController.is_action_just_pressed(\"ui_down\"):\r\n\t\tInputController.si_to_godot(\"ui_down\")\r\n```\r\n\r\n!!! warning Note\r\nWhenever you change a scene, remember to use **grab_focus()** on a **Control** node. For example, when you start the game, use it to focus the first button in your **\\_ready** function.\r\n!!!\r\n\r\n#### Other button inputs\r\n\r\nI use a combination of Godot's **grab_focus()** and then **InputController.si_to_godot(\"ui_accept\")** to confirm.\r\n\r\n```gdscript title=\"some_other_node.gd\"\r\nfunc _process(_delta: float) -> void:\r\n\tif InputController.is_action_just_pressed(\"ui_accept\"):\r\n\t\tInputController.si_to_godot(\"ui_accept\")\r\n\r\n\telif InputController.is_action_just_pressed(\"start_game\"):\r\n\t\tstart_button.grab_focus()\r\n\t\tInputController.si_to_godot(\"ui_accept\")\r\n\r\n\t elif InputController.is_action_just_pressed(\"open_close_settings\"):\r\n\t\tsettings_button.grab_focus()\r\n\t\tInputController.si_to_godot(\"ui_accept\")\r\n\r\n\telif InputController.is_action_just_pressed(\"exit_game\"):\r\n\t\texit_button.grab_focus()\r\n\t\tInputController.si_to_godot(\"ui_accept\")\r\n```\r\n"
Comments
Log in to post a comment
Licensed under the CC-BY license
See the full license details
Submitted by
Gramps
Table of contents
Compatibility
Works in Godot
3.x / 4.x
Tags
GodotSteam
SteamĀ®
steamworks
input
Share this tutorial
Share on Bluesky
Share on X / Twitter
or share this direct link:
https://thegodotbarn.com/contributions/tutorial/764/godotsteam-tutorials-input
Please wait ...
Okay
Okay
No
Yes
Okay