300 lines
7.9 KiB
Markdown
300 lines
7.9 KiB
Markdown
# 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. |