feat: made sure the aspect ration fit a pixel art game and added useful addons
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 6s
Create tag and build when new code gets to main / Export (push) Successful in 3m16s

This commit is contained in:
2025-06-27 15:19:12 +02:00
parent 4ec91c1277
commit 9a79715e47
550 changed files with 18812 additions and 0 deletions

View File

@ -0,0 +1,49 @@
@tool
@icon("res://addons/guide/inputs/guide_input.svg")
## A class representing some actuated input.
class_name GUIDEInput
extends Resource
## The current valueo f this input. Depending on the input type only parts of the
## returned vector may be relevant.
var _value:Vector3 = Vector3.ZERO
## The current input state. This will be set by GUIDE when the input is used.
var _state:GUIDEInputState = null
## Whether this input needs a reset per frame. _input is only called when
## there is input happening, but some GUIDE inputs may need to be reset
## in the absence of input.
func _needs_reset() -> bool:
return false
## Resets the input value to the default value. Is called once per frame if
## _needs_reset returns true.
func _reset() -> void:
_value = Vector3.ZERO
## Returns whether this input is the same input as the other input.
func is_same_as(other:GUIDEInput) -> bool:
return false
## Called when the input is started to be used by GUIDE. Can be used to perform
## initializations. The state object can be used to subscribe to input events
## and to get the current input state.
func _begin_usage() -> void :
pass
## Called, when the input is no longer used by GUIDE. Can be used to perform
## cleanup.
func _end_usage() -> void:
pass
func _editor_name() -> String:
return ""
func _editor_description() -> String:
return ""
func _native_value_type() -> GUIDEAction.GUIDEActionValueType:
return -1

View File

@ -0,0 +1 @@
uid://ccvqqvfooyvn0

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1.07241,0,0,1.07396,-3.11767,-2.34767)">
<path d="M17.827,2.164C26.061,2.164 32.747,8.85 32.747,17.084C32.747,25.319 26.061,32.004 17.827,32.004C9.592,32.004 2.907,25.319 2.907,17.084C2.907,8.85 9.592,2.164 17.827,2.164ZM17.827,4.857C11.08,4.857 5.604,10.337 5.604,17.084C5.604,23.831 11.08,29.311 17.827,29.311C24.574,29.311 30.05,23.831 30.05,17.084C30.05,10.337 24.574,4.857 17.827,4.857Z" style="fill:rgb(253,150,0);"/>
</g>
<g transform="matrix(1,0,0,1,0.687353,-2.69876)">
<g transform="matrix(24,0,0,24,11.6286,27.2968)">
<rect x="0.105" y="-0.717" width="0.097" height="0.717" style="fill:rgb(253,150,0);fill-rule:nonzero;"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,38 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://oku7f5t0ox3r"
path="res://.godot/imported/guide_input.svg-d7e8ae255db039e6a02cccc3f844cc0e.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/guide/inputs/guide_input.svg"
dest_files=["res://.godot/imported/guide_input.svg-d7e8ae255db039e6a02cccc3f844cc0e.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=0.5
editor/scale_with_editor_scale=true
editor/convert_colors_with_editor_theme=false

View File

@ -0,0 +1,59 @@
## An input that mirrors the action's value while the action is triggered.
@tool
class_name GUIDEInputAction
extends GUIDEInput
## The action that this input should mirror. This is live tracked, so any change in
## the action will update the input.
@export var action:GUIDEAction:
set(value):
if value == action:
return
action = value
emit_changed()
func _begin_usage():
if is_instance_valid(action):
action.triggered.connect(_on)
action.completed.connect(_off)
action.ongoing.connect(_off)
if action.is_triggered():
_on()
return
# not triggered or no action.
_off()
func _end_usage():
if is_instance_valid(action):
action.triggered.disconnect(_on)
action.completed.disconnect(_off)
action.ongoing.disconnect(_off)
func _on() -> void:
# on is only called when the action is actually existing, so this is
# always not-null here
_value = action.value_axis_3d
func _off() -> void:
_value = Vector3.ZERO
func is_same_as(other:GUIDEInput) -> bool:
return other is GUIDEInputAction and other.action == action
func _to_string():
return "(GUIDEInputAction: " + str(action) + ")"
func _editor_name() -> String:
return "Action"
func _editor_description() -> String:
return "An input that mirrors the action's value while the action is triggered."
func _native_value_type() -> GUIDEAction.GUIDEActionValueType:
return GUIDEAction.GUIDEActionValueType.AXIS_3D

View File

@ -0,0 +1 @@
uid://mc0mxjvhanrx

View File

@ -0,0 +1,150 @@
## Input that triggers if any input from the given device class
## is given.
@tool
class_name GUIDEInputAny
extends GUIDEInput
## Should input from mouse buttons be considered? Deprecated, use
## mouse_buttons instead.
## @deprecated
var mouse:bool:
get: return mouse_buttons
set(value): mouse_buttons = value
## Should input from joy buttons be considered. Deprecated, use
## joy_buttons instead.
## @deprecated
var joy:bool:
get: return joy_buttons
set(value): joy_buttons = value
## Should input from mouse buttons be considered?
@export var mouse_buttons:bool = false
## Should input from mouse movement be considered?
@export var mouse_movement:bool = false
## Minimum movement distance of the mouse before it is considered
## moving.
@export var minimum_mouse_movement_distance:float = 1.0
## Should input from gamepad/joystick buttons be considered?
@export var joy_buttons:bool = false
## Should input from gamepad/joystick axes be considered?
@export var joy_axes:bool = false
## Minimum strength of a single joy axis actuation before it is considered
## as actuated.
@export var minimum_joy_axis_actuation_strength:float = 0.2
## Should input from the keyboard be considered?
@export var keyboard:bool = false
## Should input from touch be considered?
@export var touch:bool = false
func _needs_reset() -> bool:
# Needs reset because we cannot detect the absence of input.
return true
func _begin_usage() -> void:
# subscribe to relevant input events
if mouse_movement:
_state.mouse_position_changed.connect(_refresh)
if mouse_buttons:
_state.mouse_button_state_changed.connect(_refresh)
if keyboard:
_state.keyboard_state_changed.connect(_refresh)
if joy_buttons:
_state.joy_button_state_changed.connect(_refresh)
if joy_axes:
_state.joy_axis_state_changed.connect(_refresh)
if touch:
_state.touch_state_changed.connect(_refresh)
_refresh()
func _end_usage() -> void:
# unsubscribe from input events
if mouse_movement:
_state.mouse_position_changed.disconnect(_refresh)
if mouse_buttons:
_state.mouse_button_state_changed.disconnect(_refresh)
if keyboard:
_state.keyboard_state_changed.disconnect(_refresh)
if joy_buttons:
_state.joy_button_state_changed.disconnect(_refresh)
if joy_axes:
_state.joy_axis_state_changed.disconnect(_refresh)
if touch:
_state.touch_state_changed.disconnect(_refresh)
func _refresh() -> void:
# if the input was already actuated this frame, remain
# actuated, even if more input events come in. Input will
# reset at the end of the frame.
if not _value.is_zero_approx():
return
if keyboard and _state.is_any_key_pressed():
_value = Vector3.RIGHT
return
if mouse_buttons and _state.is_any_mouse_button_pressed():
_value = Vector3.RIGHT
return
if mouse_movement and _state.get_mouse_delta_since_last_frame().length() >= minimum_mouse_movement_distance:
_value = Vector3.RIGHT
return
if joy_buttons and _state.is_any_joy_button_pressed():
_value = Vector3.RIGHT
return
if joy_axes and _state.is_any_joy_axis_actuated(minimum_joy_axis_actuation_strength):
_value = Vector3.RIGHT
return
if touch and _state.is_any_finger_down():
_value = Vector3.RIGHT
return
_value = Vector3.ZERO
func is_same_as(other:GUIDEInput) -> bool:
return other is GUIDEInputAny and \
other.mouse == mouse and \
other.joy == joy and \
other.keyboard == keyboard
func _editor_name() -> String:
return "Any Input"
func _editor_description() -> String:
return "Input that triggers if any input from the given device class is given."
func _native_value_type() -> GUIDEAction.GUIDEActionValueType:
return GUIDEAction.GUIDEActionValueType.BOOL
# support for legacy properties
func _get_property_list():
return [
{
"name": "mouse",
"type": TYPE_BOOL,
"usage": PROPERTY_USAGE_NO_EDITOR
},
{
"name": "joy",
"type": TYPE_BOOL,
"usage": PROPERTY_USAGE_NO_EDITOR
}
]

View File

@ -0,0 +1 @@
uid://w3fbpe7r01n8

View File

@ -0,0 +1,40 @@
## Input from a single joy axis.
@tool
class_name GUIDEInputJoyAxis1D
extends GUIDEInputJoyBase
## The joy axis to sample
@export var axis:JoyAxis = JOY_AXIS_LEFT_X:
set(value):
if value == axis:
return
axis = value
emit_changed()
func _begin_usage() -> void:
_state.joy_axis_state_changed.connect(_refresh)
func _end_usage() -> void:
_state.joy_axis_state_changed.disconnect(_refresh)
func _refresh() -> void:
_value.x = _state.get_joy_axis_value(joy_index, axis)
func is_same_as(other:GUIDEInput) -> bool:
return other is GUIDEInputJoyAxis1D and \
other.axis == axis and \
other.joy_index == joy_index
func _to_string():
return "(GUIDEInputJoyAxis1D: axis=" + str(axis) + ", joy_index=" + str(joy_index) + ")"
func _editor_name() -> String:
return "Joy Axis 1D"
func _editor_description() -> String:
return "The input from a single joy axis."
func _native_value_type() -> GUIDEAction.GUIDEActionValueType:
return GUIDEAction.GUIDEActionValueType.AXIS_1D

View File

@ -0,0 +1 @@
uid://bbhoxsiqwo07l

View File

@ -0,0 +1,53 @@
## Input from two joy axes.
class_name GUIDEInputJoyAxis2D
extends GUIDEInputJoyBase
## The joy axis to sample for x input.
@export var x:JoyAxis = JOY_AXIS_LEFT_X:
set(value):
if value == x:
return
x = value
emit_changed()
## The joy axis to sample for y input.
@export var y:JoyAxis = JOY_AXIS_LEFT_Y:
set(value):
if value == y:
return
y = value
emit_changed()
func _begin_usage() -> void:
_state.joy_axis_state_changed.connect(_refresh)
_refresh()
func _end_usage() -> void:
_state.joy_axis_state_changed.disconnect(_refresh)
func _refresh():
_value.x = _state.get_joy_axis_value(joy_index, x)
_value.y = _state.get_joy_axis_value(joy_index, y)
func is_same_as(other:GUIDEInput) -> bool:
return other is GUIDEInputJoyAxis2D and \
other.x == x and \
other.y == y and \
other.joy_index == joy_index
func _to_string():
return "(GUIDEInputJoyAxis2D: x=" + str(x) + ", y=" + str(y) + ", joy_index=" + str(joy_index) + ")"
func _editor_name() -> String:
return "Joy Axis 2D"
func _editor_description() -> String:
return "The input from two Joy axes. Usually from a stick."
func _native_value_type() -> GUIDEAction.GUIDEActionValueType:
return GUIDEAction.GUIDEActionValueType.AXIS_2D

View File

@ -0,0 +1 @@
uid://doauobik3xyea

View File

@ -0,0 +1,13 @@
## Base class for joystick inputs.
@tool
class_name GUIDEInputJoyBase
extends GUIDEInput
## The index of the connected joy pad to check. If -1 checks all joypads.
@export var joy_index:int = -1:
set(value):
if value == joy_index:
return
joy_index = value
emit_changed()

View File

@ -0,0 +1 @@
uid://cnqnbdsw3lw12

View File

@ -0,0 +1,41 @@
@tool
class_name GUIDEInputJoyButton
extends GUIDEInputJoyBase
@export var button:JoyButton = JOY_BUTTON_A:
set(value):
if value == button:
return
button = value
emit_changed()
func _begin_usage() -> void:
_state.joy_button_state_changed.connect(_refresh)
_refresh()
func _end_usage() -> void:
_state.joy_button_state_changed.disconnect(_refresh)
func _refresh():
_value.x = 1.0 if _state.is_joy_button_pressed(joy_index, button) else 0.0
func is_same_as(other:GUIDEInput) -> bool:
return other is GUIDEInputJoyButton and \
other.button == button and \
other.joy_index == joy_index
func _to_string():
return "(GUIDEInputJoyButton: button=" + str(button) + ", joy_index=" + str(joy_index) + ")"
func _editor_name() -> String:
return "Joy Button"
func _editor_description() -> String:
return "A button press from a joy button."
func _native_value_type() -> GUIDEAction.GUIDEActionValueType:
return GUIDEAction.GUIDEActionValueType.BOOL

View File

@ -0,0 +1 @@
uid://rvttn472ix6v

View File

@ -0,0 +1,126 @@
@tool
class_name GUIDEInputKey
extends GUIDEInput
## The physical keycode of the key.
@export var key:Key:
set(value):
if value == key:
return
key = value
emit_changed()
@export_group("Modifiers")
## Whether shift must be pressed.
@export var shift:bool = false:
set(value):
if value == shift:
return
shift = value
emit_changed()
## Whether control must be pressed.
@export var control:bool = false:
set(value):
if value == control:
return
control = value
emit_changed()
## Whether alt must be pressed.
@export var alt:bool = false:
set(value):
if value == alt:
return
alt = value
emit_changed()
## Whether meta/win/cmd must be pressed.
@export var meta:bool = false:
set(value):
if value == meta:
return
meta = value
emit_changed()
## Whether this input should fire if additional
## modifier keys are currently pressed.
@export var allow_additional_modifiers:bool = true:
set(value):
if value == allow_additional_modifiers:
return
allow_additional_modifiers = value
emit_changed()
## Helper array. All keys that must be pressed for this input to considered actuated.
var _must_be_pressed:Array[Key] = []
## Helper array. All keys that must not be pressed for this input to considered actuated.
var _must_not_be_pressed:Array[Key] = []
func _begin_usage() -> void:
_must_be_pressed = [key]
# also add the modifiers to the list of keys that must be pressed
if shift:
_must_be_pressed.append(KEY_SHIFT)
if control:
_must_be_pressed.append(KEY_CTRL)
if alt:
_must_be_pressed.append(KEY_ALT)
if meta:
_must_be_pressed.append(KEY_META)
_must_not_be_pressed = []
# now unless additional modifiers are allowed, add all modifiers
# that are not required to the list of keys that must not be pressed
# except if the bound key is actually the modifier itself
if not allow_additional_modifiers:
if not shift and key != KEY_SHIFT:
_must_not_be_pressed.append(KEY_SHIFT)
if not control and key != KEY_CTRL:
_must_not_be_pressed.append(KEY_CTRL)
if not alt and key != KEY_ALT:
_must_not_be_pressed.append(KEY_ALT)
if not meta and key != KEY_META:
_must_not_be_pressed.append(KEY_META)
# subscribe to input events
_state.keyboard_state_changed.connect(_refresh)
_refresh()
func _end_usage() -> void:
# unsubscribe from input events
_state.keyboard_state_changed.disconnect(_refresh)
func _refresh():
# We are actuated if all keys that must be pressed are pressed and none of the keys that must not be pressed
# are pressed.
var is_actuated:bool = _state.are_all_keys_pressed(_must_be_pressed) and not _state.is_at_least_one_key_pressed(_must_not_be_pressed)
_value.x = 1.0 if is_actuated else 0.0
func is_same_as(other:GUIDEInput) -> bool:
return other is GUIDEInputKey \
and other.key == key \
and other.shift == shift \
and other.control == control \
and other.alt == alt \
and other.meta == meta \
and other.allow_additional_modifiers == allow_additional_modifiers
func _to_string():
return "(GUIDEInputKey: key=" + str(key) + ", shift=" + str(shift) + ", alt=" + str(alt) + ", control=" + str(control) + ", meta="+ str(meta) + ")"
func _editor_name() -> String:
return "Key"
func _editor_description() -> String:
return "A button press on the keyboard."
func _native_value_type() -> GUIDEAction.GUIDEActionValueType:
return GUIDEAction.GUIDEActionValueType.BOOL

View File

@ -0,0 +1 @@
uid://cw71o87tvdx3q

View File

@ -0,0 +1,57 @@
@tool
class_name GUIDEInputMouseAxis1D
extends GUIDEInput
enum GUIDEInputMouseAxis {
X,
Y
}
@export var axis:GUIDEInputMouseAxis:
set(value):
if value == axis:
return
axis = value
emit_changed()
# we don't get mouse updates when the mouse is not moving, so this needs to be
# reset every frame
func _needs_reset() -> bool:
return true
func _begin_usage() -> void:
# subscribe to mouse movement events
_state.mouse_position_changed.connect(_refresh)
_refresh()
func _end_usage() -> void:
# unsubscribe from mouse movement events
_state.mouse_position_changed.disconnect(_refresh)
func _refresh() -> void:
var delta:Vector2 = _state.get_mouse_delta_since_last_frame()
match axis:
GUIDEInputMouseAxis.X:
_value.x = delta.x
GUIDEInputMouseAxis.Y:
_value.x = delta.y
func is_same_as(other:GUIDEInput):
return other is GUIDEInputMouseAxis1D and other.axis == axis
func _to_string():
return "(GUIDEInputMouseAxis1D: axis=" + str(axis) + ")"
func _editor_name() -> String:
return "Mouse Axis 1D"
func _editor_description() -> String:
return "Relative mouse movement on a single axis."
func _native_value_type() -> GUIDEAction.GUIDEActionValueType:
return GUIDEAction.GUIDEActionValueType.AXIS_1D

View File

@ -0,0 +1 @@
uid://b6bwb7ie85kl1

View File

@ -0,0 +1,42 @@
@tool
class_name GUIDEInputMouseAxis2D
extends GUIDEInput
# we don't get mouse updates when the mouse is not moving, so this needs to be
# reset every frame
func _needs_reset() -> bool:
return true
func _begin_usage() -> void:
# subscribe to mouse movement events
_state.mouse_position_changed.connect(_refresh)
_refresh()
func _end_usage() -> void:
# unsubscribe from mouse movement events
_state.mouse_position_changed.disconnect(_refresh)
func _refresh() -> void:
var delta:Vector2 = _state.get_mouse_delta_since_last_frame()
_value.x = delta.x
_value.y = delta.y
func is_same_as(other:GUIDEInput):
return other is GUIDEInputMouseAxis2D
func _to_string():
return "(GUIDEInputMouseAxis2D)"
func _editor_name() -> String:
return "Mouse Axis 2D"
func _editor_description() -> String:
return "Relative mouse movement on 2 axes."
func _native_value_type() -> GUIDEAction.GUIDEActionValueType:
return GUIDEAction.GUIDEActionValueType.AXIS_2D

View File

@ -0,0 +1 @@
uid://dh0hf08e0yit5

View File

@ -0,0 +1,74 @@
@tool
class_name GUIDEInputMouseButton
extends GUIDEInput
@export var button: MouseButton = MOUSE_BUTTON_LEFT:
set(value):
if value == button:
return
button = value
emit_changed()
# The value that this input will be reset to at the end of the frame.
var _reset_to: Vector3
var _was_pressed_this_frame: bool
func _needs_reset() -> bool:
# mouse wheel up and down can potentially send multiple inputs within a single frame
# so we need to smooth this out a bit.
return button == MOUSE_BUTTON_WHEEL_UP or button == MOUSE_BUTTON_WHEEL_DOWN
func _reset() -> void:
_was_pressed_this_frame = false
_value = _reset_to
func _begin_usage() -> void:
# subscribe to mouse button events
_state.mouse_button_state_changed.connect(_refresh)
_refresh()
func _end_usage() -> void:
# unsubscribe from mouse button events
_state.mouse_button_state_changed.disconnect(_refresh)
func _refresh() -> void:
var is_pressed: bool = _state.is_mouse_button_pressed(button)
if _needs_reset():
# we always reset to the last event we received in a frame
# so after the frame is over we're still in sync.
_reset_to.x = 1.0 if is_pressed else 0.0
if is_pressed:
_was_pressed_this_frame = true
if not is_pressed and _was_pressed_this_frame:
# keep pressed state for this frame
return
_value.x = 1.0 if is_pressed else 0.0
func is_same_as(other: GUIDEInput) -> bool:
return other is GUIDEInputMouseButton and other.button == button
func _to_string():
return "(GUIDEInputMouseButton: button=" + str(button) + ")"
func _editor_name() -> String:
return "Mouse Button"
func _editor_description() -> String:
return "A press of a mouse button. The mouse wheel is also a button."
func _native_value_type() -> GUIDEAction.GUIDEActionValueType:
return GUIDEAction.GUIDEActionValueType.BOOL

View File

@ -0,0 +1 @@
uid://vgjlx6p007lp

View File

@ -0,0 +1,39 @@
@tool
class_name GUIDEInputMousePosition
extends GUIDEInput
func _begin_usage() -> void :
# subscribe to mouse movement events
_state.mouse_position_changed.connect(_refresh)
_refresh()
func _end_usage() -> void:
# unsubscribe from mouse movement events
_state.mouse_position_changed.disconnect(_refresh)
func _refresh():
var position:Vector2 = _state.get_mouse_position()
_value.x = position.x
_value.y = position.y
func is_same_as(other:GUIDEInput):
return other is GUIDEInputMousePosition
func _to_string():
return "(GUIDEInputMousePosition)"
func _editor_name() -> String:
return "Mouse Position"
func _editor_description() -> String:
return "Position of the mouse in the main viewport."
func _native_value_type() -> GUIDEAction.GUIDEActionValueType:
return GUIDEAction.GUIDEActionValueType.AXIS_2D

View File

@ -0,0 +1 @@
uid://deeru16npi81q

View File

@ -0,0 +1,437 @@
## The GUIDEInputState holds the current state of all input. It is basically a wrapper around Godot's Input
## class that provides some additional functionality like getting the information if any key or mouse button
## is currently pressed. It also is the single entry point for all input events from Godot, so we don't have
## process them in every GUIDEInput object and duplicate input handling code everywere. This also improves performance.
##
class_name GUIDEInputState
## Device ID for a virtual joystick that means "any joystick".
## This relies on the fact that Godot's device IDs for joysticks are always >= 0.
## https://github.com/godotengine/godot/blob/80a3d205f1ad22e779a64921fb56d62b893881ae/core/input/input.cpp#L1821
const ANY_JOY_DEVICE_ID: int = -1
## Signalled, when the keyboard state has changed.
signal keyboard_state_changed()
## Signalled, when the mouse motion state has changed.
signal mouse_position_changed()
## Signalled, when the mouse button state has changed.
signal mouse_button_state_changed()
## Signalled, when the joy button state has changed.
signal joy_button_state_changed()
## Signalled, when the joy axis state has changed.
signal joy_axis_state_changed()
## Signalled, when the touch state has changed.
signal touch_state_changed()
# Keys that are currently pressed. Key is the key index, value is not important. The presence of a key in the dictionary
# indicates that the key is currently pressed.
var _keys: Dictionary = {}
# Fingers that are currently touching the screen. Key is the finger index, value is the position (Vector2).
var _finger_positions: Dictionary = {}
# The mouse movement since the last frame.
var _mouse_movement: Vector2 = Vector2.ZERO
# Mouse buttons that are currently pressed. Key is the button index, value is not important. The presence of a key
# in the dictionary indicates that the button is currently pressed.
var _mouse_buttons: Dictionary = {}
# Joy buttons that are currently pressed. Key is device id, value is a dictionary with the button index as key. The
# value of the inner dictionary is not important. The presence of a key in the inner dictionary indicates that the button
# is currently pressed.
var _joy_buttons: Dictionary = {}
# Current values of joy axes. Key is device id, value is a dictionary with the axis index as key.
# The value of the inner dictionary is the axis value. Once an axis is actuated, it will be added to the dictionary.
# We will not remove it anymore after that.
var _joy_axes: Dictionary = {}
# The current mapping of joy index to device id. This is used to map the joy index to the device id. A joy index
# if -1 means "any device id".
var _joy_index_to_device_id: Dictionary = {}
# This holds the state of keys that have changed this frame. The key is the key, the value is true if the key
# was last pressed and false if it was last released.
var _pending_keys:Dictionary = {}
# This holds the state of mouse buttons that have changed this frame. The key is the mouse button index, the value is
# true, if the mouse button was last pressed and false if it was last released.
var _pending_mouse_buttons:Dictionary = {}
# This holds the state of joy buttons that have changed this frame. The key is the joy device id, the value is
# a nested dictionary. The nested dictionary has the button index as key and true as value if the button was last
# pressed or false if it was last released.
var _pending_joy_buttons:Dictionary = {}
func _init():
Input.joy_connection_changed.connect(_refresh_joy_device_ids)
_clear()
# Used by the automated tests to make sure we don't have any leftovers from the
# last test.
func _clear():
_keys.clear()
_finger_positions.clear()
_mouse_movement = Vector2.ZERO
_mouse_buttons.clear()
_joy_buttons.clear()
_joy_axes.clear()
_refresh_joy_device_ids(0, 0)
# ensure we have an entry for the virtual "any device id"
_joy_buttons[ANY_JOY_DEVICE_ID] = {}
_joy_axes[ANY_JOY_DEVICE_ID] = {}
# Called when any joy device is connected or disconnected. This will refresh the joy device ids and clear out any
# joy state which is not valid anymore. Will also notify relevant inputs.
func _refresh_joy_device_ids(_ignore1, _ignore2):
# refresh the joy device ids
_joy_index_to_device_id.clear()
var connected_joys:Array[int] = Input.get_connected_joypads()
for i in connected_joys.size():
var device_id:int = connected_joys[i]
_joy_index_to_device_id[i] = device_id
# ensure we have an inner dictionary for the device id
# by setting this here, we don't need to check for the device id
# on every input event
if not _joy_buttons.has(device_id):
_joy_buttons[device_id] = {}
if not _joy_axes.has(device_id):
_joy_axes[device_id] = {}
if not _pending_joy_buttons.has(device_id):
_pending_joy_buttons[device_id] = {}
# add a virtual device id for the "any device id" case
_joy_index_to_device_id[-1] = ANY_JOY_DEVICE_ID
for device_id in _pending_joy_buttons.keys():
if device_id != ANY_JOY_DEVICE_ID and not connected_joys.has(device_id):
_pending_joy_buttons.erase(device_id)
var dirty: bool = false
# clear out any joy state which is not valid anymore
for device_id in _joy_buttons.keys():
if device_id != ANY_JOY_DEVICE_ID and not connected_joys.has(device_id):
dirty = true
_joy_buttons.erase(device_id)
if dirty:
# notify all inputs that the joy state has changed
joy_button_state_changed.emit()
dirty = false
for device_id in _joy_axes.keys():
if device_id != ANY_JOY_DEVICE_ID and not connected_joys.has(device_id):
dirty = true
_joy_axes.erase(device_id)
if dirty:
# notify all inputs that the joy state has changed
joy_axis_state_changed.emit()
## Called at the end of the frame to reset the state before the next frame.
func _reset() -> void:
_mouse_movement = Vector2.ZERO
# apply pending key state at end of the frame.
for key in _pending_keys.keys():
var is_down = _pending_keys[key]
if is_down and not _keys.has(key):
_keys[key] = true
# we emit one change event per changed key just like it would happen
# as if the keys were not pressed very fast. this is to ensure same
# execution order of things, so everything stays predictable
keyboard_state_changed.emit()
elif not is_down and _keys.has(key):
_keys.erase(key)
keyboard_state_changed.emit()
_pending_keys.clear()
# apply pending mouse button state
for button in _pending_mouse_buttons.keys():
var is_down = _pending_mouse_buttons[button]
if is_down and not _mouse_buttons.has(button):
_mouse_buttons[button] = true
mouse_button_state_changed.emit()
elif not is_down and _mouse_buttons.has(button):
_mouse_buttons.erase(button)
mouse_button_state_changed.emit()
_pending_mouse_buttons.clear()
# apply pending joy button state
for joy in _pending_joy_buttons.keys():
for button in _pending_joy_buttons[joy]:
var changed:bool = false
var is_down = _pending_joy_buttons[joy][button]
if is_down and not _joy_buttons[joy].has(button):
_joy_buttons[joy][button] = true
changed = true
elif not is_down and _joy_buttons[joy].has(button):
_joy_buttons[joy].erase(button)
changed = true
# only evaluate the ANY_JOY device if actually something changed.
# otherwise the inner value would not change
if changed:
var any_value: bool = false
for inner in _joy_buttons.keys():
if inner != ANY_JOY_DEVICE_ID and _joy_buttons[inner].has(button):
any_value = true
break
if any_value: # we don't need to check the change state here as we'r going to send an event anyways.
_joy_buttons[ANY_JOY_DEVICE_ID][button] = true
else:
_joy_buttons[ANY_JOY_DEVICE_ID].erase(button)
joy_button_state_changed.emit()
# and clear out the pending buttons for this joy
_pending_joy_buttons[joy].clear()
## Processes an input event and updates the state.
func _input(event: InputEvent) -> void:
# ----------------------- KEYBOARD -----------------------------
if event is InputEventKey:
var index: int = event.physical_keycode
# check if the key already changed value this frame
# if so, record the change only, it will be applied at the
# end of the frame
if _pending_keys.has(index):
_pending_keys[index] = event.pressed
return
_pending_keys[index] = event.pressed
if event.pressed and not _keys.has(index):
_keys[index] = true
keyboard_state_changed.emit()
return
if not event.pressed and _keys.has(index):
_keys.erase(index)
keyboard_state_changed.emit()
return
return
# ----------------------- MOUSE MOVEMENT -----------------------
if event is InputEventMouseMotion:
# Emit the mouse moved signal with the distance moved
_mouse_movement += event.relative
mouse_position_changed.emit()
return
# ----------------------- MOUSE BUTTONS -----------------------
if event is InputEventMouseButton:
var index: int = event.button_index
# check if the mouse button already changed value this frame
# if so, record the change only, it will be applied at the
# end of the frame
if _pending_mouse_buttons.has(index):
_pending_mouse_buttons[index] = event.pressed
return
_pending_mouse_buttons[index] = event.pressed
if event.pressed and not _mouse_buttons.has(index):
_mouse_buttons[index] = true
mouse_button_state_changed.emit()
return
if not event.pressed and _mouse_buttons.has(index):
_mouse_buttons.erase(index)
mouse_button_state_changed.emit()
return
return
# ----------------------- JOYSTICK BUTTONS -----------------------
if event is InputEventJoypadButton:
var device_id: int = event.device
var button: int = event.button_index
# _refresh_joy_device_ids ensures we have an inner dictionary for the device id
# so we don't need to check for it here
if _pending_joy_buttons[device_id].has(button):
_pending_joy_buttons[device_id][button] = event.pressed
return
_pending_joy_buttons[device_id][button] = event.pressed
var changed:bool = false
if event.pressed and not _joy_buttons[device_id].has(button):
_joy_buttons[device_id][button] = true
changed = true
elif not event.pressed and _joy_buttons[device_id].has(button):
_joy_buttons[device_id].erase(button)
changed = true
# finally set the ANY_JOY_DEVICE_ID state based on what we know
# only do this if the button value actually changed. Otherwise
# the Any value would not change either.
if changed:
var any_value: bool = false
for inner in _joy_buttons.keys():
if inner != ANY_JOY_DEVICE_ID and _joy_buttons[inner].has(button):
any_value = true
break
if any_value:
_joy_buttons[ANY_JOY_DEVICE_ID][button] = true
else:
_joy_buttons[ANY_JOY_DEVICE_ID].erase(button)
# Emit the joy button state changed signal if something changed
if changed:
joy_button_state_changed.emit()
return
# ----------------------- JOYSTICK AXES -----------------------
if event is InputEventJoypadMotion:
var device_id: int = event.device
var axis: int = event.axis
# update the axis value
_joy_axes[device_id][axis] = event.axis_value
# for the ANY_JOY_DEVICE_ID, we apply the maximum actuation of all devices (in any direction)
var any_value: float = 0.0
var maximum_actuation: float = 0.0
for inner in _joy_axes.keys():
if inner != ANY_JOY_DEVICE_ID and _joy_axes[inner].has(axis):
var strength: float = abs(_joy_axes[inner][axis])
if strength > maximum_actuation:
maximum_actuation = strength
any_value = _joy_axes[inner][axis]
_joy_axes[ANY_JOY_DEVICE_ID][axis] = any_value
# Emit the joy axis state changed signal
joy_axis_state_changed.emit()
return
# ----------------------- TOUCH INPUT -----------------------
if event is InputEventScreenTouch:
if event.pressed:
_finger_positions[event.index] = event.position
else:
_finger_positions.erase(event.index)
touch_state_changed.emit()
return
if event is InputEventScreenDrag:
_finger_positions[event.index] = event.position
touch_state_changed.emit()
return
## Returns true if the key with the given index is currently pressed.
func is_key_pressed(key: Key) -> bool:
return _keys.has(key)
# Returns true if at least one key in the given array is currently pressed.
func is_at_least_one_key_pressed(keys:Array[Key]) -> bool:
for key in keys:
if _keys.has(key):
return true
return false
# Returns true if all keys in the given array are currently pressed.
func are_all_keys_pressed(keys:Array[Key]) -> bool:
return _keys.has_all(keys)
## Returns true if currently any key is pressed.
func is_any_key_pressed() -> bool:
return not _keys.is_empty()
## Gets the mouse movement since the last frame.
## If no movement has been detected, returns Vector2.ZERO.
func get_mouse_delta_since_last_frame() -> Vector2:
return _mouse_movement
## Returns the current mouse position in the root viewport.
func get_mouse_position() -> Vector2:
return Engine.get_main_loop().root.get_mouse_position()
## Returns true if the mouse button with the given index is currently pressed.
func is_mouse_button_pressed(button_index: MouseButton) -> bool:
return _mouse_buttons.has(button_index)
## Returns true if currently any mouse button is pressed.
func is_any_mouse_button_pressed() -> bool:
return not _mouse_buttons.is_empty()
## Returns the current value of the given joy axis on the device with the given index. If no
## such device or axis exists, returns 0.0.
func get_joy_axis_value(index:int, axis:JoyAxis) -> float:
var device_id: int = _joy_index_to_device_id.get(index, -9999)
# unknown device
if device_id == -9999:
return 0.0
if _joy_axes.has(device_id):
var inner = _joy_axes[device_id]
return inner.get(axis, 0.0)
return 0.0
## Returns true, if the given joy button is currentely pressed on the device with the given index.
func is_joy_button_pressed(index:int, button:JoyButton) -> bool:
var device_id: int = _joy_index_to_device_id.get(index, -9999)
# unknown device
if device_id == -9999:
return false
if _joy_buttons.has(device_id):
return _joy_buttons[device_id].has(button)
return false
## Returns true, if currently any joy button is pressed on any device.
func is_any_joy_button_pressed() -> bool:
for inner in _joy_buttons.values():
if not inner.is_empty():
return true
return false
## Returns true if currently any joy axis is actuated with at least the given strength.
func is_any_joy_axis_actuated(minimum_strength: float) -> bool:
for inner in _joy_axes.values():
for value in inner.values():
if abs(value) >= minimum_strength:
return true
return false
## Gets the finger position of the finger at the given index.
## If finger_index is < 0, returns the average of all finger positions.
## Will only return a position if the amount of fingers
## currently touching matches finger_count.
##
## If no finger position can be determined, returns Vector2.INF.
func get_finger_position(finger_index: int, finger_count: int) -> Vector2:
# if we have no finger positions right now, we can cut it short here
if _finger_positions.is_empty():
return Vector2.INF
# If the finger count doesn't match we have no position right now
if _finger_positions.size() != finger_count:
return Vector2.INF
# if a finger index is set, use this fingers position, if available
if finger_index > -1:
return _finger_positions.get(finger_index, Vector2.INF)
var result: Vector2 = Vector2.ZERO
for value in _finger_positions.values():
result += value
result /= float(finger_count)
return result
## Returns true, if currently any finger is touching the screen.
func is_any_finger_down() -> bool:
return not _finger_positions.is_empty()

View File

@ -0,0 +1 @@
uid://c11q8ft87iu87

View File

@ -0,0 +1,91 @@
## Input representing angle changes between two fingers.
@tool
class_name GUIDEInputTouchAngle
extends GUIDEInput
## Unit in which the angle should be provided
enum AngleUnit {
## Angle is provided in radians
RADIANS = 0,
## Angle is provided in degrees.
DEGREES = 1
}
## The unit in which the angle should be provided
@export var unit:AngleUnit = AngleUnit.RADIANS
var _initial_angle:float = INF
# We use the reset call to calculate the angle for this frame
# so it can serve as reference for the next frame
func _needs_reset() -> bool:
return true
func _reset():
var angle = _calculate_angle()
# update initial angle when input is actuated or stops being actuated
if is_finite(_initial_angle) != is_finite(angle):
_initial_angle = angle
func _begin_usage() -> void:
# subscribe to relevant input events
_state.touch_state_changed.connect(_refresh)
_refresh()
func _end_usage() -> void:
# unsubscribe from input events
_state.touch_state_changed.disconnect(_refresh)
func _refresh():
var angle := _calculate_angle()
# if either current angle or initial angle is not set,
# we are zero
if not is_finite(angle) or not is_finite(_initial_angle):
_value = Vector3.ZERO
return
# we assume that _initial_distance is never 0 because
# you cannot have two fingers physically at the same place
# on a touch screen (unless you're a ghost, which raises
# the question how you are using a touch screen in the first place)
var final_angle:float = angle - _initial_angle
if unit == AngleUnit.DEGREES:
final_angle = rad_to_deg(final_angle)
_value = Vector3(final_angle, 0, 0)
func _calculate_angle() -> float:
var pos1:Vector2 = _state.get_finger_position(0, 2)
# if we have no position for first finger, we can immediately abort
if not pos1.is_finite():
return INF
var pos2:Vector2 = _state.get_finger_position(1, 2)
# if there is no second finger, we can abort as well
if not pos2.is_finite():
return INF
# calculate distance for the fingers
return -pos1.angle_to_point(pos2)
func is_same_as(other:GUIDEInput):
return other is GUIDEInputTouchAngle and \
other.unit == unit
func _to_string():
return "(GUIDEInputTouchAngle unit=" + ("radians" if unit == AngleUnit.RADIANS else "degrees") + ")"
func _editor_name() -> String:
return "Touch Angle"
func _editor_description() -> String:
return "Angle changes of two touching fingers."
func _native_value_type() -> GUIDEAction.GUIDEActionValueType:
return GUIDEAction.GUIDEActionValueType.AXIS_1D

View File

@ -0,0 +1 @@
uid://b3sxmqknm7ljs

View File

@ -0,0 +1,44 @@
@tool
class_name GUIDEInputTouchAxis1D
extends GUIDEInputTouchAxisBase
enum GUIDEInputTouchAxis {
X,
Y
}
@export var axis:GUIDEInputTouchAxis:
set(value):
if value == axis:
return
axis = value
emit_changed()
func is_same_as(other:GUIDEInput):
return other is GUIDEInputTouchAxis1D and \
other.finger_count == finger_count and \
other.finger_index == finger_index and \
other.axis == axis
func _apply_value(value:Vector2):
match axis:
GUIDEInputTouchAxis.X:
_value = Vector3(value.x, 0, 0)
GUIDEInputTouchAxis.Y:
_value = Vector3(value.y, 0, 0)
func _to_string():
return "(GUIDEInputTouchAxis1D finger_count=" + str(finger_count) + \
" finger_index=" + str(finger_index) +" axis=" + ("X" if axis == GUIDEInputTouchAxis.X else "Y") + ")"
func _editor_name() -> String:
return "Touch Axis1D"
func _editor_description() -> String:
return "Relative movement of a touching finger on a single axis."
func _native_value_type() -> GUIDEAction.GUIDEActionValueType:
return GUIDEAction.GUIDEActionValueType.AXIS_1D

View File

@ -0,0 +1 @@
uid://idi72xetfe0s

View File

@ -0,0 +1,27 @@
@tool
class_name GUIDEInputTouchAxis2D
extends GUIDEInputTouchAxisBase
func _apply_value(value:Vector2):
_value = Vector3(value.x, value.y, 0)
func is_same_as(other:GUIDEInput):
return other is GUIDEInputTouchAxis2D and \
other.finger_count == finger_count and \
other.finger_index == finger_index
func _to_string():
return "(GUIDEInputTouchAxis2D finger_count=" + str(finger_count) + \
" finger_index=" + str(finger_index) +")"
func _editor_name() -> String:
return "Touch Axis2D"
func _editor_description() -> String:
return "2D relative movement of a touching finger."
func _native_value_type() -> GUIDEAction.GUIDEActionValueType:
return GUIDEAction.GUIDEActionValueType.AXIS_2D

View File

@ -0,0 +1 @@
uid://83ggp3br4dqv

View File

@ -0,0 +1,49 @@
## Base class for axis-like touch input.
@tool
class_name GUIDEInputTouchAxisBase
extends GUIDEInputTouchBase
var _last_position:Vector2 = Vector2.INF
# We use the reset call to calculate the position for this frame
# so it can serve as reference for the next frame
func _needs_reset() -> bool:
return true
func _reset() -> void:
_last_position = _state.get_finger_position(finger_index, finger_count)
_apply_value(_calculate_value(_last_position))
func _begin_usage() -> void:
# subscribe to relevant input events
_state.touch_state_changed.connect(_refresh)
_refresh()
func _end_usage() -> void:
# unsubscribe from input events
_state.touch_state_changed.disconnect(_refresh)
func _refresh() -> void:
# calculate live position from the cache
var new_position:Vector2 = _state.get_finger_position(finger_index, finger_count)
_apply_value(_calculate_value(new_position))
func _apply_value(value:Vector2):
pass
func _calculate_value(new_position:Vector2) -> Vector2:
# if we cannot calculate a delta because old or new position
# are undefined, we say the delta is zero
if not _last_position.is_finite() or not new_position.is_finite():
return Vector2.ZERO
return new_position - _last_position
func is_same_as(other:GUIDEInput):
return other is GUIDEInputTouchAxis2D and \
other.finger_count == finger_count and \
other.finger_index == finger_index

View File

@ -0,0 +1 @@
uid://c32np6tk2l12

View File

@ -0,0 +1,22 @@
## Base class for generic touch input
@tool
class_name GUIDEInputTouchBase
extends GUIDEInput
## The number of fingers to be tracked.
@export_range(1, 5, 1, "or_greater") var finger_count:int = 1:
set(value):
if value < 1:
value = 1
finger_count = value
emit_changed()
## The index of the finger for which the position/delta should be reported
## (0 = first finger, 1 = second finger, etc.). If -1, reports the average position/delta for
## all fingers currently touching.
@export_range(-1, 4, 1, "or_greater") var finger_index:int = 0:
set(value):
if value < -1:
value = -1
finger_index = value
emit_changed()

View File

@ -0,0 +1 @@
uid://dfmq3cijb3ju3

View File

@ -0,0 +1,76 @@
## Input representing the distance changes between two fingers.
@tool
class_name GUIDEInputTouchDistance
extends GUIDEInput
var _initial_distance:float = INF
# We use the reset call to calculate the distance for this frame
# so it can serve as reference for the next frame
func _needs_reset() -> bool:
return true
func _reset():
var distance = _calculate_distance()
# update initial distance when input is actuated or stops being actuated
if is_finite(_initial_distance) != is_finite(distance):
_initial_distance = distance
func _begin_usage() -> void:
# subscribe to relevant input events
_state.touch_state_changed.connect(_refresh)
_refresh()
func _end_usage() -> void:
# unsubscribe from input events
_state.touch_state_changed.disconnect(_refresh)
func _refresh() -> void:
var distance := _calculate_distance()
# if either current distance or initial distance is not set,
# we are zero
if not is_finite(distance) or not is_finite(_initial_distance):
_value = Vector3.ZERO
return
# we assume that _initial_distance is never 0 because
# you cannot have two fingers physically at the same place
# on a touch screen
_value = Vector3(distance / _initial_distance, 0, 0)
func _calculate_distance() -> float:
var pos1:Vector2 = _state.get_finger_position(0, 2)
# if we have no position for first finger, we can immediately abort
if not pos1.is_finite():
return INF
var pos2:Vector2 = _state.get_finger_position(1, 2)
# if there is no second finger, we can abort as well
if not pos2.is_finite():
return INF
# calculate distance for the fingers
return pos1.distance_to(pos2)
func is_same_as(other:GUIDEInput):
return other is GUIDEInputTouchDistance
func _to_string():
return "(GUIDEInputTouchDistance)"
func _editor_name() -> String:
return "Touch Distance"
func _editor_description() -> String:
return "Distance of two touching fingers."
func _native_value_type() -> GUIDEAction.GUIDEActionValueType:
return GUIDEAction.GUIDEActionValueType.AXIS_1D

View File

@ -0,0 +1 @@
uid://hjbdbq1wud8h

View File

@ -0,0 +1,45 @@
@tool
class_name GUIDEInputTouchPosition
extends GUIDEInputTouchBase
func _begin_usage():
# subscribe to touch events
_state.touch_state_changed.connect(_refresh)
_refresh()
func _end_usage():
# unsubscribe from touch events
_state.touch_state_changed.disconnect(_refresh)
func _refresh() -> void:
# update finger position
var position:Vector2 = _state.get_finger_position(finger_index, finger_count)
if not position.is_finite():
_value = Vector3.INF
return
_value = Vector3(position.x, position.y, 0)
func is_same_as(other:GUIDEInput):
return other is GUIDEInputTouchPosition and \
other.finger_count == finger_count and \
other.finger_index == finger_index
func _to_string():
return "(GUIDEInputTouchPosition finger_count=" + str(finger_count) + \
" finger_index=" + str(finger_index) +")"
func _editor_name() -> String:
return "Touch Position"
func _editor_description() -> String:
return "Position of a touching finger."
func _native_value_type() -> GUIDEAction.GUIDEActionValueType:
return GUIDEAction.GUIDEActionValueType.AXIS_2D

View File

@ -0,0 +1 @@
uid://c2bvqibcqlmv5

View File

@ -0,0 +1 @@
uid://duw4xaip8onca