You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A request from our learning team, building on #909:
User Story
When I copy a template folder (intro, outro, minigames),
I want to automatically duplicate all included files, subfolders, and referenced resources (e.g., .tres dialogue files, sprite frames, tile sets), with scenes relinked to these new copies,
So I can safely edit my own StoryQuest without breaking templates or the base game.
Requirements
Full duplication:
Copy the entire folder structure (intro, outro, 3 minigames)
As more mini-games are added, create the option to duplicate mini-games as a standalone process.
Include all nested subfolders and files.
Resource duplication:
Duplicate all resources referenced by those scenes: .tres files, sprite frames, tile sets, etc.
Resource relinking:
Ensure the new scenes point to their duplicated resources, not originals.
No references in the learner’s copy should remain tied to the template folder or base game files.
Naming & organization:
Place copies under a new learner-specific folder.
(Optional) support renaming all instances of “template” with a chosen StoryQuest name (not just removing the word “template” from the duplicated files).
Robustness
Learners should never need to manually rename or relink files.
The copied folder should be ready to open and edit immediately.
I'm trying to figure out if we can do something a little less ad-hoc than the current implementation.
If you have a Resource (which can be a PackedScene) you can get a list of its external dependencies with ResourceLoader.get_dependencies(), which conceptually gives a list of tuples of (uid, "", path) (though the tuple is actually a string, "uid::::path", for reasons unknown).
If you have a PackedScene you can look at its SceneState to find which of its child nodes are instances of other scenes with SceneState.get_node_instance().
You can also traverse all the properties and check their values; which we could use to find other referenced resources such as SpriteFrames.
Of course Godot doesn't know that cinematic.gd and collectible_item.tscn's next_scene properties are actually UIDs of other scenes so we'll have to special case them. Ditto Quest's first_scene.
So here's my idea:
Start with the quest.tres file;
Recursively walk the graph of referenced resources, ignoring any that are not under res://scenes/quests/story_quests/NO_EDIT/
Flatten this out to a depth-first list in an order where each element only depends on elements earlier in the list
Make a dictionary duplicated mapping original UID to new UID
For each element in the list:
Duplicate it (which may mean copying the underlying file for .png files or .dialogue files, or using Resource.duplicate() for everything else
For resources, rewrite all references to other resources with the remapped UID from duplicated
Save this element
Update duplicated to map the old UID to the copy's UID
We made the decision that player.tscn's default sprite_frames should be the template. This actually makes the plugin harder to implement because when copying a scene, you have to recurse into the scenes it instantiates, even those that don't live in the template, to see if they reference something in the template.
I wonder if we should revisit this; but not today.
Actually after working out a few bugs, it's easy enough to support both cases. But I noticed that we are inconsistent: the throwing enemy scene's default sprite is the template, but the bucket default sprite is the one from the main game. We should probably be consistent one way or another. I'll file a followup.
Activity
wjt commentedon Sep 26, 2025
I think this would be easier to do if the resources were not saved as
.tresfiles but saved as sub-resources of the scene ("make unique").The dialogue plugin doesn't support this for dialogue files, but I opened an upstream ticket about it nathanhoad/godot_dialogue_manager#979 and had an interesting discussion on the Godot Discord to try to understand how hard it would be.
wjt commentedon Oct 3, 2025
I'm trying to figure out if we can do something a little less ad-hoc than the current implementation.
If you have a Resource (which can be a PackedScene) you can get a list of its external dependencies with ResourceLoader.get_dependencies(), which conceptually gives a list of tuples of (uid, "", path) (though the tuple is actually a string,
"uid::::path", for reasons unknown).If you have a PackedScene you can look at its
SceneStateto find which of its child nodes are instances of other scenes withSceneState.get_node_instance().You can also traverse all the properties and check their values; which we could use to find other referenced resources such as
SpriteFrames.Of course Godot doesn't know that
cinematic.gdandcollectible_item.tscn'snext_sceneproperties are actually UIDs of other scenes so we'll have to special case them. Ditto Quest'sfirst_scene.So here's my idea:
quest.tresfile;res://scenes/quests/story_quests/NO_EDIT/duplicatedmapping original UID to new UID.pngfiles or.dialoguefiles, or usingResource.duplicate()for everything elseduplicatedduplicatedto map the old UID to the copy's UIDwjt commentedon Oct 14, 2025
The approach above does actually work!
We made the decision that
player.tscn's defaultsprite_framesshould be the template. This actually makes the plugin harder to implement because when copying a scene, you have to recurse into the scenes it instantiates, even those that don't live in the template, to see if they reference something in the template.I wonder if we should revisit this; but not today.
wjt commentedon Oct 14, 2025
Actually after working out a few bugs, it's easy enough to support both cases. But I noticed that we are inconsistent: the throwing enemy scene's default sprite is the template, but the bucket default sprite is the one from the main game. We should probably be consistent one way or another. I'll file a followup.
storyquest_bootstrap: Copy template recursively
storyquest_bootstrap: Copy template recursively
storyquest_bootstrap: Copy template recursively