0 votes

Situation:
I have a (possibly large) number of levels, in each level the designer can set a title, subtitle and other script parameters that define the gameplay of that specific level.
I prefer to have it there as script variables so that a designer can change it directly without having to modify the script itself.

Task:
I want to create a 'Level select' page where i use some of those script parameters in the list entry of each level.

Question:
HOW can i access those script parameters without actually loading the level completely?

I understand that i can just preload and instantiate every single scene, at which point i could call a function in the scene's script that would transfer the necessary data to a global variable, but i guess that would waste a lot of memory / computation to loop through all the levels?
Especially when i might have hundreds?

TL;DR;
Is there a way to access a scene file's export parameter without 'loading' it completely into memory / what's the most efficient way to do this for hundreds of files?

asked Sep 8, 2018 in Engine by Larzans (55 points)

I will go with the load packedScene option for now, but i am not sure if this would work for hundreds of scenes.

So if someone could point out exactly if this would be good for hundreds of items?!

In the future i will probably just keep using the packedScene option but execute it only after the first installation or some update of level files and store the parsed data in an extra file, so that the parsing itself won't happen on every startup...

2 Answers

+1 vote
Best answer

I would recommend reconsidering how to organize this data. This problem is likely a nudge to change the design.

But if you really must get at it, packed scenes have a _bundled dictionary to them, which does contain those values, but it isn't clear exactly the pattern to the alignment of the "names" and "variants" arrays inside it.

http://docs.godotengine.org/en/3.0/classes/class_packedscene.html

Another approach, a bit hacky, but again if you really need to salvage something... TSCN files have those saved export values in plain text, it can be loaded and parsed out.

For a scene of my that has the following:

[node name="Node2D" type="Node2D"]
script = ExtResource( 1 )
_sections_unfolded = [ "Script" ]
number = 12

I have some code roughly like:

extends Node2D

func _ready():

    var tscn = preload("res://Node2D.tscn")

    for item in tscn._bundled: 
        # Not sure about the alignments.
        print(item, " ", tscn._bundled[item]) 


    # Needs casting.
    var value = int(get_tscn_value("res://Node2D.tscn", "number")) 

    print(value) 


func get_tscn_value(path : String, var_name : String) -> String:

    var f = File.new()

    if(f.open(path, File.READ) == OK):

        while(not f.eof_reached()):
            var line = f.get_line()

            if(line.find(var_name + " = ") > -1):
                return line.split("= ")[1]

    return ""
answered Sep 8, 2018 by avencherus (4,833 points)
selected Sep 9, 2018 by Larzans

My main concern was if i am using a lot of resources by loading a packedScene, but after making a few tests it looks like this is probably not an issue.

If I run into problems in the future i will have to resolve to parsing the text file, of which i wanted to get around, but it will probably have the least impact resource-wise.

So, after doing some tests, i will definately go with just parsing the tscn files, as they have a very simple structure and this way i have very little memory overhead, as the files are small.

With the parameter casting and everything, the parsing of the text files was ~150 times faster than the packedScene variant.

When processing hundreds of files, this is definately a noticable improvement ;p

@Larzans That is interesting, thanks for sharing your benchmarking results.

+2 votes

You can avoid instancing by using state of PackedScene, like this:

extends Node

onready var BahScn = preload("res://Bah.tscn")

func _ready():
    var state = BahScn.get_state()

    for idx in range(0, state.get_node_count() ):
        for propIdx in range(0, state.get_node_property_count(idx) ):
            var pname = state.get_node_property_name(idx, propIdx)
            var pvalue = state.get_node_property_value(idx, propIdx)

            print ( pname + " = " + str(pvalue) )

It prints this for me:

script = [GDScript:1077]
exportedString = valueOfExportedString

There's my exported variable with its value.

answered Sep 8, 2018 by hilfazer (2,154 points)

It looks like the loading does indeed not need much resources, so i will go with this for now, but i'd love to hear someone explain better IF and WHY this is ok and not a big performance problem if done for hundreds of files...

Did you considered using a thread(s) for this task? It could start processing files as soon as application is launched.

If you're new to Godot threads, check out official sample project:
https://godotengine.org/asset-library/asset/144

That is a good idea, but as i want to deploy on mobile i need to keep the resource usage as low as possible.

I actually made a test where i parsed 11 level files a hundred times, and even so the garbage collection and caching kept the memory to a minimum (which might not be the case if it were 1100 different levels) it took one full minute on my desktop pc to parse all the files.

So i think that i will go with the plain file parsing now, the tscn files are very simple and easy to parse.

I am testing the performance right now...

@hilfazer Nice, picked up a little more knowledge from that example. Didn't consider those methods would return values on a non-instanced scene.

Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read How to use this Q&A? before posting your first questions.