I have a 2 controllers connected (to my Mac). When I run my game, I want 1 controller to be the main controller where that user start the game, selects options, etc.
The controllers seem to change between runs. Sometimes the controller in USB1 is the main, sometimes the controller in USB2 is the main. This happens both in the editor, and when I build.
func _input(event: InputEvent) -> void:
if event.device == 0:
# Do stuff here
Is there any way to ensure that the same controller is the main one every time?
I don’t think there is, sadly, unless you want to do some unportable, unreliable hackery like identifying the controllers by their vendor/device ID or bus location. Chances are excellent you have two controllers with the same vendor/device id, and USB bus location is just about how the wires are connected.
There’s a “unique ID” field in every USB device that should have helped with this, but I have yet to find a USB device in the field whose “unique ID” isn’t 0.
Thinking about it, though, you could maybe fiddle it; Godot’s input map lets you decide whether an input action is on a single controller or all controllers. You could set up an input action for (say) the start button on all controllers, plus an input action for each individual controller. If you get is_action_just_pressed() on the “all” action, check is_action_just_pressed() on all the individual actions, and hopefully only one is pressed.
You could then nominate that gamepad as “main”, and since you know which one it is now, you know which set of the input map you should be using for the main controller…
It’s unfortunate that it’s not possible to know with 100% certainty who will be Player 1. I see that when the controllers are “swapped”, the mappings for the entire game is swapped as well.
I’ll look into you recommendation above… that’s a good starting point.
The input map is an array and if you can copy it for each player and add a + str(device_id) to the string to get unique inputs for each player.
So if you have “jump”, you get “jump1”, “jump2”.
Not sure if it will solve the problem.
How do you store which player is linked to which device?
I set player and device id on the root on every stage scene.
extends Node2D
var PLAYER = preload("res://Player.tscn")
func _ready() -> void:
Global.players_remaining = Global.number_of_players
var device_id: = Global.first_device_id
var player_id = 1
for i in range(Global.number_of_players):
var player_instance = PLAYER.instantiate()
player_instance.device = device_id
player_instance.player_index = player_id
device_id += 1
player_id += 1
add_child(player_instance)
Again, not sure if this will help but it could be worth to try.
I struggled a bit with setting multiplayer up and eventually decided to use a plugin for the part with the inputs. Keeping separate inputs for separate players are a mess if updating them manually, but i later learned you can just duplicate the base array and have a script assign a separate input array to each player, linked to that player’s device_id.
@baba Yes, I do the same… I append the device id to the end of the action, so I do get a jump1, jump2, etc.
My game is what people consider a “couch” game, and Player 1 starts on the left side of the screen, Player 2 on the right. If this is swapped, then it does get confusing since the players are assuming their starting positions dont change between runs of the game.
I guess I need to figure out some solution, and/or identify the player(s) on the screen. I know, for me, if I am sitting on the left with the left controller and all of a sudden I am controlling the right item, it could cause a breif bit of confusion.
I’m not sure, really. I haven’t experienced this problem but it’s probably because almost all testing i’ve done is with keyboard and joycon. I had not even considered it until now.
One thought that comes to mind - do you use Input.get_connected_joypads()? It only returns the device IDs of the connected joypads but if they were to be stored in a global array it should perhaps be possible to reuse them later, so that the left player always is player 1 etc. I’m not sure how godot keeps track of the values or if it will ever reassign values after first setting them up. I imagine it would append new entries, and that should matter for your global array only if you update it whenever something changes?
It should also be possible to have a second array, into which devices from the get_connected_joypads() are appended only after e.g. press start on character select menu. Then you could access them in order to get the one corresponding to the player id you need. Maybe.
Let me know if you figure something out or if any of this works or does not work. I was sitting quite a bit with things like this some time ago and i found it difficult, but it is about time to get started again
I recommend going old school following @hexgrid 's suggestion. Whoever presses the start button first is player 1 and is on the left. The players can either back out if someone presses the wrong button, or swap controllers.
I think I will go the route that everyone has suggested and the first person to press the “start game” button will be player 1. Since my game can support up to 4 players, I need to figure out an easy to understand method to visually show players what player number they are.
Each player gets to choose their on-screen player and I randomized that so everyone gets a fair shot, so I’ll just force a number on them.
I tried setting it up today and think it works. I keep the device ids in a separate global array. Godot’s own array can rearrange itself and it depends a bit on your computer/whatever how it works, as godot gets the array from the OS. For example, if p2 controller disconnects, player 3 can turn into player 2, since the entry for the device is deleted and not replaced with null. When p2 reconnects, it could be as p3.
I have tested the below a bit but only with keyboard and controller. They always spawn in on the right places though and keep their left/right positions after scene changes.
func _input(event: InputEvent) -> void:
handle_Input(event)
func handle_Input(event):
if event is InputEventKey or event is InputEventJoypadButton:
var device_id : int
if event.is_action_pressed("ui_accept"):
if event is InputEventKey:
device_id = -99
elif event is InputEventJoypadButton:
device_id = event.device
print("Device pressed accept: ", device_id)
if not Global.joined_devices.has(device_id):
if not Global.joined_devices.has(null):
Global.joined_devices.append(device_id)
print(Global.joined_devices)
elif Global.joined_devices.has(null):
var entry_to_replace = Global.joined_devices.find(null)
Global.joined_devices[entry_to_replace] = device_id
print("replace null with %s" % device_id)
print(Global.joined_devices)
else:
push_error("could not append device_id or replace null")
elif event.is_action_pressed("ui_cancel"):
if event is InputEventKey or event is InputEventJoypadButton:
if event is InputEventKey:
device_id = -99
elif event is InputEventJoypadButton:
device_id = event.device
print("Device pressed cancel: ", device_id)
if Global.joined_devices.has(device_id):
var entry_to_replace = Global.joined_devices.find(device_id)
Global.joined_devices[entry_to_replace] = null
print("replaced device_id with null")
print(Global.joined_devices)
elif not Global.joined_devices.has(device_id):
print("Device not in joined devices, can't make null")
return
else:
return
Player spawn in script on each stage:
func _ready() -> void:
Global.players_remaining = Global.number_of_players
var player_id = 1
for i in range(Global.number_of_players):
var player_instance = PLAYER.instantiate()
player_instance.device = Global.joined_devices[i]
player_instance.player_index = player_id
player_id += 1
add_child(player_instance)
Wanted to add that i didn’t use -1 for keyboard (as most people do) as it would possibly collide with the .find() function returns. Array.find() returns -1 if it can’t find anything.
Blockquote
For example, if p2 controller disconnects, player 3 can turn into player 2, since the entry for the device is deleted and not replaced with null. When p2 reconnects, it could be as p3.
There is a lot to consider, for sure.
I’m starting to work on a solution, and all of the info here is very valuable.