Add cap sprite to child scene and update project configuration

This commit is contained in:
2025-04-26 03:52:45 +02:00
parent d95176fba0
commit 0c1192536c
374 changed files with 11968 additions and 1276 deletions

View File

@@ -0,0 +1,50 @@
@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
## 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
## Called when an input event happens. Should update the
## the input value of this input.
func _input(event:InputEvent):
pass
## 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.
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,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,115 @@
## Input that triggers if any input from the given device class
## is given. Only looks for button inputs, not axis inputs as axes
## have a tendency to accidentally trigger.
@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 _input(event:InputEvent):
if mouse_buttons and event is InputEventMouseButton:
_value = Vector3.RIGHT
return
if mouse_movement and event is InputEventMouseMotion \
and event.relative.length() >= minimum_mouse_movement_distance:
_value = Vector3.RIGHT
return
if joy_buttons and event is InputEventJoypadButton:
_value = Vector3.RIGHT
return
if joy_axes and event is InputEventJoypadMotion \
and abs(event.axis_value) >= minimum_joy_axis_actuation_strength:
_value = Vector3.RIGHT
return
if keyboard and event is InputEventKey:
_value = Vector3.RIGHT
return
if touch and (event is InputEventScreenTouch or event is InputEventScreenDrag):
_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,43 @@
## 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 _input(event:InputEvent):
if not event is InputEventJoypadMotion:
return
if event.axis != axis:
return
if joy_index > -1 and event.device != _joy_id:
return
_value.x = event.axis_value
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,58 @@
## 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 _input(event:InputEvent):
if not event is InputEventJoypadMotion:
return
if event.axis != x and event.axis != y:
return
if joy_index > -1 and event.device != _joy_id:
return
if event.axis == x:
_value.x = event.axis_value
return
if event.axis == y:
_value.y = event.axis_value
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,35 @@
## 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()
## Cached joystick ID if we use a joy index.
var _joy_id:int = -2
func _begin_usage():
Input.joy_connection_changed.connect(_update_joy_id)
_update_joy_id(null, null)
func _end_usage():
Input.joy_connection_changed.disconnect(_update_joy_id)
func _update_joy_id(_ignore, _ignore2):
if joy_index < 0:
return
var joypads:Array[int] = Input.get_connected_joypads()
if joy_index < joypads.size():
_joy_id = joypads[joy_index]
else:
push_warning("Only ", joypads.size(), " joy pads/sticks connected. Cannot sample in put from index ", joy_index, ".")
_joy_id = -2

View File

@@ -0,0 +1,44 @@
@tool
class_name GUIDEInputJoyButton
extends GUIDEInputJoyBase
@export var button:JoyButton = JOY_BUTTON_A:
set(value):
if value == button:
return
button = value
emit_changed()
func _input(event:InputEvent):
if not event is InputEventJoypadButton:
return
if event.button_index != button:
return
if joy_index > -1 and event.device != _joy_id:
return
_value.x = 1.0 if event.pressed 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,127 @@
@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()
func _input(event:InputEvent):
if not event is InputEventKey:
return
# we start assuming the key is not pressed right now
_value.x = 0.0
# the key itself must be pressed
if not Input.is_physical_key_pressed(key):
return
# every required modifier must be pressed
if shift and not Input.is_physical_key_pressed(KEY_SHIFT):
return
if control and not Input.is_physical_key_pressed(KEY_CTRL):
return
if alt and not Input.is_physical_key_pressed(KEY_ALT):
return
if meta and not Input.is_physical_key_pressed(KEY_META):
return
# unless additional modifiers are allowed, every
# unselected modifier 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 and Input.is_physical_key_pressed(KEY_SHIFT):
return
if not control and key != KEY_CTRL and Input.is_physical_key_pressed(KEY_CTRL):
return
if not alt and key != KEY_ALT and Input.is_physical_key_pressed(KEY_ALT):
return
if not meta and key != KEY_META and Input.is_physical_key_pressed(KEY_META):
return
# we're still here, so all required keys are pressed and
# no extra keys are pressed
_value.x = 1.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,47 @@
@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 _input(event:InputEvent) -> void:
if event is InputEventMouseMotion:
match axis:
GUIDEInputMouseAxis.X:
_value.x = event.relative.x
GUIDEInputMouseAxis.Y:
_value.x = event.relative.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,35 @@
@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 _input(event:InputEvent) -> void:
if not event is InputEventMouseMotion:
return
_value.x = event.relative.x
_value.y = event.relative.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,65 @@
@tool
class_name GUIDEInputMouseButton
extends GUIDEInput
@export var button:MouseButton = MOUSE_BUTTON_LEFT:
set(value):
if value == button:
return
button = value
emit_changed()
func _needs_reset():
# 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
var _reset_to:Vector3
var _was_pressed_this_frame:bool
func _reset() -> void:
_was_pressed_this_frame = false
_value = _reset_to
func _input(event:InputEvent):
if not event is InputEventMouseButton:
return
if event.button_index != button:
return
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 event.pressed else 0.0
if event.pressed:
_was_pressed_this_frame = true
if not event.pressed and _was_pressed_this_frame:
# keep pressed state for this frame
return
_value.x = 1.0 if event.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,41 @@
@tool
class_name GUIDEInputMousePosition
extends GUIDEInput
func _begin_usage() -> void :
_update_mouse_position()
func _input(event:InputEvent) -> void:
if not event is InputEventMouseMotion:
return
_update_mouse_position()
func _update_mouse_position():
var position:Vector2 = Engine.get_main_loop().root.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,83 @@
## Input representing angle changes between two fingers.
@tool
class_name GUIDEInputTouchAngle
extends GUIDEInput
const GUIDETouchState = preload("guide_touch_state.gd")
## 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 _input(event:InputEvent) -> void:
if not GUIDETouchState.process_input_event(event):
# not touch-related
return
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
_value = Vector3(angle - _initial_angle, 0, 0)
func _calculate_angle() -> float:
var pos1:Vector2 = GUIDETouchState.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 = GUIDETouchState.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,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,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,46 @@
## Base class for axis-like touch input.
@tool
class_name GUIDEInputTouchAxisBase
extends GUIDEInputTouchBase
const GUIDETouchState = preload("guide_touch_state.gd")
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 = GUIDETouchState.get_finger_position(finger_index, finger_count)
_apply_value(_calculate_value(_last_position))
func _input(event:InputEvent) -> void:
if not GUIDETouchState.process_input_event(event):
# not touch-related
return
# calculate live position from the cache
var new_position:Vector2 = GUIDETouchState.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,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,72 @@
## Input representing the distance changes between two fingers.
@tool
class_name GUIDEInputTouchDistance
extends GUIDEInput
const GUIDETouchState = preload("guide_touch_state.gd")
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 _input(event:InputEvent) -> void:
if not GUIDETouchState.process_input_event(event):
# not touch-related
return
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 = GUIDETouchState.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 = GUIDETouchState.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,47 @@
@tool
class_name GUIDEInputTouchPosition
extends GUIDEInputTouchBase
const GUIDETouchState = preload("guide_touch_state.gd")
func _begin_usage():
_value = Vector3.INF
func _input(event:InputEvent) -> void:
# update touch state
if not GUIDETouchState.process_input_event(event):
# not touch-related
return
# update finger position
var position := GUIDETouchState.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,73 @@
@tool
## Shared information about current touch state. This simplifies implementation of the touch inputs
## and avoids having to process the same events multiple times.
# Cached finger positions
static var _finger_positions:Dictionary = {}
# Events processed this frame.
static var _processed_events:Dictionary = {}
# Last frame we were called
static var _last_frame:int = -1
## Processes an input event and updates touch state. Returns true, if the given event
## was touch-related.
static func process_input_event(event:InputEvent) -> bool:
if not event is InputEventScreenTouch and not event is InputEventScreenDrag:
return false
var this_frame = Engine.get_process_frames()
# if we are in a new frame, clear the processed events
if this_frame != _last_frame:
_last_frame = this_frame
_processed_events.clear()
# if the event already was processed, skip processing it again
if _processed_events.has(event):
return true
_processed_events[event] = true
var index:int = event.index
if event is InputEventScreenTouch:
if event.pressed:
_finger_positions[index] = event.position
else:
_finger_positions.erase(index)
if event is InputEventScreenDrag:
_finger_positions[index] = event.position
return true
## 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.
static 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.ZERO
for value in _finger_positions.values():
result += value
result /= float(finger_count)
return result