Xchange-scene is a robust, high level interface for manipulating child scenes below a given Node
and indexing them.
Disclaimer: In the following, when talking about scenes, often it's about the instance of the scene, which is added as a child scene to the tree, and which is also a Node.
Inspired by this part of the godot docs.
Scenes can be in these states:
state | function used | consequence |
---|---|---|
ACTIVE | .add_child(Node) |
running and visible |
HIDDEN | Node.hide() |
running and hidden |
STOPPED | .remove_child(Node) |
not running, but still in memory |
FREE | Node.free() |
no longer in memory and no longer indexed |
In the example/main.gd you can see all of the features in action.
Here is a first taste:
var scene1 = preload("scene1.tscn")
# x adds and removes scenes below $World
# adds itself to the tree below $World
var x = XScene.new($World)
# this is a reference to $World
var r = x.root
# add_scene takes a PackedScene or a Node
# without a key specified it indexes automatically with integers starting at 1
# (this can be changed to 0)
# default method is ACTIVE, using add_child()
x.add_scene(scene1)
# uses add_child() and .hide()
x.add_scene(scene1, "a", x.HIDDEN)
# just instances and indexes the scene
x.add_scene(scene1, "stopped_s1", x.STOPPED)
get_node("/root").print_tree_pretty()
# ┖╴root
# ┖╴Main
# ┠╴Gui
# ┃ ┖╴ColorRect
# ┠╴World
# ┃ ┠╴Node2D <- was added by the editor and isn't indexed by default
# ┃ ┃ ┖╴Node2D
# ┃ ┃ ┖╴Node2D
# ┃ ┃ ┖╴icon
# ┃ ┠╴@@2 <- x instance
# ┃ ┠╴@Node2D@3 <- 1
# ┃ ┃ ┖╴Node2D
# ┃ ┃ ┖╴Node2D
# ┃ ┃ ┖╴icon
# ┃ ┖╴@Node2D@4 <- "a" in the tree but hidden
# ┃ ┖╴Node2D
# ┃ ┖╴Node2D
# ┃ ┖╴icon
# ┖╴Test
# "stopped_s1" isnt in the tree
print(x.scenes)
# {1:{scene:[Node2D:1227], state:0}, -> ACTIVE
# a:{scene:[Node2D:1231], state:1}, -> HIDDEN
# stopped_s1:{scene:[Node2D:1235], state:2}} -> STOPPED
# uses remove_child()
x.remove_scene(1, x.STOPPED)
get_node("/root").print_tree_pretty()
# ┠╴World
# ┃ ┠╴Node2D
# ┃ ┃ ┖╴Node2D
# ┃ ┃ ┖╴Node2D
# ┃ ┃ ┖╴icon
# ┃ ┠╴@@2
# ┃ ┖╴@Node2D@4 <- "a" still in tree
# ┃ ┖╴Node2D
# ┃ ┖╴Node2D
# ┃ ┖╴icon
# ┖╴Test
# 1 also is no longer in the tree
# make all STOPPED scenes ACTIVE
# mind the plural
x.show_scenes(x.stopped)
get_node("/root").print_tree_pretty()
# ┠╴World
# ┃ ┠╴Node2D
# ┃ ┃ ┖╴Node2D
# ┃ ┃ ┖╴Node2D
# ┃ ┃ ┖╴icon
# ┃ ┠╴@@2
# ┃ ┠╴@Node2D@4 <- "a"
# ┃ ┃ ┖╴Node2D
# ┃ ┃ ┖╴Node2D
# ┃ ┃ ┖╴icon
# ┃ ┠╴@Node2D@3 <- 1
# ┃ ┃ ┖╴Node2D
# ┃ ┃ ┖╴Node2D
# ┃ ┃ ┖╴icon
# ┃ ┖╴@Node2D@5 <- "stopped_s1"
# ┃ ┖╴Node2D
# ┃ ┖╴Node2D
# ┃ ┖╴icon
# ┖╴Test
# exchange scene, makes "a" ACTIVE, and uses .free() on "stopped_s1"
# it defaults to FREE, the argument isn't necessary here
x.x_scene("a", "stopped_s1", x.FREE)
get_node("/root").print_tree_pretty()
# ┠╴World
# ┃ ┠╴Node2D
# ┃ ┃ ┖╴Node2D
# ┃ ┃ ┖╴Node2D
# ┃ ┃ ┖╴icon
# ┃ ┠╴@@2
# ┃ ┠╴@Node2D@4 <- "a" no longer hidden
# ┃ ┃ ┖╴Node2D
# ┃ ┃ ┖╴Node2D
# ┃ ┃ ┖╴icon
# ┃ ┖╴@Node2D@3 <- 1
# ┃ ┖╴Node2D
# ┃ ┖╴Node2D
# ┃ ┖╴icon
# ┖╴Test
# "stopped_s1" was freed and is no longer indexed
# to access ("x"ess) the scene/node of "a" directly
x.x("a").hide()
# to access all hidden scenes directly, returns an array of nodes
print(x.xs(x.HIDDEN))
# [[Node2D:1231]] <- this is the node/scene of "a" in an array
# note that a was hidden externally and is still indexed correctly,
# this is done lazily, only when accessing that node
# put x.root and everything indexed into a file using PackedScene.pack() and ResourceSaver.save()
x.pack_root("res://example/test.scn")
# this can be loaded later, it includes x.root
# .free() everything indexed by x, remove_scene/s defaults to FREE
# mind the plural
x.remove_scenes(x.scenes.keys())
- Indexing and easy access
- All scenes managed by this plugin will be indexed in a dictionary and can be accessed either one by one or grouped by state.
- Lazy validity checks on access
- The validity and state of indexed Nodes is always checked before accessed (lazily). This goes for external
.free()
,.hide()
,.show()
or.remove_child(Node)
.
- The validity and state of indexed Nodes is always checked before accessed (lazily). This goes for external
- Active sync
- You can keep in sync with external additions to the tree, meaning external
add_child(Node)
. (this can be slow, see Caveats)
- You can keep in sync with external additions to the tree, meaning external
- Deferred calls
- All of the tree changes can be called deferred for (thread) safety.
- Ownership management and
pack()
supportNode.owner
of nodes added belowroot
can be set recursively, so you canpack_root()
and save the whole created scene to file for later.
- Nested Scenes
- Instances of the XScene class will add themselves below
root
, thus freeing themselves whenroot
is freed.
- Instances of the XScene class will add themselves below
- Bulk functions
- To add/show/remove many nodes at once
See this part of the godot docs
get_tree().change_scene()
exchanges the whole current scene against another whole scene. Only the
AutoLoads are transferred automatically. If that doesn't cut it for you, this
plugin might be for you.
With the commands from this plugin you can more granularly control, which part of the whole current scene you want to change and how you want to change it. See example/main.gd for possibilities. This is especially interesting for better control over memory.
Made with Godot version 3.3.2.stable.official
This repo is in a Godot Plugin format.
You can:
- Download a .zip of this repo and unpack it into your project
The version in the AsserLib is not up to date
For more details, read the godot docs on installing Plugins
Don't forget to enable it in your project settings!
To run the examples yourself, you can
- Clone this repo
git clone https://github.com/aMOPel/godot-xchange-scene.git xscene
- Run godot in it (eg. using linux and bash)
cd xscene; godot --editor
- Comment and uncomment the functions in example/main.gd
_ready()
- Run the main scene in godot
In the example/main.gd you can see how to use it. There are little tutorials split in functions with a lot of comments to explain everything in detail.
Also in docs/XScene.md is a full markdown reference built from the docstrings. However it is hard to read on Github because it merges the linebreaks. Either read it in an editor on read it "Raw" on Github.
# example/main.gd
# gives an instance of XScene
# it adds itself below $World
# it doesn't index itself
x = XScene.new($World)
# ┖╴root
# ┖╴Main
# ┠╴Gui
# ┃ ┖╴ColorRect
# ┖╴World <- acts below World
# ┖╴@@2 <- x
This will give you an instance of XScene
(the main class), which acts and sits below
World
.
from\to | ACTIVE = 0 | HIDDEN = 1 | STOPPED = 2 | FREE = 3 |
---|---|---|---|---|
ACTIVE | --- | remove_scene(key, HIDDEN) |
remove_scene(key, STOPPED) |
remove_scene(key, FREE) |
HIDDEN | show_scene(key) |
--- | remove_scene(key, STOPPED) |
remove_scene(key, FREE) |
STOPPED | show_scene(key) |
--- | --- | remove_scene(key, FREE) |
FREE | add_scene(scene, key, ACTIVE) |
add_scene(scene, key, HIDDEN) |
add_scene(scene, key, STOPPED) |
--- |
The states are just an enum
, so you can also use the integers, but writing out
the names helps readability of your code.
Normally the visibility of a node (HIDDEN) and if it's in the tree or not
(STOPPED), are unrelated. However, in this plugin it's either or. Meaning, when
a hidden scene is stopped, its visibility will be reset to true
. And when a
stopped scene happened to also be hidden, show_scene
will reset its visibility
to true
.
NOTE: Although this plugin resembles a state machine, it isn't implemented as one.
- This plugin adds an overhead to adding and removing scenes. When you add or remove in high quantities, you should consider using the built-in commands, if you don't have to index the scenes so thoroughly.
- The sync feature adds more overhead and should also only be used for small quantities of scenes. Mind that this feature checks for every addition in the whole tree. So if you were to have a few
XScene
instances with sync enabled, every instances will make checks and add even more overhead
Here are some really basic benchmarks taken on my mediocre personal PC.
This benchmark comes from adding (ACTIVE) and removing (FREE) 1000 instances of a scene
that consists of 4 nodes below root
. Every test was done 10 times and the
time was averaged. In example/main.gd test_time()
you can see the code used.
X | no sync | sync |
---|---|---|
add_child() |
0.011148 | 0.020159 |
add_scene(ACTIVE) |
0.014747 | 0.017216 |
free() |
0.090902 | 0.098739 |
remove_scene(FREE) |
0.098556 | 0.099513 |
These measurements aren't exactly statistically significant, but they give a good idea of the overhead added by this plugin, especially when using sync. Note that the overhead is more or less independent of sync when removing scenes.
- You can open an issue if you're missing a feature