This commit is contained in:
2025-07-30 21:52:27 -07:00
commit a429a3e06b
29 changed files with 3068 additions and 0 deletions

2
.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
# Godot 4+ specific ignores
.godot/
/android/

116
action_class_design.md Normal file
View File

@@ -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.

275
action_completion_system.md Normal file
View File

@@ -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.

313
action_types_design.md Normal file
View File

@@ -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.

59
cutscene/Action.gd Normal file
View File

@@ -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)

228
cutscene/CutsceneManager.gd Normal file
View File

@@ -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

135
cutscene/README.md Normal file
View File

@@ -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.

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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="."]

View File

@@ -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()

View File

@@ -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)

View File

@@ -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!")

170
cutscene_manager_design.md Normal file
View File

@@ -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.

View File

@@ -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.

116
cutscene_system_design.md Normal file
View File

@@ -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.

148
cutscene_system_summary.md Normal file
View File

@@ -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.

300
extensibility_guide.md Normal file
View File

@@ -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.

1
icon.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg>

After

Width:  |  Height:  |  Size: 994 B

37
icon.svg.import Normal file
View File

@@ -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

229
implementation_roadmap.md Normal file
View File

@@ -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.

3
main.tscn Normal file
View File

@@ -0,0 +1,3 @@
[gd_scene format=3 uid="uid://ovmd1xwe60gm"]
[node name="Node2D" type="Node2D"]

16
project.godot Normal file
View File

@@ -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"

242
sample_cutscene_example.md Normal file
View File

@@ -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.

View File