Pyxus / fray

Fray – Godot Combat Framework
https://fray.pyxus.dev
MIT License
263 stars 11 forks source link

Create example of how to implement contextual inputs. #23

Open Pyxus opened 1 year ago

Pyxus commented 1 year ago

As requested a example/tutorial on implementing contextual inputs should be created. An example of such input is the sprint/dodge button in souls games like Elden ring. When tapped the player enters a dodge state but if held the player enters a run state.

Remi123 commented 1 year ago

I figured out how to do this and let me tell you this : It fall exactly into some forgotten crack in the system. By that I mean that the solution need to bypass some of the tools of FrayCombatStateMachine.

Conceptually, we are trying to determine if the button was pressed and released, or is it held. For the threshold I arbitrarily chose 0.35s. If the player release the button before 0.35s, it trigger a release, otherwise we simply ignore this signal and let the state machine check if the button is held.

There isn't a is_held function in FrayInput, but it's easy to implement

## Returns [code]true[/code] if the [kbd]input[/kbd] is being held for [kdb]min_time_frame[/kdb] .
func is_held(input: StringName, min_time_frame: int = 500, device: int = DEVICE_KBM_JOY1) -> bool:
match _get_input_state(input, device):
  var input_state:
    return input_state.is_pressed && (Time.get_ticks_msec() - input_state.time_pressed) > min_time_frame
  null:
    return false

However, it was difficult to setup the combat state machine to interact correctly with the buffer_button. Basically you need to filter the input_detected. Here is my implementation :

FrayInput.input_detected.connect(func (_input_event : FrayInputEvent) :
  if _input_event.input in ["R1","A",'X']: # Nothing special on those input
    if(FrayInput.is_just_pressed(_input_event.input) or FrayInput.is_just_released(_input_event.input)):
      csm.buffer_button(_input_event.input, _input_event.is_pressed())
  elif _input_event.input == 'B' and not _input_event.is_pressed() and _input_event.get_time_held_sec() < 0.34:
    csm.buffer_button(_input_event.input, false) # only send a release event if released before 0.34s
  elif _input_event.input == 'B' and _input_event.is_just_pressed() :
    csm.buffer_button(_input_event.input, true) # Only send pressed event.
,CONNECT_DEFERRED
)

# and in the CombatStateMachine
#...
# Dash using action button
.transition_button('idle','dash',{
    input='B',prereqs=[_cond_on_floor],
    is_triggered_on_release=true,
})

This is just a prototype since I just was able to make it work. I'll replace the code to send the the dash with a custom input ( probably 'b_tapped' ) in order to allow to use 'B' in other cases.

I hope I'm clear.

What was difficult to learn is the FrayInput.input_detected signal also is called on echo. I thought that it was only when an input is detected, basically no echo event.