313 lines
10 KiB
GDScript
313 lines
10 KiB
GDScript
@tool
|
|
class_name CutsceneGenerator
|
|
extends Object
|
|
|
|
# System for generating executable cutscene code from graph data
|
|
|
|
# Properties
|
|
var graph_nodes: Dictionary = {} # Map of node names 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
|
|
|
|
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()
|
|
|
|
# Find entry node
|
|
var entry_node = _find_entry_node()
|
|
if not entry_node:
|
|
printerr("No entry node found in cutscene")
|
|
return cutscene_manager
|
|
|
|
# Build action sequence starting from entry node
|
|
_build_action_sequence(entry_node, cutscene_manager)
|
|
|
|
return cutscene_manager
|
|
|
|
# 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]
|
|
if node_data["type"] == "entry":
|
|
return node_data
|
|
return {}
|
|
|
|
# Build action sequence from graph data
|
|
func _build_action_sequence(start_node: Dictionary, cutscene_manager: CutsceneManager) -> void:
|
|
# 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"])
|
|
|
|
# Add initial connections to queue
|
|
for conn in next_connections:
|
|
node_queue.append(conn["to_node"])
|
|
|
|
# Process nodes in order
|
|
while not node_queue.is_empty():
|
|
var current_node_name = node_queue.pop_front()
|
|
|
|
# Skip if already processed
|
|
if processed_nodes.has(current_node_name):
|
|
continue
|
|
|
|
# Mark as processed
|
|
processed_nodes[current_node_name] = true
|
|
|
|
# Get node data
|
|
var node_data = graph_nodes.get(current_node_name, {})
|
|
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)
|
|
|
|
# Add next nodes to queue
|
|
var outgoing_connections = _get_outgoing_connections(current_node_name)
|
|
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"]
|
|
|
|
match node_data["type"]:
|
|
"move":
|
|
var character_path = parameters.get("character", "")
|
|
var target_x = parameters.get("target_x", 0.0)
|
|
var target_y = parameters.get("target_y", 0.0)
|
|
var speed = parameters.get("speed", 100.0)
|
|
|
|
# In a real implementation, we would resolve the character path to an actual node
|
|
# For now, we'll create a placeholder
|
|
var character_node = null # This would be resolved at runtime
|
|
var target_position = Vector2(target_x, target_y)
|
|
|
|
return MoveAction.new(character_node, target_position, speed)
|
|
|
|
"turn":
|
|
var character_path = parameters.get("character", "")
|
|
var target = parameters.get("target", "")
|
|
var turn_speed = parameters.get("turn_speed", 2.0)
|
|
|
|
# In a real implementation, we would resolve the paths to actual nodes
|
|
var character_node = null # This would be resolved at runtime
|
|
var target_node = null # This would be resolved at runtime
|
|
|
|
return TurnAction.new(character_node, target_node, turn_speed)
|
|
|
|
"dialogue":
|
|
var character_path = parameters.get("character", "")
|
|
var text = parameters.get("text", "")
|
|
var duration = parameters.get("duration", 0.0)
|
|
|
|
var character_node = null # This would be resolved at runtime
|
|
|
|
return DialogueAction.new(character_node, text, duration)
|
|
|
|
"animation":
|
|
var character_path = parameters.get("character", "")
|
|
var animation_name = parameters.get("animation_name", "")
|
|
var loop = parameters.get("loop", false)
|
|
|
|
var character_node = null # This would be resolved at runtime
|
|
|
|
return AnimationAction.new(character_node, animation_name, loop)
|
|
|
|
"wait":
|
|
var duration = parameters.get("duration", 1.0)
|
|
return WaitAction.new(duration)
|
|
|
|
_:
|
|
printerr("Unknown node type: %s" % node_data["type"])
|
|
return null
|
|
|
|
# Get outgoing connections from a node
|
|
func _get_outgoing_connections(node_name: String) -> Array:
|
|
var outgoing = []
|
|
for conn in connections:
|
|
if conn["from_node"] == node_name:
|
|
outgoing.append(conn)
|
|
return outgoing
|
|
|
|
# Get incoming connections to a node
|
|
func _get_incoming_connections(node_name: String) -> Array:
|
|
var incoming = []
|
|
for conn in connections:
|
|
if conn["to_node"] == node_name:
|
|
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, " ")
|