Pyxus / fray

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

Sequence matcher not matching on "236P" input sequence #89

Closed endorphins closed 4 months ago

endorphins commented 4 months ago

I'm trying to follow the documentation to make a 236P sequence for a character. However, I'm running into issues trying to account for which side the player is on. Basically I'm creating conditional inputs for forward, back, down forward, down back, up forward, and up back, then using those for sequences. The issue is that the sequence matcher doesn't seem to register my 236P sequence in this case.

My 236P sequence never fires, but my 26P sequence does. Additionally, if I remove the forward conditional input and use the right input instead (assuming my player is on the left), then 236P will register. However, that won't work once the character is on the right side.

I feel like I'm missing something obvious, but I'm having a hard time troubleshooting/debugging this.

Below is my example script:

extends Area2D

@onready var state_machine: FrayStateMachine = $FrayStateMachine
var sequence_tree := FraySequenceTree.new()
var sequence_matcher := FraySequenceMatcher.new()

func _ready():
    # Connect signals
    FrayInput.input_detected.connect(_on_FrayInput_input_detected)
    sequence_matcher.match_found.connect(_on_SequenceMatcher_match_found)

    # Bind over Godot's input system
    FrayInputMap.add_bind_action("left", "left")
    FrayInputMap.add_bind_action("right", "right")
    FrayInputMap.add_bind_action("up", "up")
    FrayInputMap.add_bind_action("down", "down")
    FrayInputMap.add_bind_action("kick", "kick")
    FrayInputMap.add_bind_action("punch", "punch")

    # Create conditional inputs so our inputs work correctly on each side
    FrayInputMap.add_composite_input("forward", FrayConditionalInput.builder()
        .add_component_simple("right")
        .add_component_simple("left")
        .use_condition(is_on_right)
        .build()
    )
    # Would ideally use our conditional 'forward' input, but causes "Invalid get index 'is_pressed' (on base: 'Nil')"
    FrayInputMap.add_composite_input("down_forward", FrayConditionalInput.builder()
        .add_component(FrayCombinationInput.builder()
            .add_component_simple("down")
            .add_component_simple("right")
            .mode_async()
            .build()
        )
        .add_component(FrayCombinationInput.builder()
            .add_component_simple("down")
            .add_component_simple("left")
            .mode_async()
            .build()
        ).use_condition(is_on_right)
        .is_virtual()
        .build()
    )

    # Add 236P sequence trees
    # This sequence never matches
    sequence_tree.add("236p", FraySequenceBranch.builder()
        .first("down")
        .then("down_forward")
        .then("forward")
        .then("punch")
        .build()
    )
    # For convenience
    # This sequence will match
    sequence_tree.add("236p", FraySequenceBranch.builder()
        .first("down")
        .then("forward")
        .then("punch")
        .build()
    )

    sequence_matcher.initialize(sequence_tree)

func _on_FrayInput_input_detected(input_event: FrayInputEvent) -> void:
    sequence_matcher.read(input_event)

func _on_SequenceMatcher_match_found(sequence_name: String) -> void:
    # Placeholder, just print out if we have a sequence for now
    print("Sequence matched '%s'" % [sequence_name])

func is_on_right(device: int) -> bool:
    # Placeholder, just assume we are P1 side for now
    return false
endorphins commented 4 months ago

So I've been able to resolve this. I think there were a few issues happening that made it hard to isolate them.

  1. I was handling left/right inputs, which would mess up the sequence. EDIT: Instead of explicitly ignoring left/right inputs, I found the is_distinct() getter, which also filters out the additional forward/back inputs during diagonals.
  2. I was handling all presses, not "just" presses, so there were a lot of extra inputs being handled (not 100% sure this was an issue, but I don't see a reason to handle all presses for now)
  3. I defined the forward input before the down_forward input, and didn't specify a priority. This caused the forward input to have more priority, and so forward always registered before down_forward did, ruining the sequence.

So the main updates were to the input detected callback, but also figuring out priority of composite inputs.

Marking this issue as resolved but, IMO, I imagine this kind of usage is pretty common and it would be really nice if the docs highlighted it.

Here's some updated code below for anybody wondering what the changes look like:

extends Area2D

# Arbitrary values that give some wiggle room for lower priorities just in case.
const ORTHOGONAL_DIRECTION_PRIORITY = 11
const DIAGONAL_DIRECTION_PRIORITY = ORTHOGONAL_DIRECTION_PRIORITY + 1

var sequence_tree := FraySequenceTree.new()
var sequence_matcher := FraySequenceMatcher.new()

func _ready():
    # Connect signals
    FrayInput.input_detected.connect(_on_FrayInput_input_detected)
    sequence_matcher.match_found.connect(_on_SequenceMatcher_match_found)

    # Bind over Godot's input system
    FrayInputMap.add_bind_action("left", "left")
    FrayInputMap.add_bind_action("right", "right")
    FrayInputMap.add_bind_action("up", "up")
    FrayInputMap.add_bind_action("down", "down")
    FrayInputMap.add_bind_action("kick", "kick")
    FrayInputMap.add_bind_action("punch", "punch")

    # Create orthogonal conditional inputs so our inputs work correctly on each side.
    # Make it reusable for the diagonal inputs as well.
    var forward_input = (FrayConditionalInput.builder()
        .priority(ORTHOGONAL_DIRECTION_PRIORITY)
        .add_component_simple("right")
        .add_component_simple("left")
        .use_condition(is_on_right)
        .build()
    )
    FrayInputMap.add_composite_input("forward", forward_input)
    # Create the diagonal combination inputs
    FrayInputMap.add_composite_input("down_forward", FrayCombinationInput.builder()
        .priority(DIAGONAL_DIRECTION_PRIORITY)
        .add_component_simple("down")
        .add_component(forward_input)
        .mode_async()
        .is_virtual()
        .build()
    )

    # Add 236P sequence trees
    sequence_tree.add("236p", FraySequenceBranch.builder()
        .first("down")
        .then("down_forward")
        .then("forward")
        .then("punch")
        .build()
    )
    # For convenience
    sequence_tree.add("236p", FraySequenceBranch.builder()
        .first("down")
        .then("forward")
        .then("punch")
        .build()
    )

    sequence_matcher.initialize(sequence_tree)

func _on_FrayInput_input_detected(input_event: FrayInputEvent) -> void:
    if input_event.is_just_pressed() && input_event.is_distinct():
        sequence_matcher.read(input_event)

func _on_SequenceMatcher_match_found(sequence_name: String) -> void:
    # Placeholder, just print out if we have a sequence for now
    print("Sequence matched '%s'" % [sequence_name])

func is_on_right(device: int) -> bool:
    # Placeholder, just assume we are P1 side for now
    return false