Each level in my project has a number of instanced scenes (or "props" as I call them), some of them enable the player to transition to another level.
A landing pad prop contains a property with a path to the target scene ("file_path"), assuming the pad has been activated, when the player lands on the pad, off they go to the new level.
Slightly more complex is the drum prop, which can be used with a switch prop to activate multiple other types of props, if the switch activates a landing pad and the drum in the switch has a "custom" property then the landing pad will take the player to the level specified in the "custom" property which overrides the landing pads "file_path" property.
Phew! as you can imagine even with a handful of level, working out how all this fits together soon gets out of hand... what I needed was a graph.
now that's better!
It was a little more work than I anticipated but, assuming you only use instanced scenes as a mechanism to move to different levels, then I have written it in a way that should be usable by others. The script is run using the file / run menu item in the script window. There are a number of variables you need to edit to make it work for your specific project.
var levels_path = "res://levels/"
var dot_path = "res://notes/graph.dot"
var png_path = "res://notes/world.png"
var exits: Array = [
[preload("res://props/landingpad/landingpad.gd"), "file_path"],
[preload("res://props/drum/drum.gd"), "custom"]
]
levels_path is the directory that contains your levels, I put each level in its own sub directory.
dot_path is the dot file that describes the graph that the script generates.
png_path is the output graphic of the graph.
exits is a 2d array that holds the script that identifies the instanced scene in a level (I couldn't work out how to identify of a scene that was instanced and where pointed to so I used its script) if any of your props don't have a script just add an empty one but do remember to remove any template functions as a function with just a pass does have an impact on speed.
hopefully it should just work! Here is the complete script.
@tool
extends EditorScript
var levels_path = "res://levels/"
var dot_path = "res://notes/graph.dot"
var png_path = "res://notes/world.png"
# couldn't figure out how to find out what a node is instanced from in a way I could get a path
# so for now just using its script to id it...
var exits: Array = [
[preload("res://props/landingpad/landingpad.gd"), "file_path"],
[preload("res://props/drum/drum.gd"), "custom"]
]
var file: FileAccess
func scan(path):
var dir = DirAccess.open(path)
if dir:
dir.list_dir_begin()
var file_name = dir.get_next()
var cd = ""
# iterate the path
while file_name != "":
# reenter if a dir
if dir.current_is_dir():
cd = dir.get_current_dir()
scan(cd+file_name)
else:
# is it a scene
if is_valid_scene_path(dir.get_current_dir()+"/"+file_name):
var scene_name = dir.get_current_dir() + "/" + file_name
var nodes = check_scene_for_instance(dir.get_current_dir()+"/"+file_name)
for node in nodes:
# check each exit type against the possible instance
for exit in exits:
# look for the exit property
if has_property(node, exit[1]):
# see if it points somewhere
var scene_path = node.get(exit[1])
if scene_path:
# if we can instance it, output
var scene = load(node.get(exit[1]))
if scene:
var inst = scene.instantiate()
if inst:
file.store_line("\""+scene_name + "\" -> \"" + inst.scene_file_path+"\" [label=\""+node.name+"\"]; ")
file_name = dir.get_next()
else:
print("An error occurred when trying to access the path.", path)
func has_property(node: Node, property_name: String) -> bool:
for prop in node.get_property_list():
if prop.name == property_name:
return true
return false
func is_valid_scene_path(scene_path: String) -> bool:
if not ResourceLoader.exists(scene_path):
return false # Path does not exist.
var resource = ResourceLoader.load(scene_path)
if resource == null:
return false # Resource could not be loaded.
# Check if the loaded resource is a PackedScene.
if resource is PackedScene:
return true # It's a valid PackedScene.
return false # Not a PackedScene.
func check_scene_for_instance(scene_path: String):
var scene = load(scene_path) # Load the scene
if not scene:
push_error("Failed to load scene: " + scene_path)
return false
var instance = scene.instantiate() # Create an instance
if not instance:
push_error("Failed to instantiate scene: " + scene_path)
return false
return find_instances(instance) # Search for the target scene instance
func find_instances(node: Node):
var nodes:Array[Node] = []
for child in node.get_children():
for exit in exits:
if child.get_script() == exit[0]:
nodes.append(child)
return nodes
func _run():
# really? you can't clear the console
"""for i in range(100):
if i % 2:
print(" ")
else:
print(" ")"""
#TODO file requestor ???
file = FileAccess.open(dot_path, FileAccess.WRITE)
file.store_line("digraph Scenes {")
scan(levels_path)
file.store_line("}")
file.close()
print(dot_path, " written")
# assumes dot executable is in path
# tested on Linux only
var project_path = ProjectSettings.globalize_path("res://")
var relative_path = dot_path.replace("res://", "")
var real_dot = project_path.path_join(relative_path)
relative_path = png_path.replace("res://", "")
var real_png = project_path.path_join(relative_path)
var args = ["-Tpng", real_dot, "-o", real_png]
var output = []
print("running dot...")
var exit_code = OS.execute("dot", args, output, true)
print("output: ", output)
print("exit_code: ", exit_code)
Enjoy !