progress
0
.gitattributes
vendored
Normal file → Executable file
0
.gitignore
vendored
Normal file → Executable file
0
addons/cutscene_editor/CHANGES.md
Normal file → Executable file
17
addons/cutscene_editor/CutsceneEditorPlugin.gd
Normal file → Executable file
@@ -59,9 +59,6 @@ func _create_dock_panel() -> Control:
|
||||
graph_edit = _create_graph_edit()
|
||||
main_vbox.add_child(graph_edit)
|
||||
|
||||
# Set up preview system
|
||||
_setup_preview_system()
|
||||
|
||||
# Set up undo/redo system
|
||||
_setup_undo_redo_system()
|
||||
|
||||
@@ -114,12 +111,6 @@ func _create_toolbar() -> Control:
|
||||
var separator2 = VSeparator.new()
|
||||
toolbar.add_child(separator2)
|
||||
|
||||
# Preview button
|
||||
var preview_button = Button.new()
|
||||
preview_button.text = "Preview"
|
||||
preview_button.icon = get_editor_interface().get_base_control().get_theme_icon("Play", "EditorIcons")
|
||||
preview_button.connect("pressed", _on_preview_pressed)
|
||||
toolbar.add_child(preview_button)
|
||||
|
||||
return toolbar
|
||||
|
||||
@@ -149,10 +140,6 @@ func _create_graph_edit() -> GraphEdit:
|
||||
|
||||
return graph
|
||||
|
||||
func _setup_preview_system() -> void:
|
||||
# Set up the preview system
|
||||
if graph_edit:
|
||||
graph_edit.setup_preview()
|
||||
|
||||
func _setup_undo_redo_system() -> void:
|
||||
# Set up the undo/redo system
|
||||
@@ -209,7 +196,3 @@ func _on_undo_pressed() -> void:
|
||||
func _on_redo_pressed() -> void:
|
||||
if graph_edit:
|
||||
graph_edit.redo()
|
||||
|
||||
func _on_preview_pressed() -> void:
|
||||
if graph_edit:
|
||||
graph_edit.start_preview()
|
||||
|
||||
0
addons/cutscene_editor/README.md
Normal file → Executable file
63
addons/cutscene_editor/editor/CutsceneGenerator.gd
Normal file → Executable file
@@ -9,7 +9,7 @@ var graph_nodes: Dictionary = {} # Map of node IDs to node data
|
||||
var connections: Array = [] # List of connection data
|
||||
|
||||
# Generate a cutscene from graph data
|
||||
func generate_cutscene(cutscene_resource: CutsceneResource) -> CutsceneManager:
|
||||
func generate_cutscene(scene: Node, cutscene_resource: CutsceneResource) -> CutsceneManager:
|
||||
# Clear previous data
|
||||
graph_nodes.clear()
|
||||
connections.clear()
|
||||
@@ -30,7 +30,7 @@ func generate_cutscene(cutscene_resource: CutsceneResource) -> CutsceneManager:
|
||||
return cutscene_manager
|
||||
|
||||
# Build action sequence starting from entry node
|
||||
_build_action_sequence(entry_node, cutscene_manager)
|
||||
_build_action_sequence(scene, entry_node, cutscene_manager)
|
||||
|
||||
return cutscene_manager
|
||||
|
||||
@@ -43,45 +43,42 @@ func _find_entry_node() -> Dictionary:
|
||||
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["id"])
|
||||
func _build_action_sequence(scene: Node, start_node: Dictionary, cutscene_manager: CutsceneManager) -> void:
|
||||
# First pass: Create all actions
|
||||
var action_map = {} # Map of node IDs to action instances
|
||||
|
||||
# Add initial connections to queue
|
||||
for conn in next_connections:
|
||||
node_queue.append(conn["to_node"])
|
||||
for node_id in graph_nodes:
|
||||
var node_data = graph_nodes[node_id]
|
||||
|
||||
# Process nodes in order
|
||||
while not node_queue.is_empty():
|
||||
var current_node_id = node_queue.pop_front()
|
||||
|
||||
# Skip if already processed
|
||||
if processed_nodes.has(current_node_id):
|
||||
continue
|
||||
|
||||
# Mark as processed
|
||||
processed_nodes[current_node_id] = true
|
||||
|
||||
# Get node data
|
||||
var node_data = graph_nodes.get(current_node_id, {})
|
||||
if node_data.is_empty():
|
||||
# Skip entry and exit nodes as they don't create actions
|
||||
if node_data["type"] == "entry" or node_data["type"] == "exit":
|
||||
continue
|
||||
|
||||
# Create action for regular nodes
|
||||
var action = _create_action_from_node(node_data)
|
||||
var action = _create_action_from_node(scene, node_data)
|
||||
if action:
|
||||
cutscene_manager.add_action(action)
|
||||
action_map[node_id] = action
|
||||
|
||||
# Add next nodes to queue
|
||||
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"])
|
||||
# Second pass: Add actions with their dependencies
|
||||
for node_id in action_map:
|
||||
print("setting up", node_id)
|
||||
var action = action_map[node_id]
|
||||
|
||||
# Get dependencies (incoming connections)
|
||||
var deps = []
|
||||
var incoming_connections = _get_incoming_connections(node_id)
|
||||
|
||||
for conn in incoming_connections:
|
||||
# Only add as dependency if the source node creates an action
|
||||
var from_node_data = graph_nodes.get(conn["from_node"], {})
|
||||
if not from_node_data.is_empty() and from_node_data["type"] != "entry":
|
||||
deps.append(conn["from_node"])
|
||||
|
||||
# Add action with dependencies
|
||||
cutscene_manager.add_action(node_id, action, deps)
|
||||
|
||||
# Create an action instance from node data
|
||||
func _create_action_from_node(node_data: Dictionary):
|
||||
func _create_action_from_node(scene: Node, node_data: Dictionary):
|
||||
var parameters = node_data["parameters"]
|
||||
|
||||
match node_data["type"]:
|
||||
@@ -93,7 +90,7 @@ func _create_action_from_node(node_data: Dictionary):
|
||||
|
||||
# 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 character_node = scene.find_child(character_path) # This would be resolved at runtime
|
||||
var target_position = Vector2(target_x, target_y)
|
||||
|
||||
return MoveAction.new(character_node, target_position, speed)
|
||||
|
||||
50
addons/cutscene_editor/editor/CutsceneGraphEdit.gd
Normal file → Executable file
@@ -16,9 +16,6 @@ var node_counter: int = 0 # For generating unique node IDs
|
||||
var current_cutscene: CutsceneResource # The cutscene being edited
|
||||
|
||||
# Preview properties
|
||||
var preview_manager: PreviewManager
|
||||
var preview_panel: PreviewPanel
|
||||
|
||||
# Undo/Redo properties
|
||||
var undo_redo_manager: UndoRedoManager
|
||||
|
||||
@@ -289,7 +286,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.node_id or connection["to_node"] == node.node_id:
|
||||
if connection["from_node"] == node.name or connection["to_node"] == node.name:
|
||||
disconnect_node(connection["from_node"], connection["from_port"], connection["to_node"], connection["to_port"])
|
||||
|
||||
|
||||
@@ -332,6 +329,9 @@ func clear_graph() -> void:
|
||||
# Emit signal
|
||||
emit_signal("graph_changed")
|
||||
|
||||
func get_graph_node_by_id(id: String):
|
||||
return find_child(id)
|
||||
|
||||
# Load graph from cutscene resource
|
||||
func load_from_cutscene(cutscene: CutsceneResource) -> void:
|
||||
# Clear existing graph
|
||||
@@ -343,14 +343,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["position"]["x"], node_data["position"]["y"]))
|
||||
node.owner=self
|
||||
if node:
|
||||
node.node_id = node_data["id"]
|
||||
node.name = 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:
|
||||
print(get_graph_node_by_id(connection_data["from_node"]))
|
||||
connect_node(connection_data["from_node"], connection_data["from_port"],
|
||||
connection_data["to_node"], connection_data["to_port"])
|
||||
|
||||
@@ -370,7 +372,7 @@ func save_to_cutscene() -> CutsceneResource:
|
||||
for child in get_children():
|
||||
if child is BaseGraphNode:
|
||||
var node_data = {
|
||||
"id": child.node_id,
|
||||
"id": str(child.name),
|
||||
"type": child.node_type,
|
||||
"position": {
|
||||
"x": child.position_offset.x,
|
||||
@@ -384,9 +386,9 @@ func save_to_cutscene() -> CutsceneResource:
|
||||
for connection in get_connection_list():
|
||||
var connection_data = {
|
||||
"id": _generate_unique_connection_id(),
|
||||
"from_node": connection["from_node"],
|
||||
"from_node": str(connection["from_node"]),
|
||||
"from_port": connection["from_port"],
|
||||
"to_node": connection["to_node"],
|
||||
"to_node": str(connection["to_node"]),
|
||||
"to_port": connection["to_port"]
|
||||
}
|
||||
current_cutscene.connections.append(connection_data)
|
||||
@@ -398,39 +400,7 @@ func _generate_unique_connection_id() -> String:
|
||||
return "conn_" + str(Time.get_ticks_msec())
|
||||
|
||||
|
||||
# Set up preview system
|
||||
func setup_preview() -> void:
|
||||
# Create preview manager
|
||||
preview_manager = PreviewManager.new()
|
||||
add_child(preview_manager)
|
||||
|
||||
# Create preview panel
|
||||
preview_panel = PreviewPanel.new()
|
||||
preview_panel.set_preview_manager(preview_manager)
|
||||
preview_panel.set_graph_edit(self)
|
||||
|
||||
# Add to editor interface (this would be added to the editor UI)
|
||||
# For now, we'll just keep a reference to it
|
||||
|
||||
# Start preview
|
||||
func start_preview() -> void:
|
||||
if preview_manager:
|
||||
preview_manager.start_preview()
|
||||
|
||||
# Stop preview
|
||||
func stop_preview() -> void:
|
||||
if preview_manager:
|
||||
preview_manager.stop_preview()
|
||||
|
||||
# Pause preview
|
||||
func pause_preview() -> void:
|
||||
if preview_manager:
|
||||
preview_manager.pause_preview()
|
||||
|
||||
# Resume preview
|
||||
func resume_preview() -> void:
|
||||
if preview_manager:
|
||||
preview_manager.resume_preview()
|
||||
|
||||
# Set up undo/redo system
|
||||
func _setup_undo_redo() -> void:
|
||||
|
||||
@@ -1,274 +0,0 @@
|
||||
@tool
|
||||
class_name PreviewManager
|
||||
extends Node
|
||||
|
||||
# Manager for real-time cutscene preview
|
||||
|
||||
# Signals
|
||||
signal preview_started()
|
||||
signal preview_stopped()
|
||||
signal preview_paused()
|
||||
signal preview_resumed()
|
||||
signal preview_progress(progress: float)
|
||||
signal node_activated(node_name: String)
|
||||
signal node_completed(node_name: String)
|
||||
|
||||
# Properties
|
||||
var is_previewing: bool = false
|
||||
var is_paused: bool = false
|
||||
var current_cutscene: CutsceneManager
|
||||
var preview_scene: Node2D # The scene being previewed
|
||||
var preview_characters: Dictionary = {} # Map of character names to nodes
|
||||
var graph_edit: CutsceneGraphEdit # Reference to the graph editor
|
||||
|
||||
# Preview settings
|
||||
var preview_speed: float = 1.0
|
||||
var show_debug_info: bool = true
|
||||
|
||||
# Initialize the preview manager
|
||||
func _init() -> void:
|
||||
name = "PreviewManager"
|
||||
|
||||
# Set up the preview with a graph editor
|
||||
func setup_preview(graph: CutsceneGraphEdit) -> void:
|
||||
graph_edit = graph
|
||||
|
||||
# Connect to graph change signals
|
||||
if graph_edit:
|
||||
graph_edit.connect("graph_changed", _on_graph_changed)
|
||||
|
||||
# Start the preview
|
||||
func start_preview() -> void:
|
||||
if is_previewing:
|
||||
return
|
||||
|
||||
# Create preview scene
|
||||
_setup_preview_scene()
|
||||
|
||||
# Generate cutscene from current graph
|
||||
var cutscene_resource = graph_edit.save_to_cutscene()
|
||||
var generator = CutsceneGenerator.new()
|
||||
current_cutscene = generator.generate_cutscene(cutscene_resource)
|
||||
|
||||
# Add cutscene to preview scene
|
||||
if preview_scene and current_cutscene:
|
||||
preview_scene.add_child(current_cutscene)
|
||||
|
||||
# Connect to cutscene signals for feedback
|
||||
_connect_cutscene_signals()
|
||||
|
||||
# Start the cutscene
|
||||
current_cutscene.start()
|
||||
|
||||
# Update state
|
||||
is_previewing = true
|
||||
is_paused = false
|
||||
emit_signal("preview_started")
|
||||
|
||||
# Stop the preview
|
||||
func stop_preview() -> void:
|
||||
if not is_previewing:
|
||||
return
|
||||
|
||||
# Stop the cutscene
|
||||
if current_cutscene:
|
||||
current_cutscene.stop()
|
||||
_disconnect_cutscene_signals()
|
||||
|
||||
# Remove from preview scene
|
||||
if current_cutscene.get_parent() == preview_scene:
|
||||
preview_scene.remove_child(current_cutscene)
|
||||
current_cutscene.queue_free()
|
||||
|
||||
# Clean up preview scene
|
||||
_cleanup_preview_scene()
|
||||
|
||||
# Update state
|
||||
is_previewing = false
|
||||
is_paused = false
|
||||
emit_signal("preview_stopped")
|
||||
|
||||
# Pause the preview
|
||||
func pause_preview() -> void:
|
||||
if not is_previewing or is_paused:
|
||||
return
|
||||
|
||||
if current_cutscene:
|
||||
current_cutscene.pause()
|
||||
is_paused = true
|
||||
emit_signal("preview_paused")
|
||||
|
||||
# Resume the preview
|
||||
func resume_preview() -> void:
|
||||
if not is_previewing or not is_paused:
|
||||
return
|
||||
|
||||
if current_cutscene:
|
||||
current_cutscene.resume()
|
||||
is_paused = false
|
||||
emit_signal("preview_resumed")
|
||||
|
||||
# Set preview speed
|
||||
func set_preview_speed(speed: float) -> void:
|
||||
preview_speed = speed
|
||||
# In a real implementation, this would affect the time scale
|
||||
# of the preview scene
|
||||
|
||||
# Set up the preview scene
|
||||
func _setup_preview_scene() -> void:
|
||||
# Create or reuse preview scene
|
||||
if not preview_scene:
|
||||
preview_scene = Node2D.new()
|
||||
preview_scene.name = "PreviewScene"
|
||||
add_child(preview_scene)
|
||||
|
||||
# Set up characters for preview
|
||||
_setup_preview_characters()
|
||||
|
||||
# Set up characters for preview
|
||||
func _setup_preview_characters() -> void:
|
||||
# Clear existing characters
|
||||
preview_characters.clear()
|
||||
|
||||
# Create placeholder characters for preview
|
||||
# In a real implementation, this would load actual character scenes
|
||||
var character1 = _create_preview_character("Character1", Vector2(100, 100))
|
||||
var character2 = _create_preview_character("Character2", Vector2(200, 100))
|
||||
|
||||
preview_characters["Character1"] = character1
|
||||
preview_characters["Character2"] = character2
|
||||
|
||||
# Add to preview scene
|
||||
if preview_scene:
|
||||
preview_scene.add_child(character1)
|
||||
preview_scene.add_child(character2)
|
||||
|
||||
# Create a preview character
|
||||
func _create_preview_character(name: String, position: Vector2) -> Node2D:
|
||||
var character = Node2D.new()
|
||||
character.name = name
|
||||
character.position = position
|
||||
|
||||
# Add a visual representation
|
||||
var sprite = Polygon2D.new()
|
||||
sprite.polygon = PackedVector2Array([
|
||||
Vector2(-10, -20),
|
||||
Vector2(-30, 0),
|
||||
Vector2(-10, 20),
|
||||
Vector2(10, 20),
|
||||
Vector2(30, 0),
|
||||
Vector2(10, -20)
|
||||
])
|
||||
sprite.color = Color(0.5, 0.7, 1.0)
|
||||
character.add_child(sprite)
|
||||
|
||||
return character
|
||||
|
||||
# Clean up the preview scene
|
||||
func _cleanup_preview_scene() -> void:
|
||||
# Remove characters
|
||||
for char_name in preview_characters:
|
||||
var character = preview_characters[char_name]
|
||||
if character.get_parent() == preview_scene:
|
||||
preview_scene.remove_child(character)
|
||||
character.queue_free()
|
||||
|
||||
preview_characters.clear()
|
||||
|
||||
# Connect to cutscene signals for feedback
|
||||
func _connect_cutscene_signals() -> void:
|
||||
if not current_cutscene:
|
||||
return
|
||||
|
||||
# Disconnect existing connections
|
||||
_disconnect_cutscene_signals()
|
||||
|
||||
# Connect to signals
|
||||
current_cutscene.connect("cutscene_started", _on_cutscene_started)
|
||||
current_cutscene.connect("cutscene_completed", _on_cutscene_completed)
|
||||
current_cutscene.connect("cutscene_paused", _on_cutscene_paused)
|
||||
current_cutscene.connect("cutscene_resumed", _on_cutscene_resumed)
|
||||
current_cutscene.connect("action_started", _on_action_started)
|
||||
current_cutscene.connect("action_completed", _on_action_completed)
|
||||
|
||||
# Disconnect from cutscene signals
|
||||
func _disconnect_cutscene_signals() -> void:
|
||||
if not current_cutscene:
|
||||
return
|
||||
|
||||
if current_cutscene.is_connected("cutscene_started", _on_cutscene_started):
|
||||
current_cutscene.disconnect("cutscene_started", _on_cutscene_started)
|
||||
if current_cutscene.is_connected("cutscene_completed", _on_cutscene_completed):
|
||||
current_cutscene.disconnect("cutscene_completed", _on_cutscene_completed)
|
||||
if current_cutscene.is_connected("cutscene_paused", _on_cutscene_paused):
|
||||
current_cutscene.disconnect("cutscene_paused", _on_cutscene_paused)
|
||||
if current_cutscene.is_connected("cutscene_resumed", _on_cutscene_resumed):
|
||||
current_cutscene.disconnect("cutscene_resumed", _on_cutscene_resumed)
|
||||
if current_cutscene.is_connected("action_started", _on_action_started):
|
||||
current_cutscene.disconnect("action_started", _on_action_started)
|
||||
if current_cutscene.is_connected("action_completed", _on_action_completed):
|
||||
current_cutscene.disconnect("action_completed", _on_action_completed)
|
||||
|
||||
# Handle graph changes
|
||||
func _on_graph_changed() -> void:
|
||||
# If we're previewing, restart the preview to reflect changes
|
||||
if is_previewing:
|
||||
stop_preview()
|
||||
start_preview()
|
||||
|
||||
# Handle cutscene started
|
||||
func _on_cutscene_started() -> void:
|
||||
emit_signal("preview_started")
|
||||
|
||||
# Handle cutscene completed
|
||||
func _on_cutscene_completed() -> void:
|
||||
emit_signal("preview_stopped")
|
||||
is_previewing = false
|
||||
is_paused = false
|
||||
|
||||
# Handle cutscene paused
|
||||
func _on_cutscene_paused() -> void:
|
||||
emit_signal("preview_paused")
|
||||
is_paused = true
|
||||
|
||||
# Handle cutscene resumed
|
||||
func _on_cutscene_resumed() -> void:
|
||||
emit_signal("preview_resumed")
|
||||
is_paused = false
|
||||
|
||||
# Handle action started
|
||||
func _on_action_started(action: Action) -> void:
|
||||
# Find the corresponding node in the graph and highlight it
|
||||
var node_name = _find_node_for_action(action)
|
||||
if node_name:
|
||||
emit_signal("node_activated", node_name)
|
||||
|
||||
# Update graph editor visualization
|
||||
if graph_edit:
|
||||
var node = graph_edit.get_node_or_null(node_name)
|
||||
if node and node.has_method("set_state"):
|
||||
node.set_state(BaseGraphNode.NodeState.ACTIVE)
|
||||
|
||||
# Handle action completed
|
||||
func _on_action_completed(action: Action) -> void:
|
||||
# Find the corresponding node in the graph and mark as completed
|
||||
var node_name = _find_node_for_action(action)
|
||||
if node_name:
|
||||
emit_signal("node_completed", node_name)
|
||||
|
||||
# Update graph editor visualization
|
||||
if graph_edit:
|
||||
var node = graph_edit.get_node_or_null(node_name)
|
||||
if node and node.has_method("set_state"):
|
||||
node.set_state(BaseGraphNode.NodeState.COMPLETED)
|
||||
|
||||
# Find node corresponding to an action
|
||||
func _find_node_for_action(action: Action) -> String:
|
||||
# This would need to map actions to nodes
|
||||
# In a real implementation, we would store this mapping when generating the cutscene
|
||||
# For now, we'll return a placeholder
|
||||
return ""
|
||||
|
||||
# Get the preview scene for display
|
||||
func get_preview_scene() -> Node2D:
|
||||
return preview_scene
|
||||
@@ -1,186 +0,0 @@
|
||||
@tool
|
||||
class_name PreviewPanel
|
||||
extends PanelContainer
|
||||
|
||||
# UI panel for displaying the preview
|
||||
|
||||
# UI elements
|
||||
var preview_viewport: SubViewport
|
||||
var preview_texture: TextureRect
|
||||
var controls_container: HBoxContainer
|
||||
var play_button: Button
|
||||
var pause_button: Button
|
||||
var stop_button: Button
|
||||
var speed_slider: HSlider
|
||||
var progress_bar: ProgressBar
|
||||
var debug_label: Label
|
||||
|
||||
# Properties
|
||||
var preview_manager: PreviewManager
|
||||
var is_playing: bool = false
|
||||
|
||||
# Initialize the preview panel
|
||||
func _ready() -> void:
|
||||
_setup_ui()
|
||||
_setup_signals()
|
||||
|
||||
# Set up the UI
|
||||
func _setup_ui() -> void:
|
||||
# Main container
|
||||
var main_vbox = VBoxContainer.new()
|
||||
main_vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
main_vbox.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
add_child(main_vbox)
|
||||
|
||||
# Preview viewport
|
||||
var viewport_container = AspectRatioContainer.new()
|
||||
viewport_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
viewport_container.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
viewport_container.ratio = 16.0/9.0
|
||||
main_vbox.add_child(viewport_container)
|
||||
|
||||
preview_viewport = SubViewport.new()
|
||||
preview_viewport.size = Vector2i(800, 450)
|
||||
preview_viewport.render_target_update_mode = SubViewport.UPDATE_ALWAYS
|
||||
viewport_container.add_child(preview_viewport)
|
||||
|
||||
preview_texture = TextureRect.new()
|
||||
preview_texture.expand = true
|
||||
preview_texture.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
preview_texture.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
preview_texture.texture = preview_viewport.get_texture()
|
||||
viewport_container.add_child(preview_texture)
|
||||
|
||||
# Controls
|
||||
controls_container = HBoxContainer.new()
|
||||
controls_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
main_vbox.add_child(controls_container)
|
||||
|
||||
play_button = Button.new()
|
||||
play_button.text = "▶"
|
||||
play_button.flat = true
|
||||
controls_container.add_child(play_button)
|
||||
|
||||
pause_button = Button.new()
|
||||
pause_button.text = "⏸"
|
||||
pause_button.flat = true
|
||||
controls_container.add_child(pause_button)
|
||||
|
||||
stop_button = Button.new()
|
||||
stop_button.text = "⏹"
|
||||
stop_button.flat = true
|
||||
controls_container.add_child(stop_button)
|
||||
|
||||
var speed_label = Label.new()
|
||||
speed_label.text = "Speed:"
|
||||
controls_container.add_child(speed_label)
|
||||
|
||||
speed_slider = HSlider.new()
|
||||
speed_slider.min = 0.1
|
||||
speed_slider.max = 2.0
|
||||
speed_slider.step = 0.1
|
||||
speed_slider.value = 1.0
|
||||
speed_slider.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
controls_container.add_child(speed_slider)
|
||||
|
||||
# Progress bar
|
||||
progress_bar = ProgressBar.new()
|
||||
progress_bar.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
progress_bar.percent_visible = true
|
||||
main_vbox.add_child(progress_bar)
|
||||
|
||||
# Debug info
|
||||
debug_label = Label.new()
|
||||
debug_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
main_vbox.add_child(debug_label)
|
||||
|
||||
# Set up signal connections
|
||||
func _setup_signals() -> void:
|
||||
play_button.connect("pressed", _on_play_pressed)
|
||||
pause_button.connect("pressed", _on_pause_pressed)
|
||||
stop_button.connect("pressed", _on_stop_pressed)
|
||||
speed_slider.connect("value_changed", _on_speed_changed)
|
||||
|
||||
# Set the preview manager
|
||||
func set_preview_manager(manager: PreviewManager) -> void:
|
||||
preview_manager = manager
|
||||
|
||||
# Connect to preview manager signals
|
||||
if preview_manager:
|
||||
preview_manager.connect("preview_started", _on_preview_started)
|
||||
preview_manager.connect("preview_stopped", _on_preview_stopped)
|
||||
preview_manager.connect("preview_paused", _on_preview_paused)
|
||||
preview_manager.connect("preview_resumed", _on_preview_resumed)
|
||||
preview_manager.connect("preview_progress", _on_preview_progress)
|
||||
preview_manager.connect("node_activated", _on_node_activated)
|
||||
preview_manager.connect("node_completed", _on_node_completed)
|
||||
|
||||
# Set the graph editor
|
||||
func set_graph_edit(graph_edit: CutsceneGraphEdit) -> void:
|
||||
if preview_manager:
|
||||
preview_manager.setup_preview(graph_edit)
|
||||
|
||||
# Add preview scene to viewport
|
||||
var preview_scene = preview_manager.get_preview_scene()
|
||||
if preview_scene and preview_scene.get_parent() != preview_viewport:
|
||||
preview_viewport.add_child(preview_scene)
|
||||
|
||||
# Handle play button press
|
||||
func _on_play_pressed() -> void:
|
||||
if preview_manager:
|
||||
if is_playing:
|
||||
preview_manager.resume_preview()
|
||||
else:
|
||||
preview_manager.start_preview()
|
||||
|
||||
# Handle pause button press
|
||||
func _on_pause_pressed() -> void:
|
||||
if preview_manager:
|
||||
preview_manager.pause_preview()
|
||||
|
||||
# Handle stop button press
|
||||
func _on_stop_pressed() -> void:
|
||||
if preview_manager:
|
||||
preview_manager.stop_preview()
|
||||
|
||||
# Handle speed slider change
|
||||
func _on_speed_changed(value: float) -> void:
|
||||
if preview_manager:
|
||||
preview_manager.set_preview_speed(value)
|
||||
|
||||
# Handle preview started
|
||||
func _on_preview_started() -> void:
|
||||
is_playing = true
|
||||
play_button.text = "⏸"
|
||||
debug_label.text = "Preview started"
|
||||
|
||||
# Handle preview stopped
|
||||
func _on_preview_stopped() -> void:
|
||||
is_playing = false
|
||||
play_button.text = "▶"
|
||||
progress_bar.value = 0
|
||||
debug_label.text = "Preview stopped"
|
||||
|
||||
# Handle preview paused
|
||||
func _on_preview_paused() -> void:
|
||||
is_playing = false
|
||||
play_button.text = "▶"
|
||||
debug_label.text = "Preview paused"
|
||||
|
||||
# Handle preview resumed
|
||||
func _on_preview_resumed() -> void:
|
||||
is_playing = true
|
||||
play_button.text = "⏸"
|
||||
debug_label.text = "Preview resumed"
|
||||
|
||||
# Handle preview progress
|
||||
func _on_preview_progress(progress: float) -> void:
|
||||
progress_bar.value = progress * 100
|
||||
|
||||
# Handle node activated
|
||||
func _on_node_activated(node_name: String) -> void:
|
||||
debug_label.text = "Executing: %s" % node_name
|
||||
|
||||
# Handle node completed
|
||||
func _on_node_completed(node_name: String) -> void:
|
||||
debug_label.text = "Completed: %s" % node_name
|
||||
0
addons/cutscene_editor/editor/UndoRedoManager.gd
Normal file → Executable file
2
addons/cutscene_editor/editor/nodes/AnimationActionNode.gd
Normal file → Executable file
@@ -6,7 +6,7 @@ extends "res://addons/cutscene_editor/editor/nodes/BaseGraphNode.gd"
|
||||
|
||||
func _init() -> void:
|
||||
node_type = "animation"
|
||||
node_id = "animation_" + str(randi())
|
||||
name = "animation_" + str(randi())
|
||||
title = "Animation"
|
||||
modulate = Color(0.8, 0.4, 0.8) # Purple
|
||||
|
||||
|
||||
0
addons/cutscene_editor/editor/nodes/AnimationActionNode.tscn
Normal file → Executable file
2
addons/cutscene_editor/editor/nodes/BaseGraphNode.gd
Normal file → Executable file
@@ -20,7 +20,7 @@ enum NodeState {
|
||||
|
||||
# Properties
|
||||
var node_type: String = "base"
|
||||
var node_id: String # Unique identifier for the node
|
||||
|
||||
var action_parameters: Dictionary = {} # Stores parameter values
|
||||
var current_state: int = NodeState.IDLE
|
||||
var error_message: String = ""
|
||||
|
||||
2
addons/cutscene_editor/editor/nodes/DialogueActionNode.gd
Normal file → Executable file
@@ -6,7 +6,7 @@ extends "res://addons/cutscene_editor/editor/nodes/BaseGraphNode.gd"
|
||||
|
||||
func _init() -> void:
|
||||
node_type = "dialogue"
|
||||
node_id = "dialogue_" + str(randi())
|
||||
name = "dialogue_" + str(randi())
|
||||
title = "Dialogue"
|
||||
modulate = Color(1.0, 1.0, 0.5) # Yellow
|
||||
resizable=true
|
||||
|
||||
0
addons/cutscene_editor/editor/nodes/DialogueActionNode.tscn
Normal file → Executable file
2
addons/cutscene_editor/editor/nodes/EntryNode.gd
Normal file → Executable file
@@ -6,7 +6,7 @@ extends "res://addons/cutscene_editor/editor/nodes/BaseGraphNode.gd"
|
||||
|
||||
func _init() -> void:
|
||||
node_type = "entry"
|
||||
node_id = "entry_" + str(randi())
|
||||
name = "entry_" + str(randi())
|
||||
title = "Start"
|
||||
modulate = Color(0.5, 1.0, 0.5) # Light green
|
||||
|
||||
|
||||
12
addons/cutscene_editor/editor/nodes/EntryNode.tscn
Normal file → Executable file
@@ -1,15 +1,23 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://entrynodetscn"]
|
||||
[gd_scene load_steps=2 format=3 uid="uid://sfwelq3tmwkv"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/cutscene_editor/editor/nodes/EntryNode.gd" id="1_entry"]
|
||||
|
||||
[node name="EntryNode" type="GraphNode"]
|
||||
modulate = Color(0.5, 1.0, 0.5, 1)
|
||||
modulate = Color(0.5, 1, 0.5, 1)
|
||||
custom_minimum_size = Vector2(100, 0)
|
||||
title = "Start"
|
||||
slot/0/left_enabled = false
|
||||
slot/0/left_type = 0
|
||||
slot/0/left_color = Color(0, 0, 0, 0)
|
||||
slot/0/left_icon = null
|
||||
slot/0/right_enabled = true
|
||||
slot/0/right_type = 0
|
||||
slot/0/right_color = Color(0, 0, 0, 1)
|
||||
slot/0/right_icon = null
|
||||
slot/0/draw_stylebox = true
|
||||
script = ExtResource("1_entry")
|
||||
|
||||
[node name="Label" type="Label" parent="."]
|
||||
layout_mode = 2
|
||||
text = "completed
|
||||
"
|
||||
|
||||
2
addons/cutscene_editor/editor/nodes/ExitNode.gd
Normal file → Executable file
@@ -6,7 +6,7 @@ extends "res://addons/cutscene_editor/editor/nodes/BaseGraphNode.gd"
|
||||
|
||||
func _init() -> void:
|
||||
node_type = "exit"
|
||||
node_id = "exit_" + str(randi())
|
||||
name = "exit_" + str(randi())
|
||||
title = "End"
|
||||
modulate = Color(1.0, 0.5, 0.5) # Light red
|
||||
|
||||
|
||||
10
addons/cutscene_editor/editor/nodes/ExitNode.tscn
Normal file → Executable file
@@ -1,14 +1,22 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://exitnodetscn"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/cutscene_editor/editor/nodes/ExitNode.gd" id="1_exit"]
|
||||
|
||||
[node name="ExitNode" type="GraphNode"]
|
||||
modulate = Color(1.0, 0.5, 0.5, 1)
|
||||
modulate = Color(1, 0.5, 0.5, 1)
|
||||
custom_minimum_size = Vector2(100, 0)
|
||||
title = "End"
|
||||
slot/0/left_enabled = true
|
||||
slot/0/left_type = 0
|
||||
slot/0/left_color = Color(0, 0, 0, 1)
|
||||
slot/0/left_icon = null
|
||||
slot/0/right_enabled = false
|
||||
slot/0/right_type = 0
|
||||
slot/0/right_color = Color(0, 0, 0, 0)
|
||||
slot/0/right_icon = null
|
||||
slot/0/draw_stylebox = true
|
||||
script = ExtResource("1_exit")
|
||||
|
||||
[node name="Label" type="Label" parent="."]
|
||||
layout_mode = 2
|
||||
text = "Dependency"
|
||||
|
||||
12
addons/cutscene_editor/editor/nodes/MoveActionNode.gd
Normal file → Executable file
@@ -6,7 +6,7 @@ extends "res://addons/cutscene_editor/editor/nodes/BaseGraphNode.gd"
|
||||
|
||||
func _init() -> void:
|
||||
node_type = "move"
|
||||
node_id = "move_" + str(randi())
|
||||
name = "move_" + str(randi())
|
||||
title = "Move"
|
||||
modulate = Color(0.4, 0.6, 1.0) # Blue
|
||||
|
||||
@@ -22,6 +22,16 @@ func _ready() -> void:
|
||||
action_parameters["target_y"] = 0.0
|
||||
action_parameters["speed"] = 100.0
|
||||
|
||||
func set_parameter(pname, value) -> void:
|
||||
super.set_parameter(name, value)
|
||||
if pname == "character":
|
||||
$VBoxContainer/CharacterEDit.text = value
|
||||
elif pname == "target_x":
|
||||
$VBoxContainer/HBoxContainer/x.text = value
|
||||
elif pname == "target_y":
|
||||
$VBoxContainer/HBoxContainer/y.text = value
|
||||
elif pname == "speed":
|
||||
$VBoxContainer/speed.text = value
|
||||
func _on_character_changed(new_text: String) -> void:
|
||||
set_parameter("character", new_text)
|
||||
|
||||
|
||||
4
addons/cutscene_editor/editor/nodes/MoveActionNode.tscn
Normal file → Executable file
@@ -23,7 +23,7 @@ layout_mode = 2
|
||||
[node name="Character" type="Label" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="TextEdit" type="LineEdit" parent="VBoxContainer"]
|
||||
[node name="CharacterEDit" type="LineEdit" parent="VBoxContainer"]
|
||||
custom_minimum_size = Vector2(0, 32.865)
|
||||
layout_mode = 2
|
||||
text = "aoeu"
|
||||
@@ -49,7 +49,7 @@ text = "Speed"
|
||||
[node name="speed" type="LineEdit" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[connection signal="text_changed" from="VBoxContainer/TextEdit" to="." method="_on_character_changed"]
|
||||
[connection signal="text_changed" from="VBoxContainer/CharacterEDit" to="." method="_on_character_changed"]
|
||||
[connection signal="text_changed" from="VBoxContainer/HBoxContainer/x" to="." method="_on_target_x_changed"]
|
||||
[connection signal="text_changed" from="VBoxContainer/HBoxContainer/y" to="." method="_on_target_y_changed"]
|
||||
[connection signal="text_change_rejected" from="VBoxContainer/speed" to="." method="_on_speed_text_change_rejected"]
|
||||
|
||||
2
addons/cutscene_editor/editor/nodes/ParallelGroupNode.gd
Normal file → Executable file
@@ -14,7 +14,7 @@ var container_rect: PanelContainer # Visual container for child nodes
|
||||
|
||||
func _init() -> void:
|
||||
node_type = "parallel"
|
||||
node_id = "parallel_" + str(randi())
|
||||
name = "parallel_" + str(randi())
|
||||
title = "Parallel Group"
|
||||
modulate = Color(1.0, 0.6, 0.2) # Orange
|
||||
|
||||
|
||||
2
addons/cutscene_editor/editor/nodes/TurnActionNode.gd
Normal file → Executable file
@@ -6,7 +6,7 @@ extends "res://addons/cutscene_editor/editor/nodes/BaseGraphNode.gd"
|
||||
|
||||
func _init() -> void:
|
||||
node_type = "turn"
|
||||
node_id = "turn_" + str(randi())
|
||||
name = "turn_" + str(randi())
|
||||
title = "Turn"
|
||||
modulate = Color(0.5, 1.0, 0.5) # Green
|
||||
|
||||
|
||||
0
addons/cutscene_editor/editor/nodes/TurnActionNode.tscn
Normal file → Executable file
0
addons/cutscene_editor/editor/nodes/WaitActionNode.gd
Normal file → Executable file
0
addons/cutscene_editor/editor/nodes/WaitActionNode.tscn
Normal file → Executable file
0
addons/cutscene_editor/editor/resources/CutsceneResource.gd
Normal file → Executable file
0
addons/cutscene_editor/icons/icon_animation.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 357 B After Width: | Height: | Size: 357 B |
0
addons/cutscene_editor/icons/icon_animation.svg.import
Normal file → Executable file
0
addons/cutscene_editor/icons/icon_dialogue.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 337 B After Width: | Height: | Size: 337 B |
0
addons/cutscene_editor/icons/icon_dialogue.svg.import
Normal file → Executable file
0
addons/cutscene_editor/icons/icon_entry.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 222 B After Width: | Height: | Size: 222 B |
0
addons/cutscene_editor/icons/icon_entry.svg.import
Normal file → Executable file
0
addons/cutscene_editor/icons/icon_exit.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 244 B After Width: | Height: | Size: 244 B |
0
addons/cutscene_editor/icons/icon_exit.svg.import
Normal file → Executable file
0
addons/cutscene_editor/icons/icon_move.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 383 B After Width: | Height: | Size: 383 B |
0
addons/cutscene_editor/icons/icon_move.svg.import
Normal file → Executable file
0
addons/cutscene_editor/icons/icon_parallel.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 428 B After Width: | Height: | Size: 428 B |
0
addons/cutscene_editor/icons/icon_parallel.svg.import
Normal file → Executable file
0
addons/cutscene_editor/icons/icon_turn.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 335 B After Width: | Height: | Size: 335 B |
0
addons/cutscene_editor/icons/icon_turn.svg.import
Normal file → Executable file
0
addons/cutscene_editor/icons/icon_wait.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 407 B After Width: | Height: | Size: 407 B |
0
addons/cutscene_editor/icons/icon_wait.svg.import
Normal file → Executable file
0
addons/cutscene_editor/plugin.cfg
Normal file → Executable file
0
addons/cutscene_editor/tests/test_cutscene_resource.gd
Normal file → Executable file
0
addons/cutscene_editor/tests/test_cutscene_resource.tscn
Normal file → Executable file
0
cutscene/Action.gd
Normal file → Executable file
194
cutscene/CutsceneManager.gd
Normal file → Executable file
@@ -1,14 +1,13 @@
|
||||
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
|
||||
# Action dependency graph
|
||||
var actions: Dictionary = {} # Map of action IDs to action instances
|
||||
var dependencies: Dictionary = {} # Map of action IDs to their dependency lists
|
||||
var dependents: Dictionary = {} # Map of action IDs to actions that depend on them
|
||||
var ready_actions: Array = [] # Actions that are ready to execute (all dependencies met)
|
||||
var active_actions: Array = [] # Currently running actions
|
||||
var completed_actions: Array = [] # Actions that have completed
|
||||
|
||||
# State management
|
||||
enum State {
|
||||
@@ -39,8 +38,16 @@ func start() -> void:
|
||||
is_active = true
|
||||
emit_signal("cutscene_started")
|
||||
|
||||
# Start first action
|
||||
_execute_next_action()
|
||||
# Find actions with no dependencies to start with
|
||||
_find_ready_actions()
|
||||
|
||||
# Start all ready actions
|
||||
for action_id in ready_actions:
|
||||
_execute_action(action_id)
|
||||
|
||||
# Check if there are no actions to execute
|
||||
if actions.size() == 0:
|
||||
_on_cutscene_completed()
|
||||
|
||||
func pause() -> void:
|
||||
# Pause the current cutscene
|
||||
@@ -56,67 +63,78 @@ func resume() -> void:
|
||||
|
||||
func stop() -> void:
|
||||
# Stop the current cutscene and reset
|
||||
if current_action != null:
|
||||
current_action.stop()
|
||||
current_action = null
|
||||
# Stop all active actions
|
||||
for action in active_actions:
|
||||
if action.state == action.State.RUNNING:
|
||||
action.stop()
|
||||
|
||||
# Stop all active parallel groups
|
||||
for group in active_parallel_groups:
|
||||
for action in group["actions"]:
|
||||
if action.state == action.State.RUNNING:
|
||||
action.stop()
|
||||
active_actions.clear()
|
||||
|
||||
_reset()
|
||||
|
||||
func add_action(action: Action) -> void:
|
||||
# Add a single action to the sequential queue
|
||||
func add_action(action_id: String, action: Action, deps: Array = []) -> void:
|
||||
# Add an action with its dependencies
|
||||
if state != State.IDLE:
|
||||
print("Warning: Cannot add actions while cutscene is running")
|
||||
return
|
||||
|
||||
sequential_actions.append(action)
|
||||
actions[action_id] = action
|
||||
dependencies[action_id] = deps
|
||||
# Build reverse dependency map (dependents)
|
||||
for dep_id in deps:
|
||||
if not dependents.has(dep_id):
|
||||
dependents[dep_id] = []
|
||||
dependents[dep_id].append(action_id)
|
||||
|
||||
# Internal methods
|
||||
func _process(delta: float) -> void:
|
||||
# Main update loop
|
||||
if state != State.RUNNING:
|
||||
return
|
||||
# Find actions with no dependencies to start with
|
||||
_find_ready_actions()
|
||||
|
||||
# Update current sequential action
|
||||
if current_action != null and current_action.state == current_action.State.RUNNING:
|
||||
current_action.update(delta)
|
||||
# Start all ready actions
|
||||
for action_id in ready_actions:
|
||||
_execute_action(action_id)
|
||||
|
||||
# Update active parallel groups
|
||||
for group in active_parallel_groups:
|
||||
for action in group["actions"]:
|
||||
if action.state == action.State.RUNNING:
|
||||
action.update(delta)
|
||||
# Update all active actions
|
||||
for action in active_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
|
||||
func _find_ready_actions() -> void:
|
||||
# Find actions that are ready to execute (all dependencies met)
|
||||
ready_actions.clear()
|
||||
|
||||
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
|
||||
_execute_parallel_group(action_item)
|
||||
else:
|
||||
# This is a single action
|
||||
_execute_single_action(action_item)
|
||||
for action_id in actions:
|
||||
# Skip if already completed or active
|
||||
if completed_actions.has(action_id) or active_actions.has(actions[action_id]):
|
||||
continue
|
||||
|
||||
func _execute_single_action(action: Action) -> void:
|
||||
# Check if all dependencies are met
|
||||
var all_deps_met = true
|
||||
for dep_id in dependencies[action_id]:
|
||||
if not completed_actions.has(dep_id):
|
||||
all_deps_met = false
|
||||
break
|
||||
|
||||
if all_deps_met:
|
||||
ready_actions.append(action_id)
|
||||
|
||||
func _execute_action(action_id: String) -> void:
|
||||
# Execute a single action
|
||||
current_action = action
|
||||
var action = actions[action_id]
|
||||
|
||||
# Remove from ready actions
|
||||
ready_actions.erase(action_id)
|
||||
|
||||
# Add to active actions
|
||||
active_actions.append(action)
|
||||
|
||||
# Connect to action signals
|
||||
if not action.is_connected("completed", _on_action_completed):
|
||||
action.connect("completed", _on_action_completed.bind(action))
|
||||
action.connect("completed", _on_action_completed.bind(action_id))
|
||||
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):
|
||||
@@ -126,72 +144,37 @@ func _execute_single_action(action: Action) -> void:
|
||||
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:
|
||||
func _on_action_completed(action_id: String) -> void:
|
||||
# Handle action completion
|
||||
if action == current_action:
|
||||
current_action = null
|
||||
emit_signal("action_completed", action)
|
||||
# Move to next action
|
||||
_execute_next_action()
|
||||
var action = actions[action_id]
|
||||
active_actions.erase(action)
|
||||
completed_actions.append(action_id)
|
||||
emit_signal("action_completed", action)
|
||||
|
||||
# Check if all actions are completed
|
||||
if completed_actions.size() == actions.size():
|
||||
_on_cutscene_completed()
|
||||
else:
|
||||
# Find newly ready actions
|
||||
_find_ready_actions()
|
||||
|
||||
# Start all newly ready actions
|
||||
for new_action_id in ready_actions:
|
||||
_execute_action(new_action_id)
|
||||
|
||||
func _on_action_failed(action: Action, error_message: String) -> void:
|
||||
# Handle action failure
|
||||
if action == current_action:
|
||||
current_action = null
|
||||
active_actions.erase(action)
|
||||
|
||||
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
|
||||
@@ -200,10 +183,11 @@ func _on_cutscene_completed() -> void:
|
||||
|
||||
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
|
||||
actions.clear()
|
||||
dependencies.clear()
|
||||
dependents.clear()
|
||||
ready_actions.clear()
|
||||
active_actions.clear()
|
||||
completed_actions.clear()
|
||||
state = State.IDLE
|
||||
is_active = false
|
||||
|
||||
0
cutscene/README.md
Normal file → Executable file
0
cutscene/actions/AnimationAction.gd
Normal file → Executable file
0
cutscene/actions/DialogueAction.gd
Normal file → Executable file
1
cutscene/actions/MoveAction.gd
Normal file → Executable file
@@ -30,7 +30,6 @@ func start() -> void:
|
||||
self._set_running()
|
||||
|
||||
func update(delta: float) -> void:
|
||||
print ("updating")
|
||||
if state != State.RUNNING:
|
||||
return
|
||||
|
||||
|
||||
0
cutscene/actions/TurnAction.gd
Normal file → Executable file
0
cutscene/actions/WaitAction.gd
Normal file → Executable file
2
example_cutscene.tscn
Normal file → Executable file
@@ -1,4 +1,4 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://bi4gojo71c7jt"]
|
||||
[gd_scene load_steps=2 format=3 uid="uid://bi2c4ph8txa4y"]
|
||||
|
||||
[sub_resource type="GDScript" id="GDScript_e7tbc"]
|
||||
script/source = "extends Node2D
|
||||
|
||||
34
hello.tres
@@ -1,34 +0,0 @@
|
||||
[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"
|
||||
}
|
||||
0
icon.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 994 B After Width: | Height: | Size: 994 B |
0
icon.svg.import
Normal file → Executable file
0
plans/cutscene_data_structure.md
Normal file → Executable file
0
plans/cutscene_documentation_plan.md
Normal file → Executable file
0
plans/cutscene_generator_changes.md
Normal file → Executable file
0
plans/cutscene_graph_edit_changes.md
Normal file → Executable file
0
plans/cutscene_resource_analysis.md
Normal file → Executable file
0
plans/cutscene_resource_implementation.md
Normal file → Executable file
0
plans/cutscene_resource_implementation_summary.md
Normal file → Executable file
0
plans/cutscene_test_plan.md
Normal file → Executable file
2
project.godot
Normal file → Executable file
@@ -12,7 +12,7 @@ config_version=5
|
||||
|
||||
config/name="adventure-ai"
|
||||
run/main_scene="res://main.tscn"
|
||||
config/features=PackedStringArray("4.3", "Forward Plus")
|
||||
config/features=PackedStringArray("4.2", "Forward Plus")
|
||||
config/icon="res://icon.svg"
|
||||
|
||||
[editor_plugins]
|
||||
|
||||
18
tes2265.tmp
Executable file
@@ -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)
|
||||
101
test_cutscene.tres
Executable file
@@ -0,0 +1,101 @@
|
||||
[gd_resource type="Resource" script_class="CutsceneResource" load_steps=2 format=3 uid="uid://bfngefj7emehe"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/cutscene_editor/editor/resources/CutsceneResource.gd" id="1_p6huk"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_p6huk")
|
||||
nodes = [{
|
||||
"id": "entry_476938218",
|
||||
"parameters": {},
|
||||
"position": {
|
||||
"x": 262.0,
|
||||
"y": 155.0
|
||||
},
|
||||
"type": "entry"
|
||||
}, {
|
||||
"id": "move_3499731826",
|
||||
"parameters": {
|
||||
"character": "",
|
||||
"move_3499731826": 700.0,
|
||||
"speed": 100.0,
|
||||
"target_x": 0.0,
|
||||
"target_y": 0.0
|
||||
},
|
||||
"position": {
|
||||
"x": 380.0,
|
||||
"y": 40.0
|
||||
},
|
||||
"type": "move"
|
||||
}, {
|
||||
"id": "exit_1710964436",
|
||||
"parameters": {},
|
||||
"position": {
|
||||
"x": 1020.0,
|
||||
"y": 140.0
|
||||
},
|
||||
"type": "exit"
|
||||
}, {
|
||||
"id": "move_2800650428",
|
||||
"parameters": {
|
||||
"character": "",
|
||||
"move_2800650428": 91.0,
|
||||
"speed": 100.0,
|
||||
"target_x": 0.0,
|
||||
"target_y": 0.0
|
||||
},
|
||||
"position": {
|
||||
"x": 640.0,
|
||||
"y": 60.0
|
||||
},
|
||||
"type": "move"
|
||||
}, {
|
||||
"id": "MoveActionNode",
|
||||
"parameters": {
|
||||
"MoveActionNode": 50.0,
|
||||
"character": "",
|
||||
"speed": 100.0,
|
||||
"target_x": 0.0,
|
||||
"target_y": 0.0
|
||||
},
|
||||
"position": {
|
||||
"x": 520.0,
|
||||
"y": 340.0
|
||||
},
|
||||
"type": "move"
|
||||
}]
|
||||
connections = [{
|
||||
"from_node": "entry_476938218",
|
||||
"from_port": 0,
|
||||
"id": "conn_4022396",
|
||||
"to_node": "move_3499731826",
|
||||
"to_port": 0
|
||||
}, {
|
||||
"from_node": "move_3499731826",
|
||||
"from_port": 0,
|
||||
"id": "conn_4022396",
|
||||
"to_node": "move_2800650428",
|
||||
"to_port": 0
|
||||
}, {
|
||||
"from_node": "move_2800650428",
|
||||
"from_port": 0,
|
||||
"id": "conn_4022396",
|
||||
"to_node": "exit_1710964436",
|
||||
"to_port": 0
|
||||
}, {
|
||||
"from_node": "move_3499731826",
|
||||
"from_port": 0,
|
||||
"id": "conn_4022396",
|
||||
"to_node": "MoveActionNode",
|
||||
"to_port": 0
|
||||
}, {
|
||||
"from_node": "MoveActionNode",
|
||||
"from_port": 0,
|
||||
"id": "conn_4022396",
|
||||
"to_node": "exit_1710964436",
|
||||
"to_port": 0
|
||||
}]
|
||||
metadata = {
|
||||
"created": 1.75402e+09,
|
||||
"modified": 1.75402e+09,
|
||||
"version": "2.0"
|
||||
}
|
||||
234
test_cutscene2.gd
Executable file
@@ -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 = preload("res://test_cutscene.tres")
|
||||
|
||||
# Generate actions from the resource
|
||||
var generator = CutsceneGenerator.new()
|
||||
cutscene_manager = generator.generate_cutscene(self, 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, %s" % action.name)
|
||||
|
||||
func _on_action_completed(action: Action) -> void:
|
||||
print("Action completed: %s, %s" % action.name)
|
||||
|
||||
# Clean up when the node is removed
|
||||
func _exit_tree() -> void:
|
||||
if cutscene_manager:
|
||||
cutscene_manager.queue_free()
|
||||
18
test_cutscene2.tscn
Executable file
@@ -0,0 +1,18 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://bb0vnntvwbuff"]
|
||||
|
||||
[ext_resource type="Script" path="res://test_cutscene2.gd" id="1_6nibr"]
|
||||
|
||||
[node name="Node2D" type="Node2D"]
|
||||
script = ExtResource("1_6nibr")
|
||||
|
||||
[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)
|
||||
50
test_dependency_cutscene.gd
Executable file
@@ -0,0 +1,50 @@
|
||||
extends Node
|
||||
|
||||
# Test script for the new dependency-based cutscene system
|
||||
|
||||
func _ready():
|
||||
# Create a simple test cutscene
|
||||
_test_dependency_system()
|
||||
|
||||
func _test_dependency_system():
|
||||
print("Testing dependency-based cutscene system")
|
||||
|
||||
# Create cutscene manager
|
||||
var cutscene_manager = CutsceneManager.new()
|
||||
add_child(cutscene_manager)
|
||||
|
||||
# Create some test actions
|
||||
var wait1 = preload("res://cutscene/actions/WaitAction.gd").new(1.0)
|
||||
wait1.name = "Wait1"
|
||||
|
||||
var wait2 = preload("res://cutscene/actions/WaitAction.gd").new(2.0)
|
||||
wait2.name = "Wait2"
|
||||
|
||||
var wait3 = preload("res://cutscene/actions/WaitAction.gd").new(1.5)
|
||||
wait3.name = "Wait3"
|
||||
|
||||
# Add actions with dependencies
|
||||
# wait1 and wait2 can run in parallel (no dependencies)
|
||||
# wait3 depends on wait1
|
||||
cutscene_manager.add_action("wait1", wait1, [])
|
||||
cutscene_manager.add_action("wait2", wait2, [])
|
||||
cutscene_manager.add_action("wait3", wait3, ["wait1"])
|
||||
|
||||
# Connect to signals
|
||||
cutscene_manager.connect("cutscene_completed", _on_cutscene_completed)
|
||||
cutscene_manager.connect("action_started", _on_action_started)
|
||||
cutscene_manager.connect("action_completed", _on_action_completed)
|
||||
|
||||
# Start the cutscene
|
||||
cutscene_manager.start()
|
||||
|
||||
print("Cutscene started with dependency-based execution")
|
||||
|
||||
func _on_cutscene_completed():
|
||||
print("Cutscene completed successfully!")
|
||||
|
||||
func _on_action_started(action):
|
||||
print("Action started: %s" % action.name)
|
||||
|
||||
func _on_action_completed(action):
|
||||
print("Action completed: %s" % action.name)
|
||||
6
test_dependency_cutscene.tscn
Executable file
@@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=2]
|
||||
|
||||
[node name="Node" type="Node"]
|
||||
script = ExtResource( 1 )
|
||||
|
||||
[resource]
|
||||
8
test_resource_cutscene.gd
Normal file → Executable file
@@ -37,7 +37,7 @@ func setup_cutscene() -> void:
|
||||
|
||||
# Generate actions from the resource
|
||||
var generator = CutsceneGenerator.new()
|
||||
cutscene_manager = generator.generate_cutscene(cutscene_resource)
|
||||
cutscene_manager = generator.generate_cutscene(self, cutscene_resource)
|
||||
|
||||
# Add the cutscene manager to the scene
|
||||
add_child(cutscene_manager)
|
||||
@@ -77,7 +77,7 @@ func create_test_cutscene_resource() -> CutsceneResource:
|
||||
"type": "move",
|
||||
"position": {"x": 250, "y": 150},
|
||||
"parameters": {
|
||||
"character": "character2",
|
||||
"character": "Character2",
|
||||
"target_x": 912,
|
||||
"target_y": 235,
|
||||
"speed": 100.0
|
||||
@@ -223,10 +223,10 @@ func _on_cutscene_completed() -> void:
|
||||
print("Character2: %s" % character2.position)
|
||||
|
||||
func _on_action_started(action: Action) -> void:
|
||||
print("Action started: %s" % action.name)
|
||||
print("Action started: %s, %s" % action.name)
|
||||
|
||||
func _on_action_completed(action: Action) -> void:
|
||||
print("Action completed: %s" % action.name)
|
||||
print("Action completed: %s, %s" % action.name)
|
||||
|
||||
# Clean up when the node is removed
|
||||
func _exit_tree() -> void:
|
||||
|
||||