so heads up this is 90% me just needing a place to think out loud, so If I’m kinda rambling that’s why.
so I’m going through and refactoring an old interaction system i had, just cleaning it up yknow? and one major thing I want to do is that I want an option for both a simple “Press X to Interact” and a “Hold X to interact” version. I figured that the best option is to just make the Hold version extend off of the Press version, since that just seems the most obvious, but that’s neither here nor there. The real thing is that, in order to handle the Hold version, I need to be able to detect both when the interact input is pressed and when it’s released. and that’s what I’m currently trying to figure out what the best option is.
my First idea was to have the InteractableComponent handle checking the input, and let the Interaction Agent just handle turning the interactable components “on or off” for lack of a better term. That works but there’s 2 problems with it.
I would need to store the name of the “interact” input as a static var/const in the InteractableComponent class, which is fine but kinda cumbersome since you would need to go in and manually edit the .gd file if you ever changed the name of the “interact input”
I don’t know how having a bunch of _unhandled_input() functions would affect performance.
if that doesn’t work, what might be some other ways that you can think of to handle the “Press vs Hold” question?
My approach is to have a bunch of different flavors of interactables–maybe in a hierarchy
Something that a character body (or interaction area) just needs to collide with–aka proximity
Something that requires proximity and player input
Something that requires proximity and player input held for a time
etc.
What player input (e.g. the name of an input action) is an export var in any given component. Usually the same thing, but not always… that’s why it’s an export var and not hardcoded.
# 2.0 == 2seg
@export var secs_to_hold: float = 2.0
var timer = Timer.new()
func _ready() -> void:
# add the timer to scene_tree
add_child(timer)
# connect a func called holded_sucess to the signal timeout from Timer object
timer.connect("timeout", holded_sucess)
func _input(event):
# if we start the action called "key x", we start a timer event
if event.is_action_pressed("key x"):
print("hold")
timer.start(secs_to_hold)
else:
# if we cancel the action called "key x", we stop the timer event
if event.is_action_released("key x"):
print("release")
timer.stop()
func holded_sucess():
# this is only visible after 2.0 secs have elapsed
print(secs_to_hold, "secs")
Keep input handling centralized rather than putting _unhandled_input() on every interactable. One interaction manager can listen for press/release and notify the currently active interactable. Use the InputMap for the action name, then Press and Hold can simply implement different behaviors. Cleaner, easier to maintain, and avoids unnecessary input handlers everywhere.
thank you for the replies, and after mulling it over I do have to agree with jason on keeping input handling centralized to a singular InteractionHandler as it were. it just feels cleaner and easier to use that way.
Plus its more similar to how my old interaction system worked, where I had an InteractionComponent, which is an Area3D that defined which objects where interactable, and then an InteractionAgent which handled both sending input and telling which object we’re actually interacting with. That’s also how I’m planning to handle the “Hold to Interact” version, by making it extend off of the base InteractionComponent. similar to what hatmix suggested.
My one question then is still “How do I handle input for both the Press version and the Hold version?” Since, as I said above: the Press version only cares about when the interact key is pressed, while the Hold version cares about when the interact key is both pressed and released.
One idea is that I could maybe do a has_method() check or an is check? basically the idea is that the base InteractableComponent has an on_interact() method while the HoldInteractableComponent has both an on_interact() method and an on_interact_released() method. with InteractionAgent doing a quick check in _unhandled_input() to see if our given interactable has that method or not.
The existence of a “hold” implies the existence of a “tap” (or press as Jason Miller said), so rather than worrying about pressed and released in the component, you could have your central input handler signal on holds and taps.
Now your interactables are connecting to the appropriate input component signal and you don’t have to worry about has_method or is checks.