8.0 KiB
Action Completion and Callback System Design
Overview
This document details the completion and callback system that allows actions to notify the CutsceneManager when they have finished executing, and enables flexible callback mechanisms for complex action sequences.
Signal-Based Completion System
Core Signals
Each action implements these core signals:
# Emitted when the action starts executing
signal started()
# Emitted when the action completes successfully
signal completed()
# Emitted when the action fails
signal failed(error_message)
# Emitted when the action is manually stopped
signal stopped()
Signal Connection Flow
- CutsceneManager creates an action
- CutsceneManager connects to the action's signals
- When action starts, it emits
started() - When action completes, it emits
completed() - CutsceneManager's signal handlers update the cutscene state
# In CutsceneManager
func add_action(action: Action) -> void:
# Connect to action signals
action.connect("started", _on_action_started)
action.connect("completed", _on_action_completed)
action.connect("failed", _on_action_failed)
action.connect("stopped", _on_action_stopped)
# Add to queue
sequential_actions.append(action)
func _on_action_started(action: Action) -> void:
emit_signal("action_started", action)
func _on_action_completed(action: Action) -> void:
# Handle action completion
# Move to next action or complete cutscene
_handle_action_completion(action)
func _on_action_failed(action: Action, error_message: String) -> void:
# Handle action failure
print("Action failed: %s - %s" % [action.name, error_message])
# Stop the cutscene or handle error appropriately
stop()
func _on_action_stopped(action: Action) -> void:
# Handle action being stopped
_handle_action_completion(action)
Parallel Action Group Completion
For parallel actions, the system tracks completion of all actions in a group:
# Structure for tracking parallel action groups
{
"actions": [], # Array of actions in this group
"completed_count": 0, # Number of completed actions
"total_count": 0, # Total number of actions
"on_complete": Callable # Optional callback when group completes
}
func add_parallel_actions(actions: Array, on_complete: Callable = Callable()) -> void:
# Create a parallel action group
var group = {
"actions": actions,
"completed_count": 0,
"total_count": actions.size(),
"on_complete": on_complete
}
# Connect to each action's completed signal
for action in actions:
action.connect("completed", _on_parallel_action_completed.bind(group, action))
action.connect("failed", _on_parallel_action_failed.bind(group, action))
# Add to active parallel groups
active_parallel_groups.append(group)
# Add to sequential queue as a single item
sequential_actions.append(group)
func _on_parallel_action_completed(group: Dictionary, action: Action) -> void:
# Increment completed count
group["completed_count"] += 1
# Check if all actions in group are completed
if group["completed_count"] >= group["total_count"]:
# Remove from active groups
active_parallel_groups.erase(group)
# Call optional completion callback
if group["on_complete"].is_valid():
group["on_complete"].call()
# Continue with next sequential action
_execute_next_action()
Callback System
Action-Level Callbacks
Actions can have callbacks for specific events:
class_name Action
extends RefCounted
# Callback properties
var on_started: Callable = Callable()
var on_completed: Callable = Callable()
var on_failed: Callable = Callable()
func start() -> void:
._set_running()
# Call started callback if valid
if on_started.is_valid():
on_started.call(self)
func _set_completed() -> void:
state = State.COMPLETED
completed.emit()
# Call completed callback if valid
if on_completed.is_valid():
on_completed.call(self)
func _set_failed(error_message: String) -> void:
state = State.FAILED
failed.emit(error_message)
# Call failed callback if valid
if on_failed.is_valid():
on_failed.call(self, error_message)
Cutscene-Level Callbacks
The CutsceneManager supports callbacks for cutscene events:
class_name CutsceneManager
extends Node
# Cutscene callbacks
var on_started: Callable = Callable()
var on_completed: Callable = Callable()
var on_paused: Callable = Callable()
var on_resumed: Callable = Callable()
func start() -> void:
if state != State.IDLE:
return
state = State.RUNNING
is_active = true
emit_signal("cutscene_started")
# Call started callback
if on_started.is_valid():
on_started.call()
# Start first action
_execute_next_action()
func _on_cutscene_completed() -> void:
state = State.IDLE
is_active = false
emit_signal("cutscene_completed")
# Call completed callback
if on_completed.is_valid():
on_completed.call()
Chaining Actions with Callbacks
Actions can be chained together using callbacks:
# Example: Create a sequence using callbacks
func create_chained_sequence() -> void:
var action1 = MoveAction.new(character1, Vector2(100, 100))
var action2 = DialogueAction.new(character1, "I have arrived!")
var action3 = AnimationAction.new(character1, "celebrate")
# Chain actions using callbacks
action1.on_completed = func(action):
_execute_action(action2)
action2.on_completed = func(action):
_execute_action(action3)
# Start the sequence
_execute_action(action1)
Error Handling and Recovery
The system includes robust error handling:
# Action failure handling
func _on_action_failed(action: Action, error_message: String) -> void:
print("Action failed: %s - %s" % [action.name, error_message])
# Emit a general failure signal
emit_signal("action_failed", action, error_message)
# Decide how to handle the failure:
# 1. Stop the entire cutscene
# 2. Skip the failed action and continue
# 3. Retry the action
# 4. Execute an alternative action
# For now, we'll stop the cutscene
stop()
# Optional retry mechanism
func _retry_action(action: Action, max_retries: int = 3) -> void:
if not action.has_method("retry_count"):
action.retry_count = 0
action.retry_count += 1
if action.retry_count <= max_retries:
print("Retrying action: %s (attempt %d)" % [action.name, action.retry_count])
action.start()
else:
print("Max retries exceeded for action: %s" % action.name)
_on_action_failed(action, "Max retries exceeded")
Integration with Godot's Signal System
The completion system fully integrates with Godot's signal system:
- Uses Godot's built-in signal connection mechanisms
- Supports both method and callable-based connections
- Handles disconnection automatically when actions are removed
- Provides type safety through signal parameters
# Example of connecting to cutscene signals from outside
func setup_cutscene() -> void:
var cutscene = CutsceneManager.new()
# Connect to cutscene signals
cutscene.connect("cutscene_started", _on_cutscene_started)
cutscene.connect("cutscene_completed", _on_cutscene_completed)
cutscene.connect("action_started", _on_action_started)
cutscene.connect("action_completed", _on_action_completed)
# Configure callbacks
cutscene.on_completed = func():
print("Cutscene finished, returning to gameplay")
# Return to gameplay state
return cutscene
This completion and callback system provides a flexible, robust foundation for managing action completion in cutscenes while maintaining clean separation of concerns and enabling complex action sequences.