~/home/blogs/architecting-a-flexible-interaction-system-in-godot.md

Architecting a Flexible Interaction System in Godot

A technical design for a flexible, co-op ready object interaction system in Godot, covering architecture, component design, and implementation details.

- Raul G.
2023-02-20

1. The Challenge: Designing a Scalable Interaction System

For a co-op game currently in development, I needed to architect an object interaction system that was both flexible and scalable. The system had to meet several key requirements from the outset:

  • Versatility: Support a wide range of interactable objects, from simple doors and levers to complex machinery.
  • Multiple Interaction Types: Handle both instantaneous "tap" interactions (like a button press) and sustained "hold" interactions (like repairing an engine).
  • Context-Sensitivity: Allow objects to be interactable only by specific players, roles, or those holding certain items.
  • Co-op Ready: Gracefully handle scenarios where multiple players might attempt to interact with the same object simultaneously.
  • Extensibility: Be easy for designers to implement on new objects without needing to rewrite core logic.

This document details the architectural design for this system in the Godot engine.

Early prototype demonstrating the need for a robust interaction system.

2. Architectural Approach: A Decoupled, Event-Driven System

The core of the design is to keep the player and the interactable objects as decoupled as possible. The player should not need to know the specific details of any object it interacts with. Likewise, an object should not need a direct reference to the player. This is achieved using Godot's built-in signals and a standardized interface enforced via duck-typing.

The Player's Role

The player character is responsible for three things:

  1. Detection: Identifying potential interactable objects within its vicinity (e.g., using an Area3D node).
  2. Selection: Determining which object is the current target and notifying it that it is "selected."
  3. Input Broadcasting: Emitting signals when the player presses or releases the interact button.

The Interactable Object's Role

Any object that can be interacted with must conform to a specific interface by implementing a set of standard methods. In GDScript, we can check for this using has_method(). This approach avoids rigid class inheritance and allows any node to become interactable.

The required methods are:

  • can_be_selected_by(player_node): Returns true or false. This is the gatekeeper method. It allows an object to define its own interaction prerequisites. For example, a heavy door might check if player_node.role == "Warrior".
  • on_selected(player_node): Called by the player when this object becomes the active target. The object is responsible for its own highlighting or UI feedback.
  • on_deselected(player_node): Called when the player looks away or moves out of range.
  • on_interact_start(player_node): Triggered when the player presses the interact button.
  • on_interact_end(player_node): Triggered when the player releases the interact button.

3. The Interaction Flow

The entire process, from detection to interaction, follows a clear, event-driven sequence.

sequenceDiagram
    participant Player
    participant Interactable Object

    Player->>Interactable Object: Detects object in range
    Player->>Interactable Object: call can_be_selected_by(self)
    Interactable Object-->>Player: return true

    Player->>Player: Set object as current selection
    Player->>Interactable Object: call on_selected(self)
    Interactable Object->>Interactable Object: Enable highlight/UI

    Note over Player: Player presses 'Interact'
    Player->>Interactable Object: call on_interact_start(self)
    Interactable Object->>Interactable Object: Begin interaction logic (e.g., open door)

    Note over Player: Player releases 'Interact'
    Player->>Interactable Object: call on_interact_end(self)
    Interactable Object->>Interactable Object: Finalize interaction logic

    Note over Player: Player moves out of range
    Player->>Interactable Object: call on_deselected(self)
    Interactable Object->>Interactable Object: Disable highlight/UI

Handling Multi-Player Scenarios

To manage simultaneous interactions in a co-op environment, each interactable object maintains a list of players_currently_selecting.

  • When on_selected is called, the player is added to the list. The highlight is enabled only if the list is not empty.
  • When on_deselected is called, the player is removed. The highlight is disabled only when the list becomes empty.

This ensures that the object remains highlighted as long as at least one player is targeting it, preventing flickering UI and race conditions.

4. Conclusion

This architectural design provides a robust and designer-friendly foundation for all object interactions in the game. By leveraging Godot's core features like signals and embracing a decoupled, interface-driven approach, the system is:

  • Maintainable: Logic is cleanly separated between the player and the objects.
  • Scalable: New and unique interactable objects can be created rapidly without modifying any existing player code.
  • Co-op Ready: The system is inherently designed to handle multiple players from the start.

This approach demonstrates how thoughtful architecture can solve complex gameplay requirements in a clean and efficient manner, paving the way for smoother development as the project grows.

Share this post