Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a method to create a new inherited scene from code #3907

Open
VanDeiMin opened this issue Feb 1, 2022 · 20 comments · May be fixed by godotengine/godot#64217
Open

Add a method to create a new inherited scene from code #3907

VanDeiMin opened this issue Feb 1, 2022 · 20 comments · May be fixed by godotengine/godot#64217

Comments

@VanDeiMin
Copy link

VanDeiMin commented Feb 1, 2022

Describe the project you are working on

2D isometric car race game. currently in Godot 3.4.2

Describe the problem or limitation you are having in your project

In my game I will have to add about 1000-2000 vehicles of 200-400 types (for now i have 256 in 42 types). Each vehicle must be unique beacuse only one instance can be in game scene at once. I have base CarTemplate scene. Then I create default CarType which inherits from CarTemplate. Then I make specific Car scenes (usualy 6- 10) which inherit from CarType and have its unique features. Making one CarType by hand takes me about half an hour.

I designed EditorPlugin which helps with tedious process of making new Car scenes. It takes assets from named folders and creates nessesery Resources (e.g. SpriteFrames, simple scripts inherited from CarTemplate.gd script I have, etc.) and then ataches them to corresponding Car scenes.

Unfortunately whole proccess can not be fully automatic. Car scenes must be made by hand if I want to use benefits of inheritance... It takes time and makes a risk of typing mistakes.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

With ability to create a new inherited scene directly from GDScript, it would be possible to fully automate process of creating inherited scenes inside editor and thus could save a lot of developer's time.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

It could be a new Method added to PackedScene.

The code in GDScript should allow something like:

var inheriting_scene : PackedScene = load("res://InheritingScene.tscn")
var inherited_scene : PackedScene = inheriting_scene.create_new_inherited_scene()

If this enhancement will not be used often, can it be worked around with a few lines of script?

There is no code for this. Only tedious clicking and typing in the editor.

Is there a reason why this should be core and not an add-on in the asset library?

The proposal looks like core to me. Also I suppose change like that is imposible to do whitout changing base engine code and recompiling it.

@Calinou Calinou changed the title Create a new inherited scene from GDScript Add a method to create a new inherited scene from code Feb 2, 2022
@petterthowsen
Copy link

I also want this for a open world terrain addon. Need it for generating a bunch of scenes for each terrain tile (or "chunk").

For the time being, I assume it's possible to write the text for the .tscn file? I actually did something like that for a previous iteration of this project.

@nonunknown
Copy link

nonunknown commented Feb 12, 2022

I could not understand the proposal, whats the difference from a instanced scene to a inherited one, when instanced via code?

@Zireael07
Copy link

Currently you can't create a new scene that inherits from something else via code. You can only instance scenes that you already set up manually in editor.

@nonunknown
Copy link

but in the end its the same thing, look:

var scene = load(existing_scene_path)
add_child(scene)
# do some code here to modify the scene
#save the scene somewhere in res://

Done you have a modified scene from the chosen one.

@Zireael07
Copy link

Aah, that is what you mean. Yes, that's a solution for the OP

@nonunknown
Copy link

nonunknown commented Feb 13, 2022

its because, when you create a new inherited scene in the editor, in the backend you're just duplicating the scene, the fact that you cant modify the original nodes, its the editor (front-end) decision, but if you load this inherited scene via code and modify it, its ok. I'm not sure about this I've said, but @Calinou can confirm I think.

In case the statement above isnt true, I can try to implement it.

@lentsius-bark
Copy link

If the greyed out nodes behave the same in a scene duplicated that way the same way as they do in an inherited scene, this would indeed be a solution if not, and the inheritance is lost then it's not a functional workaround.

@Zireael07
Copy link

@lentsius-bark: If you're doing it from code, there is no such thing as the 'greyed out node' since that's only an editor display convention.

I've been doing some scene saving recently and the only thing the code cares about is the 'owner', and you can't change the owner on other instanced scenes, but that's a matter for the 'enable editable children in code' proposal, not this one.

@VanDeiMin
Copy link
Author

VanDeiMin commented Feb 14, 2022

but in the end its the same thing, look:

var scene = load(existing_scene_path)
add_child(scene)
# do some code here to modify the scene
#save the scene somewhere in res://

Done you have a modified scene from the chosen one.

I think Your solution brakes the inheritance . I made a simple scene :
InheritanceIssue.zip

which tries to make new inherited scene from code. Without success.
Plugin makes new scene but with no inheritance.

Please copy it and use "SaveScene" button in 2D editor, and analize results.
If You want fast reload the plugin, button for that is at top right corner of the editor.

Here is the main part of code I used:

   var _load_path: String = "res://Inheritance.tscn"
   var _loaded_scene: PackedScene = load(_load_path)
   var _root_node: Node = _loaded_scene.instance(1)
   var _new_node: Node2D = Node2D.new()
   _new_node.set_name("NewNode2D")
   _root_node.add_child(_new_node)
   _new_node.set_owner(_root_node)
   var _pck: int = _loaded_scene.pack(_root_node)
   var _save_path: String = "res://NewInherited.tscn"
   var _sv: int = ResourceSaver.save(_save_path, _loaded_scene)
   _root_node.free()

I still hope the problem is made only by my lack of knowledge.
I found that GEN_EDIT_STATE must be ("1") GEN_EDIT_STATE_INSTANCE if I want to make changes in instanced scene but I don't know why.

@TheDoorworlds
Copy link

Very much looking forward to progress on this. I want to be able to create a new inherited scene from code which will allow the automation of certain gamedev processes, like creating new characters. I'm working on a plugin to procedurally generate new characters for a game, but would prefer to have the generated scenes have inheritance set up from a base scene that will propagate any changes to all characters so that I'm not having to make the changes on every scene individually. I can probably automate something like that, but it would be much smoother and easier to do so via inheritance.

Thanks in advance for your work!

@nathanfranke
Copy link
Contributor

nathanfranke commented Aug 18, 2022

Edit: Updated, but only difference is the "version" property. Important though!

Godot 3.x:

func create_inherited_scene(inherits: PackedScene, root_name := "Scene") -> PackedScene:
	var scene := PackedScene.new()
	scene._bundled = { "names": [root_name], "variants": [inherits], "node_count": 1, "nodes": [-1, -1, 2147483647, 0, -1, 0, 0], "conn_count": 0, "conns": [], "node_paths": [], "editable_instances": [], "base_scene": 0, "version": 2 }
	return scene

Godot 4.0:

func create_inherited_scene(inherits: PackedScene, root_name := "Scene") -> PackedScene:
	var scene := PackedScene.new()
	scene._bundled = { "names": [root_name], "variants": [inherits], "node_count": 1, "nodes": [-1, -1, 2147483647, 0, -1, 0, 0], "conn_count": 0, "conns": [], "node_paths": [], "editable_instances": [], "base_scene": 0, "version": 3 }
	return scene

Future: The same dictionary probably won't work in a few years. To get a new dictionary, create your inherited scene in the editor, then print(load("res://my.tscn")._bundled) (also noted below).

@Zireael07
Copy link

Nice trick, should be documented somewhere. Also what is that magic number in nodes?

@nathanfranke
Copy link
Contributor

No idea- to get this dictionary I created an inherited scene in the editor and then print(load("x.tscn")._bundled).

@Reneator
Copy link

I personally would be in need/want for this.

I have certain ingame-objects saved as scenes (Items, Equipment, Armor etc.) and i have hundreds of them. These are ordered in tiers. So when i now want to create a new tier, i have to manually duplicate those scenes of the previous tier and rename them.

I tried to write a script that loads items/equipment of a certain tier, changes some values and saves them as a higher tier item, with a new file-name (sword_t1 -> sword_t2). It works at it's core, but i loose my connection to the core-scene its inheriting from, which defines certain defualt values i want to be able to easily change for all inheriting scenes, by just changing it in this parent scene.

I found an idea of how to make it work, by using a different kind of instance, but this only works for tool scripts:

https://godotengine.org/qa/120127/how-to-edit-inherited-packed-scenes

The problem with this is that it requires to run scripts in tool, but that still doesnt work and creates a scene without the inheritance connection:

What i want:
(the .tscn file)
image

what it does:

image

In the image all those ext_resource are created because it doesnt inherit anymore and the instance=ExtResource( %id ) is missing which signifies the inheritance

The initial proposal feels like it could help me make it possible for me to duplicate a scene and it's still an inherited scene. Even if i would have to do a workaround like:

  • get the scene the node is inheriting from
  • create a new inheriting scene
  • copy all values from the original scene i want to duplicate into the new inheriting scene
  • save to file-system

@VanDeiMin
Copy link
Author

VanDeiMin commented Oct 31, 2022

Hi! I've tried Your way and it works for me. I added line for using root node name of inherited scene. It's useful if You don't want to change root node name in inheriting scene.
Anyway Big Thanks for the Solution!!!

func create_inherited_scene(_inherits: PackedScene, _root_name: String = "") -> PackedScene:
  if(_root_name == ""):
  	_root_name = _inherits._bundled["names"][0];
  var scene := PackedScene.new();
  scene._bundled = {"base_scene": 0, "conn_count": 0, "conns": [], "editable_instances": [], 
  				"names": [_root_name], "node_count": 1, "node_paths": [], 
  				"nodes": [-1, -1, 2147483647, 0, -1, 0, 0], 
  				"variants": [_inherits], "version": 2};
  return scene;

@mrkdji
Copy link

mrkdji commented Mar 1, 2023

I managed to find a workaround for this by creating an inherited scene from the editor, then duplicating it from code and saving the duplicate as a new .tscn file. The duplicate scene maintains inheritance.

tool
extends EditorScript

const INHERITED_SCENE = preload("res://test/Inherited.tscn")

func _run():

	var new_scene = INHERITED_SCENE.duplicate();
	var instance = new_scene.instance(PackedScene.GEN_EDIT_STATE_INSTANCE);
	instance.name = "AnotherInherited";
	new_scene.pack(instance);
	
	ResourceSaver.save("test/AnotherInherited.tscn", new_scene);

Anyway I second the proposal for ease of use and moreover this workaround doesn't solve this use case

@TheDoorworlds
Copy link

I managed to find a workaround for this by creating an inherited scene from the editor, then duplicating it from code and saving the duplicate as a new .tscn file. The duplicate scene maintains inheritance.

tool
extends EditorScript

const INHERITED_SCENE = preload("res://test/Inherited.tscn")

func _run():

	var new_scene = INHERITED_SCENE.duplicate();
	var instance = new_scene.instance(PackedScene.GEN_EDIT_STATE_INSTANCE);
	instance.name = "AnotherInherited";
	new_scene.pack(instance);
	
	ResourceSaver.save("test/AnotherInherited.tscn", new_scene);

Anyway I second the proposal for ease of use and moreover this workaround doesn't solve this use case

This is the same approach I take for an addon I've made, but it really is inelegant and comes with its own set of problems in my experience. I've run into problems where making changes to the new .tscn file after it's been successfully created breaks the inheritance. As it stands, I'm going to have to verify and/or edit the .tscn file's content directly to make this work, which I'd really rather not do because that's so error prone.

Here's hoping the pull request gets reviewed and approved soon!

@ryevdokimov
Copy link

I submitted a PR that should work: godotengine/godot#90057

Would appreciate a review.

@Sythelux
Copy link

I had a similar Problem when writing a custom EditorScenePostImportPlugin so I made a workaround in C#.
Until the referenced pull request is merged I'll put the code here for anyone, who might need more input solving it themselves. (works with Godot 4.3)

    public override void _PostProcess(Node scene)
    {
        CreateNewInheritedScene(scene);
    }

    // scenePath is the path of the scene file (fbx, gltf, tscn) that is supposedly inherited into the scene test.tscn
    void CreateNewInheritedScene(Node sceneNode)
    {
        PackedScene scene = new PackedScene();
        scene._Bundled = new Dictionary
        {
            { "base_scene", 0 },
            { "conn_count", 0 },
            { "conns", new Array() },
            { "editable_instances", new Array() },
            { "names", new Array(new[] { sceneNode.Name }) },
            { "node_count", 1 }, { "node_paths", new Array() },
            { "nodes", new Array(new[] { -1, -1, 2147483647, 0, -1, 0, 0 }.Select(i => Variant.From(i))) },
            { "variants", new Array(new[] { ResourceLoader.Load<PackedScene>(scenePath) }) },
            { "version", 3 }
        };
        ResourceSaver.Save(scene, "res://test.tscn");
    }

@McZazz
Copy link

McZazz commented Aug 26, 2024

Last January/Feb I was able to get the equivalent of "right click > new inherited scene" on glb files by using the following:

EditorInterface.open_scene_from_path(path_to_glb)
EditorInterface.save_all_scenes()

I could run through an arbitrary folder structure with glbs, images, and json with the material info and was able to hack together the little text files Godot likes to use and it all worked.

But now, in 4.3, once it hits the first glb it finds, the editor stops executing the script (an anti pattern) and shows the popup you would get if you were doing this manually.

But when I was looking into this, I did stumble across the property that Sythelux mentioned, and skipped it because it looks too complicated. Probably will have to revisit it now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.