0 votes

I'm modifying an object's position using set_pos() (also tried set_global_pos()) and a weird thing is happening.
The game I'm doing has random encounters and what I'm trying to do is put the player in the same position he was before the encounter. The way I'm doing it is that right before changing scenes to the battle area I save the player's position to a singleton node and after coming back I set the player's position to that saved position.

# return to overworld code
print("pos before set_pos(): ", player.get_global_pos())
player.set_global_pos(Stats._get_current_position()) 
print("pos after setting pos: ", player.get_global_pos())
# have tried this with set_pos() instead just in case but same behavior

This code indeed prints the position that the player is set to in the scene editor (200, 242) and then prints whatever position I was before the encounter (I always move near 0, 0 to test this and it does print the correct value) but the player in the game is positioned at (200, 242).
No clue how to tackle this. At first, I thought that maybe the player object was being created after changing the position but wouldn't that give an error for trying to do set_pos() on a null variable?

[EDIT]
I'm pretty sure I figured out why it isn't working, but not how I can fix it. I'll explain.

The way I have set up the project is that the main scene is a Node called Game. This node reads from a file which scene (level/area/map, call it what you want) has to be instanced, and instances it. These scenes all have a Player Overworld Node2D instanced in the scene editor.

The way I change scenes is that I delete all of Game's children with a for loop and then instantiate the target scene. The reason I'm not using change_scene() or change_scene_to() is because doing so makes the Game object disappear. So because they way I've set it up is that the Game Node is always there, I can't use those functions and had to come up with another way. And here is (very likely) the problem:

for child in get_children():
        child.queue_free()

Since I'm using queue_free(), the player variable gotten from _get_player_overworld() isn't the one in the new scene, but the one on the previous scene, still in the queue waiting to be freed. If I use free() Godot itself doesn't give any type of error, but Windows does give the "A problem caused the program to stop working correctly." prompt.

I could probably solve this with a few flags but that's not really a solution. I guess the follow-up/relevant question is how to replace a Node's children rather than the whole scene, which is what change_scene() and change_scene_to() do.

in Engine by (22 points)
edited by

did you make player instance from script?
or is the player node in scene tree in editor?

Player Overworld is a node in the scene editor assigned to the player variable in script like this:

player = _get_player_overworld()

func _get_player(): # get the player node
    var p = get_tree().get_nodes_in_group("Player Overworld")
    return p[0]

Where the Player Overworld node is part of "Player Overworld" group. There are probably better ways to find it, but this works as intended. If player has a variable var test = 5 I can do

player = _get_player_overworld()
print(player.test)

succesfully.

2 Answers

+1 vote
Best answer

So if the player is a child of the your game scene and gets freed. then the new scene doesn't reference to the player character of you singleton anymore.

what you have to do it this is the case
two options:

  1. dont free the player, just keep a reference to it in the singleton and remove it from the old scene tree.
    (freeing is different to removing child. freeing erases the data, removing just removes it from the tree but the data form the object is still there (if there is a reference to it))
    and than when the new scene replace the player node with the one in the singleton.

  2. don't reference the player node to your singleton, instead just the values you need: heart, bullets, location...
    than you can remove the player completely and when loading a new scene just set the values in the _ready function.

My approach for a lvl based setup with the same player

  • make a base node for the lvl holder with a function: remove_current_lvl() and load_lvl(pathToScene) remove automatically puts the player node in the singleton and frees the rest of the scene. and load_lvl load the next lvl and than assigns the player to the new scene.
  • To make it easy for setting the player, make sure each lvl scene you make has a function add_player(player_node) so that you can determine for each scene where you want to add the player as a child node.

this is how changing lvl would look:

remove_current_lvl() #also adds the player to the singleton

load_lvl("res://lvls/level10")  # also ads the lvl as a child

# finally adds the player as a child 
# and maybe does some additional preparation
get_children()[0].add_player(_get_player_overworld()) 

I hope this helps to some extend. To be honest I'm not 100% sure what kind of game you are working on but I hope I guessed in the right direction and you get something from it.

by (333 points)
selected by

Thanks a million dude, this is definitely putting me in the right direction. Will update when I get it working.

+1 vote

The problem is that you are mixing procedural and object oriented programming.

On the one hand you have a Game object with child objects and on the other hand you have global variables (singletons and groups).

By removing child nodes and attaching new ones to a Game node you are chosing the object oriented approach, but by acquiring the player from the "Player Overworld"-group you are using procedural techniques.

I would recommend going the object oriented way and stop using the group. Instead put everything that is meant to be a child node while the game is in "overworld"-mode into a new overworld-scene, so that your Game node has one overworld-child. This node should contain a function that returns the player node. Since the overworld game mode knows where the player is attached in the tree it doesn't need to use groups and can directly access the correct path.
Structure the combat mode equally, so that there is a difference between get_node("Overworld").get_player() and get_node("Combat").get_player().

And while we are at it: instead of copying the player's global position into a variable in the Game node you could just move the player node out of the "overworld"-tree and store it in a variable inside the Game node. When the player returns to the overworld you would just have to reattach it to the overworld-tree.

by (1,122 points)

Those are some nice pointers dude, appreciate the input.

The reason why I'm using groups is because since the hierarchy looks like this:
-> Game
--> [Area]
---> Player Overworld
Since the [Area] node is between the Game and Player nodes, and since it is a different node for each area, I was having trouble coming up with ways to 'always' finding the player. Even while doing it, using groups felt kind of like a dirty solution and probably not the way the functionality is supposed to be used but in the end, it worked out fine I guess.

I also tried the "remove player and re-insert it" way but couldn't get it working properly. I was getting "Node already has a parent" error when trying to re-insert it even when print(player.get_parent()) in the line right above would print null. Not sure if it was a Godot bug or my own fault but I couldn't get it to work that way.

Still, thanks for the answer I think it'll help me a lot while moving forward with this.

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.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to webmaster@godotengine.org with your username.