Skip to content

Latest commit

 

History

History
284 lines (229 loc) · 10.5 KB

README.md

File metadata and controls

284 lines (229 loc) · 10.5 KB

xchange-scene

xchange-scene

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.

TL;DR

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())

Features

  • 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).
  • Active sync
    • You can keep in sync with external additions to the tree, meaning external add_child(Node). (this can be slow, see Caveats)
  • Deferred calls
    • All of the tree changes can be called deferred for (thread) safety.
  • Ownership management and pack() support
    • Node.owner of nodes added below root can be set recursively, so you can pack_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 when root is freed.
  • Bulk functions
    • To add/show/remove many nodes at once

Why not just use get_tree().change_scene()?

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.

Installation

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!

Run examples

To run the examples yourself, you can

  1. Clone this repo git clone https://github.com/aMOPel/godot-xchange-scene.git xscene
  2. Run godot in it (eg. using linux and bash) cd xscene; godot --editor
  3. Comment and uncomment the functions in example/main.gd _ready()
  4. Run the main scene in godot

Usage

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.

Transistions

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.

Caveats

  • 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.

TODO

  • You can open an issue if you're missing a feature

Attributions

Icon made by Freepik from Flaticon