Skip to content

Commit

Permalink
feat: add gamepad support to escoria-ui-simplemouse
Browse files Browse the repository at this point in the history
This commit starts by introducing a new action, `ESC_UI_PRIMARY_ACTION`,
that is used to represent a "primary action" from an input device, such
as a left-click on a mouse or the press of the primary button on a
gamepad. Although this action is not added to `InputMap` by default in
`escoria-core`, `_unhandled_input()` in `esc_item.gd` is updated to look
for it.

The other portion of this commit is an update to
`addons/escoria-ui-simplemouse/game.gd` that adds a gamepad mapping to
the `InputMap` for both `ESC_UI_PRIMARY_ACTION` as well as another new
action `ESC_UI_CHANGE_VERB_ACTION`. These actions are mapped to X and Y
on an XBox controller, respectively.

Note that `game.gd` is also updated to implement `_process()` such that
moving `JOY_AXIS_0` and `JOY_AXIS_1` on "gamepad 0" will move the
cursor around on the screen as the mouse would.

Overall, this makes the game fairly playable with a gamepad, though
admittedly a mouse is still currently required to click the "New game"
button on the initial menu because `BaseButton::gui_input()` appears to
be hardcoded to check for mouse events exclusively:

https://github.com/godotengine/godot/blob/a09814e4f86861948615fcd83139de6ed9a34434/scene/gui/base_button.cpp#L55-L81
  • Loading branch information
bolinfest committed Mar 2, 2022
1 parent d33ce3b commit 0f204fd
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 2 deletions.
21 changes: 19 additions & 2 deletions addons/escoria-core/game/core-scripts/esc_item.gd
Original file line number Diff line number Diff line change
Expand Up @@ -268,8 +268,7 @@ func _ready():
#
# #### Parameters
#
# - event: Triggered event
func _unhandled_input(event: InputEvent) -> void:
func _unhandled_input(input_event: InputEvent) -> void:
# If this is a trigger, then escoria.inputs_manager is not wired up to
# receive the signals this function might dispatch. In particular,
# calling get_tree().set_input_as_handled() unnecessarily will prevent
Expand All @@ -278,6 +277,24 @@ func _unhandled_input(event: InputEvent) -> void:
if is_trigger:
return

var event = input_event
# Note that event could be InputEventMouseButton, InputEventJoypadButton,
# or something else. As such, the value of the `button_index` property
# must be read in the context of the type of input event.
if input_event is InputEventJoypadButton:
if not input_event.is_action_pressed(escoria.inputs_manager.ESC_UI_PRIMARY_ACTION):
return

# For now, rather than refactor input handling to be more generic
# to accommodate gamepad support, we create a synthetic mouse event
# based on the InputEventJoypadButton.
event = InputEventMouseButton.new()
event.button_index = BUTTON_LEFT
event.doubleclick = false
event.pressed = true
# ESCActionManager expects to read the position off of the event.
event.position = get_global_mouse_position()

if event is InputEventMouseButton and event.is_pressed():
if not escoria.current_state == escoria.GAME_STATE.DEFAULT:
escoria.logger.info("Game state doesn't accept interactions")
Expand Down
4 changes: 4 additions & 0 deletions addons/escoria-core/game/inputs_manager.gd
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ const SWITCH_ACTION_VERB = "switch_action_verb"
# Input action for use by InputMap
const ESC_SHOW_DEBUG_PROMPT = "esc_show_debug_prompt"

# Input action for use by InputMap that represents a "primary action" from an
# input device, such as a left-click on a mouse or the X button on an XBox
# controller
const ESC_UI_PRIMARY_ACTION = "esc_ui_primary_action"

# The current input mode
var input_mode = INPUT_ALL
Expand Down
111 changes: 111 additions & 0 deletions addons/escoria-ui-simplemouse/game.gd
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,31 @@ Implement methods to react to inputs.
- _on_event_done(event_name: String)
"""

# Value to use for `device` argument to various `Input.get_joy` methods.
const JOY_DEVICE = 0

# See https://docs.godotengine.org/en/stable/tutorials/inputs/controllers_gamepads_joysticks.html?#dead-zone
const DEADZONE = 0.2

# Multiplier to apply to axis when it exceeds DEADZONE.
const AXIS_WEIGHT = 50.0

# JOY_BUTTON_2 corresponds to the "X" button on an XBox controller
# or the Square button on a Playstation controller. These appear to
# map to the "primary action," in practice, so we treat it like a left click.
const PRIMARY_ACTION_BUTTON = JOY_BUTTON_2

# JOY_BUTTON_3 corresponds to the "Y" button on an XBox controller
# or the Triangle button on a Playstation controller. These appear to
# map to the "secondary action," in practice, so we treat it like a right click.
const CHANGE_VERB_BUTTON = JOY_BUTTON_3

# Input action for use by InputMap
const ESC_UI_CHANGE_VERB_ACTION = "esc_change_verb"

# true when a gamepad is connected.
var _is_gamepad_connected = false

func _enter_tree():
var room_selector_parent = $CanvasLayer/ui/HBoxContainer/VBoxContainer

Expand All @@ -53,6 +78,21 @@ func _enter_tree():
).instance()
)

var input_handler = funcref(self, "_process_input")
escoria.inputs_manager.register_custom_input_handler(input_handler)

_is_gamepad_connected = Input.is_joy_known(JOY_DEVICE)
if _is_gamepad_connected:
_on_gamepad_connected()

Input.connect("joy_connection_changed", self, "_on_joy_connection_changed")


func _exit_tree():
escoria.inputs_manager.register_custom_input_handler(null)
Input.disconnect("joy_connection_changed", self, "_on_joy_connection_changed")
_on_gamepad_disconnected()


func _input(event: InputEvent) -> void:
if escoria.main.current_scene and escoria.main.current_scene.game:
Expand All @@ -61,6 +101,77 @@ func _input(event: InputEvent) -> void:
update_tooltip_following_mouse_position(event.position)


# https://github.com/godotengine/godot-demo-projects/blob/3.4-585455e/misc/joypads/joypads.gd
# was informative in wiring up the gamepad properly.

func _on_gamepad_connected():
set_physics_process(true)

var primary_event = InputEventJoypadButton.new()
primary_event.button_index = PRIMARY_ACTION_BUTTON
InputMap.add_action(escoria.inputs_manager.ESC_UI_PRIMARY_ACTION)
InputMap.action_add_event(escoria.inputs_manager.ESC_UI_PRIMARY_ACTION, primary_event)

var verb_event = InputEventJoypadButton.new()
verb_event.button_index = CHANGE_VERB_BUTTON
InputMap.add_action(ESC_UI_CHANGE_VERB_ACTION)
InputMap.action_add_event(ESC_UI_CHANGE_VERB_ACTION, verb_event)


func _on_gamepad_disconnected():
InputMap.action_erase_events(escoria.inputs_manager.ESC_UI_PRIMARY_ACTION)
InputMap.erase_action(escoria.inputs_manager.ESC_UI_PRIMARY_ACTION)

InputMap.action_erase_events(ESC_UI_CHANGE_VERB_ACTION)
InputMap.erase_action(ESC_UI_CHANGE_VERB_ACTION)

set_physics_process(false)
_is_gamepad_connected = false


func _on_joy_connection_changed(device: int, connected: bool) -> void:
if device != JOY_DEVICE:
return
elif connected:
_on_gamepad_connected()
else:
_on_gamepad_disconnected()


func _process(_delta) -> void:
if !_is_gamepad_connected:
return

var x = Input.get_joy_axis(JOY_DEVICE, JOY_AXIS_0)
var y = Input.get_joy_axis(JOY_DEVICE, JOY_AXIS_1)
var delta_x = int(x * AXIS_WEIGHT) if abs(x) > DEADZONE else 0
var delta_y = int(y * AXIS_WEIGHT) if abs(y) > DEADZONE else 0
if delta_x or delta_y:
var direction: Vector2
direction.x = delta_x
direction.y = delta_y
escoria.logger.trace("gamepad direction:", [direction])
var viewport = get_viewport()
viewport.warp_mouse(viewport.get_mouse_position() + direction)


func _process_input(event: InputEvent, is_default_state: bool) -> bool:
if not is_default_state:
# ESCBackground is not guaranteed to be set, as we may be on
# the "New Game" screen.
return false
elif _is_gamepad_connected and event is InputEventJoypadButton:
escoria.logger.trace("InputEventJoypadButton:", [event.as_text()])
if event.is_action_pressed(escoria.inputs_manager.ESC_UI_PRIMARY_ACTION):
# Admittedly, this breaks abstraction barriers and is completely
# inappropriate, but it's what works right now.
escoria.inputs_manager._on_left_click_on_bg(get_global_mouse_position())
return true
elif event.is_action_pressed(ESC_UI_CHANGE_VERB_ACTION):
mousewheel_action(1)
return true
return false

## BACKGROUND ##

func left_click_on_bg(position: Vector2) -> void:
Expand Down

0 comments on commit 0f204fd

Please sign in to comment.