How to defer custom action in scene startup? - godot

I'd like to access the root node of a scene, but even in its _ready() lifecycle method I get a runtime error telling the parent node is busy:
extends Node2D
func _ready():
self.get_tree().get_root().add_child(preload("res://MainMenu.tscn").instance())
# This works:
# self.get_tree().change_scene("res://MainMenu.tscn")
My current script is just changing scene for now. I know I can use this [object NodeTree].change_scene() method or root_node.call_deferred(), but I'd like to be able to observe to when the scene is fully initialized, so I can freely use its node tree.

At the time _ready runs, the parent node is not ready yet (let alone the scene root). Godot has not finished the routine that add children to the parent node. In fact, it just added your current node, the children of your current node, and is running _ready on your current node. After it has done that for your current node and its siblings, it will run _ready on your parent node, and so on, until it reaches the scene root… Then it will be ready.
Lightning_A is correct, inserting yield(get_tree().get_root(), "ready") before manipulating the root will work. yield will halt the execution and schedule it to continue after the given signal. The ready signal in this case. Which happens after _ready completed.
This is how you would use it:
func _ready():
yield(get_tree().get_root(), "ready")
get_tree().get_root().add_child(preload("res://MainMenu.tscn").instance())
Another common solution is to insert yield(get_tree(), "idle_frame") instead. The signal idle_frame happens just before Godot calls _process:
func _ready():
yield(get_tree(), "idle_frame")
get_tree().get_root().add_child(preload("res://MainMenu.tscn").instance())

Does Node's ready signal work for you? i.e. yield(get_tree().get_root(), "ready")

Related

How to invoke a function on unpausing the tree?

Is it possible to invoke a function or connect a signal when the tree is unpaused?
for example I have this setup:
the world script:
extends Node2D
func _ready():
get_tree().paused=true
# do something
get_tree().paused=false
the thing script:
extends Node2D
func _unpaused():
# reset something when the tree is unpaused
is something like this possible?
No, I can't find a signal or virtual method for that.
I'd say use an autoload that controls paused, and make sure everything on the projects that needs paused goes through the autoload. So the autoload can do whatever you need when it changes.

Godot: How to move KinematicBody2D node from one scene to another

I want to move my Player node (KinematicBody2D) from one scene to another. My code successfully moves Node2D node, but fails with KinematicBody2D node.
I have two Location scenes (inheriting from base Location scene) with the following structure:
FirstLocation (inherits Location)
- YSort
- Player
In the base Location scene I have two methods:
func add_player(player: Player):
get_node("YSort").add_child(player)
func remove_player() -> Player:
var player = get_node("YSort/Player")
get_node("YSort").remove_child(player)
return player
In GameWorld scene I store the possible locations inside a dictionary and the moving of the player happens inside change_location() function:
onready var _locations = {
Location.FOO: $CurrentLocation,
Location.BAR: load("res://locations/Bar.tscn").instance(),
}
onready var _current_location = $CurrentLocation
func change_location(location: int):
var player = _current_location.remove_player()
remove_child(_current_location)
_current_location = _locations[location]
add_child(_current_location)
_current_location.add_player(player)
The switching of the location works
The moving of the player also works in case the player is plain Node2D.
But when Player is KinematicBody2D then the game simply crashes, giving me no hint as to what's causing the problem.
The code works without crashing when I comment out the last line:
_current_location.add_player(player)
...but of course then the player simply doesn't get added to the other scene.
I verified that the player does get removed from the scene.
I tested with a dummy scene (which only contains KinematicBody2D as root node and a simple Sprite as a single child) instead of my actual more complex Player scene to make sure it's not related to any other code I might have in my Player scene. Node2D as root works, KinematicBody2D crashes. Must have been a fluke. Tested again and now both work, so there must be something different in my Player object.
I tried adding the Player node as a direct child of Location node (not having a YSort node in the middle) - nope, still crashes.
I tried setting the position/global_position of player before/after adding it to new scene - no difference.
I'm able to create new Player scene instance and add it to new location.
What might it be in KinematicBody2D that prevents me from moving it from scene to scene?
Found a solution, but I don't know why it works.
I was performing the location change as a response to Area2D.body_entered signal. I triggered my own signal "location_change" and then called the change_location() function in another part of the code in response to it.
Adding a tiny timeout (0.00000001 seconds) before doing the location change solved the issue. However I have no idea why, and I'm pretty sure adding timeouts is not a proper way to solve this problem.
I'm having trouble visualizing the situation. However, given that the problem happens when removing and adding a physics body, and that "body_entered" was involved, and that the solution you found was adding a time out…
Sounds like the issue was removing the physics body while Godot was still resolving physics response. And then the solution was to wait until the next frame.
You could wait until the next graphics frame with this:
yield(get_tree(), "idle_frame")
Or until the next physics frame with this:
yield(get_tree(), "physics_frame")
Which would be better than some arbitrary small timeout.
Alternatively, you could also make the signal connection deferred.
If you are connecting from the UI, there will be an "advanced" toggle in the "Connect a Signal to a Method" dialog. Enabling "advanced" toggle will reveal some extra including making the connection deferred.
If you are connecting form code, you can accomplish the same thing by passing the CONNECT_DEFERRED flag.

Why is my game scene getting errors when the main scene is actually the menu in Godot

I am adding a main menu to my game. The thing I am doing is adding them in a scene and then changing the scene to the game scene. However, my game has errors which belong to other scenes that do not have an instance in the existing scene. I get errors such as:
Invalid get index 'HasEntered' (on base: 'null instance').
My entire project is here: https://github.com/Ripple-Studios/Godot-Wild-Jam-36-Game-Ripple-Studios
I would appreciate if someone would help me.
Thanks,
I had a look at the linked code.
What is happening is that the practice of getting nodes of the parent has come back to bite you. I'm talking code that looks like this:
get_parent().get_node(...)
You have an scene (HospitalScene.tscn) instanced in your main scene (MainMenu.tscn), which has code similar to the shown above.
The particular error you refer to comes from code that looks like this:
func _on_Area2D_body_entered(body):
var HospitalPosition = get_parent().get_node("Hospital")
if HospitalPosition.HasEntered == true:
HospitalPosition.isInHospital = false
This code is firing at the start because the Area2D is overlapping an StaticBody.
The code is trying to get a sibling "Hospital" which does not exist in the scene tree. And thus HospitalPosition is null. And trying to access HospitalPosition.HasEntered when HospitalPosition is null results in the error you mention:
Invalid get index 'HasEntered' (on base: 'null instance').
The scene is trying to reach a node outside of itself. And there is no guarantee that the scene will be instanced where such node is available. Thus, in each and every case of get_parent().get_node(...) this could happen.
In fact, when running the game, I get four error that look like this (but with different paths):
E 0:00:01.494 get_node: (Node not found: "Hospital" (relative to "/root/CanvasLayer/ParentSprite/Control").)
<C++ Error> Condition "!node" is true. Returned: nullptr
<C++ Source> scene/main/node.cpp:1325 # get_node()
<Stack Trace> HospitalScene.gd:28 # _on_Area2D_body_entered()
You could use get_node_or_null instead of get_node and check for null.
You could also export NodePath variables instead of hard-coding the paths.
Better decoupling strategies than checking if instances are valid include:
Connect to the signal from outside the scene. It is a common pattern in Godot to call down the scene tree, and signal up the scene tree. See Node communication (the right way).
Connect all the signals through an Autoload (singleton). Which is another common pattern in Godot called a Signal Bus or Event Bus. See Best practices: Godot GDScript - Event Bus.

How can I use queue_free() with Area2D

When an Object enters Area2D I expect an object entering to be removed but nothing happens
I have tried queue_free(area) and Area2d.queue_free()
func _on_Area2D_area_entered(area):
queue_free()
area.queue_free
Like said above nothing happens when an object enters Area2d
Ok. I have made a simple project to test the collision and queue_free() process with regards to an interaction between KinematicBody and Area2D. Basically, the Area2D is the object that detects and signals a collision (KinematicBody can cause a collision, but not detect it). Therefore it is up to Area2D to detect when a BODY has entered (or in this case, when it enters a body), and then it can signal the collision, which then calls the _on_Area2D_body_entered function in the other object, and then executes queue_free() as instructed.
My node setup is as follows:
Node2D
|_Area2D - (SIGNAL ATTACHED - "_on_body_entered()" - CONNECTED TO KINEMATIC BODY)
| |_Sprite
| |_CollisionShape2D
|
|_KinematicBody2D
|_Sprite
|_CollisionShape2D
For my trial, the Area2D object is placed on same horizontal axis, and to the left of the KinematicBody
SCRIPT ATTACHED TO AREA2D:
extends Area2D
func _ready():
pass
func _process(delta):
self.position.x += 1 #So it moves into and collides with other object.
SCRIPT ATTACHED TO KINEMATICBODY:
extends KinematicBody2D
func _ready():
pass
func _on_Area2D_body_entered(body): # This is called when Area2D detects a collision
queue_free()
print("HIT!!!!!") #To doubly confirm the collision is recognized
Unfortunately, it kind of has to be this way. Since Area2D is the only object in this scenario which is capable of detecting and signalling the collision, it cannot be ordered to queue_free() itself, as Godot throws an error when attempting to do so.
Alternatively, however, if you wanted it the other way around - for the KinematicBody to move towards the Area2D object, and delete itself upon impact. simply move the whole delta process from the Area2D script and place it in the KinematicBody script above the _on_Area2D_body_entered(body) and change the self.position.x to -= 1. Leave the signal on the Area2D.

SpriteKit windows do not redraw until "applicationDidFinishLaunching" method quits

I use SpriteKit for Mac OS X (not iOS) to run my programs.
At the end of the "applicationDidFinishLaunching" method of the "AppDelegate"-Class I start all things which are needed for initialization. Some methods do not like to be called from a background-thread like setting window-titles, resizing windows and some other tasks. So all these things are done in the main-thread.
Then we come to my problem: I cannot simply run my main program at the end of the "applicationDidFinishLaunching" method, because when I do so, the "applicationDidFinishLaunching" method does not quit until my main program quits. And my main program does not quit, because it shows some animation on the screen directly after starting the program.
In the case, that the "applicationDidFinishLaunching" method does not quit, SpriteKit does not redraw the window, so my animation runs but I see a white window.
After quitting my program, the "applicationDidFinishLaunching" method quits, too, and I see the last picture of the animation.
So I realized a workaround: I now do the initialization in the "applicationDidFinishLaunching" method and then start a background thread which runs my main program.
The "applicationDidFinishLaunching" quits after starting the background-thread and the window is updated as expected. Everything runs fine with the background-thread doing the animation.
And now the problem, I cound not solve: I need to hide the menu bar, not directly when starting the program, but after some time.
NSMenu.setMenuBarVisible(false)
Doing so is no problem when calling from the main-thread but if I hide the menu-bar from my background thread, then I can hide it once, make it visible once, hide it a second time and when making it visible a second time an exception in the AppDelegate Class stops my program:
Thread 1: EXC_BAD_ACCESS (code=EXC_i386_GPFLT)
My idea to solve this problem, was to post an event, which is handeled by the main-thread. But if I post a keyboard event for example, the event-handling is done within the background-thread, too.
Events like selecting a menu by the user, not programmatically are handeled from the main thread but I did not find a way to post an event which is then handeled in the main thread instead of the thread, which contains the sendEvent-command:
NSApplication.sharedApplication().sendEvent(event!) // Called from background-thread
Has anybody an idea of sending an event which is handeled by the main-thread
or
Running my program completely in the main-thread without having the problem, that the window-content is not drawn at all. This second solution would be my favourite, because there are some more things, which make problems within a background thread.
Perhaps I can start my main program from another method, some time after "applicationDidFinishLaunching" has finished.
Some deeper information to the topic above but still no solution:
I discovered, that there exists a function "performSelectorOnMainThread" which can be called from swift like this:
NSApplication.performSelectorOnMainThread(Selector(myFunctionToCall()), withObject: nil, waitUntilDone: true)
This call compiles, my function is called but in my background thread not on the main thread and an error is dumped:
2015-01-17 20:11:09.142 AudioDatabase[4449:2099588] +[NSApplication (null selector)]: unrecognized selector sent to class 0x7fff7b1d8be0
But execution continues. I was not able to call the function on any other than a few types like NSApplication, NSObject, NSThread like a class function. But I never reached the main loop with this.
Another idea was to use NSInvocation, but when I look in the documentation, only the Objective-C Part appears.
It would help, if it was possible, to simply call a function of mine with or without arguments that runs in the main thread and can do there something.
While running my program in a background thread, I discovered a way, to execute neccessary commands in the main-thread asynchronous. To do so, you have to call:
dispatch_async(dispatch_get_main_queue())
{
// This block runs in the main thread
}
So my question was, so show and hide the menu bar without crashing my program. Here are the finished functions which work, when called from a background-thread:
func m_MenuBarShow ()
{
dispatch_async(dispatch_get_main_queue())
{
NSMenu.setMenuBarVisible(true) // Class func, must be called on the Class (NSMenu) and not on the Instance (NSApp.sharedApp.mainMenu)
}
}
func m_MenuBarHide ()
{
dispatch_async(dispatch_get_main_queue())
{
NSMenu.setMenuBarVisible(false) // Class func
}
}
Please note that there is a small restriction on using this: The block is called asynchronous, that means you have to make sure, that it is finished, until doing something with the result. In the case of showing the menu bar this is no problem. But if you want to do something like opening a file, you must handle this.
I will explain this as an answer to another question of mine. Please have a look at: Open File Dialog crashes in Swift

Resources