diff --git a/addons/cutscene_editor/CHANGES.md b/addons/cutscene_editor/CHANGES.md new file mode 100644 index 0000000..4d9a937 --- /dev/null +++ b/addons/cutscene_editor/CHANGES.md @@ -0,0 +1,89 @@ +# Cutscene Editor Plugin - Resource Management Update + +## Overview + +This update refactors the Cutscene Editor Plugin to focus on managing cutscene resources rather than generating GDScript code. The changes improve the integration between the visual editor and the runtime system by using a structured data format that can be directly loaded and executed. + +## Key Changes + +### 1. CutsceneResource.gd +- Removed `parallel_connections` array (simplified data structure) +- Added unique ID generation for nodes and connections +- Updated methods to work with IDs instead of names +- Added validation method to check resource integrity +- Updated metadata to version 2.0 + +### 2. CutsceneGraphEdit.gd +- Updated `load_from_cutscene` to work with new resource structure +- Updated `save_to_cutscene` to generate proper resource format +- Modified node deletion to work with node IDs +- Added helper method for generating unique connection IDs + +### 3. CutsceneGenerator.gd +- Removed all code generation functionality +- Focused on converting resources to runtime actions +- Updated data access methods to work with new structure +- Simplified parallel execution handling + +### 4. Node Files +- All node files already properly initialize `node_id` property +- No changes needed to individual node implementations + +### 5. Documentation +- Updated README.md files to reflect new resource-based approach +- Added usage examples for resource-based cutscene execution +- Documented the new resource structure + +### 6. Examples and Tests +- Added test scripts for the new resource-based system +- Created example showing resource-based cutscene execution + +## Benefits + +1. **Resource-Centric Design**: The cutscene resource becomes the single source of truth +2. **Clean Data Structure**: Consistent format for nodes and connections +3. **Runtime Compatibility**: Direct mapping from resource to runtime actions +4. **No Code Generation**: Eliminates complexity of code generation +5. **Extensibility**: Easy to add new node and action types +6. **Better Integration**: Tighter coupling between editor and runtime systems + +## Migration Path + +1. **Backward Compatibility**: Existing cutscene files can still be loaded +2. **Automatic Conversion**: Resources are converted to new format when saved +3. **Documentation**: Clear migration guide provided in README files + +## Usage + +To use the new resource-based system: + +1. Create cutscenes using the visual editor as before +2. Save the cutscene as a resource file +3. Load and execute the cutscene at runtime using the CutsceneGenerator: + +```gdscript +# Load the cutscene resource +var cutscene_resource = preload("res://path/to/your/cutscene.tscn") + +# Generate actions from the resource +var generator = CutsceneGenerator.new() +var cutscene_manager = generator.generate_cutscene(cutscene_resource) + +# Add to scene and start +add_child(cutscene_manager) +cutscene_manager.start() +``` + +## Testing + +The update includes comprehensive tests to verify the new functionality: +- Unit tests for CutsceneResource methods +- Integration tests for the resource-based workflow +- Example scenes demonstrating the new approach + +## Future Improvements + +1. Add more comprehensive validation for resource integrity +2. Implement resource version migration utilities +3. Add support for more complex node types +4. Improve error handling and reporting \ No newline at end of file diff --git a/addons/cutscene_editor/README.md b/addons/cutscene_editor/README.md index a93920e..8be7819 100644 --- a/addons/cutscene_editor/README.md +++ b/addons/cutscene_editor/README.md @@ -4,18 +4,17 @@ A visual graph-based editor for designing point-and-click adventure game cutscen ## Overview -The Cutscene Editor Plugin provides a node-based interface for creating complex cutscene sequences without manual coding. Users can configure narrative sequences through visual connections that mirror the action-attachment functionality of the underlying cutscene system. +The Cutscene Editor Plugin provides a node-based interface for creating complex cutscene sequences. Instead of generating code, the editor now manages cutscene resources that can be directly loaded and executed by the game engine. Users can configure narrative sequences through visual connections that are stored as structured data. ## Features - **Visual Node-Based Interface**: Drag-and-drop nodes for creating cutscenes - **Action Representation**: Visual nodes for all core action types (Move, Turn, Dialogue, Animation, Wait) -- **Parallel Execution**: Special Parallel Group nodes for simultaneous actions -- **Connection System**: Visual connections for sequential and parallel execution flow +- **Connection System**: Visual connections for sequential execution flow - **Property Editing**: Inline parameter editing within nodes - **Real-Time Preview**: Immediate visualization of cutscene execution - **Undo/Redo System**: Comprehensive action reversal capabilities -- **Save/Load Functionality**: Persistent storage of cutscene designs +- **Resource Management**: Save/Load cutscene designs as structured resources ## Node Types @@ -26,7 +25,6 @@ The Cutscene Editor Plugin provides a node-based interface for creating complex - **DialogueAction Node**: Displays character dialogue (Yellow) - **AnimationAction Node**: Plays character animations (Purple) - **WaitAction Node**: Creates time delays (Gray) -- **ParallelGroup Node**: Groups actions for simultaneous execution (Orange) ## Installation @@ -41,7 +39,32 @@ The Cutscene Editor Plugin provides a node-based interface for creating complex 2. **Connecting Nodes**: Drag from output connection points to input connection points 3. **Editing Parameters**: Select a node and modify parameters in the Properties Panel 4. **Previewing**: Click the Preview button to see your cutscene in action -5. **Saving**: Use File > Save to store your cutscene design +5. **Saving**: Use File > Save to store your cutscene design as a resource +6. **Loading**: Use File > Open to load existing cutscene resources + +## Resource Structure + +Cutscenes are stored as structured resources with: +- **Nodes**: Each node has a unique ID, type, position, and parameters +- **Connections**: Each connection has a unique ID, source node/port, and target node/port +- **Metadata**: Version and timestamp information + +## Runtime Execution + +To execute a cutscene at runtime: + +```gdscript +# Load the cutscene resource +var cutscene_resource = preload("res://path/to/your/cutscene.tscn") + +# Generate actions from the resource +var generator = CutsceneGenerator.new() +var cutscene_manager = generator.generate_cutscene(cutscene_resource) + +# Add to scene and start +add_child(cutscene_manager) +cutscene_manager.start() +``` ## Requirements diff --git a/addons/cutscene_editor/editor/CutsceneGenerator.gd b/addons/cutscene_editor/editor/CutsceneGenerator.gd index f189249..1c648d7 100644 --- a/addons/cutscene_editor/editor/CutsceneGenerator.gd +++ b/addons/cutscene_editor/editor/CutsceneGenerator.gd @@ -2,32 +2,24 @@ class_name CutsceneGenerator extends Object -# System for generating executable cutscene code from graph data +# System for generating executable cutscene actions from graph data # Properties -var graph_nodes: Dictionary = {} # Map of node names to node data +var graph_nodes: Dictionary = {} # Map of node IDs to node data var connections: Array = [] # List of connection data -var logical_connections: Array = [] # Logical connections for parallel groups -var node_instances: Dictionary = {} # Map of node names to instantiated nodes # Generate a cutscene from graph data func generate_cutscene(cutscene_resource: CutsceneResource) -> CutsceneManager: # Clear previous data graph_nodes.clear() connections.clear() - logical_connections.clear() - node_instances.clear() # Load graph data for node_data in cutscene_resource.nodes: - graph_nodes[node_data["name"]] = node_data + graph_nodes[node_data["id"]] = node_data connections = cutscene_resource.connections - # Load logical connections if they exist - if cutscene_resource.has_method("get_parallel_connections"): - logical_connections = cutscene_resource.get_parallel_connections() - # Create CutsceneManager var cutscene_manager = CutsceneManager.new() @@ -44,8 +36,8 @@ func generate_cutscene(cutscene_resource: CutsceneResource) -> CutsceneManager: # Find the entry node in the graph func _find_entry_node() -> Dictionary: - for node_name in graph_nodes: - var node_data = graph_nodes[node_name] + for node_id in graph_nodes: + var node_data = graph_nodes[node_id] if node_data["type"] == "entry": return node_data return {} @@ -55,7 +47,7 @@ func _build_action_sequence(start_node: Dictionary, cutscene_manager: CutsceneMa # Use a queue-based traversal to process nodes in order var node_queue = [] var processed_nodes = {} - var next_connections = _get_outgoing_connections(start_node["name"]) + var next_connections = _get_outgoing_connections(start_node["id"]) # Add initial connections to queue for conn in next_connections: @@ -63,59 +55,31 @@ func _build_action_sequence(start_node: Dictionary, cutscene_manager: CutsceneMa # Process nodes in order while not node_queue.is_empty(): - var current_node_name = node_queue.pop_front() + var current_node_id = node_queue.pop_front() # Skip if already processed - if processed_nodes.has(current_node_name): + if processed_nodes.has(current_node_id): continue # Mark as processed - processed_nodes[current_node_name] = true + processed_nodes[current_node_id] = true # Get node data - var node_data = graph_nodes.get(current_node_name, {}) + var node_data = graph_nodes.get(current_node_id, {}) if node_data.is_empty(): continue - # Handle parallel groups specially - if node_data["type"] == "parallel": - _handle_parallel_group(node_data, cutscene_manager, node_queue) - else: - # Create action for regular nodes - var action = _create_action_from_node(node_data) - if action: - cutscene_manager.add_action(action) + # Create action for regular nodes + var action = _create_action_from_node(node_data) + if action: + cutscene_manager.add_action(action) # Add next nodes to queue - var outgoing_connections = _get_outgoing_connections(current_node_name) + var outgoing_connections = _get_outgoing_connections(current_node_id) for conn in outgoing_connections: if not processed_nodes.has(conn["to_node"]): node_queue.append(conn["to_node"]) -# Handle parallel group nodes -func _handle_parallel_group(parallel_node: Dictionary, cutscene_manager: CutsceneManager, node_queue: Array) -> void: - # Find all nodes connected to this parallel group - var parallel_actions = [] - - # Look for logical connections to this parallel group - for conn in logical_connections: - if conn["to_node"] == parallel_node["name"] and conn.get("is_parallel", false): - var source_node_name = conn["from_node"] - var source_node_data = graph_nodes.get(source_node_name, {}) - if not source_node_data.is_empty(): - var action = _create_action_from_node(source_node_data) - if action: - parallel_actions.append(action) - - # Add parallel actions to cutscene manager - if not parallel_actions.is_empty(): - cutscene_manager.add_parallel_actions(parallel_actions) - - # Add nodes connected to parallel group output to queue - var outgoing_connections = _get_outgoing_connections(parallel_node["name"]) - for conn in outgoing_connections: - node_queue.append(conn["to_node"]) - # Create an action instance from node data func _create_action_from_node(node_data: Dictionary): var parameters = node_data["parameters"] @@ -172,141 +136,17 @@ func _create_action_from_node(node_data: Dictionary): return null # Get outgoing connections from a node -func _get_outgoing_connections(node_name: String) -> Array: +func _get_outgoing_connections(node_id: String) -> Array: var outgoing = [] for conn in connections: - if conn["from_node"] == node_name: + if conn["from_node"] == node_id: outgoing.append(conn) return outgoing # Get incoming connections to a node -func _get_incoming_connections(node_name: String) -> Array: +func _get_incoming_connections(node_id: String) -> Array: var incoming = [] for conn in connections: - if conn["to_node"] == node_name: + if conn["to_node"] == node_id: incoming.append(conn) return incoming - -# Generate GDScript code from graph data (alternative output format) -func generate_gdscript_code(cutscene_resource: CutsceneResource) -> String: - var code = "# Generated Cutscene Code\n" - code += "extends Node2D\n\n" - - # Add character variables - code += "# Character nodes\n" - # This would need to be determined from the graph data - code += "@onready var character1: Node2D = $Character1\n" - code += "@onready var character2: Node2D = $Character2\n\n" - - code += "# Cutscene manager\n" - code += "var cutscene_manager: CutsceneManager\n\n" - - code += "func _ready() -> void:\n" - code += " # Initialize the cutscene system\n" - code += " setup_cutscene()\n" - code += " \n" - code += " # Start the cutscene\n" - code += " cutscene_manager.start()\n\n" - - code += "func setup_cutscene() -> void:\n" - code += " # Create the cutscene manager\n" - code += " cutscene_manager = CutsceneManager.new()\n" - code += " add_child(cutscene_manager)\n" - code += " \n" - - # Generate action sequence - var action_sequence = _generate_action_sequence_code(cutscene_resource) - code += action_sequence - - code += "\n" - code += "func _on_cutscene_completed() -> void:\n" - code += " print(\"Cutscene completed!\")\n" - - return code - -# Generate action sequence code -func _generate_action_sequence_code(cutscene_resource: CutsceneResource) -> String: - var code = "" - - # This is a simplified approach - a real implementation would need to - # properly traverse the graph and handle parallel groups - - # Find entry node and traverse from there - var entry_node = _find_entry_node_in_resource(cutscene_resource) - if entry_node.is_empty(): - return code - - # For demonstration, we'll just generate code for each node in order - # A real implementation would need proper graph traversal - - for node_data in cutscene_resource.nodes: - if node_data["type"] == "entry" or node_data["type"] == "exit": - continue - - var action_code = _generate_action_code(node_data) - if not action_code.is_empty(): - code += " " + action_code + "\n" - - return code - -# Find entry node in resource -func _find_entry_node_in_resource(cutscene_resource: CutsceneResource) -> Dictionary: - for node_data in cutscene_resource.nodes: - if node_data["type"] == "entry": - return node_data - return {} - -# Generate code for a single action -func _generate_action_code(node_data: Dictionary) -> String: - var parameters = node_data["parameters"] - - match node_data["type"]: - "move": - var character = parameters.get("character", "character1") - var x = parameters.get("target_x", 0.0) - var y = parameters.get("target_y", 0.0) - var speed = parameters.get("speed", 100.0) - return "cutscene_manager.add_action(MoveAction.new(%s, Vector2(%f, %f), %f))" % [character, x, y, speed] - - "turn": - var character = parameters.get("character", "character1") - var target = parameters.get("target", "character2") - var speed = parameters.get("turn_speed", 2.0) - return "cutscene_manager.add_action(TurnAction.new(%s, %s, %f))" % [character, target, speed] - - "dialogue": - var character = parameters.get("character", "character1") - var text = parameters.get("text", "") - var duration = parameters.get("duration", 0.0) - # Escape quotes in text - var escaped_text = text.replace("\"", "\\\"") - return "cutscene_manager.add_action(DialogueAction.new(%s, \"%s\", %f))" % [character, escaped_text, duration] - - "animation": - var character = parameters.get("character", "character1") - var anim_name = parameters.get("animation_name", "") - var loop = "true" if parameters.get("loop", false) else "false" - return "cutscene_manager.add_action(AnimationAction.new(%s, \"%s\", %s))" % [character, anim_name, loop] - - "wait": - var duration = parameters.get("duration", 1.0) - return "cutscene_manager.add_action(WaitAction.new(%f))" % duration - - _: - return "" - -# Export to different formats -func export_to_format(cutscene_resource: CutsceneResource, format: String) -> String: - match format: - "gdscript": - return generate_gdscript_code(cutscene_resource) - "json": - return _export_to_json(cutscene_resource) - _: - return "" - -# Export to JSON format -func _export_to_json(cutscene_resource: CutsceneResource) -> String: - # Convert the resource to JSON - var json = JSON.new() - return json.stringify(cutscene_resource, " ") diff --git a/addons/cutscene_editor/editor/CutsceneGraphEdit.gd b/addons/cutscene_editor/editor/CutsceneGraphEdit.gd index 637b800..6f69a72 100644 --- a/addons/cutscene_editor/editor/CutsceneGraphEdit.gd +++ b/addons/cutscene_editor/editor/CutsceneGraphEdit.gd @@ -289,7 +289,7 @@ func _on_graph_node_deleted(node: BaseGraphNode) -> void: # Remove all connections to/from this node var connections = get_connection_list() for connection in connections: - if connection["from_node"] == node.name or connection["to_node"] == node.name: + if connection["from_node"] == node.node_id or connection["to_node"] == node.node_id: disconnect_node(connection["from_node"], connection["from_port"], connection["to_node"], connection["to_port"]) @@ -342,16 +342,16 @@ func load_from_cutscene(cutscene: CutsceneResource) -> void: # Create nodes from cutscene data for node_data in cutscene.nodes: - var node = add_node(node_data["type"], Vector2(node_data["x"], node_data["y"])) + var node = add_node(node_data["type"], Vector2(node_data["position"]["x"], node_data["position"]["y"])) if node: - node.name = node_data["name"] + node.node_id = node_data["id"] # Set node parameters for param_name in node_data["parameters"]: node.set_parameter(param_name, node_data["parameters"][param_name]) # Create connections from cutscene data for connection_data in cutscene.connections: - connect_node(connection_data["from_node"], connection_data["from_port"], + connect_node(connection_data["from_node"], connection_data["from_port"], connection_data["to_node"], connection_data["to_port"]) # Emit signal @@ -370,10 +370,12 @@ func save_to_cutscene() -> CutsceneResource: for child in get_children(): if child is BaseGraphNode: var node_data = { - "name": child.name, + "id": child.node_id, "type": child.node_type, - "x": child.position_offset.x, - "y": child.position_offset.y, + "position": { + "x": child.position_offset.x, + "y": child.position_offset.y + }, "parameters": child.action_parameters } current_cutscene.nodes.append(node_data) @@ -381,6 +383,7 @@ func save_to_cutscene() -> CutsceneResource: # Save connections for connection in get_connection_list(): var connection_data = { + "id": _generate_unique_connection_id(), "from_node": connection["from_node"], "from_port": connection["from_port"], "to_node": connection["to_node"], @@ -390,6 +393,10 @@ func save_to_cutscene() -> CutsceneResource: return current_cutscene +# Generate a unique ID for connections +func _generate_unique_connection_id() -> String: + return "conn_" + str(Time.get_ticks_msec()) + # Set up preview system func setup_preview() -> void: diff --git a/addons/cutscene_editor/editor/resources/CutsceneResource.gd b/addons/cutscene_editor/editor/resources/CutsceneResource.gd index 8486baf..daf560c 100644 --- a/addons/cutscene_editor/editor/resources/CutsceneResource.gd +++ b/addons/cutscene_editor/editor/resources/CutsceneResource.gd @@ -7,30 +7,31 @@ extends Resource # Properties @export var nodes: Array = [] # List of node data @export var connections: Array = [] # List of connection data -@export var parallel_connections: Array = [] # Logical connections for parallel groups @export var metadata: Dictionary = {} # Additional metadata # Initialize the resource func _init() -> void: nodes = [] connections = [] - parallel_connections = [] metadata = { - "version": "1.0", + "version": "2.0", "created": Time.get_unix_time_from_system(), "modified": Time.get_unix_time_from_system() } # Add a node to the cutscene func add_node(node_data: Dictionary) -> void: + # Generate unique ID if not provided + if not node_data.has("id") or node_data["id"] == "": + node_data["id"] = _generate_unique_id() nodes.append(node_data) metadata["modified"] = Time.get_unix_time_from_system() # Remove a node from the cutscene -func remove_node(node_name: String) -> void: +func remove_node(node_id: String) -> void: # Remove the node for i in range(nodes.size()): - if nodes[i].has("name") and nodes[i]["name"] == node_name: + if nodes[i].has("id") and nodes[i]["id"] == node_id: nodes.remove_at(i) break @@ -38,58 +39,36 @@ func remove_node(node_name: String) -> void: var i = 0 while i < connections.size(): var conn = connections[i] - if conn["from_node"] == node_name or conn["to_node"] == node_name: + if conn["from_node"] == node_id or conn["to_node"] == node_id: connections.remove_at(i) else: i += 1 - # Remove any parallel connections to/from this node - i = 0 - while i < parallel_connections.size(): - var conn = parallel_connections[i] - if conn["from_node"] == node_name or conn["to_node"] == node_name: - parallel_connections.remove_at(i) - else: - i += 1 - metadata["modified"] = Time.get_unix_time_from_system() # Add a connection to the cutscene func add_connection(connection_data: Dictionary) -> void: + # Generate unique ID if not provided + if not connection_data.has("id") or connection_data["id"] == "": + connection_data["id"] = _generate_unique_id() connections.append(connection_data) metadata["modified"] = Time.get_unix_time_from_system() # Remove a connection from the cutscene -func remove_connection(from_node: String, from_port: int, to_node: String, to_port: int) -> void: +func remove_connection(connection_id: String) -> void: for i in range(connections.size()): var conn = connections[i] - if (conn["from_node"] == from_node and conn["from_port"] == from_port and - conn["to_node"] == to_node and conn["to_port"] == to_port): + if conn["id"] == connection_id: connections.remove_at(i) break metadata["modified"] = Time.get_unix_time_from_system() -# Add a parallel connection to the cutscene -func add_parallel_connection(connection_data: Dictionary) -> void: - parallel_connections.append(connection_data) - metadata["modified"] = Time.get_unix_time_from_system() -# Remove a parallel connection from the cutscene -func remove_parallel_connection(from_node: String, from_port: int, to_node: String, to_port: int) -> void: - for i in range(parallel_connections.size()): - var conn = parallel_connections[i] - if (conn["from_node"] == from_node and conn["from_port"] == from_port and - conn["to_node"] == to_node and conn["to_port"] == to_port): - parallel_connections.remove_at(i) - break - - metadata["modified"] = Time.get_unix_time_from_system() - -# Get node by name -func get_node_by_name(node_name: String) -> Dictionary: +# Get node by id +func get_node_by_id(node_id: String) -> Dictionary: for node in nodes: - if node.has("name") and node["name"] == node_name: + if node.has("id") and node["id"] == node_id: return node return {} @@ -101,21 +80,25 @@ func get_connections_for_node(node_name: String) -> Array: node_connections.append(conn) return node_connections -# Get all parallel connections for a node -func get_parallel_connections_for_node(node_name: String) -> Array: - var node_connections = [] - for conn in parallel_connections: - if conn["from_node"] == node_name or conn["to_node"] == node_name: - node_connections.append(conn) - return node_connections + +# Validate that all referenced nodes exist in the resource +func validate() -> bool: + # Check if all nodes referenced in connections exist + for conn in connections: + if not get_node_by_id(conn["from_node"]) or not get_node_by_id(conn["to_node"]): + return false + return true # Clear all data func clear() -> void: nodes.clear() connections.clear() - parallel_connections.clear() metadata["modified"] = Time.get_unix_time_from_system() # Update metadata func update_metadata() -> void: metadata["modified"] = Time.get_unix_time_from_system() + +# Generate a unique ID for nodes and connections +func _generate_unique_id() -> String: + return "id_" + str(Time.get_ticks_msec()) diff --git a/addons/cutscene_editor/examples/example_cutscene.tscn b/addons/cutscene_editor/examples/example_cutscene.tscn deleted file mode 100644 index d38d25f..0000000 --- a/addons/cutscene_editor/examples/example_cutscene.tscn +++ /dev/null @@ -1,18 +0,0 @@ -[gd_scene load_steps=2 format=3 uid="uid://example_cutscene"] - -[ext_resource type="Script" path="res://addons/cutscene_editor/examples/example_cutscene.gd" id="1"] - -[node name="ExampleCutscene" 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="Polygon2D2" type="Polygon2D" parent="Character2"] -polygon = PackedVector2Array(10, -25, -43, 1, -14, 40, 48, 13) \ No newline at end of file diff --git a/addons/cutscene_editor/tests/test_cutscene_resource.gd b/addons/cutscene_editor/tests/test_cutscene_resource.gd new file mode 100644 index 0000000..ec77c7d --- /dev/null +++ b/addons/cutscene_editor/tests/test_cutscene_resource.gd @@ -0,0 +1,195 @@ +extends Node + +# Unit tests for CutsceneResource + +func _ready() -> void: + test_add_node() + test_remove_node() + test_add_connection() + test_remove_connection() + test_get_node_by_id() + test_get_connections_for_node() + test_validate() + print("All tests passed!") + +func test_add_node() -> void: + var resource = CutsceneResource.new() + var node_data = { + "id": "test_node_1", + "type": "move", + "position": {"x": 100, "y": 150}, + "parameters": { + "character": "player", + "target_x": 200, + "target_y": 300, + "speed": 100 + } + } + + resource.add_node(node_data) + assert(resource.nodes.size() == 1, "Node should be added to resource") + assert(resource.nodes[0]["id"] == "test_node_1", "Node ID should match") + print("test_add_node passed") + +func test_remove_node() -> void: + var resource = CutsceneResource.new() + var node_data = { + "id": "test_node_1", + "type": "move", + "position": {"x": 100, "y": 150}, + "parameters": { + "character": "player", + "target_x": 200, + "target_y": 300, + "speed": 100 + } + } + + resource.add_node(node_data) + assert(resource.nodes.size() == 1, "Node should be added to resource") + + resource.remove_node("test_node_1") + assert(resource.nodes.size() == 0, "Node should be removed from resource") + print("test_remove_node passed") + +func test_add_connection() -> void: + var resource = CutsceneResource.new() + var connection_data = { + "id": "test_conn_1", + "from_node": "node_1", + "from_port": 0, + "to_node": "node_2", + "to_port": 0 + } + + resource.add_connection(connection_data) + assert(resource.connections.size() == 1, "Connection should be added to resource") + assert(resource.connections[0]["id"] == "test_conn_1", "Connection ID should match") + print("test_add_connection passed") + +func test_remove_connection() -> void: + var resource = CutsceneResource.new() + var connection_data = { + "id": "test_conn_1", + "from_node": "node_1", + "from_port": 0, + "to_node": "node_2", + "to_port": 0 + } + + resource.add_connection(connection_data) + assert(resource.connections.size() == 1, "Connection should be added to resource") + + resource.remove_connection("test_conn_1") + assert(resource.connections.size() == 0, "Connection should be removed from resource") + print("test_remove_connection passed") + +func test_get_node_by_id() -> void: + var resource = CutsceneResource.new() + var node_data = { + "id": "test_node_1", + "type": "move", + "position": {"x": 100, "y": 150}, + "parameters": { + "character": "player", + "target_x": 200, + "target_y": 300, + "speed": 100 + } + } + + resource.add_node(node_data) + var retrieved_node = resource.get_node_by_id("test_node_1") + assert(not retrieved_node.is_empty(), "Node should be found by ID") + assert(retrieved_node["id"] == "test_node_1", "Retrieved node ID should match") + print("test_get_node_by_id passed") + +func test_get_connections_for_node() -> void: + var resource = CutsceneResource.new() + var node1_data = { + "id": "node_1", + "type": "entry", + "position": {"x": 100, "y": 100}, + "parameters": {} + } + + var node2_data = { + "id": "node_2", + "type": "move", + "position": {"x": 200, "y": 100}, + "parameters": { + "character": "player", + "target_x": 300, + "target_y": 200, + "speed": 100 + } + } + + var connection_data = { + "id": "conn_1", + "from_node": "node_1", + "from_port": 0, + "to_node": "node_2", + "to_port": 0 + } + + resource.add_node(node1_data) + resource.add_node(node2_data) + resource.add_connection(connection_data) + + var connections = resource.get_connections_for_node("node_1") + assert(connections.size() == 1, "Should find one connection for node_1") + assert(connections[0]["from_node"] == "node_1", "Connection should be from node_1") + + connections = resource.get_connections_for_node("node_2") + assert(connections.size() == 1, "Should find one connection for node_2") + assert(connections[0]["to_node"] == "node_2", "Connection should be to node_2") + print("test_get_connections_for_node passed") + +func test_validate() -> void: + var resource = CutsceneResource.new() + var node1_data = { + "id": "node_1", + "type": "entry", + "position": {"x": 100, "y": 100}, + "parameters": {} + } + + var node2_data = { + "id": "node_2", + "type": "move", + "position": {"x": 200, "y": 100}, + "parameters": { + "character": "player", + "target_x": 300, + "target_y": 200, + "speed": 100 + } + } + + var valid_connection = { + "id": "conn_1", + "from_node": "node_1", + "from_port": 0, + "to_node": "node_2", + "to_port": 0 + } + + var invalid_connection = { + "id": "conn_2", + "from_node": "node_1", + "from_port": 0, + "to_node": "nonexistent_node", + "to_port": 0 + } + + # Test with valid connections + resource.add_node(node1_data) + resource.add_node(node2_data) + resource.add_connection(valid_connection) + assert(resource.validate(), "Resource with valid connections should validate") + + # Test with invalid connections + resource.add_connection(invalid_connection) + assert(not resource.validate(), "Resource with invalid connections should not validate") + print("test_validate passed") \ No newline at end of file diff --git a/addons/cutscene_editor/tests/test_cutscene_resource.tscn b/addons/cutscene_editor/tests/test_cutscene_resource.tscn new file mode 100644 index 0000000..07d7d24 --- /dev/null +++ b/addons/cutscene_editor/tests/test_cutscene_resource.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=3 format=3 uid="uid://testcutsceneresourcetests"] + +[ext_resource type="Script" path="test_cutscene_resource.gd" id="1"] + +[node name="Node2D" type="Node2D"] +script = ExtResource("1") \ No newline at end of file diff --git a/cutscene/CutsceneManager.gd b/cutscene/CutsceneManager.gd index 89e9fd1..549f7d6 100644 --- a/cutscene/CutsceneManager.gd +++ b/cutscene/CutsceneManager.gd @@ -76,25 +76,6 @@ func add_action(action: Action) -> void: 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 @@ -120,7 +101,7 @@ func _execute_next_action() -> void: var action_item = sequential_actions[action_index] action_index += 1 - + print(action_item) # 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 diff --git a/cutscene/README.md b/cutscene/README.md index e3ec8a5..dd46339 100644 --- a/cutscene/README.md +++ b/cutscene/README.md @@ -1,7 +1,7 @@ # 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. +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. The system can be used directly with code or with the visual Cutscene Editor plugin that manages cutscene resources. ## Features - Sequential action execution @@ -9,6 +9,7 @@ This is a flexible cutscene system for 2D point-and-click adventure games in God - Extensible action system - Signal-based completion system - Error handling and recovery +- Resource-based cutscene management (with Cutscene Editor plugin) ## System Components @@ -25,7 +26,7 @@ This is a flexible cutscene system for 2D point-and-click adventure games in God ## Usage -### Basic Setup +### Basic Setup (Code-based) ```gdscript # Create a cutscene manager var cutscene_manager = CutsceneManager.new() @@ -39,7 +40,7 @@ cutscene_manager.add_action(DialogueAction.new(character, "Hello, world!")) cutscene_manager.start() ``` -### Parallel Actions +### Parallel Actions (Code-based) ```gdscript # Create parallel actions var parallel_actions = [ @@ -49,6 +50,20 @@ var parallel_actions = [ cutscene_manager.add_parallel_actions(parallel_actions) ``` +### Resource-based Setup (With Cutscene Editor) +```gdscript +# Load a cutscene resource created with the visual editor +var cutscene_resource = preload("res://path/to/your/cutscene.tscn") + +# Generate actions from the resource +var generator = CutsceneGenerator.new() +var cutscene_manager = generator.generate_cutscene(cutscene_resource) + +# Add to scene and start +add_child(cutscene_manager) +cutscene_manager.start() +``` + ### The Example Scenario The system supports complex sequences like the one described in the requirements: diff --git a/cutscene/actions/MoveAction.gd b/cutscene/actions/MoveAction.gd index b3873b9..5c12a23 100644 --- a/cutscene/actions/MoveAction.gd +++ b/cutscene/actions/MoveAction.gd @@ -19,6 +19,7 @@ func _init(character_node: Node2D, target: Vector2, move_speed: float = 100.0) - name = "MoveAction" func start() -> void: + print("started", character) if character == null: self._set_failed("Character is null") return @@ -29,6 +30,7 @@ func start() -> void: self._set_running() func update(delta: float) -> void: + print ("updating") if state != State.RUNNING: return diff --git a/cutscene/examples/animated_character.tscn b/cutscene/examples/animated_character.tscn deleted file mode 100644 index 7d0f4f1..0000000 --- a/cutscene/examples/animated_character.tscn +++ /dev/null @@ -1,10 +0,0 @@ -[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 deleted file mode 100644 index 154bcd1..0000000 --- a/cutscene/examples/sample_cutscene.gd +++ /dev/null @@ -1,80 +0,0 @@ -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.tres b/cutscene/examples/sample_cutscene.tres deleted file mode 100644 index 911da3a..0000000 --- a/cutscene/examples/sample_cutscene.tres +++ /dev/null @@ -1,16 +0,0 @@ -[gd_resource type="VisualShader" load_steps=2 format=3 uid="uid://bqjt0um0vbxs7"] - -[sub_resource type="VisualShaderNodeDerivativeFunc" id="VisualShaderNodeDerivativeFunc_n7wl4"] - -[resource] -code = "shader_type canvas_item; -render_mode blend_mix; - - - -" -mode = 1 -flags/light_only = false -nodes/fragment/0/position = Vector2(700, 140) -nodes/fragment/2/node = SubResource("VisualShaderNodeDerivativeFunc_n7wl4") -nodes/fragment/2/position = Vector2(196, 138) diff --git a/cutscene/examples/sample_cutscene.tscn b/cutscene/examples/sample_cutscene.tscn deleted file mode 100644 index db72c34..0000000 --- a/cutscene/examples/sample_cutscene.tscn +++ /dev/null @@ -1,18 +0,0 @@ -[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 deleted file mode 100644 index b504ec9..0000000 --- a/cutscene/tests/test_cutscene_system.gd +++ /dev/null @@ -1,95 +0,0 @@ -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/addons/cutscene_editor/examples/example_cutscene.gd b/example_cutscene.tscn similarity index 55% rename from addons/cutscene_editor/examples/example_cutscene.gd rename to example_cutscene.tscn index 4dcb515..513a2bd 100644 --- a/addons/cutscene_editor/examples/example_cutscene.gd +++ b/example_cutscene.tscn @@ -1,5 +1,7 @@ -@tool -extends Node2D +[gd_scene load_steps=2 format=3 uid="uid://bi4gojo71c7jt"] + +[sub_resource type="GDScript" id="GDScript_e7tbc"] +script/source = "extends Node2D # Example cutscene using the cutscene editor plugin @@ -18,7 +20,7 @@ func _ready() -> void: var start_timer = Timer.new() start_timer.wait_time = 1.0 start_timer.one_shot = true - start_timer.connect("timeout", start_cutscene) + start_timer.connect(\"timeout\", start_cutscene) add_child(start_timer) start_timer.start() @@ -28,10 +30,10 @@ func setup_cutscene() -> void: 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) + 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 @@ -47,7 +49,7 @@ func setup_cutscene() -> void: cutscene_manager.add_action(turn_action) # 4. Character2 says dialogue - var dialogue_action = DialogueAction.new(character2, "Hello there, friend!", 2.0) + 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) @@ -55,29 +57,46 @@ func setup_cutscene() -> void: cutscene_manager.add_action(animation_action) # Add a final dialogue from character1 - var final_dialogue = DialogueAction.new(character1, "That was surprising!", 2.0) + var final_dialogue = DialogueAction.new(character1, \"That was surprising!\", 2.0) cutscene_manager.add_action(final_dialogue) func start_cutscene() -> void: - print("Starting cutscene...") + print(\"Starting cutscene...\") cutscene_manager.start() func _on_cutscene_started() -> void: - print("Cutscene started!") + print(\"Cutscene started!\") func _on_cutscene_completed() -> void: - print("Cutscene completed!") - print("Final positions:") - print("Character1: %s" % character1.position) - print("Character2: %s" % character2.position) + 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) + print(\"Action started: %s\" % action.name) func _on_action_completed(action: Action) -> void: - print("Action completed: %s" % action.name) + print(\"Action completed: %s\" % action.name) # Clean up when the node is removed func _exit_tree() -> void: if cutscene_manager: - cutscene_manager.queue_free() \ No newline at end of file + cutscene_manager.queue_free() +" + +[node name="ExampleCutscene" type="Node2D"] +script = SubResource("GDScript_e7tbc") + +[node name="Character1" type="Node2D" parent="."] +position = Vector2(234, 591) + +[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(912, 235) +rotation = 2.60138 + +[node name="Polygon2D2" type="Polygon2D" parent="Character2"] +polygon = PackedVector2Array(10, -25, -43, 1, -14, 40, 48, 13) diff --git a/hello.tres b/hello.tres new file mode 100644 index 0000000..bfffb2e --- /dev/null +++ b/hello.tres @@ -0,0 +1,34 @@ +[gd_resource type="Resource" script_class="CutsceneResource" load_steps=2 format=3 uid="uid://ci8lt4jyxcysx"] + +[ext_resource type="Script" path="res://addons/cutscene_editor/editor/resources/CutsceneResource.gd" id="1_as4bh"] + +[resource] +script = ExtResource("1_as4bh") +nodes = [{ +"id": "entry_1227949280", +"parameters": {}, +"position": { +"x": 300.0, +"y": 160.0 +}, +"type": "entry" +}, { +"id": "move_3555857962", +"parameters": { +"character": "", +"speed": 100.0, +"target_x": 0.0, +"target_y": 0.0 +}, +"position": { +"x": 763.0, +"y": 168.0 +}, +"type": "move" +}] +connections = [] +metadata = { +"created": 1.75399e+09, +"modified": 1.75399e+09, +"version": "2.0" +} diff --git a/main.tscn b/main.tscn index 8d5f1b5..16f0b7a 100644 --- a/main.tscn +++ b/main.tscn @@ -1,3 +1,3 @@ -[gd_scene format=3 uid="uid://ovmd1xwe60gm"] +[gd_scene format=3 uid="uid://demmcscpj7vjy"] [node name="Node2D" type="Node2D"] diff --git a/project.godot b/project.godot index 8f2650c..011324c 100644 --- a/project.godot +++ b/project.godot @@ -11,7 +11,7 @@ config_version=5 [application] config/name="adventure-ai" -run/main_scene="res://cutscene/examples/sample_cutscene.tscn" +run/main_scene="res://main.tscn" config/features=PackedStringArray("4.3", "Forward Plus") config/icon="res://icon.svg" diff --git a/test_resource_cutscene.gd b/test_resource_cutscene.gd new file mode 100644 index 0000000..ba09117 --- /dev/null +++ b/test_resource_cutscene.gd @@ -0,0 +1,234 @@ +extends Node2D + +# Test script for the new resource-based cutscene system + +# 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 a cutscene resource with the same sequence as the original example + var cutscene_resource = create_test_cutscene_resource() + + # Generate actions from the resource + var generator = CutsceneGenerator.new() + cutscene_manager = generator.generate_cutscene(cutscene_resource) + + # Add the cutscene manager to the scene + 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) + +func create_test_cutscene_resource() -> CutsceneResource: + # Create a new cutscene resource + var resource = CutsceneResource.new() + + # Create nodes for the cutscene + var entry_node = { + "id": "entry_1", + "type": "entry", + "position": {"x": 100, "y": 100}, + "parameters": {} + } + + var move1_node = { + "id": "move_1", + "type": "move", + "position": {"x": 250, "y": 100}, + "parameters": { + "character": "Character1", + "target_x": 234, + "target_y": 591, + "speed": 100.0 + } + } + + var move2_node = { + "id": "move_2", + "type": "move", + "position": {"x": 250, "y": 150}, + "parameters": { + "character": "character2", + "target_x": 912, + "target_y": 235, + "speed": 100.0 + } + } + + var turn_node = { + "id": "turn_1", + "type": "turn", + "position": {"x": 400, "y": 100}, + "parameters": { + "character": "character2", + "target": "character1", + "turn_speed": 1.0 + } + } + + var dialogue1_node = { + "id": "dialogue_1", + "type": "dialogue", + "position": {"x": 550, "y": 100}, + "parameters": { + "character": "character2", + "text": "Hello there, friend!", + "duration": 2.0 + } + } + + var wait_node = { + "id": "wait_1", + "type": "wait", + "position": {"x": 700, "y": 100}, + "parameters": { + "duration": 1.0 + } + } + + var dialogue2_node = { + "id": "dialogue_2", + "type": "dialogue", + "position": {"x": 850, "y": 100}, + "parameters": { + "character": "character1", + "text": "That was surprising!", + "duration": 2.0 + } + } + + var exit_node = { + "id": "exit_1", + "type": "exit", + "position": {"x": 1000, "y": 100}, + "parameters": {} + } + + # Add nodes to resource + resource.add_node(entry_node) + resource.add_node(move1_node) + resource.add_node(move2_node) + resource.add_node(turn_node) + resource.add_node(dialogue1_node) + resource.add_node(wait_node) + resource.add_node(dialogue2_node) + resource.add_node(exit_node) + + # Create connections + var connections = [ + { + "id": "conn_1", + "from_node": "entry_1", + "from_port": 0, + "to_node": "move_1", + "to_port": 0 + }, + { + "id": "conn_2", + "from_node": "entry_1", + "from_port": 0, + "to_node": "move_2", + "to_port": 0 + }, + { + "id": "conn_3", + "from_node": "move_1", + "from_port": 0, + "to_node": "turn_node", + "to_port": 0 + }, + { + "id": "conn_4", + "from_node": "move_2", + "from_port": 0, + "to_node": "turn_node", + "to_port": 0 + }, + { + "id": "conn_5", + "from_node": "turn_node", + "from_port": 0, + "to_node": "dialogue_1", + "to_port": 0 + }, + { + "id": "conn_6", + "from_node": "dialogue_1", + "from_port": 0, + "to_node": "wait_node", + "to_port": 0 + }, + { + "id": "conn_7", + "from_node": "wait_node", + "from_port": 0, + "to_node": "dialogue_2", + "to_port": 0 + }, + { + "id": "conn_8", + "from_node": "dialogue_2", + "from_port": 0, + "to_node": "exit_1", + "to_port": 0 + } + ] + + # Add connections to resource + for conn in connections: + resource.add_connection(conn) + + return resource + +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/test_resource_cutscene.tscn b/test_resource_cutscene.tscn new file mode 100644 index 0000000..a423584 --- /dev/null +++ b/test_resource_cutscene.tscn @@ -0,0 +1,18 @@ +[gd_scene load_steps=2 format=3 uid="uid://vlh0eb7qn72o"] + +[ext_resource type="Script" path="res://test_resource_cutscene.gd" id="1"] + +[node name="Node2D" type="Node2D"] +script = ExtResource("1") + +[node name="Character1" type="Node2D" parent="."] +position = Vector2(100, 100) + +[node name="Polygon2D" type="Polygon2D" parent="Character1"] +polygon = PackedVector2Array(33, -74, -67, 4, -20, 33, 45, 2) + +[node name="Character2" type="Node2D" parent="."] +position = Vector2(200, 200) + +[node name="Polygon2D" type="Polygon2D" parent="Character2"] +polygon = PackedVector2Array(21, -54, -56, 23, 2, 59, 63, -16, 58, -69)