From a429a3e06baf7d4198676a3cc13e8c580dfdbd52 Mon Sep 17 00:00:00 2001 From: Bryce Date: Wed, 30 Jul 2025 21:52:27 -0700 Subject: [PATCH] initial --- .gitattributes | 2 + .gitignore | 3 + action_class_design.md | 116 ++++++++ action_completion_system.md | 275 +++++++++++++++++++ action_types_design.md | 313 ++++++++++++++++++++++ cutscene/Action.gd | 59 ++++ cutscene/CutsceneManager.gd | 228 ++++++++++++++++ cutscene/README.md | 135 ++++++++++ cutscene/actions/AnimationAction.gd | 55 ++++ cutscene/actions/DialogueAction.gd | 75 ++++++ cutscene/actions/MoveAction.gd | 54 ++++ cutscene/actions/TurnAction.gd | 69 +++++ cutscene/actions/WaitAction.gd | 25 ++ cutscene/examples/animated_character.tscn | 10 + cutscene/examples/sample_cutscene.gd | 80 ++++++ cutscene/examples/sample_cutscene.tscn | 18 ++ cutscene/tests/test_cutscene_system.gd | 95 +++++++ cutscene_manager_design.md | 170 ++++++++++++ cutscene_system_architecture.md | 194 ++++++++++++++ cutscene_system_design.md | 116 ++++++++ cutscene_system_summary.md | 148 ++++++++++ extensibility_guide.md | 300 +++++++++++++++++++++ icon.svg | 1 + icon.svg.import | 37 +++ implementation_roadmap.md | 229 ++++++++++++++++ main.tscn | 3 + project.godot | 16 ++ sample_cutscene_example.md | 242 +++++++++++++++++ scripts/cutscene/CutsceneManager.gd | 0 29 files changed, 3068 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 action_class_design.md create mode 100644 action_completion_system.md create mode 100644 action_types_design.md create mode 100644 cutscene/Action.gd create mode 100644 cutscene/CutsceneManager.gd create mode 100644 cutscene/README.md create mode 100644 cutscene/actions/AnimationAction.gd create mode 100644 cutscene/actions/DialogueAction.gd create mode 100644 cutscene/actions/MoveAction.gd create mode 100644 cutscene/actions/TurnAction.gd create mode 100644 cutscene/actions/WaitAction.gd create mode 100644 cutscene/examples/animated_character.tscn create mode 100644 cutscene/examples/sample_cutscene.gd create mode 100644 cutscene/examples/sample_cutscene.tscn create mode 100644 cutscene/tests/test_cutscene_system.gd create mode 100644 cutscene_manager_design.md create mode 100644 cutscene_system_architecture.md create mode 100644 cutscene_system_design.md create mode 100644 cutscene_system_summary.md create mode 100644 extensibility_guide.md create mode 100644 icon.svg create mode 100644 icon.svg.import create mode 100644 implementation_roadmap.md create mode 100644 main.tscn create mode 100644 project.godot create mode 100644 sample_cutscene_example.md create mode 100644 scripts/cutscene/CutsceneManager.gd diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8ad74f7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Normalize EOL for all files that Git considers text files. +* text=auto eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0af181c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# Godot 4+ specific ignores +.godot/ +/android/ diff --git a/action_class_design.md b/action_class_design.md new file mode 100644 index 0000000..63f2bc4 --- /dev/null +++ b/action_class_design.md @@ -0,0 +1,116 @@ +# Base Action Class Design + +## Overview +The `Action` class is the abstract base class for all cutscene actions. It defines the common interface and functionality that all actions must implement. + +## Class Structure + +```gdscript +class_name Action +extends RefCounted + +# Action states +enum State { + PENDING, # Action is waiting to be executed + RUNNING, # Action is currently executing + COMPLETED # Action has finished executing +} + +# Public properties +var state: int = State.PENDING +var name: String = "Action" + +# Signals +signal started() # Emitted when action starts +signal completed() # Emitted when action completes +signal failed(error) # Emitted if action fails + +# Virtual methods to be implemented by subclasses +func start() -> void: + # Start executing the action + # This method should be overridden by subclasses + pass + +func update(delta: float) -> void: + # Update the action (called every frame while running) + # This method can be overridden by subclasses + pass + +func is_completed() -> bool: + # Check if the action has completed + # This method should be overridden by subclasses + return state == State.COMPLETED + +func stop() -> void: + # Stop the action (if it's running) + # This method can be overridden by subclasses + pass + +# Helper methods +func _set_running() -> void: + # Set the action state to RUNNING and emit started signal + state = State.RUNNING + started.emit() + +func _set_completed() -> void: + # Set the action state to COMPLETED and emit completed signal + state = State.COMPLETED + completed.emit() + +func _set_failed(error_message: String) -> void: + # Set the action state to FAILED and emit failed signal + state = State.FAILED + failed.emit(error_message) +``` + +## Implementation Guidelines + +### Required Methods +All action subclasses must implement these methods: + +1. `start()` - Initialize and begin the action +2. `is_completed()` - Return true when the action is finished +3. `update(delta)` - Optional, for frame-based updates + +### State Management +Actions should properly manage their state: +- Start in `PENDING` state +- Move to `RUNNING` when `start()` is called +- Move to `COMPLETED` when finished +- Emit appropriate signals for state changes + +### Signal Usage +Actions should emit these signals: +- `started()` when the action begins +- `completed()` when the action finishes successfully +- `failed(error)` when the action encounters an error + +## Example Implementation + +```gdscript +# Example of a simple WaitAction implementation +class_name WaitAction +extends Action + +var duration: float +var elapsed_time: float = 0.0 + +func _init(wait_duration: float): + duration = wait_duration + name = "WaitAction" + +func start() -> void: + ._set_running() + elapsed_time = 0.0 + +func update(delta: float) -> void: + if state == State.RUNNING: + elapsed_time += delta + if elapsed_time >= duration: + ._set_completed() + +func is_completed() -> bool: + return state == State.COMPLETED +``` + +This design provides a solid foundation for all action types while maintaining flexibility for different implementation needs. \ No newline at end of file diff --git a/action_completion_system.md b/action_completion_system.md new file mode 100644 index 0000000..1905393 --- /dev/null +++ b/action_completion_system.md @@ -0,0 +1,275 @@ +# 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: + +```gdscript +# 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 +1. CutsceneManager creates an action +2. CutsceneManager connects to the action's signals +3. When action starts, it emits `started()` +4. When action completes, it emits `completed()` +5. CutsceneManager's signal handlers update the cutscene state + +```gdscript +# 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: + +```gdscript +# 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: + +```gdscript +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: + +```gdscript +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: + +```gdscript +# 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: + +```gdscript +# 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: + +1. Uses Godot's built-in signal connection mechanisms +2. Supports both method and callable-based connections +3. Handles disconnection automatically when actions are removed +4. Provides type safety through signal parameters + +```gdscript +# 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. \ No newline at end of file diff --git a/action_types_design.md b/action_types_design.md new file mode 100644 index 0000000..a14dc2e --- /dev/null +++ b/action_types_design.md @@ -0,0 +1,313 @@ +# Action Types Design + +## Overview +This document details the specific action types that will be implemented for the cutscene system. Each action type handles a specific kind of operation in a cutscene. + +## Core Action Types + +### 1. MoveAction +Moves a character to a specific position over time. + +```gdscript +class_name MoveAction +extends Action + +# Properties +var character: Node2D # The character to move +var target_position: Vector2 # Target position +var speed: float = 100.0 # Movement speed (pixels/second) +var threshold: float = 5.0 # Distance threshold for completion + +# Internal +var start_position: Vector2 +var distance: float +var traveled: float = 0.0 + +func _init(character_node: Node2D, target: Vector2, move_speed: float = 100.0): + character = character_node + target_position = target + speed = move_speed + name = "MoveAction" + +func start() -> void: + if character == null: + ._set_failed("Character is null") + return + + start_position = character.position + distance = start_position.distance_to(target_position) + traveled = 0.0 + ._set_running() + +func update(delta: float) -> void: + if state != State.RUNNING: + return + + if character == null: + ._set_failed("Character was destroyed during action") + return + + # Calculate movement for this frame + var frame_distance = speed * delta + traveled += frame_distance + + # If we've reached or overshot the target + if traveled >= distance: + character.position = target_position + ._set_completed() + return + + # Move character along the path + var direction = (target_position - start_position).normalized() + character.position = start_position + direction * traveled + +func is_completed() -> bool: + return state == State.COMPLETED +``` + +### 2. TurnAction +Makes a character turn to face a direction or another character. + +```gdscript +class_name TurnAction +extends Action + +# Properties +var character: Node2D # The character to turn +var target: Variant # Either a Vector2 position or another Node2D +var turn_speed: float = 2.0 # Rotation speed (radians/second) + +func _init(character_node: Node2D, turn_target: Variant, speed: float = 2.0): + character = character_node + target = turn_target + turn_speed = speed + name = "TurnAction" + +func start() -> void: + if character == null: + ._set_failed("Character is null") + return + ._set_running() + +func update(delta: float) -> void: + if state != State.RUNNING: + return + + if character == null: + ._set_failed("Character was destroyed during action") + return + + # Calculate target rotation + var target_position: Vector2 + if target is Vector2: + target_position = target + elif target is Node2D: + target_position = target.position + else: + ._set_failed("Invalid target type") + return + + var direction = target_position - character.position + var target_rotation = atan2(direction.y, direction.x) + + # Rotate toward target + var angle_diff = angle_difference(character.rotation, target_rotation) + + # If we're close enough, complete the action + if abs(angle_diff) < 0.01: + character.rotation = target_rotation + ._set_completed() + return + + # Rotate based on turn speed + var rotation_amount = sign(angle_diff) * turn_speed * delta + if abs(rotation_amount) > abs(angle_diff): + character.rotation = target_rotation + ._set_completed() + else: + character.rotation += rotation_amount + +func is_completed() -> bool: + return state == State.COMPLETED +``` + +### 3. DialogueAction +Displays dialogue text, potentially with character-specific formatting. + +```gdscript +class_name DialogueAction +extends Action + +# Properties +var character: Node2D # The speaking character (optional) +var text: String # Dialogue text +var duration: float = 0.0 # Duration to display (0 = manual advance) +var auto_advance: bool = true # Whether to auto-advance after duration + +func _init(speaking_character: Node2D, dialogue_text: String, display_duration: float = 0.0): + character = speaking_character + text = dialogue_text + duration = display_duration + name = "DialogueAction" + +func start() -> void: + # In a real implementation, this would interface with a dialogue system + # For now, we'll simulate by printing to console + print("%s: %s" % [character.name if character else "Narrator", text]) + ._set_running() + + # If duration is 0, this is a manual advance action + # In a real game, this would wait for player input + if duration <= 0: + # For demo purposes, we'll complete immediately + # In a real game, this would wait for input + ._set_completed() + else: + # Start a timer for automatic completion + var timer = Timer.new() + timer.wait_time = duration + timer.one_shot = true + timer.connect("timeout", _on_timer_timeout) + # In a real implementation, we'd add this to the scene tree + # add_child(timer) + timer.start() + +func _on_timer_timeout(): + ._set_completed() + +func is_completed() -> bool: + return state == State.COMPLETED +``` + +### 4. AnimationAction +Plays a specific animation on a character. + +```gdscript +class_name AnimationAction +extends Action + +# Properties +var character: Node2D # The character to animate +var animation_name: String # Name of the animation to play +var loop: bool = false # Whether to loop the animation + +func _init(character_node: Node2D, anim_name: String, should_loop: bool = false): + character = character_node + animation_name = anim_name + loop = should_loop + name = "AnimationAction" + +func start() -> void: + if character == null: + ._set_failed("Character is null") + return + + # In a real implementation, this would interface with an AnimationPlayer + # For now, we'll simulate by printing to console + print("Playing animation '%s' on %s" % [animation_name, character.name]) + + # Check if character has an AnimationPlayer + var anim_player = _get_animation_player() + if anim_player == null: + ._set_failed("Character has no AnimationPlayer") + return + + # Connect to animation finished signal for non-looping animations + if not loop: + if not anim_player.is_connected("animation_finished", _on_animation_finished): + anim_player.connect("animation_finished", _on_animation_finished) + + # Play the animation + anim_player.play(animation_name, loop) + ._set_running() + + # For looping animations, we complete immediately + # (they would be stopped by another action) + if loop: + ._set_completed() + +func _get_animation_player() -> AnimationPlayer: + # Try to find AnimationPlayer as a child + for child in character.get_children(): + if child is AnimationPlayer: + return child + + # Try to find AnimationPlayer in the scene tree + return character.get_node_or_null("AnimationPlayer") as AnimationPlayer + +func _on_animation_finished(anim_name: String): + if anim_name == animation_name: + ._set_completed() + +func is_completed() -> bool: + return state == State.COMPLETED +``` + +### 5. WaitAction +Pauses execution for a specified time. + +```gdscript +class_name WaitAction +extends Action + +# Properties +var duration: float # Time to wait in seconds +var elapsed_time: float = 0.0 + +func _init(wait_duration: float): + duration = wait_duration + name = "WaitAction" + +func start() -> void: + elapsed_time = 0.0 + ._set_running() + +func update(delta: float) -> void: + if state != State.RUNNING: + return + + elapsed_time += delta + if elapsed_time >= duration: + ._set_completed() + +func is_completed() -> bool: + return state == State.COMPLETED +``` + +## Extensibility + +To create new action types: + +1. Create a new class that extends `Action` +2. Implement the required methods: + - `start()` - Initialize and begin the action + - `update(delta)` - Update the action each frame (if needed) + - `is_completed()` - Return true when the action is finished +3. Optionally implement `stop()` for cleanup +4. Use appropriate signals to notify completion + +Example of a custom action: + +```gdscript +class_name CustomAction +extends Action + +# Custom properties for this action +var custom_parameter: String + +func _init(param: String): + custom_parameter = param + name = "CustomAction" + +func start() -> void: + # Perform the action + print("Executing custom action with parameter: %s" % custom_parameter) + + # For immediate actions, complete right away + ._set_completed() + +func is_completed() -> bool: + return state == State.COMPLETED +``` + +This design provides a solid set of core action types while maintaining the flexibility to add new types as needed. \ No newline at end of file diff --git a/cutscene/Action.gd b/cutscene/Action.gd new file mode 100644 index 0000000..5a4a44c --- /dev/null +++ b/cutscene/Action.gd @@ -0,0 +1,59 @@ +class_name Action +extends RefCounted + +# Action states +enum State { + PENDING, # Action is waiting to be executed + RUNNING, # Action is currently executing + COMPLETED, # Action has finished executing + FAILED # Action failed during execution +} + +# Public properties +var state: int = State.PENDING +var name: String = "Action" + +# Signals +signal started() +signal completed() +signal failed(error_message) +signal stopped() + +# Virtual methods to be implemented by subclasses +func start() -> void: + # Start executing the action + # This method should be overridden by subclasses + pass + +func update(delta: float) -> void: + # Update the action (called every frame while running) + # This method can be overridden by subclasses + pass + +func is_completed() -> bool: + # Check if the action has completed + # This method should be overridden by subclasses + return state == State.COMPLETED + +func stop() -> void: + # Stop the action (if it's running) + # This method can be overridden by subclasses + if state == State.RUNNING: + state = State.FAILED + stopped.emit() + +# Helper methods +func _set_running() -> void: + # Set the action state to RUNNING and emit started signal + state = State.RUNNING + started.emit() + +func _set_completed() -> void: + # Set the action state to COMPLETED and emit completed signal + state = State.COMPLETED + completed.emit() + +func _set_failed(error_message: String) -> void: + # Set the action state to FAILED and emit failed signal + state = State.FAILED + failed.emit(error_message) diff --git a/cutscene/CutsceneManager.gd b/cutscene/CutsceneManager.gd new file mode 100644 index 0000000..89e9fd1 --- /dev/null +++ b/cutscene/CutsceneManager.gd @@ -0,0 +1,228 @@ +class_name CutsceneManager +extends Node + +# Action queues +var sequential_actions: Array = [] # Actions that run one after another +var current_action: Action = null # The action currently being executed +var action_index: int = 0 # Index of the next sequential action + +# Parallel actions +var parallel_groups: Array = [] # Groups of actions running simultaneously +var active_parallel_groups: Array = [] # Currently running parallel action groups + +# State management +enum State { + IDLE, # Not running any cutscene + RUNNING, # Currently executing a cutscene + PAUSED # Cutscene is paused +} + +var state: int = State.IDLE +var is_active: bool = false + +# Signals +signal cutscene_started() +signal cutscene_completed() +signal cutscene_paused() +signal cutscene_resumed() +signal action_started(action) +signal action_completed(action) +signal action_failed(action, error_message) + +# Public methods +func start() -> void: + # Start executing the cutscene + if state != State.IDLE: + return + + state = State.RUNNING + is_active = true + emit_signal("cutscene_started") + + # Start first action + _execute_next_action() + +func pause() -> void: + # Pause the current cutscene + if state == State.RUNNING: + state = State.PAUSED + emit_signal("cutscene_paused") + +func resume() -> void: + # Resume a paused cutscene + if state == State.PAUSED: + state = State.RUNNING + emit_signal("cutscene_resumed") + +func stop() -> void: + # Stop the current cutscene and reset + if current_action != null: + current_action.stop() + current_action = null + + # Stop all active parallel groups + for group in active_parallel_groups: + for action in group["actions"]: + if action.state == action.State.RUNNING: + action.stop() + + _reset() + +func add_action(action: Action) -> void: + # Add a single action to the sequential queue + if state != State.IDLE: + print("Warning: Cannot add actions while cutscene is running") + return + + sequential_actions.append(action) + +func add_parallel_actions(actions: Array) -> void: + # Add a group of actions to run in parallel + if state != State.IDLE: + print("Warning: Cannot add actions while cutscene is running") + return + + if actions.size() == 0: + return + + # Create a parallel action group + var group = { + "actions": actions, + "completed_count": 0, + "total_count": actions.size() + } + + # Add to sequential actions as a single item + sequential_actions.append(group) + +# Internal methods +func _process(delta: float) -> void: + # Main update loop + if state != State.RUNNING: + return + + # Update current sequential action + if current_action != null and current_action.state == current_action.State.RUNNING: + current_action.update(delta) + + # Update active parallel groups + for group in active_parallel_groups: + for action in group["actions"]: + if action.state == action.State.RUNNING: + action.update(delta) + +func _execute_next_action() -> void: + # Start executing the next sequential action + if action_index >= sequential_actions.size(): + # No more actions, cutscene complete + _on_cutscene_completed() + return + + var action_item = sequential_actions[action_index] + action_index += 1 + + # Check if this is a parallel group or single action + if typeof(action_item) == TYPE_DICTIONARY and action_item.has("actions"): + # This is a parallel group + _execute_parallel_group(action_item) + else: + # This is a single action + _execute_single_action(action_item) + +func _execute_single_action(action: Action) -> void: + # Execute a single action + current_action = action + + # Connect to action signals + if not action.is_connected("completed", _on_action_completed): + action.connect("completed", _on_action_completed.bind(action)) + if not action.is_connected("failed", _on_action_failed): + action.connect("failed", _on_action_failed.bind(action)) + if not action.is_connected("started", _on_action_started): + action.connect("started", _on_action_started.bind(action)) + + # Start the action + emit_signal("action_started", action) + action.start() + +func _execute_parallel_group(group: Dictionary) -> void: + # Execute a group of parallel actions + var actions = group["actions"] + + # Reset completion count + group["completed_count"] = 0 + + # Add to active parallel groups + active_parallel_groups.append(group) + + # Connect to each action's signals + for action in actions: + if not action.is_connected("completed", _on_parallel_action_completed): + action.connect("completed", _on_parallel_action_completed.bind(group, action)) + if not action.is_connected("failed", _on_parallel_action_failed): + action.connect("failed", _on_parallel_action_failed.bind(group, action)) + if not action.is_connected("started", _on_action_started): + action.connect("started", _on_action_started.bind(action)) + + # Emit started signal and start action + emit_signal("action_started", action) + action.start() + +func _on_action_started(action: Action) -> void: + # Handle action started + emit_signal("action_started", action) + +func _on_action_completed(action: Action) -> void: + # Handle action completion + if action == current_action: + current_action = null + emit_signal("action_completed", action) + # Move to next action + _execute_next_action() + +func _on_action_failed(action: Action, error_message: String) -> void: + # Handle action failure + if action == current_action: + current_action = null + + emit_signal("action_failed", action, error_message) + print("Action failed: %s - %s" % [action.name, error_message]) + # Stop the cutscene + stop() + +func _on_parallel_action_completed(group: Dictionary, action: Action) -> void: + # Increment completed count + group["completed_count"] += 1 + + emit_signal("action_completed", action) + + # Check if all actions in group are completed + if group["completed_count"] >= group["total_count"]: + # Remove from active groups + active_parallel_groups.erase(group) + + # Continue with next sequential action + _execute_next_action() + +func _on_parallel_action_failed(group: Dictionary, action: Action, error_message: String) -> void: + # Handle parallel action failure + emit_signal("action_failed", action, error_message) + print("Parallel action failed: %s - %s" % [action.name, error_message]) + # Stop the cutscene + stop() + +func _on_cutscene_completed() -> void: + # Handle cutscene completion + state = State.IDLE + is_active = false + emit_signal("cutscene_completed") + +func _reset() -> void: + # Reset the manager to initial state + sequential_actions.clear() + parallel_groups.clear() + active_parallel_groups.clear() + current_action = null + action_index = 0 + state = State.IDLE + is_active = false diff --git a/cutscene/README.md b/cutscene/README.md new file mode 100644 index 0000000..e3ec8a5 --- /dev/null +++ b/cutscene/README.md @@ -0,0 +1,135 @@ +# Cutscene System for Godot 4.3 + +## Overview +This is a flexible cutscene system for 2D point-and-click adventure games in Godot 4.3. It supports both sequential and parallel action execution, with a focus on extensibility and dynamic behavior. + +## Features +- Sequential action execution +- Parallel action execution +- Extensible action system +- Signal-based completion system +- Error handling and recovery + +## System Components + +### Core Classes +- `Action`: Base class for all actions +- `CutsceneManager`: Orchestrates action execution + +### Action Types +- `MoveAction`: Move characters to specific positions +- `TurnAction`: Rotate characters to face targets +- `DialogueAction`: Display dialogue text +- `AnimationAction`: Play character animations +- `WaitAction`: Time-based delays + +## Usage + +### Basic Setup +```gdscript +# Create a cutscene manager +var cutscene_manager = CutsceneManager.new() +add_child(cutscene_manager) + +# Add sequential actions +cutscene_manager.add_action(MoveAction.new(character, Vector2(100, 100))) +cutscene_manager.add_action(DialogueAction.new(character, "Hello, world!")) + +# Start the cutscene +cutscene_manager.start() +``` + +### Parallel Actions +```gdscript +# Create parallel actions +var parallel_actions = [ + MoveAction.new(character1, Vector2(200, 200)), + MoveAction.new(character2, Vector2(300, 300)) +] +cutscene_manager.add_parallel_actions(parallel_actions) +``` + +### The Example Scenario +The system supports complex sequences like the one described in the requirements: + +```gdscript +# 1. Character1 moves to 234, 591 +# 2. At the same time Character2 moves to 912, 235 +# 3. When both arrive, character2 turns to character1 +# 4. Character2 says a dialogue line to character1 +# 5. A special animation (shocked) is played for character1 + +var cutscene = CutsceneManager.new() +add_child(cutscene) + +# Add parallel movements +var moves = [ + MoveAction.new(character1, Vector2(234, 591)), + MoveAction.new(character2, Vector2(912, 235)) +] +cutscene.add_parallel_actions(moves) + +// Add sequential actions +cutscene.add_action(TurnAction.new(character2, character1)) +cutscene.add_action(DialogueAction.new(character2, "Hello there!")) +cutscene.add_action(AnimationAction.new(character1, "shocked")) + +cutscene.start() +``` + +## Extending the System + +### Creating New Action Types +1. Create a new class that extends `Action` +2. Implement the required methods: + - `start()`: Initialize and begin the action + - `update(delta)`: Update the action each frame (optional) + - `is_completed()`: Return true when the action is finished +3. Use the action in cutscenes like any other + +```gdscript +class_name CustomAction +extends "res://cutscene/Action.gd" + +func _init(custom_parameter): + # Initialize custom properties + pass + +func start() -> void: + # Start the action + ._set_running() + +func update(delta: float) -> void: + # Update the action each frame + pass + +func is_completed() -> bool: + # Return true when the action is completed + return state == State.COMPLETED +``` + +## File Structure +``` +/cutscene/ + ├── Action.gd + ├── CutsceneManager.gd + ├── actions/ + │ ├── MoveAction.gd + │ ├── TurnAction.gd + │ ├── DialogueAction.gd + │ ├── AnimationAction.gd + │ └── WaitAction.gd + ├── examples/ + │ ├── sample_cutscene.tscn + │ ├── sample_cutscene.gd + │ ├── animated_character.tscn + │ └── animated_character.gd + └── tests/ + └── test_cutscene_system.gd +``` + +## Testing +Run the test script at `cutscene/tests/test_cutscene_system.gd` to verify the system is working correctly. + +## License +This cutscene system is provided as-is for educational and prototyping purposes. \ No newline at end of file diff --git a/cutscene/actions/AnimationAction.gd b/cutscene/actions/AnimationAction.gd new file mode 100644 index 0000000..209e5a4 --- /dev/null +++ b/cutscene/actions/AnimationAction.gd @@ -0,0 +1,55 @@ +class_name AnimationAction +extends "res://cutscene/Action.gd" + +# Properties +var character: Node2D # The character to animate +var animation_name: String # Name of the animation to play +var loop: bool = false # Whether to loop the animation + +func _init(character_node: Node2D, anim_name: String, should_loop: bool = false) -> void: + character = character_node + animation_name = anim_name + loop = should_loop + name = "AnimationAction" + +func start() -> void: + if character == null: + self._set_failed("Character is null") + return + + # Check if character has an AnimationPlayer + var anim_player = _get_animation_player() + if anim_player == null: + self._set_failed("Character has no AnimationPlayer") + return + + # Connect to animation finished signal for non-looping animations + if not loop: + if not anim_player.is_connected("animation_finished", _on_animation_finished): + anim_player.connect("animation_finished", _on_animation_finished.bind(anim_player)) + + # Play the animation + anim_player.play(animation_name) + self._set_running() + + # For looping animations, we complete immediately + # (they would be stopped by another action) + if loop: + self._set_completed() + +func _get_animation_player() -> AnimationPlayer: + # Try to find AnimationPlayer as a child + for child in character.get_children(): + if child is AnimationPlayer: + return child + + # Try to find AnimationPlayer in the scene tree + return character.get_node_or_null("AnimationPlayer") as AnimationPlayer + +func _on_animation_finished(anim_name: String, anim_player: AnimationPlayer) -> void: + # Make sure this is for our animation + if anim_name == animation_name and not loop: + self._set_completed() + +func is_completed() -> bool: + return state == State.COMPLETED diff --git a/cutscene/actions/DialogueAction.gd b/cutscene/actions/DialogueAction.gd new file mode 100644 index 0000000..feded02 --- /dev/null +++ b/cutscene/actions/DialogueAction.gd @@ -0,0 +1,75 @@ +class_name DialogueAction +extends "res://cutscene/Action.gd" + +# Properties +var character: Node2D # The speaking character (optional) +var text: String # Dialogue text +var duration: float = 0.0 # Duration to display (0 = manual advance) + +func _init(speaking_character: Node2D, dialogue_text: String, display_duration: float = 0.0) -> void: + character = speaking_character + text = dialogue_text + duration = display_duration + name = "DialogueAction" + +func start() -> void: + # Display the dialogue text + _display_dialogue() + self._set_running() + + # If duration is 0, this is a manual advance action + if duration <= 0: + # For demo purposes, we'll complete immediately + # In a real game, this would wait for player input + self._set_completed() + else: + # Start a timer for automatic completion + var timer = Timer.new() + timer.wait_time = duration + timer.one_shot = true + timer.connect("timeout", _on_timer_timeout) + + # Add timer as a child to ensure it processes + if Engine.get_main_loop().current_scene: + Engine.get_main_loop().current_scene.add_child(timer) + #else: + ## Fallback if we can't access the main scene + #var root = get_tree().root if get_tree() else null + #if root and root.get_child_count() > 0: + #root.get_child(0).add_child(timer) + #else: + ## Last resort - connect directly and manage manually + #timer.connect("timeout", _on_timer_timeout_direct) + #timer.start(duration) + #return + + timer.connect("timeout", _cleanup_timer.bind(timer)) + timer.start() + +func _display_dialogue() -> void: + # In a real implementation, this would interface with a dialogue system + # For now, we'll simulate by printing to console + var character_name = character.name if character and character.name else "Unknown" + print("%s: %s" % [character_name, text]) + +func _on_timer_timeout() -> void: + self._set_completed() + +func _on_timer_timeout_direct() -> void: + # Direct timeout handler for when we can't add the timer to the scene tree + self._set_completed() + +func _cleanup_timer(timer: Timer) -> void: + # Clean up the timer + if timer and timer.get_parent(): + timer.get_parent().remove_child(timer) + if timer: + timer.queue_free() + self._set_completed() + +func is_completed() -> bool: + return state == State.COMPLETED + +func stop() -> void: + # Clean up any timers when stopping + super.stop() diff --git a/cutscene/actions/MoveAction.gd b/cutscene/actions/MoveAction.gd new file mode 100644 index 0000000..b3873b9 --- /dev/null +++ b/cutscene/actions/MoveAction.gd @@ -0,0 +1,54 @@ +class_name MoveAction +extends "res://cutscene/Action.gd" + +# Properties +var character: Node2D # The character to move +var target_position: Vector2 # Target position +var speed: float = 100.0 # Movement speed (pixels/second) +var threshold: float = 5.0 # Distance threshold for completion + +# Internal +var start_position: Vector2 +var distance: float +var traveled: float = 0.0 + +func _init(character_node: Node2D, target: Vector2, move_speed: float = 100.0) -> void: + character = character_node + target_position = target + speed = move_speed + name = "MoveAction" + +func start() -> void: + if character == null: + self._set_failed("Character is null") + return + + start_position = character.position + distance = start_position.distance_to(target_position) + traveled = 0.0 + self._set_running() + +func update(delta: float) -> void: + if state != State.RUNNING: + return + + if character == null: + self._set_failed("Character was destroyed during action") + return + + # Calculate movement for this frame + var frame_distance = speed * delta + traveled += frame_distance + + # If we've reached or overshot the target + if traveled >= distance: + character.position = target_position + self._set_completed() + return + + # Move character along the path + var direction = (target_position - start_position).normalized() + character.position = start_position + direction * traveled + +func is_completed() -> bool: + return state == State.COMPLETED diff --git a/cutscene/actions/TurnAction.gd b/cutscene/actions/TurnAction.gd new file mode 100644 index 0000000..b11bf20 --- /dev/null +++ b/cutscene/actions/TurnAction.gd @@ -0,0 +1,69 @@ +class_name TurnAction +extends "res://cutscene/Action.gd" + +# Properties +var character: Node2D # The character to turn +var target: Variant # Either a Vector2 position or another Node2D +var turn_speed: float = 2.0 # Rotation speed (radians/second) + +func _init(character_node: Node2D, turn_target: Variant, speed: float = 2.0) -> void: + character = character_node + target = turn_target + turn_speed = speed + name = "TurnAction" + +func start() -> void: + if character == null: + self._set_failed("Character is null") + return + self._set_running() + +func update(delta: float) -> void: + if state != State.RUNNING: + return + + if character == null: + self._set_failed("Character was destroyed during action") + return + + # Calculate target rotation + var target_position: Vector2 + if target is Vector2: + target_position = target + elif target is Node2D: + target_position = target.position + else: + self._set_failed("Invalid target type") + return + + var direction = target_position - character.position + var target_rotation = atan2(direction.y, direction.x) + + # Rotate toward target + var angle_diff = _angle_difference(character.rotation, target_rotation) + + # If we're close enough, complete the action + if abs(angle_diff) < 0.01: + character.rotation = target_rotation + self._set_completed() + return + + # Rotate based on turn speed + var rotation_amount = sign(angle_diff) * turn_speed * delta + if abs(rotation_amount) > abs(angle_diff): + character.rotation = target_rotation + self._set_completed() + else: + character.rotation += rotation_amount + +func is_completed() -> bool: + return state == State.COMPLETED + +# Helper function to calculate angle difference +func _angle_difference(from: float, to: float) -> float: + var diff = fmod(to - from, 2.0 * PI) + if diff < -PI: + diff += 2.0 * PI + elif diff > PI: + diff -= 2.0 * PI + return diff diff --git a/cutscene/actions/WaitAction.gd b/cutscene/actions/WaitAction.gd new file mode 100644 index 0000000..053972a --- /dev/null +++ b/cutscene/actions/WaitAction.gd @@ -0,0 +1,25 @@ +class_name WaitAction +extends "res://cutscene/Action.gd" + +# Properties +var duration: float # Time to wait in seconds +var elapsed_time: float = 0.0 + +func _init(wait_duration: float) -> void: + duration = wait_duration + name = "WaitAction" + +func start() -> void: + elapsed_time = 0.0 + self._set_running() + +func update(delta: float) -> void: + if state != State.RUNNING: + return + + elapsed_time += delta + if elapsed_time >= duration: + self._set_completed() + +func is_completed() -> bool: + return state == State.COMPLETED diff --git a/cutscene/examples/animated_character.tscn b/cutscene/examples/animated_character.tscn new file mode 100644 index 0000000..7d0f4f1 --- /dev/null +++ b/cutscene/examples/animated_character.tscn @@ -0,0 +1,10 @@ +[gd_scene format=3 uid="uid://animatedcharacter"] + +[ext_resource type="Script" path="res://cutscene/examples/animated_character.gd" id="1"] + +[node name="AnimatedCharacter" type="Node2D"] +script = ExtResource( "1" ) + +[node name="Sprite2D" type="Sprite2D" parent="."] + +[node name="AnimationPlayer" type="AnimationPlayer" parent="."] \ No newline at end of file diff --git a/cutscene/examples/sample_cutscene.gd b/cutscene/examples/sample_cutscene.gd new file mode 100644 index 0000000..154bcd1 --- /dev/null +++ b/cutscene/examples/sample_cutscene.gd @@ -0,0 +1,80 @@ +extends Node2D + +# Character nodes +@onready var character1: Node2D = $Character1 +@onready var character2: Node2D = $Character2 + +# Cutscene manager +var cutscene_manager: CutsceneManager + +func _ready() -> void: + # Initialize the cutscene system + setup_cutscene() + + # Start the cutscene after a short delay to see the initial positions + var start_timer = Timer.new() + start_timer.wait_time = 1.0 + start_timer.one_shot = true + start_timer.connect("timeout", start_cutscene) + add_child(start_timer) + start_timer.start() + +func setup_cutscene() -> void: + # Create the cutscene manager + cutscene_manager = CutsceneManager.new() + add_child(cutscene_manager) + + # Connect to cutscene signals + cutscene_manager.connect("cutscene_started", _on_cutscene_started) + cutscene_manager.connect("cutscene_completed", _on_cutscene_completed) + cutscene_manager.connect("action_started", _on_action_started) + cutscene_manager.connect("action_completed", _on_action_completed) + + # Create the action sequence as described in requirements + + # 1. & 2. Characters move simultaneously + var parallel_moves = [ + MoveAction.new(character1, Vector2(234, 591), 100.0), # Character1 moves + MoveAction.new(character2, Vector2(912, 235), 100.0) # Character2 moves + ] + cutscene_manager.add_parallel_actions(parallel_moves) + + # 3. Character2 turns to face Character1 + var turn_action = TurnAction.new(character2, character1, 1.0) + cutscene_manager.add_action(turn_action) + + # 4. Character2 says dialogue + var dialogue_action = DialogueAction.new(character2, "Hello there, friend!", 2.0) + cutscene_manager.add_action(dialogue_action) + + # 5. Character1 plays shocked animation (simulated with a wait since we don't have an actual animation) + var animation_action = WaitAction.new(1.0) # Simulate animation with wait + cutscene_manager.add_action(animation_action) + + # Add a final dialogue from character1 + var final_dialogue = DialogueAction.new(character1, "That was surprising!", 2.0) + cutscene_manager.add_action(final_dialogue) + +func start_cutscene() -> void: + print("Starting cutscene...") + cutscene_manager.start() + +func _on_cutscene_started() -> void: + print("Cutscene started!") + +func _on_cutscene_completed() -> void: + print("Cutscene completed!") + print("Final positions:") + print("Character1: %s" % character1.position) + print("Character2: %s" % character2.position) + +func _on_action_started(action: Action) -> void: + print("Action started: %s" % action.name) + +func _on_action_completed(action: Action) -> void: + print("Action completed: %s" % action.name) + +# Clean up when the node is removed +func _exit_tree() -> void: + if cutscene_manager: + cutscene_manager.queue_free() diff --git a/cutscene/examples/sample_cutscene.tscn b/cutscene/examples/sample_cutscene.tscn new file mode 100644 index 0000000..db72c34 --- /dev/null +++ b/cutscene/examples/sample_cutscene.tscn @@ -0,0 +1,18 @@ +[gd_scene load_steps=2 format=3 uid="uid://bx826fm1kd3wk"] + +[ext_resource type="Script" path="res://cutscene/examples/sample_cutscene.gd" id="1"] + +[node name="SampleCutscene" type="Node2D"] +script = ExtResource("1") + +[node name="Character1" type="Node2D" parent="."] +position = Vector2(100, 100) + +[node name="Polygon2D" type="Polygon2D" parent="Character1"] +polygon = PackedVector2Array(-5, -19, -27, 2, 2, 15, 15, 6, 9, -19) + +[node name="Character2" type="Node2D" parent="."] +position = Vector2(200, 100) + +[node name="Polygon2D" type="Polygon2D" parent="Character2"] +polygon = PackedVector2Array(10, -25, -43, 1, -14, 40, 48, 13) diff --git a/cutscene/tests/test_cutscene_system.gd b/cutscene/tests/test_cutscene_system.gd new file mode 100644 index 0000000..b504ec9 --- /dev/null +++ b/cutscene/tests/test_cutscene_system.gd @@ -0,0 +1,95 @@ +extends Node2D + +# Test the cutscene system +func _ready() -> void: + print("Testing cutscene system...") + + # Create test characters + var char1 = Node2D.new() + char1.name = "TestCharacter1" + add_child(char1) + + var char2 = Node2D.new() + char2.name = "TestCharacter2" + add_child(char2) + + # Create animation player for char1 + var anim_player = AnimationPlayer.new() + anim_player.name = "AnimationPlayer" + char1.add_child(anim_player) + + # Create a simple animation + var animation = Animation.new() + animation.name = "test_animation" + animation.length = 1.0 + anim_player.add_animation("test_animation", animation) + + # Test the cutscene system + test_sequential_actions(char1, char2) + + # After a delay, test parallel actions + var timer = Timer.new() + timer.wait_time = 3.0 + timer.one_shot = true + timer.connect("timeout", test_parallel_actions.bind(char1, char2)) + add_child(timer) + timer.start() + +func test_sequential_actions(char1: Node2D, char2: Node2D) -> void: + print("\n=== Testing Sequential Actions ===") + + # Create cutscene manager + var manager = CutsceneManager.new() + add_child(manager) + + # Connect signals + manager.connect("cutscene_completed", _on_test_completed.bind("sequential")) + + # Add sequential actions + manager.add_action(MoveAction.new(char1, Vector2(100, 100), 50.0)) + manager.add_action(WaitAction.new(0.5)) + manager.add_action(TurnAction.new(char1, Vector2(200, 200), 1.0)) + manager.add_action(DialogueAction.new(char1, "Hello, world!", 1.0)) + manager.add_action(AnimationAction.new(char1, "test_animation", false)) + + # Start the cutscene + manager.start() + +func test_parallel_actions(char1: Node2D, char2: Node2D) -> void: + print("\n=== Testing Parallel Actions ===") + + # Create cutscene manager + var manager = CutsceneManager.new() + add_child(manager) + + # Connect signals + manager.connect("cutscene_completed", _on_test_completed.bind("parallel")) + + # Add parallel actions + var parallel_group = [ + MoveAction.new(char1, Vector2(300, 300), 100.0), + MoveAction.new(char2, Vector2(400, 400), 100.0) + ] + manager.add_parallel_actions(parallel_group) + + # Add sequential actions after parallel + manager.add_action(TurnAction.new(char1, char2, 2.0)) + manager.add_action(DialogueAction.new(char1, "We moved together!", 1.5)) + + # Start the cutscene + manager.start() + +func _on_test_completed(test_type: String) -> void: + print("Test %s completed successfully!" % test_type) + + # Clean up after a delay + var cleanup_timer = Timer.new() + cleanup_timer.wait_time = 1.0 + cleanup_timer.one_shot = true + cleanup_timer.connect("timeout", clean_up) + add_child(cleanup_timer) + cleanup_timer.start() + +func clean_up() -> void: + print("\n=== All tests completed ===") + print("Cutscene system is working correctly!") \ No newline at end of file diff --git a/cutscene_manager_design.md b/cutscene_manager_design.md new file mode 100644 index 0000000..9d0e413 --- /dev/null +++ b/cutscene_manager_design.md @@ -0,0 +1,170 @@ +# CutsceneManager Design + +## Overview +The `CutsceneManager` is the central orchestrator for executing cutscenes. It manages both sequential and parallel actions, tracks their completion, and controls the flow of the cutscene. + +## Class Structure + +```gdscript +class_name CutsceneManager +extends Node + +# Action queues +var sequential_actions: Array # Actions that run one after another +var current_action: Action # The action currently being executed +var action_index: int = 0 # Index of the next sequential action + +# Parallel actions +var parallel_actions: Array # Groups of actions running simultaneously +var active_parallel_groups: Array # Currently running parallel action groups + +# State management +enum State { + IDLE, # Not running any cutscene + RUNNING, # Currently executing a cutscene + PAUSED # Cutscene is paused +} + +var state: int = State.IDLE +var is_active: bool = false + +# Signals +signal cutscene_started() +signal cutscene_completed() +signal cutscene_paused() +signal cutscene_resumed() +signal action_started(action) +signal action_completed(action) + +# Public methods +func start() -> void: + # Start executing the cutscene + pass + +func pause() -> void: + # Pause the current cutscene + pass + +func resume() -> void: + # Resume a paused cutscene + pass + +func stop() -> void: + # Stop the current cutscene and reset + pass + +func add_action(action: Action) -> void: + # Add a single action to the sequential queue + pass + +func add_parallel_actions(actions: Array) -> void: + # Add a group of actions to run in parallel + pass + +# Internal methods +func _process(delta: float) -> void: + # Main update loop + pass + +func _execute_next_action() -> void: + # Start executing the next sequential action + pass + +func _check_parallel_groups() -> void: + # Check if any parallel action groups have completed + pass + +func _on_action_completed(action: Action) -> void: + # Handle action completion + pass + +func _reset() -> void: + # Reset the manager to initial state + pass +``` + +## Sequential Execution System + +The sequential execution system runs actions one after another: + +1. Actions are added to the `sequential_actions` queue +2. When the cutscene starts, the first action is executed +3. When an action completes, the next one in the queue is started +4. The cutscene completes when all sequential actions are finished + +```gdscript +# Example flow: +# 1. Add actions: [MoveAction, DialogueAction, AnimationAction] +# 2. Start cutscene +# 3. MoveAction starts and runs +# 4. When MoveAction completes, DialogueAction starts +# 5. When DialogueAction completes, AnimationAction starts +# 6. When AnimationAction completes, cutscene ends +``` + +## Parallel Execution System + +The parallel execution system runs multiple actions simultaneously: + +1. Groups of actions are added as parallel groups +2. All actions in a group start at the same time +3. The manager waits for all actions in the group to complete +4. When all actions in a group complete, the next sequential action can proceed + +```gdscript +# Example flow: +# 1. Add sequential action: MoveAction (character1) +# 2. Add parallel group: [MoveAction (character2), MoveAction (character3)] +# 3. Add sequential action: DialogueAction +# +# Execution: +# 1. MoveAction (character1) runs and completes +# 2. Both MoveAction (character2) and MoveAction (character3) start simultaneously +# 3. When both complete, DialogueAction starts +# 4. When DialogueAction completes, cutscene ends +``` + +## Action Group Management + +Parallel actions are managed in groups: + +```gdscript +# Structure for parallel action groups: +{ + "actions": [Action, Action, ...], # The actions in this group + "completed_count": 0, # How many actions have completed + "total_count": 0 # Total number of actions in group +} +``` + +## State Transitions + +The CutsceneManager has these states: +- `IDLE`: Not running any cutscene +- `RUNNING`: Executing a cutscene +- `PAUSED`: Cutscene is temporarily paused + +State transitions: +- IDLE → RUNNING: When `start()` is called +- RUNNING → PAUSED: When `pause()` is called +- PAUSED → RUNNING: When `resume()` is called +- RUNNING → IDLE: When cutscene completes or `stop()` is called +- PAUSED → IDLE: When `stop()` is called + +## Error Handling + +The manager should handle these error cases: +- Attempting to start a cutscene that's already running +- Adding actions while a cutscene is running +- Actions that fail during execution +- Empty action queues + +## Integration with Godot Engine + +The CutsceneManager should: +- Inherit from `Node` to be added to the scene tree +- Use `_process()` for frame-based updates +- Connect to action signals for completion notifications +- Be easily instantiated and configured in the editor or via code + +This design provides a robust foundation for managing complex cutscene sequences with both sequential and parallel execution patterns. \ No newline at end of file diff --git a/cutscene_system_architecture.md b/cutscene_system_architecture.md new file mode 100644 index 0000000..1b8c87c --- /dev/null +++ b/cutscene_system_architecture.md @@ -0,0 +1,194 @@ +# Cutscene System Architecture + +## Overview +This document provides a visual representation of the cutscene system architecture using Mermaid diagrams. + +## System Components + +```mermaid +graph TD + A[CutsceneManager] --> B[Sequential Actions Queue] + A --> C[Parallel Actions Groups] + A --> D[State Management] + A --> E[Signal System] + + B --> F[Action 1] + B --> G[Action 2] + B --> H[Action 3] + + C --> I[Parallel Group 1] + C --> J[Parallel Group 2] + + I --> K[Action A] + I --> L[Action B] + + J --> M[Action C] + J --> N[Action D] + J --> O[Action E] + + F --> P[Action Base Class] + G --> P + H --> P + K --> P + L --> P + M --> P + N --> P + O --> P + + P --> Q[Start Method] + P --> R[Update Method] + P --> S[Is Completed Method] + P --> T[Stop Method] +``` + +## Action State Flow + +```mermaid +stateDiagram-v2 + [*] --> PENDING + PENDING --> RUNNING: start() + RUNNING --> COMPLETED: _set_completed() + RUNNING --> FAILED: _set_failed() + RUNNING --> STOPPED: stop() + FAILED --> [*] + STOPPED --> [*] + COMPLETED --> [*] +``` + +## CutsceneManager State Flow + +```mermaid +stateDiagram-v2 + [*] --> IDLE + IDLE --> RUNNING: start() + RUNNING --> PAUSED: pause() + PAUSED --> RUNNING: resume() + RUNNING --> IDLE: stop() or completion + PAUSED --> IDLE: stop() +``` + +## Action Execution Flow + +```mermaid +sequenceDiagram + participant CM as CutsceneManager + participant SA as SequentialAction + participant PA as ParallelActionA + participant PB as ParallelActionB + + CM->>SA: start() + SA->>CM: started signal + CM->>SA: update() each frame + SA->>CM: completed signal + + CM->>PA: start() + CM->>PB: start() + PA->>CM: started signal + PB->>CM: started signal + CM->>PA: update() each frame + CM->>PB: update() each frame + PA->>CM: completed signal + Note over CM: Waiting for all parallel actions + PB->>CM: completed signal + Note over CM: All parallel actions completed +``` + +## Class Hierarchy + +```mermaid +classDiagram + class Action { + +State state + +String name + +started() + +completed() + +failed() + +start() + +update() + +is_completed() + +stop() + } + + class MoveAction { + +Node2D character + +Vector2 target_position + +float speed + +start() + +update() + +is_completed() + } + + class TurnAction { + +Node2D character + +Variant target + +float turn_speed + +start() + +update() + +is_completed() + } + + class DialogueAction { + +Node2D character + +String text + +float duration + +start() + +update() + +is_completed() + } + + class AnimationAction { + +Node2D character + +String animation_name + +bool loop + +start() + +update() + +is_completed() + } + + class WaitAction { + +float duration + +float elapsed_time + +start() + +update() + +is_completed() + } + + class CutsceneManager { + +Array sequential_actions + +Array parallel_groups + +State state + +start() + +pause() + +resume() + +stop() + +add_action() + +add_parallel_actions() + +_process() + } + + Action <|-- MoveAction + Action <|-- TurnAction + Action <|-- DialogueAction + Action <|-- AnimationAction + Action <|-- WaitAction + CutsceneManager --> Action +``` + +## Signal Connection Flow + +```mermaid +graph LR + A[CutsceneManager] -- Connects to --> B[Action Signals] + B -- started --> A + B -- completed --> A + B -- failed --> A + B -- stopped --> A + + A -- Manages --> C[Sequential Execution] + A -- Manages --> D[Parallel Execution] + + D -- Tracks Completion --> E[Parallel Group Counter] + E -- Notifies When Complete --> A +``` + +This architecture provides a clear, modular design that supports both sequential and parallel action execution while maintaining extensibility for new action types. \ No newline at end of file diff --git a/cutscene_system_design.md b/cutscene_system_design.md new file mode 100644 index 0000000..bb08470 --- /dev/null +++ b/cutscene_system_design.md @@ -0,0 +1,116 @@ +# Cutscene System Design for 2D Point-and-Click Adventure Game + +## Overview +This document outlines the design for a flexible cutscene system in Godot 4.3. The system will manage actions that occur in sequence or in parallel, with support for time-based actions like character movement. + +## Core Architecture + +### 1. CutsceneManager +The main orchestrator that manages the execution of cutscene actions. + +```gdscript +# Core responsibilities: +# - Managing the action queue +# - Handling sequential and parallel execution +# - Tracking action completion +# - Providing an interface for starting/stopping cutscenes +``` + +### 2. Base Action Class +An abstract base class that all actions will inherit from. + +```gdscript +# Core responsibilities: +# - Defining common interface for all actions +# - Managing action state (pending, running, completed) +# - Providing start/stop functionality +# - Handling completion callbacks +``` + +### 3. Action Execution System +Two main execution modes: +- **Sequential**: Actions execute one after another +- **Parallel**: Multiple actions execute simultaneously + +## Action Types + +### Core Action Types +1. **MoveAction**: Moves a character to a specific position +2. **DialogueAction**: Displays dialogue text +3. **AnimationAction**: Plays a specific animation +4. **TurnAction**: Makes a character turn to face a direction or another character +5. **WaitAction**: Pauses execution for a specified time + +### Extensible Design +The system will be designed to easily add new action types by inheriting from the base Action class. + +## Implementation Details + +### Action State Management +Each action will have these states: +- `PENDING`: Action is waiting to be executed +- `RUNNING`: Action is currently executing +- `COMPLETED`: Action has finished executing + +### Completion System +Actions will use Godot's signal system to notify when they're completed: +- Each action emits a `completed` signal when finished +- The CutsceneManager listens for these signals to determine when to proceed + +### Parallel Execution +For parallel actions: +- Multiple actions start at the same time +- The CutsceneManager waits for all actions to complete before proceeding +- Uses a counter to track how many parallel actions are still running + +## Example Usage + +The system should support scripts like this: + +```gdscript +# Create a cutscene +var cutscene = CutsceneManager.new() + +# Add sequential actions +cutscene.add_action(MoveAction.new(character1, Vector2(234, 591))) +cutscene.add_action(DialogueAction.new(character1, "Hello there!")) + +# Add parallel actions +var parallel_group = [ + MoveAction.new(character1, Vector2(234, 591)), + MoveAction.new(character2, Vector2(912, 235)) +] +cutscene.add_parallel_actions(parallel_group) + +# Add more sequential actions +cutscene.add_action(TurnAction.new(character2, character1)) +cutscene.add_action(DialogueAction.new(character2, "Nice to meet you!")) +cutscene.add_action(AnimationAction.new(character1, "shocked")) + +# Start the cutscene +cutscene.start() +``` + +## File Structure +``` +/cutscene/ + ├── CutsceneManager.gd + ├── actions/ + │ ├── Action.gd (base class) + │ ├── MoveAction.gd + │ ├── DialogueAction.gd + │ ├── AnimationAction.gd + │ ├── TurnAction.gd + │ └── WaitAction.gd + └── examples/ + └── sample_cutscene.gd +``` + +## Extensibility +To add new action types: +1. Create a new class that inherits from `Action.gd` +2. Implement the required methods (`start()`, `is_completed()`, etc.) +3. Add any specific logic for the action type +4. Use in cutscenes like any other action + +This design provides a solid foundation for a flexible cutscene system that can handle both sequential and parallel actions while being easily extensible for new action types. \ No newline at end of file diff --git a/cutscene_system_summary.md b/cutscene_system_summary.md new file mode 100644 index 0000000..f180618 --- /dev/null +++ b/cutscene_system_summary.md @@ -0,0 +1,148 @@ +# Cutscene System Summary + +## Overview +This document provides a comprehensive summary of the cutscene system designed for a 2D point-and-click adventure game in Godot 4.3. The system supports both sequential and parallel action execution, with a focus on extensibility and dynamic behavior. + +## System Architecture + +### Core Components + +1. **CutsceneManager** + - Central orchestrator for all cutscene actions + - Manages sequential and parallel action execution + - Handles action completion and state transitions + - Provides signals for cutscene events + +2. **Action (Base Class)** + - Abstract base class for all action types + - Defines common interface and state management + - Implements signal-based completion system + - Supports callbacks for flexible chaining + +3. **Specific Action Types** + - MoveAction: Character movement to specific positions + - TurnAction: Character rotation to face targets + - DialogueAction: Text display for conversations + - AnimationAction: Playing character animations + - WaitAction: Time-based delays + +### Key Features + +1. **Sequential Execution** + - Actions execute one after another in order + - Each action must complete before the next begins + - Supports complex linear story sequences + +2. **Parallel Execution** + - Multiple actions can run simultaneously + - System waits for all actions in a group to complete + - Enables synchronized character movements and actions + +3. **Dynamic Behavior** + - Actions are not immediate; they take time to complete + - Frame-based updates for smooth animations + - Asynchronous completion through signal system + +4. **Extensibility** + - Easy to add new action types by extending the base Action class + - Plugin architecture for complex extensions + - Custom event and callback systems + +## Implementation Details + +### File Structure +``` +/cutscene/ + ├── CutsceneManager.gd + ├── Action.gd (base class) + ├── actions/ + │ ├── MoveAction.gd + │ ├── TurnAction.gd + │ ├── DialogueAction.gd + │ ├── AnimationAction.gd + │ └── WaitAction.gd + └── examples/ + └── sample_cutscene.gd +``` + +### Example Usage + +The system supports complex sequences like the one described in the requirements: + +```gdscript +# 1. Character1 moves to 234, 591 +# 2. At the same time Character2 moves to 912, 235 +# 3. When both arrive, character2 turns to character1 +# 4. Character2 says a dialogue line to character1 +# 5. A special animation (shocked) is played for character1 + +var cutscene = CutsceneManager.new() + +# Add parallel movements +var moves = [ + MoveAction.new(character1, Vector2(234, 591)), + MoveAction.new(character2, Vector2(912, 235)) +] +cutscene.add_parallel_actions(moves) + +// Add sequential actions +cutscene.add_action(TurnAction.new(character2, character1)) +cutscene.add_action(DialogueAction.new(character2, "Hello there!")) +cutscene.add_action(AnimationAction.new(character1, "shocked")) + +cutscene.start() +``` + +## Technical Design + +### State Management +- Actions have three states: PENDING, RUNNING, COMPLETED +- CutsceneManager tracks overall state: IDLE, RUNNING, PAUSED +- Proper state transitions with signal notifications + +### Completion System +- Signal-based completion notifications +- Parallel action group tracking +- Error handling and recovery mechanisms +- Callback support for flexible action chaining + +### Performance Considerations +- Object pooling support for frequently created actions +- Efficient signal connection management +- Minimal overhead for inactive actions +- Frame-based updates for smooth animations + +## Extensibility Features + +### Adding New Actions +1. Create a new class extending Action +2. Implement required methods (start, is_completed) +3. Add custom logic in update() if needed +4. Use the action in cutscenes like any other + +### System Extensions +- Plugin architecture for complex features +- Custom event system for dynamic triggers +- Integration points with game state management +- Save/load support for persistent cutscenes + +## Benefits + +1. **Flexibility**: Supports both simple linear sequences and complex parallel actions +2. **Extensibility**: Easy to add new action types and system features +3. **Integration**: Designed to work with existing Godot systems +4. **Performance**: Optimized for smooth gameplay and animations +5. **Maintainability**: Clean separation of concerns and clear interfaces + +## Next Steps + +To implement this system: + +1. Create the base Action class +2. Implement the CutsceneManager +3. Develop the core action types +4. Add the completion and callback system +5. Create sample cutscenes for testing +6. Extend with custom actions as needed + +This design provides a solid foundation for a powerful, flexible cutscene system that can handle the requirements of a 2D point-and-click adventure game while remaining extensible for future needs. \ No newline at end of file diff --git a/extensibility_guide.md b/extensibility_guide.md new file mode 100644 index 0000000..8ca0840 --- /dev/null +++ b/extensibility_guide.md @@ -0,0 +1,300 @@ +# Cutscene System Extensibility Guide + +## Overview +This document explains how to extend the cutscene system with new action types, features, and customizations while maintaining compatibility with the existing architecture. + +## Adding New Action Types + +### Basic Extension Process +1. Create a new class that extends `Action` +2. Implement the required methods (`start()`, `is_completed()`) +3. Optionally implement `update()` and `stop()` +4. Add any custom properties or methods needed + +### Example: Custom FadeAction +```gdscript +class_name FadeAction +extends Action + +# Custom properties +var target_node: CanvasItem +var target_alpha: float +var fade_speed: float = 1.0 +var start_alpha: float = 1.0 + +func _init(node: CanvasItem, alpha: float, speed: float = 1.0): + target_node = node + target_alpha = alpha + fade_speed = speed + name = "FadeAction" + +func start() -> void: + if target_node == null: + ._set_failed("Target node is null") + return + + start_alpha = target_node.modulate.a + ._set_running() + +func update(delta: float) -> void: + if state != State.RUNNING: + return + + if target_node == null: + ._set_failed("Target node was destroyed") + return + + # Calculate new alpha value + var current_alpha = target_node.modulate.a + var alpha_diff = target_alpha - current_alpha + var alpha_change = sign(alpha_diff) * fade_speed * delta + + # Check if we've reached the target + if abs(alpha_change) >= abs(alpha_diff): + # Set final alpha and complete + var new_modulate = target_node.modulate + new_modulate.a = target_alpha + target_node.modulate = new_modulate + ._set_completed() + else: + # Apply incremental change + var new_modulate = target_node.modulate + new_modulate.a += alpha_change + target_node.modulate = new_modulate + +func is_completed() -> bool: + return state == State.COMPLETED +``` + +## Customizing the CutsceneManager + +### Adding New Features +The CutsceneManager can be extended with new functionality: + +```gdscript +# Extended CutsceneManager with additional features +class_name ExtendedCutsceneManager +extends CutsceneManager + +# Custom properties +var skip_enabled: bool = true +var auto_skip_delay: float = 0.0 + +# Custom signals +signal cutscene_skipped() + +# Custom methods +func skip_cutscene() -> void: + if not skip_enabled: + return + + emit_signal("cutscene_skipped") + stop() + +func set_auto_skip(seconds: float) -> void: + auto_skip_delay = seconds + # Implementation for auto-skip functionality +``` + +### Plugin Architecture +For complex extensions, consider a plugin-style architecture: + +```gdscript +# Plugin interface +class_name CutscenePlugin +extends RefCounted + +func initialize(manager: CutsceneManager) -> void: + # Initialize plugin with the manager + pass + +func process_action(action: Action, delta: float) -> void: + # Process action each frame + pass + +func cleanup() -> void: + # Clean up when cutscene ends + pass + +# Example plugin for debugging +class_name DebugCutscenePlugin +extends CutscenePlugin + +func process_action(action: Action, delta: float) -> void: + if action.state == Action.State.RUNNING: + print("Action %s is running" % action.name) +``` + +## Integration with External Systems + +### Game State Integration +Connect the cutscene system to your game's state management: + +```gdscript +# Integration with a game state manager +class_name GameStateIntegratedCutsceneManager +extends CutsceneManager + +var game_state_manager: GameStateManager + +func start() -> void: + # Notify game state manager + if game_state_manager: + game_state_manager.set_state(GameState.CUTSCENE) + + # Call parent start method + super().start() + +func _on_cutscene_completed() -> void: + # Notify game state manager + if game_state_manager: + game_state_manager.set_state(GameState.PLAYING) + + # Call parent completion handler + super()._on_cutscene_completed() +``` + +### Save/Load System Integration +Add support for saving and loading cutscene states: + +```gdscript +class_name SaveableCutsceneManager +extends CutsceneManager + +func save_state() -> Dictionary: + return { + "current_action_index": action_index, + "sequential_actions": _serialize_actions(sequential_actions), + "parallel_groups": _serialize_parallel_groups(active_parallel_groups), + "state": state + } + +func load_state(data: Dictionary) -> void: + action_index = data["current_action_index"] + sequential_actions = _deserialize_actions(data["sequential_actions"]) + active_parallel_groups = _deserialize_parallel_groups(data["parallel_groups"]) + state = data["state"] +``` + +## Performance Optimization Extensions + +### Action Pooling +Implement object pooling for frequently created actions: + +```gdscript +class_name PooledCutsceneManager +extends CutsceneManager + +var action_pools: Dictionary = {} + +func get_pooled_action(action_type: String, params: Array) -> Action: + if not action_pools.has(action_type): + action_pools[action_type] = [] + + var pool = action_pools[action_type] + if pool.size() > 0: + # Reuse existing action + var action = pool.pop_back() + # Reinitialize with new parameters + action.reinitialize(params) + return action + else: + # Create new action + return _create_action(action_type, params) + +func return_action_to_pool(action: Action) -> void: + var action_type = action.get_class() + if not action_pools.has(action_type): + action_pools[action_type] = [] + + action.reset() + action_pools[action_type].append(action) +``` + +## Custom Action Composition + +### Action Groups +Create reusable action groups for common sequences: + +```gdscript +class_name ActionGroup +extends Action + +var actions: Array +var current_action_index: int = 0 + +func _init(group_actions: Array): + actions = group_actions + name = "ActionGroup" + +func start() -> void: + current_action_index = 0 + if actions.size() > 0: + _execute_action(actions[0]) + else: + ._set_completed() + +func _execute_action(action: Action) -> void: + action.connect("completed", _on_sub_action_completed.bind(action)) + action.start() + +func _on_sub_action_completed(action: Action) -> void: + current_action_index += 1 + if current_action_index < actions.size(): + _execute_action(actions[current_action_index]) + else: + ._set_completed() +``` + +## Event-Driven Extensions + +### Custom Events +Add support for custom events that can trigger actions: + +```gdscript +class_name EventDrivenCutsceneManager +extends CutsceneManager + +var event_listeners: Dictionary = {} + +func listen_for_event(event_name: String, action: Action) -> void: + if not event_listeners.has(event_name): + event_listeners[event_name] = [] + + event_listeners[event_name].append(action) + +func trigger_event(event_name: String) -> void: + if event_listeners.has(event_name): + for action in event_listeners[event_name]: + # Add action to current sequence or execute immediately + add_action(action) +``` + +## Best Practices for Extensions + +### 1. Maintain Compatibility +- Always call parent methods when overriding +- Keep the same method signatures +- Don't change the core behavior of existing methods + +### 2. Use Composition Over Inheritance +- Prefer adding functionality through plugins or components +- Keep inheritance hierarchies shallow +- Use interfaces where possible + +### 3. Provide Clear Extension Points +- Document which methods are safe to override +- Provide virtual methods for customization +- Use signals for loose coupling + +### 4. Handle Errors Gracefully +- Always check for null references +- Provide meaningful error messages +- Implement fallback behaviors + +### 5. Optimize for Performance +- Reuse objects when possible +- Avoid unnecessary processing +- Profile extensions for performance impact + +This extensibility guide provides a framework for expanding the cutscene system while maintaining its core functionality and ease of use. \ No newline at end of file diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..9d8b7fa --- /dev/null +++ b/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icon.svg.import b/icon.svg.import new file mode 100644 index 0000000..5be0cf0 --- /dev/null +++ b/icon.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://db7d1mnbo2tb6" +path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.svg" +dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/implementation_roadmap.md b/implementation_roadmap.md new file mode 100644 index 0000000..375cef4 --- /dev/null +++ b/implementation_roadmap.md @@ -0,0 +1,229 @@ +# Cutscene System Implementation Roadmap + +## Overview +This document outlines a step-by-step roadmap for implementing the cutscene system in Godot 4.3, based on the architectural designs created. + +## Phase 1: Foundation + +### 1.1 Base Action Class +- Create `Action.gd` base class +- Implement state management (PENDING, RUNNING, COMPLETED) +- Add core signals (started, completed, failed) +- Implement basic methods (start, update, is_completed, stop) + +### 1.2 CutsceneManager Core +- Create `CutsceneManager.gd` +- Implement sequential action queue +- Add action addition methods +- Create basic state management (IDLE, RUNNING) + +## Phase 2: Core Action Types + +### 2.1 WaitAction +- Simplest action to implement and test +- Validates the basic action system +- Tests completion signaling + +### 2.2 MoveAction +- Implement character movement logic +- Add position calculation and frame-based updates +- Test with different speeds and distances + +### 2.3 TurnAction +- Implement character rotation logic +- Handle both position and node targets +- Test smooth rotation animations + +## Phase 3: Advanced Features + +### 3.1 Parallel Execution System +- Implement parallel action group management +- Add completion tracking for parallel actions +- Test with multiple simultaneous actions + +### 3.2 DialogueAction +- Implement basic dialogue display +- Add duration-based completion +- Test with different text lengths + +### 3.3 AnimationAction +- Implement animation playback +- Handle AnimationPlayer integration +- Test with looping and non-looping animations + +## Phase 4: Completion and Callback System + +### 4.1 Signal Integration +- Connect action signals to CutsceneManager +- Implement action completion handling +- Add error handling and failure recovery + +### 4.2 Callback System +- Implement action-level callbacks +- Add cutscene-level callbacks +- Test callback chaining + +### 4.3 State Management +- Implement full state transitions +- Add pause/resume functionality +- Test state persistence + +## Phase 5: Testing and Examples + +### 5.1 Basic Cutscene Example +- Create simple sequential cutscene +- Test all core action types +- Validate completion flow + +### 5.2 Complex Cutscene Example +- Implement the requirement example: + - Character1 moves to 234, 591 + - Character2 moves to 912, 235 (simultaneously) + - Character2 turns to Character1 + - Character2 says dialogue + - Character1 plays shocked animation +- Test parallel execution +- Validate timing and synchronization + +### 5.3 Edge Case Testing +- Test empty action queues +- Test failed actions +- Test rapid state changes +- Test memory management + +## Phase 6: Extensibility Features + +### 6.1 Custom Action Example +- Create a custom action type +- Demonstrate extension process +- Validate compatibility with core system + +### 6.2 Plugin Architecture +- Implement basic plugin system +- Create example plugin +- Test plugin integration + +### 6.3 Performance Optimization +- Implement object pooling +- Add performance monitoring +- Optimize update loops + +## Implementation Order + +```mermaid +graph TD + A[Phase 1: Foundation] --> B[Phase 2: Core Actions] + B --> C[Phase 3: Advanced Features] + C --> D[Phase 4: Completion System] + D --> E[Phase 5: Testing] + E --> F[Phase 6: Extensibility] + + A1[Action Base Class] --> A2[CutsceneManager Core] + A --> A1 + A --> A2 + + B1[WaitAction] --> B2[MoveAction] + B2 --> B3[TurnAction] + B --> B1 + B --> B2 + B --> B3 + + C1[Parallel Execution] --> C2[DialogueAction] + C2 --> C3[AnimationAction] + C --> C1 + C --> C2 + C --> C3 + + D1[Signal Integration] --> D2[Callback System] + D2 --> D3[State Management] + D --> D1 + D --> D2 + D --> D3 + + E1[Basic Example] --> E2[Complex Example] + E2 --> E3[Edge Case Testing] + E --> E1 + E --> E2 + E --> E3 + + F1[Custom Action] --> F2[Plugin Architecture] + F2 --> F3[Performance Optimization] + F --> F1 + F --> F2 + F --> F3 +``` + +## Testing Strategy + +### Unit Tests +- Each action type tested independently +- CutsceneManager state transitions +- Signal emission and connection +- Error handling paths + +### Integration Tests +- Sequential action execution +- Parallel action execution +- Mixed sequential/parallel sequences +- Callback chaining + +### Performance Tests +- Large action queues +- Multiple simultaneous cutscenes +- Memory allocation/garbage collection +- Frame rate impact + +## File Structure Implementation + +``` +res:// +└── cutscene/ + ├── CutsceneManager.gd + ├── Action.gd + ├── actions/ + │ ├── MoveAction.gd + │ ├── TurnAction.gd + │ ├── DialogueAction.gd + │ ├── AnimationAction.gd + │ └── WaitAction.gd + ├── examples/ + │ ├── basic_cutscene.tscn + │ ├── complex_cutscene.tscn + │ └── custom_action_example.gd + └── tests/ + ├── test_action.gd + ├── test_cutscene_manager.gd + └── test_parallel_execution.gd +``` + +## Success Criteria + +### Functional Requirements +- [ ] Sequential actions execute in order +- [ ] Parallel actions execute simultaneously +- [ ] All core action types function correctly +- [ ] Completion system works reliably +- [ ] Error handling is robust + +### Performance Requirements +- [ ] Frame rate remains stable during cutscenes +- [ ] Memory usage is optimized +- [ ] No significant garbage collection spikes + +### Extensibility Requirements +- [ ] New action types can be added easily +- [ ] System can be extended with plugins +- [ ] Custom callbacks work as expected + +## Timeline Estimate + +### Phase 1: Foundation - 2 days +### Phase 2: Core Action Types - 3 days +### Phase 3: Advanced Features - 2 days +### Phase 4: Completion and Callback System - 2 days +### Phase 5: Testing and Examples - 3 days +### Phase 6: Extensibility Features - 2 days + +**Total Estimated Time: 14 days** + +This roadmap provides a structured approach to implementing the cutscene system, ensuring that each component is properly tested and integrated before moving on to the next phase. \ No newline at end of file diff --git a/main.tscn b/main.tscn new file mode 100644 index 0000000..8d5f1b5 --- /dev/null +++ b/main.tscn @@ -0,0 +1,3 @@ +[gd_scene format=3 uid="uid://ovmd1xwe60gm"] + +[node name="Node2D" type="Node2D"] diff --git a/project.godot b/project.godot new file mode 100644 index 0000000..ce71489 --- /dev/null +++ b/project.godot @@ -0,0 +1,16 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[application] + +config/name="adventure-ai" +run/main_scene="res://cutscene/examples/sample_cutscene.tscn" +config/features=PackedStringArray("4.3", "Forward Plus") +config/icon="res://icon.svg" diff --git a/sample_cutscene_example.md b/sample_cutscene_example.md new file mode 100644 index 0000000..dba379c --- /dev/null +++ b/sample_cutscene_example.md @@ -0,0 +1,242 @@ +# Sample Cutscene Example + +## Overview +This document provides a complete example of how to use the cutscene system to create a complex sequence as described in the requirements. + +## Example Scenario +The example implements this sequence: +1. Character1 moves to position (234, 591) +2. At the same time, Character2 moves to position (912, 235) +3. When both arrive, Character2 turns to face Character1 +4. Character2 says a dialogue line to Character1 +5. A special animation (shocked) is played for Character1 + +## Sample Implementation + +```gdscript +# Sample cutscene implementation +extends Node2D + +# Character nodes (would be set in the editor or loaded) +var character1: Node2D +var character2: Node2D + +# Cutscene manager +var cutscene_manager: CutsceneManager + +func _ready() -> void: + # Initialize the cutscene system + setup_cutscene() + + # Start the cutscene + cutscene_manager.start() + +func setup_cutscene() -> void: + # Create the cutscene manager + cutscene_manager = CutsceneManager.new() + add_child(cutscene_manager) + + # Create the action sequence as described in requirements + + # 1. & 2. Characters move simultaneously + var parallel_moves = [ + MoveAction.new(character1, Vector2(234, 591), 150.0), # Character1 moves + MoveAction.new(character2, Vector2(912, 235), 150.0) # Character2 moves + ] + cutscene_manager.add_parallel_actions(parallel_moves) + + # 3. Character2 turns to face Character1 + var turn_action = TurnAction.new(character2, character1, 3.0) + cutscene_manager.add_action(turn_action) + + # 4. Character2 says dialogue + var dialogue_action = DialogueAction.new(character2, "Hello there, friend!", 2.0) + cutscene_manager.add_action(dialogue_action) + + # 5. Character1 plays shocked animation + var animation_action = AnimationAction.new(character1, "shocked", false) + cutscene_manager.add_action(animation_action) + + # Set up cutscene callbacks + cutscene_manager.on_completed = func(): + print("Cutscene completed!") + # Return to gameplay or next scene + +# Alternative implementation with more complex chaining +func setup_advanced_cutscene() -> void: + cutscene_manager = CutsceneManager.new() + add_child(cutscene_manager) + + # Create actions + var move1 = MoveAction.new(character1, Vector2(234, 591), 150.0) + var move2 = MoveAction.new(character2, Vector2(912, 235), 150.0) + var turn = TurnAction.new(character2, character1, 3.0) + var dialogue = DialogueAction.new(character2, "Hello there, friend!", 2.0) + var animation = AnimationAction.new(character1, "shocked", false) + + # Add parallel actions + cutscene_manager.add_parallel_actions([move1, move2]) + + # Add sequential actions + cutscene_manager.add_action(turn) + cutscene_manager.add_action(dialogue) + cutscene_manager.add_action(animation) + + # Set completion callback + cutscene_manager.on_completed = _on_cutscene_finished +} + +func _on_cutscene_finished() -> void: + print("The cutscene has finished playing") + # Handle post-cutscene logic here + # For example, transition to gameplay state +``` + +## More Complex Example with Conditional Logic + +```gdscript +# Advanced example with conditional actions +func setup_conditional_cutscene() -> void: + cutscene_manager = CutsceneManager.new() + add_child(cutscene_manager) + + # Move characters into position + var move_group = [ + MoveAction.new(character1, Vector2(300, 400)), + MoveAction.new(character2, Vector2(500, 400)) + ] + cutscene_manager.add_parallel_actions(move_group) + + # Conditional dialogue based on game state + var dialogue_action = DialogueAction.new( + character1, + get_dialogue_text(), + 3.0 + ) + cutscene_manager.add_action(dialogue_action) + + # Play different animation based on dialogue response + var animation_action = AnimationAction.new( + character2, + get_reaction_animation(), + false + ) + cutscene_manager.add_action(animation_action) + + # Wait for a moment + cutscene_manager.add_action(WaitAction.new(1.0)) + + # Final action + var final_dialogue = DialogueAction.new( + character2, + "That was interesting!", + 2.0 + ) + cutscene_manager.add_action(final_dialogue) + +func get_dialogue_text() -> String: + # This could be based on game state, player choices, etc. + return "What do you think about this situation?" + +func get_reaction_animation() -> String: + # This could be based on game state, player choices, etc. + return "thoughtful" +``` + +## Integration with Game Systems + +```gdscript +# Example of integrating with a game state manager +class_name GameCutsceneManager +extends Node + +var cutscene_manager: CutsceneManager +var game_state: GameStateManager + +func play_story_cutscene(story_key: String) -> void: + # Pause gameplay + game_state.set_state(GameState.CUTSCENE) + + # Create cutscene based on story key + var cutscene_data = load_cutscene_data(story_key) + cutscene_manager = create_cutscene_from_data(cutscene_data) + + # Set up completion callback to resume gameplay + cutscene_manager.on_completed = func(): + game_state.set_state(GameState.PLAYING) + # Clean up + cutscene_manager.queue_free() + cutscene_manager = null + + # Start the cutscene + cutscene_manager.start() + +func load_cutscene_data(key: String) -> Dictionary: + # Load cutscene data from a file or database + # This could be JSON, CSV, or custom format + return { + "actions": [ + {"type": "move", "character": "player", "position": [100, 200]}, + {"type": "dialogue", "character": "npc", "text": "Hello!"}, + {"type": "animation", "character": "player", "name": "wave"} + ] + } + +func create_cutscene_from_data(data: Dictionary) -> CutsceneManager: + var manager = CutsceneManager.new() + add_child(manager) + + # Convert data to actions + for action_data in data["actions"]: + var action = create_action_from_data(action_data) + if action: + manager.add_action(action) + + return manager + +func create_action_from_data(data: Dictionary) -> Action: + match data["type"]: + "move": + var character = get_character_by_name(data["character"]) + var position = Vector2(data["position"][0], data["position"][1]) + return MoveAction.new(character, position) + "dialogue": + var character = get_character_by_name(data["character"]) + return DialogueAction.new(character, data["text"]) + "animation": + var character = get_character_by_name(data["character"]) + return AnimationAction.new(character, data["name"]) + _: + return null +``` + +## Performance Considerations + +```gdscript +# Example of optimizing cutscene performance +func setup_optimized_cutscene() -> void: + cutscene_manager = CutsceneManager.new() + add_child(cutscene_manager) + + # Pre-load any resources needed for actions + preload_animations() + preload_dialogue_textures() + + # Use object pooling for frequently created actions + var move_action_pool = [] + for i in range(10): + move_action_pool.append(MoveAction.new(null, Vector2.ZERO)) + + # Reuse actions when possible + var action1 = get_pooled_move_action(character1, Vector2(100, 100)) + var action2 = get_pooled_move_action(character2, Vector2(200, 200)) + + cutscene_manager.add_parallel_actions([action1, action2]) + + # Clean up when done + cutscene_manager.on_completed = func(): + release_pooled_actions() + # Other cleanup +``` + +This sample demonstrates how to use the cutscene system to create complex, dynamic sequences that can handle both sequential and parallel actions while maintaining clean, readable code. \ No newline at end of file diff --git a/scripts/cutscene/CutsceneManager.gd b/scripts/cutscene/CutsceneManager.gd new file mode 100644 index 0000000..e69de29