/godot-signals

A powerful signal system for Godot Engine that provides efficient signal handling with support for transform operations, and high-performance signal processing.

Primary LanguageGDScriptMIT LicenseMIT

banner

ci Discord Shield

A powerful signal system for Godot Engine that provides efficient signal handling with support for filtering, mapping, centralized event management, and high-performance signal processing.

๐Ÿ›ฃ๏ธ Roadmap

Here are some planned features and improvements:

  • ๐Ÿ”„ Broker Binding: Possibility to bind to the broker and receive a GSignal instance
  • โšก Extended Operations: Add more operations to GSignal such as debounce, merge, and more
  • ๐Ÿ” Suggest a feature by opening an issue on GitHub!

โœจ Features

  • ๐Ÿ”„ Signal Processing: Create signal processing chains with filter and map operations
  • ๐Ÿ“ก Signal Broker: Connect signals across your game without direct object references
  • โšก Smart Connection Types: Automatically optimizes for high or low frequency signals
  • ๐Ÿ” Pattern Matching: Subscribe to signals using wildcard patterns
  • ๐Ÿท๏ธ Alias System: Identify objects by name, groups, or custom aliases
  • ๐Ÿš€ Optimized Performance: Reduced overhead and minimal allocations for efficient processing

๐Ÿš€ Quick Start

๐Ÿ”„ Signal Processing

Create powerful signal processing chains with just a few lines of code:

# Filter, transform, and react to signals
GSignals.from(damage_signal)
    .filter(func(amount: int) -> bool: return amount >= 10)
    .map(func(amount: int) -> int: return amount * critical_multiplier)
    .bind(func(final_damage: int): apply_damage(final_damage))

๐Ÿ“ก Signal Broker

Connect components without direct references:

# Broadcasting side: Register player node's signals with the broker
GBroker.broadcast_signals_of(player_node, "player")

# Listening side: Subscribe to player damage events anywhere in your code
GBroker.subscribe("player:damage_taken", func(amount): update_health_ui(amount))

๐Ÿ“ฅ Installation

  1. Download or clone this repository
  2. Copy the addons/godot-signals folder into your project's addons directory
  3. Enable the plugin in Project Settings > Plugins

๐Ÿ“– Tutorial

๐Ÿ”„ Signal Processing

๐Ÿ”Œ Basic Signal Connection

# Connect to a signal with a simple callback
GSignals.from(player.health_changed)
    .bind(func(new_health): update_health_bar(new_health))

๐Ÿ” Filtering Signals

# Only process signals where the value meets certain criteria
GSignals.from(enemy.attack)
    .filter(func(damage: int) -> bool: return damage > 5)
    .bind(func(damage): play_heavy_hit_sound())

๐Ÿ”„ Transforming Signals

# Transform signal data before processing
GSignals.from(position_changed)
    .map(func(pos: Vector2) -> float: return pos.distance_to(Vector2.ZERO))
    .bind(func(distance: float): set_volume(100 - distance))

โฑ๏ธ Delaying Signals

# Delay signal processing by a specific time
GSignals.from(damage_taken)
    .delay(0.5)  # Delay by 0.5 seconds
    .bind(func(amount): play_delayed_damage_effect(amount))

๐Ÿ›‘ Debouncing Signals

# Debounce rapid signal emissions
GSignals.from(mouse_moved)
    .debounce(0.1)  # Only process after 0.1s of inactivity
    .bind(func(position): update_hover_effect(position))

๐ŸŽ›๏ธ Connection Management

# Store and manage connections
var connection = GSignals.from(timer.timeout).bind(func(): spawn_enemy())

# Temporarily disable connection
connection.stop()

# Re-enable connection
connection.start()

๐Ÿ“ก Signal Broker

๐Ÿ“ฃ Broadcasting Signals

# Register an object's signals with the broker
GBroker.broadcast_signals_of(
    player,                   # The object whose signals will be broadcasted
    "player",                 # Alias for identifying the object (optional)
    GBroker.GBrokerBroadcastFlags.SCRIPT_ONLY  # Which signals to broadcast
)

๐Ÿท๏ธ Setting Multiple Aliases

# Register with multiple aliases for more flexible subscription patterns
GBroker.broadcast_signals_of(
    boss_entity,
    ["boss", "enemy", "entity"]
)

๐Ÿ‘‚ Subscribing to Signals

# Subscribe to specific signals
GBroker.subscribe("player:health_changed", func(new_health): update_health_ui(new_health))

# Use wildcard patterns to subscribe to multiple signals
GBroker.subscribe("player:*", func(emitter, signal_name, args): print("Player signal: ", signal_name))
GBroker.subscribe("*:damage_taken", func(damage): update_global_damage_counter(damage))

๐Ÿงน Cleanup

# Clear all subscriptions and signal handlers when switching scenes
func _exit_tree():
    GBroker.reset()

๐Ÿ”ฌ Advanced Broker Usage

๐Ÿ“Š Callback Arguments Handling

The broker intelligently handles callback arguments depending on how many parameters your callback function accepts:

# Just receive the signal arguments (most common)
GBroker.subscribe("player:health_changed", func(health_amount): update_ui(health_amount))

# Receive emitter object, signal name, and arguments
GBroker.subscribe("player:*", func(emitter, signal_name, args):
    print("Signal %s from %s with args %s" % [signal_name, emitter.name, args])
)

# Mixed patterns with different argument counts
GBroker.subscribe("enemy:hit", func(damage, hit_position):
    spawn_particle(hit_position)
    apply_damage(damage)
)

# Missing arguments are filled with null
GBroker.subscribe("*:*", func(emitter, signal_name, arg1, arg2, arg3):
    # arg2 and arg3 will be null if the signal emits less than 3 arguments
    print("%s emitted %s with up to 3 args: %s, %s, %s" % [
        emitter.name, signal_name, arg1, arg2, arg3
    ])
)
๐Ÿ“ Notes on argument handling:
  • If the callback has fewer parameters than the signal provides, extra signal arguments are ignored
  • If the callback has more parameters than the signal provides, extra callback parameters receive null
  • The first callback parameter can receive the emitter object if an extra parameter is available
  • The second parameter can receive the signal name if additional parameters are available
  • Subsequent parameters receive the signal arguments

๐Ÿ”ง Advanced Features

โšก Custom Signal Frequency Optimization

# Optimize for high-frequency signals (e.g., position updates)
GSignals.from(position_changed, GSignals.GSignalsBindFlags.HIGH_FREQUENCY_HINT)

๐Ÿค– Automatic Alias Detection

When no alias is provided, the broker automatically uses:

  • ๐Ÿ‘ฅ The node's groups (except internal groups)
  • ๐Ÿ“› The node's name
  • ๐Ÿ”ค Snake case version of the node's name
# For a node named "PlayerCharacter" in group "characters"
GBroker.broadcast_signals_of(player_node)
# This will automatically register with aliases:
# - "characters" (from group)
# - "PlayerCharacter" (from name)
# - "player_character" (snake case conversion)

โš™๏ธ Performance Considerations

  • ๐Ÿ”— Chain operations carefully as each adds processing overhead
  • ๐ŸŽ๏ธ Use the HIGH_FREQUENCY_HINT flag for signals that emit multiple times per frame
  • ๐Ÿง  The pattern matching system uses caching to optimize frequent signal matches
  • ๐Ÿ”Œ Direct signal connections with GSignals are more efficient than broker subscriptions when you have direct references

๐Ÿ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.