Assume we have a main menu with multiple buttons, and we have a text file which contains data behind button 1, button 2, etc.... The text data is loaded into an array of dictionaries.
We are loading a scene like this:
# Global singleton provides a data set, i.e.
var mydata = [{...}, { ... }, etc...]
# On the main menu scene we have buttons
func _on_Button1_pressed():
get_tree().change_scene("res://DataList.tscn")
newscene???.send_data(mydata[0]) # ????
How do I pass the preloaded dictionary object associated with Button1 into the scene that is about to appear?
I understand that change_scene() is "deferred" so we cant just straight away call a "setter" function for this scene, or can we? I am still learning godot so I am a bit lost as to know how to do this
Note that I am expecting to "build up" and "rebuild" the new scene when the user presses the button, and "teardown" when a user exits the scene.
# Global singleton provides a data set, i.e.
If you already have an autoload (singleton), I would put there the information you want to give the other scene, and have the other scene read it.
I understand that change_scene() is "deferred" so we cant just straight away call a "setter" function for this scene, or can we?
Correct, you can't. For the instant where the new scene is loaded the current one is already unloaded, so it can't really call a method on the new one.
Unless you take control of the process. See Change scenes manually.
Related
I'm new to coding and Godot and I need help with changing the value of my progress bar from the script inside my button node. The error I get is 'get_node: (Node not found: "/root/ProgressBar" (absolute path attempted from "/root/Node2D/Button").)' I want every time the button is '_pressed()' to increment health by 10 which would change 'value' in ProgressBar to that value.
extends Button
var health= 0
func _ready():
pass
func _process(delta):
pass
func _pressed():
health += 10
print(health)
get_node("/root/ProgressBar").set_value(health)
The path /root/ProgressBar is unlikely to be correct. Usually, as a child of root, you would have the current scene, and as child of that something else (So the path could be something like /root/MySceneName/ProgressBar). And relative paths work too… Usually, we would solve this by means of figuring out the correct path…
But things have changed (For Godot 3.5 and newer). What you are going to do is open the script while the scene is open, and then drag - with CTRL pressed - the Control you want to access (i.e. the ProgressBar) on script, outside of any method (func).
By doing that Godot will generate a line of code that looks something like this:
onready var progress_bar: ProgressBar = $"Path/To/ProgressBar"
In other words, Godot will figure out the path you need, and set up a variable you can use. Godot will set the variable to the Node you dragged as part of the initialization of you script. Assuming all goes well.
You might get an error saying that the script is not used in the scene. Perhaps you have the wrong script open, or you use the script in multiple places and Godot got confused… Either way, close the script and open it from the scene, and it should work.
And, of course, you can use the variable to set its properties, for example:
progress_bar.value = health
You might also be interested in scene unique names. On the contextual menu of the Nodes in the Scene panel you can select "% Access As Scene Unique Name", this will allow you to access the Node with this syntax:
$"%ProgressBar"
Regardless of where it is in the scene tree. Except, be aware that the name must be unique for that scene (you won't be able to do that with two Nodes that have the same name).
This has the advantage that if later you change where the Node is in the scene tree (for example to add some Container to organize your Controls), you don't have to update the path to where you use it. Instead it should continue to work, as long as you don't change the name.
Let that be yet another reason to pick good Node names.
And yes, dragging the Node to the script also works with these.
The problem with your path is, that your missing the name of your main node in it.
So let's say your node tree looks like this:
MainNode
ProgressBar
Button
To get the ProgressBar from anywhere you would need to use get_node("/root/MainNode/ProgressBar")
I have put nodes in a group. And I have put a func on the scene script to make changes to the nodes in group. In that func,I made the nodes to queue free and stuffs like that. But when I change scene and come back to the previous scene,the queued free is back again,it is not queued free anymore. How do I make it not change even after changing scene?
Why information is lost
When you change the current scene with change_scene or change_scene_to the current scene is unloaded, and the new one is loaded and instanced.
Perhaps it helps to conceptualize that the instance of the scene is not the same as the scene in storage. So what happens is like opening a file in an editor, modifying and not saving it.
Alright, that is one solution if you really want to go that route: you can save a scene.
First create a PackedScene, for example:
var packed_scene := PackedScene.new()
Then tell it to pack the root node of the scene, for example:
packed_scene.pack(self)
Note: any Nodes that don't have their owner property set to the Node you passed here will be ignored. And no, add_child does not set the owner property.
If you have been paying attention, you know you can give a PackedScene to change_scene_to... But how do you keep it around when the scene changes?
There are a few ways. And the thing is: if we can keep information around, we might not need to save the scene.
Keep things around in autoloads
The scripts and scene you add to your autoloads (singleton) in project settings stay there (unless you explicitly remove them) even when you change scene.
Thus, a very simple way to keep information around is to have a script with some variables that you can write and read from anywhere in your project. And you can do that with an autoload.
Keep things around in resources
Godot caches resources. When you load the same resource in multiple places, you actually get the same object.
Well, you know you can create a custom Resource class. To do that, on the context menu of the FileSystem panel select "New Script", and in the script make a class that extends Resource, and give it a class_name.
Then you can create resources of that Resource from the context menu of the FielSystem panel by selecting "New Resource" and picking your Resource class when asked.
Everywhere you load one of those, you are going to get the same object. Yes, even if it is in another scene. So you can add variables to your Resource class, write them in one scene and read them in another.
I explain a more concrete example elsewhere.
Keep things around in storage
You can write to a file and read from a file. For example you can do this:
# save
var file = File.new()
file.open("user://a.sav", File.WRITE)
file.store_pascal_string(var2str(data))
file.close()
# load
file.open("user://a.sav", File.READ)
data = str2var(file.get_pascal_string())
file.close()
Or, if what you want to store is a Resource (be it a PackedScene, or some other Resource class including a custom one), you can use ResourceSaver and ResourceLoader:
# save
ResourceSaver.save("user://a.tres", resource)
# load
resource = ResourceSaver.load("user://a.tres")
Of course, you can also load resources with load, or preload. You may also be interested in Background Loading.
By the way, if you are going to save player progress, having all the player progress data in a single object makes sense. And if you are going to have all the data you are keeping all the player progress data in a single place, it makes sense it would be somewhere it is accesible all the time and stays around even when you change scenes, thus: put the player progress data in an autoload. Bonus: you can put the functions to save and load to a file there too.
Don't change scenes
Since changing scenes brings problems - consider another solution: don't.
You can load an scene:
var packed_scene := load("res://scene.tscn") as PackedScene
Then make an instance of it:
var scene := packed_scene.instance()
Then add it to your current scene tree:
add_child(scene)
Yes, it is a Node! It also means you can…
scene.queue_free()
Or you can simply remove it with remove_child which does not free it, so you can add it back later.
So you would be in control of what gets loaded or unloaded and when. Which is useful to keep stuff around (e.g. the UI, the player character, etc...). A drawback of doing it the way I describe here is get_tree().current_scene would not be useful to you anymore. See also Change scenes manually.
Recently I would like to bring a dialog in front of it's father window(always do, no matter its father window gets focus or not) but not topmost. In other words, I only want it cover its father window but not other applications' window.
I've tried:
// this covers other windows
SetWindowPos(&wndTopMost, rectPos.left, rectPos.top, width, height, SWP_SHOWWINDOW);
// this doesn't work
SetWindowPos(&GetParentFrame()->wndTop, rectPos.left, rectPos.top, width, height, SWP_SHOWWINDOW);
Any ideas?
Xiangguan Zheng, in your original post you stated:
I only want it cover its father window but not other applications' window.
Later in your comment you mentioned:
I want do edit the father dialog by clicking the buttons on the child dialog.
These are two completely different requirements.
If you want a second dialog to be contained in the parent dialog area, you can achieve this by setting WS_CHILD style to the second dialog and calling Create. this will show the child dialog over the top of the parent and keep it within the parent area.
to fulfill the second requirement, you will have to pass the pointer to the parent dialog as the second parameter in Create call, or pass it when the child dialog is instantiated. Either way, you will have to save that pointer in the child dialog and use it to either call the public function exposed by the parent or use the pointer to send/post messaged to the parent.
I want to add a new View to a Layout dynamically by layout.addView(view) method. The new View is not supposed to be visible, but I want it's Display List to be created so it doesn't have to redraw (call onDraw method) itself when I decide to show it (by animation, for example fade in).
I run this code:
customView = new CustomView(this.getContext());
customView .setAlpha(0.0f);
this.addView(customView ); // 'this' is RelativeLayout instance in this case
onDraw method gets called for customView and everything is fine. However, when I change anything in my layout (push a button, scroll, anything that invalidates layout), the onDraw method for my customView is called second time. After that, it isn't called any more if I don't invalidate my customView (correct behaviour).
I don't know why Android behaves this way. I want it to create customView, call onDraw, create a Display List for it, and not call onDraw any more until I invalidate my view. To sum up, onDraw is supposed to be called once.
And it has nothing to do with initial invisibility of customView, if alpha is set to 0.5f, behaviour is the same.
My question is, how to make Android call onDraw only once?
And if Android really has to call onDraw twice, then what should I do to enforce it to do it in some code right after this.addView(view); without setting up any timers because THAT would be totally ugly.
The behavior you are describing is ok and is a part of the android framework for view object -
Drawing
Drawing is handled by walking the tree and rendering each view that
intersects the invalid region. Because the tree is traversed in-order,
this means that parents will draw before (i.e., behind) their
children, with siblings drawn in the order they appear in the tree. If
you set a background drawable for a View, then the View will draw it
for you before calling back to its onDraw() method. Note that the
framework will not draw views that are not in the invalid region. To
force a view to draw, call invalidate()
(Taken from the official google android API docs).
Meaning your custom view is contained by the same view that contains the button\scrollbar etc. If you don't want it to be rendered everytime the onDraw method is called for the subtree your view resides in you can set the view's boolean flag to false - use setWillNotDraw() to do that. (you should place it on the activity's onCreate in order to render the view set this flag to false (which is also the default) and use invalidate() whenever you want to render the view).You can read the official google docs for further information.
Still working on learning here ... I'm trying to make an application window (stage) into which I can call child windows. The parent naturally comes with minimize, maximize and close (x) buttons, but when I add a child window I can't move or resize the child, and it does not have the standard three buttons.
Here's code I've been toying with:
// Stage ventasStage = new Stage(); // originally the child was stand alone and had the standard 3 buttons
AnchorPane ventas = (AnchorPane) FXMLLoader.load(Punto_de_Venta.class.getResource("VentasGUI.fxml"));
// Scene ventasScene = new Scene(ventas); //"stage" and "scene" removed to add "getChildren"
home.getChildren().add(ventas);
The getChildren gets my new window to be part of the parent scene, but I cannot get the 3 standard buttons. I assume the buttons are added to a Stage and NOT to an AnchorPane (which is what getChildren is getting here) but getChildren can't be used with a Stage, right? So how do I make a parent with interchangeable children where each child is moveable, resizable and has the standard three buttons (minimize, maximize and close)?
Three buttons correspond to a standart Window (it is provided by OS), and it is Stage. Scene - is an object, which corresponds to scene graph and is a propeerty of stage. So use stage.setScene(..) to set Scene to Stage. Stene has a root node (usually, some kind of layout). And it seems for me, that you should use Scene.setRoot(...) method.
BTW, about Stage: you can use stage.init...() to use different decoration schemas, and different types of modality.