Godot Version
4.5
Question
I am currently working on a 3D local multiplayer game, and my devices include Keyboard 1, JOY Controller 1, and JOY Controller 2. When I realized that the input() function is a global input and cannot detect a specific character individually, I thought of binding the character and device to solve the problem of one device being able to control multiple characters:
#本地模式启用,输入按键绑定玩家
func _input(event):
#按下键盘space 或 手柄A加入游戏
if (event is InputEventKey and event.pressed and not event.echo and event.keycode == KEY_SPACE) or \
(event is InputEventJoypadButton and event.pressed and event.button_index == JOY_BUTTON_A):
#print(event)
# 绑定生成唯一设备ID
var device_id = InputManager.get_Device_By_Event(event)
# 检查该设备是否已使用,防止重复加入
if InputManager.used_devices.has(device_id):
return
# 标记设备已使用并分配玩家
InputManager.used_devices.append(device_id)
# 动态分配玩家槽位
var player_slot = func()-> int:
for i in range(GameManager.MAXPLAYER):
if not InputManager.player_joined[i]:
return i
return -1
var _slot:int=player_slot.call()
# 如果还有空槽位
if _slot != -1:
# 绑定输入设备到玩家
InputManager.player_joined[_slot] = true
GameManager.curPlayerNum += 1
InputManager.device_to_slot[device_id] = _slot
#print(InputManager.device_to_player)
# 更新按钮UI文字
idBtns[_slot].text="Player"+str(_slot+1)+" is ready"
#动态分配玩家inputMap映射
#左方向
if not InputMap.has_action("p"+str(_slot)+"_left"):
InputMap.add_action("p"+str(_slot)+"_left")
if event is InputEventKey:
var key_eventA = InputEventKey.new()
key_eventA.keycode = KEY_A
InputMap.action_add_event("p"+str(_slot)+"_left", key_eventA)
else:
var left_stick_left = InputEventJoypadMotion.new()
left_stick_left.axis = JOY_AXIS_LEFT_X
left_stick_left.axis_value = -1.0
InputMap.action_add_event("p"+str(_slot)+"_left", left_stick_left)
#右方向
if not InputMap.has_action("p"+str(_slot)+"_right"):
InputMap.add_action("p"+str(_slot)+"_right")
if event is InputEventKey:
var key_eventD = InputEventKey.new()
key_eventD.keycode = KEY_D
InputMap.action_add_event("p"+str(_slot)+"_right", key_eventD)
else:
var left_stick_right = InputEventJoypadMotion.new()
left_stick_right.axis = JOY_AXIS_LEFT_X
left_stick_right.axis_value = 1.0
InputMap.action_add_event("p"+str(_slot)+"_right", left_stick_right )
#上方向
if not InputMap.has_action("p"+str(_slot)+"_up"):
InputMap.add_action("p"+str(_slot)+"_up")
if event is InputEventKey:
var key_eventW = InputEventKey.new()
key_eventW.keycode = KEY_W
InputMap.action_add_event("p"+str(_slot)+"_up", key_eventW)
else:
var left_stick_up = InputEventJoypadMotion.new()
left_stick_up.axis = JOY_AXIS_LEFT_Y
left_stick_up.axis_value = -1.0
InputMap.action_add_event("p"+str(_slot)+"_up", left_stick_up )
#下方向
if not InputMap.has_action("p"+str(_slot)+"_down"):
InputMap.add_action("p"+str(_slot)+"_down")
if event is InputEventKey:
var key_eventS = InputEventKey.new()
key_eventS.keycode = KEY_S
InputMap.action_add_event("p"+str(_slot)+"_down", key_eventS)
else:
var left_stick_down = InputEventJoypadMotion.new()
left_stick_down.axis = JOY_AXIS_LEFT_Y
left_stick_down.axis_value = 1.0
InputMap.action_add_event("p"+str(_slot)+"_down", left_stick_down )
The following content is the character GD code:
func _input(event):
#没有存活无法控制
if not get_meta("isAlive"):
return
#if InputManager.device_to_slot.get(InputManager.get_Device_By_Event(event), -1) != slot:
#return
if roleDevice != InputManager.get_Device_By_Event(event):
return
##不是自己角色的设备止运行
#if InputManager.used_devices[slot] != InputManager.get_Device_By_Event(event):
#return
##不是自己角色编号,的手柄/键盘不运行,检测摇杆不属于设备?
#if InputManager.get_Player_Slot_By_Event(event) != slot:
#return
#if roleDevice=="joypad_1" or roleDevice=="joypad_0":
#print("输入设备:",InputManager.get_Device_By_Event(event))
#print("角色编号:",slot)
#处理跳跃输入
if event.is_action_pressed("ui_accept"):
jump_pressed = true
# 地面 + 可移动 才处理
if is_on_floor() and can_move:
#检测输入
input_dir = Input.get_vector("p"+str(slot)+"_left","p"+str(slot)+"_right","p"+str(slot)+"_up","p"+str(slot)+"_down")
move_dir = input_dir
#print("slot:",slot)
#print(input_dir)
#print("角色", slot, " 事件设备=", InputManager.get_Device_By_Event(event), " 写死设备=", roleDevice, " 向量=", input_dir)
#if event is InputEventJoypadMotion:
#print("角色", slot, "轴=", event.axis, "轴值=", event.axis_value, "越过=", abs(event.axis_value) )
func _physics_process(delta: float) -> void:
# 重力
if not is_on_floor():
velocity += get_gravity() * delta
# 跳跃
if jump_pressed and is_on_floor() and can_move:
velocity.y = JUMP_VELOCITY
jump_pressed = false # 重置标志
last_ground_dir = move_dir.normalized()
# 空中:只能沿起跳瞬间的惯性方向滑行(不能改向)
# 只有当有惯性方向时才应用,否则保持当前XZ速度
if last_ground_dir.length_squared() > 0.0001:
var inertial_3d_dir = (transform.basis * Vector3(last_ground_dir.x, 0, last_ground_dir.y)).normalized()
velocity.x = inertial_3d_dir.x * SPEED_AIR
velocity.z = inertial_3d_dir.z * SPEED_AIR
#地面 可移动
if is_on_floor() and can_move: ##增加is_on_floor()可以控制是否可以空中转向
#print("name:",name,"+","device:",device,"+","currentinputSlot:",InputManager.used_devices[slot],"+","inputDir:",input_dir)
if input_dir.length_squared() > 0.0001: # 有输入
# 计算目标角度(同原逻辑)
target_angle = atan2(input_dir.x, input_dir.y)
# 立即给模型转向(一帧到位,平滑仍在 physics 里)
model.global_rotation.y = target_angle
# 把输入缓存到成员变量,physics 里再用
#move_dir = input_dir # Vector2 成员变量,新建即可
#else:
#move_dir = Vector2.ZERO # 空中 or 不可移动时清零
var direction:= (transform.basis * Vector3(move_dir.x, 0, move_dir.y)).normalized()
if direction.length_squared() > 0.0001:
velocity.x = direction.x * speed
velocity.z = direction.z * speed
last_ground_dir = Vector2(direction.x, direction.z) # 同步更新惯性
# 让角色朝向与移动方向一致
var input_angle = atan2(move_dir.x,move_dir.y)
target_angle=input_angle
#平滑转向插值
model.global_rotation.y=lerp_angle(model.global_rotation.y, target_angle, turn_speed * delta)
else:
velocity.x = move_toward(velocity.x, 0, speed)
velocity.z = move_toward(velocity.z, 0, speed)
move_and_slide()
I have implemented the filtering of player devices through the code logic that binds the above characters and devices. The current effect is: keyboard 1 player correctly controls their character, JOY controller 1 player correctly controls their character, and JOY controller 2 player cannot control their character. In short, as long as the controller control character is filtered, there will be one JOY controller that cannot control the character. Printing can receive the correct player ID and device ID, but print(input_dir) always remains at (0,0)
Currently, Godot’s support for local multiplayer is not very good. What can I do now to enable players to control their own characters without conflicts or filtering failures between cross player control characters and devices?