Add Transition Configurator plugin for easy exit configuration
Features: - Fuzzy search for finding destination rooms - Lists all TransitionPieces in selected room as arrival points - Bidirectional wiring - updates return transition automatically - Auto-reloads destination scene in editor - UndoRedo support for source scene changes Files added: - addons/transition_configurator/plugin.cfg - addons/transition_configurator/transition_configurator.gd - addons/transition_configurator/transition_inspector_plugin.gd - addons/transition_configurator/config_dialog.gd - addons/transition_configurator/fuzzy_search.gd - addons/transition_configurator/README.md
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,7 +1,8 @@
|
||||
.godot/**
|
||||
tools/venv/**
|
||||
.import
|
||||
addons
|
||||
addons/*
|
||||
!addons/transition_configurator/
|
||||
build/
|
||||
tmp/**
|
||||
.tmp/**
|
||||
|
||||
58
addons/transition_configurator/README.md
Normal file
58
addons/transition_configurator/README.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# Transition Configurator Plugin
|
||||
|
||||
A Godot 4.x editor plugin that simplifies configuring TransitionPiece exits in the King's Quest IV remake.
|
||||
|
||||
## Features
|
||||
|
||||
- **Fuzzy search** for finding rooms quickly
|
||||
- **Visual selection** of destination TransitionPieces as arrival points
|
||||
- **Bidirectional wiring** - automatically updates return transitions
|
||||
- **Validation** - warns if destination doesn't have appropriate arrival points
|
||||
- **Auto-reload** - refreshes scenes in editor after changes
|
||||
|
||||
## Installation
|
||||
|
||||
1. Copy the `addons/transition_configurator/` folder to your project's `addons/` directory
|
||||
2. Enable the plugin in Project Settings → Plugins
|
||||
3. Restart the editor (recommended)
|
||||
|
||||
## Usage
|
||||
|
||||
1. Select a TransitionPiece node in your scene
|
||||
2. In the Inspector, click the **"Configure Exit..."** button
|
||||
3. Use the search box to filter rooms (fuzzy matching)
|
||||
4. Click on a room to see its available TransitionPieces
|
||||
5. Select an arrival point
|
||||
6. Click **Apply Changes**
|
||||
|
||||
The plugin will:
|
||||
- Update the selected TransitionPiece's `target` and `appear_at_node` properties
|
||||
- Update (or create) the return transition in the destination room
|
||||
- Reload the destination scene in the editor
|
||||
|
||||
## How It Works
|
||||
|
||||
- **Room Discovery**: Scans `res://scenes/` for `.tscn` files starting with `kq4_`
|
||||
- **Arrival Points**: Lists all TransitionPiece instances in the selected room
|
||||
- **Bidirectional Updates**: Finds or creates a return TransitionPiece named after the current room
|
||||
- **UID Resolution**: Extracts UIDs from scene files for proper target references
|
||||
|
||||
## Files
|
||||
|
||||
- `plugin.cfg` - Plugin metadata
|
||||
- `transition_configurator.gd` - Main EditorPlugin entry point
|
||||
- `transition_inspector_plugin.gd` - Adds UI to TransitionPiece inspector
|
||||
- `config_dialog.gd` - Configuration dialog with search and selection
|
||||
- `fuzzy_search.gd` - Fuzzy text matching utility
|
||||
|
||||
## Requirements
|
||||
|
||||
- Godot 4.x
|
||||
- TransitionPiece class (class_name TransitionPiece)
|
||||
- Room scenes in `res://scenes/` following `kq4_XXX_*` naming convention
|
||||
|
||||
## Notes
|
||||
|
||||
- The plugin uses UndoRedo for source scene changes
|
||||
- Destination scene changes are saved and reloaded automatically
|
||||
- If bidirectional update fails, the source changes are still applied
|
||||
417
addons/transition_configurator/config_dialog.gd
Normal file
417
addons/transition_configurator/config_dialog.gd
Normal file
@@ -0,0 +1,417 @@
|
||||
@tool
|
||||
extends Window
|
||||
|
||||
const FUZZY_SEARCH = preload("res://addons/transition_configurator/fuzzy_search.gd")
|
||||
const TRANSITION_PIECE_SCENE = "res://TransitionPiece.tscn"
|
||||
|
||||
var transition_piece: TransitionPiece = null
|
||||
|
||||
# UI elements
|
||||
@onready var search_input: LineEdit
|
||||
@onready var room_list: ItemList
|
||||
@onready var room_filter_label: Label
|
||||
@onready var arrival_list: ItemList
|
||||
@onready var arrival_label: Label
|
||||
@onready var status_label: Label
|
||||
@onready var apply_button: Button
|
||||
|
||||
# Data
|
||||
var all_rooms: Array[Dictionary] = []
|
||||
var current_room_path: String = ""
|
||||
|
||||
func _ready() -> void:
|
||||
title = "Configure Transition Exit"
|
||||
min_size = Vector2i(900, 600)
|
||||
|
||||
var main_vbox = VBoxContainer.new()
|
||||
main_vbox.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
main_vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
main_vbox.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
main_vbox.add_theme_constant_override("separation", 10)
|
||||
add_child(main_vbox)
|
||||
|
||||
# Add margins
|
||||
var margin = MarginContainer.new()
|
||||
margin.add_theme_constant_override("margin_left", 20)
|
||||
margin.add_theme_constant_override("margin_right", 20)
|
||||
margin.add_theme_constant_override("margin_top", 20)
|
||||
margin.add_theme_constant_override("margin_bottom", 20)
|
||||
margin.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
margin.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
main_vbox.add_child(margin)
|
||||
|
||||
var content_vbox = VBoxContainer.new()
|
||||
content_vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
content_vbox.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
content_vbox.add_theme_constant_override("separation", 15)
|
||||
margin.add_child(content_vbox)
|
||||
|
||||
# Title
|
||||
var title_label = Label.new()
|
||||
title_label.text = "Configure Exit for: %s" % (transition_piece.name if transition_piece else "Unknown")
|
||||
title_label.add_theme_font_size_override("font_size", 18)
|
||||
content_vbox.add_child(title_label)
|
||||
|
||||
# Main content - HSplitContainer
|
||||
var split = HSplitContainer.new()
|
||||
split.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
split.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
split.split_offset = 300
|
||||
content_vbox.add_child(split)
|
||||
|
||||
# Left side - Room selection
|
||||
var left_vbox = VBoxContainer.new()
|
||||
left_vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
left_vbox.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
left_vbox.add_theme_constant_override("separation", 10)
|
||||
split.add_child(left_vbox)
|
||||
|
||||
var search_label = Label.new()
|
||||
search_label.text = "Search Rooms:"
|
||||
left_vbox.add_child(search_label)
|
||||
|
||||
search_input = LineEdit.new()
|
||||
search_input.placeholder_text = "Type to filter rooms..."
|
||||
search_input.text_changed.connect(_on_search_changed)
|
||||
left_vbox.add_child(search_input)
|
||||
|
||||
room_filter_label = Label.new()
|
||||
room_filter_label.text = "Showing all rooms"
|
||||
room_filter_label.add_theme_color_override("font_color", Color(0.7, 0.7, 0.7))
|
||||
left_vbox.add_child(room_filter_label)
|
||||
|
||||
room_list = ItemList.new()
|
||||
room_list.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
room_list.item_selected.connect(_on_room_selected)
|
||||
left_vbox.add_child(room_list)
|
||||
|
||||
# Right side - Arrival point selection
|
||||
var right_vbox = VBoxContainer.new()
|
||||
right_vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
right_vbox.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
right_vbox.add_theme_constant_override("separation", 10)
|
||||
split.add_child(right_vbox)
|
||||
|
||||
arrival_label = Label.new()
|
||||
arrival_label.text = "Select a room to see arrival points"
|
||||
arrival_label.add_theme_font_size_override("font_size", 14)
|
||||
right_vbox.add_child(arrival_label)
|
||||
|
||||
arrival_list = ItemList.new()
|
||||
arrival_list.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
arrival_list.item_selected.connect(_validate_selection)
|
||||
right_vbox.add_child(arrival_list)
|
||||
|
||||
# Status label
|
||||
status_label = Label.new()
|
||||
status_label.text = ""
|
||||
status_label.add_theme_color_override("font_color", Color(1, 0.5, 0.2))
|
||||
content_vbox.add_child(status_label)
|
||||
|
||||
# Buttons
|
||||
var button_hbox = HBoxContainer.new()
|
||||
button_hbox.alignment = BoxContainer.ALIGNMENT_END
|
||||
button_hbox.add_theme_constant_override("separation", 10)
|
||||
content_vbox.add_child(button_hbox)
|
||||
|
||||
apply_button = Button.new()
|
||||
apply_button.text = "Apply Changes"
|
||||
apply_button.disabled = true
|
||||
apply_button.custom_minimum_size = Vector2(150, 40)
|
||||
apply_button.pressed.connect(_on_apply)
|
||||
button_hbox.add_child(apply_button)
|
||||
|
||||
var cancel_button = Button.new()
|
||||
cancel_button.text = "Cancel"
|
||||
cancel_button.custom_minimum_size = Vector2(100, 40)
|
||||
cancel_button.pressed.connect(hide)
|
||||
button_hbox.add_child(cancel_button)
|
||||
|
||||
# Close on escape
|
||||
close_requested.connect(hide)
|
||||
|
||||
func populate_rooms() -> void:
|
||||
all_rooms.clear()
|
||||
room_list.clear()
|
||||
|
||||
var scene_files = _find_room_scenes()
|
||||
for file_path in scene_files:
|
||||
var room_name = file_path.get_file().get_basename()
|
||||
var uid = _get_scene_uid(file_path)
|
||||
all_rooms.append({
|
||||
"name": room_name,
|
||||
"path": file_path,
|
||||
"uid": uid
|
||||
})
|
||||
|
||||
# Sort by name
|
||||
all_rooms.sort_custom(func(a, b): return a["name"] < b["name"])
|
||||
|
||||
_update_room_list(all_rooms)
|
||||
|
||||
room_filter_label.text = "Showing %d rooms" % all_rooms.size()
|
||||
|
||||
func _update_room_list(rooms: Array) -> void:
|
||||
room_list.clear()
|
||||
for room in rooms:
|
||||
room_list.add_item(room["name"])
|
||||
room_list.set_item_metadata(room_list.item_count - 1, room)
|
||||
|
||||
func _on_search_changed(new_text: String) -> void:
|
||||
if new_text.is_empty():
|
||||
_update_room_list(all_rooms)
|
||||
room_filter_label.text = "Showing all %d rooms" % all_rooms.size()
|
||||
else:
|
||||
var filtered = FUZZY_SEARCH.sort_by_match(new_text, all_rooms, "name")
|
||||
_update_room_list(filtered)
|
||||
room_filter_label.text = "Showing %d of %d rooms" % [filtered.size(), all_rooms.size()]
|
||||
|
||||
func _on_room_selected(index: int) -> void:
|
||||
arrival_list.clear()
|
||||
apply_button.disabled = true
|
||||
|
||||
if index < 0 or index >= room_list.item_count:
|
||||
return
|
||||
|
||||
var room_data = room_list.get_item_metadata(index)
|
||||
current_room_path = room_data["path"]
|
||||
|
||||
arrival_label.text = "Arrival Points in %s:" % room_data["name"]
|
||||
|
||||
# Find all TransitionPieces in the selected room
|
||||
var transitions = _find_transition_pieces_in_scene(current_room_path)
|
||||
|
||||
if transitions.is_empty():
|
||||
arrival_list.add_item("No TransitionPieces found in this room")
|
||||
arrival_list.set_item_disabled(0, true)
|
||||
status_label.text = "Warning: No arrival points available"
|
||||
else:
|
||||
for trans in transitions:
|
||||
arrival_list.add_item(trans["name"])
|
||||
arrival_list.set_item_metadata(arrival_list.item_count - 1, trans)
|
||||
status_label.text = ""
|
||||
|
||||
_validate_selection()
|
||||
|
||||
func _validate_selection() -> void:
|
||||
var room_idx = room_list.get_selected_items()
|
||||
var arrival_idx = arrival_list.get_selected_items()
|
||||
|
||||
if room_idx.is_empty():
|
||||
apply_button.disabled = true
|
||||
status_label.text = "Please select a destination room"
|
||||
return
|
||||
|
||||
if arrival_idx.is_empty():
|
||||
apply_button.disabled = true
|
||||
status_label.text = "Please select an arrival point"
|
||||
return
|
||||
|
||||
var arrival_data = arrival_list.get_item_metadata(arrival_idx[0])
|
||||
if arrival_data == null or arrival_data.is_empty():
|
||||
apply_button.disabled = true
|
||||
status_label.text = "Invalid arrival point selected"
|
||||
return
|
||||
|
||||
apply_button.disabled = false
|
||||
status_label.text = ""
|
||||
|
||||
func _on_apply() -> void:
|
||||
var room_indices = room_list.get_selected_items()
|
||||
var arrival_indices = arrival_list.get_selected_items()
|
||||
|
||||
if room_indices.is_empty() or arrival_indices.is_empty():
|
||||
return
|
||||
|
||||
var room_data = room_list.get_item_metadata(room_indices[0])
|
||||
var arrival_data = arrival_list.get_item_metadata(arrival_indices[0])
|
||||
|
||||
var dest_path = room_data["path"]
|
||||
var dest_uid = room_data["uid"]
|
||||
var arrival_node_name = arrival_data["name"]
|
||||
|
||||
if transition_piece == null:
|
||||
push_error("TransitionPiece is null")
|
||||
return
|
||||
|
||||
# Update the source TransitionPiece
|
||||
var undo_redo = EditorInterface.get_editor_undo_redo()
|
||||
undo_redo.create_action("Configure Transition Exit")
|
||||
|
||||
# Update target and appear_at_node on source
|
||||
undo_redo.add_undo_property(transition_piece, "target", transition_piece.target)
|
||||
undo_redo.add_undo_property(transition_piece, "appear_at_node", transition_piece.appear_at_node)
|
||||
undo_redo.add_do_property(transition_piece, "target", dest_uid)
|
||||
undo_redo.add_do_property(transition_piece, "appear_at_node", arrival_node_name)
|
||||
|
||||
# Apply changes to source
|
||||
transition_piece.target = dest_uid
|
||||
transition_piece.appear_at_node = arrival_node_name
|
||||
transition_piece.notify_property_list_changed()
|
||||
|
||||
# Try to update bidirectionally
|
||||
var bidirectional_result = _update_bidirectional_connection(dest_path, arrival_node_name)
|
||||
|
||||
undo_redo.commit_action()
|
||||
|
||||
if bidirectional_result.is_empty():
|
||||
print("Transition configured successfully!")
|
||||
else:
|
||||
push_warning(bidirectional_result)
|
||||
|
||||
hide()
|
||||
|
||||
func _update_bidirectional_connection(dest_scene_path: String, arrival_node_name: String) -> String:
|
||||
"""
|
||||
Update the destination scene to create/update the return transition.
|
||||
Returns empty string on success, error message on failure.
|
||||
"""
|
||||
var current_scene_path = transition_piece.get_tree().edited_scene_root.scene_file_path
|
||||
var current_room_name = transition_piece.name
|
||||
|
||||
# Load destination packed scene
|
||||
var dest_packed = load(dest_scene_path) as PackedScene
|
||||
if dest_packed == null:
|
||||
return "Failed to load destination scene: %s" % dest_scene_path
|
||||
|
||||
var dest_state = dest_packed.get_state()
|
||||
var found_transition_idx = -1
|
||||
var found_transition_node_name = ""
|
||||
|
||||
# Find existing TransitionPiece that points back to current room
|
||||
for i in range(dest_state.get_node_count()):
|
||||
var node_instance = dest_state.get_node_instance(i)
|
||||
if node_instance == null:
|
||||
continue
|
||||
|
||||
# Check if this is a TransitionPiece instance
|
||||
var instance_path = node_instance.resource_path
|
||||
if instance_path != TRANSITION_PIECE_SCENE:
|
||||
continue
|
||||
|
||||
var node_name = dest_state.get_node_name(i)
|
||||
if node_name == current_room_name:
|
||||
found_transition_idx = i
|
||||
found_transition_node_name = node_name
|
||||
break
|
||||
|
||||
# Reload scene for editing
|
||||
var dest_scene = dest_packed.instantiate()
|
||||
if dest_scene == null:
|
||||
return "Failed to instantiate destination scene"
|
||||
|
||||
var return_transition: TransitionPiece = null
|
||||
|
||||
if found_transition_idx >= 0:
|
||||
# Update existing transition
|
||||
return_transition = dest_scene.get_node_or_null(found_transition_node_name)
|
||||
if return_transition == null:
|
||||
return "Found transition index but couldn't get node: %s" % found_transition_node_name
|
||||
else:
|
||||
# Create new TransitionPiece
|
||||
return_transition = load(TRANSITION_PIECE_SCENE).instantiate()
|
||||
return_transition.name = current_room_name
|
||||
dest_scene.add_child(return_transition)
|
||||
return_transition.owner = dest_scene
|
||||
|
||||
# Update the return transition properties
|
||||
var current_scene_uid = _get_scene_uid(current_scene_path)
|
||||
return_transition.target = current_scene_uid
|
||||
return_transition.appear_at_node = transition_piece.name
|
||||
|
||||
# Save the modified scene
|
||||
var new_packed = PackedScene.new()
|
||||
var pack_result = new_packed.pack(dest_scene)
|
||||
if pack_result != OK:
|
||||
dest_scene.free()
|
||||
return "Failed to pack modified scene: %s" % error_string(pack_result)
|
||||
|
||||
var save_result = ResourceSaver.save(new_packed, dest_scene_path)
|
||||
dest_scene.free()
|
||||
|
||||
if save_result != OK:
|
||||
return "Failed to save destination scene: %s" % error_string(save_result)
|
||||
|
||||
# Reload the scene in editor
|
||||
EditorInterface.reload_scene_from_path(dest_scene_path)
|
||||
|
||||
return ""
|
||||
|
||||
func _find_room_scenes(path: String = "res://scenes/") -> PackedStringArray:
|
||||
var results: PackedStringArray = []
|
||||
var dir = DirAccess.open(path)
|
||||
|
||||
if not dir:
|
||||
push_error("Could not open directory: " + path)
|
||||
return results
|
||||
|
||||
dir.list_dir_begin()
|
||||
var file_name = dir.get_next()
|
||||
|
||||
while file_name != "":
|
||||
var full_path = path.path_join(file_name)
|
||||
|
||||
if dir.current_is_dir():
|
||||
if not file_name.begins_with(".") and file_name != "addons":
|
||||
results.append_array(_find_room_scenes(full_path))
|
||||
else:
|
||||
if file_name.ends_with(".tscn") and file_name.begins_with("kq4_"):
|
||||
results.append(full_path)
|
||||
|
||||
file_name = dir.get_next()
|
||||
|
||||
dir.list_dir_end()
|
||||
return results
|
||||
|
||||
func _get_scene_uid(scene_path: String) -> String:
|
||||
"""Get the UID for a scene file."""
|
||||
var file = FileAccess.open(scene_path, FileAccess.READ)
|
||||
if not file:
|
||||
# Fallback to using the path
|
||||
return scene_path
|
||||
|
||||
var first_line = file.get_line()
|
||||
file.close()
|
||||
|
||||
# Parse uid="uid://xxxxx" from the header
|
||||
var regex = RegEx.new()
|
||||
regex.compile('uid="(uid://[^"]+)"')
|
||||
var result = regex.search(first_line)
|
||||
|
||||
if result:
|
||||
return result.get_string(1)
|
||||
|
||||
# Fallback: use ResourceUID
|
||||
var uid_path = ResourceUID.path_to_uid(scene_path)
|
||||
if uid_path.begins_with("uid://"):
|
||||
return uid_path
|
||||
|
||||
return scene_path
|
||||
|
||||
func _find_transition_pieces_in_scene(scene_path: String) -> Array[Dictionary]:
|
||||
"""Find all TransitionPiece nodes in a scene file."""
|
||||
var results: Array[Dictionary] = []
|
||||
|
||||
var packed = load(scene_path) as PackedScene
|
||||
if packed == null:
|
||||
return results
|
||||
|
||||
var state = packed.get_state()
|
||||
|
||||
for i in range(state.get_node_count()):
|
||||
var node_instance = state.get_node_instance(i)
|
||||
if node_instance == null:
|
||||
continue
|
||||
|
||||
# Check if this is a TransitionPiece instance
|
||||
var instance_path = node_instance.resource_path
|
||||
if instance_path != TRANSITION_PIECE_SCENE:
|
||||
continue
|
||||
|
||||
var node_name = state.get_node_name(i)
|
||||
results.append({
|
||||
"name": node_name,
|
||||
"index": i
|
||||
})
|
||||
|
||||
return results
|
||||
1
addons/transition_configurator/config_dialog.gd.uid
Normal file
1
addons/transition_configurator/config_dialog.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://1zh0uy36mkm11
|
||||
64
addons/transition_configurator/fuzzy_search.gd
Normal file
64
addons/transition_configurator/fuzzy_search.gd
Normal file
@@ -0,0 +1,64 @@
|
||||
extends RefCounted
|
||||
class_name FuzzySearch
|
||||
|
||||
## Simple fuzzy matching algorithm for text search
|
||||
## Returns a score between 0 (no match) and 1 (perfect match)
|
||||
|
||||
static func match(query: String, target: String) -> float:
|
||||
if query.is_empty():
|
||||
return 1.0
|
||||
|
||||
query = query.to_lower()
|
||||
target = target.to_lower()
|
||||
|
||||
# Exact match
|
||||
if target == query:
|
||||
return 1.0
|
||||
|
||||
# Contains query as substring
|
||||
if target.find(query) != -1:
|
||||
return 0.9
|
||||
|
||||
# Fuzzy match - all characters in query must appear in order in target
|
||||
var query_idx: int = 0
|
||||
var target_idx: int = 0
|
||||
var matches: int = 0
|
||||
var consecutive_bonus: float = 0.0
|
||||
var last_match_idx: int = -1
|
||||
|
||||
while query_idx < query.length() and target_idx < target.length():
|
||||
if query[query_idx] == target[target_idx]:
|
||||
matches += 1
|
||||
if last_match_idx != -1 and target_idx == last_match_idx + 1:
|
||||
consecutive_bonus += 0.1
|
||||
last_match_idx = target_idx
|
||||
query_idx += 1
|
||||
target_idx += 1
|
||||
|
||||
# All characters matched
|
||||
if query_idx == query.length():
|
||||
var base_score: float = float(matches) / float(query.length())
|
||||
var bonus: float = min(consecutive_bonus, 0.3) # Cap bonus at 0.3
|
||||
return base_score * 0.7 + bonus
|
||||
|
||||
return 0.0
|
||||
|
||||
## Sort an array of items by fuzzy match score
|
||||
## items should be Dictionary with at least a "name" or specified key field
|
||||
static func sort_by_match(query: String, items: Array, key: String = "name") -> Array:
|
||||
if query.is_empty():
|
||||
return items.duplicate()
|
||||
|
||||
var scored: Array[Dictionary] = []
|
||||
for item in items:
|
||||
var item_name: String = item.get(key, "")
|
||||
var score: float = match(query, item_name)
|
||||
if score > 0:
|
||||
scored.append({"item": item, "score": score})
|
||||
|
||||
# Sort by score descending
|
||||
scored.sort_custom(func(a: Dictionary, b: Dictionary) -> bool:
|
||||
return a["score"] > b["score"]
|
||||
)
|
||||
|
||||
return scored.map(func(s): return s["item"])
|
||||
1
addons/transition_configurator/fuzzy_search.gd.uid
Normal file
1
addons/transition_configurator/fuzzy_search.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://zeiq1cspn49j
|
||||
7
addons/transition_configurator/plugin.cfg
Normal file
7
addons/transition_configurator/plugin.cfg
Normal file
@@ -0,0 +1,7 @@
|
||||
[plugin]
|
||||
|
||||
name="Transition Configurator"
|
||||
description="Easy configuration of TransitionPiece exits with fuzzy search and bidirectional wiring"
|
||||
author="Game Developer"
|
||||
version="1.0"
|
||||
script="transition_configurator.gd"
|
||||
14
addons/transition_configurator/transition_configurator.gd
Normal file
14
addons/transition_configurator/transition_configurator.gd
Normal file
@@ -0,0 +1,14 @@
|
||||
@tool
|
||||
extends EditorPlugin
|
||||
|
||||
const INSPECTOR_PLUGIN = preload("res://addons/transition_configurator/transition_inspector_plugin.gd")
|
||||
|
||||
var inspector_plugin: EditorInspectorPlugin
|
||||
|
||||
func _enter_tree() -> void:
|
||||
inspector_plugin = INSPECTOR_PLUGIN.new()
|
||||
add_inspector_plugin(inspector_plugin)
|
||||
|
||||
func _exit_tree() -> void:
|
||||
remove_inspector_plugin(inspector_plugin)
|
||||
inspector_plugin = null
|
||||
@@ -0,0 +1 @@
|
||||
uid://3een9hfa2figm
|
||||
@@ -0,0 +1,43 @@
|
||||
@tool
|
||||
extends EditorInspectorPlugin
|
||||
|
||||
const CONFIG_DIALOG = preload("res://addons/transition_configurator/config_dialog.gd")
|
||||
|
||||
var config_dialog: Window = null
|
||||
|
||||
func _can_handle(object: Object) -> bool:
|
||||
return object is TransitionPiece
|
||||
|
||||
func _parse_begin(object: Object) -> void:
|
||||
var container = VBoxContainer.new()
|
||||
container.add_theme_constant_override("separation", 10)
|
||||
|
||||
# Header label
|
||||
var header = Label.new()
|
||||
header.text = "Exit Configuration"
|
||||
header.add_theme_font_size_override("font_size", 14)
|
||||
container.add_child(header)
|
||||
|
||||
# Configure button
|
||||
var button = Button.new()
|
||||
button.text = "Configure Exit..."
|
||||
button.custom_minimum_size = Vector2(200, 40)
|
||||
button.pressed.connect(_on_configure_pressed.bind(object))
|
||||
container.add_child(button)
|
||||
|
||||
# Separator
|
||||
var separator = HSeparator.new()
|
||||
separator.add_theme_constant_override("separation", 10)
|
||||
container.add_child(separator)
|
||||
|
||||
add_custom_control(container)
|
||||
|
||||
func _on_configure_pressed(transition_piece: TransitionPiece) -> void:
|
||||
if config_dialog == null:
|
||||
config_dialog = CONFIG_DIALOG.new()
|
||||
add_child(config_dialog)
|
||||
|
||||
config_dialog.transition_piece = transition_piece
|
||||
config_dialog.populate_rooms()
|
||||
|
||||
EditorInterface.popup_dialog_centered(config_dialog, Vector2i(900, 600))
|
||||
@@ -0,0 +1 @@
|
||||
uid://1c4ywc7gtm8u
|
||||
Reference in New Issue
Block a user